| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783678467856786678767886789679067916792679367946795679667976798679968006801680268036804680568066807680868096810681168126813681468156816681768186819682068216822682368246825682668276828682968306831683268336834683568366837683868396840684168426843684468456846684768486849685068516852685368546855685668576858685968606861686268636864686568666867686868696870687168726873687468756876687768786879688068816882688368846885688668876888688968906891689268936894689568966897689868996900690169026903690469056906690769086909691069116912691369146915691669176918691969206921692269236924692569266927692869296930693169326933693469356936693769386939694069416942694369446945694669476948694969506951695269536954695569566957695869596960696169626963696469656966696769686969697069716972697369746975697669776978697969806981698269836984698569866987698869896990699169926993699469956996699769986999700070017002700370047005700670077008700970107011701270137014701570167017701870197020702170227023702470257026702770287029703070317032703370347035703670377038703970407041704270437044704570467047704870497050705170527053705470557056705770587059706070617062706370647065706670677068706970707071707270737074707570767077707870797080708170827083708470857086708770887089709070917092709370947095709670977098709971007101710271037104710571067107710871097110711171127113711471157116711771187119712071217122712371247125712671277128712971307131713271337134713571367137713871397140714171427143714471457146714771487149715071517152715371547155715671577158715971607161716271637164716571667167716871697170717171727173717471757176717771787179718071817182718371847185718671877188718971907191719271937194719571967197719871997200720172027203720472057206720772087209721072117212721372147215721672177218721972207221722272237224722572267227722872297230723172327233723472357236723772387239724072417242724372447245724672477248724972507251725272537254725572567257725872597260726172627263726472657266726772687269727072717272727372747275727672777278727972807281728272837284728572867287728872897290729172927293729472957296729772987299730073017302730373047305730673077308730973107311731273137314731573167317731873197320732173227323732473257326732773287329733073317332733373347335733673377338733973407341734273437344734573467347734873497350735173527353735473557356735773587359736073617362736373647365736673677368736973707371737273737374737573767377737873797380738173827383738473857386738773887389739073917392739373947395739673977398739974007401740274037404740574067407740874097410741174127413741474157416741774187419742074217422742374247425742674277428742974307431743274337434743574367437743874397440744174427443744474457446744774487449745074517452745374547455745674577458745974607461746274637464746574667467746874697470747174727473747474757476747774787479748074817482748374847485748674877488748974907491749274937494749574967497749874997500750175027503750475057506750775087509751075117512751375147515751675177518751975207521752275237524752575267527752875297530753175327533753475357536753775387539754075417542754375447545754675477548754975507551755275537554755575567557755875597560756175627563756475657566756775687569757075717572757375747575757675777578757975807581758275837584758575867587758875897590759175927593759475957596759775987599760076017602760376047605760676077608760976107611761276137614761576167617761876197620762176227623762476257626762776287629763076317632763376347635763676377638763976407641764276437644764576467647764876497650765176527653765476557656765776587659766076617662766376647665766676677668766976707671767276737674767576767677767876797680768176827683768476857686768776887689769076917692769376947695769676977698769977007701770277037704770577067707770877097710771177127713771477157716771777187719772077217722772377247725772677277728772977307731773277337734773577367737773877397740774177427743774477457746774777487749775077517752775377547755775677577758775977607761776277637764776577667767776877697770777177727773777477757776777777787779778077817782778377847785778677877788778977907791779277937794779577967797779877997800780178027803780478057806780778087809781078117812781378147815781678177818781978207821782278237824782578267827782878297830783178327833783478357836783778387839784078417842784378447845784678477848784978507851785278537854785578567857785878597860786178627863786478657866786778687869787078717872787378747875787678777878787978807881788278837884788578867887788878897890789178927893789478957896789778987899790079017902790379047905790679077908790979107911791279137914791579167917791879197920792179227923792479257926792779287929793079317932793379347935793679377938793979407941794279437944794579467947794879497950795179527953795479557956795779587959796079617962796379647965796679677968796979707971797279737974797579767977797879797980798179827983798479857986798779887989799079917992799379947995799679977998799980008001800280038004800580068007800880098010801180128013801480158016801780188019802080218022802380248025802680278028802980308031803280338034803580368037803880398040804180428043804480458046804780488049805080518052805380548055805680578058805980608061806280638064806580668067806880698070807180728073807480758076807780788079808080818082808380848085808680878088808980908091809280938094809580968097809880998100810181028103810481058106810781088109811081118112811381148115811681178118811981208121812281238124812581268127812881298130813181328133813481358136813781388139814081418142814381448145814681478148814981508151815281538154815581568157815881598160816181628163816481658166816781688169817081718172817381748175817681778178817981808181818281838184818581868187818881898190819181928193819481958196819781988199820082018202820382048205820682078208820982108211821282138214821582168217821882198220822182228223822482258226822782288229823082318232823382348235823682378238823982408241824282438244824582468247824882498250825182528253825482558256825782588259826082618262826382648265826682678268826982708271827282738274827582768277827882798280828182828283828482858286828782888289829082918292829382948295829682978298829983008301830283038304830583068307830883098310831183128313831483158316831783188319832083218322832383248325832683278328832983308331833283338334833583368337833883398340834183428343834483458346834783488349835083518352835383548355835683578358835983608361836283638364836583668367836883698370837183728373837483758376837783788379838083818382838383848385838683878388838983908391839283938394839583968397839883998400840184028403840484058406840784088409841084118412841384148415841684178418841984208421842284238424842584268427842884298430843184328433843484358436843784388439844084418442844384448445844684478448844984508451845284538454845584568457845884598460846184628463846484658466846784688469847084718472847384748475847684778478847984808481848284838484848584868487848884898490849184928493849484958496849784988499850085018502850385048505850685078508850985108511851285138514851585168517851885198520852185228523852485258526852785288529853085318532853385348535853685378538853985408541854285438544854585468547854885498550855185528553855485558556855785588559856085618562856385648565856685678568856985708571857285738574857585768577857885798580858185828583858485858586858785888589859085918592859385948595859685978598859986008601860286038604860586068607860886098610861186128613861486158616861786188619862086218622862386248625862686278628862986308631863286338634863586368637863886398640864186428643864486458646864786488649865086518652865386548655865686578658865986608661866286638664866586668667866886698670867186728673867486758676867786788679868086818682868386848685868686878688868986908691869286938694869586968697869886998700870187028703870487058706870787088709871087118712871387148715871687178718871987208721872287238724872587268727872887298730873187328733873487358736873787388739874087418742874387448745874687478748874987508751875287538754875587568757875887598760876187628763876487658766876787688769877087718772877387748775877687778778877987808781878287838784878587868787878887898790879187928793879487958796879787988799880088018802880388048805880688078808880988108811881288138814881588168817881888198820882188228823882488258826882788288829883088318832883388348835883688378838883988408841884288438844884588468847884888498850885188528853885488558856885788588859886088618862886388648865886688678868886988708871887288738874887588768877887888798880888188828883888488858886888788888889889088918892889388948895889688978898889989008901890289038904890589068907890889098910891189128913891489158916891789188919892089218922892389248925892689278928892989308931893289338934893589368937893889398940894189428943894489458946894789488949895089518952895389548955895689578958895989608961896289638964896589668967896889698970897189728973897489758976897789788979898089818982898389848985898689878988898989908991899289938994899589968997899889999000900190029003900490059006900790089009901090119012901390149015901690179018901990209021902290239024902590269027902890299030903190329033903490359036903790389039904090419042904390449045904690479048904990509051905290539054905590569057905890599060906190629063906490659066906790689069907090719072907390749075907690779078907990809081908290839084908590869087908890899090909190929093909490959096909790989099910091019102910391049105910691079108910991109111911291139114911591169117911891199120912191229123912491259126912791289129913091319132913391349135913691379138913991409141914291439144914591469147914891499150915191529153915491559156915791589159916091619162916391649165916691679168916991709171917291739174917591769177917891799180918191829183918491859186918791889189919091919192919391949195919691979198919992009201920292039204920592069207920892099210921192129213921492159216921792189219922092219222922392249225922692279228922992309231923292339234923592369237923892399240924192429243924492459246924792489249925092519252925392549255925692579258925992609261926292639264926592669267926892699270927192729273927492759276927792789279928092819282928392849285928692879288928992909291929292939294929592969297929892999300930193029303930493059306930793089309931093119312931393149315931693179318931993209321932293239324932593269327932893299330933193329333933493359336933793389339934093419342934393449345934693479348934993509351935293539354935593569357935893599360936193629363936493659366936793689369937093719372937393749375937693779378937993809381938293839384938593869387938893899390939193929393939493959396939793989399940094019402940394049405940694079408940994109411941294139414941594169417941894199420942194229423942494259426942794289429943094319432943394349435943694379438943994409441944294439444944594469447944894499450945194529453945494559456945794589459946094619462946394649465946694679468946994709471947294739474947594769477947894799480948194829483948494859486948794889489949094919492949394949495949694979498949995009501950295039504950595069507950895099510951195129513951495159516951795189519952095219522952395249525952695279528952995309531953295339534953595369537953895399540954195429543954495459546954795489549955095519552955395549555955695579558955995609561956295639564956595669567956895699570957195729573957495759576957795789579958095819582958395849585958695879588958995909591959295939594959595969597959895999600960196029603960496059606960796089609961096119612961396149615961696179618961996209621962296239624962596269627962896299630963196329633963496359636963796389639964096419642964396449645964696479648964996509651965296539654965596569657965896599660966196629663966496659666966796689669967096719672967396749675967696779678967996809681968296839684968596869687968896899690969196929693969496959696969796989699970097019702970397049705970697079708970997109711971297139714971597169717971897199720972197229723972497259726972797289729973097319732973397349735973697379738973997409741974297439744974597469747974897499750975197529753975497559756975797589759976097619762976397649765976697679768976997709771977297739774977597769777977897799780978197829783978497859786978797889789979097919792979397949795979697979798979998009801980298039804980598069807980898099810981198129813981498159816981798189819982098219822982398249825982698279828982998309831983298339834983598369837983898399840984198429843984498459846984798489849985098519852985398549855985698579858985998609861986298639864986598669867986898699870987198729873987498759876987798789879988098819882988398849885988698879888988998909891989298939894989598969897989898999900990199029903990499059906990799089909991099119912991399149915991699179918991999209921992299239924992599269927992899299930993199329933993499359936993799389939994099419942994399449945994699479948994999509951995299539954995599569957995899599960996199629963996499659966996799689969997099719972997399749975997699779978997999809981998299839984998599869987998899899990999199929993999499959996999799989999100001000110002100031000410005100061000710008100091001010011100121001310014100151001610017100181001910020100211002210023100241002510026100271002810029100301003110032100331003410035100361003710038100391004010041100421004310044100451004610047100481004910050100511005210053100541005510056100571005810059100601006110062100631006410065100661006710068100691007010071100721007310074100751007610077100781007910080100811008210083100841008510086100871008810089100901009110092100931009410095100961009710098100991010010101101021010310104101051010610107101081010910110101111011210113101141011510116101171011810119101201012110122101231012410125101261012710128101291013010131101321013310134101351013610137101381013910140101411014210143101441014510146101471014810149101501015110152101531015410155101561015710158101591016010161101621016310164101651016610167101681016910170101711017210173101741017510176101771017810179101801018110182101831018410185101861018710188101891019010191101921019310194101951019610197101981019910200102011020210203102041020510206102071020810209102101021110212102131021410215102161021710218102191022010221102221022310224102251022610227102281022910230102311023210233102341023510236102371023810239102401024110242102431024410245102461024710248102491025010251102521025310254102551025610257102581025910260102611026210263102641026510266102671026810269102701027110272102731027410275102761027710278102791028010281102821028310284102851028610287102881028910290102911029210293102941029510296102971029810299103001030110302103031030410305103061030710308103091031010311103121031310314103151031610317103181031910320103211032210323103241032510326103271032810329103301033110332103331033410335103361033710338103391034010341103421034310344103451034610347103481034910350103511035210353103541035510356103571035810359103601036110362103631036410365103661036710368103691037010371103721037310374103751037610377103781037910380103811038210383103841038510386103871038810389103901039110392103931039410395103961039710398103991040010401104021040310404104051040610407104081040910410104111041210413104141041510416104171041810419104201042110422104231042410425104261042710428104291043010431104321043310434104351043610437104381043910440104411044210443104441044510446104471044810449104501045110452104531045410455104561045710458104591046010461104621046310464104651046610467104681046910470104711047210473104741047510476104771047810479104801048110482104831048410485104861048710488104891049010491104921049310494104951049610497104981049910500105011050210503105041050510506105071050810509105101051110512105131051410515105161051710518105191052010521105221052310524105251052610527105281052910530105311053210533105341053510536105371053810539105401054110542105431054410545105461054710548105491055010551105521055310554105551055610557105581055910560105611056210563105641056510566105671056810569105701057110572105731057410575105761057710578105791058010581105821058310584105851058610587105881058910590105911059210593105941059510596105971059810599106001060110602106031060410605106061060710608106091061010611106121061310614106151061610617106181061910620106211062210623106241062510626106271062810629106301063110632106331063410635106361063710638106391064010641106421064310644106451064610647106481064910650106511065210653106541065510656106571065810659106601066110662106631066410665106661066710668106691067010671106721067310674106751067610677106781067910680106811068210683106841068510686106871068810689106901069110692106931069410695106961069710698106991070010701107021070310704107051070610707107081070910710107111071210713107141071510716107171071810719107201072110722107231072410725107261072710728107291073010731107321073310734107351073610737107381073910740107411074210743107441074510746107471074810749107501075110752107531075410755107561075710758107591076010761107621076310764107651076610767107681076910770107711077210773107741077510776107771077810779107801078110782107831078410785107861078710788107891079010791107921079310794107951079610797107981079910800108011080210803108041080510806108071080810809108101081110812108131081410815108161081710818108191082010821108221082310824108251082610827108281082910830108311083210833108341083510836108371083810839108401084110842108431084410845108461084710848108491085010851108521085310854108551085610857108581085910860108611086210863108641086510866108671086810869108701087110872108731087410875108761087710878108791088010881108821088310884108851088610887108881088910890108911089210893108941089510896108971089810899109001090110902109031090410905109061090710908109091091010911109121091310914109151091610917109181091910920109211092210923109241092510926109271092810929109301093110932109331093410935109361093710938109391094010941109421094310944109451094610947109481094910950109511095210953109541095510956109571095810959109601096110962109631096410965109661096710968109691097010971109721097310974109751097610977109781097910980109811098210983109841098510986109871098810989109901099110992109931099410995109961099710998109991100011001110021100311004110051100611007110081100911010110111101211013110141101511016110171101811019110201102111022110231102411025110261102711028110291103011031110321103311034110351103611037110381103911040110411104211043110441104511046110471104811049110501105111052110531105411055110561105711058110591106011061110621106311064110651106611067110681106911070110711107211073110741107511076110771107811079110801108111082110831108411085110861108711088110891109011091110921109311094110951109611097110981109911100111011110211103111041110511106111071110811109111101111111112111131111411115111161111711118111191112011121111221112311124111251112611127111281112911130111311113211133111341113511136111371113811139111401114111142111431114411145111461114711148111491115011151111521115311154111551115611157111581115911160111611116211163111641116511166111671116811169111701117111172111731117411175111761117711178111791118011181111821118311184111851118611187111881118911190111911119211193111941119511196111971119811199112001120111202112031120411205112061120711208112091121011211112121121311214112151121611217112181121911220112211122211223112241122511226112271122811229112301123111232112331123411235112361123711238112391124011241112421124311244112451124611247112481124911250112511125211253112541125511256112571125811259112601126111262112631126411265112661126711268112691127011271112721127311274112751127611277112781127911280112811128211283112841128511286112871128811289112901129111292112931129411295112961129711298112991130011301113021130311304113051130611307113081130911310113111131211313113141131511316113171131811319113201132111322113231132411325113261132711328113291133011331113321133311334113351133611337113381133911340113411134211343113441134511346113471134811349113501135111352113531135411355113561135711358113591136011361113621136311364113651136611367113681136911370113711137211373113741137511376113771137811379113801138111382113831138411385113861138711388113891139011391113921139311394113951139611397113981139911400114011140211403114041140511406114071140811409114101141111412114131141411415114161141711418114191142011421114221142311424114251142611427114281142911430114311143211433114341143511436114371143811439114401144111442114431144411445114461144711448114491145011451114521145311454114551145611457114581145911460114611146211463114641146511466114671146811469114701147111472114731147411475114761147711478114791148011481114821148311484114851148611487114881148911490114911149211493114941149511496114971149811499115001150111502115031150411505115061150711508115091151011511115121151311514115151151611517115181151911520115211152211523115241152511526115271152811529115301153111532115331153411535115361153711538115391154011541115421154311544115451154611547115481154911550115511155211553115541155511556115571155811559115601156111562115631156411565115661156711568115691157011571115721157311574115751157611577115781157911580115811158211583115841158511586115871158811589115901159111592115931159411595115961159711598115991160011601116021160311604116051160611607116081160911610116111161211613116141161511616116171161811619116201162111622116231162411625116261162711628116291163011631116321163311634116351163611637116381163911640116411164211643116441164511646116471164811649116501165111652116531165411655116561165711658116591166011661116621166311664116651166611667116681166911670116711167211673116741167511676116771167811679116801168111682116831168411685116861168711688116891169011691116921169311694116951169611697116981169911700117011170211703117041170511706117071170811709117101171111712117131171411715117161171711718117191172011721117221172311724117251172611727117281172911730117311173211733117341173511736117371173811739117401174111742117431174411745117461174711748117491175011751117521175311754117551175611757117581175911760117611176211763117641176511766117671176811769117701177111772117731177411775117761177711778117791178011781117821178311784117851178611787117881178911790117911179211793117941179511796117971179811799118001180111802118031180411805118061180711808118091181011811118121181311814118151181611817118181181911820118211182211823118241182511826118271182811829118301183111832118331183411835118361183711838118391184011841118421184311844118451184611847118481184911850118511185211853118541185511856118571185811859118601186111862118631186411865118661186711868118691187011871118721187311874118751187611877118781187911880118811188211883118841188511886118871188811889118901189111892118931189411895118961189711898118991190011901119021190311904119051190611907119081190911910119111191211913119141191511916119171191811919119201192111922119231192411925119261192711928119291193011931119321193311934119351193611937119381193911940119411194211943119441194511946119471194811949119501195111952119531195411955119561195711958119591196011961119621196311964119651196611967119681196911970119711197211973119741197511976119771197811979119801198111982119831198411985119861198711988119891199011991119921199311994119951199611997119981199912000120011200212003120041200512006120071200812009120101201112012120131201412015120161201712018120191202012021120221202312024120251202612027120281202912030120311203212033120341203512036120371203812039120401204112042120431204412045120461204712048120491205012051120521205312054120551205612057120581205912060120611206212063120641206512066120671206812069120701207112072120731207412075120761207712078120791208012081120821208312084120851208612087120881208912090120911209212093120941209512096120971209812099121001210112102121031210412105121061210712108121091211012111121121211312114121151211612117121181211912120121211212212123121241212512126121271212812129121301213112132121331213412135121361213712138121391214012141121421214312144121451214612147121481214912150121511215212153121541215512156121571215812159121601216112162121631216412165121661216712168121691217012171121721217312174121751217612177121781217912180121811218212183121841218512186121871218812189121901219112192121931219412195121961219712198121991220012201122021220312204122051220612207122081220912210122111221212213122141221512216122171221812219122201222112222122231222412225122261222712228122291223012231122321223312234122351223612237122381223912240122411224212243122441224512246122471224812249122501225112252122531225412255122561225712258122591226012261122621226312264122651226612267122681226912270122711227212273122741227512276122771227812279122801228112282122831228412285122861228712288122891229012291122921229312294122951229612297122981229912300123011230212303123041230512306123071230812309123101231112312123131231412315123161231712318123191232012321123221232312324123251232612327123281232912330123311233212333123341233512336123371233812339123401234112342123431234412345123461234712348123491235012351123521235312354123551235612357123581235912360123611236212363123641236512366123671236812369123701237112372123731237412375123761237712378123791238012381123821238312384123851238612387123881238912390123911239212393123941239512396123971239812399124001240112402124031240412405124061240712408124091241012411124121241312414124151241612417124181241912420124211242212423124241242512426124271242812429124301243112432124331243412435124361243712438124391244012441124421244312444124451244612447124481244912450124511245212453124541245512456124571245812459124601246112462124631246412465124661246712468124691247012471124721247312474124751247612477124781247912480124811248212483124841248512486124871248812489124901249112492124931249412495124961249712498124991250012501125021250312504125051250612507125081250912510125111251212513125141251512516125171251812519125201252112522125231252412525125261252712528125291253012531125321253312534125351253612537125381253912540125411254212543125441254512546125471254812549125501255112552125531255412555125561255712558125591256012561125621256312564125651256612567125681256912570125711257212573125741257512576125771257812579125801258112582125831258412585125861258712588125891259012591125921259312594125951259612597125981259912600126011260212603126041260512606126071260812609126101261112612126131261412615126161261712618126191262012621126221262312624126251262612627126281262912630126311263212633126341263512636126371263812639126401264112642126431264412645126461264712648126491265012651126521265312654126551265612657126581265912660126611266212663126641266512666126671266812669126701267112672126731267412675126761267712678126791268012681126821268312684126851268612687126881268912690126911269212693126941269512696126971269812699127001270112702127031270412705127061270712708127091271012711127121271312714127151271612717127181271912720127211272212723127241272512726127271272812729127301273112732127331273412735127361273712738127391274012741127421274312744127451274612747127481274912750127511275212753127541275512756127571275812759127601276112762127631276412765127661276712768127691277012771127721277312774127751277612777127781277912780127811278212783127841278512786127871278812789127901279112792127931279412795127961279712798127991280012801128021280312804128051280612807128081280912810128111281212813128141281512816128171281812819128201282112822128231282412825128261282712828128291283012831128321283312834128351283612837128381283912840128411284212843128441284512846128471284812849128501285112852128531285412855128561285712858128591286012861128621286312864128651286612867128681286912870128711287212873128741287512876128771287812879128801288112882128831288412885128861288712888128891289012891128921289312894128951289612897128981289912900129011290212903129041290512906129071290812909129101291112912129131291412915129161291712918129191292012921129221292312924129251292612927129281292912930129311293212933129341293512936129371293812939129401294112942129431294412945129461294712948129491295012951129521295312954129551295612957129581295912960129611296212963129641296512966129671296812969129701297112972129731297412975129761297712978129791298012981129821298312984129851298612987129881298912990129911299212993129941299512996129971299812999130001300113002130031300413005130061300713008130091301013011130121301313014130151301613017130181301913020130211302213023130241302513026130271302813029130301303113032130331303413035130361303713038130391304013041130421304313044130451304613047130481304913050130511305213053130541305513056130571305813059130601306113062130631306413065130661306713068130691307013071130721307313074130751307613077130781307913080130811308213083130841308513086130871308813089130901309113092130931309413095130961309713098130991310013101131021310313104131051310613107131081310913110131111311213113131141311513116131171311813119131201312113122131231312413125131261312713128131291313013131131321313313134131351313613137131381313913140131411314213143131441314513146131471314813149131501315113152131531315413155131561315713158131591316013161131621316313164131651316613167131681316913170131711317213173131741317513176131771317813179131801318113182131831318413185131861318713188131891319013191131921319313194131951319613197131981319913200132011320213203132041320513206132071320813209132101321113212132131321413215132161321713218132191322013221132221322313224132251322613227132281322913230132311323213233132341323513236132371323813239132401324113242132431324413245132461324713248132491325013251132521325313254132551325613257132581325913260132611326213263132641326513266132671326813269132701327113272132731327413275132761327713278132791328013281132821328313284132851328613287132881328913290132911329213293132941329513296132971329813299133001330113302133031330413305133061330713308133091331013311133121331313314133151331613317133181331913320133211332213323133241332513326133271332813329133301333113332133331333413335133361333713338133391334013341133421334313344133451334613347133481334913350133511335213353133541335513356133571335813359133601336113362133631336413365133661336713368133691337013371133721337313374133751337613377133781337913380133811338213383133841338513386133871338813389133901339113392133931339413395133961339713398133991340013401134021340313404134051340613407134081340913410134111341213413134141341513416134171341813419134201342113422134231342413425134261342713428134291343013431134321343313434134351343613437134381343913440134411344213443134441344513446134471344813449134501345113452134531345413455134561345713458134591346013461134621346313464134651346613467134681346913470134711347213473134741347513476134771347813479134801348113482134831348413485134861348713488134891349013491134921349313494134951349613497134981349913500135011350213503135041350513506135071350813509135101351113512135131351413515135161351713518135191352013521135221352313524135251352613527135281352913530135311353213533135341353513536135371353813539135401354113542135431354413545135461354713548135491355013551135521355313554135551355613557135581355913560135611356213563135641356513566135671356813569135701357113572135731357413575135761357713578135791358013581135821358313584135851358613587135881358913590135911359213593135941359513596135971359813599136001360113602136031360413605136061360713608136091361013611136121361313614136151361613617136181361913620136211362213623136241362513626136271362813629136301363113632136331363413635136361363713638136391364013641136421364313644136451364613647136481364913650136511365213653136541365513656136571365813659136601366113662136631366413665136661366713668136691367013671136721367313674136751367613677136781367913680136811368213683136841368513686136871368813689136901369113692136931369413695136961369713698136991370013701137021370313704137051370613707137081370913710137111371213713137141371513716137171371813719137201372113722137231372413725137261372713728137291373013731137321373313734137351373613737137381373913740137411374213743137441374513746137471374813749137501375113752137531375413755137561375713758137591376013761137621376313764137651376613767137681376913770137711377213773137741377513776137771377813779137801378113782137831378413785137861378713788137891379013791137921379313794137951379613797137981379913800138011380213803138041380513806138071380813809138101381113812138131381413815138161381713818138191382013821138221382313824138251382613827138281382913830138311383213833138341383513836138371383813839138401384113842138431384413845138461384713848138491385013851138521385313854138551385613857138581385913860138611386213863138641386513866138671386813869138701387113872138731387413875138761387713878138791388013881138821388313884138851388613887138881388913890138911389213893138941389513896138971389813899139001390113902139031390413905139061390713908139091391013911139121391313914139151391613917139181391913920139211392213923139241392513926139271392813929139301393113932139331393413935139361393713938139391394013941139421394313944139451394613947139481394913950139511395213953139541395513956139571395813959139601396113962139631396413965139661396713968139691397013971139721397313974139751397613977139781397913980139811398213983139841398513986139871398813989139901399113992139931399413995139961399713998139991400014001140021400314004140051400614007140081400914010140111401214013140141401514016140171401814019140201402114022140231402414025140261402714028140291403014031140321403314034140351403614037140381403914040140411404214043140441404514046140471404814049140501405114052140531405414055140561405714058140591406014061140621406314064140651406614067140681406914070140711407214073140741407514076140771407814079140801408114082140831408414085140861408714088140891409014091140921409314094140951409614097140981409914100141011410214103141041410514106141071410814109141101411114112141131411414115141161411714118141191412014121141221412314124141251412614127141281412914130141311413214133141341413514136141371413814139141401414114142141431414414145141461414714148141491415014151141521415314154141551415614157141581415914160141611416214163141641416514166141671416814169141701417114172141731417414175141761417714178141791418014181141821418314184141851418614187141881418914190141911419214193141941419514196141971419814199142001420114202142031420414205142061420714208142091421014211142121421314214142151421614217142181421914220142211422214223142241422514226142271422814229142301423114232142331423414235142361423714238142391424014241142421424314244142451424614247142481424914250142511425214253142541425514256142571425814259142601426114262142631426414265142661426714268142691427014271142721427314274142751427614277142781427914280142811428214283142841428514286142871428814289142901429114292142931429414295142961429714298142991430014301143021430314304143051430614307143081430914310143111431214313143141431514316143171431814319143201432114322143231432414325143261432714328143291433014331143321433314334143351433614337143381433914340143411434214343143441434514346143471434814349143501435114352143531435414355143561435714358143591436014361143621436314364143651436614367143681436914370143711437214373143741437514376143771437814379143801438114382143831438414385143861438714388143891439014391 | /*! * Copyright 2015 Drifty Co. * http://drifty.com/ * * Ionic, v1.3.1 * A powerful HTML5 mobile app framework. * http://ionicframework.com/ * * By @maxlynch, @benjsperry, @adamdbradley <3 * * Licensed under the MIT license. Please see LICENSE for more information. * */(function() {/* eslint no-unused-vars:0 */var IonicModule = angular.module('ionic', ['ngAnimate', 'ngSanitize', 'ui.router', 'ngIOS9UIWebViewPatch']),  extend = angular.extend,  forEach = angular.forEach,  isDefined = angular.isDefined,  isNumber = angular.isNumber,  isString = angular.isString,  jqLite = angular.element,  noop = angular.noop;/** * @ngdoc service * @name $ionicActionSheet * @module ionic * @description * The Action Sheet is a slide-up pane that lets the user choose from a set of options. * Dangerous options are highlighted in red and made obvious. * * There are easy ways to cancel out of the action sheet, such as tapping the backdrop or even * hitting escape on the keyboard for desktop testing. * *  * * @usage * To trigger an Action Sheet in your code, use the $ionicActionSheet service in your angular controllers: * * ```js * angular.module('mySuperApp', ['ionic']) * .controller(function($scope, $ionicActionSheet, $timeout) { * *  // Triggered on a button click, or some other target *  $scope.show = function() { * *    // Show the action sheet *    var hideSheet = $ionicActionSheet.show({ *      buttons: [ *        { text: '<b>Share</b> This' }, *        { text: 'Move' } *      ], *      destructiveText: 'Delete', *      titleText: 'Modify your album', *      cancelText: 'Cancel', *      cancel: function() {          // add cancel code..        }, *      buttonClicked: function(index) { *        return true; *      } *    }); * *    // For example's sake, hide the sheet after two seconds *    $timeout(function() { *      hideSheet(); *    }, 2000); * *  }; * }); * ``` * */IonicModule.factory('$ionicActionSheet', [  '$rootScope',  '$compile',  '$animate',  '$timeout',  '$ionicTemplateLoader',  '$ionicPlatform',  '$ionicBody',  'IONIC_BACK_PRIORITY',function($rootScope, $compile, $animate, $timeout, $ionicTemplateLoader, $ionicPlatform, $ionicBody, IONIC_BACK_PRIORITY) {  return {    show: actionSheet  };  /**   * @ngdoc method   * @name $ionicActionSheet#show   * @description   * Load and return a new action sheet.   *   * A new isolated scope will be created for the   * action sheet and the new element will be appended into the body.   *   * @param {object} options The options for this ActionSheet. Properties:   *   *  - `[Object]` `buttons` Which buttons to show.  Each button is an object with a `text` field.   *  - `{string}` `titleText` The title to show on the action sheet.   *  - `{string=}` `cancelText` the text for a 'cancel' button on the action sheet.   *  - `{string=}` `destructiveText` The text for a 'danger' on the action sheet.   *  - `{function=}` `cancel` Called if the cancel button is pressed, the backdrop is tapped or   *     the hardware back button is pressed.   *  - `{function=}` `buttonClicked` Called when one of the non-destructive buttons is clicked,   *     with the index of the button that was clicked and the button object. Return true to close   *     the action sheet, or false to keep it opened.   *  - `{function=}` `destructiveButtonClicked` Called when the destructive button is clicked.   *     Return true to close the action sheet, or false to keep it opened.   *  -  `{boolean=}` `cancelOnStateChange` Whether to cancel the actionSheet when navigating   *     to a new state.  Default true.   *  - `{string}` `cssClass` The custom CSS class name.   *   * @returns {function} `hideSheet` A function which, when called, hides & cancels the action sheet.   */  function actionSheet(opts) {    var scope = $rootScope.$new(true);    extend(scope, {      cancel: noop,      destructiveButtonClicked: noop,      buttonClicked: noop,      $deregisterBackButton: noop,      buttons: [],      cancelOnStateChange: true    }, opts || {});    function textForIcon(text) {      if (text && /icon/.test(text)) {        scope.$actionSheetHasIcon = true;      }    }    for (var x = 0; x < scope.buttons.length; x++) {      textForIcon(scope.buttons[x].text);    }    textForIcon(scope.cancelText);    textForIcon(scope.destructiveText);    // Compile the template    var element = scope.element = $compile('<ion-action-sheet ng-class="cssClass" buttons="buttons"></ion-action-sheet>')(scope);    // Grab the sheet element for animation    var sheetEl = jqLite(element[0].querySelector('.action-sheet-wrapper'));    var stateChangeListenDone = scope.cancelOnStateChange ?      $rootScope.$on('$stateChangeSuccess', function() { scope.cancel(); }) :      noop;    // removes the actionSheet from the screen    scope.removeSheet = function(done) {      if (scope.removed) return;      scope.removed = true;      sheetEl.removeClass('action-sheet-up');      $timeout(function() {        // wait to remove this due to a 300ms delay native        // click which would trigging whatever was underneath this        $ionicBody.removeClass('action-sheet-open');      }, 400);      scope.$deregisterBackButton();      stateChangeListenDone();      $animate.removeClass(element, 'active').then(function() {        scope.$destroy();        element.remove();        // scope.cancel.$scope is defined near the bottom        scope.cancel.$scope = sheetEl = null;        (done || noop)(opts.buttons);      });    };    scope.showSheet = function(done) {      if (scope.removed) return;      $ionicBody.append(element)                .addClass('action-sheet-open');      $animate.addClass(element, 'active').then(function() {        if (scope.removed) return;        (done || noop)();      });      $timeout(function() {        if (scope.removed) return;        sheetEl.addClass('action-sheet-up');      }, 20, false);    };    // registerBackButtonAction returns a callback to deregister the action    scope.$deregisterBackButton = $ionicPlatform.registerBackButtonAction(      function() {        $timeout(scope.cancel);      },      IONIC_BACK_PRIORITY.actionSheet    );    // called when the user presses the cancel button    scope.cancel = function() {      // after the animation is out, call the cancel callback      scope.removeSheet(opts.cancel);    };    scope.buttonClicked = function(index) {      // Check if the button click event returned true, which means      // we can close the action sheet      if (opts.buttonClicked(index, opts.buttons[index]) === true) {        scope.removeSheet();      }    };    scope.destructiveButtonClicked = function() {      // Check if the destructive button click event returned true, which means      // we can close the action sheet      if (opts.destructiveButtonClicked() === true) {        scope.removeSheet();      }    };    scope.showSheet();    // Expose the scope on $ionicActionSheet's return value for the sake    // of testing it.    scope.cancel.$scope = scope;    return scope.cancel;  }}]);jqLite.prototype.addClass = function(cssClasses) {  var x, y, cssClass, el, splitClasses, existingClasses;  if (cssClasses && cssClasses != 'ng-scope' && cssClasses != 'ng-isolate-scope') {    for (x = 0; x < this.length; x++) {      el = this[x];      if (el.setAttribute) {        if (cssClasses.indexOf(' ') < 0 && el.classList.add) {          el.classList.add(cssClasses);        } else {          existingClasses = (' ' + (el.getAttribute('class') || '') + ' ')            .replace(/[\n\t]/g, " ");          splitClasses = cssClasses.split(' ');          for (y = 0; y < splitClasses.length; y++) {            cssClass = splitClasses[y].trim();            if (existingClasses.indexOf(' ' + cssClass + ' ') === -1) {              existingClasses += cssClass + ' ';            }          }          el.setAttribute('class', existingClasses.trim());        }      }    }  }  return this;};jqLite.prototype.removeClass = function(cssClasses) {  var x, y, splitClasses, cssClass, el;  if (cssClasses) {    for (x = 0; x < this.length; x++) {      el = this[x];      if (el.getAttribute) {        if (cssClasses.indexOf(' ') < 0 && el.classList.remove) {          el.classList.remove(cssClasses);        } else {          splitClasses = cssClasses.split(' ');          for (y = 0; y < splitClasses.length; y++) {            cssClass = splitClasses[y];            el.setAttribute('class', (                (" " + (el.getAttribute('class') || '') + " ")                .replace(/[\n\t]/g, " ")                .replace(" " + cssClass.trim() + " ", " ")).trim()            );          }        }      }    }  }  return this;};/** * @ngdoc service * @name $ionicBackdrop * @module ionic * @description * Shows and hides a backdrop over the UI.  Appears behind popups, loading, * and other overlays. * * Often, multiple UI components require a backdrop, but only one backdrop is * ever needed in the DOM at a time. * * Therefore, each component that requires the backdrop to be shown calls * `$ionicBackdrop.retain()` when it wants the backdrop, then `$ionicBackdrop.release()` * when it is done with the backdrop. * * For each time `retain` is called, the backdrop will be shown until `release` is called. * * For example, if `retain` is called three times, the backdrop will be shown until `release` * is called three times. * * **Notes:** * - The backdrop service will broadcast 'backdrop.shown' and 'backdrop.hidden' events from the root scope, * this is useful for alerting native components not in html. * * @usage * * ```js * function MyController($scope, $ionicBackdrop, $timeout, $rootScope) { *   //Show a backdrop for one second *   $scope.action = function() { *     $ionicBackdrop.retain(); *     $timeout(function() { *       $ionicBackdrop.release(); *     }, 1000); *   }; * *   // Execute action on backdrop disappearing *   $scope.$on('backdrop.hidden', function() { *     // Execute action *   }); * *   // Execute action on backdrop appearing *   $scope.$on('backdrop.shown', function() { *     // Execute action *   }); * * } * ``` */IonicModule.factory('$ionicBackdrop', [  '$document', '$timeout', '$$rAF', '$rootScope',function($document, $timeout, $$rAF, $rootScope) {  var el = jqLite('<div class="backdrop">');  var backdropHolds = 0;  $document[0].body.appendChild(el[0]);  return {    /**     * @ngdoc method     * @name $ionicBackdrop#retain     * @description Retains the backdrop.     */    retain: retain,    /**     * @ngdoc method     * @name $ionicBackdrop#release     * @description     * Releases the backdrop.     */    release: release,    getElement: getElement,    // exposed for testing    _element: el  };  function retain() {    backdropHolds++;    if (backdropHolds === 1) {      el.addClass('visible');      $rootScope.$broadcast('backdrop.shown');      $$rAF(function() {        // If we're still at >0 backdropHolds after async...        if (backdropHolds >= 1) el.addClass('active');      });    }  }  function release() {    if (backdropHolds === 1) {      el.removeClass('active');      $rootScope.$broadcast('backdrop.hidden');      $timeout(function() {        // If we're still at 0 backdropHolds after async...        if (backdropHolds === 0) el.removeClass('visible');      }, 400, false);    }    backdropHolds = Math.max(0, backdropHolds - 1);  }  function getElement() {    return el;  }}]);/** * @private */IonicModule.factory('$ionicBind', ['$parse', '$interpolate', function($parse, $interpolate) {  var LOCAL_REGEXP = /^\s*([@=&])(\??)\s*(\w*)\s*$/;  return function(scope, attrs, bindDefinition) {    forEach(bindDefinition || {}, function(definition, scopeName) {      //Adapted from angular.js $compile      var match = definition.match(LOCAL_REGEXP) || [],        attrName = match[3] || scopeName,        mode = match[1], // @, =, or &        parentGet,        unwatch;      switch (mode) {        case '@':          if (!attrs[attrName]) {            return;          }          attrs.$observe(attrName, function(value) {            scope[scopeName] = value;          });          // we trigger an interpolation to ensure          // the value is there for use immediately          if (attrs[attrName]) {            scope[scopeName] = $interpolate(attrs[attrName])(scope);          }          break;        case '=':          if (!attrs[attrName]) {            return;          }          unwatch = scope.$watch(attrs[attrName], function(value) {            scope[scopeName] = value;          });          //Destroy parent scope watcher when this scope is destroyed          scope.$on('$destroy', unwatch);          break;        case '&':          /* jshint -W044 */          if (attrs[attrName] && attrs[attrName].match(RegExp(scopeName + '\(.*?\)'))) {            throw new Error('& expression binding "' + scopeName + '" looks like it will recursively call "' +                          attrs[attrName] + '" and cause a stack overflow! Please choose a different scopeName.');          }          parentGet = $parse(attrs[attrName]);          scope[scopeName] = function(locals) {            return parentGet(scope, locals);          };          break;      }    });  };}]);/** * @ngdoc service * @name $ionicBody * @module ionic * @description An angular utility service to easily and efficiently * add and remove CSS classes from the document's body element. */IonicModule.factory('$ionicBody', ['$document', function($document) {  return {    /**     * @ngdoc method     * @name $ionicBody#addClass     * @description Add a class to the document's body element.     * @param {string} class Each argument will be added to the body element.     * @returns {$ionicBody} The $ionicBody service so methods can be chained.     */    addClass: function() {      for (var x = 0; x < arguments.length; x++) {        $document[0].body.classList.add(arguments[x]);      }      return this;    },    /**     * @ngdoc method     * @name $ionicBody#removeClass     * @description Remove a class from the document's body element.     * @param {string} class Each argument will be removed from the body element.     * @returns {$ionicBody} The $ionicBody service so methods can be chained.     */    removeClass: function() {      for (var x = 0; x < arguments.length; x++) {        $document[0].body.classList.remove(arguments[x]);      }      return this;    },    /**     * @ngdoc method     * @name $ionicBody#enableClass     * @description Similar to the `add` method, except the first parameter accepts a boolean     * value determining if the class should be added or removed. Rather than writing user code,     * such as "if true then add the class, else then remove the class", this method can be     * given a true or false value which reduces redundant code.     * @param {boolean} shouldEnableClass A true/false value if the class should be added or removed.     * @param {string} class Each remaining argument would be added or removed depending on     * the first argument.     * @returns {$ionicBody} The $ionicBody service so methods can be chained.     */    enableClass: function(shouldEnableClass) {      var args = Array.prototype.slice.call(arguments).slice(1);      if (shouldEnableClass) {        this.addClass.apply(this, args);      } else {        this.removeClass.apply(this, args);      }      return this;    },    /**     * @ngdoc method     * @name $ionicBody#append     * @description Append a child to the document's body.     * @param {element} element The element to be appended to the body. The passed in element     * can be either a jqLite element, or a DOM element.     * @returns {$ionicBody} The $ionicBody service so methods can be chained.     */    append: function(ele) {      $document[0].body.appendChild(ele.length ? ele[0] : ele);      return this;    },    /**     * @ngdoc method     * @name $ionicBody#get     * @description Get the document's body element.     * @returns {element} Returns the document's body element.     */    get: function() {      return $document[0].body;    }  };}]);IonicModule.factory('$ionicClickBlock', [  '$document',  '$ionicBody',  '$timeout',function($document, $ionicBody, $timeout) {  var CSS_HIDE = 'click-block-hide';  var cbEle, fallbackTimer, pendingShow;  function preventClick(ev) {    ev.preventDefault();    ev.stopPropagation();  }  function addClickBlock() {    if (pendingShow) {      if (cbEle) {        cbEle.classList.remove(CSS_HIDE);      } else {        cbEle = $document[0].createElement('div');        cbEle.className = 'click-block';        $ionicBody.append(cbEle);        cbEle.addEventListener('touchstart', preventClick);        cbEle.addEventListener('mousedown', preventClick);      }      pendingShow = false;    }  }  function removeClickBlock() {    cbEle && cbEle.classList.add(CSS_HIDE);  }  return {    show: function(autoExpire) {      pendingShow = true;      $timeout.cancel(fallbackTimer);      fallbackTimer = $timeout(this.hide, autoExpire || 310, false);      addClickBlock();    },    hide: function() {      pendingShow = false;      $timeout.cancel(fallbackTimer);      removeClickBlock();    }  };}]);/** * @ngdoc service * @name $ionicGesture * @module ionic * @description An angular service exposing ionic * {@link ionic.utility:ionic.EventController}'s gestures. */IonicModule.factory('$ionicGesture', [function() {  return {    /**     * @ngdoc method     * @name $ionicGesture#on     * @description Add an event listener for a gesture on an element. See {@link ionic.utility:ionic.EventController#onGesture}.     * @param {string} eventType The gesture event to listen for.     * @param {function(e)} callback The function to call when the gesture     * happens.     * @param {element} $element The angular element to listen for the event on.     * @param {object} options object.     * @returns {ionic.Gesture} The gesture object (use this to remove the gesture later on).     */    on: function(eventType, cb, $element, options) {      return window.ionic.onGesture(eventType, cb, $element[0], options);    },    /**     * @ngdoc method     * @name $ionicGesture#off     * @description Remove an event listener for a gesture on an element. See {@link ionic.utility:ionic.EventController#offGesture}.     * @param {ionic.Gesture} gesture The gesture that should be removed.     * @param {string} eventType The gesture event to remove the listener for.     * @param {function(e)} callback The listener to remove.     */    off: function(gesture, eventType, cb) {      return window.ionic.offGesture(gesture, eventType, cb);    }  };}]);/** * @ngdoc service * @name $ionicHistory * @module ionic * @description * $ionicHistory keeps track of views as the user navigates through an app. Similar to the way a * browser behaves, an Ionic app is able to keep track of the previous view, the current view, and * the forward view (if there is one).  However, a typical web browser only keeps track of one * history stack in a linear fashion. * * Unlike a traditional browser environment, apps and webapps have parallel independent histories, * such as with tabs. Should a user navigate few pages deep on one tab, and then switch to a new * tab and back, the back button relates not to the previous tab, but to the previous pages * visited within _that_ tab. * * `$ionicHistory` facilitates this parallel history architecture. */IonicModule.factory('$ionicHistory', [  '$rootScope',  '$state',  '$location',  '$window',  '$timeout',  '$ionicViewSwitcher',  '$ionicNavViewDelegate',function($rootScope, $state, $location, $window, $timeout, $ionicViewSwitcher, $ionicNavViewDelegate) {  // history actions while navigating views  var ACTION_INITIAL_VIEW = 'initialView';  var ACTION_NEW_VIEW = 'newView';  var ACTION_MOVE_BACK = 'moveBack';  var ACTION_MOVE_FORWARD = 'moveForward';  // direction of navigation  var DIRECTION_BACK = 'back';  var DIRECTION_FORWARD = 'forward';  var DIRECTION_ENTER = 'enter';  var DIRECTION_EXIT = 'exit';  var DIRECTION_SWAP = 'swap';  var DIRECTION_NONE = 'none';  var stateChangeCounter = 0;  var lastStateId, nextViewOptions, deregisterStateChangeListener, nextViewExpireTimer, forcedNav;  var viewHistory = {    histories: { root: { historyId: 'root', parentHistoryId: null, stack: [], cursor: -1 } },    views: {},    backView: null,    forwardView: null,    currentView: null  };  var View = function() {};  View.prototype.initialize = function(data) {    if (data) {      for (var name in data) this[name] = data[name];      return this;    }    return null;  };  View.prototype.go = function() {    if (this.stateName) {      return $state.go(this.stateName, this.stateParams);    }    if (this.url && this.url !== $location.url()) {      if (viewHistory.backView === this) {        return $window.history.go(-1);      } else if (viewHistory.forwardView === this) {        return $window.history.go(1);      }      $location.url(this.url);    }    return null;  };  View.prototype.destroy = function() {    if (this.scope) {      this.scope.$destroy && this.scope.$destroy();      this.scope = null;    }  };  function getViewById(viewId) {    return (viewId ? viewHistory.views[ viewId ] : null);  }  function getBackView(view) {    return (view ? getViewById(view.backViewId) : null);  }  function getForwardView(view) {    return (view ? getViewById(view.forwardViewId) : null);  }  function getHistoryById(historyId) {    return (historyId ? viewHistory.histories[ historyId ] : null);  }  function getHistory(scope) {    var histObj = getParentHistoryObj(scope);    if (!viewHistory.histories[ histObj.historyId ]) {      // this history object exists in parent scope, but doesn't      // exist in the history data yet      viewHistory.histories[ histObj.historyId ] = {        historyId: histObj.historyId,        parentHistoryId: getParentHistoryObj(histObj.scope.$parent).historyId,        stack: [],        cursor: -1      };    }    return getHistoryById(histObj.historyId);  }  function getParentHistoryObj(scope) {    var parentScope = scope;    while (parentScope) {      if (parentScope.hasOwnProperty('$historyId')) {        // this parent scope has a historyId        return { historyId: parentScope.$historyId, scope: parentScope };      }      // nothing found keep climbing up      parentScope = parentScope.$parent;    }    // no history for the parent, use the root    return { historyId: 'root', scope: $rootScope };  }  function setNavViews(viewId) {    viewHistory.currentView = getViewById(viewId);    viewHistory.backView = getBackView(viewHistory.currentView);    viewHistory.forwardView = getForwardView(viewHistory.currentView);  }  function getCurrentStateId() {    var id;    if ($state && $state.current && $state.current.name) {      id = $state.current.name;      if ($state.params) {        for (var key in $state.params) {          if ($state.params.hasOwnProperty(key) && $state.params[key]) {            id += "_" + key + "=" + $state.params[key];          }        }      }      return id;    }    // if something goes wrong make sure its got a unique stateId    return ionic.Utils.nextUid();  }  function getCurrentStateParams() {    var rtn;    if ($state && $state.params) {      for (var key in $state.params) {        if ($state.params.hasOwnProperty(key)) {          rtn = rtn || {};          rtn[key] = $state.params[key];        }      }    }    return rtn;  }  return {    register: function(parentScope, viewLocals) {      var currentStateId = getCurrentStateId(),          hist = getHistory(parentScope),          currentView = viewHistory.currentView,          backView = viewHistory.backView,          forwardView = viewHistory.forwardView,          viewId = null,          action = null,          direction = DIRECTION_NONE,          historyId = hist.historyId,          url = $location.url(),          tmp, x, ele;      if (lastStateId !== currentStateId) {        lastStateId = currentStateId;        stateChangeCounter++;      }      if (forcedNav) {        // we've previously set exactly what to do        viewId = forcedNav.viewId;        action = forcedNav.action;        direction = forcedNav.direction;        forcedNav = null;      } else if (backView && backView.stateId === currentStateId) {        // they went back one, set the old current view as a forward view        viewId = backView.viewId;        historyId = backView.historyId;        action = ACTION_MOVE_BACK;        if (backView.historyId === currentView.historyId) {          // went back in the same history          direction = DIRECTION_BACK;        } else if (currentView) {          direction = DIRECTION_EXIT;          tmp = getHistoryById(backView.historyId);          if (tmp && tmp.parentHistoryId === currentView.historyId) {            direction = DIRECTION_ENTER;          } else {            tmp = getHistoryById(currentView.historyId);            if (tmp && tmp.parentHistoryId === hist.parentHistoryId) {              direction = DIRECTION_SWAP;            }          }        }      } else if (forwardView && forwardView.stateId === currentStateId) {        // they went to the forward one, set the forward view to no longer a forward view        viewId = forwardView.viewId;        historyId = forwardView.historyId;        action = ACTION_MOVE_FORWARD;        if (forwardView.historyId === currentView.historyId) {          direction = DIRECTION_FORWARD;        } else if (currentView) {          direction = DIRECTION_EXIT;          if (currentView.historyId === hist.parentHistoryId) {            direction = DIRECTION_ENTER;          } else {            tmp = getHistoryById(currentView.historyId);            if (tmp && tmp.parentHistoryId === hist.parentHistoryId) {              direction = DIRECTION_SWAP;            }          }        }        tmp = getParentHistoryObj(parentScope);        if (forwardView.historyId && tmp.scope) {          // if a history has already been created by the forward view then make sure it stays the same          tmp.scope.$historyId = forwardView.historyId;          historyId = forwardView.historyId;        }      } else if (currentView && currentView.historyId !== historyId &&                hist.cursor > -1 && hist.stack.length > 0 && hist.cursor < hist.stack.length &&                hist.stack[hist.cursor].stateId === currentStateId) {        // they just changed to a different history and the history already has views in it        var switchToView = hist.stack[hist.cursor];        viewId = switchToView.viewId;        historyId = switchToView.historyId;        action = ACTION_MOVE_BACK;        direction = DIRECTION_SWAP;        tmp = getHistoryById(currentView.historyId);        if (tmp && tmp.parentHistoryId === historyId) {          direction = DIRECTION_EXIT;        } else {          tmp = getHistoryById(historyId);          if (tmp && tmp.parentHistoryId === currentView.historyId) {            direction = DIRECTION_ENTER;          }        }        // if switching to a different history, and the history of the view we're switching        // to has an existing back view from a different history than itself, then        // it's back view would be better represented using the current view as its back view        tmp = getViewById(switchToView.backViewId);        if (tmp && switchToView.historyId !== tmp.historyId) {          // the new view is being removed from it's old position in the history and being placed at the top,          // so we need to update any views that reference it as a backview, otherwise there will be infinitely loops          var viewIds = Object.keys(viewHistory.views);          viewIds.forEach(function(viewId) {            var view = viewHistory.views[viewId];            if ( view.backViewId === switchToView.viewId ) {              view.backViewId = null;            }          });          hist.stack[hist.cursor].backViewId = currentView.viewId;        }      } else {        // create an element from the viewLocals template        ele = $ionicViewSwitcher.createViewEle(viewLocals);        if (this.isAbstractEle(ele, viewLocals)) {          return {            action: 'abstractView',            direction: DIRECTION_NONE,            ele: ele          };        }        // set a new unique viewId        viewId = ionic.Utils.nextUid();        if (currentView) {          // set the forward view if there is a current view (ie: if its not the first view)          currentView.forwardViewId = viewId;          action = ACTION_NEW_VIEW;          // check if there is a new forward view within the same history          if (forwardView && currentView.stateId !== forwardView.stateId &&             currentView.historyId === forwardView.historyId) {            // they navigated to a new view but the stack already has a forward view            // since its a new view remove any forwards that existed            tmp = getHistoryById(forwardView.historyId);            if (tmp) {              // the forward has a history              for (x = tmp.stack.length - 1; x >= forwardView.index; x--) {                // starting from the end destroy all forwards in this history from this point                var stackItem = tmp.stack[x];                stackItem && stackItem.destroy && stackItem.destroy();                tmp.stack.splice(x);              }              historyId = forwardView.historyId;            }          }          // its only moving forward if its in the same history          if (hist.historyId === currentView.historyId) {            direction = DIRECTION_FORWARD;          } else if (currentView.historyId !== hist.historyId) {            // DB: this is a new view in a different tab            direction = DIRECTION_ENTER;            tmp = getHistoryById(currentView.historyId);            if (tmp && tmp.parentHistoryId === hist.parentHistoryId) {              direction = DIRECTION_SWAP;            } else {              tmp = getHistoryById(tmp.parentHistoryId);              if (tmp && tmp.historyId === hist.historyId) {                direction = DIRECTION_EXIT;              }            }          }        } else {          // there's no current view, so this must be the initial view          action = ACTION_INITIAL_VIEW;        }        if (stateChangeCounter < 2) {          // views that were spun up on the first load should not animate          direction = DIRECTION_NONE;        }        // add the new view        viewHistory.views[viewId] = this.createView({          viewId: viewId,          index: hist.stack.length,          historyId: hist.historyId,          backViewId: (currentView && currentView.viewId ? currentView.viewId : null),          forwardViewId: null,          stateId: currentStateId,          stateName: this.currentStateName(),          stateParams: getCurrentStateParams(),          url: url,          canSwipeBack: canSwipeBack(ele, viewLocals)        });        // add the new view to this history's stack        hist.stack.push(viewHistory.views[viewId]);      }      deregisterStateChangeListener && deregisterStateChangeListener();      $timeout.cancel(nextViewExpireTimer);      if (nextViewOptions) {        if (nextViewOptions.disableAnimate) direction = DIRECTION_NONE;        if (nextViewOptions.disableBack) viewHistory.views[viewId].backViewId = null;        if (nextViewOptions.historyRoot) {          for (x = 0; x < hist.stack.length; x++) {            if (hist.stack[x].viewId === viewId) {              hist.stack[x].index = 0;              hist.stack[x].backViewId = hist.stack[x].forwardViewId = null;            } else {              delete viewHistory.views[hist.stack[x].viewId];            }          }          hist.stack = [viewHistory.views[viewId]];        }        nextViewOptions = null;      }      setNavViews(viewId);      if (viewHistory.backView && historyId == viewHistory.backView.historyId && currentStateId == viewHistory.backView.stateId && url == viewHistory.backView.url) {        for (x = 0; x < hist.stack.length; x++) {          if (hist.stack[x].viewId == viewId) {            action = 'dupNav';            direction = DIRECTION_NONE;            if (x > 0) {              hist.stack[x - 1].forwardViewId = null;            }            viewHistory.forwardView = null;            viewHistory.currentView.index = viewHistory.backView.index;            viewHistory.currentView.backViewId = viewHistory.backView.backViewId;            viewHistory.backView = getBackView(viewHistory.backView);            hist.stack.splice(x, 1);            break;          }        }      }      hist.cursor = viewHistory.currentView.index;      return {        viewId: viewId,        action: action,        direction: direction,        historyId: historyId,        enableBack: this.enabledBack(viewHistory.currentView),        isHistoryRoot: (viewHistory.currentView.index === 0),        ele: ele      };    },    registerHistory: function(scope) {      scope.$historyId = ionic.Utils.nextUid();    },    createView: function(data) {      var newView = new View();      return newView.initialize(data);    },    getViewById: getViewById,    /**     * @ngdoc method     * @name $ionicHistory#viewHistory     * @description The app's view history data, such as all the views and histories, along     * with how they are ordered and linked together within the navigation stack.     * @returns {object} Returns an object containing the apps view history data.     */    viewHistory: function() {      return viewHistory;    },    /**     * @ngdoc method     * @name $ionicHistory#currentView     * @description The app's current view.     * @returns {object} Returns the current view.     */    currentView: function(view) {      if (arguments.length) {        viewHistory.currentView = view;      }      return viewHistory.currentView;    },    /**     * @ngdoc method     * @name $ionicHistory#currentHistoryId     * @description The ID of the history stack which is the parent container of the current view.     * @returns {string} Returns the current history ID.     */    currentHistoryId: function() {      return viewHistory.currentView ? viewHistory.currentView.historyId : null;    },    /**     * @ngdoc method     * @name $ionicHistory#currentTitle     * @description Gets and sets the current view's title.     * @param {string=} val The title to update the current view with.     * @returns {string} Returns the current view's title.     */    currentTitle: function(val) {      if (viewHistory.currentView) {        if (arguments.length) {          viewHistory.currentView.title = val;        }        return viewHistory.currentView.title;      }    },    /**     * @ngdoc method     * @name $ionicHistory#backView     * @description Returns the view that was before the current view in the history stack.     * If the user navigated from View A to View B, then View A would be the back view, and     * View B would be the current view.     * @returns {object} Returns the back view.     */    backView: function(view) {      if (arguments.length) {        viewHistory.backView = view;      }      return viewHistory.backView;    },    /**     * @ngdoc method     * @name $ionicHistory#backTitle     * @description Gets the back view's title.     * @returns {string} Returns the back view's title.     */    backTitle: function(view) {      var backView = (view && getViewById(view.backViewId)) || viewHistory.backView;      return backView && backView.title;    },    /**     * @ngdoc method     * @name $ionicHistory#forwardView     * @description Returns the view that was in front of the current view in the history stack.     * A forward view would exist if the user navigated from View A to View B, then     * navigated back to View A. At this point then View B would be the forward view, and View     * A would be the current view.     * @returns {object} Returns the forward view.     */    forwardView: function(view) {      if (arguments.length) {        viewHistory.forwardView = view;      }      return viewHistory.forwardView;    },    /**     * @ngdoc method     * @name $ionicHistory#currentStateName     * @description Returns the current state name.     * @returns {string}     */    currentStateName: function() {      return ($state && $state.current ? $state.current.name : null);    },    isCurrentStateNavView: function(navView) {      return !!($state && $state.current && $state.current.views && $state.current.views[navView]);    },    goToHistoryRoot: function(historyId) {      if (historyId) {        var hist = getHistoryById(historyId);        if (hist && hist.stack.length) {          if (viewHistory.currentView && viewHistory.currentView.viewId === hist.stack[0].viewId) {            return;          }          forcedNav = {            viewId: hist.stack[0].viewId,            action: ACTION_MOVE_BACK,            direction: DIRECTION_BACK          };          hist.stack[0].go();        }      }    },    /**     * @ngdoc method     * @name $ionicHistory#goBack     * @param {number=} backCount Optional negative integer setting how many views to go     * back. By default it'll go back one view by using the value `-1`. To go back two     * views you would use `-2`. If the number goes farther back than the number of views     * in the current history's stack then it'll go to the first view in the current history's     * stack. If the number is zero or greater then it'll do nothing. It also does not     * cross history stacks, meaning it can only go as far back as the current history.     * @description Navigates the app to the back view, if a back view exists.     */    goBack: function(backCount) {      if (isDefined(backCount) && backCount !== -1) {        if (backCount > -1) return;        var currentHistory = viewHistory.histories[this.currentHistoryId()];        var newCursor = currentHistory.cursor + backCount + 1;        if (newCursor < 1) {          newCursor = 1;        }        currentHistory.cursor = newCursor;        setNavViews(currentHistory.stack[newCursor].viewId);        var cursor = newCursor - 1;        var clearStateIds = [];        var fwdView = getViewById(currentHistory.stack[cursor].forwardViewId);        while (fwdView) {          clearStateIds.push(fwdView.stateId || fwdView.viewId);          cursor++;          if (cursor >= currentHistory.stack.length) break;          fwdView = getViewById(currentHistory.stack[cursor].forwardViewId);        }        var self = this;        if (clearStateIds.length) {          $timeout(function() {            self.clearCache(clearStateIds);          }, 300);        }      }      viewHistory.backView && viewHistory.backView.go();    },    /**     * @ngdoc method     * @name $ionicHistory#removeBackView     * @description Remove the previous view from the history completely, including the     * cached element and scope (if they exist).     */    removeBackView: function() {      var self = this;      var currentHistory = viewHistory.histories[this.currentHistoryId()];      var currentCursor = currentHistory.cursor;      var currentView = currentHistory.stack[currentCursor];      var backView = currentHistory.stack[currentCursor - 1];      var replacementView = currentHistory.stack[currentCursor - 2];      // fail if we dont have enough views in the history      if (!backView || !replacementView) {        return;      }      // remove the old backView and the cached element/scope      currentHistory.stack.splice(currentCursor - 1, 1);      self.clearCache([backView.viewId]);      // make the replacementView and currentView point to each other (bypass the old backView)      currentView.backViewId = replacementView.viewId;      currentView.index = currentView.index - 1;      replacementView.forwardViewId = currentView.viewId;      // update the cursor and set new backView      viewHistory.backView = replacementView;      currentHistory.currentCursor += -1;    },    enabledBack: function(view) {      var backView = getBackView(view);      return !!(backView && backView.historyId === view.historyId);    },    /**     * @ngdoc method     * @name $ionicHistory#clearHistory     * @description Clears out the app's entire history, except for the current view.     */    clearHistory: function() {      var      histories = viewHistory.histories,      currentView = viewHistory.currentView;      if (histories) {        for (var historyId in histories) {          if (histories[historyId].stack) {            histories[historyId].stack = [];            histories[historyId].cursor = -1;          }          if (currentView && currentView.historyId === historyId) {            currentView.backViewId = currentView.forwardViewId = null;            histories[historyId].stack.push(currentView);          } else if (histories[historyId].destroy) {            histories[historyId].destroy();          }        }      }      for (var viewId in viewHistory.views) {        if (viewId !== currentView.viewId) {          delete viewHistory.views[viewId];        }      }      if (currentView) {        setNavViews(currentView.viewId);      }    },    /**     * @ngdoc method     * @name $ionicHistory#clearCache	 * @return promise     * @description Removes all cached views within every {@link ionic.directive:ionNavView}.     * This both removes the view element from the DOM, and destroy it's scope.     */    clearCache: function(stateIds) {      return $timeout(function() {        $ionicNavViewDelegate._instances.forEach(function(instance) {          instance.clearCache(stateIds);        });      });    },    /**     * @ngdoc method     * @name $ionicHistory#nextViewOptions     * @description Sets options for the next view. This method can be useful to override     * certain view/transition defaults right before a view transition happens. For example,     * the {@link ionic.directive:menuClose} directive uses this method internally to ensure     * an animated view transition does not happen when a side menu is open, and also sets     * the next view as the root of its history stack. After the transition these options     * are set back to null.     *     * Available options:     *     * * `disableAnimate`: Do not animate the next transition.     * * `disableBack`: The next view should forget its back view, and set it to null.     * * `historyRoot`: The next view should become the root view in its history stack.     *     * ```js     * $ionicHistory.nextViewOptions({     *   disableAnimate: true,     *   disableBack: true     * });     * ```     */    nextViewOptions: function(opts) {      deregisterStateChangeListener && deregisterStateChangeListener();      if (arguments.length) {        $timeout.cancel(nextViewExpireTimer);        if (opts === null) {          nextViewOptions = opts;        } else {          nextViewOptions = nextViewOptions || {};          extend(nextViewOptions, opts);          if (nextViewOptions.expire) {              deregisterStateChangeListener = $rootScope.$on('$stateChangeSuccess', function() {                nextViewExpireTimer = $timeout(function() {                  nextViewOptions = null;                  }, nextViewOptions.expire);              });          }        }      }      return nextViewOptions;    },    isAbstractEle: function(ele, viewLocals) {      if (viewLocals && viewLocals.$$state && viewLocals.$$state.self['abstract']) {        return true;      }      return !!(ele && (isAbstractTag(ele) || isAbstractTag(ele.children())));    },    isActiveScope: function(scope) {      if (!scope) return false;      var climbScope = scope;      var currentHistoryId = this.currentHistoryId();      var foundHistoryId;      while (climbScope) {        if (climbScope.$$disconnected) {          return false;        }        if (!foundHistoryId && climbScope.hasOwnProperty('$historyId')) {          foundHistoryId = true;        }        if (currentHistoryId) {          if (climbScope.hasOwnProperty('$historyId') && currentHistoryId == climbScope.$historyId) {            return true;          }          if (climbScope.hasOwnProperty('$activeHistoryId')) {            if (currentHistoryId == climbScope.$activeHistoryId) {              if (climbScope.hasOwnProperty('$historyId')) {                return true;              }              if (!foundHistoryId) {                return true;              }            }          }        }        if (foundHistoryId && climbScope.hasOwnProperty('$activeHistoryId')) {          foundHistoryId = false;        }        climbScope = climbScope.$parent;      }      return currentHistoryId ? currentHistoryId == 'root' : true;    }  };  function isAbstractTag(ele) {    return ele && ele.length && /ion-side-menus|ion-tabs/i.test(ele[0].tagName);  }  function canSwipeBack(ele, viewLocals) {    if (viewLocals && viewLocals.$$state && viewLocals.$$state.self.canSwipeBack === false) {      return false;    }    if (ele && ele.attr('can-swipe-back') === 'false') {      return false;    }    var eleChild = ele.find('ion-view');    if (eleChild && eleChild.attr('can-swipe-back') === 'false') {      return false;    }    return true;  }}]).run([  '$rootScope',  '$state',  '$location',  '$document',  '$ionicPlatform',  '$ionicHistory',  'IONIC_BACK_PRIORITY',function($rootScope, $state, $location, $document, $ionicPlatform, $ionicHistory, IONIC_BACK_PRIORITY) {  // always reset the keyboard state when change stage  $rootScope.$on('$ionicView.beforeEnter', function() {    ionic.keyboard && ionic.keyboard.hide && ionic.keyboard.hide();  });  $rootScope.$on('$ionicHistory.change', function(e, data) {    if (!data) return null;    var viewHistory = $ionicHistory.viewHistory();    var hist = (data.historyId ? viewHistory.histories[ data.historyId ] : null);    if (hist && hist.cursor > -1 && hist.cursor < hist.stack.length) {      // the history they're going to already exists      // go to it's last view in its stack      var view = hist.stack[ hist.cursor ];      return view.go(data);    }    // this history does not have a URL, but it does have a uiSref    // figure out its URL from the uiSref    if (!data.url && data.uiSref) {      data.url = $state.href(data.uiSref);    }    if (data.url) {      // don't let it start with a #, messes with $location.url()      if (data.url.indexOf('#') === 0) {        data.url = data.url.replace('#', '');      }      if (data.url !== $location.url()) {        // we've got a good URL, ready GO!        $location.url(data.url);      }    }  });  $rootScope.$ionicGoBack = function(backCount) {    $ionicHistory.goBack(backCount);  };  // Set the document title when a new view is shown  $rootScope.$on('$ionicView.afterEnter', function(ev, data) {    if (data && data.title) {      $document[0].title = data.title;    }  });  // Triggered when devices with a hardware back button (Android) is clicked by the user  // This is a Cordova/Phonegap platform specifc method  function onHardwareBackButton(e) {    var backView = $ionicHistory.backView();    if (backView) {      // there is a back view, go to it      backView.go();    } else {      // there is no back view, so close the app instead      ionic.Platform.exitApp();    }    e.preventDefault();    return false;  }  $ionicPlatform.registerBackButtonAction(    onHardwareBackButton,    IONIC_BACK_PRIORITY.view  );}]);/** * @ngdoc provider * @name $ionicConfigProvider * @module ionic * @description * Ionic automatically takes platform configurations into account to adjust things like what * transition style to use and whether tab icons should show on the top or bottom. For example, * iOS will move forward by transitioning the entering view from right to center and the leaving * view from center to left. However, Android will transition with the entering view going from * bottom to center, covering the previous view, which remains stationary. It should be noted * that when a platform is not iOS or Android, then it'll default to iOS. So if you are * developing on a desktop browser, it's going to take on iOS default configs. * * These configs can be changed using the `$ionicConfigProvider` during the configuration phase * of your app. Additionally, `$ionicConfig` can also set and get config values during the run * phase and within the app itself. * * By default, all base config variables are set to `'platform'`, which means it'll take on the * default config of the platform on which it's running. Config variables can be set at this * level so all platforms follow the same setting, rather than its platform config. * The following code would set the same config variable for all platforms: * * ```js * $ionicConfigProvider.views.maxCache(10); * ``` * * Additionally, each platform can have it's own config within the `$ionicConfigProvider.platform` * property. The config below would only apply to Android devices. * * ```js * $ionicConfigProvider.platform.android.views.maxCache(5); * ``` * * @usage * ```js * var myApp = angular.module('reallyCoolApp', ['ionic']); * * myApp.config(function($ionicConfigProvider) { *   $ionicConfigProvider.views.maxCache(5); * *   // note that you can also chain configs *   $ionicConfigProvider.backButton.text('Go Back').icon('ion-chevron-left'); * }); * ``` *//** * @ngdoc method * @name $ionicConfigProvider#views.transition * @description Animation style when transitioning between views. Default `platform`. * * @param {string} transition Which style of view transitioning to use. * * * `platform`: Dynamically choose the correct transition style depending on the platform * the app is running from. If the platform is not `ios` or `android` then it will default * to `ios`. * * `ios`: iOS style transition. * * `android`: Android style transition. * * `none`: Do not perform animated transitions. * * @returns {string} value *//** * @ngdoc method * @name $ionicConfigProvider#views.maxCache * @description  Maximum number of view elements to cache in the DOM. When the max number is * exceeded, the view with the longest time period since it was accessed is removed. Views that * stay in the DOM cache the view's scope, current state, and scroll position. The scope is * disconnected from the `$watch` cycle when it is cached and reconnected when it enters again. * When the maximum cache is `0`, the leaving view's element will be removed from the DOM after * each view transition, and the next time the same view is shown, it will have to re-compile, * attach to the DOM, and link the element again. This disables caching, in effect. * @param {number} maxNumber Maximum number of views to retain. Default `10`. * @returns {number} How many views Ionic will hold onto until the a view is removed. *//** * @ngdoc method * @name $ionicConfigProvider#views.forwardCache * @description  By default, when navigating, views that were recently visited are cached, and * the same instance data and DOM elements are referenced when navigating back. However, when * navigating back in the history, the "forward" views are removed from the cache. If you * navigate forward to the same view again, it'll create a new DOM element and controller * instance. Basically, any forward views are reset each time. Set this config to `true` to have * forward views cached and not reset on each load. * @param {boolean} value * @returns {boolean} */ /**  * @ngdoc method  * @name $ionicConfigProvider#views.swipeBackEnabled  * @description  By default on iOS devices, swipe to go back functionality is enabled by default.  * This method can be used to disable it globally, or on a per-view basis.  * Note: This functionality is only supported on iOS.  * @param {boolean} value  * @returns {boolean}  *//** * @ngdoc method * @name $ionicConfigProvider#scrolling.jsScrolling * @description  Whether to use JS or Native scrolling. Defaults to native scrolling. Setting this to * `true` has the same effect as setting each `ion-content` to have `overflow-scroll='false'`. * @param {boolean} value Defaults to `false` as of Ionic 1.2 * @returns {boolean} *//** * @ngdoc method * @name $ionicConfigProvider#backButton.icon * @description Back button icon. * @param {string} value * @returns {string} *//** * @ngdoc method * @name $ionicConfigProvider#backButton.text * @description Back button text. * @param {string} value Defaults to `Back`. * @returns {string} *//** * @ngdoc method * @name $ionicConfigProvider#backButton.previousTitleText * @description If the previous title text should become the back button text. This * is the default for iOS. * @param {boolean} value * @returns {boolean} *//** * @ngdoc method * @name $ionicConfigProvider#form.checkbox * @description Checkbox style. Android defaults to `square` and iOS defaults to `circle`. * @param {string} value * @returns {string} *//** * @ngdoc method * @name $ionicConfigProvider#form.toggle * @description Toggle item style. Android defaults to `small` and iOS defaults to `large`. * @param {string} value * @returns {string} *//** * @ngdoc method * @name $ionicConfigProvider#spinner.icon * @description Default spinner icon to use. * @param {string} value Can be: `android`, `ios`, `ios-small`, `bubbles`, `circles`, `crescent`, * `dots`, `lines`, `ripple`, or `spiral`. * @returns {string} *//** * @ngdoc method * @name $ionicConfigProvider#tabs.style * @description Tab style. Android defaults to `striped` and iOS defaults to `standard`. * @param {string} value Available values include `striped` and `standard`. * @returns {string} *//** * @ngdoc method * @name $ionicConfigProvider#tabs.position * @description Tab position. Android defaults to `top` and iOS defaults to `bottom`. * @param {string} value Available values include `top` and `bottom`. * @returns {string} *//** * @ngdoc method * @name $ionicConfigProvider#templates.maxPrefetch * @description Sets the maximum number of templates to prefetch from the templateUrls defined in * $stateProvider.state. If set to `0`, the user will have to wait * for a template to be fetched the first time when navigating to a new page. Default `30`. * @param {integer} value Max number of template to prefetch from the templateUrls defined in * `$stateProvider.state()`. * @returns {integer} *//** * @ngdoc method * @name $ionicConfigProvider#navBar.alignTitle * @description Which side of the navBar to align the title. Default `center`. * * @param {string} value side of the navBar to align the title. * * * `platform`: Dynamically choose the correct title style depending on the platform * the app is running from. If the platform is `ios`, it will default to `center`. * If the platform is `android`, it will default to `left`. If the platform is not * `ios` or `android`, it will default to `center`. * * * `left`: Left align the title in the navBar * * `center`: Center align the title in the navBar * * `right`: Right align the title in the navBar. * * @returns {string} value *//**  * @ngdoc method  * @name $ionicConfigProvider#navBar.positionPrimaryButtons  * @description Which side of the navBar to align the primary navBar buttons. Default `left`.  *  * @param {string} value side of the navBar to align the primary navBar buttons.  *  * * `platform`: Dynamically choose the correct title style depending on the platform  * the app is running from. If the platform is `ios`, it will default to `left`.  * If the platform is `android`, it will default to `right`. If the platform is not  * `ios` or `android`, it will default to `left`.  *  * * `left`: Left align the primary navBar buttons in the navBar  * * `right`: Right align the primary navBar buttons in the navBar.  *  * @returns {string} value  *//** * @ngdoc method * @name $ionicConfigProvider#navBar.positionSecondaryButtons * @description Which side of the navBar to align the secondary navBar buttons. Default `right`. * * @param {string} value side of the navBar to align the secondary navBar buttons. * * * `platform`: Dynamically choose the correct title style depending on the platform * the app is running from. If the platform is `ios`, it will default to `right`. * If the platform is `android`, it will default to `right`. If the platform is not * `ios` or `android`, it will default to `right`. * * * `left`: Left align the secondary navBar buttons in the navBar * * `right`: Right align the secondary navBar buttons in the navBar. * * @returns {string} value */IonicModule.provider('$ionicConfig', function() {  var provider = this;  provider.platform = {};  var PLATFORM = 'platform';  var configProperties = {    views: {      maxCache: PLATFORM,      forwardCache: PLATFORM,      transition: PLATFORM,      swipeBackEnabled: PLATFORM,      swipeBackHitWidth: PLATFORM    },    navBar: {      alignTitle: PLATFORM,      positionPrimaryButtons: PLATFORM,      positionSecondaryButtons: PLATFORM,      transition: PLATFORM    },    backButton: {      icon: PLATFORM,      text: PLATFORM,      previousTitleText: PLATFORM    },    form: {      checkbox: PLATFORM,      toggle: PLATFORM    },    scrolling: {      jsScrolling: PLATFORM    },    spinner: {      icon: PLATFORM    },    tabs: {      style: PLATFORM,      position: PLATFORM    },    templates: {      maxPrefetch: PLATFORM    },    platform: {}  };  createConfig(configProperties, provider, '');  // Default  // -------------------------  setPlatformConfig('default', {    views: {      maxCache: 10,      forwardCache: false,      transition: 'ios',      swipeBackEnabled: true,      swipeBackHitWidth: 45    },    navBar: {      alignTitle: 'center',      positionPrimaryButtons: 'left',      positionSecondaryButtons: 'right',      transition: 'view'    },    backButton: {      icon: 'ion-ios-arrow-back',      text: 'Back',      previousTitleText: true    },    form: {      checkbox: 'circle',      toggle: 'large'    },    scrolling: {      jsScrolling: true    },    spinner: {      icon: 'ios'    },    tabs: {      style: 'standard',      position: 'bottom'    },    templates: {      maxPrefetch: 30    }  });  // iOS (it is the default already)  // -------------------------  setPlatformConfig('ios', {});  // Android  // -------------------------  setPlatformConfig('android', {    views: {      transition: 'android',      swipeBackEnabled: false    },    navBar: {      alignTitle: 'left',      positionPrimaryButtons: 'right',      positionSecondaryButtons: 'right'    },    backButton: {      icon: 'ion-android-arrow-back',      text: false,      previousTitleText: false    },    form: {      checkbox: 'square',      toggle: 'small'    },    spinner: {      icon: 'android'    },    tabs: {      style: 'striped',      position: 'top'    },    scrolling: {      jsScrolling: false    }  });  // Windows Phone  // -------------------------  setPlatformConfig('windowsphone', {    //scrolling: {    //  jsScrolling: false    //}    spinner: {      icon: 'android'    }  });  provider.transitions = {    views: {},    navBar: {}  };  // iOS Transitions  // -----------------------  provider.transitions.views.ios = function(enteringEle, leavingEle, direction, shouldAnimate) {    function setStyles(ele, opacity, x, boxShadowOpacity) {      var css = {};      css[ionic.CSS.TRANSITION_DURATION] = d.shouldAnimate ? '' : 0;      css.opacity = opacity;      if (boxShadowOpacity > -1) {        css.boxShadow = '0 0 10px rgba(0,0,0,' + (d.shouldAnimate ? boxShadowOpacity * 0.45 : 0.3) + ')';      }      css[ionic.CSS.TRANSFORM] = 'translate3d(' + x + '%,0,0)';      ionic.DomUtil.cachedStyles(ele, css);    }    var d = {      run: function(step) {        if (direction == 'forward') {          setStyles(enteringEle, 1, (1 - step) * 99, 1 - step); // starting at 98% prevents a flicker          setStyles(leavingEle, (1 - 0.1 * step), step * -33, -1);        } else if (direction == 'back') {          setStyles(enteringEle, (1 - 0.1 * (1 - step)), (1 - step) * -33, -1);          setStyles(leavingEle, 1, step * 100, 1 - step);        } else {          // swap, enter, exit          setStyles(enteringEle, 1, 0, -1);          setStyles(leavingEle, 0, 0, -1);        }      },      shouldAnimate: shouldAnimate && (direction == 'forward' || direction == 'back')    };    return d;  };  provider.transitions.navBar.ios = function(enteringHeaderBar, leavingHeaderBar, direction, shouldAnimate) {    function setStyles(ctrl, opacity, titleX, backTextX) {      var css = {};      css[ionic.CSS.TRANSITION_DURATION] = d.shouldAnimate ? '' : '0ms';      css.opacity = opacity === 1 ? '' : opacity;      ctrl.setCss('buttons-left', css);      ctrl.setCss('buttons-right', css);      ctrl.setCss('back-button', css);      css[ionic.CSS.TRANSFORM] = 'translate3d(' + backTextX + 'px,0,0)';      ctrl.setCss('back-text', css);      css[ionic.CSS.TRANSFORM] = 'translate3d(' + titleX + 'px,0,0)';      ctrl.setCss('title', css);    }    function enter(ctrlA, ctrlB, step) {      if (!ctrlA || !ctrlB) return;      var titleX = (ctrlA.titleTextX() + ctrlA.titleWidth()) * (1 - step);      var backTextX = (ctrlB && (ctrlB.titleTextX() - ctrlA.backButtonTextLeft()) * (1 - step)) || 0;      setStyles(ctrlA, step, titleX, backTextX);    }    function leave(ctrlA, ctrlB, step) {      if (!ctrlA || !ctrlB) return;      var titleX = (-(ctrlA.titleTextX() - ctrlB.backButtonTextLeft()) - (ctrlA.titleLeftRight())) * step;      setStyles(ctrlA, 1 - step, titleX, 0);    }    var d = {      run: function(step) {        var enteringHeaderCtrl = enteringHeaderBar.controller();        var leavingHeaderCtrl = leavingHeaderBar && leavingHeaderBar.controller();        if (d.direction == 'back') {          leave(enteringHeaderCtrl, leavingHeaderCtrl, 1 - step);          enter(leavingHeaderCtrl, enteringHeaderCtrl, 1 - step);        } else {          enter(enteringHeaderCtrl, leavingHeaderCtrl, step);          leave(leavingHeaderCtrl, enteringHeaderCtrl, step);        }      },      direction: direction,      shouldAnimate: shouldAnimate && (direction == 'forward' || direction == 'back')    };    return d;  };  // Android Transitions  // -----------------------  provider.transitions.views.android = function(enteringEle, leavingEle, direction, shouldAnimate) {    shouldAnimate = shouldAnimate && (direction == 'forward' || direction == 'back');    function setStyles(ele, x, opacity) {      var css = {};      css[ionic.CSS.TRANSITION_DURATION] = d.shouldAnimate ? '' : 0;      css[ionic.CSS.TRANSFORM] = 'translate3d(' + x + '%,0,0)';      css.opacity = opacity;      ionic.DomUtil.cachedStyles(ele, css);    }    var d = {      run: function(step) {        if (direction == 'forward') {          setStyles(enteringEle, (1 - step) * 99, 1); // starting at 98% prevents a flicker          setStyles(leavingEle, step * -100, 1);        } else if (direction == 'back') {          setStyles(enteringEle, (1 - step) * -100, 1);          setStyles(leavingEle, step * 100, 1);        } else {          // swap, enter, exit          setStyles(enteringEle, 0, 1);          setStyles(leavingEle, 0, 0);        }      },      shouldAnimate: shouldAnimate    };    return d;  };  provider.transitions.navBar.android = function(enteringHeaderBar, leavingHeaderBar, direction, shouldAnimate) {    function setStyles(ctrl, opacity) {      if (!ctrl) return;      var css = {};      css.opacity = opacity === 1 ? '' : opacity;      ctrl.setCss('buttons-left', css);      ctrl.setCss('buttons-right', css);      ctrl.setCss('back-button', css);      ctrl.setCss('back-text', css);      ctrl.setCss('title', css);    }    return {      run: function(step) {        setStyles(enteringHeaderBar.controller(), step);        setStyles(leavingHeaderBar && leavingHeaderBar.controller(), 1 - step);      },      shouldAnimate: shouldAnimate && (direction == 'forward' || direction == 'back')    };  };  // No Transition  // -----------------------  provider.transitions.views.none = function(enteringEle, leavingEle) {    return {      run: function(step) {        provider.transitions.views.android(enteringEle, leavingEle, false, false).run(step);      },      shouldAnimate: false    };  };  provider.transitions.navBar.none = function(enteringHeaderBar, leavingHeaderBar) {    return {      run: function(step) {        provider.transitions.navBar.ios(enteringHeaderBar, leavingHeaderBar, false, false).run(step);        provider.transitions.navBar.android(enteringHeaderBar, leavingHeaderBar, false, false).run(step);      },      shouldAnimate: false    };  };  // private: used to set platform configs  function setPlatformConfig(platformName, platformConfigs) {    configProperties.platform[platformName] = platformConfigs;    provider.platform[platformName] = {};    addConfig(configProperties, configProperties.platform[platformName]);    createConfig(configProperties.platform[platformName], provider.platform[platformName], '');  }  // private: used to recursively add new platform configs  function addConfig(configObj, platformObj) {    for (var n in configObj) {      if (n != PLATFORM && configObj.hasOwnProperty(n)) {        if (angular.isObject(configObj[n])) {          if (!isDefined(platformObj[n])) {            platformObj[n] = {};          }          addConfig(configObj[n], platformObj[n]);        } else if (!isDefined(platformObj[n])) {          platformObj[n] = null;        }      }    }  }  // private: create methods for each config to get/set  function createConfig(configObj, providerObj, platformPath) {    forEach(configObj, function(value, namespace) {      if (angular.isObject(configObj[namespace])) {        // recursively drill down the config object so we can create a method for each one        providerObj[namespace] = {};        createConfig(configObj[namespace], providerObj[namespace], platformPath + '.' + namespace);      } else {        // create a method for the provider/config methods that will be exposed        providerObj[namespace] = function(newValue) {          if (arguments.length) {            configObj[namespace] = newValue;            return providerObj;          }          if (configObj[namespace] == PLATFORM) {            // if the config is set to 'platform', then get this config's platform value            var platformConfig = stringObj(configProperties.platform, ionic.Platform.platform() + platformPath + '.' + namespace);            if (platformConfig || platformConfig === false) {              return platformConfig;            }            // didnt find a specific platform config, now try the default            return stringObj(configProperties.platform, 'default' + platformPath + '.' + namespace);          }          return configObj[namespace];        };      }    });  }  function stringObj(obj, str) {    str = str.split(".");    for (var i = 0; i < str.length; i++) {      if (obj && isDefined(obj[str[i]])) {        obj = obj[str[i]];      } else {        return null;      }    }    return obj;  }  provider.setPlatformConfig = setPlatformConfig;  // private: Service definition for internal Ionic use  /**   * @ngdoc service   * @name $ionicConfig   * @module ionic   * @private   */  provider.$get = function() {    return provider;  };})// Fix for URLs in Cordova apps on Windows Phone// http://blogs.msdn.com/b/msdn_answers/archive/2015/02/10/// running-cordova-apps-on-windows-and-windows-phone-8-1-using-ionic-angularjs-and-other-frameworks.aspx.config(['$compileProvider', function($compileProvider) {  $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|sms|tel|geo|ftp|mailto|file|ghttps?|ms-appx-web|ms-appx|x-wmapp0):/);  $compileProvider.imgSrcSanitizationWhitelist(/^\s*(https?|ftp|file|content|blob|ms-appx|ms-appx-web|x-wmapp0):|data:image\//);}]);var LOADING_TPL =  '<div class="loading-container">' +    '<div class="loading">' +    '</div>' +  '</div>';/** * @ngdoc service * @name $ionicLoading * @module ionic * @description * An overlay that can be used to indicate activity while blocking user * interaction. * * @usage * ```js * angular.module('LoadingApp', ['ionic']) * .controller('LoadingCtrl', function($scope, $ionicLoading) { *   $scope.show = function() { *     $ionicLoading.show({ *       template: 'Loading...' *     }).then(function(){ *        console.log("The loading indicator is now displayed"); *     }); *   }; *   $scope.hide = function(){ *     $ionicLoading.hide().then(function(){ *        console.log("The loading indicator is now hidden"); *     }); *   }; * }); * ``` *//** * @ngdoc object * @name $ionicLoadingConfig * @module ionic * @description * Set the default options to be passed to the {@link ionic.service:$ionicLoading} service. * * @usage * ```js * var app = angular.module('myApp', ['ionic']) * app.constant('$ionicLoadingConfig', { *   template: 'Default Loading Template...' * }); * app.controller('AppCtrl', function($scope, $ionicLoading) { *   $scope.showLoading = function() { *     //options default to values in $ionicLoadingConfig *     $ionicLoading.show().then(function(){ *        console.log("The loading indicator is now displayed"); *     }); *   }; * }); * ``` */IonicModule.constant('$ionicLoadingConfig', {  template: '<ion-spinner></ion-spinner>'}).factory('$ionicLoading', [  '$ionicLoadingConfig',  '$ionicBody',  '$ionicTemplateLoader',  '$ionicBackdrop',  '$timeout',  '$q',  '$log',  '$compile',  '$ionicPlatform',  '$rootScope',  'IONIC_BACK_PRIORITY',function($ionicLoadingConfig, $ionicBody, $ionicTemplateLoader, $ionicBackdrop, $timeout, $q, $log, $compile, $ionicPlatform, $rootScope, IONIC_BACK_PRIORITY) {  var loaderInstance;  //default values  var deregisterBackAction = noop;  var deregisterStateListener1 = noop;  var deregisterStateListener2 = noop;  var loadingShowDelay = $q.when();  return {    /**     * @ngdoc method     * @name $ionicLoading#show     * @description Shows a loading indicator. If the indicator is already shown,     * it will set the options given and keep the indicator shown.     * @returns {promise} A promise which is resolved when the loading indicator is presented.     * @param {object} opts The options for the loading indicator. Available properties:     *  - `{string=}` `template` The html content of the indicator.     *  - `{string=}` `templateUrl` The url of an html template to load as the content of the indicator.     *  - `{object=}` `scope` The scope to be a child of. Default: creates a child of $rootScope.     *  - `{boolean=}` `noBackdrop` Whether to hide the backdrop. By default it will be shown.     *  - `{boolean=}` `hideOnStateChange` Whether to hide the loading spinner when navigating     *    to a new state. Default false.     *  - `{number=}` `delay` How many milliseconds to delay showing the indicator. By default there is no delay.     *  - `{number=}` `duration` How many milliseconds to wait until automatically     *  hiding the indicator. By default, the indicator will be shown until `.hide()` is called.     */    show: showLoader,    /**     * @ngdoc method     * @name $ionicLoading#hide     * @description Hides the loading indicator, if shown.     * @returns {promise} A promise which is resolved when the loading indicator is hidden.     */    hide: hideLoader,    /**     * @private for testing     */    _getLoader: getLoader  };  function getLoader() {    if (!loaderInstance) {      loaderInstance = $ionicTemplateLoader.compile({        template: LOADING_TPL,        appendTo: $ionicBody.get()      })      .then(function(self) {        self.show = function(options) {          var templatePromise = options.templateUrl ?            $ionicTemplateLoader.load(options.templateUrl) :            //options.content: deprecated            $q.when(options.template || options.content || '');          self.scope = options.scope || self.scope;          if (!self.isShown) {            //options.showBackdrop: deprecated            self.hasBackdrop = !options.noBackdrop && options.showBackdrop !== false;            if (self.hasBackdrop) {              $ionicBackdrop.retain();              $ionicBackdrop.getElement().addClass('backdrop-loading');            }          }          if (options.duration) {            $timeout.cancel(self.durationTimeout);            self.durationTimeout = $timeout(              angular.bind(self, self.hide),              +options.duration            );          }          deregisterBackAction();          //Disable hardware back button while loading          deregisterBackAction = $ionicPlatform.registerBackButtonAction(            noop,            IONIC_BACK_PRIORITY.loading          );          templatePromise.then(function(html) {            if (html) {              var loading = self.element.children();              loading.html(html);              $compile(loading.contents())(self.scope);            }            //Don't show until template changes            if (self.isShown) {              self.element.addClass('visible');              ionic.requestAnimationFrame(function() {                if (self.isShown) {                  self.element.addClass('active');                  $ionicBody.addClass('loading-active');                }              });            }          });          self.isShown = true;        };        self.hide = function() {          deregisterBackAction();          if (self.isShown) {            if (self.hasBackdrop) {              $ionicBackdrop.release();              $ionicBackdrop.getElement().removeClass('backdrop-loading');            }            self.element.removeClass('active');            $ionicBody.removeClass('loading-active');            self.element.removeClass('visible');            ionic.requestAnimationFrame(function() {              !self.isShown && self.element.removeClass('visible');            });          }          $timeout.cancel(self.durationTimeout);          self.isShown = false;          var loading = self.element.children();          loading.html("");        };        return self;      });    }    return loaderInstance;  }  function showLoader(options) {    options = extend({}, $ionicLoadingConfig || {}, options || {});    // use a default delay of 100 to avoid some issues reported on github    // https://github.com/driftyco/ionic/issues/3717    var delay = options.delay || options.showDelay || 0;    deregisterStateListener1();    deregisterStateListener2();    if (options.hideOnStateChange) {      deregisterStateListener1 = $rootScope.$on('$stateChangeSuccess', hideLoader);      deregisterStateListener2 = $rootScope.$on('$stateChangeError', hideLoader);    }    //If loading.show() was called previously, cancel it and show with our new options    $timeout.cancel(loadingShowDelay);    loadingShowDelay = $timeout(noop, delay);    return loadingShowDelay.then(getLoader).then(function(loader) {      return loader.show(options);    });  }  function hideLoader() {    deregisterStateListener1();    deregisterStateListener2();    $timeout.cancel(loadingShowDelay);    return getLoader().then(function(loader) {      return loader.hide();    });  }}]);/** * @ngdoc service * @name $ionicModal * @module ionic * @codepen gblny * @description * * Related: {@link ionic.controller:ionicModal ionicModal controller}. * * The Modal is a content pane that can go over the user's main view * temporarily.  Usually used for making a choice or editing an item. * * Put the content of the modal inside of an `<ion-modal-view>` element. * * **Notes:** * - A modal will broadcast 'modal.shown', 'modal.hidden', and 'modal.removed' events from its originating * scope, passing in itself as an event argument. Both the modal.removed and modal.hidden events are * called when the modal is removed. * * - This example assumes your modal is in your main index file or another template file. If it is in its own * template file, remove the script tags and call it by file name. * * @usage * ```html * <script id="my-modal.html" type="text/ng-template"> *   <ion-modal-view> *     <ion-header-bar> *       <h1 class="title">My Modal title</h1> *     </ion-header-bar> *     <ion-content> *       Hello! *     </ion-content> *   </ion-modal-view> * </script> * ``` * ```js * angular.module('testApp', ['ionic']) * .controller('MyController', function($scope, $ionicModal) { *   $ionicModal.fromTemplateUrl('my-modal.html', { *     scope: $scope, *     animation: 'slide-in-up' *   }).then(function(modal) { *     $scope.modal = modal; *   }); *   $scope.openModal = function() { *     $scope.modal.show(); *   }; *   $scope.closeModal = function() { *     $scope.modal.hide(); *   }; *   // Cleanup the modal when we're done with it! *   $scope.$on('$destroy', function() { *     $scope.modal.remove(); *   }); *   // Execute action on hide modal *   $scope.$on('modal.hidden', function() { *     // Execute action *   }); *   // Execute action on remove modal *   $scope.$on('modal.removed', function() { *     // Execute action *   }); * }); * ``` */IonicModule.factory('$ionicModal', [  '$rootScope',  '$ionicBody',  '$compile',  '$timeout',  '$ionicPlatform',  '$ionicTemplateLoader',  '$$q',  '$log',  '$ionicClickBlock',  '$window',  'IONIC_BACK_PRIORITY',function($rootScope, $ionicBody, $compile, $timeout, $ionicPlatform, $ionicTemplateLoader, $$q, $log, $ionicClickBlock, $window, IONIC_BACK_PRIORITY) {  /**   * @ngdoc controller   * @name ionicModal   * @module ionic   * @description   * Instantiated by the {@link ionic.service:$ionicModal} service.   *   * Be sure to call [remove()](#remove) when you are done with each modal   * to clean it up and avoid memory leaks.   *   * Note: a modal will broadcast 'modal.shown', 'modal.hidden', and 'modal.removed' events from its originating   * scope, passing in itself as an event argument. Note: both modal.removed and modal.hidden are   * called when the modal is removed.   */  var ModalView = ionic.views.Modal.inherit({    /**     * @ngdoc method     * @name ionicModal#initialize     * @description Creates a new modal controller instance.     * @param {object} options An options object with the following properties:     *  - `{object=}` `scope` The scope to be a child of.     *    Default: creates a child of $rootScope.     *  - `{string=}` `animation` The animation to show & hide with.     *    Default: 'slide-in-up'     *  - `{boolean=}` `focusFirstInput` Whether to autofocus the first input of     *    the modal when shown. Will only show the keyboard on iOS, to force the keyboard to show     *    on Android, please use the [Ionic keyboard plugin](https://github.com/driftyco/ionic-plugin-keyboard#keyboardshow).     *    Default: false.     *  - `{boolean=}` `backdropClickToClose` Whether to close the modal on clicking the backdrop.     *    Default: true.     *  - `{boolean=}` `hardwareBackButtonClose` Whether the modal can be closed using the hardware     *    back button on Android and similar devices.  Default: true.     */    initialize: function(opts) {      ionic.views.Modal.prototype.initialize.call(this, opts);      this.animation = opts.animation || 'slide-in-up';    },    /**     * @ngdoc method     * @name ionicModal#show     * @description Show this modal instance.     * @returns {promise} A promise which is resolved when the modal is finished animating in.     */    show: function(target) {      var self = this;      if (self.scope.$$destroyed) {        $log.error('Cannot call ' + self.viewType + '.show() after remove(). Please create a new ' + self.viewType + ' instance.');        return $$q.when();      }      // on iOS, clicks will sometimes bleed through/ghost click on underlying      // elements      $ionicClickBlock.show(600);      stack.add(self);      var modalEl = jqLite(self.modalEl);      self.el.classList.remove('hide');      $timeout(function() {        if (!self._isShown) return;        $ionicBody.addClass(self.viewType + '-open');      }, 400, false);      if (!self.el.parentElement) {        modalEl.addClass(self.animation);        $ionicBody.append(self.el);      }      // if modal was closed while the keyboard was up, reset scroll view on      // next show since we can only resize it once it's visible      var scrollCtrl = modalEl.data('$$ionicScrollController');      scrollCtrl && scrollCtrl.resize();      if (target && self.positionView) {        self.positionView(target, modalEl);        // set up a listener for in case the window size changes        self._onWindowResize = function() {          if (self._isShown) self.positionView(target, modalEl);        };        ionic.on('resize', self._onWindowResize, window);      }      modalEl.addClass('ng-enter active')             .removeClass('ng-leave ng-leave-active');      self._isShown = true;      self._deregisterBackButton = $ionicPlatform.registerBackButtonAction(        self.hardwareBackButtonClose ? angular.bind(self, self.hide) : noop,        IONIC_BACK_PRIORITY.modal      );      ionic.views.Modal.prototype.show.call(self);      $timeout(function() {        if (!self._isShown) return;        modalEl.addClass('ng-enter-active');        ionic.trigger('resize');        self.scope.$parent && self.scope.$parent.$broadcast(self.viewType + '.shown', self);        self.el.classList.add('active');        self.scope.$broadcast('$ionicHeader.align');        self.scope.$broadcast('$ionicFooter.align');        self.scope.$broadcast('$ionic.modalPresented');      }, 20);      return $timeout(function() {        if (!self._isShown) return;        self.$el.on('touchmove', function(e) {          //Don't allow scrolling while open by dragging on backdrop          var isInScroll = ionic.DomUtil.getParentOrSelfWithClass(e.target, 'scroll');          if (!isInScroll) {            e.preventDefault();          }        });        //After animating in, allow hide on backdrop click        self.$el.on('click', function(e) {          if (self.backdropClickToClose && e.target === self.el && stack.isHighest(self)) {            self.hide();          }        });      }, 400);    },    /**     * @ngdoc method     * @name ionicModal#hide     * @description Hide this modal instance.     * @returns {promise} A promise which is resolved when the modal is finished animating out.     */    hide: function() {      var self = this;      var modalEl = jqLite(self.modalEl);      // on iOS, clicks will sometimes bleed through/ghost click on underlying      // elements      $ionicClickBlock.show(600);      stack.remove(self);      self.el.classList.remove('active');      modalEl.addClass('ng-leave');      $timeout(function() {        if (self._isShown) return;        modalEl.addClass('ng-leave-active')               .removeClass('ng-enter ng-enter-active active');        self.scope.$broadcast('$ionic.modalRemoved');      }, 20, false);      self.$el.off('click');      self._isShown = false;      self.scope.$parent && self.scope.$parent.$broadcast(self.viewType + '.hidden', self);      self._deregisterBackButton && self._deregisterBackButton();      ionic.views.Modal.prototype.hide.call(self);      // clean up event listeners      if (self.positionView) {        ionic.off('resize', self._onWindowResize, window);      }      return $timeout(function() {        $ionicBody.removeClass(self.viewType + '-open');        self.el.classList.add('hide');      }, self.hideDelay || 320);    },    /**     * @ngdoc method     * @name ionicModal#remove     * @description Remove this modal instance from the DOM and clean up.     * @returns {promise} A promise which is resolved when the modal is finished animating out.     */    remove: function() {      var self = this,          deferred, promise;      self.scope.$parent && self.scope.$parent.$broadcast(self.viewType + '.removed', self);      // Only hide modal, when it is actually shown!      // The hide function shows a click-block-div for a split second, because on iOS,      // clicks will sometimes bleed through/ghost click on underlying elements.      // However, this will make the app unresponsive for short amount of time.      // We don't want that, if the modal window is already hidden.      if (self._isShown) {        promise = self.hide();      } else {        deferred = $$q.defer();        deferred.resolve();        promise = deferred.promise;      }      return promise.then(function() {        self.scope.$destroy();        self.$el.remove();      });    },    /**     * @ngdoc method     * @name ionicModal#isShown     * @returns boolean Whether this modal is currently shown.     */    isShown: function() {      return !!this._isShown;    }  });  var createModal = function(templateString, options) {    // Create a new scope for the modal    var scope = options.scope && options.scope.$new() || $rootScope.$new(true);    options.viewType = options.viewType || 'modal';    extend(scope, {      $hasHeader: false,      $hasSubheader: false,      $hasFooter: false,      $hasSubfooter: false,      $hasTabs: false,      $hasTabsTop: false    });    // Compile the template    var element = $compile('<ion-' + options.viewType + '>' + templateString + '</ion-' + options.viewType + '>')(scope);    options.$el = element;    options.el = element[0];    options.modalEl = options.el.querySelector('.' + options.viewType);    var modal = new ModalView(options);    modal.scope = scope;    // If this wasn't a defined scope, we can assign the viewType to the isolated scope    // we created    if (!options.scope) {      scope[ options.viewType ] = modal;    }    return modal;  };  var modalStack = [];  var stack = {    add: function(modal) {      modalStack.push(modal);    },    remove: function(modal) {      var index = modalStack.indexOf(modal);      if (index > -1 && index < modalStack.length) {        modalStack.splice(index, 1);      }    },    isHighest: function(modal) {      var index = modalStack.indexOf(modal);      return (index > -1 && index === modalStack.length - 1);    }  };  return {    /**     * @ngdoc method     * @name $ionicModal#fromTemplate     * @param {string} templateString The template string to use as the modal's     * content.     * @param {object} options Options to be passed {@link ionic.controller:ionicModal#initialize ionicModal#initialize} method.     * @returns {object} An instance of an {@link ionic.controller:ionicModal}     * controller.     */    fromTemplate: function(templateString, options) {      var modal = createModal(templateString, options || {});      return modal;    },    /**     * @ngdoc method     * @name $ionicModal#fromTemplateUrl     * @param {string} templateUrl The url to load the template from.     * @param {object} options Options to be passed {@link ionic.controller:ionicModal#initialize ionicModal#initialize} method.     * options object.     * @returns {promise} A promise that will be resolved with an instance of     * an {@link ionic.controller:ionicModal} controller.     */    fromTemplateUrl: function(url, options, _) {      var cb;      //Deprecated: allow a callback as second parameter. Now we return a promise.      if (angular.isFunction(options)) {        cb = options;        options = _;      }      return $ionicTemplateLoader.load(url).then(function(templateString) {        var modal = createModal(templateString, options || {});        cb && cb(modal);        return modal;      });    },    stack: stack  };}]);/** * @ngdoc service * @name $ionicNavBarDelegate * @module ionic * @description * Delegate for controlling the {@link ionic.directive:ionNavBar} directive. * * @usage * * ```html * <body ng-controller="MyCtrl"> *   <ion-nav-bar> *     <button ng-click="setNavTitle('banana')"> *       Set title to banana! *     </button> *   </ion-nav-bar> * </body> * ``` * ```js * function MyCtrl($scope, $ionicNavBarDelegate) { *   $scope.setNavTitle = function(title) { *     $ionicNavBarDelegate.title(title); *   } * } * ``` */IonicModule.service('$ionicNavBarDelegate', ionic.DelegateService([  /**   * @ngdoc method   * @name $ionicNavBarDelegate#align   * @description Aligns the title with the buttons in a given direction.   * @param {string=} direction The direction to the align the title text towards.   * Available: 'left', 'right', 'center'. Default: 'center'.   */  'align',  /**   * @ngdoc method   * @name $ionicNavBarDelegate#showBackButton   * @description   * Set/get whether the {@link ionic.directive:ionNavBackButton} is shown   * (if it exists and there is a previous view that can be navigated to).   * @param {boolean=} show Whether to show the back button.   * @returns {boolean} Whether the back button is shown.   */  'showBackButton',  /**   * @ngdoc method   * @name $ionicNavBarDelegate#showBar   * @description   * Set/get whether the {@link ionic.directive:ionNavBar} is shown.   * @param {boolean} show Whether to show the bar.   * @returns {boolean} Whether the bar is shown.   */  'showBar',  /**   * @ngdoc method   * @name $ionicNavBarDelegate#title   * @description   * Set the title for the {@link ionic.directive:ionNavBar}.   * @param {string} title The new title to show.   */  'title',  // DEPRECATED, as of v1.0.0-beta14 -------  'changeTitle',  'setTitle',  'getTitle',  'back',  'getPreviousTitle'  // END DEPRECATED -------]));IonicModule.service('$ionicNavViewDelegate', ionic.DelegateService([  'clearCache']));/** * @ngdoc service * @name $ionicPlatform * @module ionic * @description * An angular abstraction of {@link ionic.utility:ionic.Platform}. * * Used to detect the current platform, as well as do things like override the * Android back button in PhoneGap/Cordova. */IonicModule.constant('IONIC_BACK_PRIORITY', {  view: 100,  sideMenu: 150,  modal: 200,  actionSheet: 300,  popup: 400,  loading: 500}).provider('$ionicPlatform', function() {  return {    $get: ['$q', '$ionicScrollDelegate', function($q, $ionicScrollDelegate) {      var self = {        /**         * @ngdoc method         * @name $ionicPlatform#onHardwareBackButton         * @description         * Some platforms have a hardware back button, so this is one way to         * bind to it.         * @param {function} callback the callback to trigger when this event occurs         */        onHardwareBackButton: function(cb) {          ionic.Platform.ready(function() {            document.addEventListener('backbutton', cb, false);          });        },        /**         * @ngdoc method         * @name $ionicPlatform#offHardwareBackButton         * @description         * Remove an event listener for the backbutton.         * @param {function} callback The listener function that was         * originally bound.         */        offHardwareBackButton: function(fn) {          ionic.Platform.ready(function() {            document.removeEventListener('backbutton', fn);          });        },        /**         * @ngdoc method         * @name $ionicPlatform#registerBackButtonAction         * @description         * Register a hardware back button action. Only one action will execute         * when the back button is clicked, so this method decides which of         * the registered back button actions has the highest priority.         *         * For example, if an actionsheet is showing, the back button should         * close the actionsheet, but it should not also go back a page view         * or close a modal which may be open.         *         * The priorities for the existing back button hooks are as follows:         *   Return to previous view = 100         *   Close side menu = 150         *   Dismiss modal = 200         *   Close action sheet = 300         *   Dismiss popup = 400         *   Dismiss loading overlay = 500         *         * Your back button action will override each of the above actions         * whose priority is less than the priority you provide. For example,         * an action assigned a priority of 101 will override the 'return to         * previous view' action, but not any of the other actions.         *         * @param {function} callback Called when the back button is pressed,         * if this listener is the highest priority.         * @param {number} priority Only the highest priority will execute.         * @param {*=} actionId The id to assign this action. Default: a         * random unique id.         * @returns {function} A function that, when called, will deregister         * this backButtonAction.         */        $backButtonActions: {},        registerBackButtonAction: function(fn, priority, actionId) {          if (!self._hasBackButtonHandler) {            // add a back button listener if one hasn't been setup yet            self.$backButtonActions = {};            self.onHardwareBackButton(self.hardwareBackButtonClick);            self._hasBackButtonHandler = true;          }          var action = {            id: (actionId ? actionId : ionic.Utils.nextUid()),            priority: (priority ? priority : 0),            fn: fn          };          self.$backButtonActions[action.id] = action;          // return a function to de-register this back button action          return function() {            delete self.$backButtonActions[action.id];          };        },        /**         * @private         */        hardwareBackButtonClick: function(e) {          // loop through all the registered back button actions          // and only run the last one of the highest priority          var priorityAction, actionId;          for (actionId in self.$backButtonActions) {            if (!priorityAction || self.$backButtonActions[actionId].priority >= priorityAction.priority) {              priorityAction = self.$backButtonActions[actionId];            }          }          if (priorityAction) {            priorityAction.fn(e);            return priorityAction;          }        },        is: function(type) {          return ionic.Platform.is(type);        },        /**         * @ngdoc method         * @name $ionicPlatform#on         * @description         * Add Cordova event listeners, such as `pause`, `resume`, `volumedownbutton`, `batterylow`,         * `offline`, etc. More information about available event types can be found in         * [Cordova's event documentation](https://cordova.apache.org/docs/en/edge/cordova_events_events.md.html#Events).         * @param {string} type Cordova [event type](https://cordova.apache.org/docs/en/edge/cordova_events_events.md.html#Events).         * @param {function} callback Called when the Cordova event is fired.         * @returns {function} Returns a deregistration function to remove the event listener.         */        on: function(type, cb) {          ionic.Platform.ready(function() {            document.addEventListener(type, cb, false);          });          return function() {            ionic.Platform.ready(function() {              document.removeEventListener(type, cb);            });          };        },        /**         * @ngdoc method         * @name $ionicPlatform#ready         * @description         * Trigger a callback once the device is ready,         * or immediately if the device is already ready.         * @param {function=} callback The function to call.         * @returns {promise} A promise which is resolved when the device is ready.         */        ready: function(cb) {          var q = $q.defer();          ionic.Platform.ready(function() {            window.addEventListener('statusTap', function() {              $ionicScrollDelegate.scrollTop(true);            });            q.resolve();            cb && cb();          });          return q.promise;        }      };      return self;    }]  };});/** * @ngdoc service * @name $ionicPopover * @module ionic * @description * * Related: {@link ionic.controller:ionicPopover ionicPopover controller}. * * The Popover is a view that floats above an app’s content. Popovers provide an * easy way to present or gather information from the user and are * commonly used in the following situations: * * - Show more info about the current view * - Select a commonly used tool or configuration * - Present a list of actions to perform inside one of your views * * Put the content of the popover inside of an `<ion-popover-view>` element. * * @usage * ```html * <p> *   <button ng-click="openPopover($event)">Open Popover</button> * </p> * * <script id="my-popover.html" type="text/ng-template"> *   <ion-popover-view> *     <ion-header-bar> *       <h1 class="title">My Popover Title</h1> *     </ion-header-bar> *     <ion-content> *       Hello! *     </ion-content> *   </ion-popover-view> * </script> * ``` * ```js * angular.module('testApp', ['ionic']) * .controller('MyController', function($scope, $ionicPopover) { * *   // .fromTemplate() method *   var template = '<ion-popover-view><ion-header-bar> <h1 class="title">My Popover Title</h1> </ion-header-bar> <ion-content> Hello! </ion-content></ion-popover-view>'; * *   $scope.popover = $ionicPopover.fromTemplate(template, { *     scope: $scope *   }); * *   // .fromTemplateUrl() method *   $ionicPopover.fromTemplateUrl('my-popover.html', { *     scope: $scope *   }).then(function(popover) { *     $scope.popover = popover; *   }); * * *   $scope.openPopover = function($event) { *     $scope.popover.show($event); *   }; *   $scope.closePopover = function() { *     $scope.popover.hide(); *   }; *   //Cleanup the popover when we're done with it! *   $scope.$on('$destroy', function() { *     $scope.popover.remove(); *   }); *   // Execute action on hide popover *   $scope.$on('popover.hidden', function() { *     // Execute action *   }); *   // Execute action on remove popover *   $scope.$on('popover.removed', function() { *     // Execute action *   }); * }); * ``` */IonicModule.factory('$ionicPopover', ['$ionicModal', '$ionicPosition', '$document', '$window',function($ionicModal, $ionicPosition, $document, $window) {  var POPOVER_BODY_PADDING = 6;  var POPOVER_OPTIONS = {    viewType: 'popover',    hideDelay: 1,    animation: 'none',    positionView: positionView  };  function positionView(target, popoverEle) {    var targetEle = jqLite(target.target || target);    var buttonOffset = $ionicPosition.offset(targetEle);    var popoverWidth = popoverEle.prop('offsetWidth');    var popoverHeight = popoverEle.prop('offsetHeight');    // Use innerWidth and innerHeight, because clientWidth and clientHeight    // doesn't work consistently for body on all platforms    var bodyWidth = $window.innerWidth;    var bodyHeight = $window.innerHeight;    var popoverCSS = {      left: buttonOffset.left + buttonOffset.width / 2 - popoverWidth / 2    };    var arrowEle = jqLite(popoverEle[0].querySelector('.popover-arrow'));    if (popoverCSS.left < POPOVER_BODY_PADDING) {      popoverCSS.left = POPOVER_BODY_PADDING;    } else if (popoverCSS.left + popoverWidth + POPOVER_BODY_PADDING > bodyWidth) {      popoverCSS.left = bodyWidth - popoverWidth - POPOVER_BODY_PADDING;    }    // If the popover when popped down stretches past bottom of screen,    // make it pop up if there's room above    if (buttonOffset.top + buttonOffset.height + popoverHeight > bodyHeight &&        buttonOffset.top - popoverHeight > 0) {      popoverCSS.top = buttonOffset.top - popoverHeight;      popoverEle.addClass('popover-bottom');    } else {      popoverCSS.top = buttonOffset.top + buttonOffset.height;      popoverEle.removeClass('popover-bottom');    }    arrowEle.css({      left: buttonOffset.left + buttonOffset.width / 2 -        arrowEle.prop('offsetWidth') / 2 - popoverCSS.left + 'px'    });    popoverEle.css({      top: popoverCSS.top + 'px',      left: popoverCSS.left + 'px',      marginLeft: '0',      opacity: '1'    });  }  /**   * @ngdoc controller   * @name ionicPopover   * @module ionic   * @description   * Instantiated by the {@link ionic.service:$ionicPopover} service.   *   * Be sure to call [remove()](#remove) when you are done with each popover   * to clean it up and avoid memory leaks.   *   * Note: a popover will broadcast 'popover.shown', 'popover.hidden', and 'popover.removed' events from its originating   * scope, passing in itself as an event argument. Both the popover.removed and popover.hidden events are   * called when the popover is removed.   */  /**   * @ngdoc method   * @name ionicPopover#initialize   * @description Creates a new popover controller instance.   * @param {object} options An options object with the following properties:   *  - `{object=}` `scope` The scope to be a child of.   *    Default: creates a child of $rootScope.   *  - `{boolean=}` `focusFirstInput` Whether to autofocus the first input of   *    the popover when shown.  Default: false.   *  - `{boolean=}` `backdropClickToClose` Whether to close the popover on clicking the backdrop.   *    Default: true.   *  - `{boolean=}` `hardwareBackButtonClose` Whether the popover can be closed using the hardware   *    back button on Android and similar devices.  Default: true.   */  /**   * @ngdoc method   * @name ionicPopover#show   * @description Show this popover instance.   * @param {$event} $event The $event or target element which the popover should align   * itself next to.   * @returns {promise} A promise which is resolved when the popover is finished animating in.   */  /**   * @ngdoc method   * @name ionicPopover#hide   * @description Hide this popover instance.   * @returns {promise} A promise which is resolved when the popover is finished animating out.   */  /**   * @ngdoc method   * @name ionicPopover#remove   * @description Remove this popover instance from the DOM and clean up.   * @returns {promise} A promise which is resolved when the popover is finished animating out.   */  /**   * @ngdoc method   * @name ionicPopover#isShown   * @returns boolean Whether this popover is currently shown.   */  return {    /**     * @ngdoc method     * @name $ionicPopover#fromTemplate     * @param {string} templateString The template string to use as the popovers's     * content.     * @param {object} options Options to be passed to the initialize method.     * @returns {object} An instance of an {@link ionic.controller:ionicPopover}     * controller (ionicPopover is built on top of $ionicPopover).     */    fromTemplate: function(templateString, options) {      return $ionicModal.fromTemplate(templateString, ionic.Utils.extend({}, POPOVER_OPTIONS, options));    },    /**     * @ngdoc method     * @name $ionicPopover#fromTemplateUrl     * @param {string} templateUrl The url to load the template from.     * @param {object} options Options to be passed to the initialize method.     * @returns {promise} A promise that will be resolved with an instance of     * an {@link ionic.controller:ionicPopover} controller (ionicPopover is built on top of $ionicPopover).     */    fromTemplateUrl: function(url, options) {      return $ionicModal.fromTemplateUrl(url, ionic.Utils.extend({}, POPOVER_OPTIONS, options));    }  };}]);var POPUP_TPL =  '<div class="popup-container" ng-class="cssClass">' +    '<div class="popup">' +      '<div class="popup-head">' +        '<h3 class="popup-title" ng-bind-html="title"></h3>' +        '<h5 class="popup-sub-title" ng-bind-html="subTitle" ng-if="subTitle"></h5>' +      '</div>' +      '<div class="popup-body">' +      '</div>' +      '<div class="popup-buttons" ng-show="buttons.length">' +        '<button ng-repeat="button in buttons" ng-click="$buttonTapped(button, $event)" class="button" ng-class="button.type || \'button-default\'" ng-bind-html="button.text"></button>' +      '</div>' +    '</div>' +  '</div>';/** * @ngdoc service * @name $ionicPopup * @module ionic * @restrict E * @codepen zkmhJ * @description * * The Ionic Popup service allows programmatically creating and showing popup * windows that require the user to respond in order to continue. * * The popup system has support for more flexible versions of the built in `alert()`, `prompt()`, * and `confirm()` functions that users are used to, in addition to allowing popups with completely * custom content and look. * * An input can be given an `autofocus` attribute so it automatically receives focus when * the popup first shows. However, depending on certain use-cases this can cause issues with * the tap/click system, which is why Ionic prefers using the `autofocus` attribute as * an opt-in feature and not the default. * * @usage * A few basic examples, see below for details about all of the options available. * * ```js *angular.module('mySuperApp', ['ionic']) *.controller('PopupCtrl',function($scope, $ionicPopup, $timeout) { * * // Triggered on a button click, or some other target * $scope.showPopup = function() { *   $scope.data = {}; * *   // An elaborate, custom popup *   var myPopup = $ionicPopup.show({ *     template: '<input type="password" ng-model="data.wifi">', *     title: 'Enter Wi-Fi Password', *     subTitle: 'Please use normal things', *     scope: $scope, *     buttons: [ *       { text: 'Cancel' }, *       { *         text: '<b>Save</b>', *         type: 'button-positive', *         onTap: function(e) { *           if (!$scope.data.wifi) { *             //don't allow the user to close unless he enters wifi password *             e.preventDefault(); *           } else { *             return $scope.data.wifi; *           } *         } *       } *     ] *   }); * *   myPopup.then(function(res) { *     console.log('Tapped!', res); *   }); * *   $timeout(function() { *      myPopup.close(); //close the popup after 3 seconds for some reason *   }, 3000); *  }; * *  // A confirm dialog *  $scope.showConfirm = function() { *    var confirmPopup = $ionicPopup.confirm({ *      title: 'Consume Ice Cream', *      template: 'Are you sure you want to eat this ice cream?' *    }); * *    confirmPopup.then(function(res) { *      if(res) { *        console.log('You are sure'); *      } else { *        console.log('You are not sure'); *      } *    }); *  }; * *  // An alert dialog *  $scope.showAlert = function() { *    var alertPopup = $ionicPopup.alert({ *      title: 'Don\'t eat that!', *      template: 'It might taste good' *    }); * *    alertPopup.then(function(res) { *      console.log('Thank you for not eating my delicious ice cream cone'); *    }); *  }; *}); *``` */IonicModule.factory('$ionicPopup', [  '$ionicTemplateLoader',  '$ionicBackdrop',  '$q',  '$timeout',  '$rootScope',  '$ionicBody',  '$compile',  '$ionicPlatform',  '$ionicModal',  'IONIC_BACK_PRIORITY',function($ionicTemplateLoader, $ionicBackdrop, $q, $timeout, $rootScope, $ionicBody, $compile, $ionicPlatform, $ionicModal, IONIC_BACK_PRIORITY) {  //TODO allow this to be configured  var config = {    stackPushDelay: 75  };  var popupStack = [];  var $ionicPopup = {    /**     * @ngdoc method     * @description     * Show a complex popup. This is the master show function for all popups.     *     * A complex popup has a `buttons` array, with each button having a `text` and `type`     * field, in addition to an `onTap` function.  The `onTap` function, called when     * the corresponding button on the popup is tapped, will by default close the popup     * and resolve the popup promise with its return value.  If you wish to prevent the     * default and keep the popup open on button tap, call `event.preventDefault()` on the     * passed in tap event.  Details below.     *     * @name $ionicPopup#show     * @param {object} options The options for the new popup, of the form:     *     * ```     * {     *   title: '', // String. The title of the popup.     *   cssClass: '', // String, The custom CSS class name     *   subTitle: '', // String (optional). The sub-title of the popup.     *   template: '', // String (optional). The html template to place in the popup body.     *   templateUrl: '', // String (optional). The URL of an html template to place in the popup   body.     *   scope: null, // Scope (optional). A scope to link to the popup content.     *   buttons: [{ // Array[Object] (optional). Buttons to place in the popup footer.     *     text: 'Cancel',     *     type: 'button-default',     *     onTap: function(e) {     *       // e.preventDefault() will stop the popup from closing when tapped.     *       e.preventDefault();     *     }     *   }, {     *     text: 'OK',     *     type: 'button-positive',     *     onTap: function(e) {     *       // Returning a value will cause the promise to resolve with the given value.     *       return scope.data.response;     *     }     *   }]     * }     * ```     *     * @returns {object} A promise which is resolved when the popup is closed. Has an additional     * `close` function, which can be used to programmatically close the popup.     */    show: showPopup,    /**     * @ngdoc method     * @name $ionicPopup#alert     * @description Show a simple alert popup with a message and one button that the user can     * tap to close the popup.     *     * @param {object} options The options for showing the alert, of the form:     *     * ```     * {     *   title: '', // String. The title of the popup.     *   cssClass: '', // String, The custom CSS class name     *   subTitle: '', // String (optional). The sub-title of the popup.     *   template: '', // String (optional). The html template to place in the popup body.     *   templateUrl: '', // String (optional). The URL of an html template to place in the popup   body.     *   okText: '', // String (default: 'OK'). The text of the OK button.     *   okType: '', // String (default: 'button-positive'). The type of the OK button.     * }     * ```     *     * @returns {object} A promise which is resolved when the popup is closed. Has one additional     * function `close`, which can be called with any value to programmatically close the popup     * with the given value.     */    alert: showAlert,    /**     * @ngdoc method     * @name $ionicPopup#confirm     * @description     * Show a simple confirm popup with a Cancel and OK button.     *     * Resolves the promise with true if the user presses the OK button, and false if the     * user presses the Cancel button.     *     * @param {object} options The options for showing the confirm popup, of the form:     *     * ```     * {     *   title: '', // String. The title of the popup.     *   cssClass: '', // String, The custom CSS class name     *   subTitle: '', // String (optional). The sub-title of the popup.     *   template: '', // String (optional). The html template to place in the popup body.     *   templateUrl: '', // String (optional). The URL of an html template to place in the popup   body.     *   cancelText: '', // String (default: 'Cancel'). The text of the Cancel button.     *   cancelType: '', // String (default: 'button-default'). The type of the Cancel button.     *   okText: '', // String (default: 'OK'). The text of the OK button.     *   okType: '', // String (default: 'button-positive'). The type of the OK button.     * }     * ```     *     * @returns {object} A promise which is resolved when the popup is closed. Has one additional     * function `close`, which can be called with any value to programmatically close the popup     * with the given value.     */    confirm: showConfirm,    /**     * @ngdoc method     * @name $ionicPopup#prompt     * @description Show a simple prompt popup, which has an input, OK button, and Cancel button.     * Resolves the promise with the value of the input if the user presses OK, and with undefined     * if the user presses Cancel.     *     * ```javascript     *  $ionicPopup.prompt({     *    title: 'Password Check',     *    template: 'Enter your secret password',     *    inputType: 'password',     *    inputPlaceholder: 'Your password'     *  }).then(function(res) {     *    console.log('Your password is', res);     *  });     * ```     * @param {object} options The options for showing the prompt popup, of the form:     *     * ```     * {     *   title: '', // String. The title of the popup.     *   cssClass: '', // String, The custom CSS class name     *   subTitle: '', // String (optional). The sub-title of the popup.     *   template: '', // String (optional). The html template to place in the popup body.     *   templateUrl: '', // String (optional). The URL of an html template to place in the popup body.     *   inputType: // String (default: 'text'). The type of input to use     *   defaultText: // String (default: ''). The initial value placed into the input.     *   maxLength: // Integer (default: null). Specify a maxlength attribute for the input.     *   inputPlaceholder: // String (default: ''). A placeholder to use for the input.     *   cancelText: // String (default: 'Cancel'. The text of the Cancel button.     *   cancelType: // String (default: 'button-default'). The type of the Cancel button.     *   okText: // String (default: 'OK'). The text of the OK button.     *   okType: // String (default: 'button-positive'). The type of the OK button.     * }     * ```     *     * @returns {object} A promise which is resolved when the popup is closed. Has one additional     * function `close`, which can be called with any value to programmatically close the popup     * with the given value.     */    prompt: showPrompt,    /**     * @private for testing     */    _createPopup: createPopup,    _popupStack: popupStack  };  return $ionicPopup;  function createPopup(options) {    options = extend({      scope: null,      title: '',      buttons: []    }, options || {});    var self = {};    self.scope = (options.scope || $rootScope).$new();    self.element = jqLite(POPUP_TPL);    self.responseDeferred = $q.defer();    $ionicBody.get().appendChild(self.element[0]);    $compile(self.element)(self.scope);    extend(self.scope, {      title: options.title,      buttons: options.buttons,      subTitle: options.subTitle,      cssClass: options.cssClass,      $buttonTapped: function(button, event) {        var result = (button.onTap || noop).apply(self, [event]);        event = event.originalEvent || event; //jquery events        if (!event.defaultPrevented) {          self.responseDeferred.resolve(result);        }      }    });    $q.when(      options.templateUrl ?      $ionicTemplateLoader.load(options.templateUrl) :        (options.template || options.content || '')    ).then(function(template) {      var popupBody = jqLite(self.element[0].querySelector('.popup-body'));      if (template) {        popupBody.html(template);        $compile(popupBody.contents())(self.scope);      } else {        popupBody.remove();      }    });    self.show = function() {      if (self.isShown || self.removed) return;      $ionicModal.stack.add(self);      self.isShown = true;      ionic.requestAnimationFrame(function() {        //if hidden while waiting for raf, don't show        if (!self.isShown) return;        self.element.removeClass('popup-hidden');        self.element.addClass('popup-showing active');        focusInput(self.element);      });    };    self.hide = function(callback) {      callback = callback || noop;      if (!self.isShown) return callback();      $ionicModal.stack.remove(self);      self.isShown = false;      self.element.removeClass('active');      self.element.addClass('popup-hidden');      $timeout(callback, 250, false);    };    self.remove = function() {      if (self.removed) return;      self.hide(function() {        self.element.remove();        self.scope.$destroy();      });      self.removed = true;    };    return self;  }  function onHardwareBackButton() {    var last = popupStack[popupStack.length - 1];    last && last.responseDeferred.resolve();  }  function showPopup(options) {    var popup = $ionicPopup._createPopup(options);    var showDelay = 0;    if (popupStack.length > 0) {      showDelay = config.stackPushDelay;      $timeout(popupStack[popupStack.length - 1].hide, showDelay, false);    } else {      //Add popup-open & backdrop if this is first popup      $ionicBody.addClass('popup-open');      $ionicBackdrop.retain();      //only show the backdrop on the first popup      $ionicPopup._backButtonActionDone = $ionicPlatform.registerBackButtonAction(        onHardwareBackButton,        IONIC_BACK_PRIORITY.popup      );    }    // Expose a 'close' method on the returned promise    popup.responseDeferred.promise.close = function popupClose(result) {      if (!popup.removed) popup.responseDeferred.resolve(result);    };    //DEPRECATED: notify the promise with an object with a close method    popup.responseDeferred.notify({ close: popup.responseDeferred.close });    doShow();    return popup.responseDeferred.promise;    function doShow() {      popupStack.push(popup);      $timeout(popup.show, showDelay, false);      popup.responseDeferred.promise.then(function(result) {        var index = popupStack.indexOf(popup);        if (index !== -1) {          popupStack.splice(index, 1);        }        popup.remove();        if (popupStack.length > 0) {          popupStack[popupStack.length - 1].show();        } else {          $ionicBackdrop.release();          //Remove popup-open & backdrop if this is last popup          $timeout(function() {            // wait to remove this due to a 300ms delay native            // click which would trigging whatever was underneath this            if (!popupStack.length) {              $ionicBody.removeClass('popup-open');            }          }, 400, false);          ($ionicPopup._backButtonActionDone || noop)();        }        return result;      });    }  }  function focusInput(element) {    var focusOn = element[0].querySelector('[autofocus]');    if (focusOn) {      focusOn.focus();    }  }  function showAlert(opts) {    return showPopup(extend({      buttons: [{        text: opts.okText || 'OK',        type: opts.okType || 'button-positive',        onTap: function() {          return true;        }      }]    }, opts || {}));  }  function showConfirm(opts) {    return showPopup(extend({      buttons: [{        text: opts.cancelText || 'Cancel',        type: opts.cancelType || 'button-default',        onTap: function() { return false; }      }, {        text: opts.okText || 'OK',        type: opts.okType || 'button-positive',        onTap: function() { return true; }      }]    }, opts || {}));  }  function showPrompt(opts) {    var scope = $rootScope.$new(true);    scope.data = {};    scope.data.fieldtype = opts.inputType ? opts.inputType : 'text';    scope.data.response = opts.defaultText ? opts.defaultText : '';    scope.data.placeholder = opts.inputPlaceholder ? opts.inputPlaceholder : '';    scope.data.maxlength = opts.maxLength ? parseInt(opts.maxLength) : '';    var text = '';    if (opts.template && /<[a-z][\s\S]*>/i.test(opts.template) === false) {      text = '<span>' + opts.template + '</span>';      delete opts.template;    }    return showPopup(extend({      template: text + '<input ng-model="data.response" '        + 'type="{{ data.fieldtype }}"'        + 'maxlength="{{ data.maxlength }}"'        + 'placeholder="{{ data.placeholder }}"'        + '>',      scope: scope,      buttons: [{        text: opts.cancelText || 'Cancel',        type: opts.cancelType || 'button-default',        onTap: function() {}      }, {        text: opts.okText || 'OK',        type: opts.okType || 'button-positive',        onTap: function() {          return scope.data.response || '';        }      }]    }, opts || {}));  }}]);/** * @ngdoc service * @name $ionicPosition * @module ionic * @description * A set of utility methods that can be use to retrieve position of DOM elements. * It is meant to be used where we need to absolute-position DOM elements in * relation to other, existing elements (this is the case for tooltips, popovers, etc.). * * Adapted from [AngularUI Bootstrap](https://github.com/angular-ui/bootstrap/blob/master/src/position/position.js), * ([license](https://github.com/angular-ui/bootstrap/blob/master/LICENSE)) */IonicModule.factory('$ionicPosition', ['$document', '$window', function($document, $window) {  function getStyle(el, cssprop) {    if (el.currentStyle) { //IE      return el.currentStyle[cssprop];    } else if ($window.getComputedStyle) {      return $window.getComputedStyle(el)[cssprop];    }    // finally try and get inline style    return el.style[cssprop];  }  /**   * Checks if a given element is statically positioned   * @param element - raw DOM element   */  function isStaticPositioned(element) {    return (getStyle(element, 'position') || 'static') === 'static';  }  /**   * returns the closest, non-statically positioned parentOffset of a given element   * @param element   */  var parentOffsetEl = function(element) {    var docDomEl = $document[0];    var offsetParent = element.offsetParent || docDomEl;    while (offsetParent && offsetParent !== docDomEl && isStaticPositioned(offsetParent)) {      offsetParent = offsetParent.offsetParent;    }    return offsetParent || docDomEl;  };  return {    /**     * @ngdoc method     * @name $ionicPosition#position     * @description Get the current coordinates of the element, relative to the offset parent.     * Read-only equivalent of [jQuery's position function](http://api.jquery.com/position/).     * @param {element} element The element to get the position of.     * @returns {object} Returns an object containing the properties top, left, width and height.     */    position: function(element) {      var elBCR = this.offset(element);      var offsetParentBCR = { top: 0, left: 0 };      var offsetParentEl = parentOffsetEl(element[0]);      if (offsetParentEl != $document[0]) {        offsetParentBCR = this.offset(jqLite(offsetParentEl));        offsetParentBCR.top += offsetParentEl.clientTop - offsetParentEl.scrollTop;        offsetParentBCR.left += offsetParentEl.clientLeft - offsetParentEl.scrollLeft;      }      var boundingClientRect = element[0].getBoundingClientRect();      return {        width: boundingClientRect.width || element.prop('offsetWidth'),        height: boundingClientRect.height || element.prop('offsetHeight'),        top: elBCR.top - offsetParentBCR.top,        left: elBCR.left - offsetParentBCR.left      };    },    /**     * @ngdoc method     * @name $ionicPosition#offset     * @description Get the current coordinates of the element, relative to the document.     * Read-only equivalent of [jQuery's offset function](http://api.jquery.com/offset/).     * @param {element} element The element to get the offset of.     * @returns {object} Returns an object containing the properties top, left, width and height.     */    offset: function(element) {      var boundingClientRect = element[0].getBoundingClientRect();      return {        width: boundingClientRect.width || element.prop('offsetWidth'),        height: boundingClientRect.height || element.prop('offsetHeight'),        top: boundingClientRect.top + ($window.pageYOffset || $document[0].documentElement.scrollTop),        left: boundingClientRect.left + ($window.pageXOffset || $document[0].documentElement.scrollLeft)      };    }  };}]);/** * @ngdoc service * @name $ionicScrollDelegate * @module ionic * @description * Delegate for controlling scrollViews (created by * {@link ionic.directive:ionContent} and * {@link ionic.directive:ionScroll} directives). * * Methods called directly on the $ionicScrollDelegate service will control all scroll * views.  Use the {@link ionic.service:$ionicScrollDelegate#$getByHandle $getByHandle} * method to control specific scrollViews. * * @usage * * ```html * <body ng-controller="MainCtrl"> *   <ion-content> *     <button ng-click="scrollTop()">Scroll to Top!</button> *   </ion-content> * </body> * ``` * ```js * function MainCtrl($scope, $ionicScrollDelegate) { *   $scope.scrollTop = function() { *     $ionicScrollDelegate.scrollTop(); *   }; * } * ``` * * Example of advanced usage, with two scroll areas using `delegate-handle` * for fine control. * * ```html * <body ng-controller="MainCtrl"> *   <ion-content delegate-handle="mainScroll"> *     <button ng-click="scrollMainToTop()"> *       Scroll content to top! *     </button> *     <ion-scroll delegate-handle="small" style="height: 100px;"> *       <button ng-click="scrollSmallToTop()"> *         Scroll small area to top! *       </button> *     </ion-scroll> *   </ion-content> * </body> * ``` * ```js * function MainCtrl($scope, $ionicScrollDelegate) { *   $scope.scrollMainToTop = function() { *     $ionicScrollDelegate.$getByHandle('mainScroll').scrollTop(); *   }; *   $scope.scrollSmallToTop = function() { *     $ionicScrollDelegate.$getByHandle('small').scrollTop(); *   }; * } * ``` */IonicModule.service('$ionicScrollDelegate', ionic.DelegateService([  /**   * @ngdoc method   * @name $ionicScrollDelegate#resize   * @description Tell the scrollView to recalculate the size of its container.   */  'resize',  /**   * @ngdoc method   * @name $ionicScrollDelegate#scrollTop   * @param {boolean=} shouldAnimate Whether the scroll should animate.   */  'scrollTop',  /**   * @ngdoc method   * @name $ionicScrollDelegate#scrollBottom   * @param {boolean=} shouldAnimate Whether the scroll should animate.   */  'scrollBottom',  /**   * @ngdoc method   * @name $ionicScrollDelegate#scrollTo   * @param {number} left The x-value to scroll to.   * @param {number} top The y-value to scroll to.   * @param {boolean=} shouldAnimate Whether the scroll should animate.   */  'scrollTo',  /**   * @ngdoc method   * @name $ionicScrollDelegate#scrollBy   * @param {number} left The x-offset to scroll by.   * @param {number} top The y-offset to scroll by.   * @param {boolean=} shouldAnimate Whether the scroll should animate.   */  'scrollBy',  /**   * @ngdoc method   * @name $ionicScrollDelegate#zoomTo   * @param {number} level Level to zoom to.   * @param {boolean=} animate Whether to animate the zoom.   * @param {number=} originLeft Zoom in at given left coordinate.   * @param {number=} originTop Zoom in at given top coordinate.   */  'zoomTo',  /**   * @ngdoc method   * @name $ionicScrollDelegate#zoomBy   * @param {number} factor The factor to zoom by.   * @param {boolean=} animate Whether to animate the zoom.   * @param {number=} originLeft Zoom in at given left coordinate.   * @param {number=} originTop Zoom in at given top coordinate.   */  'zoomBy',  /**   * @ngdoc method   * @name $ionicScrollDelegate#getScrollPosition   * @returns {object} The scroll position of this view, with the following properties:   *  - `{number}` `left` The distance the user has scrolled from the left (starts at 0).   *  - `{number}` `top` The distance the user has scrolled from the top (starts at 0).   *  - `{number}` `zoom` The current zoom level.   */  'getScrollPosition',  /**   * @ngdoc method   * @name $ionicScrollDelegate#anchorScroll   * @description Tell the scrollView to scroll to the element with an id   * matching window.location.hash.   *   * If no matching element is found, it will scroll to top.   *   * @param {boolean=} shouldAnimate Whether the scroll should animate.   */  'anchorScroll',  /**   * @ngdoc method   * @name $ionicScrollDelegate#freezeScroll   * @description Does not allow this scroll view to scroll either x or y.   * @param {boolean=} shouldFreeze Should this scroll view be prevented from scrolling or not.   * @returns {boolean} If the scroll view is being prevented from scrolling or not.   */  'freezeScroll',  /**   * @ngdoc method   * @name $ionicScrollDelegate#freezeAllScrolls   * @description Does not allow any of the app's scroll views to scroll either x or y.   * @param {boolean=} shouldFreeze Should all app scrolls be prevented from scrolling or not.   */  'freezeAllScrolls',  /**   * @ngdoc method   * @name $ionicScrollDelegate#getScrollView   * @returns {object} The scrollView associated with this delegate.   */  'getScrollView'  /**   * @ngdoc method   * @name $ionicScrollDelegate#$getByHandle   * @param {string} handle   * @returns `delegateInstance` A delegate instance that controls only the   * scrollViews with `delegate-handle` matching the given handle.   *   * Example: `$ionicScrollDelegate.$getByHandle('my-handle').scrollTop();`   */]));/** * @ngdoc service * @name $ionicSideMenuDelegate * @module ionic * * @description * Delegate for controlling the {@link ionic.directive:ionSideMenus} directive. * * Methods called directly on the $ionicSideMenuDelegate service will control all side * menus.  Use the {@link ionic.service:$ionicSideMenuDelegate#$getByHandle $getByHandle} * method to control specific ionSideMenus instances. * * @usage * * ```html * <body ng-controller="MainCtrl"> *   <ion-side-menus> *     <ion-side-menu-content> *       Content! *       <button ng-click="toggleLeftSideMenu()"> *         Toggle Left Side Menu *       </button> *     </ion-side-menu-content> *     <ion-side-menu side="left"> *       Left Menu! *     <ion-side-menu> *   </ion-side-menus> * </body> * ``` * ```js * function MainCtrl($scope, $ionicSideMenuDelegate) { *   $scope.toggleLeftSideMenu = function() { *     $ionicSideMenuDelegate.toggleLeft(); *   }; * } * ``` */IonicModule.service('$ionicSideMenuDelegate', ionic.DelegateService([  /**   * @ngdoc method   * @name $ionicSideMenuDelegate#toggleLeft   * @description Toggle the left side menu (if it exists).   * @param {boolean=} isOpen Whether to open or close the menu.   * Default: Toggles the menu.   */  'toggleLeft',  /**   * @ngdoc method   * @name $ionicSideMenuDelegate#toggleRight   * @description Toggle the right side menu (if it exists).   * @param {boolean=} isOpen Whether to open or close the menu.   * Default: Toggles the menu.   */  'toggleRight',  /**   * @ngdoc method   * @name $ionicSideMenuDelegate#getOpenRatio   * @description Gets the ratio of open amount over menu width. For example, a   * menu of width 100 that is opened by 50 pixels is 50% opened, and would return   * a ratio of 0.5.   *   * @returns {float} 0 if nothing is open, between 0 and 1 if left menu is   * opened/opening, and between 0 and -1 if right menu is opened/opening.   */  'getOpenRatio',  /**   * @ngdoc method   * @name $ionicSideMenuDelegate#isOpen   * @returns {boolean} Whether either the left or right menu is currently opened.   */  'isOpen',  /**   * @ngdoc method   * @name $ionicSideMenuDelegate#isOpenLeft   * @returns {boolean} Whether the left menu is currently opened.   */  'isOpenLeft',  /**   * @ngdoc method   * @name $ionicSideMenuDelegate#isOpenRight   * @returns {boolean} Whether the right menu is currently opened.   */  'isOpenRight',  /**   * @ngdoc method   * @name $ionicSideMenuDelegate#canDragContent   * @param {boolean=} canDrag Set whether the content can or cannot be dragged to open   * side menus.   * @returns {boolean} Whether the content can be dragged to open side menus.   */  'canDragContent',  /**   * @ngdoc method   * @name $ionicSideMenuDelegate#edgeDragThreshold   * @param {boolean|number=} value Set whether the content drag can only start if it is below a certain threshold distance from the edge of the screen. Accepts three different values:   *  - If a non-zero number is given, that many pixels is used as the maximum allowed distance from the edge that starts dragging the side menu.   *  - If true is given, the default number of pixels (25) is used as the maximum allowed distance.   *  - If false or 0 is given, the edge drag threshold is disabled, and dragging from anywhere on the content is allowed.   * @returns {boolean} Whether the drag can start only from within the edge of screen threshold.   */  'edgeDragThreshold'  /**   * @ngdoc method   * @name $ionicSideMenuDelegate#$getByHandle   * @param {string} handle   * @returns `delegateInstance` A delegate instance that controls only the   * {@link ionic.directive:ionSideMenus} directives with `delegate-handle` matching   * the given handle.   *   * Example: `$ionicSideMenuDelegate.$getByHandle('my-handle').toggleLeft();`   */]));/** * @ngdoc service * @name $ionicSlideBoxDelegate * @module ionic * @description * Delegate that controls the {@link ionic.directive:ionSlideBox} directive. * * Methods called directly on the $ionicSlideBoxDelegate service will control all slide boxes.  Use the {@link ionic.service:$ionicSlideBoxDelegate#$getByHandle $getByHandle} * method to control specific slide box instances. * * @usage * * ```html * <ion-view> *   <ion-slide-box> *     <ion-slide> *       <div class="box blue"> *         <button ng-click="nextSlide()">Next slide!</button> *       </div> *     </ion-slide> *     <ion-slide> *       <div class="box red"> *         Slide 2! *       </div> *     </ion-slide> *   </ion-slide-box> * </ion-view> * ``` * ```js * function MyCtrl($scope, $ionicSlideBoxDelegate) { *   $scope.nextSlide = function() { *     $ionicSlideBoxDelegate.next(); *   } * } * ``` */IonicModule.service('$ionicSlideBoxDelegate', ionic.DelegateService([  /**   * @ngdoc method   * @name $ionicSlideBoxDelegate#update   * @description   * Update the slidebox (for example if using Angular with ng-repeat,   * resize it for the elements inside).   */  'update',  /**   * @ngdoc method   * @name $ionicSlideBoxDelegate#slide   * @param {number} to The index to slide to.   * @param {number=} speed The number of milliseconds the change should take.   */  'slide',  'select',  /**   * @ngdoc method   * @name $ionicSlideBoxDelegate#enableSlide   * @param {boolean=} shouldEnable Whether to enable sliding the slidebox.   * @returns {boolean} Whether sliding is enabled.   */  'enableSlide',  /**   * @ngdoc method   * @name $ionicSlideBoxDelegate#previous   * @param {number=} speed The number of milliseconds the change should take.   * @description Go to the previous slide. Wraps around if at the beginning.   */  'previous',  /**   * @ngdoc method   * @name $ionicSlideBoxDelegate#next   * @param {number=} speed The number of milliseconds the change should take.   * @description Go to the next slide. Wraps around if at the end.   */  'next',  /**   * @ngdoc method   * @name $ionicSlideBoxDelegate#stop   * @description Stop sliding. The slideBox will not move again until   * explicitly told to do so.   */  'stop',  'autoPlay',  /**   * @ngdoc method   * @name $ionicSlideBoxDelegate#start   * @description Start sliding again if the slideBox was stopped.   */  'start',  /**   * @ngdoc method   * @name $ionicSlideBoxDelegate#currentIndex   * @returns number The index of the current slide.   */  'currentIndex',  'selected',  /**   * @ngdoc method   * @name $ionicSlideBoxDelegate#slidesCount   * @returns number The number of slides there are currently.   */  'slidesCount',  'count',  'loop'  /**   * @ngdoc method   * @name $ionicSlideBoxDelegate#$getByHandle   * @param {string} handle   * @returns `delegateInstance` A delegate instance that controls only the   * {@link ionic.directive:ionSlideBox} directives with `delegate-handle` matching   * the given handle.   *   * Example: `$ionicSlideBoxDelegate.$getByHandle('my-handle').stop();`   */]));/** * @ngdoc service * @name $ionicTabsDelegate * @module ionic * * @description * Delegate for controlling the {@link ionic.directive:ionTabs} directive. * * Methods called directly on the $ionicTabsDelegate service will control all ionTabs * directives. Use the {@link ionic.service:$ionicTabsDelegate#$getByHandle $getByHandle} * method to control specific ionTabs instances. * * @usage * * ```html * <body ng-controller="MyCtrl"> *   <ion-tabs> * *     <ion-tab title="Tab 1"> *       Hello tab 1! *       <button ng-click="selectTabWithIndex(1)">Select tab 2!</button> *     </ion-tab> *     <ion-tab title="Tab 2">Hello tab 2!</ion-tab> * *   </ion-tabs> * </body> * ``` * ```js * function MyCtrl($scope, $ionicTabsDelegate) { *   $scope.selectTabWithIndex = function(index) { *     $ionicTabsDelegate.select(index); *   } * } * ``` */IonicModule.service('$ionicTabsDelegate', ionic.DelegateService([  /**   * @ngdoc method   * @name $ionicTabsDelegate#select   * @description Select the tab matching the given index.   *   * @param {number} index Index of the tab to select.   */  'select',  /**   * @ngdoc method   * @name $ionicTabsDelegate#selectedIndex   * @returns `number` The index of the selected tab, or -1.   */  'selectedIndex',  /**   * @ngdoc method   * @name $ionicTabsDelegate#showBar   * @description   * Set/get whether the {@link ionic.directive:ionTabs} is shown   * @param {boolean} show Whether to show the bar.   * @returns {boolean} Whether the bar is shown.   */  'showBar'  /**   * @ngdoc method   * @name $ionicTabsDelegate#$getByHandle   * @param {string} handle   * @returns `delegateInstance` A delegate instance that controls only the   * {@link ionic.directive:ionTabs} directives with `delegate-handle` matching   * the given handle.   *   * Example: `$ionicTabsDelegate.$getByHandle('my-handle').select(0);`   */]));// closure to keep things neat(function() {  var templatesToCache = [];/** * @ngdoc service * @name $ionicTemplateCache * @module ionic * @description A service that preemptively caches template files to eliminate transition flicker and boost performance. * @usage * State templates are cached automatically, but you can optionally cache other templates. * * ```js * $ionicTemplateCache('myNgIncludeTemplate.html'); * ``` * * Optionally disable all preemptive caching with the `$ionicConfigProvider` or individual states by setting `prefetchTemplate` * in the `$state` definition * * ```js *   angular.module('myApp', ['ionic']) *   .config(function($stateProvider, $ionicConfigProvider) { * *     // disable preemptive template caching globally *     $ionicConfigProvider.templates.prefetch(false); * *     // disable individual states *     $stateProvider *       .state('tabs', { *         url: "/tab", *         abstract: true, *         prefetchTemplate: false, *         templateUrl: "tabs-templates/tabs.html" *       }) *       .state('tabs.home', { *         url: "/home", *         views: { *           'home-tab': { *             prefetchTemplate: false, *             templateUrl: "tabs-templates/home.html", *             controller: 'HomeTabCtrl' *           } *         } *       }); *   }); * ``` */IonicModule.factory('$ionicTemplateCache', ['$http','$templateCache','$timeout',function($http, $templateCache, $timeout) {  var toCache = templatesToCache,      hasRun;  function $ionicTemplateCache(templates) {    if (typeof templates === 'undefined') {      return run();    }    if (isString(templates)) {      templates = [templates];    }    forEach(templates, function(template) {      toCache.push(template);    });    if (hasRun) {      run();    }  }  // run through methods - internal method  function run() {    var template;    $ionicTemplateCache._runCount++;    hasRun = true;    // ignore if race condition already zeroed out array    if (toCache.length === 0) return;    var i = 0;    while (i < 4 && (template = toCache.pop())) {      // note that inline templates are ignored by this request      if (isString(template)) $http.get(template, { cache: $templateCache });      i++;    }    // only preload 3 templates a second    if (toCache.length) {      $timeout(run, 1000);    }  }  // exposing for testing  $ionicTemplateCache._runCount = 0;  // default method  return $ionicTemplateCache;}])// Intercepts the $stateprovider.state() command to look for templateUrls that can be cached.config(['$stateProvider','$ionicConfigProvider',function($stateProvider, $ionicConfigProvider) {  var stateProviderState = $stateProvider.state;  $stateProvider.state = function(stateName, definition) {    // don't even bother if it's disabled. note, another config may run after this, so it's not a catch-all    if (typeof definition === 'object') {      var enabled = definition.prefetchTemplate !== false && templatesToCache.length < $ionicConfigProvider.templates.maxPrefetch();      if (enabled && isString(definition.templateUrl)) templatesToCache.push(definition.templateUrl);      if (angular.isObject(definition.views)) {        for (var key in definition.views) {          enabled = definition.views[key].prefetchTemplate !== false && templatesToCache.length < $ionicConfigProvider.templates.maxPrefetch();          if (enabled && isString(definition.views[key].templateUrl)) templatesToCache.push(definition.views[key].templateUrl);        }      }    }    return stateProviderState.call($stateProvider, stateName, definition);  };}])// process the templateUrls collected by the $stateProvider, adding them to the cache.run(['$ionicTemplateCache', function($ionicTemplateCache) {  $ionicTemplateCache();}]);})();IonicModule.factory('$ionicTemplateLoader', [  '$compile',  '$controller',  '$http',  '$q',  '$rootScope',  '$templateCache',function($compile, $controller, $http, $q, $rootScope, $templateCache) {  return {    load: fetchTemplate,    compile: loadAndCompile  };  function fetchTemplate(url) {    return $http.get(url, {cache: $templateCache})    .then(function(response) {      return response.data && response.data.trim();    });  }  function loadAndCompile(options) {    options = extend({      template: '',      templateUrl: '',      scope: null,      controller: null,      locals: {},      appendTo: null    }, options || {});    var templatePromise = options.templateUrl ?      this.load(options.templateUrl) :      $q.when(options.template);    return templatePromise.then(function(template) {      var controller;      var scope = options.scope || $rootScope.$new();      //Incase template doesn't have just one root element, do this      var element = jqLite('<div>').html(template).contents();      if (options.controller) {        controller = $controller(          options.controller,          extend(options.locals, {            $scope: scope          })        );        element.children().data('$ngControllerController', controller);      }      if (options.appendTo) {        jqLite(options.appendTo).append(element);      }      $compile(element)(scope);      return {        element: element,        scope: scope      };    });  }}]);/** * @private * DEPRECATED, as of v1.0.0-beta14 ------- */IonicModule.factory('$ionicViewService', ['$ionicHistory', '$log', function($ionicHistory, $log) {  function warn(oldMethod, newMethod) {    $log.warn('$ionicViewService' + oldMethod + ' is deprecated, please use $ionicHistory' + newMethod + ' instead: http://ionicframework.com/docs/nightly/api/service/$ionicHistory/');  }  warn('', '');  var methodsMap = {    getCurrentView: 'currentView',    getBackView: 'backView',    getForwardView: 'forwardView',    getCurrentStateName: 'currentStateName',    nextViewOptions: 'nextViewOptions',    clearHistory: 'clearHistory'  };  forEach(methodsMap, function(newMethod, oldMethod) {    methodsMap[oldMethod] = function() {      warn('.' + oldMethod, '.' + newMethod);      return $ionicHistory[newMethod].apply(this, arguments);    };  });  return methodsMap;}]);/** * @private * TODO document */IonicModule.factory('$ionicViewSwitcher', [  '$timeout',  '$document',  '$q',  '$ionicClickBlock',  '$ionicConfig',  '$ionicNavBarDelegate',function($timeout, $document, $q, $ionicClickBlock, $ionicConfig, $ionicNavBarDelegate) {  var TRANSITIONEND_EVENT = 'webkitTransitionEnd transitionend';  var DATA_NO_CACHE = '$noCache';  var DATA_DESTROY_ELE = '$destroyEle';  var DATA_ELE_IDENTIFIER = '$eleId';  var DATA_VIEW_ACCESSED = '$accessed';  var DATA_FALLBACK_TIMER = '$fallbackTimer';  var DATA_VIEW = '$viewData';  var NAV_VIEW_ATTR = 'nav-view';  var VIEW_STATUS_ACTIVE = 'active';  var VIEW_STATUS_CACHED = 'cached';  var VIEW_STATUS_STAGED = 'stage';  var transitionCounter = 0;  var nextTransition, nextDirection;  ionic.transition = ionic.transition || {};  ionic.transition.isActive = false;  var isActiveTimer;  var cachedAttr = ionic.DomUtil.cachedAttr;  var transitionPromises = [];  var defaultTimeout = 1100;  var ionicViewSwitcher = {    create: function(navViewCtrl, viewLocals, enteringView, leavingView, renderStart, renderEnd) {      // get a reference to an entering/leaving element if they exist      // loop through to see if the view is already in the navViewElement      var enteringEle, leavingEle;      var transitionId = ++transitionCounter;      var alreadyInDom;      var switcher = {        init: function(registerData, callback) {          ionicViewSwitcher.isTransitioning(true);          switcher.loadViewElements(registerData);          switcher.render(registerData, function() {            callback && callback();          });        },        loadViewElements: function(registerData) {          var x, l, viewEle;          var viewElements = navViewCtrl.getViewElements();          var enteringEleIdentifier = getViewElementIdentifier(viewLocals, enteringView);          var navViewActiveEleId = navViewCtrl.activeEleId();          for (x = 0, l = viewElements.length; x < l; x++) {            viewEle = viewElements.eq(x);            if (viewEle.data(DATA_ELE_IDENTIFIER) === enteringEleIdentifier) {              // we found an existing element in the DOM that should be entering the view              if (viewEle.data(DATA_NO_CACHE)) {                // the existing element should not be cached, don't use it                viewEle.data(DATA_ELE_IDENTIFIER, enteringEleIdentifier + ionic.Utils.nextUid());                viewEle.data(DATA_DESTROY_ELE, true);              } else {                enteringEle = viewEle;              }            } else if (isDefined(navViewActiveEleId) && viewEle.data(DATA_ELE_IDENTIFIER) === navViewActiveEleId) {              leavingEle = viewEle;            }            if (enteringEle && leavingEle) break;          }          alreadyInDom = !!enteringEle;          if (!alreadyInDom) {            // still no existing element to use            // create it using existing template/scope/locals            enteringEle = registerData.ele || ionicViewSwitcher.createViewEle(viewLocals);            // existing elements in the DOM are looked up by their state name and state id            enteringEle.data(DATA_ELE_IDENTIFIER, enteringEleIdentifier);          }          if (renderEnd) {            navViewCtrl.activeEleId(enteringEleIdentifier);          }          registerData.ele = null;        },        render: function(registerData, callback) {          if (alreadyInDom) {            // it was already found in the DOM, just reconnect the scope            ionic.Utils.reconnectScope(enteringEle.scope());          } else {            // the entering element is not already in the DOM            // set that the entering element should be "staged" and its            // styles of where this element will go before it hits the DOM            navViewAttr(enteringEle, VIEW_STATUS_STAGED);            var enteringData = getTransitionData(viewLocals, enteringEle, registerData.direction, enteringView);            var transitionFn = $ionicConfig.transitions.views[enteringData.transition] || $ionicConfig.transitions.views.none;            transitionFn(enteringEle, null, enteringData.direction, true).run(0);            enteringEle.data(DATA_VIEW, {              viewId: enteringData.viewId,              historyId: enteringData.historyId,              stateName: enteringData.stateName,              stateParams: enteringData.stateParams            });            // if the current state has cache:false            // or the element has cache-view="false" attribute            if (viewState(viewLocals).cache === false || viewState(viewLocals).cache === 'false' ||                enteringEle.attr('cache-view') == 'false' || $ionicConfig.views.maxCache() === 0) {              enteringEle.data(DATA_NO_CACHE, true);            }            // append the entering element to the DOM, create a new scope and run link            var viewScope = navViewCtrl.appendViewElement(enteringEle, viewLocals);            delete enteringData.direction;            delete enteringData.transition;            viewScope.$emit('$ionicView.loaded', enteringData);          }          // update that this view was just accessed          enteringEle.data(DATA_VIEW_ACCESSED, Date.now());          callback && callback();        },        transition: function(direction, enableBack, allowAnimate) {          var deferred;          var enteringData = getTransitionData(viewLocals, enteringEle, direction, enteringView);          var leavingData = extend(extend({}, enteringData), getViewData(leavingView));          enteringData.transitionId = leavingData.transitionId = transitionId;          enteringData.fromCache = !!alreadyInDom;          enteringData.enableBack = !!enableBack;          enteringData.renderStart = renderStart;          enteringData.renderEnd = renderEnd;          cachedAttr(enteringEle.parent(), 'nav-view-transition', enteringData.transition);          cachedAttr(enteringEle.parent(), 'nav-view-direction', enteringData.direction);          // cancel any previous transition complete fallbacks          $timeout.cancel(enteringEle.data(DATA_FALLBACK_TIMER));          // get the transition ready and see if it'll animate          var transitionFn = $ionicConfig.transitions.views[enteringData.transition] || $ionicConfig.transitions.views.none;          var viewTransition = transitionFn(enteringEle, leavingEle, enteringData.direction,                                            enteringData.shouldAnimate && allowAnimate && renderEnd);          if (viewTransition.shouldAnimate) {            // attach transitionend events (and fallback timer)            enteringEle.on(TRANSITIONEND_EVENT, completeOnTransitionEnd);            enteringEle.data(DATA_FALLBACK_TIMER, $timeout(transitionComplete, defaultTimeout));            $ionicClickBlock.show(defaultTimeout);          }          if (renderStart) {            // notify the views "before" the transition starts            switcher.emit('before', enteringData, leavingData);            // stage entering element, opacity 0, no transition duration            navViewAttr(enteringEle, VIEW_STATUS_STAGED);            // render the elements in the correct location for their starting point            viewTransition.run(0);          }          if (renderEnd) {            // create a promise so we can keep track of when all transitions finish            // only required if this transition should complete            deferred = $q.defer();            transitionPromises.push(deferred.promise);          }          if (renderStart && renderEnd) {            // CSS "auto" transitioned, not manually transitioned            // wait a frame so the styles apply before auto transitioning            $timeout(function() {              ionic.requestAnimationFrame(onReflow);            });          } else if (!renderEnd) {            // just the start of a manual transition            // but it will not render the end of the transition            navViewAttr(enteringEle, 'entering');            navViewAttr(leavingEle, 'leaving');            // return the transition run method so each step can be ran manually            return {              run: viewTransition.run,              cancel: function(shouldAnimate) {                if (shouldAnimate) {                  enteringEle.on(TRANSITIONEND_EVENT, cancelOnTransitionEnd);                  enteringEle.data(DATA_FALLBACK_TIMER, $timeout(cancelTransition, defaultTimeout));                  $ionicClickBlock.show(defaultTimeout);                } else {                  cancelTransition();                }                viewTransition.shouldAnimate = shouldAnimate;                viewTransition.run(0);                viewTransition = null;              }            };          } else if (renderEnd) {            // just the end of a manual transition            // happens after the manual transition has completed            // and a full history change has happened            onReflow();          }          function onReflow() {            // remove that we're staging the entering element so it can auto transition            navViewAttr(enteringEle, viewTransition.shouldAnimate ? 'entering' : VIEW_STATUS_ACTIVE);            navViewAttr(leavingEle, viewTransition.shouldAnimate ? 'leaving' : VIEW_STATUS_CACHED);            // start the auto transition and let the CSS take over            viewTransition.run(1);            // trigger auto transitions on the associated nav bars            $ionicNavBarDelegate._instances.forEach(function(instance) {              instance.triggerTransitionStart(transitionId);            });            if (!viewTransition.shouldAnimate) {              // no animated auto transition              transitionComplete();            }          }          // Make sure that transitionend events bubbling up from children won't fire          // transitionComplete. Will only go forward if ev.target == the element listening.          function completeOnTransitionEnd(ev) {            if (ev.target !== this) return;            transitionComplete();          }          function transitionComplete() {            if (transitionComplete.x) return;            transitionComplete.x = true;            enteringEle.off(TRANSITIONEND_EVENT, completeOnTransitionEnd);            $timeout.cancel(enteringEle.data(DATA_FALLBACK_TIMER));            leavingEle && $timeout.cancel(leavingEle.data(DATA_FALLBACK_TIMER));            // resolve that this one transition (there could be many w/ nested views)            deferred && deferred.resolve(navViewCtrl);            // the most recent transition added has completed and all the active            // transition promises should be added to the services array of promises            if (transitionId === transitionCounter) {              $q.all(transitionPromises).then(ionicViewSwitcher.transitionEnd);              // emit that the views have finished transitioning              // each parent nav-view will update which views are active and cached              switcher.emit('after', enteringData, leavingData);              switcher.cleanup(enteringData);            }            // tell the nav bars that the transition has ended            $ionicNavBarDelegate._instances.forEach(function(instance) {              instance.triggerTransitionEnd();            });            // remove any references that could cause memory issues            nextTransition = nextDirection = enteringView = leavingView = enteringEle = leavingEle = null;          }          // Make sure that transitionend events bubbling up from children won't fire          // transitionComplete. Will only go forward if ev.target == the element listening.          function cancelOnTransitionEnd(ev) {            if (ev.target !== this) return;            cancelTransition();          }          function cancelTransition() {            navViewAttr(enteringEle, VIEW_STATUS_CACHED);            navViewAttr(leavingEle, VIEW_STATUS_ACTIVE);            enteringEle.off(TRANSITIONEND_EVENT, cancelOnTransitionEnd);            $timeout.cancel(enteringEle.data(DATA_FALLBACK_TIMER));            ionicViewSwitcher.transitionEnd([navViewCtrl]);          }        },      emit: function(step, enteringData, leavingData) {          var enteringScope = getScopeForElement(enteringEle, enteringData);          var leavingScope = getScopeForElement(leavingEle, leavingData);          var prefixesAreEqual;          if ( !enteringData.viewId || enteringData.abstractView ) {            // it's an abstract view, so treat it accordingly            // we only get access to the leaving scope once in the transition,            // so dispatch all events right away if it exists            if ( leavingScope ) {              leavingScope.$emit('$ionicView.beforeLeave', leavingData);              leavingScope.$emit('$ionicView.leave', leavingData);              leavingScope.$emit('$ionicView.afterLeave', leavingData);              leavingScope.$broadcast('$ionicParentView.beforeLeave', leavingData);              leavingScope.$broadcast('$ionicParentView.leave', leavingData);              leavingScope.$broadcast('$ionicParentView.afterLeave', leavingData);            }          }          else {            // it's a regular view, so do the normal process            if (step == 'after') {              if (enteringScope) {                enteringScope.$emit('$ionicView.enter', enteringData);                enteringScope.$broadcast('$ionicParentView.enter', enteringData);              }              if (leavingScope) {                leavingScope.$emit('$ionicView.leave', leavingData);                leavingScope.$broadcast('$ionicParentView.leave', leavingData);              }              else if (enteringScope && leavingData && leavingData.viewId && enteringData.stateName !== leavingData.stateName) {                // we only want to dispatch this when we are doing a single-tier                // state change such as changing a tab, so compare the state                // for the same state-prefix but different suffix                prefixesAreEqual = compareStatePrefixes(enteringData.stateName, leavingData.stateName);                if ( prefixesAreEqual ) {                  enteringScope.$emit('$ionicNavView.leave', leavingData);                }              }            }            if (enteringScope) {              enteringScope.$emit('$ionicView.' + step + 'Enter', enteringData);              enteringScope.$broadcast('$ionicParentView.' + step + 'Enter', enteringData);            }            if (leavingScope) {              leavingScope.$emit('$ionicView.' + step + 'Leave', leavingData);              leavingScope.$broadcast('$ionicParentView.' + step + 'Leave', leavingData);            } else if (enteringScope && leavingData && leavingData.viewId && enteringData.stateName !== leavingData.stateName) {              // we only want to dispatch this when we are doing a single-tier              // state change such as changing a tab, so compare the state              // for the same state-prefix but different suffix              prefixesAreEqual = compareStatePrefixes(enteringData.stateName, leavingData.stateName);              if ( prefixesAreEqual ) {                enteringScope.$emit('$ionicNavView.' + step + 'Leave', leavingData);              }            }          }        },        cleanup: function(transData) {          // check if any views should be removed          if (leavingEle && transData.direction == 'back' && !$ionicConfig.views.forwardCache()) {            // if they just navigated back we can destroy the forward view            // do not remove forward views if cacheForwardViews config is true            destroyViewEle(leavingEle);          }          var viewElements = navViewCtrl.getViewElements();          var viewElementsLength = viewElements.length;          var x, viewElement;          var removeOldestAccess = (viewElementsLength - 1) > $ionicConfig.views.maxCache();          var removableEle;          var oldestAccess = Date.now();          for (x = 0; x < viewElementsLength; x++) {            viewElement = viewElements.eq(x);            if (removeOldestAccess && viewElement.data(DATA_VIEW_ACCESSED) < oldestAccess) {              // remember what was the oldest element to be accessed so it can be destroyed              oldestAccess = viewElement.data(DATA_VIEW_ACCESSED);              removableEle = viewElements.eq(x);            } else if (viewElement.data(DATA_DESTROY_ELE) && navViewAttr(viewElement) != VIEW_STATUS_ACTIVE) {              destroyViewEle(viewElement);            }          }          destroyViewEle(removableEle);          if (enteringEle.data(DATA_NO_CACHE)) {            enteringEle.data(DATA_DESTROY_ELE, true);          }        },        enteringEle: function() { return enteringEle; },        leavingEle: function() { return leavingEle; }      };      return switcher;    },    transitionEnd: function(navViewCtrls) {      forEach(navViewCtrls, function(navViewCtrl) {        navViewCtrl.transitionEnd();      });      ionicViewSwitcher.isTransitioning(false);      $ionicClickBlock.hide();      transitionPromises = [];    },    nextTransition: function(val) {      nextTransition = val;    },    nextDirection: function(val) {      nextDirection = val;    },    isTransitioning: function(val) {      if (arguments.length) {        ionic.transition.isActive = !!val;        $timeout.cancel(isActiveTimer);        if (val) {          isActiveTimer = $timeout(function() {            ionicViewSwitcher.isTransitioning(false);          }, 999);        }      }      return ionic.transition.isActive;    },    createViewEle: function(viewLocals) {      var containerEle = $document[0].createElement('div');      if (viewLocals && viewLocals.$template) {        containerEle.innerHTML = viewLocals.$template;        if (containerEle.children.length === 1) {          containerEle.children[0].classList.add('pane');          if ( viewLocals.$$state && viewLocals.$$state.self && viewLocals.$$state.self['abstract'] ) {            angular.element(containerEle.children[0]).attr("abstract", "true");          }          else {            if ( viewLocals.$$state && viewLocals.$$state.self ) {              angular.element(containerEle.children[0]).attr("state", viewLocals.$$state.self.name);            }          }          return jqLite(containerEle.children[0]);        }      }      containerEle.className = "pane";      return jqLite(containerEle);    },    viewEleIsActive: function(viewEle, isActiveAttr) {      navViewAttr(viewEle, isActiveAttr ? VIEW_STATUS_ACTIVE : VIEW_STATUS_CACHED);    },    getTransitionData: getTransitionData,    navViewAttr: navViewAttr,    destroyViewEle: destroyViewEle  };  return ionicViewSwitcher;  function getViewElementIdentifier(locals, view) {    if (viewState(locals)['abstract']) return viewState(locals).name;    if (view) return view.stateId || view.viewId;    return ionic.Utils.nextUid();  }  function viewState(locals) {    return locals && locals.$$state && locals.$$state.self || {};  }  function getTransitionData(viewLocals, enteringEle, direction, view) {    // Priority    // 1) attribute directive on the button/link to this view    // 2) entering element's attribute    // 3) entering view's $state config property    // 4) view registration data    // 5) global config    // 6) fallback value    var state = viewState(viewLocals);    var viewTransition = nextTransition || cachedAttr(enteringEle, 'view-transition') || state.viewTransition || $ionicConfig.views.transition() || 'ios';    var navBarTransition = $ionicConfig.navBar.transition();    direction = nextDirection || cachedAttr(enteringEle, 'view-direction') || state.viewDirection || direction || 'none';    return extend(getViewData(view), {      transition: viewTransition,      navBarTransition: navBarTransition === 'view' ? viewTransition : navBarTransition,      direction: direction,      shouldAnimate: (viewTransition !== 'none' && direction !== 'none')    });  }  function getViewData(view) {    view = view || {};    return {      viewId: view.viewId,      historyId: view.historyId,      stateId: view.stateId,      stateName: view.stateName,      stateParams: view.stateParams    };  }  function navViewAttr(ele, value) {    if (arguments.length > 1) {      cachedAttr(ele, NAV_VIEW_ATTR, value);    } else {      return cachedAttr(ele, NAV_VIEW_ATTR);    }  }  function destroyViewEle(ele) {    // we found an element that should be removed    // destroy its scope, then remove the element    if (ele && ele.length) {      var viewScope = ele.scope();      if (viewScope) {        viewScope.$emit('$ionicView.unloaded', ele.data(DATA_VIEW));        viewScope.$destroy();      }      ele.remove();    }  }  function compareStatePrefixes(enteringStateName, exitingStateName) {    var enteringStateSuffixIndex = enteringStateName.lastIndexOf('.');    var exitingStateSuffixIndex = exitingStateName.lastIndexOf('.');    // if either of the prefixes are empty, just return false    if ( enteringStateSuffixIndex < 0 || exitingStateSuffixIndex < 0 ) {      return false;    }    var enteringPrefix = enteringStateName.substring(0, enteringStateSuffixIndex);    var exitingPrefix = exitingStateName.substring(0, exitingStateSuffixIndex);    return enteringPrefix === exitingPrefix;  }  function getScopeForElement(element, stateData) {    if ( !element ) {      return null;    }    // check if it's abstract    var attributeValue = angular.element(element).attr("abstract");    var stateValue = angular.element(element).attr("state");    if ( attributeValue !== "true" ) {      // it's not an abstract view, so make sure the element      // matches the state.  Due to abstract view weirdness,      // sometimes it doesn't. If it doesn't, don't dispatch events      // so leave the scope undefined      if ( stateValue === stateData.stateName ) {        return angular.element(element).scope();      }      return null;    }    else {      // it is an abstract element, so look for element with the "state" attributeValue      // set to the name of the stateData state      var elements = aggregateNavViewChildren(element);      for ( var i = 0; i < elements.length; i++ ) {          var state = angular.element(elements[i]).attr("state");          if ( state === stateData.stateName ) {            stateData.abstractView = true;            return angular.element(elements[i]).scope();          }      }      // we didn't find a match, so return null      return null;    }  }  function aggregateNavViewChildren(element) {    var aggregate = [];    var navViews = angular.element(element).find("ion-nav-view");    for ( var i = 0; i < navViews.length; i++ ) {      var children = angular.element(navViews[i]).children();      var childrenAggregated = [];      for ( var j = 0; j < children.length; j++ ) {        childrenAggregated = childrenAggregated.concat(children[j]);      }      aggregate = aggregate.concat(childrenAggregated);    }    return aggregate;  }}]);/** * ==================  angular-ios9-uiwebview.patch.js v1.1.1 ================== * * This patch works around iOS9 UIWebView regression that causes infinite digest * errors in Angular. * * The patch can be applied to Angular 1.2.0 – 1.4.5. Newer versions of Angular * have the workaround baked in. * * To apply this patch load/bundle this file with your application and add a * dependency on the "ngIOS9UIWebViewPatch" module to your main app module. * * For example: * * ``` * angular.module('myApp', ['ngRoute'])` * ``` * * becomes * * ``` * angular.module('myApp', ['ngRoute', 'ngIOS9UIWebViewPatch']) * ``` * * * More info: * - https://openradar.appspot.com/22186109 * - https://github.com/angular/angular.js/issues/12241 * - https://github.com/driftyco/ionic/issues/4082 * * * @license AngularJS * (c) 2010-2015 Google, Inc. http://angularjs.org * License: MIT */angular.module('ngIOS9UIWebViewPatch', ['ng']).config(['$provide', function($provide) {  'use strict';  $provide.decorator('$browser', ['$delegate', '$window', function($delegate, $window) {    if (isIOS9UIWebView($window.navigator.userAgent)) {      return applyIOS9Shim($delegate);    }    return $delegate;    function isIOS9UIWebView(userAgent) {      return /(iPhone|iPad|iPod).* OS 9_\d/.test(userAgent) && !/Version\/9\./.test(userAgent);    }    function applyIOS9Shim(browser) {      var pendingLocationUrl = null;      var originalUrlFn = browser.url;      browser.url = function() {        if (arguments.length) {          pendingLocationUrl = arguments[0];          return originalUrlFn.apply(browser, arguments);        }        return pendingLocationUrl || originalUrlFn.apply(browser, arguments);      };      window.addEventListener('popstate', clearPendingLocationUrl, false);      window.addEventListener('hashchange', clearPendingLocationUrl, false);      function clearPendingLocationUrl() {        pendingLocationUrl = null;      }      return browser;    }  }]);}]);/** * @private * Parts of Ionic requires that $scope data is attached to the element. * We do not want to disable adding $scope data to the $element when * $compileProvider.debugInfoEnabled(false) is used. */IonicModule.config(['$provide', function($provide) {  $provide.decorator('$compile', ['$delegate', function($compile) {     $compile.$$addScopeInfo = function $$addScopeInfo($element, scope, isolated, noTemplate) {       var dataName = isolated ? (noTemplate ? '$isolateScopeNoTemplate' : '$isolateScope') : '$scope';       $element.data(dataName, scope);     };     return $compile;  }]);}]);/** * @private */IonicModule.config([  '$provide',function($provide) {  function $LocationDecorator($location, $timeout) {    $location.__hash = $location.hash;    //Fix: when window.location.hash is set, the scrollable area    //found nearest to body's scrollTop is set to scroll to an element    //with that ID.    $location.hash = function(value) {      if (isDefined(value) && value.length > 0) {        $timeout(function() {          var scroll = document.querySelector('.scroll-content');          if (scroll) {            scroll.scrollTop = 0;          }        }, 0, false);      }      return $location.__hash(value);    };    return $location;  }  $provide.decorator('$location', ['$delegate', '$timeout', $LocationDecorator]);}]);IonicModule.controller('$ionicHeaderBar', [  '$scope',  '$element',  '$attrs',  '$q',  '$ionicConfig',  '$ionicHistory',function($scope, $element, $attrs, $q, $ionicConfig, $ionicHistory) {  var TITLE = 'title';  var BACK_TEXT = 'back-text';  var BACK_BUTTON = 'back-button';  var DEFAULT_TITLE = 'default-title';  var PREVIOUS_TITLE = 'previous-title';  var HIDE = 'hide';  var self = this;  var titleText = '';  var previousTitleText = '';  var titleLeft = 0;  var titleRight = 0;  var titleCss = '';  var isBackEnabled = false;  var isBackShown = true;  var isNavBackShown = true;  var isBackElementShown = false;  var titleTextWidth = 0;  self.beforeEnter = function(viewData) {    $scope.$broadcast('$ionicView.beforeEnter', viewData);  };  self.title = function(newTitleText) {    if (arguments.length && newTitleText !== titleText) {      getEle(TITLE).innerHTML = newTitleText;      titleText = newTitleText;      titleTextWidth = 0;    }    return titleText;  };  self.enableBack = function(shouldEnable, disableReset) {    // whether or not the back button show be visible, according    // to the navigation and history    if (arguments.length) {      isBackEnabled = shouldEnable;      if (!disableReset) self.updateBackButton();    }    return isBackEnabled;  };  self.showBack = function(shouldShow, disableReset) {    // different from enableBack() because this will always have the back    // visually hidden if false, even if the history says it should show    if (arguments.length) {      isBackShown = shouldShow;      if (!disableReset) self.updateBackButton();    }    return isBackShown;  };  self.showNavBack = function(shouldShow) {    // different from showBack() because this is for the entire nav bar's    // setting for all of it's child headers. For internal use.    isNavBackShown = shouldShow;    self.updateBackButton();  };  self.updateBackButton = function() {    var ele;    if ((isBackShown && isNavBackShown && isBackEnabled) !== isBackElementShown) {      isBackElementShown = isBackShown && isNavBackShown && isBackEnabled;      ele = getEle(BACK_BUTTON);      ele && ele.classList[ isBackElementShown ? 'remove' : 'add' ](HIDE);    }    if (isBackEnabled) {      ele = ele || getEle(BACK_BUTTON);      if (ele) {        if (self.backButtonIcon !== $ionicConfig.backButton.icon()) {          ele = getEle(BACK_BUTTON + ' .icon');          if (ele) {            self.backButtonIcon = $ionicConfig.backButton.icon();            ele.className = 'icon ' + self.backButtonIcon;          }        }        if (self.backButtonText !== $ionicConfig.backButton.text()) {          ele = getEle(BACK_BUTTON + ' .back-text');          if (ele) {            ele.textContent = self.backButtonText = $ionicConfig.backButton.text();          }        }      }    }  };  self.titleTextWidth = function() {    var element = getEle(TITLE);    if ( element ) {      // If the element has a nav-bar-title, use that instead      // to calculate the width of the title      var children = angular.element(element).children();      for ( var i = 0; i < children.length; i++ ) {        if ( angular.element(children[i]).hasClass('nav-bar-title') ) {          element = children[i];          break;        }      }    }    var bounds = ionic.DomUtil.getTextBounds(element);    titleTextWidth = Math.min(bounds && bounds.width || 30);    return titleTextWidth;  };  self.titleWidth = function() {    var titleWidth = self.titleTextWidth();    var offsetWidth = getEle(TITLE).offsetWidth;    if (offsetWidth < titleWidth) {      titleWidth = offsetWidth + (titleLeft - titleRight - 5);    }    return titleWidth;  };  self.titleTextX = function() {    return ($element[0].offsetWidth / 2) - (self.titleWidth() / 2);  };  self.titleLeftRight = function() {    return titleLeft - titleRight;  };  self.backButtonTextLeft = function() {    var offsetLeft = 0;    var ele = getEle(BACK_TEXT);    while (ele) {      offsetLeft += ele.offsetLeft;      ele = ele.parentElement;    }    return offsetLeft;  };  self.resetBackButton = function(viewData) {    if ($ionicConfig.backButton.previousTitleText()) {      var previousTitleEle = getEle(PREVIOUS_TITLE);      if (previousTitleEle) {        previousTitleEle.classList.remove(HIDE);        var view = (viewData && $ionicHistory.getViewById(viewData.viewId));        var newPreviousTitleText = $ionicHistory.backTitle(view);        if (newPreviousTitleText !== previousTitleText) {          previousTitleText = previousTitleEle.innerHTML = newPreviousTitleText;        }      }      var defaultTitleEle = getEle(DEFAULT_TITLE);      if (defaultTitleEle) {        defaultTitleEle.classList.remove(HIDE);      }    }  };  self.align = function(textAlign) {    var titleEle = getEle(TITLE);    textAlign = textAlign || $attrs.alignTitle || $ionicConfig.navBar.alignTitle();    var widths = self.calcWidths(textAlign, false);    if (isBackShown && previousTitleText && $ionicConfig.backButton.previousTitleText()) {      var previousTitleWidths = self.calcWidths(textAlign, true);      var availableTitleWidth = $element[0].offsetWidth - previousTitleWidths.titleLeft - previousTitleWidths.titleRight;      if (self.titleTextWidth() <= availableTitleWidth) {        widths = previousTitleWidths;      }    }    return self.updatePositions(titleEle, widths.titleLeft, widths.titleRight, widths.buttonsLeft, widths.buttonsRight, widths.css, widths.showPrevTitle);  };  self.calcWidths = function(textAlign, isPreviousTitle) {    var titleEle = getEle(TITLE);    var backBtnEle = getEle(BACK_BUTTON);    var x, y, z, b, c, d, childSize, bounds;    var childNodes = $element[0].childNodes;    var buttonsLeft = 0;    var buttonsRight = 0;    var isCountRightOfTitle;    var updateTitleLeft = 0;    var updateTitleRight = 0;    var updateCss = '';    var backButtonWidth = 0;    // Compute how wide the left children are    // Skip all titles (there may still be two titles, one leaving the dom)    // Once we encounter a titleEle, realize we are now counting the right-buttons, not left    for (x = 0; x < childNodes.length; x++) {      c = childNodes[x];      childSize = 0;      if (c.nodeType == 1) {        // element node        if (c === titleEle) {          isCountRightOfTitle = true;          continue;        }        if (c.classList.contains(HIDE)) {          continue;        }        if (isBackShown && c === backBtnEle) {          for (y = 0; y < c.childNodes.length; y++) {            b = c.childNodes[y];            if (b.nodeType == 1) {              if (b.classList.contains(BACK_TEXT)) {                for (z = 0; z < b.children.length; z++) {                  d = b.children[z];                  if (isPreviousTitle) {                    if (d.classList.contains(DEFAULT_TITLE)) continue;                    backButtonWidth += d.offsetWidth;                  } else {                    if (d.classList.contains(PREVIOUS_TITLE)) continue;                    backButtonWidth += d.offsetWidth;                  }                }              } else {                backButtonWidth += b.offsetWidth;              }            } else if (b.nodeType == 3 && b.nodeValue.trim()) {              bounds = ionic.DomUtil.getTextBounds(b);              backButtonWidth += bounds && bounds.width || 0;            }          }          childSize = backButtonWidth || c.offsetWidth;        } else {          // not the title, not the back button, not a hidden element          childSize = c.offsetWidth;        }      } else if (c.nodeType == 3 && c.nodeValue.trim()) {        // text node        bounds = ionic.DomUtil.getTextBounds(c);        childSize = bounds && bounds.width || 0;      }      if (isCountRightOfTitle) {        buttonsRight += childSize;      } else {        buttonsLeft += childSize;      }    }    // Size and align the header titleEle based on the sizes of the left and    // right children, and the desired alignment mode    if (textAlign == 'left') {      updateCss = 'title-left';      if (buttonsLeft) {        updateTitleLeft = buttonsLeft + 15;      }      if (buttonsRight) {        updateTitleRight = buttonsRight + 15;      }    } else if (textAlign == 'right') {      updateCss = 'title-right';      if (buttonsLeft) {        updateTitleLeft = buttonsLeft + 15;      }      if (buttonsRight) {        updateTitleRight = buttonsRight + 15;      }    } else {      // center the default      var margin = Math.max(buttonsLeft, buttonsRight) + 10;      if (margin > 10) {        updateTitleLeft = updateTitleRight = margin;      }    }    return {      backButtonWidth: backButtonWidth,      buttonsLeft: buttonsLeft,      buttonsRight: buttonsRight,      titleLeft: updateTitleLeft,      titleRight: updateTitleRight,      showPrevTitle: isPreviousTitle,      css: updateCss    };  };  self.updatePositions = function(titleEle, updateTitleLeft, updateTitleRight, buttonsLeft, buttonsRight, updateCss, showPreviousTitle) {    var deferred = $q.defer();    // only make DOM updates when there are actual changes    if (titleEle) {      if (updateTitleLeft !== titleLeft) {        titleEle.style.left = updateTitleLeft ? updateTitleLeft + 'px' : '';        titleLeft = updateTitleLeft;      }      if (updateTitleRight !== titleRight) {        titleEle.style.right = updateTitleRight ? updateTitleRight + 'px' : '';        titleRight = updateTitleRight;      }      if (updateCss !== titleCss) {        updateCss && titleEle.classList.add(updateCss);        titleCss && titleEle.classList.remove(titleCss);        titleCss = updateCss;      }    }    if ($ionicConfig.backButton.previousTitleText()) {      var prevTitle = getEle(PREVIOUS_TITLE);      var defaultTitle = getEle(DEFAULT_TITLE);      prevTitle && prevTitle.classList[ showPreviousTitle ? 'remove' : 'add'](HIDE);      defaultTitle && defaultTitle.classList[ showPreviousTitle ? 'add' : 'remove'](HIDE);    }    ionic.requestAnimationFrame(function() {      if (titleEle && titleEle.offsetWidth + 10 < titleEle.scrollWidth) {        var minRight = buttonsRight + 5;        var testRight = $element[0].offsetWidth - titleLeft - self.titleTextWidth() - 20;        updateTitleRight = testRight < minRight ? minRight : testRight;        if (updateTitleRight !== titleRight) {          titleEle.style.right = updateTitleRight + 'px';          titleRight = updateTitleRight;        }      }      deferred.resolve();    });    return deferred.promise;  };  self.setCss = function(elementClassname, css) {    ionic.DomUtil.cachedStyles(getEle(elementClassname), css);  };  var eleCache = {};  function getEle(className) {    if (!eleCache[className]) {      eleCache[className] = $element[0].querySelector('.' + className);    }    return eleCache[className];  }  $scope.$on('$destroy', function() {    for (var n in eleCache) eleCache[n] = null;  });}]);IonicModule.controller('$ionInfiniteScroll', [  '$scope',  '$attrs',  '$element',  '$timeout',function($scope, $attrs, $element, $timeout) {  var self = this;  self.isLoading = false;  $scope.icon = function() {    return isDefined($attrs.icon) ? $attrs.icon : 'ion-load-d';  };  $scope.spinner = function() {    return isDefined($attrs.spinner) ? $attrs.spinner : '';  };  $scope.$on('scroll.infiniteScrollComplete', function() {    finishInfiniteScroll();  });  $scope.$on('$destroy', function() {    if (self.scrollCtrl && self.scrollCtrl.$element) self.scrollCtrl.$element.off('scroll', self.checkBounds);    if (self.scrollEl && self.scrollEl.removeEventListener) {      self.scrollEl.removeEventListener('scroll', self.checkBounds);    }  });  // debounce checking infinite scroll events  self.checkBounds = ionic.Utils.throttle(checkInfiniteBounds, 300);  function onInfinite() {    ionic.requestAnimationFrame(function() {      $element[0].classList.add('active');    });    self.isLoading = true;    $scope.$parent && $scope.$parent.$apply($attrs.onInfinite || '');  }  function finishInfiniteScroll() {    ionic.requestAnimationFrame(function() {      $element[0].classList.remove('active');    });    $timeout(function() {      if (self.jsScrolling) self.scrollView.resize();      // only check bounds again immediately if the page isn't cached (scroll el has height)      if ((self.jsScrolling && self.scrollView.__container && self.scrollView.__container.offsetHeight > 0) ||      !self.jsScrolling) {        self.checkBounds();      }    }, 30, false);    self.isLoading = false;  }  // check if we've scrolled far enough to trigger an infinite scroll  function checkInfiniteBounds() {    if (self.isLoading) return;    var maxScroll = {};    if (self.jsScrolling) {      maxScroll = self.getJSMaxScroll();      var scrollValues = self.scrollView.getValues();      if ((maxScroll.left !== -1 && scrollValues.left >= maxScroll.left) ||        (maxScroll.top !== -1 && scrollValues.top >= maxScroll.top)) {        onInfinite();      }    } else {      maxScroll = self.getNativeMaxScroll();      if ((        maxScroll.left !== -1 &&        self.scrollEl.scrollLeft >= maxScroll.left - self.scrollEl.clientWidth        ) || (        maxScroll.top !== -1 &&        self.scrollEl.scrollTop >= maxScroll.top - self.scrollEl.clientHeight        )) {        onInfinite();      }    }  }  // determine the threshold at which we should fire an infinite scroll  // note: this gets processed every scroll event, can it be cached?  self.getJSMaxScroll = function() {    var maxValues = self.scrollView.getScrollMax();    return {      left: self.scrollView.options.scrollingX ?        calculateMaxValue(maxValues.left) :        -1,      top: self.scrollView.options.scrollingY ?        calculateMaxValue(maxValues.top) :        -1    };  };  self.getNativeMaxScroll = function() {    var maxValues = {      left: self.scrollEl.scrollWidth,      top: self.scrollEl.scrollHeight    };    var computedStyle = window.getComputedStyle(self.scrollEl) || {};    return {      left: maxValues.left &&        (computedStyle.overflowX === 'scroll' ||        computedStyle.overflowX === 'auto' ||        self.scrollEl.style['overflow-x'] === 'scroll') ?        calculateMaxValue(maxValues.left) : -1,      top: maxValues.top &&        (computedStyle.overflowY === 'scroll' ||        computedStyle.overflowY === 'auto' ||        self.scrollEl.style['overflow-y'] === 'scroll' ) ?        calculateMaxValue(maxValues.top) : -1    };  };  // determine pixel refresh distance based on % or value  function calculateMaxValue(maximum) {    var distance = ($attrs.distance || '2.5%').trim();    var isPercent = distance.indexOf('%') !== -1;    return isPercent ?    maximum * (1 - parseFloat(distance) / 100) :    maximum - parseFloat(distance);  }  //for testing  self.__finishInfiniteScroll = finishInfiniteScroll;}]);/** * @ngdoc service * @name $ionicListDelegate * @module ionic * * @description * Delegate for controlling the {@link ionic.directive:ionList} directive. * * Methods called directly on the $ionicListDelegate service will control all lists. * Use the {@link ionic.service:$ionicListDelegate#$getByHandle $getByHandle} * method to control specific ionList instances. * * @usage * ```html * {% raw %} * <ion-content ng-controller="MyCtrl"> *   <button class="button" ng-click="showDeleteButtons()"></button> *   <ion-list> *     <ion-item ng-repeat="i in items"> *       Hello, {{i}}! *       <ion-delete-button class="ion-minus-circled"></ion-delete-button> *     </ion-item> *   </ion-list> * </ion-content> * {% endraw %} * ``` * ```js * function MyCtrl($scope, $ionicListDelegate) { *   $scope.showDeleteButtons = function() { *     $ionicListDelegate.showDelete(true); *   }; * } * ``` */IonicModule.service('$ionicListDelegate', ionic.DelegateService([  /**   * @ngdoc method   * @name $ionicListDelegate#showReorder   * @param {boolean=} showReorder Set whether or not this list is showing its reorder buttons.   * @returns {boolean} Whether the reorder buttons are shown.   */  'showReorder',  /**   * @ngdoc method   * @name $ionicListDelegate#showDelete   * @param {boolean=} showDelete Set whether or not this list is showing its delete buttons.   * @returns {boolean} Whether the delete buttons are shown.   */  'showDelete',  /**   * @ngdoc method   * @name $ionicListDelegate#canSwipeItems   * @param {boolean=} canSwipeItems Set whether or not this list is able to swipe to show   * option buttons.   * @returns {boolean} Whether the list is able to swipe to show option buttons.   */  'canSwipeItems',  /**   * @ngdoc method   * @name $ionicListDelegate#closeOptionButtons   * @description Closes any option buttons on the list that are swiped open.   */  'closeOptionButtons'  /**   * @ngdoc method   * @name $ionicListDelegate#$getByHandle   * @param {string} handle   * @returns `delegateInstance` A delegate instance that controls only the   * {@link ionic.directive:ionList} directives with `delegate-handle` matching   * the given handle.   *   * Example: `$ionicListDelegate.$getByHandle('my-handle').showReorder(true);`   */])).controller('$ionicList', [  '$scope',  '$attrs',  '$ionicListDelegate',  '$ionicHistory',function($scope, $attrs, $ionicListDelegate, $ionicHistory) {  var self = this;  var isSwipeable = true;  var isReorderShown = false;  var isDeleteShown = false;  var deregisterInstance = $ionicListDelegate._registerInstance(    self, $attrs.delegateHandle, function() {      return $ionicHistory.isActiveScope($scope);    }  );  $scope.$on('$destroy', deregisterInstance);  self.showReorder = function(show) {    if (arguments.length) {      isReorderShown = !!show;    }    return isReorderShown;  };  self.showDelete = function(show) {    if (arguments.length) {      isDeleteShown = !!show;    }    return isDeleteShown;  };  self.canSwipeItems = function(can) {    if (arguments.length) {      isSwipeable = !!can;    }    return isSwipeable;  };  self.closeOptionButtons = function() {    self.listView && self.listView.clearDragEffects();  };}]);IonicModule.controller('$ionicNavBar', [  '$scope',  '$element',  '$attrs',  '$compile',  '$timeout',  '$ionicNavBarDelegate',  '$ionicConfig',  '$ionicHistory',function($scope, $element, $attrs, $compile, $timeout, $ionicNavBarDelegate, $ionicConfig, $ionicHistory) {  var CSS_HIDE = 'hide';  var DATA_NAV_BAR_CTRL = '$ionNavBarController';  var PRIMARY_BUTTONS = 'primaryButtons';  var SECONDARY_BUTTONS = 'secondaryButtons';  var BACK_BUTTON = 'backButton';  var ITEM_TYPES = 'primaryButtons secondaryButtons leftButtons rightButtons title'.split(' ');  var self = this;  var headerBars = [];  var navElementHtml = {};  var isVisible = true;  var queuedTransitionStart, queuedTransitionEnd, latestTransitionId;  $element.parent().data(DATA_NAV_BAR_CTRL, self);  var delegateHandle = $attrs.delegateHandle || 'navBar' + ionic.Utils.nextUid();  var deregisterInstance = $ionicNavBarDelegate._registerInstance(self, delegateHandle);  self.init = function() {    $element.addClass('nav-bar-container');    ionic.DomUtil.cachedAttr($element, 'nav-bar-transition', $ionicConfig.views.transition());    // create two nav bar blocks which will trade out which one is shown    self.createHeaderBar(false);    self.createHeaderBar(true);    $scope.$emit('ionNavBar.init', delegateHandle);  };  self.createHeaderBar = function(isActive) {    var containerEle = jqLite('<div class="nav-bar-block">');    ionic.DomUtil.cachedAttr(containerEle, 'nav-bar', isActive ? 'active' : 'cached');    var alignTitle = $attrs.alignTitle || $ionicConfig.navBar.alignTitle();    var headerBarEle = jqLite('<ion-header-bar>').addClass($attrs['class']).attr('align-title', alignTitle);    if (isDefined($attrs.noTapScroll)) headerBarEle.attr('no-tap-scroll', $attrs.noTapScroll);    var titleEle = jqLite('<div class="title title-' + alignTitle + '">');    var navEle = {};    var lastViewItemEle = {};    var leftButtonsEle, rightButtonsEle;    navEle[BACK_BUTTON] = createNavElement(BACK_BUTTON);    navEle[BACK_BUTTON] && headerBarEle.append(navEle[BACK_BUTTON]);    // append title in the header, this is the rock to where buttons append    headerBarEle.append(titleEle);    forEach(ITEM_TYPES, function(itemType) {      // create default button elements      navEle[itemType] = createNavElement(itemType);      // append and position buttons      positionItem(navEle[itemType], itemType);    });    // add header-item to the root children    for (var x = 0; x < headerBarEle[0].children.length; x++) {      headerBarEle[0].children[x].classList.add('header-item');    }    // compile header and append to the DOM    containerEle.append(headerBarEle);    $element.append($compile(containerEle)($scope.$new()));    var headerBarCtrl = headerBarEle.data('$ionHeaderBarController');    headerBarCtrl.backButtonIcon = $ionicConfig.backButton.icon();    headerBarCtrl.backButtonText = $ionicConfig.backButton.text();    var headerBarInstance = {      isActive: isActive,      title: function(newTitleText) {        headerBarCtrl.title(newTitleText);      },      setItem: function(navBarItemEle, itemType) {        // first make sure any exiting nav bar item has been removed        headerBarInstance.removeItem(itemType);        if (navBarItemEle) {          if (itemType === 'title') {            // clear out the text based title            headerBarInstance.title("");          }          // there's a custom nav bar item          positionItem(navBarItemEle, itemType);          if (navEle[itemType]) {            // make sure the default on this itemType is hidden            navEle[itemType].addClass(CSS_HIDE);          }          lastViewItemEle[itemType] = navBarItemEle;        } else if (navEle[itemType]) {          // there's a default button for this side and no view button          navEle[itemType].removeClass(CSS_HIDE);        }      },      removeItem: function(itemType) {        if (lastViewItemEle[itemType]) {          lastViewItemEle[itemType].scope().$destroy();          lastViewItemEle[itemType].remove();          lastViewItemEle[itemType] = null;        }      },      containerEle: function() {        return containerEle;      },      headerBarEle: function() {        return headerBarEle;      },      afterLeave: function() {        forEach(ITEM_TYPES, function(itemType) {          headerBarInstance.removeItem(itemType);        });        headerBarCtrl.resetBackButton();      },      controller: function() {        return headerBarCtrl;      },      destroy: function() {        forEach(ITEM_TYPES, function(itemType) {          headerBarInstance.removeItem(itemType);        });        containerEle.scope().$destroy();        for (var n in navEle) {          if (navEle[n]) {            navEle[n].removeData();            navEle[n] = null;          }        }        leftButtonsEle && leftButtonsEle.removeData();        rightButtonsEle && rightButtonsEle.removeData();        titleEle.removeData();        headerBarEle.removeData();        containerEle.remove();        containerEle = headerBarEle = titleEle = leftButtonsEle = rightButtonsEle = null;      }    };    function positionItem(ele, itemType) {      if (!ele) return;      if (itemType === 'title') {        // title element        titleEle.append(ele);      } else if (itemType == 'rightButtons' ||                (itemType == SECONDARY_BUTTONS && $ionicConfig.navBar.positionSecondaryButtons() != 'left') ||                (itemType == PRIMARY_BUTTONS && $ionicConfig.navBar.positionPrimaryButtons() == 'right')) {        // right side        if (!rightButtonsEle) {          rightButtonsEle = jqLite('<div class="buttons buttons-right">');          headerBarEle.append(rightButtonsEle);        }        if (itemType == SECONDARY_BUTTONS) {          rightButtonsEle.append(ele);        } else {          rightButtonsEle.prepend(ele);        }      } else {        // left side        if (!leftButtonsEle) {          leftButtonsEle = jqLite('<div class="buttons buttons-left">');          if (navEle[BACK_BUTTON]) {            navEle[BACK_BUTTON].after(leftButtonsEle);          } else {            headerBarEle.prepend(leftButtonsEle);          }        }        if (itemType == SECONDARY_BUTTONS) {          leftButtonsEle.append(ele);        } else {          leftButtonsEle.prepend(ele);        }      }    }    headerBars.push(headerBarInstance);    return headerBarInstance;  };  self.navElement = function(type, html) {    if (isDefined(html)) {      navElementHtml[type] = html;    }    return navElementHtml[type];  };  self.update = function(viewData) {    var showNavBar = !viewData.hasHeaderBar && viewData.showNavBar;    viewData.transition = $ionicConfig.views.transition();    if (!showNavBar) {      viewData.direction = 'none';    }    self.enable(showNavBar);    var enteringHeaderBar = self.isInitialized ? getOffScreenHeaderBar() : getOnScreenHeaderBar();    var leavingHeaderBar = self.isInitialized ? getOnScreenHeaderBar() : null;    var enteringHeaderCtrl = enteringHeaderBar.controller();    // update if the entering header should show the back button or not    enteringHeaderCtrl.enableBack(viewData.enableBack, true);    enteringHeaderCtrl.showBack(viewData.showBack, true);    enteringHeaderCtrl.updateBackButton();    // update the entering header bar's title    self.title(viewData.title, enteringHeaderBar);    self.showBar(showNavBar);    // update the nav bar items, depending if the view has their own or not    if (viewData.navBarItems) {      forEach(ITEM_TYPES, function(itemType) {        enteringHeaderBar.setItem(viewData.navBarItems[itemType], itemType);      });    }    // begin transition of entering and leaving header bars    self.transition(enteringHeaderBar, leavingHeaderBar, viewData);    self.isInitialized = true;    navSwipeAttr('');  };  self.transition = function(enteringHeaderBar, leavingHeaderBar, viewData) {    var enteringHeaderBarCtrl = enteringHeaderBar.controller();    var transitionFn = $ionicConfig.transitions.navBar[viewData.navBarTransition] || $ionicConfig.transitions.navBar.none;    var transitionId = viewData.transitionId;    enteringHeaderBarCtrl.beforeEnter(viewData);    var navBarTransition = transitionFn(enteringHeaderBar, leavingHeaderBar, viewData.direction, viewData.shouldAnimate && self.isInitialized);    ionic.DomUtil.cachedAttr($element, 'nav-bar-transition', viewData.navBarTransition);    ionic.DomUtil.cachedAttr($element, 'nav-bar-direction', viewData.direction);    if (navBarTransition.shouldAnimate && viewData.renderEnd) {      navBarAttr(enteringHeaderBar, 'stage');    } else {      navBarAttr(enteringHeaderBar, 'entering');      navBarAttr(leavingHeaderBar, 'leaving');    }    enteringHeaderBarCtrl.resetBackButton(viewData);    navBarTransition.run(0);    self.activeTransition = {      run: function(step) {        navBarTransition.shouldAnimate = false;        navBarTransition.direction = 'back';        navBarTransition.run(step);      },      cancel: function(shouldAnimate, speed, cancelData) {        navSwipeAttr(speed);        navBarAttr(leavingHeaderBar, 'active');        navBarAttr(enteringHeaderBar, 'cached');        navBarTransition.shouldAnimate = shouldAnimate;        navBarTransition.run(0);        self.activeTransition = navBarTransition = null;        var runApply;        if (cancelData.showBar !== self.showBar()) {          self.showBar(cancelData.showBar);        }        if (cancelData.showBackButton !== self.showBackButton()) {          self.showBackButton(cancelData.showBackButton);        }        if (runApply) {          $scope.$apply();        }      },      complete: function(shouldAnimate, speed) {        navSwipeAttr(speed);        navBarTransition.shouldAnimate = shouldAnimate;        navBarTransition.run(1);        queuedTransitionEnd = transitionEnd;      }    };    $timeout(enteringHeaderBarCtrl.align, 16);    queuedTransitionStart = function() {      if (latestTransitionId !== transitionId) return;      navBarAttr(enteringHeaderBar, 'entering');      navBarAttr(leavingHeaderBar, 'leaving');      navBarTransition.run(1);      queuedTransitionEnd = function() {        if (latestTransitionId == transitionId || !navBarTransition.shouldAnimate) {          transitionEnd();        }      };      queuedTransitionStart = null;    };    function transitionEnd() {      for (var x = 0; x < headerBars.length; x++) {        headerBars[x].isActive = false;      }      enteringHeaderBar.isActive = true;      navBarAttr(enteringHeaderBar, 'active');      navBarAttr(leavingHeaderBar, 'cached');      self.activeTransition = navBarTransition = queuedTransitionEnd = null;    }    queuedTransitionStart();  };  self.triggerTransitionStart = function(triggerTransitionId) {    latestTransitionId = triggerTransitionId;    queuedTransitionStart && queuedTransitionStart();  };  self.triggerTransitionEnd = function() {    queuedTransitionEnd && queuedTransitionEnd();  };  self.showBar = function(shouldShow) {    if (arguments.length) {      self.visibleBar(shouldShow);      $scope.$parent.$hasHeader = !!shouldShow;    }    return !!$scope.$parent.$hasHeader;  };  self.visibleBar = function(shouldShow) {    if (shouldShow && !isVisible) {      $element.removeClass(CSS_HIDE);      self.align();    } else if (!shouldShow && isVisible) {      $element.addClass(CSS_HIDE);    }    isVisible = shouldShow;  };  self.enable = function(val) {    // set primary to show first    self.visibleBar(val);    // set non primary to hide second    for (var x = 0; x < $ionicNavBarDelegate._instances.length; x++) {      if ($ionicNavBarDelegate._instances[x] !== self) $ionicNavBarDelegate._instances[x].visibleBar(false);    }  };  /**   * @ngdoc method   * @name $ionicNavBar#showBackButton   * @description Show/hide the nav bar back button when there is a   * back view. If the back button is not possible, for example, the   * first view in the stack, then this will not force the back button   * to show.   */  self.showBackButton = function(shouldShow) {    if (arguments.length) {      for (var x = 0; x < headerBars.length; x++) {        headerBars[x].controller().showNavBack(!!shouldShow);      }      $scope.$isBackButtonShown = !!shouldShow;    }    return $scope.$isBackButtonShown;  };  /**   * @ngdoc method   * @name $ionicNavBar#showActiveBackButton   * @description Show/hide only the active header bar's back button.   */  self.showActiveBackButton = function(shouldShow) {    var headerBar = getOnScreenHeaderBar();    if (headerBar) {      if (arguments.length) {        return headerBar.controller().showBack(shouldShow);      }      return headerBar.controller().showBack();    }  };  self.title = function(newTitleText, headerBar) {    if (isDefined(newTitleText)) {      newTitleText = newTitleText || '';      headerBar = headerBar || getOnScreenHeaderBar();      headerBar && headerBar.title(newTitleText);      $scope.$title = newTitleText;      $ionicHistory.currentTitle(newTitleText);    }    return $scope.$title;  };  self.align = function(val, headerBar) {    headerBar = headerBar || getOnScreenHeaderBar();    headerBar && headerBar.controller().align(val);  };  self.hasTabsTop = function(isTabsTop) {    $element[isTabsTop ? 'addClass' : 'removeClass']('nav-bar-tabs-top');  };  self.hasBarSubheader = function(isBarSubheader) {    $element[isBarSubheader ? 'addClass' : 'removeClass']('nav-bar-has-subheader');  };  // DEPRECATED, as of v1.0.0-beta14 -------  self.changeTitle = function(val) {    deprecatedWarning('changeTitle(val)', 'title(val)');    self.title(val);  };  self.setTitle = function(val) {    deprecatedWarning('setTitle(val)', 'title(val)');    self.title(val);  };  self.getTitle = function() {    deprecatedWarning('getTitle()', 'title()');    return self.title();  };  self.back = function() {    deprecatedWarning('back()', '$ionicHistory.goBack()');    $ionicHistory.goBack();  };  self.getPreviousTitle = function() {    deprecatedWarning('getPreviousTitle()', '$ionicHistory.backTitle()');    $ionicHistory.goBack();  };  function deprecatedWarning(oldMethod, newMethod) {    var warn = console.warn || console.log;    warn && warn.call(console, 'navBarController.' + oldMethod + ' is deprecated, please use ' + newMethod + ' instead');  }  // END DEPRECATED -------  function createNavElement(type) {    if (navElementHtml[type]) {      return jqLite(navElementHtml[type]);    }  }  function getOnScreenHeaderBar() {    for (var x = 0; x < headerBars.length; x++) {      if (headerBars[x].isActive) return headerBars[x];    }  }  function getOffScreenHeaderBar() {    for (var x = 0; x < headerBars.length; x++) {      if (!headerBars[x].isActive) return headerBars[x];    }  }  function navBarAttr(ctrl, val) {    ctrl && ionic.DomUtil.cachedAttr(ctrl.containerEle(), 'nav-bar', val);  }  function navSwipeAttr(val) {    ionic.DomUtil.cachedAttr($element, 'nav-swipe', val);  }  $scope.$on('$destroy', function() {    $scope.$parent.$hasHeader = false;    $element.parent().removeData(DATA_NAV_BAR_CTRL);    for (var x = 0; x < headerBars.length; x++) {      headerBars[x].destroy();    }    $element.remove();    $element = headerBars = null;    deregisterInstance();  });}]);IonicModule.controller('$ionicNavView', [  '$scope',  '$element',  '$attrs',  '$compile',  '$controller',  '$ionicNavBarDelegate',  '$ionicNavViewDelegate',  '$ionicHistory',  '$ionicViewSwitcher',  '$ionicConfig',  '$ionicScrollDelegate',  '$ionicSideMenuDelegate',function($scope, $element, $attrs, $compile, $controller, $ionicNavBarDelegate, $ionicNavViewDelegate, $ionicHistory, $ionicViewSwitcher, $ionicConfig, $ionicScrollDelegate, $ionicSideMenuDelegate) {  var DATA_ELE_IDENTIFIER = '$eleId';  var DATA_DESTROY_ELE = '$destroyEle';  var DATA_NO_CACHE = '$noCache';  var VIEW_STATUS_ACTIVE = 'active';  var VIEW_STATUS_CACHED = 'cached';  var self = this;  var direction;  var isPrimary = false;  var navBarDelegate;  var activeEleId;  var navViewAttr = $ionicViewSwitcher.navViewAttr;  var disableRenderStartViewId, disableAnimation;  self.scope = $scope;  self.element = $element;  self.init = function() {    var navViewName = $attrs.name || '';    // Find the details of the parent view directive (if any) and use it    // to derive our own qualified view name, then hang our own details    // off the DOM so child directives can find it.    var parent = $element.parent().inheritedData('$uiView');    var parentViewName = ((parent && parent.state) ? parent.state.name : '');    if (navViewName.indexOf('@') < 0) navViewName = navViewName + '@' + parentViewName;    var viewData = { name: navViewName, state: null };    $element.data('$uiView', viewData);    var deregisterInstance = $ionicNavViewDelegate._registerInstance(self, $attrs.delegateHandle);    $scope.$on('$destroy', function() {      deregisterInstance();      // ensure no scrolls have been left frozen      if (self.isSwipeFreeze) {        $ionicScrollDelegate.freezeAllScrolls(false);      }    });    $scope.$on('$ionicHistory.deselect', self.cacheCleanup);    $scope.$on('$ionicTabs.top', onTabsTop);    $scope.$on('$ionicSubheader', onBarSubheader);    $scope.$on('$ionicTabs.beforeLeave', onTabsLeave);    $scope.$on('$ionicTabs.afterLeave', onTabsLeave);    $scope.$on('$ionicTabs.leave', onTabsLeave);    ionic.Platform.ready(function() {      if ( ionic.Platform.isWebView() && ionic.Platform.isIOS() ) {          self.initSwipeBack();      }    });    return viewData;  };  self.register = function(viewLocals) {    var leavingView = extend({}, $ionicHistory.currentView());    // register that a view is coming in and get info on how it should transition    var registerData = $ionicHistory.register($scope, viewLocals);    // update which direction    self.update(registerData);    // begin rendering and transitioning    var enteringView = $ionicHistory.getViewById(registerData.viewId) || {};    var renderStart = (disableRenderStartViewId !== registerData.viewId);    self.render(registerData, viewLocals, enteringView, leavingView, renderStart, true);  };  self.update = function(registerData) {    // always reset that this is the primary navView    isPrimary = true;    // remember what direction this navView should use    // this may get updated later by a child navView    direction = registerData.direction;    var parentNavViewCtrl = $element.parent().inheritedData('$ionNavViewController');    if (parentNavViewCtrl) {      // this navView is nested inside another one      // update the parent to use this direction and not      // the other it originally was set to      // inform the parent navView that it is not the primary navView      parentNavViewCtrl.isPrimary(false);      if (direction === 'enter' || direction === 'exit') {        // they're entering/exiting a history        // find parent navViewController        parentNavViewCtrl.direction(direction);        if (direction === 'enter') {          // reset the direction so this navView doesn't animate          // because it's parent will          direction = 'none';        }      }    }  };  self.render = function(registerData, viewLocals, enteringView, leavingView, renderStart, renderEnd) {    // register the view and figure out where it lives in the various    // histories and nav stacks, along with how views should enter/leave    var switcher = $ionicViewSwitcher.create(self, viewLocals, enteringView, leavingView, renderStart, renderEnd);    // init the rendering of views for this navView directive    switcher.init(registerData, function() {      // the view is now compiled, in the dom and linked, now lets transition the views.      // this uses a callback incase THIS nav-view has a nested nav-view, and after the NESTED      // nav-view links, the NESTED nav-view would update which direction THIS nav-view should use      // kick off the transition of views      switcher.transition(self.direction(), registerData.enableBack, !disableAnimation);      // reset private vars for next time      disableRenderStartViewId = disableAnimation = null;    });  };  self.beforeEnter = function(transitionData) {    if (isPrimary) {      // only update this nav-view's nav-bar if this is the primary nav-view      navBarDelegate = transitionData.navBarDelegate;      var associatedNavBarCtrl = getAssociatedNavBarCtrl();      associatedNavBarCtrl && associatedNavBarCtrl.update(transitionData);      navSwipeAttr('');    }  };  self.activeEleId = function(eleId) {    if (arguments.length) {      activeEleId = eleId;    }    return activeEleId;  };  self.transitionEnd = function() {    var viewElements = $element.children();    var x, l, viewElement;    for (x = 0, l = viewElements.length; x < l; x++) {      viewElement = viewElements.eq(x);      if (viewElement.data(DATA_ELE_IDENTIFIER) === activeEleId) {        // this is the active element        navViewAttr(viewElement, VIEW_STATUS_ACTIVE);      } else if (navViewAttr(viewElement) === 'leaving' || navViewAttr(viewElement) === VIEW_STATUS_ACTIVE || navViewAttr(viewElement) === VIEW_STATUS_CACHED) {        // this is a leaving element or was the former active element, or is an cached element        if (viewElement.data(DATA_DESTROY_ELE) || viewElement.data(DATA_NO_CACHE)) {          // this element shouldn't stay cached          $ionicViewSwitcher.destroyViewEle(viewElement);        } else {          // keep in the DOM, mark as cached          navViewAttr(viewElement, VIEW_STATUS_CACHED);          // disconnect the leaving scope          ionic.Utils.disconnectScope(viewElement.scope());        }      }    }    navSwipeAttr('');    // ensure no scrolls have been left frozen    if (self.isSwipeFreeze) {      $ionicScrollDelegate.freezeAllScrolls(false);    }  };  function onTabsLeave(ev, data) {    var viewElements = $element.children();    var viewElement, viewScope;    for (var x = 0, l = viewElements.length; x < l; x++) {      viewElement = viewElements.eq(x);      if (navViewAttr(viewElement) == VIEW_STATUS_ACTIVE) {        viewScope = viewElement.scope();        viewScope && viewScope.$emit(ev.name.replace('Tabs', 'View'), data);        viewScope && viewScope.$broadcast(ev.name.replace('Tabs', 'ParentView'), data);        break;      }    }  }  self.cacheCleanup = function() {    var viewElements = $element.children();    for (var x = 0, l = viewElements.length; x < l; x++) {      if (viewElements.eq(x).data(DATA_DESTROY_ELE)) {        $ionicViewSwitcher.destroyViewEle(viewElements.eq(x));      }    }  };  self.clearCache = function(stateIds) {    var viewElements = $element.children();    var viewElement, viewScope, x, l, y, eleIdentifier;    for (x = 0, l = viewElements.length; x < l; x++) {      viewElement = viewElements.eq(x);      if (stateIds) {        eleIdentifier = viewElement.data(DATA_ELE_IDENTIFIER);        for (y = 0; y < stateIds.length; y++) {          if (eleIdentifier === stateIds[y]) {            $ionicViewSwitcher.destroyViewEle(viewElement);          }        }        continue;      }      if (navViewAttr(viewElement) == VIEW_STATUS_CACHED) {        $ionicViewSwitcher.destroyViewEle(viewElement);      } else if (navViewAttr(viewElement) == VIEW_STATUS_ACTIVE) {        viewScope = viewElement.scope();        viewScope && viewScope.$broadcast('$ionicView.clearCache');      }    }  };  self.getViewElements = function() {    return $element.children();  };  self.appendViewElement = function(viewEle, viewLocals) {    // compile the entering element and get the link function    var linkFn = $compile(viewEle);    $element.append(viewEle);    var viewScope = $scope.$new();    if (viewLocals && viewLocals.$$controller) {      viewLocals.$scope = viewScope;      var controller = $controller(viewLocals.$$controller, viewLocals);      if (viewLocals.$$controllerAs) {        viewScope[viewLocals.$$controllerAs] = controller;      }      $element.children().data('$ngControllerController', controller);    }    linkFn(viewScope);    return viewScope;  };  self.title = function(val) {    var associatedNavBarCtrl = getAssociatedNavBarCtrl();    associatedNavBarCtrl && associatedNavBarCtrl.title(val);  };  /**   * @ngdoc method   * @name $ionicNavView#enableBackButton   * @description Enable/disable if the back button can be shown or not. For   * example, the very first view in the navigation stack would not have a   * back view, so the back button would be disabled.   */  self.enableBackButton = function(shouldEnable) {    var associatedNavBarCtrl = getAssociatedNavBarCtrl();    associatedNavBarCtrl && associatedNavBarCtrl.enableBackButton(shouldEnable);  };  /**   * @ngdoc method   * @name $ionicNavView#showBackButton   * @description Show/hide the nav bar active back button. If the back button   * is not possible this will not force the back button to show. The   * `enableBackButton()` method handles if a back button is even possible or not.   */  self.showBackButton = function(shouldShow) {    var associatedNavBarCtrl = getAssociatedNavBarCtrl();    if (associatedNavBarCtrl) {      if (arguments.length) {        return associatedNavBarCtrl.showActiveBackButton(shouldShow);      }      return associatedNavBarCtrl.showActiveBackButton();    }    return true;  };  self.showBar = function(val) {    var associatedNavBarCtrl = getAssociatedNavBarCtrl();    if (associatedNavBarCtrl) {      if (arguments.length) {        return associatedNavBarCtrl.showBar(val);      }      return associatedNavBarCtrl.showBar();    }    return true;  };  self.isPrimary = function(val) {    if (arguments.length) {      isPrimary = val;    }    return isPrimary;  };  self.direction = function(val) {    if (arguments.length) {      direction = val;    }    return direction;  };  self.initSwipeBack = function() {    var swipeBackHitWidth = $ionicConfig.views.swipeBackHitWidth();    var viewTransition, associatedNavBarCtrl, backView;    var deregDragStart, deregDrag, deregRelease;    var windowWidth, startDragX, dragPoints;    var cancelData = {};    function onDragStart(ev) {      if (!isPrimary || !$ionicConfig.views.swipeBackEnabled() || $ionicSideMenuDelegate.isOpenRight() ) return;      startDragX = getDragX(ev);      if (startDragX > swipeBackHitWidth) return;      backView = $ionicHistory.backView();      var currentView = $ionicHistory.currentView();      if (!backView || backView.historyId !== currentView.historyId || currentView.canSwipeBack === false) return;      if (!windowWidth) windowWidth = window.innerWidth;      self.isSwipeFreeze = $ionicScrollDelegate.freezeAllScrolls(true);      var registerData = {        direction: 'back'      };      dragPoints = [];      cancelData = {        showBar: self.showBar(),        showBackButton: self.showBackButton()      };      var switcher = $ionicViewSwitcher.create(self, registerData, backView, currentView, true, false);      switcher.loadViewElements(registerData);      switcher.render(registerData);      viewTransition = switcher.transition('back', $ionicHistory.enabledBack(backView), true);      associatedNavBarCtrl = getAssociatedNavBarCtrl();      deregDrag = ionic.onGesture('drag', onDrag, $element[0]);      deregRelease = ionic.onGesture('release', onRelease, $element[0]);    }    function onDrag(ev) {      if (isPrimary && viewTransition) {        var dragX = getDragX(ev);        dragPoints.push({          t: Date.now(),          x: dragX        });        if (dragX >= windowWidth - 15) {          onRelease(ev);        } else {          var step = Math.min(Math.max(getSwipeCompletion(dragX), 0), 1);          viewTransition.run(step);          associatedNavBarCtrl && associatedNavBarCtrl.activeTransition && associatedNavBarCtrl.activeTransition.run(step);        }      }    }    function onRelease(ev) {      if (isPrimary && viewTransition && dragPoints && dragPoints.length > 1) {        var now = Date.now();        var releaseX = getDragX(ev);        var startDrag = dragPoints[dragPoints.length - 1];        for (var x = dragPoints.length - 2; x >= 0; x--) {          if (now - startDrag.t > 200) {            break;          }          startDrag = dragPoints[x];        }        var isSwipingRight = (releaseX >= dragPoints[dragPoints.length - 2].x);        var releaseSwipeCompletion = getSwipeCompletion(releaseX);        var velocity = Math.abs(startDrag.x - releaseX) / (now - startDrag.t);        // private variables because ui-router has no way to pass custom data using $state.go        disableRenderStartViewId = backView.viewId;        disableAnimation = (releaseSwipeCompletion < 0.03 || releaseSwipeCompletion > 0.97);        if (isSwipingRight && (releaseSwipeCompletion > 0.5 || velocity > 0.1)) {          // complete view transition on release          var speed = (velocity > 0.5 || velocity < 0.05 || releaseX > windowWidth - 45) ? 'fast' : 'slow';          navSwipeAttr(disableAnimation ? '' : speed);          backView.go();          associatedNavBarCtrl && associatedNavBarCtrl.activeTransition && associatedNavBarCtrl.activeTransition.complete(!disableAnimation, speed);        } else {          // cancel view transition on release          navSwipeAttr(disableAnimation ? '' : 'fast');          disableRenderStartViewId = null;          viewTransition.cancel(!disableAnimation);          associatedNavBarCtrl && associatedNavBarCtrl.activeTransition && associatedNavBarCtrl.activeTransition.cancel(!disableAnimation, 'fast', cancelData);          disableAnimation = null;        }      }      ionic.offGesture(deregDrag, 'drag', onDrag);      ionic.offGesture(deregRelease, 'release', onRelease);      windowWidth = viewTransition = dragPoints = null;      self.isSwipeFreeze = $ionicScrollDelegate.freezeAllScrolls(false);    }    function getDragX(ev) {      return ionic.tap.pointerCoord(ev.gesture.srcEvent).x;    }    function getSwipeCompletion(dragX) {      return (dragX - startDragX) / windowWidth;    }    deregDragStart = ionic.onGesture('dragstart', onDragStart, $element[0]);    $scope.$on('$destroy', function() {      ionic.offGesture(deregDragStart, 'dragstart', onDragStart);      ionic.offGesture(deregDrag, 'drag', onDrag);      ionic.offGesture(deregRelease, 'release', onRelease);      self.element = viewTransition = associatedNavBarCtrl = null;    });  };  function navSwipeAttr(val) {    ionic.DomUtil.cachedAttr($element, 'nav-swipe', val);  }  function onTabsTop(ev, isTabsTop) {    var associatedNavBarCtrl = getAssociatedNavBarCtrl();    associatedNavBarCtrl && associatedNavBarCtrl.hasTabsTop(isTabsTop);  }  function onBarSubheader(ev, isBarSubheader) {    var associatedNavBarCtrl = getAssociatedNavBarCtrl();    associatedNavBarCtrl && associatedNavBarCtrl.hasBarSubheader(isBarSubheader);  }  function getAssociatedNavBarCtrl() {    if (navBarDelegate) {      for (var x = 0; x < $ionicNavBarDelegate._instances.length; x++) {        if ($ionicNavBarDelegate._instances[x].$$delegateHandle == navBarDelegate) {          return $ionicNavBarDelegate._instances[x];        }      }    }    return $element.inheritedData('$ionNavBarController');  }}]);IonicModule.controller('$ionicRefresher', [  '$scope',  '$attrs',  '$element',  '$ionicBind',  '$timeout',  function($scope, $attrs, $element, $ionicBind, $timeout) {    var self = this,        isDragging = false,        isOverscrolling = false,        dragOffset = 0,        lastOverscroll = 0,        ptrThreshold = 60,        activated = false,        scrollTime = 500,        startY = null,        deltaY = null,        canOverscroll = true,        scrollParent,        scrollChild;    if (!isDefined($attrs.pullingIcon)) {      $attrs.$set('pullingIcon', 'ion-android-arrow-down');    }    $scope.showSpinner = !isDefined($attrs.refreshingIcon) && $attrs.spinner != 'none';    $scope.showIcon = isDefined($attrs.refreshingIcon);    $ionicBind($scope, $attrs, {      pullingIcon: '@',      pullingText: '@',      refreshingIcon: '@',      refreshingText: '@',      spinner: '@',      disablePullingRotation: '@',      $onRefresh: '&onRefresh',      $onPulling: '&onPulling'    });    function handleMousedown(e) {      e.touches = e.touches || [{        screenX: e.screenX,        screenY: e.screenY      }];      // Mouse needs this      startY = Math.floor(e.touches[0].screenY);    }    function handleTouchstart(e) {      e.touches = e.touches || [{        screenX: e.screenX,        screenY: e.screenY      }];      startY = e.touches[0].screenY;    }    function handleTouchend() {      // reset Y      startY = null;      // if this wasn't an overscroll, get out immediately      if (!canOverscroll && !isDragging) {        return;      }      // the user has overscrolled but went back to native scrolling      if (!isDragging) {        dragOffset = 0;        isOverscrolling = false;        setScrollLock(false);      } else {        isDragging = false;        dragOffset = 0;        // the user has scroll far enough to trigger a refresh        if (lastOverscroll > ptrThreshold) {          start();          scrollTo(ptrThreshold, scrollTime);        // the user has overscrolled but not far enough to trigger a refresh        } else {          scrollTo(0, scrollTime, deactivate);          isOverscrolling = false;        }      }    }    function handleTouchmove(e) {      e.touches = e.touches || [{        screenX: e.screenX,        screenY: e.screenY      }];      // Force mouse events to have had a down event first      if (!startY && e.type == 'mousemove') {        return;      }      // if multitouch or regular scroll event, get out immediately      if (!canOverscroll || e.touches.length > 1) {        return;      }      //if this is a new drag, keep track of where we start      if (startY === null) {        startY = e.touches[0].screenY;      }      deltaY = e.touches[0].screenY - startY;      // how far have we dragged so far?      // kitkat fix for touchcancel events http://updates.html5rocks.com/2014/05/A-More-Compatible-Smoother-Touch      // Only do this if we're not on crosswalk      if (ionic.Platform.isAndroid() && ionic.Platform.version() === 4.4 && !ionic.Platform.isCrosswalk() && scrollParent.scrollTop === 0 && deltaY > 0) {        isDragging = true;        e.preventDefault();      }      // if we've dragged up and back down in to native scroll territory      if (deltaY - dragOffset <= 0 || scrollParent.scrollTop !== 0) {        if (isOverscrolling) {          isOverscrolling = false;          setScrollLock(false);        }        if (isDragging) {          nativescroll(scrollParent, deltaY - dragOffset * -1);        }        // if we're not at overscroll 0 yet, 0 out        if (lastOverscroll !== 0) {          overscroll(0);        }        return;      } else if (deltaY > 0 && scrollParent.scrollTop === 0 && !isOverscrolling) {        // starting overscroll, but drag started below scrollTop 0, so we need to offset the position        dragOffset = deltaY;      }      // prevent native scroll events while overscrolling      e.preventDefault();      // if not overscrolling yet, initiate overscrolling      if (!isOverscrolling) {        isOverscrolling = true;        setScrollLock(true);      }      isDragging = true;      // overscroll according to the user's drag so far      overscroll((deltaY - dragOffset) / 3);      // update the icon accordingly      if (!activated && lastOverscroll > ptrThreshold) {        activated = true;        ionic.requestAnimationFrame(activate);      } else if (activated && lastOverscroll < ptrThreshold) {        activated = false;        ionic.requestAnimationFrame(deactivate);      }    }    function handleScroll(e) {      // canOverscrol is used to greatly simplify the drag handler during normal scrolling      canOverscroll = (e.target.scrollTop === 0) || isDragging;    }    function overscroll(val) {      scrollChild.style[ionic.CSS.TRANSFORM] = 'translate3d(0px, ' + val + 'px, 0px)';      lastOverscroll = val;    }    function nativescroll(target, newScrollTop) {      // creates a scroll event that bubbles, can be cancelled, and with its view      // and detail property initialized to window and 1, respectively      target.scrollTop = newScrollTop;      var e = document.createEvent("UIEvents");      e.initUIEvent("scroll", true, true, window, 1);      target.dispatchEvent(e);    }    function setScrollLock(enabled) {      // set the scrollbar to be position:fixed in preparation to overscroll      // or remove it so the app can be natively scrolled      if (enabled) {        ionic.requestAnimationFrame(function() {          scrollChild.classList.add('overscroll');          show();        });      } else {        ionic.requestAnimationFrame(function() {          scrollChild.classList.remove('overscroll');          hide();          deactivate();        });      }    }    $scope.$on('scroll.refreshComplete', function() {      // prevent the complete from firing before the scroll has started      $timeout(function() {        ionic.requestAnimationFrame(tail);        // scroll back to home during tail animation        scrollTo(0, scrollTime, deactivate);        // return to native scrolling after tail animation has time to finish        $timeout(function() {          if (isOverscrolling) {            isOverscrolling = false;            setScrollLock(false);          }        }, scrollTime);      }, scrollTime);    });    function scrollTo(Y, duration, callback) {      // scroll animation loop w/ easing      // credit https://gist.github.com/dezinezync/5487119      var start = Date.now(),          from = lastOverscroll;      if (from === Y) {        callback();        return; /* Prevent scrolling to the Y point if already there */      }      // decelerating to zero velocity      function easeOutCubic(t) {        return (--t) * t * t + 1;      }      // scroll loop      function scroll() {        var currentTime = Date.now(),          time = Math.min(1, ((currentTime - start) / duration)),          // where .5 would be 50% of time on a linear scale easedT gives a          // fraction based on the easing method          easedT = easeOutCubic(time);        overscroll(Math.floor((easedT * (Y - from)) + from));        if (time < 1) {          ionic.requestAnimationFrame(scroll);        } else {          if (Y < 5 && Y > -5) {            isOverscrolling = false;            setScrollLock(false);          }          callback && callback();        }      }      // start scroll loop      ionic.requestAnimationFrame(scroll);    }    var touchStartEvent, touchMoveEvent, touchEndEvent;    if (window.navigator.pointerEnabled) {      touchStartEvent = 'pointerdown';      touchMoveEvent = 'pointermove';      touchEndEvent = 'pointerup';    } else if (window.navigator.msPointerEnabled) {      touchStartEvent = 'MSPointerDown';      touchMoveEvent = 'MSPointerMove';      touchEndEvent = 'MSPointerUp';    } else {      touchStartEvent = 'touchstart';      touchMoveEvent = 'touchmove';      touchEndEvent = 'touchend';    }    self.init = function() {      scrollParent = $element.parent().parent()[0];      scrollChild = $element.parent()[0];      if (!scrollParent || !scrollParent.classList.contains('ionic-scroll') ||        !scrollChild || !scrollChild.classList.contains('scroll')) {        throw new Error('Refresher must be immediate child of ion-content or ion-scroll');      }      ionic.on(touchStartEvent, handleTouchstart, scrollChild);      ionic.on(touchMoveEvent, handleTouchmove, scrollChild);      ionic.on(touchEndEvent, handleTouchend, scrollChild);      ionic.on('mousedown', handleMousedown, scrollChild);      ionic.on('mousemove', handleTouchmove, scrollChild);      ionic.on('mouseup', handleTouchend, scrollChild);      ionic.on('scroll', handleScroll, scrollParent);      // cleanup when done      $scope.$on('$destroy', destroy);    };    function destroy() {      if ( scrollChild ) {        ionic.off(touchStartEvent, handleTouchstart, scrollChild);        ionic.off(touchMoveEvent, handleTouchmove, scrollChild);        ionic.off(touchEndEvent, handleTouchend, scrollChild);        ionic.off('mousedown', handleMousedown, scrollChild);        ionic.off('mousemove', handleTouchmove, scrollChild);        ionic.off('mouseup', handleTouchend, scrollChild);      }      if ( scrollParent ) {        ionic.off('scroll', handleScroll, scrollParent);      }      scrollParent = null;      scrollChild = null;    }    // DOM manipulation and broadcast methods shared by JS and Native Scrolling    // getter used by JS Scrolling    self.getRefresherDomMethods = function() {      return {        activate: activate,        deactivate: deactivate,        start: start,        show: show,        hide: hide,        tail: tail      };    };    function activate() {      $element[0].classList.add('active');      $scope.$onPulling();    }    function deactivate() {      // give tail 150ms to finish      $timeout(function() {        // deactivateCallback        $element.removeClass('active refreshing refreshing-tail');        if (activated) activated = false;      }, 150);    }    function start() {      // startCallback      $element[0].classList.add('refreshing');      var q = $scope.$onRefresh();      if (q && q.then) {        q['finally'](function() {          $scope.$broadcast('scroll.refreshComplete');        });      }    }    function show() {      // showCallback      $element[0].classList.remove('invisible');    }    function hide() {      // showCallback      $element[0].classList.add('invisible');    }    function tail() {      // tailCallback      $element[0].classList.add('refreshing-tail');    }    // for testing    self.__handleTouchmove = handleTouchmove;    self.__getScrollChild = function() { return scrollChild; };    self.__getScrollParent = function() { return scrollParent; };  }]);/** * @private */IonicModule.controller('$ionicScroll', [  '$scope',  'scrollViewOptions',  '$timeout',  '$window',  '$location',  '$document',  '$ionicScrollDelegate',  '$ionicHistory',function($scope,         scrollViewOptions,         $timeout,         $window,         $location,         $document,         $ionicScrollDelegate,         $ionicHistory) {  var self = this;  // for testing  self.__timeout = $timeout;  self._scrollViewOptions = scrollViewOptions; //for testing  self.isNative = function() {    return !!scrollViewOptions.nativeScrolling;  };  var element = self.element = scrollViewOptions.el;  var $element = self.$element = jqLite(element);  var scrollView;  if (self.isNative()) {    scrollView = self.scrollView = new ionic.views.ScrollNative(scrollViewOptions);  } else {    scrollView = self.scrollView = new ionic.views.Scroll(scrollViewOptions);  }  //Attach self to element as a controller so other directives can require this controller  //through `require: '$ionicScroll'  //Also attach to parent so that sibling elements can require this  ($element.parent().length ? $element.parent() : $element)    .data('$$ionicScrollController', self);  var deregisterInstance = $ionicScrollDelegate._registerInstance(    self, scrollViewOptions.delegateHandle, function() {      return $ionicHistory.isActiveScope($scope);    }  );  if (!isDefined(scrollViewOptions.bouncing)) {    ionic.Platform.ready(function() {      if (scrollView && scrollView.options) {        scrollView.options.bouncing = true;        if (ionic.Platform.isAndroid()) {          // No bouncing by default on Android          scrollView.options.bouncing = false;          // Faster scroll decel          scrollView.options.deceleration = 0.95;        }      }    });  }  var resize = angular.bind(scrollView, scrollView.resize);  angular.element($window).on('resize', resize);  var scrollFunc = function(e) {    var detail = (e.originalEvent || e).detail || {};    $scope.$onScroll && $scope.$onScroll({      event: e,      scrollTop: detail.scrollTop || 0,      scrollLeft: detail.scrollLeft || 0    });  };  $element.on('scroll', scrollFunc);  $scope.$on('$destroy', function() {    deregisterInstance();    scrollView && scrollView.__cleanup && scrollView.__cleanup();    angular.element($window).off('resize', resize);    if ( $element ) {      $element.off('scroll', scrollFunc);    }    if ( self._scrollViewOptions ) {      self._scrollViewOptions.el = null;    }    if ( scrollViewOptions ) {        scrollViewOptions.el = null;    }    scrollView = self.scrollView = scrollViewOptions = self._scrollViewOptions = element = self.$element = $element = null;  });  $timeout(function() {    scrollView && scrollView.run && scrollView.run();  });  self.getScrollView = function() {    return scrollView;  };  self.getScrollPosition = function() {    return scrollView.getValues();  };  self.resize = function() {    return $timeout(resize, 0, false).then(function() {      $element && $element.triggerHandler('scroll-resize');    });  };  self.scrollTop = function(shouldAnimate) {    self.resize().then(function() {      if (!scrollView) {        return;      }      scrollView.scrollTo(0, 0, !!shouldAnimate);    });  };  self.scrollBottom = function(shouldAnimate) {    self.resize().then(function() {      if (!scrollView) {        return;      }      var max = scrollView.getScrollMax();      scrollView.scrollTo(max.left, max.top, !!shouldAnimate);    });  };  self.scrollTo = function(left, top, shouldAnimate) {    self.resize().then(function() {      if (!scrollView) {        return;      }      scrollView.scrollTo(left, top, !!shouldAnimate);    });  };  self.zoomTo = function(zoom, shouldAnimate, originLeft, originTop) {    self.resize().then(function() {      if (!scrollView) {        return;      }      scrollView.zoomTo(zoom, !!shouldAnimate, originLeft, originTop);    });  };  self.zoomBy = function(zoom, shouldAnimate, originLeft, originTop) {    self.resize().then(function() {      if (!scrollView) {        return;      }      scrollView.zoomBy(zoom, !!shouldAnimate, originLeft, originTop);    });  };  self.scrollBy = function(left, top, shouldAnimate) {    self.resize().then(function() {      if (!scrollView) {        return;      }      scrollView.scrollBy(left, top, !!shouldAnimate);    });  };  self.anchorScroll = function(shouldAnimate) {    self.resize().then(function() {      if (!scrollView) {        return;      }      var hash = $location.hash();      var elm = hash && $document[0].getElementById(hash);      if (!(hash && elm)) {        scrollView.scrollTo(0, 0, !!shouldAnimate);        return;      }      var curElm = elm;      var scrollLeft = 0, scrollTop = 0;      do {        if (curElm !== null) scrollLeft += curElm.offsetLeft;        if (curElm !== null) scrollTop += curElm.offsetTop;        curElm = curElm.offsetParent;      } while (curElm.attributes != self.element.attributes && curElm.offsetParent);      scrollView.scrollTo(scrollLeft, scrollTop, !!shouldAnimate);    });  };  self.freezeScroll = scrollView.freeze;  self.freezeScrollShut = scrollView.freezeShut;  self.freezeAllScrolls = function(shouldFreeze) {    for (var i = 0; i < $ionicScrollDelegate._instances.length; i++) {      $ionicScrollDelegate._instances[i].freezeScroll(shouldFreeze);    }  };  /**   * @private   */  self._setRefresher = function(refresherScope, refresherElement, refresherMethods) {    self.refresher = refresherElement;    var refresherHeight = self.refresher.clientHeight || 60;    scrollView.activatePullToRefresh(      refresherHeight,      refresherMethods    );  };}]);IonicModule.controller('$ionicSideMenus', [  '$scope',  '$attrs',  '$ionicSideMenuDelegate',  '$ionicPlatform',  '$ionicBody',  '$ionicHistory',  '$ionicScrollDelegate',  'IONIC_BACK_PRIORITY',  '$rootScope',function($scope, $attrs, $ionicSideMenuDelegate, $ionicPlatform, $ionicBody, $ionicHistory, $ionicScrollDelegate, IONIC_BACK_PRIORITY, $rootScope) {  var self = this;  var rightShowing, leftShowing, isDragging;  var startX, lastX, offsetX, isAsideExposed;  var enableMenuWithBackViews = true;  self.$scope = $scope;  self.initialize = function(options) {    self.left = options.left;    self.right = options.right;    self.setContent(options.content);    self.dragThresholdX = options.dragThresholdX || 10;    $ionicHistory.registerHistory(self.$scope);  };  /**   * Set the content view controller if not passed in the constructor options.   *   * @param {object} content   */  self.setContent = function(content) {    if (content) {      self.content = content;      self.content.onDrag = function(e) {        self._handleDrag(e);      };      self.content.endDrag = function(e) {        self._endDrag(e);      };    }  };  self.isOpenLeft = function() {    return self.getOpenAmount() > 0;  };  self.isOpenRight = function() {    return self.getOpenAmount() < 0;  };  /**   * Toggle the left menu to open 100%   */  self.toggleLeft = function(shouldOpen) {    if (isAsideExposed || !self.left.isEnabled) return;    var openAmount = self.getOpenAmount();    if (arguments.length === 0) {      shouldOpen = openAmount <= 0;    }    self.content.enableAnimation();    if (!shouldOpen) {      self.openPercentage(0);      $rootScope.$emit('$ionicSideMenuClose', 'left');    } else {      self.openPercentage(100);      $rootScope.$emit('$ionicSideMenuOpen', 'left');    }  };  /**   * Toggle the right menu to open 100%   */  self.toggleRight = function(shouldOpen) {    if (isAsideExposed || !self.right.isEnabled) return;    var openAmount = self.getOpenAmount();    if (arguments.length === 0) {      shouldOpen = openAmount >= 0;    }    self.content.enableAnimation();    if (!shouldOpen) {      self.openPercentage(0);      $rootScope.$emit('$ionicSideMenuClose', 'right');    } else {      self.openPercentage(-100);      $rootScope.$emit('$ionicSideMenuOpen', 'right');    }  };  self.toggle = function(side) {    if (side == 'right') {      self.toggleRight();    } else {      self.toggleLeft();    }  };  /**   * Close all menus.   */  self.close = function() {    self.openPercentage(0);    $rootScope.$emit('$ionicSideMenuClose', 'left');    $rootScope.$emit('$ionicSideMenuClose', 'right');  };  /**   * @return {float} The amount the side menu is open, either positive or negative for left (positive), or right (negative)   */  self.getOpenAmount = function() {    return self.content && self.content.getTranslateX() || 0;  };  /**   * @return {float} The ratio of open amount over menu width. For example, a   * menu of width 100 open 50 pixels would be open 50% or a ratio of 0.5. Value is negative   * for right menu.   */  self.getOpenRatio = function() {    var amount = self.getOpenAmount();    if (amount >= 0) {      return amount / self.left.width;    }    return amount / self.right.width;  };  self.isOpen = function() {    return self.getOpenAmount() !== 0;  };  /**   * @return {float} The percentage of open amount over menu width. For example, a   * menu of width 100 open 50 pixels would be open 50%. Value is negative   * for right menu.   */  self.getOpenPercentage = function() {    return self.getOpenRatio() * 100;  };  /**   * Open the menu with a given percentage amount.   * @param {float} percentage The percentage (positive or negative for left/right) to open the menu.   */  self.openPercentage = function(percentage) {    var p = percentage / 100;    if (self.left && percentage >= 0) {      self.openAmount(self.left.width * p);    } else if (self.right && percentage < 0) {      self.openAmount(self.right.width * p);    }    // add the CSS class "menu-open" if the percentage does not    // equal 0, otherwise remove the class from the body element    $ionicBody.enableClass((percentage !== 0), 'menu-open');    self.content.setCanScroll(percentage == 0);  };  /*  function freezeAllScrolls(shouldFreeze) {    if (shouldFreeze && !self.isScrollFreeze) {      $ionicScrollDelegate.freezeAllScrolls(shouldFreeze);    } else if (!shouldFreeze && self.isScrollFreeze) {      $ionicScrollDelegate.freezeAllScrolls(false);    }    self.isScrollFreeze = shouldFreeze;  }  */  /**   * Open the menu the given pixel amount.   * @param {float} amount the pixel amount to open the menu. Positive value for left menu,   * negative value for right menu (only one menu will be visible at a time).   */  self.openAmount = function(amount) {    var maxLeft = self.left && self.left.width || 0;    var maxRight = self.right && self.right.width || 0;    // Check if we can move to that side, depending if the left/right panel is enabled    if (!(self.left && self.left.isEnabled) && amount > 0) {      self.content.setTranslateX(0);      return;    }    if (!(self.right && self.right.isEnabled) && amount < 0) {      self.content.setTranslateX(0);      return;    }    if (leftShowing && amount > maxLeft) {      self.content.setTranslateX(maxLeft);      return;    }    if (rightShowing && amount < -maxRight) {      self.content.setTranslateX(-maxRight);      return;    }    self.content.setTranslateX(amount);    if (amount >= 0) {      leftShowing = true;      rightShowing = false;      if (amount > 0) {        // Push the z-index of the right menu down        self.right && self.right.pushDown && self.right.pushDown();        // Bring the z-index of the left menu up        self.left && self.left.bringUp && self.left.bringUp();      }    } else {      rightShowing = true;      leftShowing = false;      // Bring the z-index of the right menu up      self.right && self.right.bringUp && self.right.bringUp();      // Push the z-index of the left menu down      self.left && self.left.pushDown && self.left.pushDown();    }  };  /**   * Given an event object, find the final resting position of this side   * menu. For example, if the user "throws" the content to the right and   * releases the touch, the left menu should snap open (animated, of course).   *   * @param {Event} e the gesture event to use for snapping   */  self.snapToRest = function(e) {    // We want to animate at the end of this    self.content.enableAnimation();    isDragging = false;    // Check how much the panel is open after the drag, and    // what the drag velocity is    var ratio = self.getOpenRatio();    if (ratio === 0) {      // Just to be safe      self.openPercentage(0);      return;    }    var velocityThreshold = 0.3;    var velocityX = e.gesture.velocityX;    var direction = e.gesture.direction;    // Going right, less than half, too slow (snap back)    if (ratio > 0 && ratio < 0.5 && direction == 'right' && velocityX < velocityThreshold) {      self.openPercentage(0);    }    // Going left, more than half, too slow (snap back)    else if (ratio > 0.5 && direction == 'left' && velocityX < velocityThreshold) {      self.openPercentage(100);    }    // Going left, less than half, too slow (snap back)    else if (ratio < 0 && ratio > -0.5 && direction == 'left' && velocityX < velocityThreshold) {      self.openPercentage(0);    }    // Going right, more than half, too slow (snap back)    else if (ratio < 0.5 && direction == 'right' && velocityX < velocityThreshold) {      self.openPercentage(-100);    }    // Going right, more than half, or quickly (snap open)    else if (direction == 'right' && ratio >= 0 && (ratio >= 0.5 || velocityX > velocityThreshold)) {      self.openPercentage(100);    }    // Going left, more than half, or quickly (span open)    else if (direction == 'left' && ratio <= 0 && (ratio <= -0.5 || velocityX > velocityThreshold)) {      self.openPercentage(-100);    }    // Snap back for safety    else {      self.openPercentage(0);    }  };  self.enableMenuWithBackViews = function(val) {    if (arguments.length) {      enableMenuWithBackViews = !!val;    }    return enableMenuWithBackViews;  };  self.isAsideExposed = function() {    return !!isAsideExposed;  };  self.exposeAside = function(shouldExposeAside) {    if (!(self.left && self.left.isEnabled) && !(self.right && self.right.isEnabled)) return;    self.close();    isAsideExposed = shouldExposeAside;    if ((self.left && self.left.isEnabled) && (self.right && self.right.isEnabled)) {      self.content.setMarginLeftAndRight(isAsideExposed ? self.left.width : 0, isAsideExposed ? self.right.width : 0);    } else if (self.left && self.left.isEnabled) {      // set the left marget width if it should be exposed      // otherwise set false so there's no left margin      self.content.setMarginLeft(isAsideExposed ? self.left.width : 0);    } else if (self.right && self.right.isEnabled) {      self.content.setMarginRight(isAsideExposed ? self.right.width : 0);    }    self.$scope.$emit('$ionicExposeAside', isAsideExposed);  };  self.activeAsideResizing = function(isResizing) {    $ionicBody.enableClass(isResizing, 'aside-resizing');  };  // End a drag with the given event  self._endDrag = function(e) {    if (isAsideExposed) return;    if (isDragging) {      self.snapToRest(e);    }    startX = null;    lastX = null;    offsetX = null;  };  // Handle a drag event  self._handleDrag = function(e) {    if (isAsideExposed || !$scope.dragContent) return;    // If we don't have start coords, grab and store them    if (!startX) {      startX = e.gesture.touches[0].pageX;      lastX = startX;    } else {      // Grab the current tap coords      lastX = e.gesture.touches[0].pageX;    }    // Calculate difference from the tap points    if (!isDragging && Math.abs(lastX - startX) > self.dragThresholdX) {      // if the difference is greater than threshold, start dragging using the current      // point as the starting point      startX = lastX;      isDragging = true;      // Initialize dragging      self.content.disableAnimation();      offsetX = self.getOpenAmount();    }    if (isDragging) {      self.openAmount(offsetX + (lastX - startX));      //self.content.setCanScroll(false);    }  };  self.canDragContent = function(canDrag) {    if (arguments.length) {      $scope.dragContent = !!canDrag;    }    return $scope.dragContent;  };  self.edgeThreshold = 25;  self.edgeThresholdEnabled = false;  self.edgeDragThreshold = function(value) {    if (arguments.length) {      if (isNumber(value) && value > 0) {        self.edgeThreshold = value;        self.edgeThresholdEnabled = true;      } else {        self.edgeThresholdEnabled = !!value;      }    }    return self.edgeThresholdEnabled;  };  self.isDraggableTarget = function(e) {    //Only restrict edge when sidemenu is closed and restriction is enabled    var shouldOnlyAllowEdgeDrag = self.edgeThresholdEnabled && !self.isOpen();    var startX = e.gesture.startEvent && e.gesture.startEvent.center &&      e.gesture.startEvent.center.pageX;    var dragIsWithinBounds = !shouldOnlyAllowEdgeDrag ||      startX <= self.edgeThreshold ||      startX >= self.content.element.offsetWidth - self.edgeThreshold;    var backView = $ionicHistory.backView();    var menuEnabled = enableMenuWithBackViews ? true : !backView;    if (!menuEnabled) {      var currentView = $ionicHistory.currentView() || {};      return (dragIsWithinBounds && (backView.historyId !== currentView.historyId));    }    return ($scope.dragContent || self.isOpen()) &&      dragIsWithinBounds &&      !e.gesture.srcEvent.defaultPrevented &&      menuEnabled &&      !e.target.tagName.match(/input|textarea|select|object|embed/i) &&      !e.target.isContentEditable &&      !(e.target.dataset ? e.target.dataset.preventScroll : e.target.getAttribute('data-prevent-scroll') == 'true');  };  $scope.sideMenuContentTranslateX = 0;  var deregisterBackButtonAction = noop;  var closeSideMenu = angular.bind(self, self.close);  $scope.$watch(function() {    return self.getOpenAmount() !== 0;  }, function(isOpen) {    deregisterBackButtonAction();    if (isOpen) {      deregisterBackButtonAction = $ionicPlatform.registerBackButtonAction(        closeSideMenu,        IONIC_BACK_PRIORITY.sideMenu      );    }  });  var deregisterInstance = $ionicSideMenuDelegate._registerInstance(    self, $attrs.delegateHandle, function() {      return $ionicHistory.isActiveScope($scope);    }  );  $scope.$on('$destroy', function() {    deregisterInstance();    deregisterBackButtonAction();    self.$scope = null;    if (self.content) {      self.content.setCanScroll(true);      self.content.element = null;      self.content = null;    }  });  self.initialize({    left: {      width: 275    },    right: {      width: 275    }  });}]);(function(ionic) {  var TRANSLATE32 = 'translate(32,32)';  var STROKE_OPACITY = 'stroke-opacity';  var ROUND = 'round';  var INDEFINITE = 'indefinite';  var DURATION = '750ms';  var NONE = 'none';  var SHORTCUTS = {    a: 'animate',    an: 'attributeName',    at: 'animateTransform',    c: 'circle',    da: 'stroke-dasharray',    os: 'stroke-dashoffset',    f: 'fill',    lc: 'stroke-linecap',    rc: 'repeatCount',    sw: 'stroke-width',    t: 'transform',    v: 'values'  };  var SPIN_ANIMATION = {    v: '0,32,32;360,32,32',    an: 'transform',    type: 'rotate',    rc: INDEFINITE,    dur: DURATION  };  function createSvgElement(tagName, data, parent, spinnerName) {    var ele = document.createElement(SHORTCUTS[tagName] || tagName);    var k, x, y;    for (k in data) {      if (angular.isArray(data[k])) {        for (x = 0; x < data[k].length; x++) {          if (data[k][x].fn) {            for (y = 0; y < data[k][x].t; y++) {              createSvgElement(k, data[k][x].fn(y, spinnerName), ele, spinnerName);            }          } else {            createSvgElement(k, data[k][x], ele, spinnerName);          }        }      } else {        setSvgAttribute(ele, k, data[k]);      }    }    parent.appendChild(ele);  }  function setSvgAttribute(ele, k, v) {    ele.setAttribute(SHORTCUTS[k] || k, v);  }  function animationValues(strValues, i) {    var values = strValues.split(';');    var back = values.slice(i);    var front = values.slice(0, values.length - back.length);    values = back.concat(front).reverse();    return values.join(';') + ';' + values[0];  }  var IOS_SPINNER = {    sw: 4,    lc: ROUND,    line: [{      fn: function(i, spinnerName) {        return {          y1: spinnerName == 'ios' ? 17 : 12,          y2: spinnerName == 'ios' ? 29 : 20,          t: TRANSLATE32 + ' rotate(' + (30 * i + (i < 6 ? 180 : -180)) + ')',          a: [{            fn: function() {              return {                an: STROKE_OPACITY,                dur: DURATION,                v: animationValues('0;.1;.15;.25;.35;.45;.55;.65;.7;.85;1', i),                rc: INDEFINITE              };            },            t: 1          }]        };      },      t: 12    }]  };  var spinners = {    android: {      c: [{        sw: 6,        da: 128,        os: 82,        r: 26,        cx: 32,        cy: 32,        f: NONE      }]    },    ios: IOS_SPINNER,    'ios-small': IOS_SPINNER,    bubbles: {      sw: 0,      c: [{        fn: function(i) {          return {            cx: 24 * Math.cos(2 * Math.PI * i / 8),            cy: 24 * Math.sin(2 * Math.PI * i / 8),            t: TRANSLATE32,            a: [{              fn: function() {                return {                  an: 'r',                  dur: DURATION,                  v: animationValues('1;2;3;4;5;6;7;8', i),                  rc: INDEFINITE                };              },              t: 1            }]          };        },        t: 8      }]    },    circles: {      c: [{        fn: function(i) {          return {            r: 5,            cx: 24 * Math.cos(2 * Math.PI * i / 8),            cy: 24 * Math.sin(2 * Math.PI * i / 8),            t: TRANSLATE32,            sw: 0,            a: [{              fn: function() {                return {                  an: 'fill-opacity',                  dur: DURATION,                  v: animationValues('.3;.3;.3;.4;.7;.85;.9;1', i),                  rc: INDEFINITE                };              },              t: 1            }]          };        },        t: 8      }]    },    crescent: {      c: [{        sw: 4,        da: 128,        os: 82,        r: 26,        cx: 32,        cy: 32,        f: NONE,        at: [SPIN_ANIMATION]      }]    },    dots: {      c: [{        fn: function(i) {          return {            cx: 16 + (16 * i),            cy: 32,            sw: 0,            a: [{              fn: function() {                return {                  an: 'fill-opacity',                  dur: DURATION,                  v: animationValues('.5;.6;.8;1;.8;.6;.5', i),                  rc: INDEFINITE                };              },              t: 1            }, {              fn: function() {                return {                  an: 'r',                  dur: DURATION,                  v: animationValues('4;5;6;5;4;3;3', i),                  rc: INDEFINITE                };              },              t: 1            }]          };        },        t: 3      }]    },    lines: {      sw: 7,      lc: ROUND,      line: [{        fn: function(i) {          return {            x1: 10 + (i * 14),            x2: 10 + (i * 14),            a: [{              fn: function() {                return {                  an: 'y1',                  dur: DURATION,                  v: animationValues('16;18;28;18;16', i),                  rc: INDEFINITE                };              },              t: 1            }, {              fn: function() {                return {                  an: 'y2',                  dur: DURATION,                  v: animationValues('48;44;36;46;48', i),                  rc: INDEFINITE                };              },              t: 1            }, {              fn: function() {                return {                  an: STROKE_OPACITY,                  dur: DURATION,                  v: animationValues('1;.8;.5;.4;1', i),                  rc: INDEFINITE                };              },              t: 1            }]          };        },        t: 4      }]    },    ripple: {      f: NONE,      'fill-rule': 'evenodd',      sw: 3,      circle: [{        fn: function(i) {          return {            cx: 32,            cy: 32,            a: [{              fn: function() {                return {                  an: 'r',                  begin: (i * -1) + 's',                  dur: '2s',                  v: '0;24',                  keyTimes: '0;1',                  keySplines: '0.1,0.2,0.3,1',                  calcMode: 'spline',                  rc: INDEFINITE                };              },              t: 1            }, {              fn: function() {                return {                  an: STROKE_OPACITY,                  begin: (i * -1) + 's',                  dur: '2s',                  v: '.2;1;.2;0',                  rc: INDEFINITE                };              },              t: 1            }]          };        },        t: 2      }]    },    spiral: {      defs: [{        linearGradient: [{          id: 'sGD',          gradientUnits: 'userSpaceOnUse',          x1: 55, y1: 46, x2: 2, y2: 46,          stop: [{            offset: 0.1,            class: 'stop1'          }, {            offset: 1,            class: 'stop2'          }]        }]      }],      g: [{        sw: 4,        lc: ROUND,        f: NONE,        path: [{          stroke: 'url(#sGD)',          d: 'M4,32 c0,15,12,28,28,28c8,0,16-4,21-9'        }, {          d: 'M60,32 C60,16,47.464,4,32,4S4,16,4,32'        }],        at: [SPIN_ANIMATION]      }]    }  };  var animations = {    android: function(ele) {      // Note that this is called as a function, not a constructor.      var self = {};      this.stop = false;      var rIndex = 0;      var rotateCircle = 0;      var startTime;      var svgEle = ele.querySelector('g');      var circleEle = ele.querySelector('circle');      function run() {        if (self.stop) return;        var v = easeInOutCubic(Date.now() - startTime, 650);        var scaleX = 1;        var translateX = 0;        var dasharray = (188 - (58 * v));        var dashoffset = (182 - (182 * v));        if (rIndex % 2) {          scaleX = -1;          translateX = -64;          dasharray = (128 - (-58 * v));          dashoffset = (182 * v);        }        var rotateLine = [0, -101, -90, -11, -180, 79, -270, -191][rIndex];        setSvgAttribute(circleEle, 'da', Math.max(Math.min(dasharray, 188), 128));        setSvgAttribute(circleEle, 'os', Math.max(Math.min(dashoffset, 182), 0));        setSvgAttribute(circleEle, 't', 'scale(' + scaleX + ',1) translate(' + translateX + ',0) rotate(' + rotateLine + ',32,32)');        rotateCircle += 4.1;        if (rotateCircle > 359) rotateCircle = 0;        setSvgAttribute(svgEle, 't', 'rotate(' + rotateCircle + ',32,32)');        if (v >= 1) {          rIndex++;          if (rIndex > 7) rIndex = 0;          startTime = Date.now();        }        ionic.requestAnimationFrame(run);      }      return function() {        startTime = Date.now();        run();        return self;      };    }  };  function easeInOutCubic(t, c) {    t /= c / 2;    if (t < 1) return 1 / 2 * t * t * t;    t -= 2;    return 1 / 2 * (t * t * t + 2);  }  IonicModule  .controller('$ionicSpinner', [    '$element',    '$attrs',    '$ionicConfig',  function($element, $attrs, $ionicConfig) {    var spinnerName, anim;    this.init = function() {      spinnerName = $attrs.icon || $ionicConfig.spinner.icon();      var container = document.createElement('div');      createSvgElement('svg', {        viewBox: '0 0 64 64',        g: [spinners[spinnerName]]      }, container, spinnerName);      // Specifically for animations to work,      // Android 4.3 and below requires the element to be      // added as an html string, rather than dynmically      // building up the svg element and appending it.      $element.html(container.innerHTML);      this.start();      return spinnerName;    };    this.start = function() {      animations[spinnerName] && (anim = animations[spinnerName]($element[0])());    };    this.stop = function() {      animations[spinnerName] && (anim.stop = true);    };  }]);})(ionic);IonicModule.controller('$ionicTab', [  '$scope',  '$ionicHistory',  '$attrs',  '$location',  '$state',function($scope, $ionicHistory, $attrs, $location, $state) {  this.$scope = $scope;  //All of these exposed for testing  this.hrefMatchesState = function() {    return $attrs.href && $location.path().indexOf(      $attrs.href.replace(/^#/, '').replace(/\/$/, '')    ) === 0;  };  this.srefMatchesState = function() {    return $attrs.uiSref && $state.includes($attrs.uiSref.split('(')[0]);  };  this.navNameMatchesState = function() {    return this.navViewName && $ionicHistory.isCurrentStateNavView(this.navViewName);  };  this.tabMatchesState = function() {    return this.hrefMatchesState() || this.srefMatchesState() || this.navNameMatchesState();  };}]);IonicModule.controller('$ionicTabs', [  '$scope',  '$element',  '$ionicHistory',function($scope, $element, $ionicHistory) {  var self = this;  var selectedTab = null;  var previousSelectedTab = null;  var selectedTabIndex;  var isVisible = true;  self.tabs = [];  self.selectedIndex = function() {    return self.tabs.indexOf(selectedTab);  };  self.selectedTab = function() {    return selectedTab;  };  self.previousSelectedTab = function() {    return previousSelectedTab;  };  self.add = function(tab) {    $ionicHistory.registerHistory(tab);    self.tabs.push(tab);  };  self.remove = function(tab) {    var tabIndex = self.tabs.indexOf(tab);    if (tabIndex === -1) {      return;    }    //Use a field like '$tabSelected' so developers won't accidentally set it in controllers etc    if (tab.$tabSelected) {      self.deselect(tab);      //Try to select a new tab if we're removing a tab      if (self.tabs.length === 1) {        //Do nothing if there are no other tabs to select      } else {        //Select previous tab if it's the last tab, else select next tab        var newTabIndex = tabIndex === self.tabs.length - 1 ? tabIndex - 1 : tabIndex + 1;        self.select(self.tabs[newTabIndex]);      }    }    self.tabs.splice(tabIndex, 1);  };  self.deselect = function(tab) {    if (tab.$tabSelected) {      previousSelectedTab = selectedTab;      selectedTab = selectedTabIndex = null;      tab.$tabSelected = false;      (tab.onDeselect || noop)();      tab.$broadcast && tab.$broadcast('$ionicHistory.deselect');    }  };  self.select = function(tab, shouldEmitEvent) {    var tabIndex;    if (isNumber(tab)) {      tabIndex = tab;      if (tabIndex >= self.tabs.length) return;      tab = self.tabs[tabIndex];    } else {      tabIndex = self.tabs.indexOf(tab);    }    if (arguments.length === 1) {      shouldEmitEvent = !!(tab.navViewName || tab.uiSref);    }    if (selectedTab && selectedTab.$historyId == tab.$historyId) {      if (shouldEmitEvent) {        $ionicHistory.goToHistoryRoot(tab.$historyId);      }    } else if (selectedTabIndex !== tabIndex) {      forEach(self.tabs, function(tab) {        self.deselect(tab);      });      selectedTab = tab;      selectedTabIndex = tabIndex;      if (self.$scope && self.$scope.$parent) {        self.$scope.$parent.$activeHistoryId = tab.$historyId;      }      //Use a funny name like $tabSelected so the developer doesn't overwrite the var in a child scope      tab.$tabSelected = true;      (tab.onSelect || noop)();      if (shouldEmitEvent) {        $scope.$emit('$ionicHistory.change', {          type: 'tab',          tabIndex: tabIndex,          historyId: tab.$historyId,          navViewName: tab.navViewName,          hasNavView: !!tab.navViewName,          title: tab.title,          url: tab.href,          uiSref: tab.uiSref        });      }      $scope.$broadcast("tabSelected", { selectedTab: tab, selectedTabIndex: tabIndex});    }  };  self.hasActiveScope = function() {    for (var x = 0; x < self.tabs.length; x++) {      if ($ionicHistory.isActiveScope(self.tabs[x])) {        return true;      }    }    return false;  };  self.showBar = function(show) {    if (arguments.length) {      if (show) {        $element.removeClass('tabs-item-hide');      } else {        $element.addClass('tabs-item-hide');      }      isVisible = !!show;    }    return isVisible;  };}]);IonicModule.controller('$ionicView', [  '$scope',  '$element',  '$attrs',  '$compile',  '$rootScope',function($scope, $element, $attrs, $compile, $rootScope) {  var self = this;  var navElementHtml = {};  var navViewCtrl;  var navBarDelegateHandle;  var hasViewHeaderBar;  var deregisters = [];  var viewTitle;  var deregIonNavBarInit = $scope.$on('ionNavBar.init', function(ev, delegateHandle) {    // this view has its own ion-nav-bar, remember the navBarDelegateHandle for this view    ev.stopPropagation();    navBarDelegateHandle = delegateHandle;  });  self.init = function() {    deregIonNavBarInit();    var modalCtrl = $element.inheritedData('$ionModalController');    navViewCtrl = $element.inheritedData('$ionNavViewController');    // don't bother if inside a modal or there's no parent navView    if (!navViewCtrl || modalCtrl) return;    // add listeners for when this view changes    $scope.$on('$ionicView.beforeEnter', self.beforeEnter);    $scope.$on('$ionicView.afterEnter', afterEnter);    $scope.$on('$ionicView.beforeLeave', deregisterFns);  };  self.beforeEnter = function(ev, transData) {    // this event was emitted, starting at intial ion-view, then bubbles up    // only the first ion-view should do something with it, parent ion-views should ignore    if (transData && !transData.viewNotified) {      transData.viewNotified = true;      if (!$rootScope.$$phase) $scope.$digest();      viewTitle = isDefined($attrs.viewTitle) ? $attrs.viewTitle : $attrs.title;      var navBarItems = {};      for (var n in navElementHtml) {        navBarItems[n] = generateNavBarItem(navElementHtml[n]);      }      navViewCtrl.beforeEnter(extend(transData, {        title: viewTitle,        showBack: !attrTrue('hideBackButton'),        navBarItems: navBarItems,        navBarDelegate: navBarDelegateHandle || null,        showNavBar: !attrTrue('hideNavBar'),        hasHeaderBar: !!hasViewHeaderBar      }));      // make sure any existing observers are cleaned up      deregisterFns();    }  };  function afterEnter() {    // only listen for title updates after it has entered    // but also deregister the observe before it leaves    var viewTitleAttr = isDefined($attrs.viewTitle) && 'viewTitle' || isDefined($attrs.title) && 'title';    if (viewTitleAttr) {      titleUpdate($attrs[viewTitleAttr]);      deregisters.push($attrs.$observe(viewTitleAttr, titleUpdate));    }    if (isDefined($attrs.hideBackButton)) {      deregisters.push($scope.$watch($attrs.hideBackButton, function(val) {        navViewCtrl.showBackButton(!val);      }));    }    if (isDefined($attrs.hideNavBar)) {      deregisters.push($scope.$watch($attrs.hideNavBar, function(val) {        navViewCtrl.showBar(!val);      }));    }  }  function titleUpdate(newTitle) {    if (isDefined(newTitle) && newTitle !== viewTitle) {      viewTitle = newTitle;      navViewCtrl.title(viewTitle);    }  }  function deregisterFns() {    // remove all existing $attrs.$observe's    for (var x = 0; x < deregisters.length; x++) {      deregisters[x]();    }    deregisters = [];  }  function generateNavBarItem(html) {    if (html) {      // every time a view enters we need to recreate its view buttons if they exist      return $compile(html)($scope.$new());    }  }  function attrTrue(key) {    return !!$scope.$eval($attrs[key]);  }  self.navElement = function(type, html) {    navElementHtml[type] = html;  };}]);/* * We don't document the ionActionSheet directive, we instead document * the $ionicActionSheet service */IonicModule.directive('ionActionSheet', ['$document', function($document) {  return {    restrict: 'E',    scope: true,    replace: true,    link: function($scope, $element) {      var keyUp = function(e) {        if (e.which == 27) {          $scope.cancel();          $scope.$apply();        }      };      var backdropClick = function(e) {        if (e.target == $element[0]) {          $scope.cancel();          $scope.$apply();        }      };      $scope.$on('$destroy', function() {        $element.remove();        $document.unbind('keyup', keyUp);      });      $document.bind('keyup', keyUp);      $element.bind('click', backdropClick);    },    template: '<div class="action-sheet-backdrop">' +                '<div class="action-sheet-wrapper">' +                  '<div class="action-sheet" ng-class="{\'action-sheet-has-icons\': $actionSheetHasIcon}">' +                    '<div class="action-sheet-group action-sheet-options">' +                      '<div class="action-sheet-title" ng-if="titleText" ng-bind-html="titleText"></div>' +                      '<button class="button action-sheet-option" ng-click="buttonClicked($index)" ng-class="b.className" ng-repeat="b in buttons" ng-bind-html="b.text"></button>' +                      '<button class="button destructive action-sheet-destructive" ng-if="destructiveText" ng-click="destructiveButtonClicked()" ng-bind-html="destructiveText"></button>' +                    '</div>' +                    '<div class="action-sheet-group action-sheet-cancel" ng-if="cancelText">' +                      '<button class="button" ng-click="cancel()" ng-bind-html="cancelText"></button>' +                    '</div>' +                  '</div>' +                '</div>' +              '</div>'  };}]);/** * @ngdoc directive * @name ionCheckbox * @module ionic * @restrict E * @codepen hqcju * @description * The checkbox is no different than the HTML checkbox input, except it's styled differently. * * The checkbox behaves like any [AngularJS checkbox](http://docs.angularjs.org/api/ng/input/input[checkbox]). * * @usage * ```html * <ion-checkbox ng-model="isChecked">Checkbox Label</ion-checkbox> * ``` */IonicModule.directive('ionCheckbox', ['$ionicConfig', function($ionicConfig) {  return {    restrict: 'E',    replace: true,    require: '?ngModel',    transclude: true,    template:      '<label class="item item-checkbox">' +        '<div class="checkbox checkbox-input-hidden disable-pointer-events">' +          '<input type="checkbox">' +          '<i class="checkbox-icon"></i>' +        '</div>' +        '<div class="item-content disable-pointer-events" ng-transclude></div>' +      '</label>',    compile: function(element, attr) {      var input = element.find('input');      forEach({        'name': attr.name,        'ng-value': attr.ngValue,        'ng-model': attr.ngModel,        'ng-checked': attr.ngChecked,        'ng-disabled': attr.ngDisabled,        'ng-true-value': attr.ngTrueValue,        'ng-false-value': attr.ngFalseValue,        'ng-change': attr.ngChange,        'ng-required': attr.ngRequired,        'required': attr.required      }, function(value, name) {        if (isDefined(value)) {          input.attr(name, value);        }      });      var checkboxWrapper = element[0].querySelector('.checkbox');      checkboxWrapper.classList.add('checkbox-' + $ionicConfig.form.checkbox());    }  };}]);/** * @ngdoc directive * @restrict A * @name collectionRepeat * @module ionic * @codepen 7ec1ec58f2489ab8f359fa1a0fe89c15 * @description * `collection-repeat` allows an app to show huge lists of items much more performantly than * `ng-repeat`. * * It renders into the DOM only as many items as are currently visible. * * This means that on a phone screen that can fit eight items, only the eight items matching * the current scroll position will be rendered. * * **The Basics**: * * - The data given to collection-repeat must be an array. * - If the `item-height` and `item-width` attributes are not supplied, it will be assumed that *   every item in the list has the same dimensions as the first item. * - Don't use angular one-time binding (`::`) with collection-repeat. The scope of each item is *   assigned new data and re-digested as you scroll. Bindings need to update, and one-time bindings *   won't. * * **Performance Tips**: * * - The iOS webview has a performance bottleneck when switching out `<img src>` attributes. *   To increase performance of images on iOS, cache your images in advance and, *   if possible, lower the number of unique images. We're working on [a solution](https://github.com/driftyco/ionic/issues/3194). * * @usage * #### Basic Item List ([codepen](http://codepen.io/ionic/pen/0c2c35a34a8b18ad4d793fef0b081693)) * ```html * <ion-content> *   <ion-item collection-repeat="item in items"> *     {% raw %}{{item}}{% endraw %} *   </ion-item> * </ion-content> * ``` * * #### Grid of Images ([codepen](http://codepen.io/ionic/pen/5515d4efd9d66f780e96787387f41664)) * ```html * <ion-content> *   <img collection-repeat="photo in photos" *     item-width="33%" *     item-height="200px" *     ng-src="{% raw %}{{photo.url}}{% endraw %}"> * </ion-content> * ``` * * #### Horizontal Scroller, Dynamic Item Width ([codepen](http://codepen.io/ionic/pen/67cc56b349124a349acb57a0740e030e)) * ```html * <ion-content> *   <h2>Available Kittens:</h2> *   <ion-scroll direction="x" class="available-scroller"> *     <div class="photo" collection-repeat="photo in main.photos" *        item-height="250" item-width="photo.width + 30"> *        <img ng-src="{% raw %}{{photo.src}}{% endraw %}"> *     </div> *   </ion-scroll> * </ion-content> * ``` * * @param {expression} collection-repeat The expression indicating how to enumerate a collection, *   of the format  `variable in expression` – where variable is the user defined loop variable *   and `expression` is a scope expression giving the collection to enumerate. *   For example: `album in artist.albums` or `album in artist.albums | orderBy:'name'`. * @param {expression=} item-width The width of the repeated element. The expression must return *   a number (pixels) or a percentage. Defaults to the width of the first item in the list. *   (previously named collection-item-width) * @param {expression=} item-height The height of the repeated element. The expression must return *   a number (pixels) or a percentage. Defaults to the height of the first item in the list. *   (previously named collection-item-height) * @param {number=} item-render-buffer The number of items to load before and after the visible *   items in the list. Default 3. Tip: set this higher if you have lots of images to preload, but *   don't set it too high or you'll see performance loss. * @param {boolean=} force-refresh-images Force images to refresh as you scroll. This fixes a problem *   where, when an element is interchanged as scrolling, its image will still have the old src *   while the new src loads. Setting this to true comes with a small performance loss. */IonicModule.directive('collectionRepeat', CollectionRepeatDirective).factory('$ionicCollectionManager', RepeatManagerFactory);var ONE_PX_TRANSPARENT_IMG_SRC = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';var WIDTH_HEIGHT_REGEX = /height:.*?px;\s*width:.*?px/;var DEFAULT_RENDER_BUFFER = 3;CollectionRepeatDirective.$inject = ['$ionicCollectionManager', '$parse', '$window', '$$rAF', '$rootScope', '$timeout'];function CollectionRepeatDirective($ionicCollectionManager, $parse, $window, $$rAF, $rootScope, $timeout) {  return {    restrict: 'A',    priority: 1000,    transclude: 'element',    $$tlb: true,    require: '^^$ionicScroll',    link: postLink  };  function postLink(scope, element, attr, scrollCtrl, transclude) {    var scrollView = scrollCtrl.scrollView;    var node = element[0];    var containerNode = angular.element('<div class="collection-repeat-container">')[0];    node.parentNode.replaceChild(containerNode, node);    if (scrollView.options.scrollingX && scrollView.options.scrollingY) {      throw new Error("collection-repeat expected a parent x or y scrollView, not " +                      "an xy scrollView.");    }    var repeatExpr = attr.collectionRepeat;    var match = repeatExpr.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?\s*$/);    if (!match) {      throw new Error("collection-repeat expected expression in form of '_item_ in " +                      "_collection_[ track by _id_]' but got '" + attr.collectionRepeat + "'.");    }    var keyExpr = match[1];    var listExpr = match[2];    var listGetter = $parse(listExpr);    var heightData = {};    var widthData = {};    var computedStyleDimensions = {};    var data = [];    var repeatManager;    // attr.collectionBufferSize is deprecated    var renderBufferExpr = attr.itemRenderBuffer || attr.collectionBufferSize;    var renderBuffer = angular.isDefined(renderBufferExpr) ?      parseInt(renderBufferExpr) :      DEFAULT_RENDER_BUFFER;    // attr.collectionItemHeight is deprecated    var heightExpr = attr.itemHeight || attr.collectionItemHeight;    // attr.collectionItemWidth is deprecated    var widthExpr = attr.itemWidth || attr.collectionItemWidth;    var afterItemsContainer = initAfterItemsContainer();    var changeValidator = makeChangeValidator();    initDimensions();    // Dimensions are refreshed on resize or data change.    scrollCtrl.$element.on('scroll-resize', refreshDimensions);    angular.element($window).on('resize', onResize);    var unlistenToExposeAside = $rootScope.$on('$ionicExposeAside', ionic.animationFrameThrottle(function() {      scrollCtrl.scrollView.resize();      onResize();    }));    $timeout(refreshDimensions, 0, false);    function onResize() {      if (changeValidator.resizeRequiresRefresh(scrollView.__clientWidth, scrollView.__clientHeight)) {        refreshDimensions();      }    }    scope.$watchCollection(listGetter, function(newValue) {      data = newValue || (newValue = []);      if (!angular.isArray(newValue)) {        throw new Error("collection-repeat expected an array for '" + listExpr + "', " +          "but got a " + typeof value);      }      // Wait for this digest to end before refreshing everything.      scope.$$postDigest(function() {        getRepeatManager().setData(data);        if (changeValidator.dataChangeRequiresRefresh(data)) refreshDimensions();      });    });    scope.$on('$destroy', function() {      angular.element($window).off('resize', onResize);      unlistenToExposeAside();      scrollCtrl.$element && scrollCtrl.$element.off('scroll-resize', refreshDimensions);      computedStyleNode && computedStyleNode.parentNode &&        computedStyleNode.parentNode.removeChild(computedStyleNode);      computedStyleScope && computedStyleScope.$destroy();      computedStyleScope = computedStyleNode = null;      repeatManager && repeatManager.destroy();      repeatManager = null;    });    function makeChangeValidator() {      var self;      return (self = {        dataLength: 0,        width: 0,        height: 0,        // A resize triggers a refresh only if we have data, the scrollView has size,        // and the size has changed.        resizeRequiresRefresh: function(newWidth, newHeight) {          var requiresRefresh = self.dataLength && newWidth && newHeight &&            (newWidth !== self.width || newHeight !== self.height);          self.width = newWidth;          self.height = newHeight;          return !!requiresRefresh;        },        // A change in data only triggers a refresh if the data has length, or if the data's        // length is less than before.        dataChangeRequiresRefresh: function(newData) {          var requiresRefresh = newData.length > 0 || newData.length < self.dataLength;          self.dataLength = newData.length;          return !!requiresRefresh;        }      });    }    function getRepeatManager() {      return repeatManager || (repeatManager = new $ionicCollectionManager({        afterItemsNode: afterItemsContainer[0],        containerNode: containerNode,        heightData: heightData,        widthData: widthData,        forceRefreshImages: !!(isDefined(attr.forceRefreshImages) && attr.forceRefreshImages !== 'false'),        keyExpression: keyExpr,        renderBuffer: renderBuffer,        scope: scope,        scrollView: scrollCtrl.scrollView,        transclude: transclude      }));    }    function initAfterItemsContainer() {      var container = angular.element(        scrollView.__content.querySelector('.collection-repeat-after-container')      );      // Put everything in the view after the repeater into a container.      if (!container.length) {        var elementIsAfterRepeater = false;        var afterNodes = [].filter.call(scrollView.__content.childNodes, function(node) {          if (ionic.DomUtil.contains(node, containerNode)) {            elementIsAfterRepeater = true;            return false;          }          return elementIsAfterRepeater;        });        container = angular.element('<span class="collection-repeat-after-container">');        if (scrollView.options.scrollingX) {          container.addClass('horizontal');        }        container.append(afterNodes);        scrollView.__content.appendChild(container[0]);      }      return container;    }    function initDimensions() {      //Height and width have four 'modes':      //1) Computed Mode      //  - Nothing is supplied, so we getComputedStyle() on one element in the list and use      //    that width and height value for the width and height of every item. This is re-computed      //    every resize.      //2) Constant Mode, Static Integer      //  - The user provides a constant number for width or height, in pixels. We parse it,      //    store it on the `value` field, and it never changes      //3) Constant Mode, Percent      //  - The user provides a percent string for width or height. The getter for percent is      //    stored on the `getValue()` field, and is re-evaluated once every resize. The result      //    is stored on the `value` field.      //4) Dynamic Mode      //  - The user provides a dynamic expression for the width or height.  This is re-evaluated      //    for every item, stored on the `.getValue()` field.      if (heightExpr) {        parseDimensionAttr(heightExpr, heightData);      } else {        heightData.computed = true;      }      if (widthExpr) {        parseDimensionAttr(widthExpr, widthData);      } else {        widthData.computed = true;      }    }    function refreshDimensions() {      var hasData = data.length > 0;      if (hasData && (heightData.computed || widthData.computed)) {        computeStyleDimensions();      }      if (hasData && heightData.computed) {        heightData.value = computedStyleDimensions.height;        if (!heightData.value) {          throw new Error('collection-repeat tried to compute the height of repeated elements "' +            repeatExpr + '", but was unable to. Please provide the "item-height" attribute. ' +            'http://ionicframework.com/docs/api/directive/collectionRepeat/');        }      } else if (!heightData.dynamic && heightData.getValue) {        // If it's a constant with a getter (eg percent), we just refresh .value after resize        heightData.value = heightData.getValue();      }      if (hasData && widthData.computed) {        widthData.value = computedStyleDimensions.width;        if (!widthData.value) {          throw new Error('collection-repeat tried to compute the width of repeated elements "' +            repeatExpr + '", but was unable to. Please provide the "item-width" attribute. ' +            'http://ionicframework.com/docs/api/directive/collectionRepeat/');        }      } else if (!widthData.dynamic && widthData.getValue) {        // If it's a constant with a getter (eg percent), we just refresh .value after resize        widthData.value = widthData.getValue();      }      // Dynamic dimensions aren't updated on resize. Since they're already dynamic anyway,      // .getValue() will be used.      getRepeatManager().refreshLayout();    }    function parseDimensionAttr(attrValue, dimensionData) {      if (!attrValue) return;      var parsedValue;      // Try to just parse the plain attr value      try {        parsedValue = $parse(attrValue);      } catch (e) {        // If the parse fails and the value has `px` or `%` in it, surround the attr in        // quotes, to attempt to let the user provide a simple `attr="100%"` or `attr="100px"`        if (attrValue.trim().match(/\d+(px|%)$/)) {          attrValue = '"' + attrValue + '"';        }        parsedValue = $parse(attrValue);      }      var constantAttrValue = attrValue.replace(/(\'|\"|px|%)/g, '').trim();      var isConstant = constantAttrValue.length && !/([a-zA-Z]|\$|:|\?)/.test(constantAttrValue);      dimensionData.attrValue = attrValue;      // If it's a constant, it's either a percent or just a constant pixel number.      if (isConstant) {        // For percents, store the percent getter on .getValue()        if (attrValue.indexOf('%') > -1) {          var decimalValue = parseFloat(parsedValue()) / 100;          dimensionData.getValue = dimensionData === heightData ?            function() { return Math.floor(decimalValue * scrollView.__clientHeight); } :            function() { return Math.floor(decimalValue * scrollView.__clientWidth); };        } else {          // For static constants, just store the static constant.          dimensionData.value = parseInt(parsedValue());        }      } else {        dimensionData.dynamic = true;        dimensionData.getValue = dimensionData === heightData ?          function heightGetter(scope, locals) {            var result = parsedValue(scope, locals);            if (result.charAt && result.charAt(result.length - 1) === '%') {              return Math.floor(parseFloat(result) / 100 * scrollView.__clientHeight);            }            return parseInt(result);          } :          function widthGetter(scope, locals) {            var result = parsedValue(scope, locals);            if (result.charAt && result.charAt(result.length - 1) === '%') {              return Math.floor(parseFloat(result) / 100 * scrollView.__clientWidth);            }            return parseInt(result);          };      }    }    var computedStyleNode;    var computedStyleScope;    function computeStyleDimensions() {      if (!computedStyleNode) {        transclude(computedStyleScope = scope.$new(), function(clone) {          clone[0].removeAttribute('collection-repeat'); // remove absolute position styling          computedStyleNode = clone[0];        });      }      computedStyleScope[keyExpr] = (listGetter(scope) || [])[0];      if (!$rootScope.$$phase) computedStyleScope.$digest();      containerNode.appendChild(computedStyleNode);      var style = $window.getComputedStyle(computedStyleNode);      computedStyleDimensions.width = parseInt(style.width);      computedStyleDimensions.height = parseInt(style.height);      containerNode.removeChild(computedStyleNode);    }  }}RepeatManagerFactory.$inject = ['$rootScope', '$window', '$$rAF'];function RepeatManagerFactory($rootScope, $window, $$rAF) {  var EMPTY_DIMENSION = { primaryPos: 0, secondaryPos: 0, primarySize: 0, secondarySize: 0, rowPrimarySize: 0 };  return function RepeatController(options) {    var afterItemsNode = options.afterItemsNode;    var containerNode = options.containerNode;    var forceRefreshImages = options.forceRefreshImages;    var heightData = options.heightData;    var widthData = options.widthData;    var keyExpression = options.keyExpression;    var renderBuffer = options.renderBuffer;    var scope = options.scope;    var scrollView = options.scrollView;    var transclude = options.transclude;    var data = [];    var getterLocals = {};    var heightFn = heightData.getValue || function() { return heightData.value; };    var heightGetter = function(index, value) {      getterLocals[keyExpression] = value;      getterLocals.$index = index;      return heightFn(scope, getterLocals);    };    var widthFn = widthData.getValue || function() { return widthData.value; };    var widthGetter = function(index, value) {      getterLocals[keyExpression] = value;      getterLocals.$index = index;      return widthFn(scope, getterLocals);    };    var isVertical = !!scrollView.options.scrollingY;    // We say it's a grid view if we're either dynamic or not 100% width    var isGridView = isVertical ?      (widthData.dynamic || widthData.value !== scrollView.__clientWidth) :      (heightData.dynamic || heightData.value !== scrollView.__clientHeight);    var isStaticView = !heightData.dynamic && !widthData.dynamic;    var PRIMARY = 'PRIMARY';    var SECONDARY = 'SECONDARY';    var TRANSLATE_TEMPLATE_STR = isVertical ?      'translate3d(SECONDARYpx,PRIMARYpx,0)' :      'translate3d(PRIMARYpx,SECONDARYpx,0)';    var WIDTH_HEIGHT_TEMPLATE_STR = isVertical ?      'height: PRIMARYpx; width: SECONDARYpx;' :      'height: SECONDARYpx; width: PRIMARYpx;';    var estimatedHeight;    var estimatedWidth;    var repeaterBeforeSize = 0;    var repeaterAfterSize = 0;    var renderStartIndex = -1;    var renderEndIndex = -1;    var renderAfterBoundary = -1;    var renderBeforeBoundary = -1;    var itemsPool = [];    var itemsLeaving = [];    var itemsEntering = [];    var itemsShownMap = {};    var nextItemId = 0;    var scrollViewSetDimensions = isVertical ?      function() { scrollView.setDimensions(null, null, null, view.getContentSize(), true); } :      function() { scrollView.setDimensions(null, null, view.getContentSize(), null, true); };    // view is a mix of list/grid methods + static/dynamic methods.    // See bottom for implementations. Available methods:    //    // getEstimatedPrimaryPos(i), getEstimatedSecondaryPos(i), getEstimatedIndex(scrollTop),    // calculateDimensions(toIndex), getDimensions(index),    // updateRenderRange(scrollTop, scrollValueEnd), onRefreshLayout(), onRefreshData()    var view = isVertical ? new VerticalViewType() : new HorizontalViewType();    (isGridView ? GridViewType : ListViewType).call(view);    (isStaticView ? StaticViewType : DynamicViewType).call(view);    var contentSizeStr = isVertical ? 'getContentHeight' : 'getContentWidth';    var originalGetContentSize = scrollView.options[contentSizeStr];    scrollView.options[contentSizeStr] = angular.bind(view, view.getContentSize);    scrollView.__$callback = scrollView.__callback;    scrollView.__callback = function(transformLeft, transformTop, zoom, wasResize) {      var scrollValue = view.getScrollValue();      if (renderStartIndex === -1 ||          scrollValue + view.scrollPrimarySize > renderAfterBoundary ||          scrollValue < renderBeforeBoundary) {        render();      }      scrollView.__$callback(transformLeft, transformTop, zoom, wasResize);    };    var isLayoutReady = false;    var isDataReady = false;    this.refreshLayout = function() {      if (data.length) {        estimatedHeight = heightGetter(0, data[0]);        estimatedWidth = widthGetter(0, data[0]);      } else {        // If we don't have any data in our array, just guess.        estimatedHeight = 100;        estimatedWidth = 100;      }      // Get the size of every element AFTER the repeater. We have to get the margin before and      // after the first/last element to fix a browser bug with getComputedStyle() not counting      // the first/last child's margins into height.      var style = getComputedStyle(afterItemsNode) || {};      var firstStyle = afterItemsNode.firstElementChild && getComputedStyle(afterItemsNode.firstElementChild) || {};      var lastStyle = afterItemsNode.lastElementChild && getComputedStyle(afterItemsNode.lastElementChild) || {};      repeaterAfterSize = (parseInt(style[isVertical ? 'height' : 'width']) || 0) +        (firstStyle && parseInt(firstStyle[isVertical ? 'marginTop' : 'marginLeft']) || 0) +        (lastStyle && parseInt(lastStyle[isVertical ? 'marginBottom' : 'marginRight']) || 0);      // Get the offsetTop of the repeater.      repeaterBeforeSize = 0;      var current = containerNode;      do {        repeaterBeforeSize += current[isVertical ? 'offsetTop' : 'offsetLeft'];      } while ( ionic.DomUtil.contains(scrollView.__content, current = current.offsetParent) );      var containerPrevNode = containerNode.previousElementSibling;      var beforeStyle = containerPrevNode ? $window.getComputedStyle(containerPrevNode) : {};      var beforeMargin = parseInt(beforeStyle[isVertical ? 'marginBottom' : 'marginRight'] || 0);      // Because we position the collection container with position: relative, it doesn't take      // into account where to position itself relative to the previous element's marginBottom.      // To compensate, we translate the container up by the previous element's margin.      containerNode.style[ionic.CSS.TRANSFORM] = TRANSLATE_TEMPLATE_STR        .replace(PRIMARY, -beforeMargin)        .replace(SECONDARY, 0);      repeaterBeforeSize -= beforeMargin;      if (!scrollView.__clientHeight || !scrollView.__clientWidth) {        scrollView.__clientWidth = scrollView.__container.clientWidth;        scrollView.__clientHeight = scrollView.__container.clientHeight;      }      (view.onRefreshLayout || angular.noop)();      view.refreshDirection();      scrollViewSetDimensions();      // Create the pool of items for reuse, setting the size to (estimatedItemsOnScreen) * 2,      // plus the size of the renderBuffer.      if (!isLayoutReady) {        var poolSize = Math.max(20, renderBuffer * 3);        for (var i = 0; i < poolSize; i++) {          itemsPool.push(new RepeatItem());        }      }      isLayoutReady = true;      if (isLayoutReady && isDataReady) {        // If the resize or latest data change caused the scrollValue to        // now be out of bounds, resize the scrollView.        if (scrollView.__scrollLeft > scrollView.__maxScrollLeft ||            scrollView.__scrollTop > scrollView.__maxScrollTop) {          scrollView.resize();        }        forceRerender(true);      }    };    this.setData = function(newData) {      data = newData;      (view.onRefreshData || angular.noop)();      isDataReady = true;    };    this.destroy = function() {      render.destroyed = true;      itemsPool.forEach(function(item) {        item.scope.$destroy();        item.scope = item.element = item.node = item.images = null;      });      itemsPool.length = itemsEntering.length = itemsLeaving.length = 0;      itemsShownMap = {};      //Restore the scrollView's normal behavior and resize it to normal size.      scrollView.options[contentSizeStr] = originalGetContentSize;      scrollView.__callback = scrollView.__$callback;      scrollView.resize();      (view.onDestroy || angular.noop)();    };    function forceRerender() {      return render(true);    }    function render(forceRerender) {      if (render.destroyed) return;      var i;      var ii;      var item;      var dim;      var scope;      var scrollValue = view.getScrollValue();      var scrollValueEnd = scrollValue + view.scrollPrimarySize;      view.updateRenderRange(scrollValue, scrollValueEnd);      renderStartIndex = Math.max(0, renderStartIndex - renderBuffer);      renderEndIndex = Math.min(data.length - 1, renderEndIndex + renderBuffer);      for (i in itemsShownMap) {        if (i < renderStartIndex || i > renderEndIndex) {          item = itemsShownMap[i];          delete itemsShownMap[i];          itemsLeaving.push(item);          item.isShown = false;        }      }      // Render indicies that aren't shown yet      //      // NOTE(ajoslin): this may sound crazy, but calling any other functions during this render      // loop will often push the render time over the edge from less than one frame to over      // one frame, causing visible jank.      // DON'T call any other functions inside this loop unless it's vital.      for (i = renderStartIndex; i <= renderEndIndex; i++) {        // We only go forward with render if the index is in data, the item isn't already shown,        // or forceRerender is on.        if (i >= data.length || (itemsShownMap[i] && !forceRerender)) continue;        item = itemsShownMap[i] || (itemsShownMap[i] = itemsLeaving.length ? itemsLeaving.pop() :                                    itemsPool.length ? itemsPool.shift() :                                    new RepeatItem());        itemsEntering.push(item);        item.isShown = true;        scope = item.scope;        scope.$index = i;        scope[keyExpression] = data[i];        scope.$first = (i === 0);        scope.$last = (i === (data.length - 1));        scope.$middle = !(scope.$first || scope.$last);        scope.$odd = !(scope.$even = (i & 1) === 0);        if (scope.$$disconnected) ionic.Utils.reconnectScope(item.scope);        dim = view.getDimensions(i);        if (item.secondaryPos !== dim.secondaryPos || item.primaryPos !== dim.primaryPos) {          item.node.style[ionic.CSS.TRANSFORM] = TRANSLATE_TEMPLATE_STR            .replace(PRIMARY, (item.primaryPos = dim.primaryPos))            .replace(SECONDARY, (item.secondaryPos = dim.secondaryPos));        }        if (item.secondarySize !== dim.secondarySize || item.primarySize !== dim.primarySize) {          item.node.style.cssText = item.node.style.cssText            .replace(WIDTH_HEIGHT_REGEX, WIDTH_HEIGHT_TEMPLATE_STR              //TODO fix item.primarySize + 1 hack              .replace(PRIMARY, (item.primarySize = dim.primarySize) + 1)              .replace(SECONDARY, (item.secondarySize = dim.secondarySize))            );        }      }      // If we reach the end of the list, render the afterItemsNode - this contains all the      // elements the developer placed after the collection-repeat      if (renderEndIndex === data.length - 1) {        dim = view.getDimensions(data.length - 1) || EMPTY_DIMENSION;        afterItemsNode.style[ionic.CSS.TRANSFORM] = TRANSLATE_TEMPLATE_STR          .replace(PRIMARY, dim.primaryPos + dim.primarySize)          .replace(SECONDARY, 0);      }      while (itemsLeaving.length) {        item = itemsLeaving.pop();        item.scope.$broadcast('$collectionRepeatLeave');        ionic.Utils.disconnectScope(item.scope);        itemsPool.push(item);        item.node.style[ionic.CSS.TRANSFORM] = 'translate3d(-9999px,-9999px,0)';        item.primaryPos = item.secondaryPos = null;      }      if (forceRefreshImages) {        for (i = 0, ii = itemsEntering.length; i < ii && (item = itemsEntering[i]); i++) {          if (!item.images) continue;          for (var j = 0, jj = item.images.length, img; j < jj && (img = item.images[j]); j++) {            var src = img.src;            img.src = ONE_PX_TRANSPARENT_IMG_SRC;            img.src = src;          }        }      }      if (forceRerender) {        var rootScopePhase = $rootScope.$$phase;        while (itemsEntering.length) {          item = itemsEntering.pop();          if (!rootScopePhase) item.scope.$digest();        }      } else {        digestEnteringItems();      }    }    function digestEnteringItems() {      var item;      if (digestEnteringItems.running) return;      digestEnteringItems.running = true;      $$rAF(function process() {        var rootScopePhase = $rootScope.$$phase;        while (itemsEntering.length) {          item = itemsEntering.pop();          if (item.isShown) {            if (!rootScopePhase) item.scope.$digest();          }        }        digestEnteringItems.running = false;      });    }    function RepeatItem() {      var self = this;      this.scope = scope.$new();      this.id = 'item' + (nextItemId++);      transclude(this.scope, function(clone) {        self.element = clone;        self.element.data('$$collectionRepeatItem', self);        // TODO destroy        self.node = clone[0];        // Batch style setting to lower repaints        self.node.style[ionic.CSS.TRANSFORM] = 'translate3d(-9999px,-9999px,0)';        self.node.style.cssText += ' height: 0px; width: 0px;';        ionic.Utils.disconnectScope(self.scope);        containerNode.appendChild(self.node);        self.images = clone[0].getElementsByTagName('img');      });    }    function VerticalViewType() {      this.getItemPrimarySize = heightGetter;      this.getItemSecondarySize = widthGetter;      this.getScrollValue = function() {        return Math.max(0, Math.min(scrollView.__scrollTop - repeaterBeforeSize,          scrollView.__maxScrollTop - repeaterBeforeSize - repeaterAfterSize));      };      this.refreshDirection = function() {        this.scrollPrimarySize = scrollView.__clientHeight;        this.scrollSecondarySize = scrollView.__clientWidth;        this.estimatedPrimarySize = estimatedHeight;        this.estimatedSecondarySize = estimatedWidth;        this.estimatedItemsAcross = isGridView &&          Math.floor(scrollView.__clientWidth / estimatedWidth) ||          1;      };    }    function HorizontalViewType() {      this.getItemPrimarySize = widthGetter;      this.getItemSecondarySize = heightGetter;      this.getScrollValue = function() {        return Math.max(0, Math.min(scrollView.__scrollLeft - repeaterBeforeSize,          scrollView.__maxScrollLeft - repeaterBeforeSize - repeaterAfterSize));      };      this.refreshDirection = function() {        this.scrollPrimarySize = scrollView.__clientWidth;        this.scrollSecondarySize = scrollView.__clientHeight;        this.estimatedPrimarySize = estimatedWidth;        this.estimatedSecondarySize = estimatedHeight;        this.estimatedItemsAcross = isGridView &&          Math.floor(scrollView.__clientHeight / estimatedHeight) ||          1;      };    }    function GridViewType() {      this.getEstimatedSecondaryPos = function(index) {        return (index % this.estimatedItemsAcross) * this.estimatedSecondarySize;      };      this.getEstimatedPrimaryPos = function(index) {        return Math.floor(index / this.estimatedItemsAcross) * this.estimatedPrimarySize;      };      this.getEstimatedIndex = function(scrollValue) {        return Math.floor(scrollValue / this.estimatedPrimarySize) *          this.estimatedItemsAcross;      };    }    function ListViewType() {      this.getEstimatedSecondaryPos = function() {        return 0;      };      this.getEstimatedPrimaryPos = function(index) {        return index * this.estimatedPrimarySize;      };      this.getEstimatedIndex = function(scrollValue) {        return Math.floor((scrollValue) / this.estimatedPrimarySize);      };    }    function StaticViewType() {      this.getContentSize = function() {        return this.getEstimatedPrimaryPos(data.length - 1) + this.estimatedPrimarySize +          repeaterBeforeSize + repeaterAfterSize;      };      // static view always returns the same object for getDimensions, to avoid memory allocation      // while scrolling. This could be dangerous if this was a public function, but it's not.      // Only we use it.      var dim = {};      this.getDimensions = function(index) {        dim.primaryPos = this.getEstimatedPrimaryPos(index);        dim.secondaryPos = this.getEstimatedSecondaryPos(index);        dim.primarySize = this.estimatedPrimarySize;        dim.secondarySize = this.estimatedSecondarySize;        return dim;      };      this.updateRenderRange = function(scrollValue, scrollValueEnd) {        renderStartIndex = Math.max(0, this.getEstimatedIndex(scrollValue));        // Make sure the renderEndIndex takes into account all the items on the row        renderEndIndex = Math.min(data.length - 1,          this.getEstimatedIndex(scrollValueEnd) + this.estimatedItemsAcross - 1);        renderBeforeBoundary = Math.max(0,          this.getEstimatedPrimaryPos(renderStartIndex));        renderAfterBoundary = this.getEstimatedPrimaryPos(renderEndIndex) +          this.estimatedPrimarySize;      };    }    function DynamicViewType() {      var self = this;      var debouncedScrollViewSetDimensions = ionic.debounce(scrollViewSetDimensions, 25, true);      var calculateDimensions = isGridView ? calculateDimensionsGrid : calculateDimensionsList;      var dimensionsIndex;      var dimensions = [];      // Get the dimensions at index. {width, height, left, top}.      // We start with no dimensions calculated, then any time dimensions are asked for at an      // index we calculate dimensions up to there.      function calculateDimensionsList(toIndex) {        var i, prevDimension, dim;        for (i = Math.max(0, dimensionsIndex); i <= toIndex && (dim = dimensions[i]); i++) {          prevDimension = dimensions[i - 1] || EMPTY_DIMENSION;          dim.primarySize = self.getItemPrimarySize(i, data[i]);          dim.secondarySize = self.scrollSecondarySize;          dim.primaryPos = prevDimension.primaryPos + prevDimension.primarySize;          dim.secondaryPos = 0;        }      }      function calculateDimensionsGrid(toIndex) {        var i, prevDimension, dim;        for (i = Math.max(dimensionsIndex, 0); i <= toIndex && (dim = dimensions[i]); i++) {          prevDimension = dimensions[i - 1] || EMPTY_DIMENSION;          dim.secondarySize = Math.min(            self.getItemSecondarySize(i, data[i]),            self.scrollSecondarySize          );          dim.secondaryPos = prevDimension.secondaryPos + prevDimension.secondarySize;          if (i === 0 || dim.secondaryPos + dim.secondarySize > self.scrollSecondarySize) {            dim.secondaryPos = 0;            dim.primarySize = self.getItemPrimarySize(i, data[i]);            dim.primaryPos = prevDimension.primaryPos + prevDimension.rowPrimarySize;            dim.rowStartIndex = i;            dim.rowPrimarySize = dim.primarySize;          } else {            dim.primarySize = self.getItemPrimarySize(i, data[i]);            dim.primaryPos = prevDimension.primaryPos;            dim.rowStartIndex = prevDimension.rowStartIndex;            dimensions[dim.rowStartIndex].rowPrimarySize = dim.rowPrimarySize = Math.max(              dimensions[dim.rowStartIndex].rowPrimarySize,              dim.primarySize            );            dim.rowPrimarySize = Math.max(dim.primarySize, dim.rowPrimarySize);          }        }      }      this.getContentSize = function() {        var dim = dimensions[dimensionsIndex] || EMPTY_DIMENSION;        return ((dim.primaryPos + dim.primarySize) || 0) +          this.getEstimatedPrimaryPos(data.length - dimensionsIndex - 1) +          repeaterBeforeSize + repeaterAfterSize;      };      this.onDestroy = function() {        dimensions.length = 0;      };      this.onRefreshData = function() {        var i;        var ii;        // Make sure dimensions has as many items as data.length.        // This is to be sure we don't have to allocate objects while scrolling.        for (i = dimensions.length, ii = data.length; i < ii; i++) {          dimensions.push({});        }        dimensionsIndex = -1;      };      this.onRefreshLayout = function() {        dimensionsIndex = -1;      };      this.getDimensions = function(index) {        index = Math.min(index, data.length - 1);        if (dimensionsIndex < index) {          // Once we start asking for dimensions near the end of the list, go ahead and calculate          // everything. This is to make sure when the user gets to the end of the list, the          // scroll height of the list is 100% accurate (not estimated anymore).          if (index > data.length * 0.9) {            calculateDimensions(data.length - 1);            dimensionsIndex = data.length - 1;            scrollViewSetDimensions();          } else {            calculateDimensions(index);            dimensionsIndex = index;            debouncedScrollViewSetDimensions();          }        }        return dimensions[index];      };      var oldRenderStartIndex = -1;      var oldScrollValue = -1;      this.updateRenderRange = function(scrollValue, scrollValueEnd) {        var i;        var len;        var dim;        // Calculate more dimensions than we estimate we'll need, to be sure.        this.getDimensions( this.getEstimatedIndex(scrollValueEnd) * 2 );        // -- Calculate renderStartIndex        // base case: start at 0        if (oldRenderStartIndex === -1 || scrollValue === 0) {          i = 0;        // scrolling down        } else if (scrollValue >= oldScrollValue) {          for (i = oldRenderStartIndex, len = data.length; i < len; i++) {            if ((dim = this.getDimensions(i)) && dim.primaryPos + dim.rowPrimarySize >= scrollValue) {              break;            }          }        // scrolling up        } else {          for (i = oldRenderStartIndex; i >= 0; i--) {            if ((dim = this.getDimensions(i)) && dim.primaryPos <= scrollValue) {              // when grid view, make sure the render starts at the beginning of a row.              i = isGridView ? dim.rowStartIndex : i;              break;            }          }        }        renderStartIndex = Math.min(Math.max(0, i), data.length - 1);        renderBeforeBoundary = renderStartIndex !== -1 ? this.getDimensions(renderStartIndex).primaryPos : -1;        // -- Calculate renderEndIndex        var lastRowDim;        for (i = renderStartIndex + 1, len = data.length; i < len; i++) {          if ((dim = this.getDimensions(i)) && dim.primaryPos + dim.rowPrimarySize > scrollValueEnd) {            // Go all the way to the end of the row if we're in a grid            if (isGridView) {              lastRowDim = dim;              while (i < len - 1 &&                    (dim = this.getDimensions(i + 1)).primaryPos === lastRowDim.primaryPos) {                i++;              }            }            break;          }        }        renderEndIndex = Math.min(i, data.length - 1);        renderAfterBoundary = renderEndIndex !== -1 ?          ((dim = this.getDimensions(renderEndIndex)).primaryPos + (dim.rowPrimarySize || dim.primarySize)) :          -1;        oldScrollValue = scrollValue;        oldRenderStartIndex = renderStartIndex;      };    }  };}/** * @ngdoc directive * @name ionContent * @module ionic * @delegate ionic.service:$ionicScrollDelegate * @restrict E * * @description * The ionContent directive provides an easy to use content area that can be configured * to use Ionic's custom Scroll View, or the built in overflow scrolling of the browser. * * While we recommend using the custom Scroll features in Ionic in most cases, sometimes * (for performance reasons) only the browser's native overflow scrolling will suffice, * and so we've made it easy to toggle between the Ionic scroll implementation and * overflow scrolling. * * You can implement pull-to-refresh with the {@link ionic.directive:ionRefresher} * directive, and infinite scrolling with the {@link ionic.directive:ionInfiniteScroll} * directive. * * If there is any dynamic content inside the ion-content, be sure to call `.resize()` with {@link ionic.service:$ionicScrollDelegate} * after the content has been added. * * Be aware that this directive gets its own child scope. If you do not understand why this * is important, you can read [https://docs.angularjs.org/guide/scope](https://docs.angularjs.org/guide/scope). * * @param {string=} delegate-handle The handle used to identify this scrollView * with {@link ionic.service:$ionicScrollDelegate}. * @param {string=} direction Which way to scroll. 'x' or 'y' or 'xy'. Default 'y'. * @param {boolean=} locking Whether to lock scrolling in one direction at a time. Useful to set to false when zoomed in or scrolling in two directions. Default true. * @param {boolean=} padding Whether to add padding to the content. * Defaults to true on iOS, false on Android. * @param {boolean=} scroll Whether to allow scrolling of content.  Defaults to true. * @param {boolean=} overflow-scroll Whether to use overflow-scrolling instead of * Ionic scroll. See {@link ionic.provider:$ionicConfigProvider} to set this as the global default. * @param {boolean=} scrollbar-x Whether to show the horizontal scrollbar. Default true. * @param {boolean=} scrollbar-y Whether to show the vertical scrollbar. Default true. * @param {string=} start-x Initial horizontal scroll position. Default 0. * @param {string=} start-y Initial vertical scroll position. Default 0. * @param {expression=} on-scroll Expression to evaluate when the content is scrolled. * @param {expression=} on-scroll-complete Expression to evaluate when a scroll action completes. Has access to 'scrollLeft' and 'scrollTop' locals. * @param {boolean=} has-bouncing Whether to allow scrolling to bounce past the edges * of the content.  Defaults to true on iOS, false on Android. * @param {number=} scroll-event-interval Number of milliseconds between each firing of the 'on-scroll' expression. Default 10. */IonicModule.directive('ionContent', [  '$timeout',  '$controller',  '$ionicBind',  '$ionicConfig',function($timeout, $controller, $ionicBind, $ionicConfig) {  return {    restrict: 'E',    require: '^?ionNavView',    scope: true,    priority: 800,    compile: function(element, attr) {      var innerElement;      var scrollCtrl;      element.addClass('scroll-content ionic-scroll');      if (attr.scroll != 'false') {        //We cannot use normal transclude here because it breaks element.data()        //inheritance on compile        innerElement = jqLite('<div class="scroll"></div>');        innerElement.append(element.contents());        element.append(innerElement);      } else {        element.addClass('scroll-content-false');      }      var nativeScrolling = attr.overflowScroll !== "false" && (attr.overflowScroll === "true" || !$ionicConfig.scrolling.jsScrolling());      // collection-repeat requires JS scrolling      if (nativeScrolling) {        nativeScrolling = !element[0].querySelector('[collection-repeat]');      }      return { pre: prelink };      function prelink($scope, $element, $attr) {        var parentScope = $scope.$parent;        $scope.$watch(function() {          return (parentScope.$hasHeader ? ' has-header' : '') +            (parentScope.$hasSubheader ? ' has-subheader' : '') +            (parentScope.$hasFooter ? ' has-footer' : '') +            (parentScope.$hasSubfooter ? ' has-subfooter' : '') +            (parentScope.$hasTabs ? ' has-tabs' : '') +            (parentScope.$hasTabsTop ? ' has-tabs-top' : '');        }, function(className, oldClassName) {          $element.removeClass(oldClassName);          $element.addClass(className);        });        //Only this ionContent should use these variables from parent scopes        $scope.$hasHeader = $scope.$hasSubheader =          $scope.$hasFooter = $scope.$hasSubfooter =          $scope.$hasTabs = $scope.$hasTabsTop =          false;        $ionicBind($scope, $attr, {          $onScroll: '&onScroll',          $onScrollComplete: '&onScrollComplete',          hasBouncing: '@',          padding: '@',          direction: '@',          scrollbarX: '@',          scrollbarY: '@',          startX: '@',          startY: '@',          scrollEventInterval: '@'        });        $scope.direction = $scope.direction || 'y';        if (isDefined($attr.padding)) {          $scope.$watch($attr.padding, function(newVal) {              (innerElement || $element).toggleClass('padding', !!newVal);          });        }        if ($attr.scroll === "false") {          //do nothing        } else {          var scrollViewOptions = {};          // determined in compile phase above          if (nativeScrolling) {            // use native scrolling            $element.addClass('overflow-scroll');            scrollViewOptions = {              el: $element[0],              delegateHandle: attr.delegateHandle,              startX: $scope.$eval($scope.startX) || 0,              startY: $scope.$eval($scope.startY) || 0,              nativeScrolling: true            };          } else {            // Use JS scrolling            scrollViewOptions = {              el: $element[0],              delegateHandle: attr.delegateHandle,              locking: (attr.locking || 'true') === 'true',              bouncing: $scope.$eval($scope.hasBouncing),              startX: $scope.$eval($scope.startX) || 0,              startY: $scope.$eval($scope.startY) || 0,              scrollbarX: $scope.$eval($scope.scrollbarX) !== false,              scrollbarY: $scope.$eval($scope.scrollbarY) !== false,              scrollingX: $scope.direction.indexOf('x') >= 0,              scrollingY: $scope.direction.indexOf('y') >= 0,              scrollEventInterval: parseInt($scope.scrollEventInterval, 10) || 10,              scrollingComplete: onScrollComplete            };          }          // init scroll controller with appropriate options          scrollCtrl = $controller('$ionicScroll', {            $scope: $scope,            scrollViewOptions: scrollViewOptions          });          $scope.scrollCtrl = scrollCtrl;          $scope.$on('$destroy', function() {            if (scrollViewOptions) {              scrollViewOptions.scrollingComplete = noop;              delete scrollViewOptions.el;            }            innerElement = null;            $element = null;            attr.$$element = null;          });        }        function onScrollComplete() {          $scope.$onScrollComplete({            scrollTop: scrollCtrl.scrollView.__scrollTop,            scrollLeft: scrollCtrl.scrollView.__scrollLeft          });        }      }    }  };}]);/** * @ngdoc directive * @name exposeAsideWhen * @module ionic * @restrict A * @parent ionic.directive:ionSideMenus * * @description * It is common for a tablet application to hide a menu when in portrait mode, but to show the * same menu on the left side when the tablet is in landscape mode. The `exposeAsideWhen` attribute * directive can be used to accomplish a similar interface. * * By default, side menus are hidden underneath its side menu content, and can be opened by either * swiping the content left or right, or toggling a button to show the side menu. However, by adding the * `exposeAsideWhen` attribute directive to an {@link ionic.directive:ionSideMenu} element directive, * a side menu can be given instructions on "when" the menu should be exposed (always viewable). For * example, the `expose-aside-when="large"` attribute will keep the side menu hidden when the viewport's * width is less than `768px`, but when the viewport's width is `768px` or greater, the menu will then * always be shown and can no longer be opened or closed like it could when it was hidden for smaller * viewports. * * Using `large` as the attribute's value is a shortcut value to `(min-width:768px)` since it is * the most common use-case. However, for added flexibility, any valid media query could be added * as the value, such as `(min-width:600px)` or even multiple queries such as * `(min-width:750px) and (max-width:1200px)`. * @usage * ```html * <ion-side-menus> *   <!-- Center content --> *   <ion-side-menu-content> *   </ion-side-menu-content> * *   <!-- Left menu --> *   <ion-side-menu expose-aside-when="large"> *   </ion-side-menu> * </ion-side-menus> * ``` * For a complete side menu example, see the * {@link ionic.directive:ionSideMenus} documentation. */IonicModule.directive('exposeAsideWhen', ['$window', function($window) {  return {    restrict: 'A',    require: '^ionSideMenus',    link: function($scope, $element, $attr, sideMenuCtrl) {      var prevInnerWidth = $window.innerWidth;      var prevInnerHeight = $window.innerHeight;      ionic.on('resize', function() {        if (prevInnerWidth === $window.innerWidth && prevInnerHeight === $window.innerHeight) {          return;        }        prevInnerWidth = $window.innerWidth;        prevInnerHeight = $window.innerHeight;        onResize();      }, $window);      function checkAsideExpose() {        var mq = $attr.exposeAsideWhen == 'large' ? '(min-width:768px)' : $attr.exposeAsideWhen;        sideMenuCtrl.exposeAside($window.matchMedia(mq).matches);        sideMenuCtrl.activeAsideResizing(false);      }      function onResize() {        sideMenuCtrl.activeAsideResizing(true);        debouncedCheck();      }      var debouncedCheck = ionic.debounce(function() {        $scope.$apply(checkAsideExpose);      }, 300, false);      $scope.$evalAsync(checkAsideExpose);    }  };}]);var GESTURE_DIRECTIVES = 'onHold onTap onDoubleTap onTouch onRelease onDragStart onDrag onDragEnd onDragUp onDragRight onDragDown onDragLeft onSwipe onSwipeUp onSwipeRight onSwipeDown onSwipeLeft'.split(' ');GESTURE_DIRECTIVES.forEach(function(name) {  IonicModule.directive(name, gestureDirective(name));});/** * @ngdoc directive * @name onHold * @module ionic * @restrict A * * @description * Touch stays at the same location for 500ms. Similar to long touch events available for AngularJS and jQuery. * * @usage * ```html * <button on-hold="onHold()" class="button">Test</button> * ``` *//** * @ngdoc directive * @name onTap * @module ionic * @restrict A * * @description * Quick touch at a location. If the duration of the touch goes * longer than 250ms it is no longer a tap gesture. * * @usage * ```html * <button on-tap="onTap()" class="button">Test</button> * ``` *//** * @ngdoc directive * @name onDoubleTap * @module ionic * @restrict A * * @description * Double tap touch at a location. * * @usage * ```html * <button on-double-tap="onDoubleTap()" class="button">Test</button> * ``` *//** * @ngdoc directive * @name onTouch * @module ionic * @restrict A * * @description * Called immediately when the user first begins a touch. This * gesture does not wait for a touchend/mouseup. * * @usage * ```html * <button on-touch="onTouch()" class="button">Test</button> * ``` *//** * @ngdoc directive * @name onRelease * @module ionic * @restrict A * * @description * Called when the user ends a touch. * * @usage * ```html * <button on-release="onRelease()" class="button">Test</button> * ``` *//** * @ngdoc directive * @name onDragStart * @module ionic * @restrict A * * @description * Called when a drag gesture has started. * * @usage * ```html * <button on-drag-start="onDragStart()" class="button">Test</button> * ``` *//** * @ngdoc directive * @name onDrag * @module ionic * @restrict A * * @description * Move with one touch around on the page. Blocking the scrolling when * moving left and right is a good practice. When all the drag events are * blocking you disable scrolling on that area. * * @usage * ```html * <button on-drag="onDrag()" class="button">Test</button> * ``` *//** * @ngdoc directive * @name onDragEnd * @module ionic * @restrict A * * @description * Called when a drag gesture has ended. * * @usage * ```html * <button on-drag-end="onDragEnd()" class="button">Test</button> * ``` *//** * @ngdoc directive * @name onDragUp * @module ionic * @restrict A * * @description * Called when the element is dragged up. * * @usage * ```html * <button on-drag-up="onDragUp()" class="button">Test</button> * ``` *//** * @ngdoc directive * @name onDragRight * @module ionic * @restrict A * * @description * Called when the element is dragged to the right. * * @usage * ```html * <button on-drag-right="onDragRight()" class="button">Test</button> * ``` *//** * @ngdoc directive * @name onDragDown * @module ionic * @restrict A * * @description * Called when the element is dragged down. * * @usage * ```html * <button on-drag-down="onDragDown()" class="button">Test</button> * ``` *//** * @ngdoc directive * @name onDragLeft * @module ionic * @restrict A * * @description * Called when the element is dragged to the left. * * @usage * ```html * <button on-drag-left="onDragLeft()" class="button">Test</button> * ``` *//** * @ngdoc directive * @name onSwipe * @module ionic * @restrict A * * @description * Called when a moving touch has a high velocity in any direction. * * @usage * ```html * <button on-swipe="onSwipe()" class="button">Test</button> * ``` *//** * @ngdoc directive * @name onSwipeUp * @module ionic * @restrict A * * @description * Called when a moving touch has a high velocity moving up. * * @usage * ```html * <button on-swipe-up="onSwipeUp()" class="button">Test</button> * ``` *//** * @ngdoc directive * @name onSwipeRight * @module ionic * @restrict A * * @description * Called when a moving touch has a high velocity moving to the right. * * @usage * ```html * <button on-swipe-right="onSwipeRight()" class="button">Test</button> * ``` *//** * @ngdoc directive * @name onSwipeDown * @module ionic * @restrict A * * @description * Called when a moving touch has a high velocity moving down. * * @usage * ```html * <button on-swipe-down="onSwipeDown()" class="button">Test</button> * ``` *//** * @ngdoc directive * @name onSwipeLeft * @module ionic * @restrict A * * @description * Called when a moving touch has a high velocity moving to the left. * * @usage * ```html * <button on-swipe-left="onSwipeLeft()" class="button">Test</button> * ``` */function gestureDirective(directiveName) {  return ['$ionicGesture', '$parse', function($ionicGesture, $parse) {    var eventType = directiveName.substr(2).toLowerCase();    return function(scope, element, attr) {      var fn = $parse( attr[directiveName] );      var listener = function(ev) {        scope.$apply(function() {          fn(scope, {            $event: ev          });        });      };      var gesture = $ionicGesture.on(eventType, listener, element);      scope.$on('$destroy', function() {        $ionicGesture.off(gesture, eventType, listener);      });    };  }];}IonicModule//.directive('ionHeaderBar', tapScrollToTopDirective())/** * @ngdoc directive * @name ionHeaderBar * @module ionic * @restrict E * * @description * Adds a fixed header bar above some content. * * Can also be a subheader (lower down) if the 'bar-subheader' class is applied. * See [the header CSS docs](/docs/components/#subheader). * * @param {string=} align-title How to align the title. By default the title * will be aligned the same as how the platform aligns its titles (iOS centers * titles, Android aligns them left). * Available: 'left', 'right', or 'center'.  Defaults to the same as the platform. * @param {boolean=} no-tap-scroll By default, the header bar will scroll the * content to the top when tapped.  Set no-tap-scroll to true to disable this * behavior. * Available: true or false.  Defaults to false. * * @usage * ```html * <ion-header-bar align-title="left" class="bar-positive"> *   <div class="buttons"> *     <button class="button" ng-click="doSomething()">Left Button</button> *   </div> *   <h1 class="title">Title!</h1> *   <div class="buttons"> *     <button class="button">Right Button</button> *   </div> * </ion-header-bar> * <ion-content class="has-header"> *   Some content! * </ion-content> * ``` */.directive('ionHeaderBar', headerFooterBarDirective(true))/** * @ngdoc directive * @name ionFooterBar * @module ionic * @restrict E * * @description * Adds a fixed footer bar below some content. * * Can also be a subfooter (higher up) if the 'bar-subfooter' class is applied. * See [the footer CSS docs](/docs/components/#footer). * * Note: If you use ionFooterBar in combination with ng-if, the surrounding content * will not align correctly.  This will be fixed soon. * * @param {string=} align-title Where to align the title. * Available: 'left', 'right', or 'center'.  Defaults to 'center'. * * @usage * ```html * <ion-content class="has-footer"> *   Some content! * </ion-content> * <ion-footer-bar align-title="left" class="bar-assertive"> *   <div class="buttons"> *     <button class="button">Left Button</button> *   </div> *   <h1 class="title">Title!</h1> *   <div class="buttons" ng-click="doSomething()"> *     <button class="button">Right Button</button> *   </div> * </ion-footer-bar> * ``` */.directive('ionFooterBar', headerFooterBarDirective(false));function tapScrollToTopDirective() { //eslint-disable-line no-unused-vars  return ['$ionicScrollDelegate', function($ionicScrollDelegate) {    return {      restrict: 'E',      link: function($scope, $element, $attr) {        if ($attr.noTapScroll == 'true') {          return;        }        ionic.on('tap', onTap, $element[0]);        $scope.$on('$destroy', function() {          ionic.off('tap', onTap, $element[0]);        });        function onTap(e) {          var depth = 3;          var current = e.target;          //Don't scroll to top in certain cases          while (depth-- && current) {            if (current.classList.contains('button') ||                current.tagName.match(/input|textarea|select/i) ||                current.isContentEditable) {              return;            }            current = current.parentNode;          }          var touch = e.gesture && e.gesture.touches[0] || e.detail.touches[0];          var bounds = $element[0].getBoundingClientRect();          if (ionic.DomUtil.rectContains(            touch.pageX, touch.pageY,            bounds.left, bounds.top - 20,            bounds.left + bounds.width, bounds.top + bounds.height          )) {            $ionicScrollDelegate.scrollTop(true);          }        }      }    };  }];}function headerFooterBarDirective(isHeader) {  return ['$document', '$timeout', function($document, $timeout) {    return {      restrict: 'E',      controller: '$ionicHeaderBar',      compile: function(tElement) {        tElement.addClass(isHeader ? 'bar bar-header' : 'bar bar-footer');        // top style tabs? if so, remove bottom border for seamless display        $timeout(function() {          if (isHeader && $document[0].getElementsByClassName('tabs-top').length) tElement.addClass('has-tabs-top');        });        return { pre: prelink };        function prelink($scope, $element, $attr, ctrl) {          if (isHeader) {            $scope.$watch(function() { return $element[0].className; }, function(value) {              var isShown = value.indexOf('ng-hide') === -1;              var isSubheader = value.indexOf('bar-subheader') !== -1;              $scope.$hasHeader = isShown && !isSubheader;              $scope.$hasSubheader = isShown && isSubheader;              $scope.$emit('$ionicSubheader', $scope.$hasSubheader);            });            $scope.$on('$destroy', function() {              delete $scope.$hasHeader;              delete $scope.$hasSubheader;            });            ctrl.align();            $scope.$on('$ionicHeader.align', function() {              ionic.requestAnimationFrame(function() {                ctrl.align();              });            });          } else {            $scope.$watch(function() { return $element[0].className; }, function(value) {              var isShown = value.indexOf('ng-hide') === -1;              var isSubfooter = value.indexOf('bar-subfooter') !== -1;              $scope.$hasFooter = isShown && !isSubfooter;              $scope.$hasSubfooter = isShown && isSubfooter;            });            $scope.$on('$destroy', function() {              delete $scope.$hasFooter;              delete $scope.$hasSubfooter;            });            $scope.$watch('$hasTabs', function(val) {              $element.toggleClass('has-tabs', !!val);            });            ctrl.align();            $scope.$on('$ionicFooter.align', function() {              ionic.requestAnimationFrame(function() {                ctrl.align();              });            });          }        }      }    };  }];}/** * @ngdoc directive * @name ionInfiniteScroll * @module ionic * @parent ionic.directive:ionContent, ionic.directive:ionScroll * @restrict E * * @description * The ionInfiniteScroll directive allows you to call a function whenever * the user gets to the bottom of the page or near the bottom of the page. * * The expression you pass in for `on-infinite` is called when the user scrolls * greater than `distance` away from the bottom of the content.  Once `on-infinite` * is done loading new data, it should broadcast the `scroll.infiniteScrollComplete` * event from your controller (see below example). * * @param {expression} on-infinite What to call when the scroller reaches the * bottom. * @param {string=} distance The distance from the bottom that the scroll must * reach to trigger the on-infinite expression. Default: 1%. * @param {string=} spinner The {@link ionic.directive:ionSpinner} to show while loading. The SVG * {@link ionic.directive:ionSpinner} is now the default, replacing rotating font icons. * @param {string=} icon The icon to show while loading. Default: 'ion-load-d'.  This is depreicated * in favor of the SVG {@link ionic.directive:ionSpinner}. * @param {boolean=} immediate-check Whether to check the infinite scroll bounds immediately on load. * * @usage * ```html * <ion-content ng-controller="MyController"> *   <ion-list> *   .... *   .... *   </ion-list> * *   <ion-infinite-scroll *     on-infinite="loadMore()" *     distance="1%"> *   </ion-infinite-scroll> * </ion-content> * ``` * ```js * function MyController($scope, $http) { *   $scope.items = []; *   $scope.loadMore = function() { *     $http.get('/more-items').success(function(items) { *       useItems(items); *       $scope.$broadcast('scroll.infiniteScrollComplete'); *     }); *   }; * *   $scope.$on('$stateChangeSuccess', function() { *     $scope.loadMore(); *   }); * } * ``` * * An easy to way to stop infinite scroll once there is no more data to load * is to use angular's `ng-if` directive: * * ```html * <ion-infinite-scroll *   ng-if="moreDataCanBeLoaded()" *   icon="ion-loading-c" *   on-infinite="loadMoreData()"> * </ion-infinite-scroll> * ``` */IonicModule.directive('ionInfiniteScroll', ['$timeout', function($timeout) {  return {    restrict: 'E',    require: ['?^$ionicScroll', 'ionInfiniteScroll'],    template: function($element, $attrs) {      if ($attrs.icon) return '<i class="icon {{icon()}} icon-refreshing {{scrollingType}}"></i>';      return '<ion-spinner icon="{{spinner()}}"></ion-spinner>';    },    scope: true,    controller: '$ionInfiniteScroll',    link: function($scope, $element, $attrs, ctrls) {      var infiniteScrollCtrl = ctrls[1];      var scrollCtrl = infiniteScrollCtrl.scrollCtrl = ctrls[0];      var jsScrolling = infiniteScrollCtrl.jsScrolling = !scrollCtrl.isNative();      // if this view is not beneath a scrollCtrl, it can't be injected, proceed w/ native scrolling      if (jsScrolling) {        infiniteScrollCtrl.scrollView = scrollCtrl.scrollView;        $scope.scrollingType = 'js-scrolling';        //bind to JS scroll events        scrollCtrl.$element.on('scroll', infiniteScrollCtrl.checkBounds);      } else {        // grabbing the scrollable element, to determine dimensions, and current scroll pos        var scrollEl = ionic.DomUtil.getParentOrSelfWithClass($element[0].parentNode, 'overflow-scroll');        infiniteScrollCtrl.scrollEl = scrollEl;        // if there's no scroll controller, and no overflow scroll div, infinite scroll wont work        if (!scrollEl) {          throw 'Infinite scroll must be used inside a scrollable div';        }        //bind to native scroll events        infiniteScrollCtrl.scrollEl.addEventListener('scroll', infiniteScrollCtrl.checkBounds);      }      // Optionally check bounds on start after scrollView is fully rendered      var doImmediateCheck = isDefined($attrs.immediateCheck) ? $scope.$eval($attrs.immediateCheck) : true;      if (doImmediateCheck) {        $timeout(function() { infiniteScrollCtrl.checkBounds(); });      }    }  };}]);/*** @ngdoc directive* @name ionInput* @parent ionic.directive:ionList* @module ionic* @restrict E* Creates a text input group that can easily be focused** @usage** ```html* <ion-list>*   <ion-input>*     <input type="text" placeholder="First Name">*   </ion-input>**   <ion-input>*     <ion-label>Username</ion-label>*     <input type="text">*   </ion-input>* </ion-list>* ```*/var labelIds = -1;IonicModule.directive('ionInput', [function() {  return {    restrict: 'E',    controller: ['$scope', '$element', function($scope, $element) {      this.$scope = $scope;      this.$element = $element;      this.setInputAriaLabeledBy = function(id) {        var inputs = $element[0].querySelectorAll('input,textarea');        inputs.length && inputs[0].setAttribute('aria-labelledby', id);      };      this.focus = function() {        var inputs = $element[0].querySelectorAll('input,textarea');        inputs.length && inputs[0].focus();      };    }]  };}]);/*** @ngdoc directive* @name ionLabel* @parent ionic.directive:ionList* @module ionic* @restrict E** New in Ionic 1.2. It is strongly recommended that you use `<ion-label>` in place* of any `<label>` elements for maximum cross-browser support and performance.** Creates a label for a form input.** @usage** ```html* <ion-list>*   <ion-input>*     <ion-label>Username</ion-label>*     <input type="text">*   </ion-input>* </ion-list>* ```*/IonicModule.directive('ionLabel', [function() {  return {    restrict: 'E',    require: '?^ionInput',    compile: function() {      return function link($scope, $element, $attrs, ionInputCtrl) {        var element = $element[0];        $element.addClass('input-label');        $element.attr('aria-label', $element.text());        var id = element.id || '_label-' + ++labelIds;        if (!element.id) {          $element.attr('id', id);        }        if (ionInputCtrl) {          ionInputCtrl.setInputAriaLabeledBy(id);          $element.on('click', function() {            ionInputCtrl.focus();          });        }      };    }  };}]);/** * Input label adds accessibility to <span class="input-label">. */IonicModule.directive('inputLabel', [function() {  return {    restrict: 'C',    require: '?^ionInput',    compile: function() {      return function link($scope, $element, $attrs, ionInputCtrl) {        var element = $element[0];        $element.attr('aria-label', $element.text());        var id = element.id || '_label-' + ++labelIds;        if (!element.id) {          $element.attr('id', id);        }        if (ionInputCtrl) {          ionInputCtrl.setInputAriaLabeledBy(id);        }      };    }  };}]);/*** @ngdoc directive* @name ionItem* @parent ionic.directive:ionList* @module ionic* @restrict E* Creates a list-item that can easily be swiped,* deleted, reordered, edited, and more.** See {@link ionic.directive:ionList} for a complete example & explanation.** Can be assigned any item class name. See the* [list CSS documentation](/docs/components/#list).** @usage** ```html* <ion-list>*   <ion-item>Hello!</ion-item>*   <ion-item href="#/detail">*     Link to detail page*   </ion-item>* </ion-list>* ```*/IonicModule.directive('ionItem', ['$$rAF', function($$rAF) {  return {    restrict: 'E',    controller: ['$scope', '$element', function($scope, $element) {      this.$scope = $scope;      this.$element = $element;    }],    scope: true,    compile: function($element, $attrs) {      var isAnchor = isDefined($attrs.href) ||                     isDefined($attrs.ngHref) ||                     isDefined($attrs.uiSref);      var isComplexItem = isAnchor ||        //Lame way of testing, but we have to know at compile what to do with the element        /ion-(delete|option|reorder)-button/i.test($element.html());      if (isComplexItem) {        var innerElement = jqLite(isAnchor ? '<a></a>' : '<div></div>');        innerElement.addClass('item-content');        if (isDefined($attrs.href) || isDefined($attrs.ngHref)) {          innerElement.attr('ng-href', '{{$href()}}');          if (isDefined($attrs.target)) {            innerElement.attr('target', '{{$target()}}');          }        }        innerElement.append($element.contents());        $element.addClass('item item-complex')                .append(innerElement);      } else {        $element.addClass('item');      }      return function link($scope, $element, $attrs) {        $scope.$href = function() {          return $attrs.href || $attrs.ngHref;        };        $scope.$target = function() {          return $attrs.target;        };        var content = $element[0].querySelector('.item-content');        if (content) {          $scope.$on('$collectionRepeatLeave', function() {            if (content && content.$$ionicOptionsOpen) {              content.style[ionic.CSS.TRANSFORM] = '';              content.style[ionic.CSS.TRANSITION] = 'none';              $$rAF(function() {                content.style[ionic.CSS.TRANSITION] = '';              });              content.$$ionicOptionsOpen = false;            }          });        }      };    }  };}]);var ITEM_TPL_DELETE_BUTTON =  '<div class="item-left-edit item-delete enable-pointer-events">' +  '</div>';/*** @ngdoc directive* @name ionDeleteButton* @parent ionic.directive:ionItem* @module ionic* @restrict E* Creates a delete button inside a list item, that is visible when the* {@link ionic.directive:ionList ionList parent's} `show-delete` evaluates to true or* `$ionicListDelegate.showDelete(true)` is called.** Takes any ionicon as a class.** See {@link ionic.directive:ionList} for a complete example & explanation.** @usage** ```html* <ion-list show-delete="shouldShowDelete">*   <ion-item>*     <ion-delete-button class="ion-minus-circled"></ion-delete-button>*     Hello, list item!*   </ion-item>* </ion-list>* <ion-toggle ng-model="shouldShowDelete">*   Show Delete?* </ion-toggle>* ```*/IonicModule.directive('ionDeleteButton', function() {  function stopPropagation(ev) {    ev.stopPropagation();  }  return {    restrict: 'E',    require: ['^^ionItem', '^?ionList'],    //Run before anything else, so we can move it before other directives process    //its location (eg ngIf relies on the location of the directive in the dom)    priority: Number.MAX_VALUE,    compile: function($element, $attr) {      //Add the classes we need during the compile phase, so that they stay      //even if something else like ngIf removes the element and re-addss it      $attr.$set('class', ($attr['class'] || '') + ' button icon button-icon', true);      return function($scope, $element, $attr, ctrls) {        var itemCtrl = ctrls[0];        var listCtrl = ctrls[1];        var container = jqLite(ITEM_TPL_DELETE_BUTTON);        container.append($element);        itemCtrl.$element.append(container).addClass('item-left-editable');        //Don't bubble click up to main .item        $element.on('click', stopPropagation);        init();        $scope.$on('$ionic.reconnectScope', init);        function init() {          listCtrl = listCtrl || $element.controller('ionList');          if (listCtrl && listCtrl.showDelete()) {            container.addClass('visible active');          }        }      };    }  };});IonicModule.directive('itemFloatingLabel', function() {  return {    restrict: 'C',    link: function(scope, element) {      var el = element[0];      var input = el.querySelector('input, textarea');      var inputLabel = el.querySelector('.input-label');      if (!input || !inputLabel) return;      var onInput = function() {        if (input.value) {          inputLabel.classList.add('has-input');        } else {          inputLabel.classList.remove('has-input');        }      };      input.addEventListener('input', onInput);      var ngModelCtrl = jqLite(input).controller('ngModel');      if (ngModelCtrl) {        ngModelCtrl.$render = function() {          input.value = ngModelCtrl.$viewValue || '';          onInput();        };      }      scope.$on('$destroy', function() {        input.removeEventListener('input', onInput);      });    }  };});var ITEM_TPL_OPTION_BUTTONS =  '<div class="item-options invisible">' +  '</div>';/*** @ngdoc directive* @name ionOptionButton* @parent ionic.directive:ionItem* @module ionic* @restrict E* @description* Creates an option button inside a list item, that is visible when the item is swiped* to the left by the user.  Swiped open option buttons can be hidden with* {@link ionic.service:$ionicListDelegate#closeOptionButtons $ionicListDelegate.closeOptionButtons}.** Can be assigned any button class.** See {@link ionic.directive:ionList} for a complete example & explanation.** @usage** ```html* <ion-list>*   <ion-item>*     I love kittens!*     <ion-option-button class="button-positive">Share</ion-option-button>*     <ion-option-button class="button-assertive">Edit</ion-option-button>*   </ion-item>* </ion-list>* ```*/IonicModule.directive('ionOptionButton', [function() {  function stopPropagation(e) {    e.stopPropagation();  }  return {    restrict: 'E',    require: '^ionItem',    priority: Number.MAX_VALUE,    compile: function($element, $attr) {      $attr.$set('class', ($attr['class'] || '') + ' button', true);      return function($scope, $element, $attr, itemCtrl) {        if (!itemCtrl.optionsContainer) {          itemCtrl.optionsContainer = jqLite(ITEM_TPL_OPTION_BUTTONS);          itemCtrl.$element.append(itemCtrl.optionsContainer);        }        itemCtrl.optionsContainer.append($element);        itemCtrl.$element.addClass('item-right-editable');        //Don't bubble click up to main .item        $element.on('click', stopPropagation);      };    }  };}]);var ITEM_TPL_REORDER_BUTTON =  '<div data-prevent-scroll="true" class="item-right-edit item-reorder enable-pointer-events">' +  '</div>';/*** @ngdoc directive* @name ionReorderButton* @parent ionic.directive:ionItem* @module ionic* @restrict E* Creates a reorder button inside a list item, that is visible when the* {@link ionic.directive:ionList ionList parent's} `show-reorder` evaluates to true or* `$ionicListDelegate.showReorder(true)` is called.** Can be dragged to reorder items in the list. Takes any ionicon class.** Note: Reordering works best when used with `ng-repeat`.  Be sure that all `ion-item` children of an `ion-list` are part of the same `ng-repeat` expression.** When an item reorder is complete, the expression given in the `on-reorder` attribute is called. The `on-reorder` expression is given two locals that can be used: `$fromIndex` and `$toIndex`.  See below for an example.** Look at {@link ionic.directive:ionList} for more examples.** @usage** ```html* <ion-list ng-controller="MyCtrl" show-reorder="true">*   <ion-item ng-repeat="item in items">*     Item {{item}}*     <ion-reorder-button class="ion-navicon"*                         on-reorder="moveItem(item, $fromIndex, $toIndex)">*     </ion-reorder-button>*   </ion-item>* </ion-list>* ```* ```js* function MyCtrl($scope) {*   $scope.items = [1, 2, 3, 4];*   $scope.moveItem = function(item, fromIndex, toIndex) {*     //Move the item in the array*     $scope.items.splice(fromIndex, 1);*     $scope.items.splice(toIndex, 0, item);*   };* }* ```** @param {expression=} on-reorder Expression to call when an item is reordered.* Parameters given: $fromIndex, $toIndex.*/IonicModule.directive('ionReorderButton', ['$parse', function($parse) {  return {    restrict: 'E',    require: ['^ionItem', '^?ionList'],    priority: Number.MAX_VALUE,    compile: function($element, $attr) {      $attr.$set('class', ($attr['class'] || '') + ' button icon button-icon', true);      $element[0].setAttribute('data-prevent-scroll', true);      return function($scope, $element, $attr, ctrls) {        var itemCtrl = ctrls[0];        var listCtrl = ctrls[1];        var onReorderFn = $parse($attr.onReorder);        $scope.$onReorder = function(oldIndex, newIndex) {          onReorderFn($scope, {            $fromIndex: oldIndex,            $toIndex: newIndex          });        };        // prevent clicks from bubbling up to the item        if (!$attr.ngClick && !$attr.onClick && !$attr.onclick) {          $element[0].onclick = function(e) {            e.stopPropagation();            return false;          };        }        var container = jqLite(ITEM_TPL_REORDER_BUTTON);        container.append($element);        itemCtrl.$element.append(container).addClass('item-right-editable');        if (listCtrl && listCtrl.showReorder()) {          container.addClass('visible active');        }      };    }  };}]);/** * @ngdoc directive * @name keyboardAttach * @module ionic * @restrict A * * @description * keyboard-attach is an attribute directive which will cause an element to float above * the keyboard when the keyboard shows. Currently only supports the * [ion-footer-bar]({{ page.versionHref }}/api/directive/ionFooterBar/) directive. * * ### Notes * - This directive requires the * [Ionic Keyboard Plugin](https://github.com/driftyco/ionic-plugins-keyboard). * - On Android not in fullscreen mode, i.e. you have *   `<preference name="Fullscreen" value="false" />` or no preference in your `config.xml` file, *   this directive is unnecessary since it is the default behavior. * - On iOS, if there is an input in your footer, you will need to set *   `cordova.plugins.Keyboard.disableScroll(true)`. * * @usage * * ```html *  <ion-footer-bar align-title="left" keyboard-attach class="bar-assertive"> *    <h1 class="title">Title!</h1> *  </ion-footer-bar> * ``` */IonicModule.directive('keyboardAttach', function() {  return function(scope, element) {    ionic.on('native.keyboardshow', onShow, window);    ionic.on('native.keyboardhide', onHide, window);    //deprecated    ionic.on('native.showkeyboard', onShow, window);    ionic.on('native.hidekeyboard', onHide, window);    var scrollCtrl;    function onShow(e) {      if (ionic.Platform.isAndroid() && !ionic.Platform.isFullScreen) {        return;      }      //for testing      var keyboardHeight = e.keyboardHeight || (e.detail && e.detail.keyboardHeight);      element.css('bottom', keyboardHeight + "px");      scrollCtrl = element.controller('$ionicScroll');      if (scrollCtrl) {        scrollCtrl.scrollView.__container.style.bottom = keyboardHeight + keyboardAttachGetClientHeight(element[0]) + "px";      }    }    function onHide() {      if (ionic.Platform.isAndroid() && !ionic.Platform.isFullScreen) {        return;      }      element.css('bottom', '');      if (scrollCtrl) {        scrollCtrl.scrollView.__container.style.bottom = '';      }    }    scope.$on('$destroy', function() {      ionic.off('native.keyboardshow', onShow, window);      ionic.off('native.keyboardhide', onHide, window);      //deprecated      ionic.off('native.showkeyboard', onShow, window);      ionic.off('native.hidekeyboard', onHide, window);    });  };});function keyboardAttachGetClientHeight(element) {  return element.clientHeight;}/*** @ngdoc directive* @name ionList* @module ionic* @delegate ionic.service:$ionicListDelegate* @codepen JsHjf* @restrict E* @description* The List is a widely used interface element in almost any mobile app, and can include* content ranging from basic text all the way to buttons, toggles, icons, and thumbnails.** Both the list, which contains items, and the list items themselves can be any HTML* element. The containing element requires the `list` class and each list item requires* the `item` class.** However, using the ionList and ionItem directives make it easy to support various* interaction modes such as swipe to edit, drag to reorder, and removing items.** Related: {@link ionic.directive:ionItem}, {@link ionic.directive:ionOptionButton}* {@link ionic.directive:ionReorderButton}, {@link ionic.directive:ionDeleteButton}, [`list CSS documentation`](/docs/components/#list).** @usage** Basic Usage:** ```html* <ion-list>*   <ion-item ng-repeat="item in items">*     {% raw %}Hello, {{item}}!{% endraw %}*   </ion-item>* </ion-list>* ```** Advanced Usage: Thumbnails, Delete buttons, Reordering, Swiping** ```html* <ion-list ng-controller="MyCtrl"*           show-delete="shouldShowDelete"*           show-reorder="shouldShowReorder"*           can-swipe="listCanSwipe">*   <ion-item ng-repeat="item in items"*             class="item-thumbnail-left">**     {% raw %}<img ng-src="{{item.img}}">*     <h2>{{item.title}}</h2>*     <p>{{item.description}}</p>{% endraw %}*     <ion-option-button class="button-positive"*                        ng-click="share(item)">*       Share*     </ion-option-button>*     <ion-option-button class="button-info"*                        ng-click="edit(item)">*       Edit*     </ion-option-button>*     <ion-delete-button class="ion-minus-circled"*                        ng-click="items.splice($index, 1)">*     </ion-delete-button>*     <ion-reorder-button class="ion-navicon"*                         on-reorder="reorderItem(item, $fromIndex, $toIndex)">*     </ion-reorder-button>**   </ion-item>* </ion-list>* ```**```javascript* app.controller('MyCtrl', function($scope) {*  $scope.shouldShowDelete = false;*  $scope.shouldShowReorder = false;*  $scope.listCanSwipe = true* });*```** @param {string=} delegate-handle The handle used to identify this list with* {@link ionic.service:$ionicListDelegate}.* @param type {string=} The type of list to use (list-inset or card)* @param show-delete {boolean=} Whether the delete buttons for the items in the list are* currently shown or hidden.* @param show-reorder {boolean=} Whether the reorder buttons for the items in the list are* currently shown or hidden.* @param can-swipe {boolean=} Whether the items in the list are allowed to be swiped to reveal* option buttons. Default: true.*/IonicModule.directive('ionList', [  '$timeout',function($timeout) {  return {    restrict: 'E',    require: ['ionList', '^?$ionicScroll'],    controller: '$ionicList',    compile: function($element, $attr) {      var listEl = jqLite('<div class="list">')        .append($element.contents())        .addClass($attr.type);      $element.append(listEl);      return function($scope, $element, $attrs, ctrls) {        var listCtrl = ctrls[0];        var scrollCtrl = ctrls[1];        // Wait for child elements to render...        $timeout(init);        function init() {          var listView = listCtrl.listView = new ionic.views.ListView({            el: $element[0],            listEl: $element.children()[0],            scrollEl: scrollCtrl && scrollCtrl.element,            scrollView: scrollCtrl && scrollCtrl.scrollView,            onReorder: function(el, oldIndex, newIndex) {              var itemScope = jqLite(el).scope();              if (itemScope && itemScope.$onReorder) {                // Make sure onReorder is called in apply cycle,                // but also make sure it has no conflicts by doing                // $evalAsync                $timeout(function() {                  itemScope.$onReorder(oldIndex, newIndex);                });              }            },            canSwipe: function() {              return listCtrl.canSwipeItems();            }          });          $scope.$on('$destroy', function() {            if (listView) {              listView.deregister && listView.deregister();              listView = null;            }          });          if (isDefined($attr.canSwipe)) {            $scope.$watch('!!(' + $attr.canSwipe + ')', function(value) {              listCtrl.canSwipeItems(value);            });          }          if (isDefined($attr.showDelete)) {            $scope.$watch('!!(' + $attr.showDelete + ')', function(value) {              listCtrl.showDelete(value);            });          }          if (isDefined($attr.showReorder)) {            $scope.$watch('!!(' + $attr.showReorder + ')', function(value) {              listCtrl.showReorder(value);            });          }          $scope.$watch(function() {            return listCtrl.showDelete();          }, function(isShown, wasShown) {            //Only use isShown=false if it was already shown            if (!isShown && !wasShown) { return; }            if (isShown) listCtrl.closeOptionButtons();            listCtrl.canSwipeItems(!isShown);            $element.children().toggleClass('list-left-editing', isShown);            $element.toggleClass('disable-pointer-events', isShown);            var deleteButton = jqLite($element[0].getElementsByClassName('item-delete'));            setButtonShown(deleteButton, listCtrl.showDelete);          });          $scope.$watch(function() {            return listCtrl.showReorder();          }, function(isShown, wasShown) {            //Only use isShown=false if it was already shown            if (!isShown && !wasShown) { return; }            if (isShown) listCtrl.closeOptionButtons();            listCtrl.canSwipeItems(!isShown);            $element.children().toggleClass('list-right-editing', isShown);            $element.toggleClass('disable-pointer-events', isShown);            var reorderButton = jqLite($element[0].getElementsByClassName('item-reorder'));            setButtonShown(reorderButton, listCtrl.showReorder);          });          function setButtonShown(el, shown) {            shown() && el.addClass('visible') || el.removeClass('active');            ionic.requestAnimationFrame(function() {              shown() && el.addClass('active') || el.removeClass('visible');            });          }        }      };    }  };}]);/** * @ngdoc directive * @name menuClose * @module ionic * @restrict AC * * @description * `menu-close` is an attribute directive that closes a currently opened side menu. * Note that by default, navigation transitions will not animate between views when * the menu is open. Additionally, this directive will reset the entering view's * history stack, making the new page the root of the history stack. This is done * to replicate the user experience seen in most side menu implementations, which is * to not show the back button at the root of the stack and show only the * menu button. We recommend that you also use the `enable-menu-with-back-views="false"` * {@link ionic.directive:ionSideMenus} attribute when using the menuClose directive. * * @usage * Below is an example of a link within a side menu. Tapping this link would * automatically close the currently opened menu. * * ```html * <a menu-close href="#/home" class="item">Home</a> * ``` * * Note that if your destination state uses a resolve and that resolve asynchronously * takes longer than a standard transition (300ms), you'll need to set the * `nextViewOptions` manually as your resolve completes. * * ```js * $ionicHistory.nextViewOptions({ *  historyRoot: true, *  disableAnimate: true, *  expire: 300 * }); * ``` */IonicModule.directive('menuClose', ['$ionicHistory', '$timeout', function($ionicHistory, $timeout) {  return {    restrict: 'AC',    link: function($scope, $element) {      $element.bind('click', function() {        var sideMenuCtrl = $element.inheritedData('$ionSideMenusController');        if (sideMenuCtrl) {          $ionicHistory.nextViewOptions({            historyRoot: true,            disableAnimate: true,            expire: 300          });          // if no transition in 300ms, reset nextViewOptions          // the expire should take care of it, but will be cancelled in some          // cases. This directive is an exception to the rules of history.js          $timeout( function() {            $ionicHistory.nextViewOptions({              historyRoot: false,              disableAnimate: false            });          }, 300);          sideMenuCtrl.close();        }      });    }  };}]);/** * @ngdoc directive * @name menuToggle * @module ionic * @restrict AC * * @description * Toggle a side menu on the given side. * * @usage * Below is an example of a link within a nav bar. Tapping this button * would open the given side menu, and tapping it again would close it. * * ```html * <ion-nav-bar> *   <ion-nav-buttons side="left"> *    <!-- Toggle left side menu --> *    <button menu-toggle="left" class="button button-icon icon ion-navicon"></button> *   </ion-nav-buttons> *   <ion-nav-buttons side="right"> *    <!-- Toggle right side menu --> *    <button menu-toggle="right" class="button button-icon icon ion-navicon"></button> *   </ion-nav-buttons> * </ion-nav-bar> * ``` * * ### Button Hidden On Child Views * By default, the menu toggle button will only appear on a root * level side-menu page. Navigating in to child views will hide the menu- * toggle button. They can be made visible on child pages by setting the * enable-menu-with-back-views attribute of the {@link ionic.directive:ionSideMenus} * directive to true. * * ```html * <ion-side-menus enable-menu-with-back-views="true"> * ``` */IonicModule.directive('menuToggle', function() {  return {    restrict: 'AC',    link: function($scope, $element, $attr) {      $scope.$on('$ionicView.beforeEnter', function(ev, viewData) {        if (viewData.enableBack) {          var sideMenuCtrl = $element.inheritedData('$ionSideMenusController');          if (!sideMenuCtrl.enableMenuWithBackViews()) {            $element.addClass('hide');          }        } else {          $element.removeClass('hide');        }      });      $element.bind('click', function() {        var sideMenuCtrl = $element.inheritedData('$ionSideMenusController');        sideMenuCtrl && sideMenuCtrl.toggle($attr.menuToggle);      });    }  };});/* * We don't document the ionModal directive, we instead document * the $ionicModal service */IonicModule.directive('ionModal', [function() {  return {    restrict: 'E',    transclude: true,    replace: true,    controller: [function() {}],    template: '<div class="modal-backdrop">' +                '<div class="modal-backdrop-bg"></div>' +                '<div class="modal-wrapper" ng-transclude></div>' +              '</div>'  };}]);IonicModule.directive('ionModalView', function() {  return {    restrict: 'E',    compile: function(element) {      element.addClass('modal');    }  };});/** * @ngdoc directive * @name ionNavBackButton * @module ionic * @restrict E * @parent ionNavBar * @description * Creates a back button inside an {@link ionic.directive:ionNavBar}. * * The back button will appear when the user is able to go back in the current navigation stack. By * default, the markup of the back button is automatically built using platform-appropriate defaults * (iOS back button icon on iOS and Android icon on Android). * * Additionally, the button is automatically set to `$ionicGoBack()` on click/tap. By default, the * app will navigate back one view when the back button is clicked.  More advanced behavior is also * possible, as outlined below. * * @usage * * Recommended markup for default settings: * * ```html * <ion-nav-bar> *   <ion-nav-back-button> *   </ion-nav-back-button> * </ion-nav-bar> * ``` * * With custom inner markup, and automatically adds a default click action: * * ```html * <ion-nav-bar> *   <ion-nav-back-button class="button-clear"> *     <i class="ion-arrow-left-c"></i> Back *   </ion-nav-back-button> * </ion-nav-bar> * ``` * * With custom inner markup and custom click action, using {@link ionic.service:$ionicHistory}: * * ```html * <ion-nav-bar ng-controller="MyCtrl"> *   <ion-nav-back-button class="button-clear" *     ng-click="myGoBack()"> *     <i class="ion-arrow-left-c"></i> Back *   </ion-nav-back-button> * </ion-nav-bar> * ``` * ```js * function MyCtrl($scope, $ionicHistory) { *   $scope.myGoBack = function() { *     $ionicHistory.goBack(); *   }; * } * ``` */IonicModule.directive('ionNavBackButton', ['$ionicConfig', '$document', function($ionicConfig, $document) {  return {    restrict: 'E',    require: '^ionNavBar',    compile: function(tElement, tAttrs) {      // clone the back button, but as a <div>      var buttonEle = $document[0].createElement('button');      for (var n in tAttrs.$attr) {        buttonEle.setAttribute(tAttrs.$attr[n], tAttrs[n]);      }      if (!tAttrs.ngClick) {        buttonEle.setAttribute('ng-click', '$ionicGoBack()');      }      buttonEle.className = 'button back-button hide buttons ' + (tElement.attr('class') || '');      buttonEle.innerHTML = tElement.html() || '';      var childNode;      var hasIcon = hasIconClass(tElement[0]);      var hasInnerText;      var hasButtonText;      var hasPreviousTitle;      for (var x = 0; x < tElement[0].childNodes.length; x++) {        childNode = tElement[0].childNodes[x];        if (childNode.nodeType === 1) {          if (hasIconClass(childNode)) {            hasIcon = true;          } else if (childNode.classList.contains('default-title')) {            hasButtonText = true;          } else if (childNode.classList.contains('previous-title')) {            hasPreviousTitle = true;          }        } else if (!hasInnerText && childNode.nodeType === 3) {          hasInnerText = !!childNode.nodeValue.trim();        }      }      function hasIconClass(ele) {        return /ion-|icon/.test(ele.className);      }      var defaultIcon = $ionicConfig.backButton.icon();      if (!hasIcon && defaultIcon && defaultIcon !== 'none') {        buttonEle.innerHTML = '<i class="icon ' + defaultIcon + '"></i> ' + buttonEle.innerHTML;        buttonEle.className += ' button-clear';      }      if (!hasInnerText) {        var buttonTextEle = $document[0].createElement('span');        buttonTextEle.className = 'back-text';        if (!hasButtonText && $ionicConfig.backButton.text()) {          buttonTextEle.innerHTML += '<span class="default-title">' + $ionicConfig.backButton.text() + '</span>';        }        if (!hasPreviousTitle && $ionicConfig.backButton.previousTitleText()) {          buttonTextEle.innerHTML += '<span class="previous-title"></span>';        }        buttonEle.appendChild(buttonTextEle);      }      tElement.attr('class', 'hide');      tElement.empty();      return {        pre: function($scope, $element, $attr, navBarCtrl) {          // only register the plain HTML, the navBarCtrl takes care of scope/compile/link          navBarCtrl.navElement('backButton', buttonEle.outerHTML);          buttonEle = null;        }      };    }  };}]);/** * @ngdoc directive * @name ionNavBar * @module ionic * @delegate ionic.service:$ionicNavBarDelegate * @restrict E * * @description * If we have an {@link ionic.directive:ionNavView} directive, we can also create an * `<ion-nav-bar>`, which will create a topbar that updates as the application state changes. * * We can add a back button by putting an {@link ionic.directive:ionNavBackButton} inside. * * We can add buttons depending on the currently visible view using * {@link ionic.directive:ionNavButtons}. * * Note that the ion-nav-bar element will only work correctly if your content has an * ionView around it. * * @usage * * ```html * <body ng-app="starter"> *   <!-- The nav bar that will be updated as we navigate --> *   <ion-nav-bar class="bar-positive"> *   </ion-nav-bar> * *   <!-- where the initial view template will be rendered --> *   <ion-nav-view> *     <ion-view> *       <ion-content>Hello!</ion-content> *     </ion-view> *   </ion-nav-view> * </body> * ``` * * @param {string=} delegate-handle The handle used to identify this navBar * with {@link ionic.service:$ionicNavBarDelegate}. * @param align-title {string=} Where to align the title of the navbar. * Available: 'left', 'right', 'center'. Defaults to 'center'. * @param {boolean=} no-tap-scroll By default, the navbar will scroll the content * to the top when tapped.  Set no-tap-scroll to true to disable this behavior. * * </table><br/> */IonicModule.directive('ionNavBar', function() {  return {    restrict: 'E',    controller: '$ionicNavBar',    scope: true,    link: function($scope, $element, $attr, ctrl) {      ctrl.init();    }  };});/** * @ngdoc directive * @name ionNavButtons * @module ionic * @restrict E * @parent ionNavView * * @description * Use nav buttons to set the buttons on your {@link ionic.directive:ionNavBar} * from within an {@link ionic.directive:ionView}. This gives each * view template the ability to specify which buttons should show in the nav bar, * overriding any default buttons already placed in the nav bar. * * Any buttons you declare will be positioned on the navbar's corresponding side. Primary * buttons generally map to the left side of the header, and secondary buttons are * generally on the right side. However, their exact locations are platform-specific. * For example, in iOS, the primary buttons are on the far left of the header, and * secondary buttons are on the far right, with the header title centered between them. * For Android, however, both groups of buttons are on the far right of the header, * with the header title aligned left. * * We recommend always using `primary` and `secondary`, so the buttons correctly map * to the side familiar to users of each platform. However, in cases where buttons should * always be on an exact side, both `left` and `right` sides are still available. For * example, a toggle button for a left side menu should be on the left side; in this case, * we'd recommend using `side="left"`, so it's always on the left, no matter the platform. * * ***Note*** that `ion-nav-buttons` must be immediate descendants of the `ion-view` or * `ion-nav-bar` element (basically, don't wrap it in another div). * * @usage * ```html * <ion-nav-bar> * </ion-nav-bar> * <ion-nav-view> *   <ion-view> *     <ion-nav-buttons side="primary"> *       <button class="button" ng-click="doSomething()"> *         I'm a button on the primary of the navbar! *       </button> *     </ion-nav-buttons> *     <ion-content> *       Some super content here! *     </ion-content> *   </ion-view> * </ion-nav-view> * ``` * * @param {string} side The side to place the buttons in the * {@link ionic.directive:ionNavBar}. Available sides: `primary`, `secondary`, `left`, and `right`. */IonicModule.directive('ionNavButtons', ['$document', function($document) {  return {    require: '^ionNavBar',    restrict: 'E',    compile: function(tElement, tAttrs) {      var side = 'left';      if (/^primary|secondary|right$/i.test(tAttrs.side || '')) {        side = tAttrs.side.toLowerCase();      }      var spanEle = $document[0].createElement('span');      spanEle.className = side + '-buttons';      spanEle.innerHTML = tElement.html();      var navElementType = side + 'Buttons';      tElement.attr('class', 'hide');      tElement.empty();      return {        pre: function($scope, $element, $attrs, navBarCtrl) {          // only register the plain HTML, the navBarCtrl takes care of scope/compile/link          var parentViewCtrl = $element.parent().data('$ionViewController');          if (parentViewCtrl) {            // if the parent is an ion-view, then these are ion-nav-buttons for JUST this ion-view            parentViewCtrl.navElement(navElementType, spanEle.outerHTML);          } else {            // these are buttons for all views that do not have their own ion-nav-buttons            navBarCtrl.navElement(navElementType, spanEle.outerHTML);          }          spanEle = null;        }      };    }  };}]);/** * @ngdoc directive * @name navDirection * @module ionic * @restrict A * * @description * The direction which the nav view transition should animate. Available options * are: `forward`, `back`, `enter`, `exit`, `swap`. * * @usage * * ```html * <a nav-direction="forward" href="#/home">Home</a> * ``` */IonicModule.directive('navDirection', ['$ionicViewSwitcher', function($ionicViewSwitcher) {  return {    restrict: 'A',    priority: 1000,    link: function($scope, $element, $attr) {      $element.bind('click', function() {        $ionicViewSwitcher.nextDirection($attr.navDirection);      });    }  };}]);/** * @ngdoc directive * @name ionNavTitle * @module ionic * @restrict E * @parent ionNavView * * @description * * The nav title directive replaces an {@link ionic.directive:ionNavBar} title text with * custom HTML from within an {@link ionic.directive:ionView} template. This gives each * view the ability to specify its own custom title element, such as an image or any HTML, * rather than being text-only. Alternatively, text-only titles can be updated using the * `view-title` {@link ionic.directive:ionView} attribute. * * Note that `ion-nav-title` must be an immediate descendant of the `ion-view` or * `ion-nav-bar` element (basically don't wrap it in another div). * * @usage * ```html * <ion-nav-bar> * </ion-nav-bar> * <ion-nav-view> *   <ion-view> *     <ion-nav-title> *       <img src="logo.svg"> *     </ion-nav-title> *     <ion-content> *       Some super content here! *     </ion-content> *   </ion-view> * </ion-nav-view> * ``` * */IonicModule.directive('ionNavTitle', ['$document', function($document) {  return {    require: '^ionNavBar',    restrict: 'E',    compile: function(tElement, tAttrs) {      var navElementType = 'title';      var spanEle = $document[0].createElement('span');      for (var n in tAttrs.$attr) {        spanEle.setAttribute(tAttrs.$attr[n], tAttrs[n]);      }      spanEle.classList.add('nav-bar-title');      spanEle.innerHTML = tElement.html();      tElement.attr('class', 'hide');      tElement.empty();      return {        pre: function($scope, $element, $attrs, navBarCtrl) {          // only register the plain HTML, the navBarCtrl takes care of scope/compile/link          var parentViewCtrl = $element.parent().data('$ionViewController');          if (parentViewCtrl) {            // if the parent is an ion-view, then these are ion-nav-buttons for JUST this ion-view            parentViewCtrl.navElement(navElementType, spanEle.outerHTML);          } else {            // these are buttons for all views that do not have their own ion-nav-buttons            navBarCtrl.navElement(navElementType, spanEle.outerHTML);          }          spanEle = null;        }      };    }  };}]);/** * @ngdoc directive * @name navTransition * @module ionic * @restrict A * * @description * The transition type which the nav view transition should use when it animates. * Current, options are `ios`, `android`, and `none`. More options coming soon. * * @usage * * ```html * <a nav-transition="none" href="#/home">Home</a> * ``` */IonicModule.directive('navTransition', ['$ionicViewSwitcher', function($ionicViewSwitcher) {  return {    restrict: 'A',    priority: 1000,    link: function($scope, $element, $attr) {      $element.bind('click', function() {        $ionicViewSwitcher.nextTransition($attr.navTransition);      });    }  };}]);/** * @ngdoc directive * @name ionNavView * @module ionic * @restrict E * @codepen odqCz * * @description * As a user navigates throughout your app, Ionic is able to keep track of their * navigation history. By knowing their history, transitions between views * correctly enter and exit using the platform's transition style. An additional * benefit to Ionic's navigation system is its ability to manage multiple * histories. For example, each tab can have it's own navigation history stack. * * Ionic uses the AngularUI Router module so app interfaces can be organized * into various "states". Like Angular's core $route service, URLs can be used * to control the views. However, the AngularUI Router provides a more powerful * state manager in that states are bound to named, nested, and parallel views, * allowing more than one template to be rendered on the same page. * Additionally, each state is not required to be bound to a URL, and data can * be pushed to each state which allows much flexibility. * * The ionNavView directive is used to render templates in your application. Each template * is part of a state. States are usually mapped to a url, and are defined programatically * using angular-ui-router (see [their docs](https://github.com/angular-ui/ui-router/wiki), * and remember to replace ui-view with ion-nav-view in examples). * * @usage * In this example, we will create a navigation view that contains our different states for the app. * * To do this, in our markup we use ionNavView top level directive. To display a header bar we use * the {@link ionic.directive:ionNavBar} directive that updates as we navigate through the * navigation stack. * * Next, we need to setup our states that will be rendered. * * ```js * var app = angular.module('myApp', ['ionic']); * app.config(function($stateProvider) { *   $stateProvider *   .state('index', { *     url: '/', *     templateUrl: 'home.html' *   }) *   .state('music', { *     url: '/music', *     templateUrl: 'music.html' *   }); * }); * ``` * Then on app start, $stateProvider will look at the url, see if it matches the index state, * and then try to load home.html into the `<ion-nav-view>`. * * Pages are loaded by the URLs given. One simple way to create templates in Angular is to put * them directly into your HTML file and use the `<script type="text/ng-template">` syntax. * So here is one way to put home.html into our app: * * ```html * <script id="home" type="text/ng-template"> *   <!-- The title of the ion-view will be shown on the navbar --> *   <ion-view view-title="Home"> *     <ion-content ng-controller="HomeCtrl"> *       <!-- The content of the page --> *       <a href="#/music">Go to music page!</a> *     </ion-content> *   </ion-view> * </script> * ``` * * This is good to do because the template will be cached for very fast loading, instead of * having to fetch them from the network. * * ## Caching * * By default, views are cached to improve performance. When a view is navigated away from, its * element is left in the DOM, and its scope is disconnected from the `$watch` cycle. When * navigating to a view that is already cached, its scope is then reconnected, and the existing * element that was left in the DOM becomes the active view. This also allows for the scroll * position of previous views to be maintained. * * Caching can be disabled and enabled in multiple ways. By default, Ionic will cache a maximum of * 10 views, and not only can this be configured, but apps can also explicitly state which views * should and should not be cached. * * Note that because we are caching these views, *we aren’t destroying scopes*. Instead, scopes * are being disconnected from the watch cycle. Because scopes are not being destroyed and * recreated, controllers are not loading again on a subsequent viewing. If the app/controller * needs to know when a view has entered or has left, then view events emitted from the * {@link ionic.directive:ionView} scope, such as `$ionicView.enter`, may be useful. * * By default, when navigating back in the history, the "forward" views are removed from the cache. * If you navigate forward to the same view again, it'll create a new DOM element and controller * instance. Basically, any forward views are reset each time. This can be configured using the * {@link ionic.provider:$ionicConfigProvider}: * * ```js * $ionicConfigProvider.views.forwardCache(true); * ``` * * #### Disable cache globally * * The {@link ionic.provider:$ionicConfigProvider} can be used to set the maximum allowable views * which can be cached, but this can also be use to disable all caching by setting it to 0. * * ```js * $ionicConfigProvider.views.maxCache(0); * ``` * * #### Disable cache within state provider * * ```js * $stateProvider.state('myState', { *    cache: false, *    url : '/myUrl', *    templateUrl : 'my-template.html' * }) * ``` * * #### Disable cache with an attribute * * ```html * <ion-view cache-view="false" view-title="My Title!"> *   ... * </ion-view> * ``` * * * ## AngularUI Router * * Please visit [AngularUI Router's docs](https://github.com/angular-ui/ui-router/wiki) for * more info. Below is a great video by the AngularUI Router team that may help to explain * how it all works: * * <iframe width="560" height="315" src="//www.youtube.com/embed/dqJRoh8MnBo" * frameborder="0" allowfullscreen></iframe> * * Note: We do not recommend using [resolve](https://github.com/angular-ui/ui-router/wiki#resolve) * of AngularUI Router. The recommended approach is to execute any logic needed before beginning the state transition. * * @param {string=} name A view name. The name should be unique amongst the other views in the * same state. You can have views of the same name that live in different states. For more * information, see ui-router's * [ui-view documentation](http://angular-ui.github.io/ui-router/site/#/api/ui.router.state.directive:ui-view). */IonicModule.directive('ionNavView', [  '$state',  '$ionicConfig',function($state, $ionicConfig) {  // IONIC's fork of Angular UI Router, v0.2.10  // the navView handles registering views in the history and how to transition between them  return {    restrict: 'E',    terminal: true,    priority: 2000,    transclude: true,    controller: '$ionicNavView',    compile: function(tElement, tAttrs, transclude) {      // a nav view element is a container for numerous views      tElement.addClass('view-container');      ionic.DomUtil.cachedAttr(tElement, 'nav-view-transition', $ionicConfig.views.transition());      return function($scope, $element, $attr, navViewCtrl) {        var latestLocals;        // Put in the compiled initial view        transclude($scope, function(clone) {          $element.append(clone);        });        var viewData = navViewCtrl.init();        // listen for $stateChangeSuccess        $scope.$on('$stateChangeSuccess', function() {          updateView(false);        });        $scope.$on('$viewContentLoading', function() {          updateView(false);        });        // initial load, ready go        updateView(true);        function updateView(firstTime) {          // get the current local according to the $state          var viewLocals = $state.$current && $state.$current.locals[viewData.name];          // do not update THIS nav-view if its is not the container for the given state          // if the viewLocals are the same as THIS latestLocals, then nothing to do          if (!viewLocals || (!firstTime && viewLocals === latestLocals)) return;          // update the latestLocals          latestLocals = viewLocals;          viewData.state = viewLocals.$$state;          // register, update and transition to the new view          navViewCtrl.register(viewLocals);        }      };    }  };}]);IonicModule.config(['$provide', function($provide) {  $provide.decorator('ngClickDirective', ['$delegate', function($delegate) {    // drop the default ngClick directive    $delegate.shift();    return $delegate;  }]);}])/** * @private */.factory('$ionicNgClick', ['$parse', function($parse) {  return function(scope, element, clickExpr) {    var clickHandler = angular.isFunction(clickExpr) ?      clickExpr :      $parse(clickExpr);    element.on('click', function(event) {      scope.$apply(function() {        clickHandler(scope, {$event: (event)});      });    });    // Hack for iOS Safari's benefit. It goes searching for onclick handlers and is liable to click    // something else nearby.    element.onclick = noop;  };}]).directive('ngClick', ['$ionicNgClick', function($ionicNgClick) {  return function(scope, element, attr) {    $ionicNgClick(scope, element, attr.ngClick);  };}]).directive('ionStopEvent', function() {  return {    restrict: 'A',    link: function(scope, element, attr) {      element.bind(attr.ionStopEvent, eventStopPropagation);    }  };});function eventStopPropagation(e) {  e.stopPropagation();}/** * @ngdoc directive * @name ionPane * @module ionic * @restrict E * * @description A simple container that fits content, with no side effects.  Adds the 'pane' class to the element. */IonicModule.directive('ionPane', function() {  return {    restrict: 'E',    link: function(scope, element) {      element.addClass('pane');    }  };});/* * We don't document the ionPopover directive, we instead document * the $ionicPopover service */IonicModule.directive('ionPopover', [function() {  return {    restrict: 'E',    transclude: true,    replace: true,    controller: [function() {}],    template: '<div class="popover-backdrop">' +                '<div class="popover-wrapper" ng-transclude></div>' +              '</div>'  };}]);IonicModule.directive('ionPopoverView', function() {  return {    restrict: 'E',    compile: function(element) {      element.append(jqLite('<div class="popover-arrow">'));      element.addClass('popover');    }  };});/** * @ngdoc directive * @name ionRadio * @module ionic * @restrict E * @codepen saoBG * @description * The radio directive is no different than the HTML radio input, except it's styled differently. * * Radio behaves like [AngularJS radio](http://docs.angularjs.org/api/ng/input/input[radio]). * * @usage * ```html * <ion-radio ng-model="choice" ng-value="'A'">Choose A</ion-radio> * <ion-radio ng-model="choice" ng-value="'B'">Choose B</ion-radio> * <ion-radio ng-model="choice" ng-value="'C'">Choose C</ion-radio> * ``` * * @param {string=} name The name of the radio input. * @param {expression=} value The value of the radio input. * @param {boolean=} disabled The state of the radio input. * @param {string=} icon The icon to use when the radio input is selected. * @param {expression=} ng-value Angular equivalent of the value attribute. * @param {expression=} ng-model The angular model for the radio input. * @param {boolean=} ng-disabled Angular equivalent of the disabled attribute. * @param {expression=} ng-change Triggers given expression when radio input's model changes */IonicModule.directive('ionRadio', function() {  return {    restrict: 'E',    replace: true,    require: '?ngModel',    transclude: true,    template:      '<label class="item item-radio">' +        '<input type="radio" name="radio-group">' +        '<div class="radio-content">' +          '<div class="item-content disable-pointer-events" ng-transclude></div>' +          '<i class="radio-icon disable-pointer-events icon ion-checkmark"></i>' +        '</div>' +      '</label>',    compile: function(element, attr) {      if (attr.icon) {        var iconElm = element.find('i');        iconElm.removeClass('ion-checkmark').addClass(attr.icon);      }      var input = element.find('input');      forEach({          'name': attr.name,          'value': attr.value,          'disabled': attr.disabled,          'ng-value': attr.ngValue,          'ng-model': attr.ngModel,          'ng-disabled': attr.ngDisabled,          'ng-change': attr.ngChange,          'ng-required': attr.ngRequired,          'required': attr.required      }, function(value, name) {        if (isDefined(value)) {            input.attr(name, value);          }      });      return function(scope, element, attr) {        scope.getValue = function() {          return scope.ngValue || attr.value;        };      };    }  };});/** * @ngdoc directive * @name ionRefresher * @module ionic * @restrict E * @parent ionic.directive:ionContent, ionic.directive:ionScroll * @description * Allows you to add pull-to-refresh to a scrollView. * * Place it as the first child of your {@link ionic.directive:ionContent} or * {@link ionic.directive:ionScroll} element. * * When refreshing is complete, $broadcast the 'scroll.refreshComplete' event * from your controller. * * @usage * * ```html * <ion-content ng-controller="MyController"> *   <ion-refresher *     pulling-text="Pull to refresh..." *     on-refresh="doRefresh()"> *   </ion-refresher> *   <ion-list> *     <ion-item ng-repeat="item in items"></ion-item> *   </ion-list> * </ion-content> * ``` * ```js * angular.module('testApp', ['ionic']) * .controller('MyController', function($scope, $http) { *   $scope.items = [1,2,3]; *   $scope.doRefresh = function() { *     $http.get('/new-items') *      .success(function(newItems) { *        $scope.items = newItems; *      }) *      .finally(function() { *        // Stop the ion-refresher from spinning *        $scope.$broadcast('scroll.refreshComplete'); *      }); *   }; * }); * ``` * * @param {expression=} on-refresh Called when the user pulls down enough and lets go * of the refresher. * @param {expression=} on-pulling Called when the user starts to pull down * on the refresher. * @param {string=} pulling-text The text to display while the user is pulling down. * @param {string=} pulling-icon The icon to display while the user is pulling down. * Default: 'ion-android-arrow-down'. * @param {string=} spinner The {@link ionic.directive:ionSpinner} icon to display * after user lets go of the refresher. The SVG {@link ionic.directive:ionSpinner} * is now the default, replacing rotating font icons. Set to `none` to disable both the * spinner and the icon. * @param {string=} refreshing-icon The font icon to display after user lets go of the * refresher. This is deprecated in favor of the SVG {@link ionic.directive:ionSpinner}. * @param {boolean=} disable-pulling-rotation Disables the rotation animation of the pulling * icon when it reaches its activated threshold. To be used with a custom `pulling-icon`. * */IonicModule.directive('ionRefresher', [function() {  return {    restrict: 'E',    replace: true,    require: ['?^$ionicScroll', 'ionRefresher'],    controller: '$ionicRefresher',    template:    '<div class="scroll-refresher invisible" collection-repeat-ignore>' +      '<div class="ionic-refresher-content" ' +      'ng-class="{\'ionic-refresher-with-text\': pullingText || refreshingText}">' +        '<div class="icon-pulling" ng-class="{\'pulling-rotation-disabled\':disablePullingRotation}">' +          '<i class="icon {{pullingIcon}}"></i>' +        '</div>' +        '<div class="text-pulling" ng-bind-html="pullingText"></div>' +        '<div class="icon-refreshing">' +          '<ion-spinner ng-if="showSpinner" icon="{{spinner}}"></ion-spinner>' +          '<i ng-if="showIcon" class="icon {{refreshingIcon}}"></i>' +        '</div>' +        '<div class="text-refreshing" ng-bind-html="refreshingText"></div>' +      '</div>' +    '</div>',    link: function($scope, $element, $attrs, ctrls) {      // JS Scrolling uses the scroll controller      var scrollCtrl = ctrls[0],          refresherCtrl = ctrls[1];      if (!scrollCtrl || scrollCtrl.isNative()) {        // Kick off native scrolling        refresherCtrl.init();      } else {        $element[0].classList.add('js-scrolling');        scrollCtrl._setRefresher(          $scope,          $element[0],          refresherCtrl.getRefresherDomMethods()        );        $scope.$on('scroll.refreshComplete', function() {          $scope.$evalAsync(function() {            scrollCtrl.scrollView.finishPullToRefresh();          });        });      }    }  };}]);/** * @ngdoc directive * @name ionScroll * @module ionic * @delegate ionic.service:$ionicScrollDelegate * @codepen mwFuh * @restrict E * * @description * Creates a scrollable container for all content inside. * * @usage * * Basic usage: * * ```html * <ion-scroll zooming="true" direction="xy" style="width: 500px; height: 500px"> *   <div style="width: 5000px; height: 5000px; background: url('https://upload.wikimedia.org/wikipedia/commons/a/ad/Europe_geological_map-en.jpg') repeat"></div> *  </ion-scroll> * ``` * * Note that it's important to set the height of the scroll box as well as the height of the inner * content to enable scrolling. This makes it possible to have full control over scrollable areas. * * If you'd just like to have a center content scrolling area, use {@link ionic.directive:ionContent} instead. * * @param {string=} delegate-handle The handle used to identify this scrollView * with {@link ionic.service:$ionicScrollDelegate}. * @param {string=} direction Which way to scroll. 'x' or 'y' or 'xy'. Default 'y'. * @param {boolean=} locking Whether to lock scrolling in one direction at a time. Useful to set to false when zoomed in or scrolling in two directions. Default true. * @param {boolean=} paging Whether to scroll with paging. * @param {expression=} on-refresh Called on pull-to-refresh, triggered by an {@link ionic.directive:ionRefresher}. * @param {expression=} on-scroll Called whenever the user scrolls. * @param {boolean=} scrollbar-x Whether to show the horizontal scrollbar. Default true. * @param {boolean=} scrollbar-y Whether to show the vertical scrollbar. Default true. * @param {boolean=} zooming Whether to support pinch-to-zoom * @param {integer=} min-zoom The smallest zoom amount allowed (default is 0.5) * @param {integer=} max-zoom The largest zoom amount allowed (default is 3) * @param {boolean=} has-bouncing Whether to allow scrolling to bounce past the edges * of the content.  Defaults to true on iOS, false on Android. */IonicModule.directive('ionScroll', [  '$timeout',  '$controller',  '$ionicBind',  '$ionicConfig',function($timeout, $controller, $ionicBind, $ionicConfig) {  return {    restrict: 'E',    scope: true,    controller: function() {},    compile: function(element, attr) {      element.addClass('scroll-view ionic-scroll');      //We cannot transclude here because it breaks element.data() inheritance on compile      var innerElement = jqLite('<div class="scroll"></div>');      innerElement.append(element.contents());      element.append(innerElement);      var nativeScrolling = attr.overflowScroll !== "false" && (attr.overflowScroll === "true" || !$ionicConfig.scrolling.jsScrolling());      return { pre: prelink };      function prelink($scope, $element, $attr) {        $ionicBind($scope, $attr, {          direction: '@',          paging: '@',          $onScroll: '&onScroll',          scroll: '@',          scrollbarX: '@',          scrollbarY: '@',          zooming: '@',          minZoom: '@',          maxZoom: '@'        });        $scope.direction = $scope.direction || 'y';        if (isDefined($attr.padding)) {          $scope.$watch($attr.padding, function(newVal) {            innerElement.toggleClass('padding', !!newVal);          });        }        if ($scope.$eval($scope.paging) === true) {          innerElement.addClass('scroll-paging');        }        if (!$scope.direction) { $scope.direction = 'y'; }        var isPaging = $scope.$eval($scope.paging) === true;        if (nativeScrolling) {          $element.addClass('overflow-scroll');        }        $element.addClass('scroll-' + $scope.direction);        var scrollViewOptions = {          el: $element[0],          delegateHandle: $attr.delegateHandle,          locking: ($attr.locking || 'true') === 'true',          bouncing: $scope.$eval($attr.hasBouncing),          paging: isPaging,          scrollbarX: $scope.$eval($scope.scrollbarX) !== false,          scrollbarY: $scope.$eval($scope.scrollbarY) !== false,          scrollingX: $scope.direction.indexOf('x') >= 0,          scrollingY: $scope.direction.indexOf('y') >= 0,          zooming: $scope.$eval($scope.zooming) === true,          maxZoom: $scope.$eval($scope.maxZoom) || 3,          minZoom: $scope.$eval($scope.minZoom) || 0.5,          preventDefault: true,          nativeScrolling: nativeScrolling        };        if (isPaging) {          scrollViewOptions.speedMultiplier = 0.8;          scrollViewOptions.bouncing = false;        }        $controller('$ionicScroll', {          $scope: $scope,          scrollViewOptions: scrollViewOptions        });      }    }  };}]);/** * @ngdoc directive * @name ionSideMenu * @module ionic * @restrict E * @parent ionic.directive:ionSideMenus * * @description * A container for a side menu, sibling to an {@link ionic.directive:ionSideMenuContent} directive. * * @usage * ```html * <ion-side-menu *   side="left" *   width="myWidthValue + 20" *   is-enabled="shouldLeftSideMenuBeEnabled()"> * </ion-side-menu> * ``` * For a complete side menu example, see the * {@link ionic.directive:ionSideMenus} documentation. * * @param {string} side Which side the side menu is currently on.  Allowed values: 'left' or 'right'. * @param {boolean=} is-enabled Whether this side menu is enabled. * @param {number=} width How many pixels wide the side menu should be.  Defaults to 275. */IonicModule.directive('ionSideMenu', function() {  return {    restrict: 'E',    require: '^ionSideMenus',    scope: true,    compile: function(element, attr) {      angular.isUndefined(attr.isEnabled) && attr.$set('isEnabled', 'true');      angular.isUndefined(attr.width) && attr.$set('width', '275');      element.addClass('menu menu-' + attr.side);      return function($scope, $element, $attr, sideMenuCtrl) {        $scope.side = $attr.side || 'left';        var sideMenu = sideMenuCtrl[$scope.side] = new ionic.views.SideMenu({          width: attr.width,          el: $element[0],          isEnabled: true        });        $scope.$watch($attr.width, function(val) {          var numberVal = +val;          if (numberVal && numberVal == val) {            sideMenu.setWidth(+val);          }        });        $scope.$watch($attr.isEnabled, function(val) {          sideMenu.setIsEnabled(!!val);        });      };    }  };});/** * @ngdoc directive * @name ionSideMenuContent * @module ionic * @restrict E * @parent ionic.directive:ionSideMenus * * @description * A container for the main visible content, sibling to one or more * {@link ionic.directive:ionSideMenu} directives. * * @usage * ```html * <ion-side-menu-content *   edge-drag-threshold="true" *   drag-content="true"> * </ion-side-menu-content> * ``` * For a complete side menu example, see the * {@link ionic.directive:ionSideMenus} documentation. * * @param {boolean=} drag-content Whether the content can be dragged. Default true. * @param {boolean|number=} edge-drag-threshold Whether the content drag can only start if it is below a certain threshold distance from the edge of the screen.  Default false. Accepts three types of values:   *  - If a non-zero number is given, that many pixels is used as the maximum allowed distance from the edge that starts dragging the side menu.   *  - If true is given, the default number of pixels (25) is used as the maximum allowed distance.   *  - If false or 0 is given, the edge drag threshold is disabled, and dragging from anywhere on the content is allowed. * */IonicModule.directive('ionSideMenuContent', [  '$timeout',  '$ionicGesture',  '$window',function($timeout, $ionicGesture, $window) {  return {    restrict: 'EA', //DEPRECATED 'A'    require: '^ionSideMenus',    scope: true,    compile: function(element, attr) {      element.addClass('menu-content pane');      return { pre: prelink };      function prelink($scope, $element, $attr, sideMenuCtrl) {        var startCoord = null;        var primaryScrollAxis = null;        if (isDefined(attr.dragContent)) {          $scope.$watch(attr.dragContent, function(value) {            sideMenuCtrl.canDragContent(value);          });        } else {          sideMenuCtrl.canDragContent(true);        }        if (isDefined(attr.edgeDragThreshold)) {          $scope.$watch(attr.edgeDragThreshold, function(value) {            sideMenuCtrl.edgeDragThreshold(value);          });        }        // Listen for taps on the content to close the menu        function onContentTap(gestureEvt) {          if (sideMenuCtrl.getOpenAmount() !== 0) {            sideMenuCtrl.close();            gestureEvt.gesture.srcEvent.preventDefault();            startCoord = null;            primaryScrollAxis = null;          } else if (!startCoord) {            startCoord = ionic.tap.pointerCoord(gestureEvt.gesture.srcEvent);          }        }        function onDragX(e) {          if (!sideMenuCtrl.isDraggableTarget(e)) return;          if (getPrimaryScrollAxis(e) == 'x') {            sideMenuCtrl._handleDrag(e);            e.gesture.srcEvent.preventDefault();          }        }        function onDragY(e) {          if (getPrimaryScrollAxis(e) == 'x') {            e.gesture.srcEvent.preventDefault();          }        }        function onDragRelease(e) {          sideMenuCtrl._endDrag(e);          startCoord = null;          primaryScrollAxis = null;        }        function getPrimaryScrollAxis(gestureEvt) {          // gets whether the user is primarily scrolling on the X or Y          // If a majority of the drag has been on the Y since the start of          // the drag, but the X has moved a little bit, it's still a Y drag          if (primaryScrollAxis) {            // we already figured out which way they're scrolling            return primaryScrollAxis;          }          if (gestureEvt && gestureEvt.gesture) {            if (!startCoord) {              // get the starting point              startCoord = ionic.tap.pointerCoord(gestureEvt.gesture.srcEvent);            } else {              // we already have a starting point, figure out which direction they're going              var endCoord = ionic.tap.pointerCoord(gestureEvt.gesture.srcEvent);              var xDistance = Math.abs(endCoord.x - startCoord.x);              var yDistance = Math.abs(endCoord.y - startCoord.y);              var scrollAxis = (xDistance < yDistance ? 'y' : 'x');              if (Math.max(xDistance, yDistance) > 30) {                // ok, we pretty much know which way they're going                // let's lock it in                primaryScrollAxis = scrollAxis;              }              return scrollAxis;            }          }          return 'y';        }        var content = {          element: element[0],          onDrag: function() {},          endDrag: function() {},          setCanScroll: function(canScroll) {            var c = $element[0].querySelector('.scroll');            if (!c) {              return;            }            var content = angular.element(c.parentElement);            if (!content) {              return;            }            // freeze our scroll container if we have one            var scrollScope = content.scope();            scrollScope.scrollCtrl && scrollScope.scrollCtrl.freezeScrollShut(!canScroll);          },          getTranslateX: function() {            return $scope.sideMenuContentTranslateX || 0;          },          setTranslateX: ionic.animationFrameThrottle(function(amount) {            var xTransform = content.offsetX + amount;            $element[0].style[ionic.CSS.TRANSFORM] = 'translate3d(' + xTransform + 'px,0,0)';            $timeout(function() {              $scope.sideMenuContentTranslateX = amount;            });          }),          setMarginLeft: ionic.animationFrameThrottle(function(amount) {            if (amount) {              amount = parseInt(amount, 10);              $element[0].style[ionic.CSS.TRANSFORM] = 'translate3d(' + amount + 'px,0,0)';              $element[0].style.width = ($window.innerWidth - amount) + 'px';              content.offsetX = amount;            } else {              $element[0].style[ionic.CSS.TRANSFORM] = 'translate3d(0,0,0)';              $element[0].style.width = '';              content.offsetX = 0;            }          }),          setMarginRight: ionic.animationFrameThrottle(function(amount) {            if (amount) {              amount = parseInt(amount, 10);              $element[0].style.width = ($window.innerWidth - amount) + 'px';              content.offsetX = amount;            } else {              $element[0].style.width = '';              content.offsetX = 0;            }            // reset incase left gets grabby            $element[0].style[ionic.CSS.TRANSFORM] = 'translate3d(0,0,0)';          }),          setMarginLeftAndRight: ionic.animationFrameThrottle(function(amountLeft, amountRight) {            amountLeft = amountLeft && parseInt(amountLeft, 10) || 0;            amountRight = amountRight && parseInt(amountRight, 10) || 0;            var amount = amountLeft + amountRight;            if (amount > 0) {              $element[0].style[ionic.CSS.TRANSFORM] = 'translate3d(' + amountLeft + 'px,0,0)';              $element[0].style.width = ($window.innerWidth - amount) + 'px';              content.offsetX = amountLeft;            } else {              $element[0].style[ionic.CSS.TRANSFORM] = 'translate3d(0,0,0)';              $element[0].style.width = '';              content.offsetX = 0;            }            // reset incase left gets grabby            //$element[0].style[ionic.CSS.TRANSFORM] = 'translate3d(0,0,0)';          }),          enableAnimation: function() {            $scope.animationEnabled = true;            $element[0].classList.add('menu-animated');          },          disableAnimation: function() {            $scope.animationEnabled = false;            $element[0].classList.remove('menu-animated');          },          offsetX: 0        };        sideMenuCtrl.setContent(content);        // add gesture handlers        var gestureOpts = { stop_browser_behavior: false };        gestureOpts.prevent_default_directions = ['left', 'right'];        var contentTapGesture = $ionicGesture.on('tap', onContentTap, $element, gestureOpts);        var dragRightGesture = $ionicGesture.on('dragright', onDragX, $element, gestureOpts);        var dragLeftGesture = $ionicGesture.on('dragleft', onDragX, $element, gestureOpts);        var dragUpGesture = $ionicGesture.on('dragup', onDragY, $element, gestureOpts);        var dragDownGesture = $ionicGesture.on('dragdown', onDragY, $element, gestureOpts);        var releaseGesture = $ionicGesture.on('release', onDragRelease, $element, gestureOpts);        // Cleanup        $scope.$on('$destroy', function() {          if (content) {            content.element = null;            content = null;          }          $ionicGesture.off(dragLeftGesture, 'dragleft', onDragX);          $ionicGesture.off(dragRightGesture, 'dragright', onDragX);          $ionicGesture.off(dragUpGesture, 'dragup', onDragY);          $ionicGesture.off(dragDownGesture, 'dragdown', onDragY);          $ionicGesture.off(releaseGesture, 'release', onDragRelease);          $ionicGesture.off(contentTapGesture, 'tap', onContentTap);        });      }    }  };}]);IonicModule/** * @ngdoc directive * @name ionSideMenus * @module ionic * @delegate ionic.service:$ionicSideMenuDelegate * @restrict E * * @description * A container element for side menu(s) and the main content. Allows the left and/or right side menu * to be toggled by dragging the main content area side to side. * * To automatically close an opened menu, you can add the {@link ionic.directive:menuClose} attribute * directive. The `menu-close` attribute is usually added to links and buttons within * `ion-side-menu-content`, so that when the element is clicked, the opened side menu will * automatically close. * * "Burger Icon" toggles can be added to the header with the {@link ionic.directive:menuToggle} * attribute directive. Clicking the toggle will open and close the side menu like the `menu-close` * directive. The side menu will automatically hide on child pages, but can be overridden with the * enable-menu-with-back-views attribute mentioned below. * * By default, side menus are hidden underneath their side menu content and can be opened by swiping * the content left or right or by toggling a button to show the side menu. Additionally, by adding the * {@link ionic.directive:exposeAsideWhen} attribute directive to an * {@link ionic.directive:ionSideMenu} element directive, a side menu can be given instructions about * "when" the menu should be exposed (always viewable). * *  * * For more information on side menus, check out: * * - {@link ionic.directive:ionSideMenuContent} * - {@link ionic.directive:ionSideMenu} * - {@link ionic.directive:menuToggle} * - {@link ionic.directive:menuClose} * - {@link ionic.directive:exposeAsideWhen} * * @usage * To use side menus, add an `<ion-side-menus>` parent element. This will encompass all pages that have a * side menu, and have at least 2 child elements: 1 `<ion-side-menu-content>` for the center content, * and one or more `<ion-side-menu>` directives for each side menu(left/right) that you wish to place. * * ```html * <ion-side-menus> *   <!-- Left menu --> *   <ion-side-menu side="left"> *   </ion-side-menu> * *   <ion-side-menu-content> *   <!-- Main content, usually <ion-nav-view> --> *   </ion-side-menu-content> * *   <!-- Right menu --> *   <ion-side-menu side="right"> *   </ion-side-menu> * * </ion-side-menus> * ``` * ```js * function ContentController($scope, $ionicSideMenuDelegate) { *   $scope.toggleLeft = function() { *     $ionicSideMenuDelegate.toggleLeft(); *   }; * } * ``` * * @param {bool=} enable-menu-with-back-views Determines whether the side menu is enabled when the * back button is showing. When set to `false`, any {@link ionic.directive:menuToggle} will be hidden, * and the user cannot swipe to open the menu. When going back to the root page of the side menu (the * page without a back button visible), then any menuToggle buttons will show again, and menus will be * enabled again. * @param {string=} delegate-handle The handle used to identify this side menu * with {@link ionic.service:$ionicSideMenuDelegate}. * */.directive('ionSideMenus', ['$ionicBody', function($ionicBody) {  return {    restrict: 'ECA',    controller: '$ionicSideMenus',    compile: function(element, attr) {      attr.$set('class', (attr['class'] || '') + ' view');      return { pre: prelink };      function prelink($scope, $element, $attrs, ctrl) {        ctrl.enableMenuWithBackViews($scope.$eval($attrs.enableMenuWithBackViews));        $scope.$on('$ionicExposeAside', function(evt, isAsideExposed) {          if (!$scope.$exposeAside) $scope.$exposeAside = {};          $scope.$exposeAside.active = isAsideExposed;          $ionicBody.enableClass(isAsideExposed, 'aside-open');        });        $scope.$on('$ionicView.beforeEnter', function(ev, d) {          if (d.historyId) {            $scope.$activeHistoryId = d.historyId;          }        });        $scope.$on('$destroy', function() {          $ionicBody.removeClass('menu-open', 'aside-open');        });      }    }  };}]);/** * @ngdoc directive * @name ionSlideBox * @module ionic * @codepen AjgEB * @deprecated will be removed in the next Ionic release in favor of the new ion-slides component. * Don't depend on the internal behavior of this widget. * @delegate ionic.service:$ionicSlideBoxDelegate * @restrict E * @description * The Slide Box is a multi-page container where each page can be swiped or dragged between: * * * @usage * ```html * <ion-slide-box on-slide-changed="slideHasChanged($index)"> *   <ion-slide> *     <div class="box blue"><h1>BLUE</h1></div> *   </ion-slide> *   <ion-slide> *     <div class="box yellow"><h1>YELLOW</h1></div> *   </ion-slide> *   <ion-slide> *     <div class="box pink"><h1>PINK</h1></div> *   </ion-slide> * </ion-slide-box> * ``` * * @param {string=} delegate-handle The handle used to identify this slideBox * with {@link ionic.service:$ionicSlideBoxDelegate}. * @param {boolean=} does-continue Whether the slide box should loop. * @param {boolean=} auto-play Whether the slide box should automatically slide. Default true if does-continue is true. * @param {number=} slide-interval How many milliseconds to wait to change slides (if does-continue is true). Defaults to 4000. * @param {boolean=} show-pager Whether a pager should be shown for this slide box. Accepts expressions via `show-pager="{{shouldShow()}}"`. Defaults to true. * @param {expression=} pager-click Expression to call when a pager is clicked (if show-pager is true). Is passed the 'index' variable. * @param {expression=} on-slide-changed Expression called whenever the slide is changed.  Is passed an '$index' variable. * @param {expression=} active-slide Model to bind the current slide index to. */IonicModule.directive('ionSlideBox', [  '$animate',  '$timeout',  '$compile',  '$ionicSlideBoxDelegate',  '$ionicHistory',  '$ionicScrollDelegate',function($animate, $timeout, $compile, $ionicSlideBoxDelegate, $ionicHistory, $ionicScrollDelegate) {  return {    restrict: 'E',    replace: true,    transclude: true,    scope: {      autoPlay: '=',      doesContinue: '@',      slideInterval: '@',      showPager: '@',      pagerClick: '&',      disableScroll: '@',      onSlideChanged: '&',      activeSlide: '=?',      bounce: '@'    },    controller: ['$scope', '$element', '$attrs', function($scope, $element, $attrs) {      var _this = this;      var continuous = $scope.$eval($scope.doesContinue) === true;      var bouncing = ($scope.$eval($scope.bounce) !== false); //Default to true      var shouldAutoPlay = isDefined($attrs.autoPlay) ? !!$scope.autoPlay : false;      var slideInterval = shouldAutoPlay ? $scope.$eval($scope.slideInterval) || 4000 : 0;      var slider = new ionic.views.Slider({        el: $element[0],        auto: slideInterval,        continuous: continuous,        startSlide: $scope.activeSlide,        bouncing: bouncing,        slidesChanged: function() {          $scope.currentSlide = slider.currentIndex();          // Try to trigger a digest          $timeout(function() {});        },        callback: function(slideIndex) {          $scope.currentSlide = slideIndex;          $scope.onSlideChanged({ index: $scope.currentSlide, $index: $scope.currentSlide});          $scope.$parent.$broadcast('slideBox.slideChanged', slideIndex);          $scope.activeSlide = slideIndex;          // Try to trigger a digest          $timeout(function() {});        },        onDrag: function() {          freezeAllScrolls(true);        },        onDragEnd: function() {          freezeAllScrolls(false);        }      });      function freezeAllScrolls(shouldFreeze) {        if (shouldFreeze && !_this.isScrollFreeze) {          $ionicScrollDelegate.freezeAllScrolls(shouldFreeze);        } else if (!shouldFreeze && _this.isScrollFreeze) {          $ionicScrollDelegate.freezeAllScrolls(false);        }        _this.isScrollFreeze = shouldFreeze;      }      slider.enableSlide($scope.$eval($attrs.disableScroll) !== true);      $scope.$watch('activeSlide', function(nv) {        if (isDefined(nv)) {          slider.slide(nv);        }      });      $scope.$on('slideBox.nextSlide', function() {        slider.next();      });      $scope.$on('slideBox.prevSlide', function() {        slider.prev();      });      $scope.$on('slideBox.setSlide', function(e, index) {        slider.slide(index);      });      //Exposed for testing      this.__slider = slider;      var deregisterInstance = $ionicSlideBoxDelegate._registerInstance(        slider, $attrs.delegateHandle, function() {          return $ionicHistory.isActiveScope($scope);        }      );      $scope.$on('$destroy', function() {        deregisterInstance();        slider.kill();      });      this.slidesCount = function() {        return slider.slidesCount();      };      this.onPagerClick = function(index) {        $scope.pagerClick({index: index});      };      $timeout(function() {        slider.load();      });    }],    template: '<div class="slider">' +      '<div class="slider-slides" ng-transclude>' +      '</div>' +    '</div>',    link: function($scope, $element, $attr) {      // Disable ngAnimate for slidebox and its children      $animate.enabled($element, false);      // if showPager is undefined, show the pager      if (!isDefined($attr.showPager)) {        $scope.showPager = true;        getPager().toggleClass('hide', !true);      }      $attr.$observe('showPager', function(show) {        if (show === undefined) return;        show = $scope.$eval(show);        getPager().toggleClass('hide', !show);      });      var pager;      function getPager() {        if (!pager) {          var childScope = $scope.$new();          pager = jqLite('<ion-pager></ion-pager>');          $element.append(pager);          pager = $compile(pager)(childScope);        }        return pager;      }    }  };}]).directive('ionSlide', function() {  return {    restrict: 'E',    require: '?^ionSlideBox',    compile: function(element) {      element.addClass('slider-slide');    }  };}).directive('ionPager', function() {  return {    restrict: 'E',    replace: true,    require: '^ionSlideBox',    template: '<div class="slider-pager"><span class="slider-pager-page" ng-repeat="slide in numSlides() track by $index" ng-class="{active: $index == currentSlide}" ng-click="pagerClick($index)"><i class="icon ion-record"></i></span></div>',    link: function($scope, $element, $attr, slideBox) {      var selectPage = function(index) {        var children = $element[0].children;        var length = children.length;        for (var i = 0; i < length; i++) {          if (i == index) {            children[i].classList.add('active');          } else {            children[i].classList.remove('active');          }        }      };      $scope.pagerClick = function(index) {        slideBox.onPagerClick(index);      };      $scope.numSlides = function() {        return new Array(slideBox.slidesCount());      };      $scope.$watch('currentSlide', function(v) {        selectPage(v);      });    }  };});/** * @ngdoc directive * @name ionSlides * @module ionic * @delegate ionic.service:$ionicSlideBoxDelegate * @restrict E * @description * The Slides component is a powerful multi-page container where each page can be swiped or dragged between. * * Note: this is a new version of the Ionic Slide Box based on the [Swiper](http://www.idangero.us/swiper/#.Vmc1J-ODFBc) widget from * [idangerous](http://www.idangero.us/). * *  * * @usage * ```html * <ion-content scroll="false"> *   <ion-slides  options="options" slider="data.slider"> *     <ion-slide-page> *       <div class="box blue"><h1>BLUE</h1></div> *     </ion-slide-page> *     <ion-slide-page> *       <div class="box yellow"><h1>YELLOW</h1></div> *     </ion-slide-page> *     <ion-slide-page> *       <div class="box pink"><h1>PINK</h1></div> *     </ion-slide-page> *   </ion-slides> * </ion-content> * ``` * * ```js * $scope.options = { *   loop: false, *   effect: 'fade', *   speed: 500, * } * * $scope.$on("$ionicSlides.sliderInitialized", function(event, data){ *   // data.slider is the instance of Swiper *   $scope.slider = data.slider; * }); * * $scope.$on("$ionicSlides.slideChangeStart", function(event, data){ *   console.log('Slide change is beginning'); * }); * * $scope.$on("$ionicSlides.slideChangeEnd", function(event, data){ *   // note: the indexes are 0-based *   $scope.activeIndex = data.activeIndex; *   $scope.previousIndex = data.previousIndex; * }); * * ``` * * ## Slide Events * * The slides component dispatches events when the active slide changes * * <table class="table"> *   <tr> *     <td><code>$ionicSlides.slideChangeStart</code></td> *     <td>This event is emitted when a slide change begins</td> *   </tr> *   <tr> *     <td><code>$ionicSlides.slideChangeEnd</code></td> *     <td>This event is emitted when a slide change completes</td> *   </tr> *   <tr> *     <td><code>$ionicSlides.sliderInitialized</code></td> *     <td>This event is emitted when the slider is initialized. It provides access to an instance of the slider.</td> *   </tr> * </table> * * * ## Updating Slides Dynamically * When applying data to the slider at runtime, typically everything will work as expected. * * In the event that the slides are looped, use the `updateLoop` method on the slider to ensure the slides update correctly. * * ``` * $scope.$on("$ionicSlides.sliderInitialized", function(event, data){ *   // grab an instance of the slider *   $scope.slider = data.slider; * }); * * function dataChangeHandler(){ *   // call this function when data changes, such as an HTTP request, etc *   if ( $scope.slider ){ *     $scope.slider.updateLoop(); *   } * } * ``` * */IonicModule.directive('ionSlides', [  '$animate',  '$timeout',  '$compile',function($animate, $timeout, $compile) {  return {    restrict: 'E',    transclude: true,    scope: {      options: '=',      slider: '='    },    template: '<div class="swiper-container">' +      '<div class="swiper-wrapper" ng-transclude>' +      '</div>' +        '<div ng-hide="!showPager" class="swiper-pagination"></div>' +      '</div>',    controller: ['$scope', '$element', function($scope, $element) {      var _this = this;      this.update = function() {        $timeout(function() {          if (!_this.__slider) {            return;          }          _this.__slider.update();          if (_this._options.loop) {            _this.__slider.createLoop();          }          var slidesLength = _this.__slider.slides.length;          // Don't allow pager to show with > 10 slides          if (slidesLength > 10) {            $scope.showPager = false;          }          // When slide index is greater than total then slide to last index          if (_this.__slider.activeIndex > slidesLength - 1) {            _this.__slider.slideTo(slidesLength - 1);          }        });      };      this.rapidUpdate = ionic.debounce(function() {        _this.update();      }, 50);      this.getSlider = function() {        return _this.__slider;      };      var options = $scope.options || {};      var newOptions = angular.extend({        pagination: $element.children().children()[1],        paginationClickable: true,        lazyLoading: true,        preloadImages: false      }, options);      this._options = newOptions;      $timeout(function() {        var slider = new ionic.views.Swiper($element.children()[0], newOptions, $scope, $compile);        $scope.$emit("$ionicSlides.sliderInitialized", { slider: slider });        _this.__slider = slider;        $scope.slider = _this.__slider;        $scope.$on('$destroy', function() {          slider.destroy();          _this.__slider = null;        });      });      $timeout(function() {        // if it's a loop, render the slides again just incase        _this.rapidUpdate();      }, 200);    }],    link: function($scope) {      $scope.showPager = true;      // Disable ngAnimate for slidebox and its children      //$animate.enabled(false, $element);    }  };}]).directive('ionSlidePage', [function() {  return {    restrict: 'E',    require: '?^ionSlides',    transclude: true,    replace: true,    template: '<div class="swiper-slide" ng-transclude></div>',    link: function($scope, $element, $attr, ionSlidesCtrl) {      ionSlidesCtrl.rapidUpdate();      $scope.$on('$destroy', function() {        ionSlidesCtrl.rapidUpdate();      });    }  };}]);/*** @ngdoc directive* @name ionSpinner* @module ionic* @restrict E * * @description * The `ionSpinner` directive provides a variety of animated spinners. * Spinners enables you to give your users feedback that the app is * processing/thinking/waiting/chillin' out, or whatever you'd like it to indicate. * By default, the {@link ionic.directive:ionRefresher} feature uses this spinner, rather * than rotating font icons (previously included in [ionicons](http://ionicons.com/)). * While font icons are great for simple or stationary graphics, they're not suited to * provide great animations, which is why Ionic uses SVG instead. * * Ionic offers ten spinners out of the box, and by default, it will use the appropriate spinner * for the platform on which it's running. Under the hood, the `ionSpinner` directive dynamically * builds the required SVG element, which allows Ionic to provide all ten of the animated SVGs * within 3KB. * * <style> * .spinner-table { *   max-width: 280px; * } * .spinner-table tbody > tr > th, .spinner-table tbody > tr > td { *   vertical-align: middle; *   width: 42px; *   height: 42px; * } * .spinner { *   stroke: #444; *   fill: #444; } *   .spinner svg { *     width: 28px; *     height: 28px; } *   .spinner.spinner-inverse { *     stroke: #fff; *     fill: #fff; } * * .spinner-android { *   stroke: #4b8bf4; } * * .spinner-ios, .spinner-ios-small { *   stroke: #69717d; } * * .spinner-spiral .stop1 { *   stop-color: #fff; *   stop-opacity: 0; } * .spinner-spiral.spinner-inverse .stop1 { *   stop-color: #000; } * .spinner-spiral.spinner-inverse .stop2 { *   stop-color: #fff; } * </style> * * <script src="http://code.ionicframework.com/nightly/js/ionic.bundle.min.js"></script> * <table class="table spinner-table" ng-app="ionic"> *  <tr> *    <th> *      <code>android</code> *    </th> *    <td> *      <ion-spinner icon="android"></ion-spinner> *    </td> *  </tr> *  <tr> *    <th> *      <code>ios</code> *    </th> *    <td> *      <ion-spinner icon="ios"></ion-spinner> *    </td> *  </tr> *  <tr> *    <th> *      <code>ios-small</code> *    </th> *    <td> *      <ion-spinner icon="ios-small"></ion-spinner> *    </td> *  </tr> *  <tr> *    <th> *      <code>bubbles</code> *    </th> *    <td> *      <ion-spinner icon="bubbles"></ion-spinner> *    </td> *  </tr> *  <tr> *    <th> *      <code>circles</code> *    </th> *    <td> *      <ion-spinner icon="circles"></ion-spinner> *    </td> *  </tr> *  <tr> *    <th> *      <code>crescent</code> *    </th> *    <td> *      <ion-spinner icon="crescent"></ion-spinner> *    </td> *  </tr> *  <tr> *    <th> *      <code>dots</code> *    </th> *    <td> *      <ion-spinner icon="dots"></ion-spinner> *    </td> *  </tr> *  <tr> *    <th> *      <code>lines</code> *    </th> *    <td> *      <ion-spinner icon="lines"></ion-spinner> *    </td> *  </tr> *  <tr> *    <th> *      <code>ripple</code> *    </th> *    <td> *      <ion-spinner icon="ripple"></ion-spinner> *    </td> *  </tr> *  <tr> *    <th> *      <code>spiral</code> *    </th> *    <td> *      <ion-spinner icon="spiral"></ion-spinner> *    </td> *  </tr> * </table> * * Each spinner uses SVG with SMIL animations, however, the Android spinner also uses JavaScript * so it also works on Android 4.0-4.3. Additionally, each spinner can be styled with CSS, * and scaled to any size. * * * @usage * The following code would use the default spinner for the platform it's running from. If it's neither * iOS or Android, it'll default to use `ios`. * * ```html * <ion-spinner></ion-spinner> * ``` * * By setting the `icon` attribute, you can specify which spinner to use, no matter what * the platform is. * * ```html * <ion-spinner icon="spiral"></ion-spinner> * ``` * * ## Spinner Colors * Like with most of Ionic's other components, spinners can also be styled using * Ionic's standard color naming convention. For example: * * ```html * <ion-spinner class="spinner-energized"></ion-spinner> * ``` * * * ## Styling SVG with CSS * One cool thing about SVG is its ability to be styled with CSS! Some of the properties * have different names, for example, SVG uses the term `stroke` instead of `border`, and * `fill` instead of `background-color`. * * ```css * .spinner svg { *   width: 28px; *   height: 28px; *   stroke: #444; *   fill: #444; * } * ``` **/IonicModule.directive('ionSpinner', function() {  return {    restrict: 'E',    controller: '$ionicSpinner',    link: function($scope, $element, $attrs, ctrl) {      var spinnerName = ctrl.init();      $element.addClass('spinner spinner-' + spinnerName);      $element.on('$destroy', function onDestroy() {        ctrl.stop();      });    }  };});/** * @ngdoc directive * @name ionTab * @module ionic * @restrict E * @parent ionic.directive:ionTabs * * @description * Contains a tab's content.  The content only exists while the given tab is selected. * * Each ionTab has its own view history. * * @usage * ```html * <ion-tab *   title="Tab!" *   icon="my-icon" *   href="#/tab/tab-link" *   on-select="onTabSelected()" *   on-deselect="onTabDeselected()"> * </ion-tab> * ``` * For a complete, working tab bar example, see the {@link ionic.directive:ionTabs} documentation. * * @param {string} title The title of the tab. * @param {string=} href The link that this tab will navigate to when tapped. * @param {string=} icon The icon of the tab. If given, this will become the default for icon-on and icon-off. * @param {string=} icon-on The icon of the tab while it is selected. * @param {string=} icon-off The icon of the tab while it is not selected. * @param {expression=} badge The badge to put on this tab (usually a number). * @param {expression=} badge-style The style of badge to put on this tab (eg: badge-positive). * @param {expression=} on-select Called when this tab is selected. * @param {expression=} on-deselect Called when this tab is deselected. * @param {expression=} ng-click By default, the tab will be selected on click. If ngClick is set, it will not.  You can explicitly switch tabs using {@link ionic.service:$ionicTabsDelegate#select $ionicTabsDelegate.select()}. * @param {expression=} hidden Whether the tab is to be hidden or not. * @param {expression=} disabled Whether the tab is to be disabled or not. */IonicModule.directive('ionTab', [  '$compile',  '$ionicConfig',  '$ionicBind',  '$ionicViewSwitcher',function($compile, $ionicConfig, $ionicBind, $ionicViewSwitcher) {  //Returns ' key="value"' if value exists  function attrStr(k, v) {    return isDefined(v) ? ' ' + k + '="' + v + '"' : '';  }  return {    restrict: 'E',    require: ['^ionTabs', 'ionTab'],    controller: '$ionicTab',    scope: true,    compile: function(element, attr) {      //We create the tabNavTemplate in the compile phase so that the      //attributes we pass down won't be interpolated yet - we want      //to pass down the 'raw' versions of the attributes      var tabNavTemplate = '<ion-tab-nav' +        attrStr('ng-click', attr.ngClick) +        attrStr('title', attr.title) +        attrStr('icon', attr.icon) +        attrStr('icon-on', attr.iconOn) +        attrStr('icon-off', attr.iconOff) +        attrStr('badge', attr.badge) +        attrStr('badge-style', attr.badgeStyle) +        attrStr('hidden', attr.hidden) +        attrStr('disabled', attr.disabled) +        attrStr('class', attr['class']) +        '></ion-tab-nav>';      //Remove the contents of the element so we can compile them later, if tab is selected      var tabContentEle = document.createElement('div');      for (var x = 0; x < element[0].children.length; x++) {        tabContentEle.appendChild(element[0].children[x].cloneNode(true));      }      var childElementCount = tabContentEle.childElementCount;      element.empty();      var navViewName, isNavView;      if (childElementCount) {        if (tabContentEle.children[0].tagName === 'ION-NAV-VIEW') {          // get the name if it's a nav-view          navViewName = tabContentEle.children[0].getAttribute('name');          tabContentEle.children[0].classList.add('view-container');          isNavView = true;        }        if (childElementCount === 1) {          // make the 1 child element the primary tab content container          tabContentEle = tabContentEle.children[0];        }        if (!isNavView) tabContentEle.classList.add('pane');        tabContentEle.classList.add('tab-content');      }      return function link($scope, $element, $attr, ctrls) {        var childScope;        var childElement;        var tabsCtrl = ctrls[0];        var tabCtrl = ctrls[1];        var isTabContentAttached = false;        $scope.$tabSelected = false;        $ionicBind($scope, $attr, {          onSelect: '&',          onDeselect: '&',          title: '@',          uiSref: '@',          href: '@'        });        tabsCtrl.add($scope);        $scope.$on('$destroy', function() {          if (!$scope.$tabsDestroy) {            // if the containing ionTabs directive is being destroyed            // then don't bother going through the controllers remove            // method, since remove will reset the active tab as each tab            // is being destroyed, causing unnecessary view loads and transitions            tabsCtrl.remove($scope);          }          tabNavElement.isolateScope().$destroy();          tabNavElement.remove();          tabNavElement = tabContentEle = childElement = null;        });        //Remove title attribute so browser-tooltip does not apear        $element[0].removeAttribute('title');        if (navViewName) {          tabCtrl.navViewName = $scope.navViewName = navViewName;        }        $scope.$on('$stateChangeSuccess', selectIfMatchesState);        selectIfMatchesState();        function selectIfMatchesState() {          if (tabCtrl.tabMatchesState()) {            tabsCtrl.select($scope, false);          }        }        var tabNavElement = jqLite(tabNavTemplate);        tabNavElement.data('$ionTabsController', tabsCtrl);        tabNavElement.data('$ionTabController', tabCtrl);        tabsCtrl.$tabsElement.append($compile(tabNavElement)($scope));        function tabSelected(isSelected) {          if (isSelected && childElementCount) {            // this tab is being selected            // check if the tab is already in the DOM            // only do this if the tab has child elements            if (!isTabContentAttached) {              // tab should be selected and is NOT in the DOM              // create a new scope and append it              childScope = $scope.$new();              childElement = jqLite(tabContentEle);              $ionicViewSwitcher.viewEleIsActive(childElement, true);              tabsCtrl.$element.append(childElement);              $compile(childElement)(childScope);              isTabContentAttached = true;            }            // remove the hide class so the tabs content shows up            $ionicViewSwitcher.viewEleIsActive(childElement, true);          } else if (isTabContentAttached && childElement) {            // this tab should NOT be selected, and it is already in the DOM            if ($ionicConfig.views.maxCache() > 0) {              // keep the tabs in the DOM, only css hide it              $ionicViewSwitcher.viewEleIsActive(childElement, false);            } else {              // do not keep tabs in the DOM              destroyTab();            }          }        }        function destroyTab() {          childScope && childScope.$destroy();          isTabContentAttached && childElement && childElement.remove();          tabContentEle.innerHTML = '';          isTabContentAttached = childScope = childElement = null;        }        $scope.$watch('$tabSelected', tabSelected);        $scope.$on('$ionicView.afterEnter', function() {          $ionicViewSwitcher.viewEleIsActive(childElement, $scope.$tabSelected);        });        $scope.$on('$ionicView.clearCache', function() {          if (!$scope.$tabSelected) {            destroyTab();          }        });      };    }  };}]);IonicModule.directive('ionTabNav', [function() {  return {    restrict: 'E',    replace: true,    require: ['^ionTabs', '^ionTab'],    template:    '<a ng-class="{\'has-badge\':badge, \'tab-hidden\':isHidden(), \'tab-item-active\': isTabActive()}" ' +      ' ng-disabled="disabled()" class="tab-item">' +      '<span class="badge {{badgeStyle}}" ng-if="badge">{{badge}}</span>' +      '<i class="icon {{getIcon()}}" ng-if="getIcon()"></i>' +      '<span class="tab-title" ng-bind-html="title"></span>' +    '</a>',    scope: {      title: '@',      icon: '@',      iconOn: '@',      iconOff: '@',      badge: '=',      hidden: '@',      disabled: '&',      badgeStyle: '@',      'class': '@'    },    link: function($scope, $element, $attrs, ctrls) {      var tabsCtrl = ctrls[0],        tabCtrl = ctrls[1];      //Remove title attribute so browser-tooltip does not apear      $element[0].removeAttribute('title');      $scope.selectTab = function(e) {        e.preventDefault();        tabsCtrl.select(tabCtrl.$scope, true);      };      if (!$attrs.ngClick) {        $element.on('click', function(event) {          $scope.$apply(function() {            $scope.selectTab(event);          });        });      }      $scope.isHidden = function() {        if ($attrs.hidden === 'true' || $attrs.hidden === true) return true;        return false;      };      $scope.getIconOn = function() {        return $scope.iconOn || $scope.icon;      };      $scope.getIconOff = function() {        return $scope.iconOff || $scope.icon;      };      $scope.isTabActive = function() {        return tabsCtrl.selectedTab() === tabCtrl.$scope;      };      $scope.getIcon = function() {        if ( tabsCtrl.selectedTab() === tabCtrl.$scope ) {          // active          return $scope.iconOn || $scope.icon;        }        else {          // inactive          return $scope.iconOff || $scope.icon;        }      };    }  };}]);/** * @ngdoc directive * @name ionTabs * @module ionic * @delegate ionic.service:$ionicTabsDelegate * @restrict E * @codepen odqCz * * @description * Powers a multi-tabbed interface with a Tab Bar and a set of "pages" that can be tabbed * through. * * Assign any [tabs class](/docs/components#tabs) to the element to define * its look and feel. * * For iOS, tabs will appear at the bottom of the screen. For Android, tabs will be at the top * of the screen, below the nav-bar. This follows each OS's design specification, but can be * configured with the {@link ionic.provider:$ionicConfigProvider}. * * See the {@link ionic.directive:ionTab} directive's documentation for more details on * individual tabs. * * Note: do not place ion-tabs inside of an ion-content element; it has been known to cause a * certain CSS bug. * * @usage * ```html * <ion-tabs class="tabs-positive tabs-icon-top"> * *   <ion-tab title="Home" icon-on="ion-ios-filing" icon-off="ion-ios-filing-outline"> *     <!-- Tab 1 content --> *   </ion-tab> * *   <ion-tab title="About" icon-on="ion-ios-clock" icon-off="ion-ios-clock-outline"> *     <!-- Tab 2 content --> *   </ion-tab> * *   <ion-tab title="Settings" icon-on="ion-ios-gear" icon-off="ion-ios-gear-outline"> *     <!-- Tab 3 content --> *   </ion-tab> * * </ion-tabs> * ``` * * @param {string=} delegate-handle The handle used to identify these tabs * with {@link ionic.service:$ionicTabsDelegate}. */IonicModule.directive('ionTabs', [  '$ionicTabsDelegate',  '$ionicConfig',function($ionicTabsDelegate, $ionicConfig) {  return {    restrict: 'E',    scope: true,    controller: '$ionicTabs',    compile: function(tElement) {      //We cannot use regular transclude here because it breaks element.data()      //inheritance on compile      var innerElement = jqLite('<div class="tab-nav tabs">');      innerElement.append(tElement.contents());      tElement.append(innerElement)              .addClass('tabs-' + $ionicConfig.tabs.position() + ' tabs-' + $ionicConfig.tabs.style());      return { pre: prelink, post: postLink };      function prelink($scope, $element, $attr, tabsCtrl) {        var deregisterInstance = $ionicTabsDelegate._registerInstance(          tabsCtrl, $attr.delegateHandle, tabsCtrl.hasActiveScope        );        tabsCtrl.$scope = $scope;        tabsCtrl.$element = $element;        tabsCtrl.$tabsElement = jqLite($element[0].querySelector('.tabs'));        $scope.$watch(function() { return $element[0].className; }, function(value) {          var isTabsTop = value.indexOf('tabs-top') !== -1;          var isHidden = value.indexOf('tabs-item-hide') !== -1;          $scope.$hasTabs = !isTabsTop && !isHidden;          $scope.$hasTabsTop = isTabsTop && !isHidden;          $scope.$emit('$ionicTabs.top', $scope.$hasTabsTop);        });        function emitLifecycleEvent(ev, data) {          ev.stopPropagation();          var previousSelectedTab = tabsCtrl.previousSelectedTab();          if (previousSelectedTab) {            previousSelectedTab.$broadcast(ev.name.replace('NavView', 'Tabs'), data);          }        }        $scope.$on('$ionicNavView.beforeLeave', emitLifecycleEvent);        $scope.$on('$ionicNavView.afterLeave', emitLifecycleEvent);        $scope.$on('$ionicNavView.leave', emitLifecycleEvent);        $scope.$on('$destroy', function() {          // variable to inform child tabs that they're all being blown away          // used so that while destorying an individual tab, each one          // doesn't select the next tab as the active one, which causes unnecessary          // loading of tab views when each will eventually all go away anyway          $scope.$tabsDestroy = true;          deregisterInstance();          tabsCtrl.$tabsElement = tabsCtrl.$element = tabsCtrl.$scope = innerElement = null;          delete $scope.$hasTabs;          delete $scope.$hasTabsTop;        });      }      function postLink($scope, $element, $attr, tabsCtrl) {        if (!tabsCtrl.selectedTab()) {          // all the tabs have been added          // but one hasn't been selected yet          tabsCtrl.select(0);        }      }    }  };}]);/*** @ngdoc directive* @name ionTitle* @module ionic* @restrict E** Used for titles in header and nav bars. New in 1.2** Identical to <div class="title"> but with future compatibility for Ionic 2** @usage** ```html* <ion-nav-bar>*   <ion-title>Hello</ion-title>* <ion-nav-bar>* ```*/IonicModule.directive('ionTitle', [function() {  return {    restrict: 'E',    compile: function(element) {      element.addClass('title');    }  };}]);/** * @ngdoc directive * @name ionToggle * @module ionic * @codepen tfAzj * @restrict E * * @description * A toggle is an animated switch which binds a given model to a boolean. * * Allows dragging of the switch's nub. * * The toggle behaves like any [AngularJS checkbox](http://docs.angularjs.org/api/ng/input/input[checkbox]) otherwise. * * @param toggle-class {string=} Sets the CSS class on the inner `label.toggle` element created by the directive. * * @usage * Below is an example of a toggle directive which is wired up to the `airplaneMode` model * and has the `toggle-calm` CSS class assigned to the inner element. * * ```html * <ion-toggle ng-model="airplaneMode" toggle-class="toggle-calm">Airplane Mode</ion-toggle> * ``` */IonicModule.directive('ionToggle', [  '$timeout',  '$ionicConfig',function($timeout, $ionicConfig) {  return {    restrict: 'E',    replace: true,    require: '?ngModel',    transclude: true,    template:      '<div class="item item-toggle">' +        '<div ng-transclude></div>' +        '<label class="toggle">' +          '<input type="checkbox">' +          '<div class="track">' +            '<div class="handle"></div>' +          '</div>' +        '</label>' +      '</div>',    compile: function(element, attr) {      var input = element.find('input');      forEach({        'name': attr.name,        'ng-value': attr.ngValue,        'ng-model': attr.ngModel,        'ng-checked': attr.ngChecked,        'ng-disabled': attr.ngDisabled,        'ng-true-value': attr.ngTrueValue,        'ng-false-value': attr.ngFalseValue,        'ng-change': attr.ngChange,        'ng-required': attr.ngRequired,        'required': attr.required      }, function(value, name) {        if (isDefined(value)) {          input.attr(name, value);        }      });      if (attr.toggleClass) {        element[0].getElementsByTagName('label')[0].classList.add(attr.toggleClass);      }      element.addClass('toggle-' + $ionicConfig.form.toggle());      return function($scope, $element) {        var el = $element[0].getElementsByTagName('label')[0];        var checkbox = el.children[0];        var track = el.children[1];        var handle = track.children[0];        var ngModelController = jqLite(checkbox).controller('ngModel');        $scope.toggle = new ionic.views.Toggle({          el: el,          track: track,          checkbox: checkbox,          handle: handle,          onChange: function() {            if (ngModelController) {              ngModelController.$setViewValue(checkbox.checked);              $scope.$apply();            }          }        });        $scope.$on('$destroy', function() {          $scope.toggle.destroy();        });      };    }  };}]);/** * @ngdoc directive * @name ionView * @module ionic * @restrict E * @parent ionNavView * * @description * A container for view content and any navigational and header bar information. When a view * enters and exits its parent {@link ionic.directive:ionNavView}, the view also emits view * information, such as its title, whether the back button should be displayed or not, whether the * corresponding {@link ionic.directive:ionNavBar} should be displayed or not, which transition the view * should use to animate, and which direction to animate. * * *Views are cached to improve performance.* When a view is navigated away from, its element is * left in the DOM, and its scope is disconnected from the `$watch` cycle. When navigating to a * view that is already cached, its scope is reconnected, and the existing element, which was * left in the DOM, becomes active again. This can be disabled, or the maximum number of cached * views changed in {@link ionic.provider:$ionicConfigProvider}, in the view's `$state` configuration, or * as an attribute on the view itself (see below). * * @usage * Below is an example where our page will load with a {@link ionic.directive:ionNavBar} containing * "My Page" as the title. * * ```html * <ion-nav-bar></ion-nav-bar> * <ion-nav-view> *   <ion-view view-title="My Page"> *     <ion-content> *       Hello! *     </ion-content> *   </ion-view> * </ion-nav-view> * ``` * * ## View LifeCycle and Events * * Views can be cached, which means ***controllers normally only load once***, which may * affect your controller logic. To know when a view has entered or left, events * have been added that are emitted from the view's scope. These events also * contain data about the view, such as the title and whether the back button should * show. Also contained is transition data, such as the transition type and * direction that will be or was used. * * Life cycle events are emitted upwards from the transitioning view's scope. In some cases, it is * desirable for a child/nested view to be notified of the event. * For this use case, `$ionicParentView` life cycle events are broadcast downwards. * * <table class="table"> *  <tr> *   <td><code>$ionicView.loaded</code></td> *   <td>The view has loaded. This event only happens once per * view being created and added to the DOM. If a view leaves but is cached, * then this event will not fire again on a subsequent viewing. The loaded event * is good place to put your setup code for the view; however, it is not the * recommended event to listen to when a view becomes active.</td> *  </tr> *  <tr> *   <td><code>$ionicView.enter</code></td> *   <td>The view has fully entered and is now the active view. * This event will fire, whether it was the first load or a cached view.</td> *  </tr> *  <tr> *   <td><code>$ionicView.leave</code></td> *   <td>The view has finished leaving and is no longer the * active view. This event will fire, whether it is cached or destroyed.</td> *  </tr> *  <tr> *   <td><code>$ionicView.beforeEnter</code></td> *   <td>The view is about to enter and become the active view.</td> *  </tr> *  <tr> *   <td><code>$ionicView.beforeLeave</code></td> *   <td>The view is about to leave and no longer be the active view.</td> *  </tr> *  <tr> *   <td><code>$ionicView.afterEnter</code></td> *   <td>The view has fully entered and is now the active view.</td> *  </tr> *  <tr> *   <td><code>$ionicView.afterLeave</code></td> *   <td>The view has finished leaving and is no longer the active view.</td> *  </tr> *  <tr> *   <td><code>$ionicView.unloaded</code></td> *   <td>The view's controller has been destroyed and its element has been * removed from the DOM.</td> *  </tr> *  <tr> *   <td><code>$ionicParentView.enter</code></td> *   <td>The parent view has fully entered and is now the active view. * This event will fire, whether it was the first load or a cached view.</td> *  </tr> *  <tr> *   <td><code>$ionicParentView.leave</code></td> *   <td>The parent view has finished leaving and is no longer the * active view. This event will fire, whether it is cached or destroyed.</td> *  </tr> *  <tr> *   <td><code>$ionicParentView.beforeEnter</code></td> *   <td>The parent view is about to enter and become the active view.</td> *  </tr> *  <tr> *   <td><code>$ionicParentView.beforeLeave</code></td> *   <td>The parent view is about to leave and no longer be the active view.</td> *  </tr> *  <tr> *   <td><code>$ionicParentView.afterEnter</code></td> *   <td>The parent view has fully entered and is now the active view.</td> *  </tr> *  <tr> *   <td><code>$ionicParentView.afterLeave</code></td> *   <td>The parent view has finished leaving and is no longer the active view.</td> *  </tr> * </table> * * ## LifeCycle Event Usage * * Below is an example of how to listen to life cycle events and * access state parameter data * * ```js * $scope.$on("$ionicView.beforeEnter", function(event, data){ *    // handle event *    console.log("State Params: ", data.stateParams); * }); * * $scope.$on("$ionicView.enter", function(event, data){ *    // handle event *    console.log("State Params: ", data.stateParams); * }); * * $scope.$on("$ionicView.afterEnter", function(event, data){ *    // handle event *    console.log("State Params: ", data.stateParams); * }); * ``` * * ## Caching * * Caching can be disabled and enabled in multiple ways. By default, Ionic will * cache a maximum of 10 views. You can optionally choose to disable caching at * either an individual view basis, or by global configuration. Please see the * _Caching_ section in {@link ionic.directive:ionNavView} for more info. * * @param {string=} view-title A text-only title to display on the parent {@link ionic.directive:ionNavBar}. * For an HTML title, such as an image, see {@link ionic.directive:ionNavTitle} instead. * @param {boolean=} cache-view If this view should be allowed to be cached or not. * Please see the _Caching_ section in {@link ionic.directive:ionNavView} for * more info. Default `true` * @param {boolean=} can-swipe-back If this view should be allowed to use the swipe to go back gesture or not. * This does not enable the swipe to go back feature if it is not available for the platform it's running * from, or there isn't a previous view. Default `true` * @param {boolean=} hide-back-button Whether to hide the back button on the parent * {@link ionic.directive:ionNavBar} by default. * @param {boolean=} hide-nav-bar Whether to hide the parent * {@link ionic.directive:ionNavBar} by default. */IonicModule.directive('ionView', function() {  return {    restrict: 'EA',    priority: 1000,    controller: '$ionicView',    compile: function(tElement) {      tElement.addClass('pane');      tElement[0].removeAttribute('title');      return function link($scope, $element, $attrs, viewCtrl) {        viewCtrl.init();      };    }  };});})();
 |