ionic-angular.js 450 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783678467856786678767886789679067916792679367946795679667976798679968006801680268036804680568066807680868096810681168126813681468156816681768186819682068216822682368246825682668276828682968306831683268336834683568366837683868396840684168426843684468456846684768486849685068516852685368546855685668576858685968606861686268636864686568666867686868696870687168726873687468756876687768786879688068816882688368846885688668876888688968906891689268936894689568966897689868996900690169026903690469056906690769086909691069116912691369146915691669176918691969206921692269236924692569266927692869296930693169326933693469356936693769386939694069416942694369446945694669476948694969506951695269536954695569566957695869596960696169626963696469656966696769686969697069716972697369746975697669776978697969806981698269836984698569866987698869896990699169926993699469956996699769986999700070017002700370047005700670077008700970107011701270137014701570167017701870197020702170227023702470257026702770287029703070317032703370347035703670377038703970407041704270437044704570467047704870497050705170527053705470557056705770587059706070617062706370647065706670677068706970707071707270737074707570767077707870797080708170827083708470857086708770887089709070917092709370947095709670977098709971007101710271037104710571067107710871097110711171127113711471157116711771187119712071217122712371247125712671277128712971307131713271337134713571367137713871397140714171427143714471457146714771487149715071517152715371547155715671577158715971607161716271637164716571667167716871697170717171727173717471757176717771787179718071817182718371847185718671877188718971907191719271937194719571967197719871997200720172027203720472057206720772087209721072117212721372147215721672177218721972207221722272237224722572267227722872297230723172327233723472357236723772387239724072417242724372447245724672477248724972507251725272537254725572567257725872597260726172627263726472657266726772687269727072717272727372747275727672777278727972807281728272837284728572867287728872897290729172927293729472957296729772987299730073017302730373047305730673077308730973107311731273137314731573167317731873197320732173227323732473257326732773287329733073317332733373347335733673377338733973407341734273437344734573467347734873497350735173527353735473557356735773587359736073617362736373647365736673677368736973707371737273737374737573767377737873797380738173827383738473857386738773887389739073917392739373947395739673977398739974007401740274037404740574067407740874097410741174127413741474157416741774187419742074217422742374247425742674277428742974307431743274337434743574367437743874397440744174427443744474457446744774487449745074517452745374547455745674577458745974607461746274637464746574667467746874697470747174727473747474757476747774787479748074817482748374847485748674877488748974907491749274937494749574967497749874997500750175027503750475057506750775087509751075117512751375147515751675177518751975207521752275237524752575267527752875297530753175327533753475357536753775387539754075417542754375447545754675477548754975507551755275537554755575567557755875597560756175627563756475657566756775687569757075717572757375747575757675777578757975807581758275837584758575867587758875897590759175927593759475957596759775987599760076017602760376047605760676077608760976107611761276137614761576167617761876197620762176227623762476257626762776287629763076317632763376347635763676377638763976407641764276437644764576467647764876497650765176527653765476557656765776587659766076617662766376647665766676677668766976707671767276737674767576767677767876797680768176827683768476857686768776887689769076917692769376947695769676977698769977007701770277037704770577067707770877097710771177127713771477157716771777187719772077217722772377247725772677277728772977307731773277337734773577367737773877397740774177427743774477457746774777487749775077517752775377547755775677577758775977607761776277637764776577667767776877697770777177727773777477757776777777787779778077817782778377847785778677877788778977907791779277937794779577967797779877997800780178027803780478057806780778087809781078117812781378147815781678177818781978207821782278237824782578267827782878297830783178327833783478357836783778387839784078417842784378447845784678477848784978507851785278537854785578567857785878597860786178627863786478657866786778687869787078717872787378747875787678777878787978807881788278837884788578867887788878897890789178927893789478957896789778987899790079017902790379047905790679077908790979107911791279137914791579167917791879197920792179227923792479257926792779287929793079317932793379347935793679377938793979407941794279437944794579467947794879497950795179527953795479557956795779587959796079617962796379647965796679677968796979707971797279737974797579767977797879797980798179827983798479857986798779887989799079917992799379947995799679977998799980008001800280038004800580068007800880098010801180128013801480158016801780188019802080218022802380248025802680278028802980308031803280338034803580368037803880398040804180428043804480458046804780488049805080518052805380548055805680578058805980608061806280638064806580668067806880698070807180728073807480758076807780788079808080818082808380848085808680878088808980908091809280938094809580968097809880998100810181028103810481058106810781088109811081118112811381148115811681178118811981208121812281238124812581268127812881298130813181328133813481358136813781388139814081418142814381448145814681478148814981508151815281538154815581568157815881598160816181628163816481658166816781688169817081718172817381748175817681778178817981808181818281838184818581868187818881898190819181928193819481958196819781988199820082018202820382048205820682078208820982108211821282138214821582168217821882198220822182228223822482258226822782288229823082318232823382348235823682378238823982408241824282438244824582468247824882498250825182528253825482558256825782588259826082618262826382648265826682678268826982708271827282738274827582768277827882798280828182828283828482858286828782888289829082918292829382948295829682978298829983008301830283038304830583068307830883098310831183128313831483158316831783188319832083218322832383248325832683278328832983308331833283338334833583368337833883398340834183428343834483458346834783488349835083518352835383548355835683578358835983608361836283638364836583668367836883698370837183728373837483758376837783788379838083818382838383848385838683878388838983908391839283938394839583968397839883998400840184028403840484058406840784088409841084118412841384148415841684178418841984208421842284238424842584268427842884298430843184328433843484358436843784388439844084418442844384448445844684478448844984508451845284538454845584568457845884598460846184628463846484658466846784688469847084718472847384748475847684778478847984808481848284838484848584868487848884898490849184928493849484958496849784988499850085018502850385048505850685078508850985108511851285138514851585168517851885198520852185228523852485258526852785288529853085318532853385348535853685378538853985408541854285438544854585468547854885498550855185528553855485558556855785588559856085618562856385648565856685678568856985708571857285738574857585768577857885798580858185828583858485858586858785888589859085918592859385948595859685978598859986008601860286038604860586068607860886098610861186128613861486158616861786188619862086218622862386248625862686278628862986308631863286338634863586368637863886398640864186428643864486458646864786488649865086518652865386548655865686578658865986608661866286638664866586668667866886698670867186728673867486758676867786788679868086818682868386848685868686878688868986908691869286938694869586968697869886998700870187028703870487058706870787088709871087118712871387148715871687178718871987208721872287238724872587268727872887298730873187328733873487358736873787388739874087418742874387448745874687478748874987508751875287538754875587568757875887598760876187628763876487658766876787688769877087718772877387748775877687778778877987808781878287838784878587868787878887898790879187928793879487958796879787988799880088018802880388048805880688078808880988108811881288138814881588168817881888198820882188228823882488258826882788288829883088318832883388348835883688378838883988408841884288438844884588468847884888498850885188528853885488558856885788588859886088618862886388648865886688678868886988708871887288738874887588768877887888798880888188828883888488858886888788888889889088918892889388948895889688978898889989008901890289038904890589068907890889098910891189128913891489158916891789188919892089218922892389248925892689278928892989308931893289338934893589368937893889398940894189428943894489458946894789488949895089518952895389548955895689578958895989608961896289638964896589668967896889698970897189728973897489758976897789788979898089818982898389848985898689878988898989908991899289938994899589968997899889999000900190029003900490059006900790089009901090119012901390149015901690179018901990209021902290239024902590269027902890299030903190329033903490359036903790389039904090419042904390449045904690479048904990509051905290539054905590569057905890599060906190629063906490659066906790689069907090719072907390749075907690779078907990809081908290839084908590869087908890899090909190929093909490959096909790989099910091019102910391049105910691079108910991109111911291139114911591169117911891199120912191229123912491259126912791289129913091319132913391349135913691379138913991409141914291439144914591469147914891499150915191529153915491559156915791589159916091619162916391649165916691679168916991709171917291739174917591769177917891799180918191829183918491859186918791889189919091919192919391949195919691979198919992009201920292039204920592069207920892099210921192129213921492159216921792189219922092219222922392249225922692279228922992309231923292339234923592369237923892399240924192429243924492459246924792489249925092519252925392549255925692579258925992609261926292639264926592669267926892699270927192729273927492759276927792789279928092819282928392849285928692879288928992909291929292939294929592969297929892999300930193029303930493059306930793089309931093119312931393149315931693179318931993209321932293239324932593269327932893299330933193329333933493359336933793389339934093419342934393449345934693479348934993509351935293539354935593569357935893599360936193629363936493659366936793689369937093719372937393749375937693779378937993809381938293839384938593869387938893899390939193929393939493959396939793989399940094019402940394049405940694079408940994109411941294139414941594169417941894199420942194229423942494259426942794289429943094319432943394349435943694379438943994409441944294439444944594469447944894499450945194529453945494559456945794589459946094619462946394649465946694679468946994709471947294739474947594769477947894799480948194829483948494859486948794889489949094919492949394949495949694979498949995009501950295039504950595069507950895099510951195129513951495159516951795189519952095219522952395249525952695279528952995309531953295339534953595369537953895399540954195429543954495459546954795489549955095519552955395549555955695579558955995609561956295639564956595669567956895699570957195729573957495759576957795789579958095819582958395849585958695879588958995909591959295939594959595969597959895999600960196029603960496059606960796089609961096119612961396149615961696179618961996209621962296239624962596269627962896299630963196329633963496359636963796389639964096419642964396449645964696479648964996509651965296539654965596569657965896599660966196629663966496659666966796689669967096719672967396749675967696779678967996809681968296839684968596869687968896899690969196929693969496959696969796989699970097019702970397049705970697079708970997109711971297139714971597169717971897199720972197229723972497259726972797289729973097319732973397349735973697379738973997409741974297439744974597469747974897499750975197529753975497559756975797589759976097619762976397649765976697679768976997709771977297739774977597769777977897799780978197829783978497859786978797889789979097919792979397949795979697979798979998009801980298039804980598069807980898099810981198129813981498159816981798189819982098219822982398249825982698279828982998309831983298339834983598369837983898399840984198429843984498459846984798489849985098519852985398549855985698579858985998609861986298639864986598669867986898699870987198729873987498759876987798789879988098819882988398849885988698879888988998909891989298939894989598969897989898999900990199029903990499059906990799089909991099119912991399149915991699179918991999209921992299239924992599269927992899299930993199329933993499359936993799389939994099419942994399449945994699479948994999509951995299539954995599569957995899599960996199629963996499659966996799689969997099719972997399749975997699779978997999809981998299839984998599869987998899899990999199929993999499959996999799989999100001000110002100031000410005100061000710008100091001010011100121001310014100151001610017100181001910020100211002210023100241002510026100271002810029100301003110032100331003410035100361003710038100391004010041100421004310044100451004610047100481004910050100511005210053100541005510056100571005810059100601006110062100631006410065100661006710068100691007010071100721007310074100751007610077100781007910080100811008210083100841008510086100871008810089100901009110092100931009410095100961009710098100991010010101101021010310104101051010610107101081010910110101111011210113101141011510116101171011810119101201012110122101231012410125101261012710128101291013010131101321013310134101351013610137101381013910140101411014210143101441014510146101471014810149101501015110152101531015410155101561015710158101591016010161101621016310164101651016610167101681016910170101711017210173101741017510176101771017810179101801018110182101831018410185101861018710188101891019010191101921019310194101951019610197101981019910200102011020210203102041020510206102071020810209102101021110212102131021410215102161021710218102191022010221102221022310224102251022610227102281022910230102311023210233102341023510236102371023810239102401024110242102431024410245102461024710248102491025010251102521025310254102551025610257102581025910260102611026210263102641026510266102671026810269102701027110272102731027410275102761027710278102791028010281102821028310284102851028610287102881028910290102911029210293102941029510296102971029810299103001030110302103031030410305103061030710308103091031010311103121031310314103151031610317103181031910320103211032210323103241032510326103271032810329103301033110332103331033410335103361033710338103391034010341103421034310344103451034610347103481034910350103511035210353103541035510356103571035810359103601036110362103631036410365103661036710368103691037010371103721037310374103751037610377103781037910380103811038210383103841038510386103871038810389103901039110392103931039410395103961039710398103991040010401104021040310404104051040610407104081040910410104111041210413104141041510416104171041810419104201042110422104231042410425104261042710428104291043010431104321043310434104351043610437104381043910440104411044210443104441044510446104471044810449104501045110452104531045410455104561045710458104591046010461104621046310464104651046610467104681046910470104711047210473104741047510476104771047810479104801048110482104831048410485104861048710488104891049010491104921049310494104951049610497104981049910500105011050210503105041050510506105071050810509105101051110512105131051410515105161051710518105191052010521105221052310524105251052610527105281052910530105311053210533105341053510536105371053810539105401054110542105431054410545105461054710548105491055010551105521055310554105551055610557105581055910560105611056210563105641056510566105671056810569105701057110572105731057410575105761057710578105791058010581105821058310584105851058610587105881058910590105911059210593105941059510596105971059810599106001060110602106031060410605106061060710608106091061010611106121061310614106151061610617106181061910620106211062210623106241062510626106271062810629106301063110632106331063410635106361063710638106391064010641106421064310644106451064610647106481064910650106511065210653106541065510656106571065810659106601066110662106631066410665106661066710668106691067010671106721067310674106751067610677106781067910680106811068210683106841068510686106871068810689106901069110692106931069410695106961069710698106991070010701107021070310704107051070610707107081070910710107111071210713107141071510716107171071810719107201072110722107231072410725107261072710728107291073010731107321073310734107351073610737107381073910740107411074210743107441074510746107471074810749107501075110752107531075410755107561075710758107591076010761107621076310764107651076610767107681076910770107711077210773107741077510776107771077810779107801078110782107831078410785107861078710788107891079010791107921079310794107951079610797107981079910800108011080210803108041080510806108071080810809108101081110812108131081410815108161081710818108191082010821108221082310824108251082610827108281082910830108311083210833108341083510836108371083810839108401084110842108431084410845108461084710848108491085010851108521085310854108551085610857108581085910860108611086210863108641086510866108671086810869108701087110872108731087410875108761087710878108791088010881108821088310884108851088610887108881088910890108911089210893108941089510896108971089810899109001090110902109031090410905109061090710908109091091010911109121091310914109151091610917109181091910920109211092210923109241092510926109271092810929109301093110932109331093410935109361093710938109391094010941109421094310944109451094610947109481094910950109511095210953109541095510956109571095810959109601096110962109631096410965109661096710968109691097010971109721097310974109751097610977109781097910980109811098210983109841098510986109871098810989109901099110992109931099410995109961099710998109991100011001110021100311004110051100611007110081100911010110111101211013110141101511016110171101811019110201102111022110231102411025110261102711028110291103011031110321103311034110351103611037110381103911040110411104211043110441104511046110471104811049110501105111052110531105411055110561105711058110591106011061110621106311064110651106611067110681106911070110711107211073110741107511076110771107811079110801108111082110831108411085110861108711088110891109011091110921109311094110951109611097110981109911100111011110211103111041110511106111071110811109111101111111112111131111411115111161111711118111191112011121111221112311124111251112611127111281112911130111311113211133111341113511136111371113811139111401114111142111431114411145111461114711148111491115011151111521115311154111551115611157111581115911160111611116211163111641116511166111671116811169111701117111172111731117411175111761117711178111791118011181111821118311184111851118611187111881118911190111911119211193111941119511196111971119811199112001120111202112031120411205112061120711208112091121011211112121121311214112151121611217112181121911220112211122211223112241122511226112271122811229112301123111232112331123411235112361123711238112391124011241112421124311244112451124611247112481124911250112511125211253112541125511256112571125811259112601126111262112631126411265112661126711268112691127011271112721127311274112751127611277112781127911280112811128211283112841128511286112871128811289112901129111292112931129411295112961129711298112991130011301113021130311304113051130611307113081130911310113111131211313113141131511316113171131811319113201132111322113231132411325113261132711328113291133011331113321133311334113351133611337113381133911340113411134211343113441134511346113471134811349113501135111352113531135411355113561135711358113591136011361113621136311364113651136611367113681136911370113711137211373113741137511376113771137811379113801138111382113831138411385113861138711388113891139011391113921139311394113951139611397113981139911400114011140211403114041140511406114071140811409114101141111412114131141411415114161141711418114191142011421114221142311424114251142611427114281142911430114311143211433114341143511436114371143811439114401144111442114431144411445114461144711448114491145011451114521145311454114551145611457114581145911460114611146211463114641146511466114671146811469114701147111472114731147411475114761147711478114791148011481114821148311484114851148611487114881148911490114911149211493114941149511496114971149811499115001150111502115031150411505115061150711508115091151011511115121151311514115151151611517115181151911520115211152211523115241152511526115271152811529115301153111532115331153411535115361153711538115391154011541115421154311544115451154611547115481154911550115511155211553115541155511556115571155811559115601156111562115631156411565115661156711568115691157011571115721157311574115751157611577115781157911580115811158211583115841158511586115871158811589115901159111592115931159411595115961159711598115991160011601116021160311604116051160611607116081160911610116111161211613116141161511616116171161811619116201162111622116231162411625116261162711628116291163011631116321163311634116351163611637116381163911640116411164211643116441164511646116471164811649116501165111652116531165411655116561165711658116591166011661116621166311664116651166611667116681166911670116711167211673116741167511676116771167811679116801168111682116831168411685116861168711688116891169011691116921169311694116951169611697116981169911700117011170211703117041170511706117071170811709117101171111712117131171411715117161171711718117191172011721117221172311724117251172611727117281172911730117311173211733117341173511736117371173811739117401174111742117431174411745117461174711748117491175011751117521175311754117551175611757117581175911760117611176211763117641176511766117671176811769117701177111772117731177411775117761177711778117791178011781117821178311784117851178611787117881178911790117911179211793117941179511796117971179811799118001180111802118031180411805118061180711808118091181011811118121181311814118151181611817118181181911820118211182211823118241182511826118271182811829118301183111832118331183411835118361183711838118391184011841118421184311844118451184611847118481184911850118511185211853118541185511856118571185811859118601186111862118631186411865118661186711868118691187011871118721187311874118751187611877118781187911880118811188211883118841188511886118871188811889118901189111892118931189411895118961189711898118991190011901119021190311904119051190611907119081190911910119111191211913119141191511916119171191811919119201192111922119231192411925119261192711928119291193011931119321193311934119351193611937119381193911940119411194211943119441194511946119471194811949119501195111952119531195411955119561195711958119591196011961119621196311964119651196611967119681196911970119711197211973119741197511976119771197811979119801198111982119831198411985119861198711988119891199011991119921199311994119951199611997119981199912000120011200212003120041200512006120071200812009120101201112012120131201412015120161201712018120191202012021120221202312024120251202612027120281202912030120311203212033120341203512036120371203812039120401204112042120431204412045120461204712048120491205012051120521205312054120551205612057120581205912060120611206212063120641206512066120671206812069120701207112072120731207412075120761207712078120791208012081120821208312084120851208612087120881208912090120911209212093120941209512096120971209812099121001210112102121031210412105121061210712108121091211012111121121211312114121151211612117121181211912120121211212212123121241212512126121271212812129121301213112132121331213412135121361213712138121391214012141121421214312144121451214612147121481214912150121511215212153121541215512156121571215812159121601216112162121631216412165121661216712168121691217012171121721217312174121751217612177121781217912180121811218212183121841218512186121871218812189121901219112192121931219412195121961219712198121991220012201122021220312204122051220612207122081220912210122111221212213122141221512216122171221812219122201222112222122231222412225122261222712228122291223012231122321223312234122351223612237122381223912240122411224212243122441224512246122471224812249122501225112252122531225412255122561225712258122591226012261122621226312264122651226612267122681226912270122711227212273122741227512276122771227812279122801228112282122831228412285122861228712288122891229012291122921229312294122951229612297122981229912300123011230212303123041230512306123071230812309123101231112312123131231412315123161231712318123191232012321123221232312324123251232612327123281232912330123311233212333123341233512336123371233812339123401234112342123431234412345123461234712348123491235012351123521235312354123551235612357123581235912360123611236212363123641236512366123671236812369123701237112372123731237412375123761237712378123791238012381123821238312384123851238612387123881238912390123911239212393123941239512396123971239812399124001240112402124031240412405124061240712408124091241012411124121241312414124151241612417124181241912420124211242212423124241242512426124271242812429124301243112432124331243412435124361243712438124391244012441124421244312444124451244612447124481244912450124511245212453124541245512456124571245812459124601246112462124631246412465124661246712468124691247012471124721247312474124751247612477124781247912480124811248212483124841248512486124871248812489124901249112492124931249412495124961249712498124991250012501125021250312504125051250612507125081250912510125111251212513125141251512516125171251812519125201252112522125231252412525125261252712528125291253012531125321253312534125351253612537125381253912540125411254212543125441254512546125471254812549125501255112552125531255412555125561255712558125591256012561125621256312564125651256612567125681256912570125711257212573125741257512576125771257812579125801258112582125831258412585125861258712588125891259012591125921259312594125951259612597125981259912600126011260212603126041260512606126071260812609126101261112612126131261412615126161261712618126191262012621126221262312624126251262612627126281262912630126311263212633126341263512636126371263812639126401264112642126431264412645126461264712648126491265012651126521265312654126551265612657126581265912660126611266212663126641266512666126671266812669126701267112672126731267412675126761267712678126791268012681126821268312684126851268612687126881268912690126911269212693126941269512696126971269812699127001270112702127031270412705127061270712708127091271012711127121271312714127151271612717127181271912720127211272212723127241272512726127271272812729127301273112732127331273412735127361273712738127391274012741127421274312744127451274612747127481274912750127511275212753127541275512756127571275812759127601276112762127631276412765127661276712768127691277012771127721277312774127751277612777127781277912780127811278212783127841278512786127871278812789127901279112792127931279412795127961279712798127991280012801128021280312804128051280612807128081280912810128111281212813128141281512816128171281812819128201282112822128231282412825128261282712828128291283012831128321283312834128351283612837128381283912840128411284212843128441284512846128471284812849128501285112852128531285412855128561285712858128591286012861128621286312864128651286612867128681286912870128711287212873128741287512876128771287812879128801288112882128831288412885128861288712888128891289012891128921289312894128951289612897128981289912900129011290212903129041290512906129071290812909129101291112912129131291412915129161291712918129191292012921129221292312924129251292612927129281292912930129311293212933129341293512936129371293812939129401294112942129431294412945129461294712948129491295012951129521295312954129551295612957129581295912960129611296212963129641296512966129671296812969129701297112972129731297412975129761297712978129791298012981129821298312984129851298612987129881298912990129911299212993129941299512996129971299812999130001300113002130031300413005130061300713008130091301013011130121301313014130151301613017130181301913020130211302213023130241302513026130271302813029130301303113032130331303413035130361303713038130391304013041130421304313044130451304613047130481304913050130511305213053130541305513056130571305813059130601306113062130631306413065130661306713068130691307013071130721307313074130751307613077130781307913080130811308213083130841308513086130871308813089130901309113092130931309413095130961309713098130991310013101131021310313104131051310613107131081310913110131111311213113131141311513116131171311813119131201312113122131231312413125131261312713128131291313013131131321313313134131351313613137131381313913140131411314213143131441314513146131471314813149131501315113152131531315413155131561315713158131591316013161131621316313164131651316613167131681316913170131711317213173131741317513176131771317813179131801318113182131831318413185131861318713188131891319013191131921319313194131951319613197131981319913200132011320213203132041320513206132071320813209132101321113212132131321413215132161321713218132191322013221132221322313224132251322613227132281322913230132311323213233132341323513236132371323813239132401324113242132431324413245132461324713248132491325013251132521325313254132551325613257132581325913260132611326213263132641326513266132671326813269132701327113272132731327413275132761327713278132791328013281132821328313284132851328613287132881328913290132911329213293132941329513296132971329813299133001330113302133031330413305133061330713308133091331013311133121331313314133151331613317133181331913320133211332213323133241332513326133271332813329133301333113332133331333413335133361333713338133391334013341133421334313344133451334613347133481334913350133511335213353133541335513356133571335813359133601336113362133631336413365133661336713368133691337013371133721337313374133751337613377133781337913380133811338213383133841338513386133871338813389133901339113392133931339413395133961339713398133991340013401134021340313404134051340613407134081340913410134111341213413134141341513416134171341813419134201342113422134231342413425134261342713428134291343013431134321343313434134351343613437134381343913440134411344213443134441344513446134471344813449134501345113452134531345413455134561345713458134591346013461134621346313464134651346613467134681346913470134711347213473134741347513476134771347813479134801348113482134831348413485134861348713488134891349013491134921349313494134951349613497134981349913500135011350213503135041350513506135071350813509135101351113512135131351413515135161351713518135191352013521135221352313524135251352613527135281352913530135311353213533135341353513536135371353813539135401354113542135431354413545135461354713548135491355013551135521355313554135551355613557135581355913560135611356213563135641356513566135671356813569135701357113572135731357413575135761357713578135791358013581135821358313584135851358613587135881358913590135911359213593135941359513596135971359813599136001360113602136031360413605136061360713608136091361013611136121361313614136151361613617136181361913620136211362213623136241362513626136271362813629136301363113632136331363413635136361363713638136391364013641136421364313644136451364613647136481364913650136511365213653136541365513656136571365813659136601366113662136631366413665136661366713668136691367013671136721367313674136751367613677136781367913680136811368213683136841368513686136871368813689136901369113692136931369413695136961369713698136991370013701137021370313704137051370613707137081370913710137111371213713137141371513716137171371813719137201372113722137231372413725137261372713728137291373013731137321373313734137351373613737137381373913740137411374213743137441374513746137471374813749137501375113752137531375413755137561375713758137591376013761137621376313764137651376613767137681376913770137711377213773137741377513776137771377813779137801378113782137831378413785137861378713788137891379013791137921379313794137951379613797137981379913800138011380213803138041380513806138071380813809138101381113812138131381413815138161381713818138191382013821138221382313824138251382613827138281382913830138311383213833138341383513836138371383813839138401384113842138431384413845138461384713848138491385013851138521385313854138551385613857138581385913860138611386213863138641386513866138671386813869138701387113872138731387413875138761387713878138791388013881138821388313884138851388613887138881388913890138911389213893138941389513896138971389813899139001390113902139031390413905139061390713908139091391013911139121391313914139151391613917139181391913920139211392213923139241392513926139271392813929139301393113932139331393413935139361393713938139391394013941139421394313944139451394613947139481394913950139511395213953139541395513956139571395813959139601396113962139631396413965139661396713968139691397013971139721397313974139751397613977139781397913980139811398213983139841398513986139871398813989139901399113992139931399413995139961399713998139991400014001140021400314004140051400614007140081400914010140111401214013140141401514016140171401814019140201402114022140231402414025140261402714028140291403014031140321403314034140351403614037140381403914040140411404214043140441404514046140471404814049140501405114052140531405414055140561405714058140591406014061140621406314064140651406614067140681406914070140711407214073140741407514076140771407814079140801408114082140831408414085140861408714088140891409014091140921409314094140951409614097140981409914100141011410214103141041410514106141071410814109141101411114112141131411414115141161411714118141191412014121141221412314124141251412614127141281412914130141311413214133141341413514136141371413814139141401414114142141431414414145141461414714148141491415014151141521415314154141551415614157141581415914160141611416214163141641416514166141671416814169141701417114172141731417414175141761417714178141791418014181141821418314184141851418614187141881418914190141911419214193141941419514196141971419814199142001420114202142031420414205142061420714208142091421014211142121421314214142151421614217142181421914220142211422214223142241422514226142271422814229142301423114232142331423414235142361423714238142391424014241142421424314244142451424614247142481424914250142511425214253142541425514256142571425814259142601426114262142631426414265142661426714268142691427014271142721427314274142751427614277142781427914280142811428214283142841428514286142871428814289142901429114292142931429414295142961429714298142991430014301143021430314304143051430614307143081430914310143111431214313143141431514316143171431814319143201432114322143231432414325143261432714328143291433014331143321433314334143351433614337143381433914340143411434214343143441434514346143471434814349143501435114352143531435414355143561435714358143591436014361143621436314364143651436614367143681436914370143711437214373143741437514376143771437814379143801438114382143831438414385143861438714388143891439014391143921439314394143951439614397143981439914400
  1. /*!
  2. * Copyright 2015 Drifty Co.
  3. * http://drifty.com/
  4. *
  5. * Ionic, v1.3.2
  6. * A powerful HTML5 mobile app framework.
  7. * http://ionicframework.com/
  8. *
  9. * By @maxlynch, @benjsperry, @adamdbradley <3
  10. *
  11. * Licensed under the MIT license. Please see LICENSE for more information.
  12. *
  13. */
  14. (function() {
  15. /* eslint no-unused-vars:0 */
  16. var IonicModule = angular.module('ionic', ['ngAnimate', 'ngSanitize', 'ui.router', 'ngIOS9UIWebViewPatch']),
  17. extend = angular.extend,
  18. forEach = angular.forEach,
  19. isDefined = angular.isDefined,
  20. isNumber = angular.isNumber,
  21. isString = angular.isString,
  22. jqLite = angular.element,
  23. noop = angular.noop;
  24. /**
  25. * @ngdoc service
  26. * @name $ionicActionSheet
  27. * @module ionic
  28. * @description
  29. * The Action Sheet is a slide-up pane that lets the user choose from a set of options.
  30. * Dangerous options are highlighted in red and made obvious.
  31. *
  32. * There are easy ways to cancel out of the action sheet, such as tapping the backdrop or even
  33. * hitting escape on the keyboard for desktop testing.
  34. *
  35. * ![Action Sheet](http://ionicframework.com.s3.amazonaws.com/docs/controllers/actionSheet.gif)
  36. *
  37. * @usage
  38. * To trigger an Action Sheet in your code, use the $ionicActionSheet service in your angular controllers:
  39. *
  40. * ```js
  41. * angular.module('mySuperApp', ['ionic'])
  42. * .controller(function($scope, $ionicActionSheet, $timeout) {
  43. *
  44. * // Triggered on a button click, or some other target
  45. * $scope.show = function() {
  46. *
  47. * // Show the action sheet
  48. * var hideSheet = $ionicActionSheet.show({
  49. * buttons: [
  50. * { text: '<b>Share</b> This' },
  51. * { text: 'Move' }
  52. * ],
  53. * destructiveText: 'Delete',
  54. * titleText: 'Modify your album',
  55. * cancelText: 'Cancel',
  56. * cancel: function() {
  57. // add cancel code..
  58. },
  59. * buttonClicked: function(index) {
  60. * return true;
  61. * }
  62. * });
  63. *
  64. * // For example's sake, hide the sheet after two seconds
  65. * $timeout(function() {
  66. * hideSheet();
  67. * }, 2000);
  68. *
  69. * };
  70. * });
  71. * ```
  72. *
  73. */
  74. IonicModule
  75. .factory('$ionicActionSheet', [
  76. '$rootScope',
  77. '$compile',
  78. '$animate',
  79. '$timeout',
  80. '$ionicTemplateLoader',
  81. '$ionicPlatform',
  82. '$ionicBody',
  83. 'IONIC_BACK_PRIORITY',
  84. function($rootScope, $compile, $animate, $timeout, $ionicTemplateLoader, $ionicPlatform, $ionicBody, IONIC_BACK_PRIORITY) {
  85. return {
  86. show: actionSheet
  87. };
  88. /**
  89. * @ngdoc method
  90. * @name $ionicActionSheet#show
  91. * @description
  92. * Load and return a new action sheet.
  93. *
  94. * A new isolated scope will be created for the
  95. * action sheet and the new element will be appended into the body.
  96. *
  97. * @param {object} options The options for this ActionSheet. Properties:
  98. *
  99. * - `[Object]` `buttons` Which buttons to show. Each button is an object with a `text` field.
  100. * - `{string}` `titleText` The title to show on the action sheet.
  101. * - `{string=}` `cancelText` the text for a 'cancel' button on the action sheet.
  102. * - `{string=}` `destructiveText` The text for a 'danger' on the action sheet.
  103. * - `{function=}` `cancel` Called if the cancel button is pressed, the backdrop is tapped or
  104. * the hardware back button is pressed.
  105. * - `{function=}` `buttonClicked` Called when one of the non-destructive buttons is clicked,
  106. * with the index of the button that was clicked and the button object. Return true to close
  107. * the action sheet, or false to keep it opened.
  108. * - `{function=}` `destructiveButtonClicked` Called when the destructive button is clicked.
  109. * Return true to close the action sheet, or false to keep it opened.
  110. * - `{boolean=}` `cancelOnStateChange` Whether to cancel the actionSheet when navigating
  111. * to a new state. Default true.
  112. * - `{string}` `cssClass` The custom CSS class name.
  113. *
  114. * @returns {function} `hideSheet` A function which, when called, hides & cancels the action sheet.
  115. */
  116. function actionSheet(opts) {
  117. var scope = $rootScope.$new(true);
  118. extend(scope, {
  119. cancel: noop,
  120. destructiveButtonClicked: noop,
  121. buttonClicked: noop,
  122. $deregisterBackButton: noop,
  123. buttons: [],
  124. cancelOnStateChange: true
  125. }, opts || {});
  126. function textForIcon(text) {
  127. if (text && /icon/.test(text)) {
  128. scope.$actionSheetHasIcon = true;
  129. }
  130. }
  131. for (var x = 0; x < scope.buttons.length; x++) {
  132. textForIcon(scope.buttons[x].text);
  133. }
  134. textForIcon(scope.cancelText);
  135. textForIcon(scope.destructiveText);
  136. // Compile the template
  137. var element = scope.element = $compile('<ion-action-sheet ng-class="cssClass" buttons="buttons"></ion-action-sheet>')(scope);
  138. // Grab the sheet element for animation
  139. var sheetEl = jqLite(element[0].querySelector('.action-sheet-wrapper'));
  140. var stateChangeListenDone = scope.cancelOnStateChange ?
  141. $rootScope.$on('$stateChangeSuccess', function() { scope.cancel(); }) :
  142. noop;
  143. // removes the actionSheet from the screen
  144. scope.removeSheet = function(done) {
  145. if (scope.removed) return;
  146. scope.removed = true;
  147. sheetEl.removeClass('action-sheet-up');
  148. $timeout(function() {
  149. // wait to remove this due to a 300ms delay native
  150. // click which would trigging whatever was underneath this
  151. $ionicBody.removeClass('action-sheet-open');
  152. }, 400);
  153. scope.$deregisterBackButton();
  154. stateChangeListenDone();
  155. $animate.removeClass(element, 'active').then(function() {
  156. scope.$destroy();
  157. element.remove();
  158. // scope.cancel.$scope is defined near the bottom
  159. scope.cancel.$scope = sheetEl = null;
  160. (done || noop)(opts.buttons);
  161. });
  162. };
  163. scope.showSheet = function(done) {
  164. if (scope.removed) return;
  165. $ionicBody.append(element)
  166. .addClass('action-sheet-open');
  167. $animate.addClass(element, 'active').then(function() {
  168. if (scope.removed) return;
  169. (done || noop)();
  170. });
  171. $timeout(function() {
  172. if (scope.removed) return;
  173. sheetEl.addClass('action-sheet-up');
  174. }, 20, false);
  175. };
  176. // registerBackButtonAction returns a callback to deregister the action
  177. scope.$deregisterBackButton = $ionicPlatform.registerBackButtonAction(
  178. function() {
  179. $timeout(scope.cancel);
  180. },
  181. IONIC_BACK_PRIORITY.actionSheet
  182. );
  183. // called when the user presses the cancel button
  184. scope.cancel = function() {
  185. // after the animation is out, call the cancel callback
  186. scope.removeSheet(opts.cancel);
  187. };
  188. scope.buttonClicked = function(index) {
  189. // Check if the button click event returned true, which means
  190. // we can close the action sheet
  191. if (opts.buttonClicked(index, opts.buttons[index]) === true) {
  192. scope.removeSheet();
  193. }
  194. };
  195. scope.destructiveButtonClicked = function() {
  196. // Check if the destructive button click event returned true, which means
  197. // we can close the action sheet
  198. if (opts.destructiveButtonClicked() === true) {
  199. scope.removeSheet();
  200. }
  201. };
  202. scope.showSheet();
  203. // Expose the scope on $ionicActionSheet's return value for the sake
  204. // of testing it.
  205. scope.cancel.$scope = scope;
  206. return scope.cancel;
  207. }
  208. }]);
  209. jqLite.prototype.addClass = function(cssClasses) {
  210. var x, y, cssClass, el, splitClasses, existingClasses;
  211. if (cssClasses && cssClasses != 'ng-scope' && cssClasses != 'ng-isolate-scope') {
  212. for (x = 0; x < this.length; x++) {
  213. el = this[x];
  214. if (el.setAttribute) {
  215. if (cssClasses.indexOf(' ') < 0 && el.classList.add) {
  216. el.classList.add(cssClasses);
  217. } else {
  218. existingClasses = (' ' + (el.getAttribute('class') || '') + ' ')
  219. .replace(/[\n\t]/g, " ");
  220. splitClasses = cssClasses.split(' ');
  221. for (y = 0; y < splitClasses.length; y++) {
  222. cssClass = splitClasses[y].trim();
  223. if (existingClasses.indexOf(' ' + cssClass + ' ') === -1) {
  224. existingClasses += cssClass + ' ';
  225. }
  226. }
  227. el.setAttribute('class', existingClasses.trim());
  228. }
  229. }
  230. }
  231. }
  232. return this;
  233. };
  234. jqLite.prototype.removeClass = function(cssClasses) {
  235. var x, y, splitClasses, cssClass, el;
  236. if (cssClasses) {
  237. for (x = 0; x < this.length; x++) {
  238. el = this[x];
  239. if (el.getAttribute) {
  240. if (cssClasses.indexOf(' ') < 0 && el.classList.remove) {
  241. el.classList.remove(cssClasses);
  242. } else {
  243. splitClasses = cssClasses.split(' ');
  244. for (y = 0; y < splitClasses.length; y++) {
  245. cssClass = splitClasses[y];
  246. el.setAttribute('class', (
  247. (" " + (el.getAttribute('class') || '') + " ")
  248. .replace(/[\n\t]/g, " ")
  249. .replace(" " + cssClass.trim() + " ", " ")).trim()
  250. );
  251. }
  252. }
  253. }
  254. }
  255. }
  256. return this;
  257. };
  258. /**
  259. * @ngdoc service
  260. * @name $ionicBackdrop
  261. * @module ionic
  262. * @description
  263. * Shows and hides a backdrop over the UI. Appears behind popups, loading,
  264. * and other overlays.
  265. *
  266. * Often, multiple UI components require a backdrop, but only one backdrop is
  267. * ever needed in the DOM at a time.
  268. *
  269. * Therefore, each component that requires the backdrop to be shown calls
  270. * `$ionicBackdrop.retain()` when it wants the backdrop, then `$ionicBackdrop.release()`
  271. * when it is done with the backdrop.
  272. *
  273. * For each time `retain` is called, the backdrop will be shown until `release` is called.
  274. *
  275. * For example, if `retain` is called three times, the backdrop will be shown until `release`
  276. * is called three times.
  277. *
  278. * **Notes:**
  279. * - The backdrop service will broadcast 'backdrop.shown' and 'backdrop.hidden' events from the root scope,
  280. * this is useful for alerting native components not in html.
  281. *
  282. * @usage
  283. *
  284. * ```js
  285. * function MyController($scope, $ionicBackdrop, $timeout, $rootScope) {
  286. * //Show a backdrop for one second
  287. * $scope.action = function() {
  288. * $ionicBackdrop.retain();
  289. * $timeout(function() {
  290. * $ionicBackdrop.release();
  291. * }, 1000);
  292. * };
  293. *
  294. * // Execute action on backdrop disappearing
  295. * $scope.$on('backdrop.hidden', function() {
  296. * // Execute action
  297. * });
  298. *
  299. * // Execute action on backdrop appearing
  300. * $scope.$on('backdrop.shown', function() {
  301. * // Execute action
  302. * });
  303. *
  304. * }
  305. * ```
  306. */
  307. IonicModule
  308. .factory('$ionicBackdrop', [
  309. '$document', '$timeout', '$$rAF', '$rootScope',
  310. function($document, $timeout, $$rAF, $rootScope) {
  311. var el = jqLite('<div class="backdrop">');
  312. var backdropHolds = 0;
  313. $document[0].body.appendChild(el[0]);
  314. return {
  315. /**
  316. * @ngdoc method
  317. * @name $ionicBackdrop#retain
  318. * @description Retains the backdrop.
  319. */
  320. retain: retain,
  321. /**
  322. * @ngdoc method
  323. * @name $ionicBackdrop#release
  324. * @description
  325. * Releases the backdrop.
  326. */
  327. release: release,
  328. getElement: getElement,
  329. // exposed for testing
  330. _element: el
  331. };
  332. function retain() {
  333. backdropHolds++;
  334. if (backdropHolds === 1) {
  335. el.addClass('visible');
  336. $rootScope.$broadcast('backdrop.shown');
  337. $$rAF(function() {
  338. // If we're still at >0 backdropHolds after async...
  339. if (backdropHolds >= 1) el.addClass('active');
  340. });
  341. }
  342. }
  343. function release() {
  344. if (backdropHolds === 1) {
  345. el.removeClass('active');
  346. $rootScope.$broadcast('backdrop.hidden');
  347. $timeout(function() {
  348. // If we're still at 0 backdropHolds after async...
  349. if (backdropHolds === 0) el.removeClass('visible');
  350. }, 400, false);
  351. }
  352. backdropHolds = Math.max(0, backdropHolds - 1);
  353. }
  354. function getElement() {
  355. return el;
  356. }
  357. }]);
  358. /**
  359. * @private
  360. */
  361. IonicModule
  362. .factory('$ionicBind', ['$parse', '$interpolate', function($parse, $interpolate) {
  363. var LOCAL_REGEXP = /^\s*([@=&])(\??)\s*(\w*)\s*$/;
  364. return function(scope, attrs, bindDefinition) {
  365. forEach(bindDefinition || {}, function(definition, scopeName) {
  366. //Adapted from angular.js $compile
  367. var match = definition.match(LOCAL_REGEXP) || [],
  368. attrName = match[3] || scopeName,
  369. mode = match[1], // @, =, or &
  370. parentGet,
  371. unwatch;
  372. switch (mode) {
  373. case '@':
  374. if (!attrs[attrName]) {
  375. return;
  376. }
  377. attrs.$observe(attrName, function(value) {
  378. scope[scopeName] = value;
  379. });
  380. // we trigger an interpolation to ensure
  381. // the value is there for use immediately
  382. if (attrs[attrName]) {
  383. scope[scopeName] = $interpolate(attrs[attrName])(scope);
  384. }
  385. break;
  386. case '=':
  387. if (!attrs[attrName]) {
  388. return;
  389. }
  390. unwatch = scope.$watch(attrs[attrName], function(value) {
  391. scope[scopeName] = value;
  392. });
  393. //Destroy parent scope watcher when this scope is destroyed
  394. scope.$on('$destroy', unwatch);
  395. break;
  396. case '&':
  397. /* jshint -W044 */
  398. if (attrs[attrName] && attrs[attrName].match(RegExp(scopeName + '\(.*?\)'))) {
  399. throw new Error('& expression binding "' + scopeName + '" looks like it will recursively call "' +
  400. attrs[attrName] + '" and cause a stack overflow! Please choose a different scopeName.');
  401. }
  402. parentGet = $parse(attrs[attrName]);
  403. scope[scopeName] = function(locals) {
  404. return parentGet(scope, locals);
  405. };
  406. break;
  407. }
  408. });
  409. };
  410. }]);
  411. /**
  412. * @ngdoc service
  413. * @name $ionicBody
  414. * @module ionic
  415. * @description An angular utility service to easily and efficiently
  416. * add and remove CSS classes from the document's body element.
  417. */
  418. IonicModule
  419. .factory('$ionicBody', ['$document', function($document) {
  420. return {
  421. /**
  422. * @ngdoc method
  423. * @name $ionicBody#addClass
  424. * @description Add a class to the document's body element.
  425. * @param {string} class Each argument will be added to the body element.
  426. * @returns {$ionicBody} The $ionicBody service so methods can be chained.
  427. */
  428. addClass: function() {
  429. for (var x = 0; x < arguments.length; x++) {
  430. $document[0].body.classList.add(arguments[x]);
  431. }
  432. return this;
  433. },
  434. /**
  435. * @ngdoc method
  436. * @name $ionicBody#removeClass
  437. * @description Remove a class from the document's body element.
  438. * @param {string} class Each argument will be removed from the body element.
  439. * @returns {$ionicBody} The $ionicBody service so methods can be chained.
  440. */
  441. removeClass: function() {
  442. for (var x = 0; x < arguments.length; x++) {
  443. $document[0].body.classList.remove(arguments[x]);
  444. }
  445. return this;
  446. },
  447. /**
  448. * @ngdoc method
  449. * @name $ionicBody#enableClass
  450. * @description Similar to the `add` method, except the first parameter accepts a boolean
  451. * value determining if the class should be added or removed. Rather than writing user code,
  452. * such as "if true then add the class, else then remove the class", this method can be
  453. * given a true or false value which reduces redundant code.
  454. * @param {boolean} shouldEnableClass A true/false value if the class should be added or removed.
  455. * @param {string} class Each remaining argument would be added or removed depending on
  456. * the first argument.
  457. * @returns {$ionicBody} The $ionicBody service so methods can be chained.
  458. */
  459. enableClass: function(shouldEnableClass) {
  460. var args = Array.prototype.slice.call(arguments).slice(1);
  461. if (shouldEnableClass) {
  462. this.addClass.apply(this, args);
  463. } else {
  464. this.removeClass.apply(this, args);
  465. }
  466. return this;
  467. },
  468. /**
  469. * @ngdoc method
  470. * @name $ionicBody#append
  471. * @description Append a child to the document's body.
  472. * @param {element} element The element to be appended to the body. The passed in element
  473. * can be either a jqLite element, or a DOM element.
  474. * @returns {$ionicBody} The $ionicBody service so methods can be chained.
  475. */
  476. append: function(ele) {
  477. $document[0].body.appendChild(ele.length ? ele[0] : ele);
  478. return this;
  479. },
  480. /**
  481. * @ngdoc method
  482. * @name $ionicBody#get
  483. * @description Get the document's body element.
  484. * @returns {element} Returns the document's body element.
  485. */
  486. get: function() {
  487. return $document[0].body;
  488. }
  489. };
  490. }]);
  491. IonicModule
  492. .factory('$ionicClickBlock', [
  493. '$document',
  494. '$ionicBody',
  495. '$timeout',
  496. function($document, $ionicBody, $timeout) {
  497. var CSS_HIDE = 'click-block-hide';
  498. var cbEle, fallbackTimer, pendingShow;
  499. function preventClick(ev) {
  500. ev.preventDefault();
  501. ev.stopPropagation();
  502. }
  503. function addClickBlock() {
  504. if (pendingShow) {
  505. if (cbEle) {
  506. cbEle.classList.remove(CSS_HIDE);
  507. } else {
  508. cbEle = $document[0].createElement('div');
  509. cbEle.className = 'click-block';
  510. $ionicBody.append(cbEle);
  511. cbEle.addEventListener('touchstart', preventClick);
  512. cbEle.addEventListener('mousedown', preventClick);
  513. }
  514. pendingShow = false;
  515. }
  516. }
  517. function removeClickBlock() {
  518. cbEle && cbEle.classList.add(CSS_HIDE);
  519. }
  520. return {
  521. show: function(autoExpire) {
  522. pendingShow = true;
  523. $timeout.cancel(fallbackTimer);
  524. fallbackTimer = $timeout(this.hide, autoExpire || 310, false);
  525. addClickBlock();
  526. },
  527. hide: function() {
  528. pendingShow = false;
  529. $timeout.cancel(fallbackTimer);
  530. removeClickBlock();
  531. }
  532. };
  533. }]);
  534. /**
  535. * @ngdoc service
  536. * @name $ionicGesture
  537. * @module ionic
  538. * @description An angular service exposing ionic
  539. * {@link ionic.utility:ionic.EventController}'s gestures.
  540. */
  541. IonicModule
  542. .factory('$ionicGesture', [function() {
  543. return {
  544. /**
  545. * @ngdoc method
  546. * @name $ionicGesture#on
  547. * @description Add an event listener for a gesture on an element. See {@link ionic.utility:ionic.EventController#onGesture}.
  548. * @param {string} eventType The gesture event to listen for.
  549. * @param {function(e)} callback The function to call when the gesture
  550. * happens.
  551. * @param {element} $element The angular element to listen for the event on.
  552. * @param {object} options object.
  553. * @returns {ionic.Gesture} The gesture object (use this to remove the gesture later on).
  554. */
  555. on: function(eventType, cb, $element, options) {
  556. return window.ionic.onGesture(eventType, cb, $element[0], options);
  557. },
  558. /**
  559. * @ngdoc method
  560. * @name $ionicGesture#off
  561. * @description Remove an event listener for a gesture on an element. See {@link ionic.utility:ionic.EventController#offGesture}.
  562. * @param {ionic.Gesture} gesture The gesture that should be removed.
  563. * @param {string} eventType The gesture event to remove the listener for.
  564. * @param {function(e)} callback The listener to remove.
  565. */
  566. off: function(gesture, eventType, cb) {
  567. return window.ionic.offGesture(gesture, eventType, cb);
  568. }
  569. };
  570. }]);
  571. /**
  572. * @ngdoc service
  573. * @name $ionicHistory
  574. * @module ionic
  575. * @description
  576. * $ionicHistory keeps track of views as the user navigates through an app. Similar to the way a
  577. * browser behaves, an Ionic app is able to keep track of the previous view, the current view, and
  578. * the forward view (if there is one). However, a typical web browser only keeps track of one
  579. * history stack in a linear fashion.
  580. *
  581. * Unlike a traditional browser environment, apps and webapps have parallel independent histories,
  582. * such as with tabs. Should a user navigate few pages deep on one tab, and then switch to a new
  583. * tab and back, the back button relates not to the previous tab, but to the previous pages
  584. * visited within _that_ tab.
  585. *
  586. * `$ionicHistory` facilitates this parallel history architecture.
  587. */
  588. IonicModule
  589. .factory('$ionicHistory', [
  590. '$rootScope',
  591. '$state',
  592. '$location',
  593. '$window',
  594. '$timeout',
  595. '$ionicViewSwitcher',
  596. '$ionicNavViewDelegate',
  597. function($rootScope, $state, $location, $window, $timeout, $ionicViewSwitcher, $ionicNavViewDelegate) {
  598. // history actions while navigating views
  599. var ACTION_INITIAL_VIEW = 'initialView';
  600. var ACTION_NEW_VIEW = 'newView';
  601. var ACTION_MOVE_BACK = 'moveBack';
  602. var ACTION_MOVE_FORWARD = 'moveForward';
  603. // direction of navigation
  604. var DIRECTION_BACK = 'back';
  605. var DIRECTION_FORWARD = 'forward';
  606. var DIRECTION_ENTER = 'enter';
  607. var DIRECTION_EXIT = 'exit';
  608. var DIRECTION_SWAP = 'swap';
  609. var DIRECTION_NONE = 'none';
  610. var stateChangeCounter = 0;
  611. var lastStateId, nextViewOptions, deregisterStateChangeListener, nextViewExpireTimer, forcedNav;
  612. var viewHistory = {
  613. histories: { root: { historyId: 'root', parentHistoryId: null, stack: [], cursor: -1 } },
  614. views: {},
  615. backView: null,
  616. forwardView: null,
  617. currentView: null
  618. };
  619. var View = function() {};
  620. View.prototype.initialize = function(data) {
  621. if (data) {
  622. for (var name in data) this[name] = data[name];
  623. return this;
  624. }
  625. return null;
  626. };
  627. View.prototype.go = function() {
  628. if (this.stateName) {
  629. return $state.go(this.stateName, this.stateParams);
  630. }
  631. if (this.url && this.url !== $location.url()) {
  632. if (viewHistory.backView === this) {
  633. return $window.history.go(-1);
  634. } else if (viewHistory.forwardView === this) {
  635. return $window.history.go(1);
  636. }
  637. $location.url(this.url);
  638. }
  639. return null;
  640. };
  641. View.prototype.destroy = function() {
  642. if (this.scope) {
  643. this.scope.$destroy && this.scope.$destroy();
  644. this.scope = null;
  645. }
  646. };
  647. function getViewById(viewId) {
  648. return (viewId ? viewHistory.views[ viewId ] : null);
  649. }
  650. function getBackView(view) {
  651. return (view ? getViewById(view.backViewId) : null);
  652. }
  653. function getForwardView(view) {
  654. return (view ? getViewById(view.forwardViewId) : null);
  655. }
  656. function getHistoryById(historyId) {
  657. return (historyId ? viewHistory.histories[ historyId ] : null);
  658. }
  659. function getHistory(scope) {
  660. var histObj = getParentHistoryObj(scope);
  661. if (!viewHistory.histories[ histObj.historyId ]) {
  662. // this history object exists in parent scope, but doesn't
  663. // exist in the history data yet
  664. viewHistory.histories[ histObj.historyId ] = {
  665. historyId: histObj.historyId,
  666. parentHistoryId: getParentHistoryObj(histObj.scope.$parent).historyId,
  667. stack: [],
  668. cursor: -1
  669. };
  670. }
  671. return getHistoryById(histObj.historyId);
  672. }
  673. function getParentHistoryObj(scope) {
  674. var parentScope = scope;
  675. while (parentScope) {
  676. if (parentScope.hasOwnProperty('$historyId')) {
  677. // this parent scope has a historyId
  678. return { historyId: parentScope.$historyId, scope: parentScope };
  679. }
  680. // nothing found keep climbing up
  681. parentScope = parentScope.$parent;
  682. }
  683. // no history for the parent, use the root
  684. return { historyId: 'root', scope: $rootScope };
  685. }
  686. function setNavViews(viewId) {
  687. viewHistory.currentView = getViewById(viewId);
  688. viewHistory.backView = getBackView(viewHistory.currentView);
  689. viewHistory.forwardView = getForwardView(viewHistory.currentView);
  690. }
  691. function getCurrentStateId() {
  692. var id;
  693. if ($state && $state.current && $state.current.name) {
  694. id = $state.current.name;
  695. if ($state.params) {
  696. for (var key in $state.params) {
  697. if ($state.params.hasOwnProperty(key) && $state.params[key]) {
  698. id += "_" + key + "=" + $state.params[key];
  699. }
  700. }
  701. }
  702. return id;
  703. }
  704. // if something goes wrong make sure its got a unique stateId
  705. return ionic.Utils.nextUid();
  706. }
  707. function getCurrentStateParams() {
  708. var rtn;
  709. if ($state && $state.params) {
  710. for (var key in $state.params) {
  711. if ($state.params.hasOwnProperty(key)) {
  712. rtn = rtn || {};
  713. rtn[key] = $state.params[key];
  714. }
  715. }
  716. }
  717. return rtn;
  718. }
  719. return {
  720. register: function(parentScope, viewLocals) {
  721. var currentStateId = getCurrentStateId(),
  722. hist = getHistory(parentScope),
  723. currentView = viewHistory.currentView,
  724. backView = viewHistory.backView,
  725. forwardView = viewHistory.forwardView,
  726. viewId = null,
  727. action = null,
  728. direction = DIRECTION_NONE,
  729. historyId = hist.historyId,
  730. url = $location.url(),
  731. tmp, x, ele;
  732. if (lastStateId !== currentStateId) {
  733. lastStateId = currentStateId;
  734. stateChangeCounter++;
  735. }
  736. if (forcedNav) {
  737. // we've previously set exactly what to do
  738. viewId = forcedNav.viewId;
  739. action = forcedNav.action;
  740. direction = forcedNav.direction;
  741. forcedNav = null;
  742. } else if (backView && backView.stateId === currentStateId) {
  743. // they went back one, set the old current view as a forward view
  744. viewId = backView.viewId;
  745. historyId = backView.historyId;
  746. action = ACTION_MOVE_BACK;
  747. if (backView.historyId === currentView.historyId) {
  748. // went back in the same history
  749. direction = DIRECTION_BACK;
  750. } else if (currentView) {
  751. direction = DIRECTION_EXIT;
  752. tmp = getHistoryById(backView.historyId);
  753. if (tmp && tmp.parentHistoryId === currentView.historyId) {
  754. direction = DIRECTION_ENTER;
  755. } else {
  756. tmp = getHistoryById(currentView.historyId);
  757. if (tmp && tmp.parentHistoryId === hist.parentHistoryId) {
  758. direction = DIRECTION_SWAP;
  759. }
  760. }
  761. }
  762. } else if (forwardView && forwardView.stateId === currentStateId) {
  763. // they went to the forward one, set the forward view to no longer a forward view
  764. viewId = forwardView.viewId;
  765. historyId = forwardView.historyId;
  766. action = ACTION_MOVE_FORWARD;
  767. if (forwardView.historyId === currentView.historyId) {
  768. direction = DIRECTION_FORWARD;
  769. } else if (currentView) {
  770. direction = DIRECTION_EXIT;
  771. if (currentView.historyId === hist.parentHistoryId) {
  772. direction = DIRECTION_ENTER;
  773. } else {
  774. tmp = getHistoryById(currentView.historyId);
  775. if (tmp && tmp.parentHistoryId === hist.parentHistoryId) {
  776. direction = DIRECTION_SWAP;
  777. }
  778. }
  779. }
  780. tmp = getParentHistoryObj(parentScope);
  781. if (forwardView.historyId && tmp.scope) {
  782. // if a history has already been created by the forward view then make sure it stays the same
  783. tmp.scope.$historyId = forwardView.historyId;
  784. historyId = forwardView.historyId;
  785. }
  786. } else if (currentView && currentView.historyId !== historyId &&
  787. hist.cursor > -1 && hist.stack.length > 0 && hist.cursor < hist.stack.length &&
  788. hist.stack[hist.cursor].stateId === currentStateId) {
  789. // they just changed to a different history and the history already has views in it
  790. var switchToView = hist.stack[hist.cursor];
  791. viewId = switchToView.viewId;
  792. historyId = switchToView.historyId;
  793. action = ACTION_MOVE_BACK;
  794. direction = DIRECTION_SWAP;
  795. tmp = getHistoryById(currentView.historyId);
  796. if (tmp && tmp.parentHistoryId === historyId) {
  797. direction = DIRECTION_EXIT;
  798. } else {
  799. tmp = getHistoryById(historyId);
  800. if (tmp && tmp.parentHistoryId === currentView.historyId) {
  801. direction = DIRECTION_ENTER;
  802. }
  803. }
  804. // if switching to a different history, and the history of the view we're switching
  805. // to has an existing back view from a different history than itself, then
  806. // it's back view would be better represented using the current view as its back view
  807. tmp = getViewById(switchToView.backViewId);
  808. if (tmp && switchToView.historyId !== tmp.historyId) {
  809. // the new view is being removed from it's old position in the history and being placed at the top,
  810. // so we need to update any views that reference it as a backview, otherwise there will be infinitely loops
  811. var viewIds = Object.keys(viewHistory.views);
  812. viewIds.forEach(function(viewId) {
  813. var view = viewHistory.views[viewId];
  814. if ( view.backViewId === switchToView.viewId ) {
  815. view.backViewId = null;
  816. }
  817. });
  818. hist.stack[hist.cursor].backViewId = currentView.viewId;
  819. }
  820. } else {
  821. // create an element from the viewLocals template
  822. ele = $ionicViewSwitcher.createViewEle(viewLocals);
  823. if (this.isAbstractEle(ele, viewLocals)) {
  824. return {
  825. action: 'abstractView',
  826. direction: DIRECTION_NONE,
  827. ele: ele
  828. };
  829. }
  830. // set a new unique viewId
  831. viewId = ionic.Utils.nextUid();
  832. if (currentView) {
  833. // set the forward view if there is a current view (ie: if its not the first view)
  834. currentView.forwardViewId = viewId;
  835. action = ACTION_NEW_VIEW;
  836. // check if there is a new forward view within the same history
  837. if (forwardView && currentView.stateId !== forwardView.stateId &&
  838. currentView.historyId === forwardView.historyId) {
  839. // they navigated to a new view but the stack already has a forward view
  840. // since its a new view remove any forwards that existed
  841. tmp = getHistoryById(forwardView.historyId);
  842. if (tmp) {
  843. // the forward has a history
  844. for (x = tmp.stack.length - 1; x >= forwardView.index; x--) {
  845. // starting from the end destroy all forwards in this history from this point
  846. var stackItem = tmp.stack[x];
  847. stackItem && stackItem.destroy && stackItem.destroy();
  848. tmp.stack.splice(x);
  849. }
  850. historyId = forwardView.historyId;
  851. }
  852. }
  853. // its only moving forward if its in the same history
  854. if (hist.historyId === currentView.historyId) {
  855. direction = DIRECTION_FORWARD;
  856. } else if (currentView.historyId !== hist.historyId) {
  857. // DB: this is a new view in a different tab
  858. direction = DIRECTION_ENTER;
  859. tmp = getHistoryById(currentView.historyId);
  860. if (tmp && tmp.parentHistoryId === hist.parentHistoryId) {
  861. direction = DIRECTION_SWAP;
  862. } else {
  863. tmp = getHistoryById(tmp.parentHistoryId);
  864. if (tmp && tmp.historyId === hist.historyId) {
  865. direction = DIRECTION_EXIT;
  866. }
  867. }
  868. }
  869. } else {
  870. // there's no current view, so this must be the initial view
  871. action = ACTION_INITIAL_VIEW;
  872. }
  873. if (stateChangeCounter < 2) {
  874. // views that were spun up on the first load should not animate
  875. direction = DIRECTION_NONE;
  876. }
  877. // add the new view
  878. viewHistory.views[viewId] = this.createView({
  879. viewId: viewId,
  880. index: hist.stack.length,
  881. historyId: hist.historyId,
  882. backViewId: (currentView && currentView.viewId ? currentView.viewId : null),
  883. forwardViewId: null,
  884. stateId: currentStateId,
  885. stateName: this.currentStateName(),
  886. stateParams: getCurrentStateParams(),
  887. url: url,
  888. canSwipeBack: canSwipeBack(ele, viewLocals)
  889. });
  890. // add the new view to this history's stack
  891. hist.stack.push(viewHistory.views[viewId]);
  892. }
  893. deregisterStateChangeListener && deregisterStateChangeListener();
  894. $timeout.cancel(nextViewExpireTimer);
  895. if (nextViewOptions) {
  896. if (nextViewOptions.disableAnimate) direction = DIRECTION_NONE;
  897. if (nextViewOptions.disableBack) viewHistory.views[viewId].backViewId = null;
  898. if (nextViewOptions.historyRoot) {
  899. for (x = 0; x < hist.stack.length; x++) {
  900. if (hist.stack[x].viewId === viewId) {
  901. hist.stack[x].index = 0;
  902. hist.stack[x].backViewId = hist.stack[x].forwardViewId = null;
  903. } else {
  904. delete viewHistory.views[hist.stack[x].viewId];
  905. }
  906. }
  907. hist.stack = [viewHistory.views[viewId]];
  908. }
  909. nextViewOptions = null;
  910. }
  911. setNavViews(viewId);
  912. if (viewHistory.backView && historyId == viewHistory.backView.historyId && currentStateId == viewHistory.backView.stateId && url == viewHistory.backView.url) {
  913. for (x = 0; x < hist.stack.length; x++) {
  914. if (hist.stack[x].viewId == viewId) {
  915. action = 'dupNav';
  916. direction = DIRECTION_NONE;
  917. if (x > 0) {
  918. hist.stack[x - 1].forwardViewId = null;
  919. }
  920. viewHistory.forwardView = null;
  921. viewHistory.currentView.index = viewHistory.backView.index;
  922. viewHistory.currentView.backViewId = viewHistory.backView.backViewId;
  923. viewHistory.backView = getBackView(viewHistory.backView);
  924. hist.stack.splice(x, 1);
  925. break;
  926. }
  927. }
  928. }
  929. hist.cursor = viewHistory.currentView.index;
  930. return {
  931. viewId: viewId,
  932. action: action,
  933. direction: direction,
  934. historyId: historyId,
  935. enableBack: this.enabledBack(viewHistory.currentView),
  936. isHistoryRoot: (viewHistory.currentView.index === 0),
  937. ele: ele
  938. };
  939. },
  940. registerHistory: function(scope) {
  941. scope.$historyId = ionic.Utils.nextUid();
  942. },
  943. createView: function(data) {
  944. var newView = new View();
  945. return newView.initialize(data);
  946. },
  947. getViewById: getViewById,
  948. /**
  949. * @ngdoc method
  950. * @name $ionicHistory#viewHistory
  951. * @description The app's view history data, such as all the views and histories, along
  952. * with how they are ordered and linked together within the navigation stack.
  953. * @returns {object} Returns an object containing the apps view history data.
  954. */
  955. viewHistory: function() {
  956. return viewHistory;
  957. },
  958. /**
  959. * @ngdoc method
  960. * @name $ionicHistory#currentView
  961. * @description The app's current view.
  962. * @returns {object} Returns the current view.
  963. */
  964. currentView: function(view) {
  965. if (arguments.length) {
  966. viewHistory.currentView = view;
  967. }
  968. return viewHistory.currentView;
  969. },
  970. /**
  971. * @ngdoc method
  972. * @name $ionicHistory#currentHistoryId
  973. * @description The ID of the history stack which is the parent container of the current view.
  974. * @returns {string} Returns the current history ID.
  975. */
  976. currentHistoryId: function() {
  977. return viewHistory.currentView ? viewHistory.currentView.historyId : null;
  978. },
  979. /**
  980. * @ngdoc method
  981. * @name $ionicHistory#currentTitle
  982. * @description Gets and sets the current view's title.
  983. * @param {string=} val The title to update the current view with.
  984. * @returns {string} Returns the current view's title.
  985. */
  986. currentTitle: function(val) {
  987. if (viewHistory.currentView) {
  988. if (arguments.length) {
  989. viewHistory.currentView.title = val;
  990. }
  991. return viewHistory.currentView.title;
  992. }
  993. },
  994. /**
  995. * @ngdoc method
  996. * @name $ionicHistory#backView
  997. * @description Returns the view that was before the current view in the history stack.
  998. * If the user navigated from View A to View B, then View A would be the back view, and
  999. * View B would be the current view.
  1000. * @returns {object} Returns the back view.
  1001. */
  1002. backView: function(view) {
  1003. if (arguments.length) {
  1004. viewHistory.backView = view;
  1005. }
  1006. return viewHistory.backView;
  1007. },
  1008. /**
  1009. * @ngdoc method
  1010. * @name $ionicHistory#backTitle
  1011. * @description Gets the back view's title.
  1012. * @returns {string} Returns the back view's title.
  1013. */
  1014. backTitle: function(view) {
  1015. var backView = (view && getViewById(view.backViewId)) || viewHistory.backView;
  1016. return backView && backView.title;
  1017. },
  1018. /**
  1019. * @ngdoc method
  1020. * @name $ionicHistory#forwardView
  1021. * @description Returns the view that was in front of the current view in the history stack.
  1022. * A forward view would exist if the user navigated from View A to View B, then
  1023. * navigated back to View A. At this point then View B would be the forward view, and View
  1024. * A would be the current view.
  1025. * @returns {object} Returns the forward view.
  1026. */
  1027. forwardView: function(view) {
  1028. if (arguments.length) {
  1029. viewHistory.forwardView = view;
  1030. }
  1031. return viewHistory.forwardView;
  1032. },
  1033. /**
  1034. * @ngdoc method
  1035. * @name $ionicHistory#currentStateName
  1036. * @description Returns the current state name.
  1037. * @returns {string}
  1038. */
  1039. currentStateName: function() {
  1040. return ($state && $state.current ? $state.current.name : null);
  1041. },
  1042. isCurrentStateNavView: function(navView) {
  1043. return !!($state && $state.current && $state.current.views && $state.current.views[navView]);
  1044. },
  1045. goToHistoryRoot: function(historyId) {
  1046. if (historyId) {
  1047. var hist = getHistoryById(historyId);
  1048. if (hist && hist.stack.length) {
  1049. if (viewHistory.currentView && viewHistory.currentView.viewId === hist.stack[0].viewId) {
  1050. return;
  1051. }
  1052. forcedNav = {
  1053. viewId: hist.stack[0].viewId,
  1054. action: ACTION_MOVE_BACK,
  1055. direction: DIRECTION_BACK
  1056. };
  1057. hist.stack[0].go();
  1058. }
  1059. }
  1060. },
  1061. /**
  1062. * @ngdoc method
  1063. * @name $ionicHistory#goBack
  1064. * @param {number=} backCount Optional negative integer setting how many views to go
  1065. * back. By default it'll go back one view by using the value `-1`. To go back two
  1066. * views you would use `-2`. If the number goes farther back than the number of views
  1067. * in the current history's stack then it'll go to the first view in the current history's
  1068. * stack. If the number is zero or greater then it'll do nothing. It also does not
  1069. * cross history stacks, meaning it can only go as far back as the current history.
  1070. * @description Navigates the app to the back view, if a back view exists.
  1071. */
  1072. goBack: function(backCount) {
  1073. if (isDefined(backCount) && backCount !== -1) {
  1074. if (backCount > -1) return;
  1075. var currentHistory = viewHistory.histories[this.currentHistoryId()];
  1076. var newCursor = currentHistory.cursor + backCount + 1;
  1077. if (newCursor < 1) {
  1078. newCursor = 1;
  1079. }
  1080. currentHistory.cursor = newCursor;
  1081. setNavViews(currentHistory.stack[newCursor].viewId);
  1082. var cursor = newCursor - 1;
  1083. var clearStateIds = [];
  1084. var fwdView = getViewById(currentHistory.stack[cursor].forwardViewId);
  1085. while (fwdView) {
  1086. clearStateIds.push(fwdView.stateId || fwdView.viewId);
  1087. cursor++;
  1088. if (cursor >= currentHistory.stack.length) break;
  1089. fwdView = getViewById(currentHistory.stack[cursor].forwardViewId);
  1090. }
  1091. var self = this;
  1092. if (clearStateIds.length) {
  1093. $timeout(function() {
  1094. self.clearCache(clearStateIds);
  1095. }, 300);
  1096. }
  1097. }
  1098. viewHistory.backView && viewHistory.backView.go();
  1099. },
  1100. /**
  1101. * @ngdoc method
  1102. * @name $ionicHistory#removeBackView
  1103. * @description Remove the previous view from the history completely, including the
  1104. * cached element and scope (if they exist).
  1105. */
  1106. removeBackView: function() {
  1107. var self = this;
  1108. var currentHistory = viewHistory.histories[this.currentHistoryId()];
  1109. var currentCursor = currentHistory.cursor;
  1110. var currentView = currentHistory.stack[currentCursor];
  1111. var backView = currentHistory.stack[currentCursor - 1];
  1112. var replacementView = currentHistory.stack[currentCursor - 2];
  1113. // fail if we dont have enough views in the history
  1114. if (!backView || !replacementView) {
  1115. return;
  1116. }
  1117. // remove the old backView and the cached element/scope
  1118. currentHistory.stack.splice(currentCursor - 1, 1);
  1119. self.clearCache([backView.viewId]);
  1120. // make the replacementView and currentView point to each other (bypass the old backView)
  1121. currentView.backViewId = replacementView.viewId;
  1122. currentView.index = currentView.index - 1;
  1123. replacementView.forwardViewId = currentView.viewId;
  1124. // update the cursor and set new backView
  1125. viewHistory.backView = replacementView;
  1126. currentHistory.currentCursor += -1;
  1127. },
  1128. enabledBack: function(view) {
  1129. var backView = getBackView(view);
  1130. return !!(backView && backView.historyId === view.historyId);
  1131. },
  1132. /**
  1133. * @ngdoc method
  1134. * @name $ionicHistory#clearHistory
  1135. * @description Clears out the app's entire history, except for the current view.
  1136. */
  1137. clearHistory: function() {
  1138. var
  1139. histories = viewHistory.histories,
  1140. currentView = viewHistory.currentView;
  1141. if (histories) {
  1142. for (var historyId in histories) {
  1143. if (histories[historyId].stack) {
  1144. histories[historyId].stack = [];
  1145. histories[historyId].cursor = -1;
  1146. }
  1147. if (currentView && currentView.historyId === historyId) {
  1148. currentView.backViewId = currentView.forwardViewId = null;
  1149. histories[historyId].stack.push(currentView);
  1150. } else if (histories[historyId].destroy) {
  1151. histories[historyId].destroy();
  1152. }
  1153. }
  1154. }
  1155. for (var viewId in viewHistory.views) {
  1156. if (viewId !== currentView.viewId) {
  1157. delete viewHistory.views[viewId];
  1158. }
  1159. }
  1160. if (currentView) {
  1161. setNavViews(currentView.viewId);
  1162. }
  1163. },
  1164. /**
  1165. * @ngdoc method
  1166. * @name $ionicHistory#clearCache
  1167. * @return promise
  1168. * @description Removes all cached views within every {@link ionic.directive:ionNavView}.
  1169. * This both removes the view element from the DOM, and destroy it's scope.
  1170. */
  1171. clearCache: function(stateIds) {
  1172. return $timeout(function() {
  1173. $ionicNavViewDelegate._instances.forEach(function(instance) {
  1174. instance.clearCache(stateIds);
  1175. });
  1176. });
  1177. },
  1178. /**
  1179. * @ngdoc method
  1180. * @name $ionicHistory#nextViewOptions
  1181. * @description Sets options for the next view. This method can be useful to override
  1182. * certain view/transition defaults right before a view transition happens. For example,
  1183. * the {@link ionic.directive:menuClose} directive uses this method internally to ensure
  1184. * an animated view transition does not happen when a side menu is open, and also sets
  1185. * the next view as the root of its history stack. After the transition these options
  1186. * are set back to null.
  1187. *
  1188. * Available options:
  1189. *
  1190. * * `disableAnimate`: Do not animate the next transition.
  1191. * * `disableBack`: The next view should forget its back view, and set it to null.
  1192. * * `historyRoot`: The next view should become the root view in its history stack.
  1193. *
  1194. * ```js
  1195. * $ionicHistory.nextViewOptions({
  1196. * disableAnimate: true,
  1197. * disableBack: true
  1198. * });
  1199. * ```
  1200. */
  1201. nextViewOptions: function(opts) {
  1202. deregisterStateChangeListener && deregisterStateChangeListener();
  1203. if (arguments.length) {
  1204. $timeout.cancel(nextViewExpireTimer);
  1205. if (opts === null) {
  1206. nextViewOptions = opts;
  1207. } else {
  1208. nextViewOptions = nextViewOptions || {};
  1209. extend(nextViewOptions, opts);
  1210. if (nextViewOptions.expire) {
  1211. deregisterStateChangeListener = $rootScope.$on('$stateChangeSuccess', function() {
  1212. nextViewExpireTimer = $timeout(function() {
  1213. nextViewOptions = null;
  1214. }, nextViewOptions.expire);
  1215. });
  1216. }
  1217. }
  1218. }
  1219. return nextViewOptions;
  1220. },
  1221. isAbstractEle: function(ele, viewLocals) {
  1222. if (viewLocals && viewLocals.$$state && viewLocals.$$state.self['abstract']) {
  1223. return true;
  1224. }
  1225. return !!(ele && (isAbstractTag(ele) || isAbstractTag(ele.children())));
  1226. },
  1227. isActiveScope: function(scope) {
  1228. if (!scope) return false;
  1229. var climbScope = scope;
  1230. var currentHistoryId = this.currentHistoryId();
  1231. var foundHistoryId;
  1232. while (climbScope) {
  1233. if (climbScope.$$disconnected) {
  1234. return false;
  1235. }
  1236. if (!foundHistoryId && climbScope.hasOwnProperty('$historyId')) {
  1237. foundHistoryId = true;
  1238. }
  1239. if (currentHistoryId) {
  1240. if (climbScope.hasOwnProperty('$historyId') && currentHistoryId == climbScope.$historyId) {
  1241. return true;
  1242. }
  1243. if (climbScope.hasOwnProperty('$activeHistoryId')) {
  1244. if (currentHistoryId == climbScope.$activeHistoryId) {
  1245. if (climbScope.hasOwnProperty('$historyId')) {
  1246. return true;
  1247. }
  1248. if (!foundHistoryId) {
  1249. return true;
  1250. }
  1251. }
  1252. }
  1253. }
  1254. if (foundHistoryId && climbScope.hasOwnProperty('$activeHistoryId')) {
  1255. foundHistoryId = false;
  1256. }
  1257. climbScope = climbScope.$parent;
  1258. }
  1259. return currentHistoryId ? currentHistoryId == 'root' : true;
  1260. }
  1261. };
  1262. function isAbstractTag(ele) {
  1263. return ele && ele.length && /ion-side-menus|ion-tabs/i.test(ele[0].tagName);
  1264. }
  1265. function canSwipeBack(ele, viewLocals) {
  1266. if (viewLocals && viewLocals.$$state && viewLocals.$$state.self.canSwipeBack === false) {
  1267. return false;
  1268. }
  1269. if (ele && ele.attr('can-swipe-back') === 'false') {
  1270. return false;
  1271. }
  1272. var eleChild = ele.find('ion-view');
  1273. if (eleChild && eleChild.attr('can-swipe-back') === 'false') {
  1274. return false;
  1275. }
  1276. return true;
  1277. }
  1278. }])
  1279. .run([
  1280. '$rootScope',
  1281. '$state',
  1282. '$location',
  1283. '$document',
  1284. '$ionicPlatform',
  1285. '$ionicHistory',
  1286. 'IONIC_BACK_PRIORITY',
  1287. function($rootScope, $state, $location, $document, $ionicPlatform, $ionicHistory, IONIC_BACK_PRIORITY) {
  1288. // always reset the keyboard state when change stage
  1289. $rootScope.$on('$ionicView.beforeEnter', function() {
  1290. ionic.keyboard && ionic.keyboard.hide && ionic.keyboard.hide();
  1291. });
  1292. $rootScope.$on('$ionicHistory.change', function(e, data) {
  1293. if (!data) return null;
  1294. var viewHistory = $ionicHistory.viewHistory();
  1295. var hist = (data.historyId ? viewHistory.histories[ data.historyId ] : null);
  1296. if (hist && hist.cursor > -1 && hist.cursor < hist.stack.length) {
  1297. // the history they're going to already exists
  1298. // go to it's last view in its stack
  1299. var view = hist.stack[ hist.cursor ];
  1300. return view.go(data);
  1301. }
  1302. // this history does not have a URL, but it does have a uiSref
  1303. // figure out its URL from the uiSref
  1304. if (!data.url && data.uiSref) {
  1305. data.url = $state.href(data.uiSref);
  1306. }
  1307. if (data.url) {
  1308. // don't let it start with a #, messes with $location.url()
  1309. if (data.url.indexOf('#') === 0) {
  1310. data.url = data.url.replace('#', '');
  1311. }
  1312. if (data.url !== $location.url()) {
  1313. // we've got a good URL, ready GO!
  1314. $location.url(data.url);
  1315. }
  1316. }
  1317. });
  1318. $rootScope.$ionicGoBack = function(backCount) {
  1319. $ionicHistory.goBack(backCount);
  1320. };
  1321. // Set the document title when a new view is shown
  1322. $rootScope.$on('$ionicView.afterEnter', function(ev, data) {
  1323. if (data && data.title) {
  1324. $document[0].title = data.title;
  1325. }
  1326. });
  1327. // Triggered when devices with a hardware back button (Android) is clicked by the user
  1328. // This is a Cordova/Phonegap platform specifc method
  1329. function onHardwareBackButton(e) {
  1330. var backView = $ionicHistory.backView();
  1331. if (backView) {
  1332. // there is a back view, go to it
  1333. backView.go();
  1334. } else {
  1335. // there is no back view, so close the app instead
  1336. ionic.Platform.exitApp();
  1337. }
  1338. e.preventDefault();
  1339. return false;
  1340. }
  1341. $ionicPlatform.registerBackButtonAction(
  1342. onHardwareBackButton,
  1343. IONIC_BACK_PRIORITY.view
  1344. );
  1345. }]);
  1346. /**
  1347. * @ngdoc provider
  1348. * @name $ionicConfigProvider
  1349. * @module ionic
  1350. * @description
  1351. * Ionic automatically takes platform configurations into account to adjust things like what
  1352. * transition style to use and whether tab icons should show on the top or bottom. For example,
  1353. * iOS will move forward by transitioning the entering view from right to center and the leaving
  1354. * view from center to left. However, Android will transition with the entering view going from
  1355. * bottom to center, covering the previous view, which remains stationary. It should be noted
  1356. * that when a platform is not iOS or Android, then it'll default to iOS. So if you are
  1357. * developing on a desktop browser, it's going to take on iOS default configs.
  1358. *
  1359. * These configs can be changed using the `$ionicConfigProvider` during the configuration phase
  1360. * of your app. Additionally, `$ionicConfig` can also set and get config values during the run
  1361. * phase and within the app itself.
  1362. *
  1363. * By default, all base config variables are set to `'platform'`, which means it'll take on the
  1364. * default config of the platform on which it's running. Config variables can be set at this
  1365. * level so all platforms follow the same setting, rather than its platform config.
  1366. * The following code would set the same config variable for all platforms:
  1367. *
  1368. * ```js
  1369. * $ionicConfigProvider.views.maxCache(10);
  1370. * ```
  1371. *
  1372. * Additionally, each platform can have it's own config within the `$ionicConfigProvider.platform`
  1373. * property. The config below would only apply to Android devices.
  1374. *
  1375. * ```js
  1376. * $ionicConfigProvider.platform.android.views.maxCache(5);
  1377. * ```
  1378. *
  1379. * @usage
  1380. * ```js
  1381. * var myApp = angular.module('reallyCoolApp', ['ionic']);
  1382. *
  1383. * myApp.config(function($ionicConfigProvider) {
  1384. * $ionicConfigProvider.views.maxCache(5);
  1385. *
  1386. * // note that you can also chain configs
  1387. * $ionicConfigProvider.backButton.text('Go Back').icon('ion-chevron-left');
  1388. * });
  1389. * ```
  1390. */
  1391. /**
  1392. * @ngdoc method
  1393. * @name $ionicConfigProvider#views.transition
  1394. * @description Animation style when transitioning between views. Default `platform`.
  1395. *
  1396. * @param {string} transition Which style of view transitioning to use.
  1397. *
  1398. * * `platform`: Dynamically choose the correct transition style depending on the platform
  1399. * the app is running from. If the platform is not `ios` or `android` then it will default
  1400. * to `ios`.
  1401. * * `ios`: iOS style transition.
  1402. * * `android`: Android style transition.
  1403. * * `none`: Do not perform animated transitions.
  1404. *
  1405. * @returns {string} value
  1406. */
  1407. /**
  1408. * @ngdoc method
  1409. * @name $ionicConfigProvider#views.maxCache
  1410. * @description Maximum number of view elements to cache in the DOM. When the max number is
  1411. * exceeded, the view with the longest time period since it was accessed is removed. Views that
  1412. * stay in the DOM cache the view's scope, current state, and scroll position. The scope is
  1413. * disconnected from the `$watch` cycle when it is cached and reconnected when it enters again.
  1414. * When the maximum cache is `0`, the leaving view's element will be removed from the DOM after
  1415. * each view transition, and the next time the same view is shown, it will have to re-compile,
  1416. * attach to the DOM, and link the element again. This disables caching, in effect.
  1417. * @param {number} maxNumber Maximum number of views to retain. Default `10`.
  1418. * @returns {number} How many views Ionic will hold onto until the a view is removed.
  1419. */
  1420. /**
  1421. * @ngdoc method
  1422. * @name $ionicConfigProvider#views.forwardCache
  1423. * @description By default, when navigating, views that were recently visited are cached, and
  1424. * the same instance data and DOM elements are referenced when navigating back. However, when
  1425. * navigating back in the history, the "forward" views are removed from the cache. If you
  1426. * navigate forward to the same view again, it'll create a new DOM element and controller
  1427. * instance. Basically, any forward views are reset each time. Set this config to `true` to have
  1428. * forward views cached and not reset on each load.
  1429. * @param {boolean} value
  1430. * @returns {boolean}
  1431. */
  1432. /**
  1433. * @ngdoc method
  1434. * @name $ionicConfigProvider#views.swipeBackEnabled
  1435. * @description By default on iOS devices, swipe to go back functionality is enabled by default.
  1436. * This method can be used to disable it globally, or on a per-view basis.
  1437. * Note: This functionality is only supported on iOS.
  1438. * @param {boolean} value
  1439. * @returns {boolean}
  1440. */
  1441. /**
  1442. * @ngdoc method
  1443. * @name $ionicConfigProvider#scrolling.jsScrolling
  1444. * @description Whether to use JS or Native scrolling. Defaults to native scrolling. Setting this to
  1445. * `true` has the same effect as setting each `ion-content` to have `overflow-scroll='false'`.
  1446. * @param {boolean} value Defaults to `false` as of Ionic 1.2
  1447. * @returns {boolean}
  1448. */
  1449. /**
  1450. * @ngdoc method
  1451. * @name $ionicConfigProvider#backButton.icon
  1452. * @description Back button icon.
  1453. * @param {string} value
  1454. * @returns {string}
  1455. */
  1456. /**
  1457. * @ngdoc method
  1458. * @name $ionicConfigProvider#backButton.text
  1459. * @description Back button text.
  1460. * @param {string} value Defaults to `Back`.
  1461. * @returns {string}
  1462. */
  1463. /**
  1464. * @ngdoc method
  1465. * @name $ionicConfigProvider#backButton.previousTitleText
  1466. * @description If the previous title text should become the back button text. This
  1467. * is the default for iOS.
  1468. * @param {boolean} value
  1469. * @returns {boolean}
  1470. */
  1471. /**
  1472. * @ngdoc method
  1473. * @name $ionicConfigProvider#form.checkbox
  1474. * @description Checkbox style. Android defaults to `square` and iOS defaults to `circle`.
  1475. * @param {string} value
  1476. * @returns {string}
  1477. */
  1478. /**
  1479. * @ngdoc method
  1480. * @name $ionicConfigProvider#form.toggle
  1481. * @description Toggle item style. Android defaults to `small` and iOS defaults to `large`.
  1482. * @param {string} value
  1483. * @returns {string}
  1484. */
  1485. /**
  1486. * @ngdoc method
  1487. * @name $ionicConfigProvider#spinner.icon
  1488. * @description Default spinner icon to use.
  1489. * @param {string} value Can be: `android`, `ios`, `ios-small`, `bubbles`, `circles`, `crescent`,
  1490. * `dots`, `lines`, `ripple`, or `spiral`.
  1491. * @returns {string}
  1492. */
  1493. /**
  1494. * @ngdoc method
  1495. * @name $ionicConfigProvider#tabs.style
  1496. * @description Tab style. Android defaults to `striped` and iOS defaults to `standard`.
  1497. * @param {string} value Available values include `striped` and `standard`.
  1498. * @returns {string}
  1499. */
  1500. /**
  1501. * @ngdoc method
  1502. * @name $ionicConfigProvider#tabs.position
  1503. * @description Tab position. Android defaults to `top` and iOS defaults to `bottom`.
  1504. * @param {string} value Available values include `top` and `bottom`.
  1505. * @returns {string}
  1506. */
  1507. /**
  1508. * @ngdoc method
  1509. * @name $ionicConfigProvider#templates.maxPrefetch
  1510. * @description Sets the maximum number of templates to prefetch from the templateUrls defined in
  1511. * $stateProvider.state. If set to `0`, the user will have to wait
  1512. * for a template to be fetched the first time when navigating to a new page. Default `30`.
  1513. * @param {integer} value Max number of template to prefetch from the templateUrls defined in
  1514. * `$stateProvider.state()`.
  1515. * @returns {integer}
  1516. */
  1517. /**
  1518. * @ngdoc method
  1519. * @name $ionicConfigProvider#navBar.alignTitle
  1520. * @description Which side of the navBar to align the title. Default `center`.
  1521. *
  1522. * @param {string} value side of the navBar to align the title.
  1523. *
  1524. * * `platform`: Dynamically choose the correct title style depending on the platform
  1525. * the app is running from. If the platform is `ios`, it will default to `center`.
  1526. * If the platform is `android`, it will default to `left`. If the platform is not
  1527. * `ios` or `android`, it will default to `center`.
  1528. *
  1529. * * `left`: Left align the title in the navBar
  1530. * * `center`: Center align the title in the navBar
  1531. * * `right`: Right align the title in the navBar.
  1532. *
  1533. * @returns {string} value
  1534. */
  1535. /**
  1536. * @ngdoc method
  1537. * @name $ionicConfigProvider#navBar.positionPrimaryButtons
  1538. * @description Which side of the navBar to align the primary navBar buttons. Default `left`.
  1539. *
  1540. * @param {string} value side of the navBar to align the primary navBar buttons.
  1541. *
  1542. * * `platform`: Dynamically choose the correct title style depending on the platform
  1543. * the app is running from. If the platform is `ios`, it will default to `left`.
  1544. * If the platform is `android`, it will default to `right`. If the platform is not
  1545. * `ios` or `android`, it will default to `left`.
  1546. *
  1547. * * `left`: Left align the primary navBar buttons in the navBar
  1548. * * `right`: Right align the primary navBar buttons in the navBar.
  1549. *
  1550. * @returns {string} value
  1551. */
  1552. /**
  1553. * @ngdoc method
  1554. * @name $ionicConfigProvider#navBar.positionSecondaryButtons
  1555. * @description Which side of the navBar to align the secondary navBar buttons. Default `right`.
  1556. *
  1557. * @param {string} value side of the navBar to align the secondary navBar buttons.
  1558. *
  1559. * * `platform`: Dynamically choose the correct title style depending on the platform
  1560. * the app is running from. If the platform is `ios`, it will default to `right`.
  1561. * If the platform is `android`, it will default to `right`. If the platform is not
  1562. * `ios` or `android`, it will default to `right`.
  1563. *
  1564. * * `left`: Left align the secondary navBar buttons in the navBar
  1565. * * `right`: Right align the secondary navBar buttons in the navBar.
  1566. *
  1567. * @returns {string} value
  1568. */
  1569. IonicModule
  1570. .provider('$ionicConfig', function() {
  1571. var provider = this;
  1572. provider.platform = {};
  1573. var PLATFORM = 'platform';
  1574. var configProperties = {
  1575. views: {
  1576. maxCache: PLATFORM,
  1577. forwardCache: PLATFORM,
  1578. transition: PLATFORM,
  1579. swipeBackEnabled: PLATFORM,
  1580. swipeBackHitWidth: PLATFORM
  1581. },
  1582. navBar: {
  1583. alignTitle: PLATFORM,
  1584. positionPrimaryButtons: PLATFORM,
  1585. positionSecondaryButtons: PLATFORM,
  1586. transition: PLATFORM
  1587. },
  1588. backButton: {
  1589. icon: PLATFORM,
  1590. text: PLATFORM,
  1591. previousTitleText: PLATFORM
  1592. },
  1593. form: {
  1594. checkbox: PLATFORM,
  1595. toggle: PLATFORM
  1596. },
  1597. scrolling: {
  1598. jsScrolling: PLATFORM
  1599. },
  1600. spinner: {
  1601. icon: PLATFORM
  1602. },
  1603. tabs: {
  1604. style: PLATFORM,
  1605. position: PLATFORM
  1606. },
  1607. templates: {
  1608. maxPrefetch: PLATFORM
  1609. },
  1610. platform: {}
  1611. };
  1612. createConfig(configProperties, provider, '');
  1613. // Default
  1614. // -------------------------
  1615. setPlatformConfig('default', {
  1616. views: {
  1617. maxCache: 10,
  1618. forwardCache: false,
  1619. transition: 'ios',
  1620. swipeBackEnabled: true,
  1621. swipeBackHitWidth: 45
  1622. },
  1623. navBar: {
  1624. alignTitle: 'center',
  1625. positionPrimaryButtons: 'left',
  1626. positionSecondaryButtons: 'right',
  1627. transition: 'view'
  1628. },
  1629. backButton: {
  1630. icon: 'ion-ios-arrow-back',
  1631. text: 'Back',
  1632. previousTitleText: true
  1633. },
  1634. form: {
  1635. checkbox: 'circle',
  1636. toggle: 'large'
  1637. },
  1638. scrolling: {
  1639. jsScrolling: true
  1640. },
  1641. spinner: {
  1642. icon: 'ios'
  1643. },
  1644. tabs: {
  1645. style: 'standard',
  1646. position: 'bottom'
  1647. },
  1648. templates: {
  1649. maxPrefetch: 30
  1650. }
  1651. });
  1652. // iOS (it is the default already)
  1653. // -------------------------
  1654. setPlatformConfig('ios', {});
  1655. // Android
  1656. // -------------------------
  1657. setPlatformConfig('android', {
  1658. views: {
  1659. transition: 'android',
  1660. swipeBackEnabled: false
  1661. },
  1662. navBar: {
  1663. alignTitle: 'left',
  1664. positionPrimaryButtons: 'right',
  1665. positionSecondaryButtons: 'right'
  1666. },
  1667. backButton: {
  1668. icon: 'ion-android-arrow-back',
  1669. text: false,
  1670. previousTitleText: false
  1671. },
  1672. form: {
  1673. checkbox: 'square',
  1674. toggle: 'small'
  1675. },
  1676. spinner: {
  1677. icon: 'android'
  1678. },
  1679. tabs: {
  1680. style: 'striped',
  1681. position: 'top'
  1682. },
  1683. scrolling: {
  1684. jsScrolling: false
  1685. }
  1686. });
  1687. // Windows Phone
  1688. // -------------------------
  1689. setPlatformConfig('windowsphone', {
  1690. //scrolling: {
  1691. // jsScrolling: false
  1692. //}
  1693. spinner: {
  1694. icon: 'android'
  1695. }
  1696. });
  1697. provider.transitions = {
  1698. views: {},
  1699. navBar: {}
  1700. };
  1701. // iOS Transitions
  1702. // -----------------------
  1703. provider.transitions.views.ios = function(enteringEle, leavingEle, direction, shouldAnimate) {
  1704. function setStyles(ele, opacity, x, boxShadowOpacity) {
  1705. var css = {};
  1706. css[ionic.CSS.TRANSITION_DURATION] = d.shouldAnimate ? '' : 0;
  1707. css.opacity = opacity;
  1708. if (boxShadowOpacity > -1) {
  1709. css.boxShadow = '0 0 10px rgba(0,0,0,' + (d.shouldAnimate ? boxShadowOpacity * 0.45 : 0.3) + ')';
  1710. }
  1711. css[ionic.CSS.TRANSFORM] = 'translate3d(' + x + '%,0,0)';
  1712. ionic.DomUtil.cachedStyles(ele, css);
  1713. }
  1714. var d = {
  1715. run: function(step) {
  1716. if (direction == 'forward') {
  1717. setStyles(enteringEle, 1, (1 - step) * 99, 1 - step); // starting at 98% prevents a flicker
  1718. setStyles(leavingEle, (1 - 0.1 * step), step * -33, -1);
  1719. } else if (direction == 'back') {
  1720. setStyles(enteringEle, (1 - 0.1 * (1 - step)), (1 - step) * -33, -1);
  1721. setStyles(leavingEle, 1, step * 100, 1 - step);
  1722. } else {
  1723. // swap, enter, exit
  1724. setStyles(enteringEle, 1, 0, -1);
  1725. setStyles(leavingEle, 0, 0, -1);
  1726. }
  1727. },
  1728. shouldAnimate: shouldAnimate && (direction == 'forward' || direction == 'back')
  1729. };
  1730. return d;
  1731. };
  1732. provider.transitions.navBar.ios = function(enteringHeaderBar, leavingHeaderBar, direction, shouldAnimate) {
  1733. function setStyles(ctrl, opacity, titleX, backTextX) {
  1734. var css = {};
  1735. css[ionic.CSS.TRANSITION_DURATION] = d.shouldAnimate ? '' : '0ms';
  1736. css.opacity = opacity === 1 ? '' : opacity;
  1737. ctrl.setCss('buttons-left', css);
  1738. ctrl.setCss('buttons-right', css);
  1739. ctrl.setCss('back-button', css);
  1740. css[ionic.CSS.TRANSFORM] = 'translate3d(' + backTextX + 'px,0,0)';
  1741. ctrl.setCss('back-text', css);
  1742. css[ionic.CSS.TRANSFORM] = 'translate3d(' + titleX + 'px,0,0)';
  1743. ctrl.setCss('title', css);
  1744. }
  1745. function enter(ctrlA, ctrlB, step) {
  1746. if (!ctrlA || !ctrlB) return;
  1747. var titleX = (ctrlA.titleTextX() + ctrlA.titleWidth()) * (1 - step);
  1748. var backTextX = (ctrlB && (ctrlB.titleTextX() - ctrlA.backButtonTextLeft()) * (1 - step)) || 0;
  1749. setStyles(ctrlA, step, titleX, backTextX);
  1750. }
  1751. function leave(ctrlA, ctrlB, step) {
  1752. if (!ctrlA || !ctrlB) return;
  1753. var titleX = (-(ctrlA.titleTextX() - ctrlB.backButtonTextLeft()) - (ctrlA.titleLeftRight())) * step;
  1754. setStyles(ctrlA, 1 - step, titleX, 0);
  1755. }
  1756. var d = {
  1757. run: function(step) {
  1758. var enteringHeaderCtrl = enteringHeaderBar.controller();
  1759. var leavingHeaderCtrl = leavingHeaderBar && leavingHeaderBar.controller();
  1760. if (d.direction == 'back') {
  1761. leave(enteringHeaderCtrl, leavingHeaderCtrl, 1 - step);
  1762. enter(leavingHeaderCtrl, enteringHeaderCtrl, 1 - step);
  1763. } else {
  1764. enter(enteringHeaderCtrl, leavingHeaderCtrl, step);
  1765. leave(leavingHeaderCtrl, enteringHeaderCtrl, step);
  1766. }
  1767. },
  1768. direction: direction,
  1769. shouldAnimate: shouldAnimate && (direction == 'forward' || direction == 'back')
  1770. };
  1771. return d;
  1772. };
  1773. // Android Transitions
  1774. // -----------------------
  1775. provider.transitions.views.android = function(enteringEle, leavingEle, direction, shouldAnimate) {
  1776. shouldAnimate = shouldAnimate && (direction == 'forward' || direction == 'back');
  1777. function setStyles(ele, x, opacity) {
  1778. var css = {};
  1779. css[ionic.CSS.TRANSITION_DURATION] = d.shouldAnimate ? '' : 0;
  1780. css[ionic.CSS.TRANSFORM] = 'translate3d(' + x + '%,0,0)';
  1781. css.opacity = opacity;
  1782. ionic.DomUtil.cachedStyles(ele, css);
  1783. }
  1784. var d = {
  1785. run: function(step) {
  1786. if (direction == 'forward') {
  1787. setStyles(enteringEle, (1 - step) * 99, 1); // starting at 98% prevents a flicker
  1788. setStyles(leavingEle, step * -100, 1);
  1789. } else if (direction == 'back') {
  1790. setStyles(enteringEle, (1 - step) * -100, 1);
  1791. setStyles(leavingEle, step * 100, 1);
  1792. } else {
  1793. // swap, enter, exit
  1794. setStyles(enteringEle, 0, 1);
  1795. setStyles(leavingEle, 0, 0);
  1796. }
  1797. },
  1798. shouldAnimate: shouldAnimate
  1799. };
  1800. return d;
  1801. };
  1802. provider.transitions.navBar.android = function(enteringHeaderBar, leavingHeaderBar, direction, shouldAnimate) {
  1803. function setStyles(ctrl, opacity) {
  1804. if (!ctrl) return;
  1805. var css = {};
  1806. css.opacity = opacity === 1 ? '' : opacity;
  1807. ctrl.setCss('buttons-left', css);
  1808. ctrl.setCss('buttons-right', css);
  1809. ctrl.setCss('back-button', css);
  1810. ctrl.setCss('back-text', css);
  1811. ctrl.setCss('title', css);
  1812. }
  1813. return {
  1814. run: function(step) {
  1815. setStyles(enteringHeaderBar.controller(), step);
  1816. setStyles(leavingHeaderBar && leavingHeaderBar.controller(), 1 - step);
  1817. },
  1818. shouldAnimate: shouldAnimate && (direction == 'forward' || direction == 'back')
  1819. };
  1820. };
  1821. // No Transition
  1822. // -----------------------
  1823. provider.transitions.views.none = function(enteringEle, leavingEle) {
  1824. return {
  1825. run: function(step) {
  1826. provider.transitions.views.android(enteringEle, leavingEle, false, false).run(step);
  1827. },
  1828. shouldAnimate: false
  1829. };
  1830. };
  1831. provider.transitions.navBar.none = function(enteringHeaderBar, leavingHeaderBar) {
  1832. return {
  1833. run: function(step) {
  1834. provider.transitions.navBar.ios(enteringHeaderBar, leavingHeaderBar, false, false).run(step);
  1835. provider.transitions.navBar.android(enteringHeaderBar, leavingHeaderBar, false, false).run(step);
  1836. },
  1837. shouldAnimate: false
  1838. };
  1839. };
  1840. // private: used to set platform configs
  1841. function setPlatformConfig(platformName, platformConfigs) {
  1842. configProperties.platform[platformName] = platformConfigs;
  1843. provider.platform[platformName] = {};
  1844. addConfig(configProperties, configProperties.platform[platformName]);
  1845. createConfig(configProperties.platform[platformName], provider.platform[platformName], '');
  1846. }
  1847. // private: used to recursively add new platform configs
  1848. function addConfig(configObj, platformObj) {
  1849. for (var n in configObj) {
  1850. if (n != PLATFORM && configObj.hasOwnProperty(n)) {
  1851. if (angular.isObject(configObj[n])) {
  1852. if (!isDefined(platformObj[n])) {
  1853. platformObj[n] = {};
  1854. }
  1855. addConfig(configObj[n], platformObj[n]);
  1856. } else if (!isDefined(platformObj[n])) {
  1857. platformObj[n] = null;
  1858. }
  1859. }
  1860. }
  1861. }
  1862. // private: create methods for each config to get/set
  1863. function createConfig(configObj, providerObj, platformPath) {
  1864. forEach(configObj, function(value, namespace) {
  1865. if (angular.isObject(configObj[namespace])) {
  1866. // recursively drill down the config object so we can create a method for each one
  1867. providerObj[namespace] = {};
  1868. createConfig(configObj[namespace], providerObj[namespace], platformPath + '.' + namespace);
  1869. } else {
  1870. // create a method for the provider/config methods that will be exposed
  1871. providerObj[namespace] = function(newValue) {
  1872. if (arguments.length) {
  1873. configObj[namespace] = newValue;
  1874. return providerObj;
  1875. }
  1876. if (configObj[namespace] == PLATFORM) {
  1877. // if the config is set to 'platform', then get this config's platform value
  1878. var platformConfig = stringObj(configProperties.platform, ionic.Platform.platform() + platformPath + '.' + namespace);
  1879. if (platformConfig || platformConfig === false) {
  1880. return platformConfig;
  1881. }
  1882. // didnt find a specific platform config, now try the default
  1883. return stringObj(configProperties.platform, 'default' + platformPath + '.' + namespace);
  1884. }
  1885. return configObj[namespace];
  1886. };
  1887. }
  1888. });
  1889. }
  1890. function stringObj(obj, str) {
  1891. str = str.split(".");
  1892. for (var i = 0; i < str.length; i++) {
  1893. if (obj && isDefined(obj[str[i]])) {
  1894. obj = obj[str[i]];
  1895. } else {
  1896. return null;
  1897. }
  1898. }
  1899. return obj;
  1900. }
  1901. provider.setPlatformConfig = setPlatformConfig;
  1902. // private: Service definition for internal Ionic use
  1903. /**
  1904. * @ngdoc service
  1905. * @name $ionicConfig
  1906. * @module ionic
  1907. * @private
  1908. */
  1909. provider.$get = function() {
  1910. return provider;
  1911. };
  1912. })
  1913. // Fix for URLs in Cordova apps on Windows Phone
  1914. // http://blogs.msdn.com/b/msdn_answers/archive/2015/02/10/
  1915. // running-cordova-apps-on-windows-and-windows-phone-8-1-using-ionic-angularjs-and-other-frameworks.aspx
  1916. .config(['$compileProvider', function($compileProvider) {
  1917. $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|sms|tel|geo|ftp|mailto|file|ghttps?|ms-appx-web|ms-appx|x-wmapp0):/);
  1918. $compileProvider.imgSrcSanitizationWhitelist(/^\s*(https?|ftp|file|content|blob|ms-appx|ms-appx-web|x-wmapp0):|data:image\//);
  1919. }]);
  1920. var LOADING_TPL =
  1921. '<div class="loading-container">' +
  1922. '<div class="loading">' +
  1923. '</div>' +
  1924. '</div>';
  1925. /**
  1926. * @ngdoc service
  1927. * @name $ionicLoading
  1928. * @module ionic
  1929. * @description
  1930. * An overlay that can be used to indicate activity while blocking user
  1931. * interaction.
  1932. *
  1933. * @usage
  1934. * ```js
  1935. * angular.module('LoadingApp', ['ionic'])
  1936. * .controller('LoadingCtrl', function($scope, $ionicLoading) {
  1937. * $scope.show = function() {
  1938. * $ionicLoading.show({
  1939. * template: 'Loading...',
  1940. * duration: 3000
  1941. * }).then(function(){
  1942. * console.log("The loading indicator is now displayed");
  1943. * });
  1944. * };
  1945. * $scope.hide = function(){
  1946. * $ionicLoading.hide().then(function(){
  1947. * console.log("The loading indicator is now hidden");
  1948. * });
  1949. * };
  1950. * });
  1951. * ```
  1952. */
  1953. /**
  1954. * @ngdoc object
  1955. * @name $ionicLoadingConfig
  1956. * @module ionic
  1957. * @description
  1958. * Set the default options to be passed to the {@link ionic.service:$ionicLoading} service.
  1959. *
  1960. * @usage
  1961. * ```js
  1962. * var app = angular.module('myApp', ['ionic'])
  1963. * app.constant('$ionicLoadingConfig', {
  1964. * template: 'Default Loading Template...'
  1965. * });
  1966. * app.controller('AppCtrl', function($scope, $ionicLoading) {
  1967. * $scope.showLoading = function() {
  1968. * //options default to values in $ionicLoadingConfig
  1969. * $ionicLoading.show().then(function(){
  1970. * console.log("The loading indicator is now displayed");
  1971. * });
  1972. * };
  1973. * });
  1974. * ```
  1975. */
  1976. IonicModule
  1977. .constant('$ionicLoadingConfig', {
  1978. template: '<ion-spinner></ion-spinner>'
  1979. })
  1980. .factory('$ionicLoading', [
  1981. '$ionicLoadingConfig',
  1982. '$ionicBody',
  1983. '$ionicTemplateLoader',
  1984. '$ionicBackdrop',
  1985. '$timeout',
  1986. '$q',
  1987. '$log',
  1988. '$compile',
  1989. '$ionicPlatform',
  1990. '$rootScope',
  1991. 'IONIC_BACK_PRIORITY',
  1992. function($ionicLoadingConfig, $ionicBody, $ionicTemplateLoader, $ionicBackdrop, $timeout, $q, $log, $compile, $ionicPlatform, $rootScope, IONIC_BACK_PRIORITY) {
  1993. var loaderInstance;
  1994. //default values
  1995. var deregisterBackAction = noop;
  1996. var deregisterStateListener1 = noop;
  1997. var deregisterStateListener2 = noop;
  1998. var loadingShowDelay = $q.when();
  1999. return {
  2000. /**
  2001. * @ngdoc method
  2002. * @name $ionicLoading#show
  2003. * @description Shows a loading indicator. If the indicator is already shown,
  2004. * it will set the options given and keep the indicator shown.
  2005. * @returns {promise} A promise which is resolved when the loading indicator is presented.
  2006. * @param {object} opts The options for the loading indicator. Available properties:
  2007. * - `{string=}` `template` The html content of the indicator.
  2008. * - `{string=}` `templateUrl` The url of an html template to load as the content of the indicator.
  2009. * - `{object=}` `scope` The scope to be a child of. Default: creates a child of $rootScope.
  2010. * - `{boolean=}` `noBackdrop` Whether to hide the backdrop. By default it will be shown.
  2011. * - `{boolean=}` `hideOnStateChange` Whether to hide the loading spinner when navigating
  2012. * to a new state. Default false.
  2013. * - `{number=}` `delay` How many milliseconds to delay showing the indicator. By default there is no delay.
  2014. * - `{number=}` `duration` How many milliseconds to wait until automatically
  2015. * hiding the indicator. By default, the indicator will be shown until `.hide()` is called.
  2016. */
  2017. show: showLoader,
  2018. /**
  2019. * @ngdoc method
  2020. * @name $ionicLoading#hide
  2021. * @description Hides the loading indicator, if shown.
  2022. * @returns {promise} A promise which is resolved when the loading indicator is hidden.
  2023. */
  2024. hide: hideLoader,
  2025. /**
  2026. * @private for testing
  2027. */
  2028. _getLoader: getLoader
  2029. };
  2030. function getLoader() {
  2031. if (!loaderInstance) {
  2032. loaderInstance = $ionicTemplateLoader.compile({
  2033. template: LOADING_TPL,
  2034. appendTo: $ionicBody.get()
  2035. })
  2036. .then(function(self) {
  2037. self.show = function(options) {
  2038. var templatePromise = options.templateUrl ?
  2039. $ionicTemplateLoader.load(options.templateUrl) :
  2040. //options.content: deprecated
  2041. $q.when(options.template || options.content || '');
  2042. self.scope = options.scope || self.scope;
  2043. if (!self.isShown) {
  2044. //options.showBackdrop: deprecated
  2045. self.hasBackdrop = !options.noBackdrop && options.showBackdrop !== false;
  2046. if (self.hasBackdrop) {
  2047. $ionicBackdrop.retain();
  2048. $ionicBackdrop.getElement().addClass('backdrop-loading');
  2049. }
  2050. }
  2051. if (options.duration) {
  2052. $timeout.cancel(self.durationTimeout);
  2053. self.durationTimeout = $timeout(
  2054. angular.bind(self, self.hide),
  2055. +options.duration
  2056. );
  2057. }
  2058. deregisterBackAction();
  2059. //Disable hardware back button while loading
  2060. deregisterBackAction = $ionicPlatform.registerBackButtonAction(
  2061. noop,
  2062. IONIC_BACK_PRIORITY.loading
  2063. );
  2064. templatePromise.then(function(html) {
  2065. if (html) {
  2066. var loading = self.element.children();
  2067. loading.html(html);
  2068. $compile(loading.contents())(self.scope);
  2069. }
  2070. //Don't show until template changes
  2071. if (self.isShown) {
  2072. self.element.addClass('visible');
  2073. ionic.requestAnimationFrame(function() {
  2074. if (self.isShown) {
  2075. self.element.addClass('active');
  2076. $ionicBody.addClass('loading-active');
  2077. }
  2078. });
  2079. }
  2080. });
  2081. self.isShown = true;
  2082. };
  2083. self.hide = function() {
  2084. deregisterBackAction();
  2085. if (self.isShown) {
  2086. if (self.hasBackdrop) {
  2087. $ionicBackdrop.release();
  2088. $ionicBackdrop.getElement().removeClass('backdrop-loading');
  2089. }
  2090. self.element.removeClass('active');
  2091. $ionicBody.removeClass('loading-active');
  2092. self.element.removeClass('visible');
  2093. ionic.requestAnimationFrame(function() {
  2094. !self.isShown && self.element.removeClass('visible');
  2095. });
  2096. }
  2097. $timeout.cancel(self.durationTimeout);
  2098. self.isShown = false;
  2099. var loading = self.element.children();
  2100. loading.html("");
  2101. };
  2102. return self;
  2103. });
  2104. }
  2105. return loaderInstance;
  2106. }
  2107. function showLoader(options) {
  2108. options = extend({}, $ionicLoadingConfig || {}, options || {});
  2109. // use a default delay of 100 to avoid some issues reported on github
  2110. // https://github.com/driftyco/ionic/issues/3717
  2111. var delay = options.delay || options.showDelay || 0;
  2112. deregisterStateListener1();
  2113. deregisterStateListener2();
  2114. if (options.hideOnStateChange) {
  2115. deregisterStateListener1 = $rootScope.$on('$stateChangeSuccess', hideLoader);
  2116. deregisterStateListener2 = $rootScope.$on('$stateChangeError', hideLoader);
  2117. }
  2118. //If loading.show() was called previously, cancel it and show with our new options
  2119. $timeout.cancel(loadingShowDelay);
  2120. loadingShowDelay = $timeout(noop, delay);
  2121. return loadingShowDelay.then(getLoader).then(function(loader) {
  2122. return loader.show(options);
  2123. });
  2124. }
  2125. function hideLoader() {
  2126. deregisterStateListener1();
  2127. deregisterStateListener2();
  2128. $timeout.cancel(loadingShowDelay);
  2129. return getLoader().then(function(loader) {
  2130. return loader.hide();
  2131. });
  2132. }
  2133. }]);
  2134. /**
  2135. * @ngdoc service
  2136. * @name $ionicModal
  2137. * @module ionic
  2138. * @codepen gblny
  2139. * @description
  2140. *
  2141. * Related: {@link ionic.controller:ionicModal ionicModal controller}.
  2142. *
  2143. * The Modal is a content pane that can go over the user's main view
  2144. * temporarily. Usually used for making a choice or editing an item.
  2145. *
  2146. * Put the content of the modal inside of an `<ion-modal-view>` element.
  2147. *
  2148. * **Notes:**
  2149. * - A modal will broadcast 'modal.shown', 'modal.hidden', and 'modal.removed' events from its originating
  2150. * scope, passing in itself as an event argument. Both the modal.removed and modal.hidden events are
  2151. * called when the modal is removed.
  2152. *
  2153. * - This example assumes your modal is in your main index file or another template file. If it is in its own
  2154. * template file, remove the script tags and call it by file name.
  2155. *
  2156. * @usage
  2157. * ```html
  2158. * <script id="my-modal.html" type="text/ng-template">
  2159. * <ion-modal-view>
  2160. * <ion-header-bar>
  2161. * <h1 class="title">My Modal title</h1>
  2162. * </ion-header-bar>
  2163. * <ion-content>
  2164. * Hello!
  2165. * </ion-content>
  2166. * </ion-modal-view>
  2167. * </script>
  2168. * ```
  2169. * ```js
  2170. * angular.module('testApp', ['ionic'])
  2171. * .controller('MyController', function($scope, $ionicModal) {
  2172. * $ionicModal.fromTemplateUrl('my-modal.html', {
  2173. * scope: $scope,
  2174. * animation: 'slide-in-up'
  2175. * }).then(function(modal) {
  2176. * $scope.modal = modal;
  2177. * });
  2178. * $scope.openModal = function() {
  2179. * $scope.modal.show();
  2180. * };
  2181. * $scope.closeModal = function() {
  2182. * $scope.modal.hide();
  2183. * };
  2184. * // Cleanup the modal when we're done with it!
  2185. * $scope.$on('$destroy', function() {
  2186. * $scope.modal.remove();
  2187. * });
  2188. * // Execute action on hide modal
  2189. * $scope.$on('modal.hidden', function() {
  2190. * // Execute action
  2191. * });
  2192. * // Execute action on remove modal
  2193. * $scope.$on('modal.removed', function() {
  2194. * // Execute action
  2195. * });
  2196. * });
  2197. * ```
  2198. */
  2199. IonicModule
  2200. .factory('$ionicModal', [
  2201. '$rootScope',
  2202. '$ionicBody',
  2203. '$compile',
  2204. '$timeout',
  2205. '$ionicPlatform',
  2206. '$ionicTemplateLoader',
  2207. '$$q',
  2208. '$log',
  2209. '$ionicClickBlock',
  2210. '$window',
  2211. 'IONIC_BACK_PRIORITY',
  2212. function($rootScope, $ionicBody, $compile, $timeout, $ionicPlatform, $ionicTemplateLoader, $$q, $log, $ionicClickBlock, $window, IONIC_BACK_PRIORITY) {
  2213. /**
  2214. * @ngdoc controller
  2215. * @name ionicModal
  2216. * @module ionic
  2217. * @description
  2218. * Instantiated by the {@link ionic.service:$ionicModal} service.
  2219. *
  2220. * Be sure to call [remove()](#remove) when you are done with each modal
  2221. * to clean it up and avoid memory leaks.
  2222. *
  2223. * Note: a modal will broadcast 'modal.shown', 'modal.hidden', and 'modal.removed' events from its originating
  2224. * scope, passing in itself as an event argument. Note: both modal.removed and modal.hidden are
  2225. * called when the modal is removed.
  2226. */
  2227. var ModalView = ionic.views.Modal.inherit({
  2228. /**
  2229. * @ngdoc method
  2230. * @name ionicModal#initialize
  2231. * @description Creates a new modal controller instance.
  2232. * @param {object} options An options object with the following properties:
  2233. * - `{object=}` `scope` The scope to be a child of.
  2234. * Default: creates a child of $rootScope.
  2235. * - `{string=}` `animation` The animation to show & hide with.
  2236. * Default: 'slide-in-up'
  2237. * - `{boolean=}` `focusFirstInput` Whether to autofocus the first input of
  2238. * the modal when shown. Will only show the keyboard on iOS, to force the keyboard to show
  2239. * on Android, please use the [Ionic keyboard plugin](https://github.com/driftyco/ionic-plugin-keyboard#keyboardshow).
  2240. * Default: false.
  2241. * - `{boolean=}` `backdropClickToClose` Whether to close the modal on clicking the backdrop.
  2242. * Default: true.
  2243. * - `{boolean=}` `hardwareBackButtonClose` Whether the modal can be closed using the hardware
  2244. * back button on Android and similar devices. Default: true.
  2245. */
  2246. initialize: function(opts) {
  2247. ionic.views.Modal.prototype.initialize.call(this, opts);
  2248. this.animation = opts.animation || 'slide-in-up';
  2249. },
  2250. /**
  2251. * @ngdoc method
  2252. * @name ionicModal#show
  2253. * @description Show this modal instance.
  2254. * @returns {promise} A promise which is resolved when the modal is finished animating in.
  2255. */
  2256. show: function(target) {
  2257. var self = this;
  2258. if (self.scope.$$destroyed) {
  2259. $log.error('Cannot call ' + self.viewType + '.show() after remove(). Please create a new ' + self.viewType + ' instance.');
  2260. return $$q.when();
  2261. }
  2262. // on iOS, clicks will sometimes bleed through/ghost click on underlying
  2263. // elements
  2264. $ionicClickBlock.show(600);
  2265. stack.add(self);
  2266. var modalEl = jqLite(self.modalEl);
  2267. self.el.classList.remove('hide');
  2268. $timeout(function() {
  2269. if (!self._isShown) return;
  2270. $ionicBody.addClass(self.viewType + '-open');
  2271. }, 400, false);
  2272. if (!self.el.parentElement) {
  2273. modalEl.addClass(self.animation);
  2274. $ionicBody.append(self.el);
  2275. }
  2276. // if modal was closed while the keyboard was up, reset scroll view on
  2277. // next show since we can only resize it once it's visible
  2278. var scrollCtrl = modalEl.data('$$ionicScrollController');
  2279. scrollCtrl && scrollCtrl.resize();
  2280. if (target && self.positionView) {
  2281. self.positionView(target, modalEl);
  2282. // set up a listener for in case the window size changes
  2283. self._onWindowResize = function() {
  2284. if (self._isShown) self.positionView(target, modalEl);
  2285. };
  2286. ionic.on('resize', self._onWindowResize, window);
  2287. }
  2288. modalEl.addClass('ng-enter active')
  2289. .removeClass('ng-leave ng-leave-active');
  2290. self._isShown = true;
  2291. self._deregisterBackButton = $ionicPlatform.registerBackButtonAction(
  2292. self.hardwareBackButtonClose ? angular.bind(self, self.hide) : noop,
  2293. IONIC_BACK_PRIORITY.modal
  2294. );
  2295. ionic.views.Modal.prototype.show.call(self);
  2296. $timeout(function() {
  2297. if (!self._isShown) return;
  2298. modalEl.addClass('ng-enter-active');
  2299. ionic.trigger('resize');
  2300. self.scope.$parent && self.scope.$parent.$broadcast(self.viewType + '.shown', self);
  2301. self.el.classList.add('active');
  2302. self.scope.$broadcast('$ionicHeader.align');
  2303. self.scope.$broadcast('$ionicFooter.align');
  2304. self.scope.$broadcast('$ionic.modalPresented');
  2305. }, 20);
  2306. return $timeout(function() {
  2307. if (!self._isShown) return;
  2308. self.$el.on('touchmove', function(e) {
  2309. //Don't allow scrolling while open by dragging on backdrop
  2310. var isInScroll = ionic.DomUtil.getParentOrSelfWithClass(e.target, 'scroll');
  2311. if (!isInScroll) {
  2312. e.preventDefault();
  2313. }
  2314. });
  2315. //After animating in, allow hide on backdrop click
  2316. self.$el.on('click', function(e) {
  2317. if (self.backdropClickToClose && e.target === self.el && stack.isHighest(self)) {
  2318. self.hide();
  2319. }
  2320. });
  2321. }, 400);
  2322. },
  2323. /**
  2324. * @ngdoc method
  2325. * @name ionicModal#hide
  2326. * @description Hide this modal instance.
  2327. * @returns {promise} A promise which is resolved when the modal is finished animating out.
  2328. */
  2329. hide: function() {
  2330. var self = this;
  2331. var modalEl = jqLite(self.modalEl);
  2332. // on iOS, clicks will sometimes bleed through/ghost click on underlying
  2333. // elements
  2334. $ionicClickBlock.show(600);
  2335. stack.remove(self);
  2336. self.el.classList.remove('active');
  2337. modalEl.addClass('ng-leave');
  2338. $timeout(function() {
  2339. if (self._isShown) return;
  2340. modalEl.addClass('ng-leave-active')
  2341. .removeClass('ng-enter ng-enter-active active');
  2342. self.scope.$broadcast('$ionic.modalRemoved');
  2343. }, 20, false);
  2344. self.$el.off('click');
  2345. self._isShown = false;
  2346. self.scope.$parent && self.scope.$parent.$broadcast(self.viewType + '.hidden', self);
  2347. self._deregisterBackButton && self._deregisterBackButton();
  2348. ionic.views.Modal.prototype.hide.call(self);
  2349. // clean up event listeners
  2350. if (self.positionView) {
  2351. ionic.off('resize', self._onWindowResize, window);
  2352. }
  2353. return $timeout(function() {
  2354. if (!modalStack.length) {
  2355. $ionicBody.removeClass(self.viewType + '-open');
  2356. }
  2357. self.el.classList.add('hide');
  2358. }, self.hideDelay || 320);
  2359. },
  2360. /**
  2361. * @ngdoc method
  2362. * @name ionicModal#remove
  2363. * @description Remove this modal instance from the DOM and clean up.
  2364. * @returns {promise} A promise which is resolved when the modal is finished animating out.
  2365. */
  2366. remove: function() {
  2367. var self = this,
  2368. deferred, promise;
  2369. self.scope.$parent && self.scope.$parent.$broadcast(self.viewType + '.removed', self);
  2370. // Only hide modal, when it is actually shown!
  2371. // The hide function shows a click-block-div for a split second, because on iOS,
  2372. // clicks will sometimes bleed through/ghost click on underlying elements.
  2373. // However, this will make the app unresponsive for short amount of time.
  2374. // We don't want that, if the modal window is already hidden.
  2375. if (self._isShown) {
  2376. promise = self.hide();
  2377. } else {
  2378. deferred = $$q.defer();
  2379. deferred.resolve();
  2380. promise = deferred.promise;
  2381. }
  2382. return promise.then(function() {
  2383. self.scope.$destroy();
  2384. self.$el.remove();
  2385. });
  2386. },
  2387. /**
  2388. * @ngdoc method
  2389. * @name ionicModal#isShown
  2390. * @returns boolean Whether this modal is currently shown.
  2391. */
  2392. isShown: function() {
  2393. return !!this._isShown;
  2394. }
  2395. });
  2396. var createModal = function(templateString, options) {
  2397. // Create a new scope for the modal
  2398. var scope = options.scope && options.scope.$new() || $rootScope.$new(true);
  2399. options.viewType = options.viewType || 'modal';
  2400. extend(scope, {
  2401. $hasHeader: false,
  2402. $hasSubheader: false,
  2403. $hasFooter: false,
  2404. $hasSubfooter: false,
  2405. $hasTabs: false,
  2406. $hasTabsTop: false
  2407. });
  2408. // Compile the template
  2409. var element = $compile('<ion-' + options.viewType + '>' + templateString + '</ion-' + options.viewType + '>')(scope);
  2410. options.$el = element;
  2411. options.el = element[0];
  2412. options.modalEl = options.el.querySelector('.' + options.viewType);
  2413. var modal = new ModalView(options);
  2414. modal.scope = scope;
  2415. // If this wasn't a defined scope, we can assign the viewType to the isolated scope
  2416. // we created
  2417. if (!options.scope) {
  2418. scope[ options.viewType ] = modal;
  2419. }
  2420. return modal;
  2421. };
  2422. var modalStack = [];
  2423. var stack = {
  2424. add: function(modal) {
  2425. modalStack.push(modal);
  2426. },
  2427. remove: function(modal) {
  2428. var index = modalStack.indexOf(modal);
  2429. if (index > -1 && index < modalStack.length) {
  2430. modalStack.splice(index, 1);
  2431. }
  2432. },
  2433. isHighest: function(modal) {
  2434. var index = modalStack.indexOf(modal);
  2435. return (index > -1 && index === modalStack.length - 1);
  2436. }
  2437. };
  2438. return {
  2439. /**
  2440. * @ngdoc method
  2441. * @name $ionicModal#fromTemplate
  2442. * @param {string} templateString The template string to use as the modal's
  2443. * content.
  2444. * @param {object} options Options to be passed {@link ionic.controller:ionicModal#initialize ionicModal#initialize} method.
  2445. * @returns {object} An instance of an {@link ionic.controller:ionicModal}
  2446. * controller.
  2447. */
  2448. fromTemplate: function(templateString, options) {
  2449. var modal = createModal(templateString, options || {});
  2450. return modal;
  2451. },
  2452. /**
  2453. * @ngdoc method
  2454. * @name $ionicModal#fromTemplateUrl
  2455. * @param {string} templateUrl The url to load the template from.
  2456. * @param {object} options Options to be passed {@link ionic.controller:ionicModal#initialize ionicModal#initialize} method.
  2457. * options object.
  2458. * @returns {promise} A promise that will be resolved with an instance of
  2459. * an {@link ionic.controller:ionicModal} controller.
  2460. */
  2461. fromTemplateUrl: function(url, options, _) {
  2462. var cb;
  2463. //Deprecated: allow a callback as second parameter. Now we return a promise.
  2464. if (angular.isFunction(options)) {
  2465. cb = options;
  2466. options = _;
  2467. }
  2468. return $ionicTemplateLoader.load(url).then(function(templateString) {
  2469. var modal = createModal(templateString, options || {});
  2470. cb && cb(modal);
  2471. return modal;
  2472. });
  2473. },
  2474. stack: stack
  2475. };
  2476. }]);
  2477. /**
  2478. * @ngdoc service
  2479. * @name $ionicNavBarDelegate
  2480. * @module ionic
  2481. * @description
  2482. * Delegate for controlling the {@link ionic.directive:ionNavBar} directive.
  2483. *
  2484. * @usage
  2485. *
  2486. * ```html
  2487. * <body ng-controller="MyCtrl">
  2488. * <ion-nav-bar>
  2489. * <button ng-click="setNavTitle('banana')">
  2490. * Set title to banana!
  2491. * </button>
  2492. * </ion-nav-bar>
  2493. * </body>
  2494. * ```
  2495. * ```js
  2496. * function MyCtrl($scope, $ionicNavBarDelegate) {
  2497. * $scope.setNavTitle = function(title) {
  2498. * $ionicNavBarDelegate.title(title);
  2499. * }
  2500. * }
  2501. * ```
  2502. */
  2503. IonicModule
  2504. .service('$ionicNavBarDelegate', ionic.DelegateService([
  2505. /**
  2506. * @ngdoc method
  2507. * @name $ionicNavBarDelegate#align
  2508. * @description Aligns the title with the buttons in a given direction.
  2509. * @param {string=} direction The direction to the align the title text towards.
  2510. * Available: 'left', 'right', 'center'. Default: 'center'.
  2511. */
  2512. 'align',
  2513. /**
  2514. * @ngdoc method
  2515. * @name $ionicNavBarDelegate#showBackButton
  2516. * @description
  2517. * Set/get whether the {@link ionic.directive:ionNavBackButton} is shown
  2518. * (if it exists and there is a previous view that can be navigated to).
  2519. * @param {boolean=} show Whether to show the back button.
  2520. * @returns {boolean} Whether the back button is shown.
  2521. */
  2522. 'showBackButton',
  2523. /**
  2524. * @ngdoc method
  2525. * @name $ionicNavBarDelegate#showBar
  2526. * @description
  2527. * Set/get whether the {@link ionic.directive:ionNavBar} is shown.
  2528. * @param {boolean} show Whether to show the bar.
  2529. * @returns {boolean} Whether the bar is shown.
  2530. */
  2531. 'showBar',
  2532. /**
  2533. * @ngdoc method
  2534. * @name $ionicNavBarDelegate#title
  2535. * @description
  2536. * Set the title for the {@link ionic.directive:ionNavBar}.
  2537. * @param {string} title The new title to show.
  2538. */
  2539. 'title',
  2540. // DEPRECATED, as of v1.0.0-beta14 -------
  2541. 'changeTitle',
  2542. 'setTitle',
  2543. 'getTitle',
  2544. 'back',
  2545. 'getPreviousTitle'
  2546. // END DEPRECATED -------
  2547. ]));
  2548. IonicModule
  2549. .service('$ionicNavViewDelegate', ionic.DelegateService([
  2550. 'clearCache'
  2551. ]));
  2552. /**
  2553. * @ngdoc service
  2554. * @name $ionicPlatform
  2555. * @module ionic
  2556. * @description
  2557. * An angular abstraction of {@link ionic.utility:ionic.Platform}.
  2558. *
  2559. * Used to detect the current platform, as well as do things like override the
  2560. * Android back button in PhoneGap/Cordova.
  2561. */
  2562. IonicModule
  2563. .constant('IONIC_BACK_PRIORITY', {
  2564. view: 100,
  2565. sideMenu: 150,
  2566. modal: 200,
  2567. actionSheet: 300,
  2568. popup: 400,
  2569. loading: 500
  2570. })
  2571. .provider('$ionicPlatform', function() {
  2572. return {
  2573. $get: ['$q', '$ionicScrollDelegate', function($q, $ionicScrollDelegate) {
  2574. var self = {
  2575. /**
  2576. * @ngdoc method
  2577. * @name $ionicPlatform#onHardwareBackButton
  2578. * @description
  2579. * Some platforms have a hardware back button, so this is one way to
  2580. * bind to it.
  2581. * @param {function} callback the callback to trigger when this event occurs
  2582. */
  2583. onHardwareBackButton: function(cb) {
  2584. ionic.Platform.ready(function() {
  2585. document.addEventListener('backbutton', cb, false);
  2586. });
  2587. },
  2588. /**
  2589. * @ngdoc method
  2590. * @name $ionicPlatform#offHardwareBackButton
  2591. * @description
  2592. * Remove an event listener for the backbutton.
  2593. * @param {function} callback The listener function that was
  2594. * originally bound.
  2595. */
  2596. offHardwareBackButton: function(fn) {
  2597. ionic.Platform.ready(function() {
  2598. document.removeEventListener('backbutton', fn);
  2599. });
  2600. },
  2601. /**
  2602. * @ngdoc method
  2603. * @name $ionicPlatform#registerBackButtonAction
  2604. * @description
  2605. * Register a hardware back button action. Only one action will execute
  2606. * when the back button is clicked, so this method decides which of
  2607. * the registered back button actions has the highest priority.
  2608. *
  2609. * For example, if an actionsheet is showing, the back button should
  2610. * close the actionsheet, but it should not also go back a page view
  2611. * or close a modal which may be open.
  2612. *
  2613. * The priorities for the existing back button hooks are as follows:
  2614. * Return to previous view = 100
  2615. * Close side menu = 150
  2616. * Dismiss modal = 200
  2617. * Close action sheet = 300
  2618. * Dismiss popup = 400
  2619. * Dismiss loading overlay = 500
  2620. *
  2621. * Your back button action will override each of the above actions
  2622. * whose priority is less than the priority you provide. For example,
  2623. * an action assigned a priority of 101 will override the 'return to
  2624. * previous view' action, but not any of the other actions.
  2625. *
  2626. * @param {function} callback Called when the back button is pressed,
  2627. * if this listener is the highest priority.
  2628. * @param {number} priority Only the highest priority will execute.
  2629. * @param {*=} actionId The id to assign this action. Default: a
  2630. * random unique id.
  2631. * @returns {function} A function that, when called, will deregister
  2632. * this backButtonAction.
  2633. */
  2634. $backButtonActions: {},
  2635. registerBackButtonAction: function(fn, priority, actionId) {
  2636. if (!self._hasBackButtonHandler) {
  2637. // add a back button listener if one hasn't been setup yet
  2638. self.$backButtonActions = {};
  2639. self.onHardwareBackButton(self.hardwareBackButtonClick);
  2640. self._hasBackButtonHandler = true;
  2641. }
  2642. var action = {
  2643. id: (actionId ? actionId : ionic.Utils.nextUid()),
  2644. priority: (priority ? priority : 0),
  2645. fn: fn
  2646. };
  2647. self.$backButtonActions[action.id] = action;
  2648. // return a function to de-register this back button action
  2649. return function() {
  2650. delete self.$backButtonActions[action.id];
  2651. };
  2652. },
  2653. /**
  2654. * @private
  2655. */
  2656. hardwareBackButtonClick: function(e) {
  2657. // loop through all the registered back button actions
  2658. // and only run the last one of the highest priority
  2659. var priorityAction, actionId;
  2660. for (actionId in self.$backButtonActions) {
  2661. if (!priorityAction || self.$backButtonActions[actionId].priority >= priorityAction.priority) {
  2662. priorityAction = self.$backButtonActions[actionId];
  2663. }
  2664. }
  2665. if (priorityAction) {
  2666. priorityAction.fn(e);
  2667. return priorityAction;
  2668. }
  2669. },
  2670. is: function(type) {
  2671. return ionic.Platform.is(type);
  2672. },
  2673. /**
  2674. * @ngdoc method
  2675. * @name $ionicPlatform#on
  2676. * @description
  2677. * Add Cordova event listeners, such as `pause`, `resume`, `volumedownbutton`, `batterylow`,
  2678. * `offline`, etc. More information about available event types can be found in
  2679. * [Cordova's event documentation](https://cordova.apache.org/docs/en/latest/cordova/events/events.html).
  2680. * @param {string} type Cordova [event type](https://cordova.apache.org/docs/en/latest/cordova/events/events.html).
  2681. * @param {function} callback Called when the Cordova event is fired.
  2682. * @returns {function} Returns a deregistration function to remove the event listener.
  2683. */
  2684. on: function(type, cb) {
  2685. ionic.Platform.ready(function() {
  2686. document.addEventListener(type, cb, false);
  2687. });
  2688. return function() {
  2689. ionic.Platform.ready(function() {
  2690. document.removeEventListener(type, cb);
  2691. });
  2692. };
  2693. },
  2694. /**
  2695. * @ngdoc method
  2696. * @name $ionicPlatform#ready
  2697. * @description
  2698. * Trigger a callback once the device is ready,
  2699. * or immediately if the device is already ready.
  2700. * @param {function=} callback The function to call.
  2701. * @returns {promise} A promise which is resolved when the device is ready.
  2702. */
  2703. ready: function(cb) {
  2704. var q = $q.defer();
  2705. ionic.Platform.ready(function() {
  2706. window.addEventListener('statusTap', function() {
  2707. $ionicScrollDelegate.scrollTop(true);
  2708. });
  2709. q.resolve();
  2710. cb && cb();
  2711. });
  2712. return q.promise;
  2713. }
  2714. };
  2715. return self;
  2716. }]
  2717. };
  2718. });
  2719. /**
  2720. * @ngdoc service
  2721. * @name $ionicPopover
  2722. * @module ionic
  2723. * @description
  2724. *
  2725. * Related: {@link ionic.controller:ionicPopover ionicPopover controller}.
  2726. *
  2727. * The Popover is a view that floats above an app’s content. Popovers provide an
  2728. * easy way to present or gather information from the user and are
  2729. * commonly used in the following situations:
  2730. *
  2731. * - Show more info about the current view
  2732. * - Select a commonly used tool or configuration
  2733. * - Present a list of actions to perform inside one of your views
  2734. *
  2735. * Put the content of the popover inside of an `<ion-popover-view>` element.
  2736. *
  2737. * @usage
  2738. * ```html
  2739. * <p>
  2740. * <button ng-click="openPopover($event)">Open Popover</button>
  2741. * </p>
  2742. *
  2743. * <script id="my-popover.html" type="text/ng-template">
  2744. * <ion-popover-view>
  2745. * <ion-header-bar>
  2746. * <h1 class="title">My Popover Title</h1>
  2747. * </ion-header-bar>
  2748. * <ion-content>
  2749. * Hello!
  2750. * </ion-content>
  2751. * </ion-popover-view>
  2752. * </script>
  2753. * ```
  2754. * ```js
  2755. * angular.module('testApp', ['ionic'])
  2756. * .controller('MyController', function($scope, $ionicPopover) {
  2757. *
  2758. * // .fromTemplate() method
  2759. * 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>';
  2760. *
  2761. * $scope.popover = $ionicPopover.fromTemplate(template, {
  2762. * scope: $scope
  2763. * });
  2764. *
  2765. * // .fromTemplateUrl() method
  2766. * $ionicPopover.fromTemplateUrl('my-popover.html', {
  2767. * scope: $scope
  2768. * }).then(function(popover) {
  2769. * $scope.popover = popover;
  2770. * });
  2771. *
  2772. *
  2773. * $scope.openPopover = function($event) {
  2774. * $scope.popover.show($event);
  2775. * };
  2776. * $scope.closePopover = function() {
  2777. * $scope.popover.hide();
  2778. * };
  2779. * //Cleanup the popover when we're done with it!
  2780. * $scope.$on('$destroy', function() {
  2781. * $scope.popover.remove();
  2782. * });
  2783. * // Execute action on hidden popover
  2784. * $scope.$on('popover.hidden', function() {
  2785. * // Execute action
  2786. * });
  2787. * // Execute action on remove popover
  2788. * $scope.$on('popover.removed', function() {
  2789. * // Execute action
  2790. * });
  2791. * });
  2792. * ```
  2793. */
  2794. IonicModule
  2795. .factory('$ionicPopover', ['$ionicModal', '$ionicPosition', '$document', '$window',
  2796. function($ionicModal, $ionicPosition, $document, $window) {
  2797. var POPOVER_BODY_PADDING = 6;
  2798. var POPOVER_OPTIONS = {
  2799. viewType: 'popover',
  2800. hideDelay: 1,
  2801. animation: 'none',
  2802. positionView: positionView
  2803. };
  2804. function positionView(target, popoverEle) {
  2805. var targetEle = jqLite(target.target || target);
  2806. var buttonOffset = $ionicPosition.offset(targetEle);
  2807. var popoverWidth = popoverEle.prop('offsetWidth');
  2808. var popoverHeight = popoverEle.prop('offsetHeight');
  2809. // Use innerWidth and innerHeight, because clientWidth and clientHeight
  2810. // doesn't work consistently for body on all platforms
  2811. var bodyWidth = $window.innerWidth;
  2812. var bodyHeight = $window.innerHeight;
  2813. var popoverCSS = {
  2814. left: buttonOffset.left + buttonOffset.width / 2 - popoverWidth / 2
  2815. };
  2816. var arrowEle = jqLite(popoverEle[0].querySelector('.popover-arrow'));
  2817. if (popoverCSS.left < POPOVER_BODY_PADDING) {
  2818. popoverCSS.left = POPOVER_BODY_PADDING;
  2819. } else if (popoverCSS.left + popoverWidth + POPOVER_BODY_PADDING > bodyWidth) {
  2820. popoverCSS.left = bodyWidth - popoverWidth - POPOVER_BODY_PADDING;
  2821. }
  2822. // If the popover when popped down stretches past bottom of screen,
  2823. // make it pop up if there's room above
  2824. if (buttonOffset.top + buttonOffset.height + popoverHeight > bodyHeight &&
  2825. buttonOffset.top - popoverHeight > 0) {
  2826. popoverCSS.top = buttonOffset.top - popoverHeight;
  2827. popoverEle.addClass('popover-bottom');
  2828. } else {
  2829. popoverCSS.top = buttonOffset.top + buttonOffset.height;
  2830. popoverEle.removeClass('popover-bottom');
  2831. }
  2832. arrowEle.css({
  2833. left: buttonOffset.left + buttonOffset.width / 2 -
  2834. arrowEle.prop('offsetWidth') / 2 - popoverCSS.left + 'px'
  2835. });
  2836. popoverEle.css({
  2837. top: popoverCSS.top + 'px',
  2838. left: popoverCSS.left + 'px',
  2839. marginLeft: '0',
  2840. opacity: '1'
  2841. });
  2842. }
  2843. /**
  2844. * @ngdoc controller
  2845. * @name ionicPopover
  2846. * @module ionic
  2847. * @description
  2848. * Instantiated by the {@link ionic.service:$ionicPopover} service.
  2849. *
  2850. * Be sure to call [remove()](#remove) when you are done with each popover
  2851. * to clean it up and avoid memory leaks.
  2852. *
  2853. * Note: a popover will broadcast 'popover.shown', 'popover.hidden', and 'popover.removed' events from its originating
  2854. * scope, passing in itself as an event argument. Both the popover.removed and popover.hidden events are
  2855. * called when the popover is removed.
  2856. */
  2857. /**
  2858. * @ngdoc method
  2859. * @name ionicPopover#initialize
  2860. * @description Creates a new popover controller instance.
  2861. * @param {object} options An options object with the following properties:
  2862. * - `{object=}` `scope` The scope to be a child of.
  2863. * Default: creates a child of $rootScope.
  2864. * - `{boolean=}` `focusFirstInput` Whether to autofocus the first input of
  2865. * the popover when shown. Default: false.
  2866. * - `{boolean=}` `backdropClickToClose` Whether to close the popover on clicking the backdrop.
  2867. * Default: true.
  2868. * - `{boolean=}` `hardwareBackButtonClose` Whether the popover can be closed using the hardware
  2869. * back button on Android and similar devices. Default: true.
  2870. */
  2871. /**
  2872. * @ngdoc method
  2873. * @name ionicPopover#show
  2874. * @description Show this popover instance.
  2875. * @param {$event} $event The $event or target element which the popover should align
  2876. * itself next to.
  2877. * @returns {promise} A promise which is resolved when the popover is finished animating in.
  2878. */
  2879. /**
  2880. * @ngdoc method
  2881. * @name ionicPopover#hide
  2882. * @description Hide this popover instance.
  2883. * @returns {promise} A promise which is resolved when the popover is finished animating out.
  2884. */
  2885. /**
  2886. * @ngdoc method
  2887. * @name ionicPopover#remove
  2888. * @description Remove this popover instance from the DOM and clean up.
  2889. * @returns {promise} A promise which is resolved when the popover is finished animating out.
  2890. */
  2891. /**
  2892. * @ngdoc method
  2893. * @name ionicPopover#isShown
  2894. * @returns boolean Whether this popover is currently shown.
  2895. */
  2896. return {
  2897. /**
  2898. * @ngdoc method
  2899. * @name $ionicPopover#fromTemplate
  2900. * @param {string} templateString The template string to use as the popovers's
  2901. * content.
  2902. * @param {object} options Options to be passed to the initialize method.
  2903. * @returns {object} An instance of an {@link ionic.controller:ionicPopover}
  2904. * controller (ionicPopover is built on top of $ionicPopover).
  2905. */
  2906. fromTemplate: function(templateString, options) {
  2907. return $ionicModal.fromTemplate(templateString, ionic.Utils.extend({}, POPOVER_OPTIONS, options));
  2908. },
  2909. /**
  2910. * @ngdoc method
  2911. * @name $ionicPopover#fromTemplateUrl
  2912. * @param {string} templateUrl The url to load the template from.
  2913. * @param {object} options Options to be passed to the initialize method.
  2914. * @returns {promise} A promise that will be resolved with an instance of
  2915. * an {@link ionic.controller:ionicPopover} controller (ionicPopover is built on top of $ionicPopover).
  2916. */
  2917. fromTemplateUrl: function(url, options) {
  2918. return $ionicModal.fromTemplateUrl(url, ionic.Utils.extend({}, POPOVER_OPTIONS, options));
  2919. }
  2920. };
  2921. }]);
  2922. var POPUP_TPL =
  2923. '<div class="popup-container" ng-class="cssClass">' +
  2924. '<div class="popup">' +
  2925. '<div class="popup-head">' +
  2926. '<h3 class="popup-title" ng-bind-html="title"></h3>' +
  2927. '<h5 class="popup-sub-title" ng-bind-html="subTitle" ng-if="subTitle"></h5>' +
  2928. '</div>' +
  2929. '<div class="popup-body">' +
  2930. '</div>' +
  2931. '<div class="popup-buttons" ng-show="buttons.length">' +
  2932. '<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>' +
  2933. '</div>' +
  2934. '</div>' +
  2935. '</div>';
  2936. /**
  2937. * @ngdoc service
  2938. * @name $ionicPopup
  2939. * @module ionic
  2940. * @restrict E
  2941. * @codepen zkmhJ
  2942. * @description
  2943. *
  2944. * The Ionic Popup service allows programmatically creating and showing popup
  2945. * windows that require the user to respond in order to continue.
  2946. *
  2947. * The popup system has support for more flexible versions of the built in `alert()`, `prompt()`,
  2948. * and `confirm()` functions that users are used to, in addition to allowing popups with completely
  2949. * custom content and look.
  2950. *
  2951. * An input can be given an `autofocus` attribute so it automatically receives focus when
  2952. * the popup first shows. However, depending on certain use-cases this can cause issues with
  2953. * the tap/click system, which is why Ionic prefers using the `autofocus` attribute as
  2954. * an opt-in feature and not the default.
  2955. *
  2956. * @usage
  2957. * A few basic examples, see below for details about all of the options available.
  2958. *
  2959. * ```js
  2960. *angular.module('mySuperApp', ['ionic'])
  2961. *.controller('PopupCtrl',function($scope, $ionicPopup, $timeout) {
  2962. *
  2963. * // Triggered on a button click, or some other target
  2964. * $scope.showPopup = function() {
  2965. * $scope.data = {};
  2966. *
  2967. * // An elaborate, custom popup
  2968. * var myPopup = $ionicPopup.show({
  2969. * template: '<input type="password" ng-model="data.wifi">',
  2970. * title: 'Enter Wi-Fi Password',
  2971. * subTitle: 'Please use normal things',
  2972. * scope: $scope,
  2973. * buttons: [
  2974. * { text: 'Cancel' },
  2975. * {
  2976. * text: '<b>Save</b>',
  2977. * type: 'button-positive',
  2978. * onTap: function(e) {
  2979. * if (!$scope.data.wifi) {
  2980. * //don't allow the user to close unless he enters wifi password
  2981. * e.preventDefault();
  2982. * } else {
  2983. * return $scope.data.wifi;
  2984. * }
  2985. * }
  2986. * }
  2987. * ]
  2988. * });
  2989. *
  2990. * myPopup.then(function(res) {
  2991. * console.log('Tapped!', res);
  2992. * });
  2993. *
  2994. * $timeout(function() {
  2995. * myPopup.close(); //close the popup after 3 seconds for some reason
  2996. * }, 3000);
  2997. * };
  2998. *
  2999. * // A confirm dialog
  3000. * $scope.showConfirm = function() {
  3001. * var confirmPopup = $ionicPopup.confirm({
  3002. * title: 'Consume Ice Cream',
  3003. * template: 'Are you sure you want to eat this ice cream?'
  3004. * });
  3005. *
  3006. * confirmPopup.then(function(res) {
  3007. * if(res) {
  3008. * console.log('You are sure');
  3009. * } else {
  3010. * console.log('You are not sure');
  3011. * }
  3012. * });
  3013. * };
  3014. *
  3015. * // An alert dialog
  3016. * $scope.showAlert = function() {
  3017. * var alertPopup = $ionicPopup.alert({
  3018. * title: 'Don\'t eat that!',
  3019. * template: 'It might taste good'
  3020. * });
  3021. *
  3022. * alertPopup.then(function(res) {
  3023. * console.log('Thank you for not eating my delicious ice cream cone');
  3024. * });
  3025. * };
  3026. *});
  3027. *```
  3028. */
  3029. IonicModule
  3030. .factory('$ionicPopup', [
  3031. '$ionicTemplateLoader',
  3032. '$ionicBackdrop',
  3033. '$q',
  3034. '$timeout',
  3035. '$rootScope',
  3036. '$ionicBody',
  3037. '$compile',
  3038. '$ionicPlatform',
  3039. '$ionicModal',
  3040. 'IONIC_BACK_PRIORITY',
  3041. function($ionicTemplateLoader, $ionicBackdrop, $q, $timeout, $rootScope, $ionicBody, $compile, $ionicPlatform, $ionicModal, IONIC_BACK_PRIORITY) {
  3042. //TODO allow this to be configured
  3043. var config = {
  3044. stackPushDelay: 75
  3045. };
  3046. var popupStack = [];
  3047. var $ionicPopup = {
  3048. /**
  3049. * @ngdoc method
  3050. * @description
  3051. * Show a complex popup. This is the master show function for all popups.
  3052. *
  3053. * A complex popup has a `buttons` array, with each button having a `text` and `type`
  3054. * field, in addition to an `onTap` function. The `onTap` function, called when
  3055. * the corresponding button on the popup is tapped, will by default close the popup
  3056. * and resolve the popup promise with its return value. If you wish to prevent the
  3057. * default and keep the popup open on button tap, call `event.preventDefault()` on the
  3058. * passed in tap event. Details below.
  3059. *
  3060. * @name $ionicPopup#show
  3061. * @param {object} options The options for the new popup, of the form:
  3062. *
  3063. * ```
  3064. * {
  3065. * title: '', // String. The title of the popup.
  3066. * cssClass: '', // String, The custom CSS class name
  3067. * subTitle: '', // String (optional). The sub-title of the popup.
  3068. * template: '', // String (optional). The html template to place in the popup body.
  3069. * templateUrl: '', // String (optional). The URL of an html template to place in the popup body.
  3070. * scope: null, // Scope (optional). A scope to link to the popup content.
  3071. * buttons: [{ // Array[Object] (optional). Buttons to place in the popup footer.
  3072. * text: 'Cancel',
  3073. * type: 'button-default',
  3074. * onTap: function(e) {
  3075. * // e.preventDefault() will stop the popup from closing when tapped.
  3076. * e.preventDefault();
  3077. * }
  3078. * }, {
  3079. * text: 'OK',
  3080. * type: 'button-positive',
  3081. * onTap: function(e) {
  3082. * // Returning a value will cause the promise to resolve with the given value.
  3083. * return scope.data.response;
  3084. * }
  3085. * }]
  3086. * }
  3087. * ```
  3088. *
  3089. * @returns {object} A promise which is resolved when the popup is closed. Has an additional
  3090. * `close` function, which can be used to programmatically close the popup.
  3091. */
  3092. show: showPopup,
  3093. /**
  3094. * @ngdoc method
  3095. * @name $ionicPopup#alert
  3096. * @description Show a simple alert popup with a message and one button that the user can
  3097. * tap to close the popup.
  3098. *
  3099. * @param {object} options The options for showing the alert, of the form:
  3100. *
  3101. * ```
  3102. * {
  3103. * title: '', // String. The title of the popup.
  3104. * cssClass: '', // String, The custom CSS class name
  3105. * subTitle: '', // String (optional). The sub-title of the popup.
  3106. * template: '', // String (optional). The html template to place in the popup body.
  3107. * templateUrl: '', // String (optional). The URL of an html template to place in the popup body.
  3108. * okText: '', // String (default: 'OK'). The text of the OK button.
  3109. * okType: '', // String (default: 'button-positive'). The type of the OK button.
  3110. * }
  3111. * ```
  3112. *
  3113. * @returns {object} A promise which is resolved when the popup is closed. Has one additional
  3114. * function `close`, which can be called with any value to programmatically close the popup
  3115. * with the given value.
  3116. */
  3117. alert: showAlert,
  3118. /**
  3119. * @ngdoc method
  3120. * @name $ionicPopup#confirm
  3121. * @description
  3122. * Show a simple confirm popup with a Cancel and OK button.
  3123. *
  3124. * Resolves the promise with true if the user presses the OK button, and false if the
  3125. * user presses the Cancel button.
  3126. *
  3127. * @param {object} options The options for showing the confirm popup, of the form:
  3128. *
  3129. * ```
  3130. * {
  3131. * title: '', // String. The title of the popup.
  3132. * cssClass: '', // String, The custom CSS class name
  3133. * subTitle: '', // String (optional). The sub-title of the popup.
  3134. * template: '', // String (optional). The html template to place in the popup body.
  3135. * templateUrl: '', // String (optional). The URL of an html template to place in the popup body.
  3136. * cancelText: '', // String (default: 'Cancel'). The text of the Cancel button.
  3137. * cancelType: '', // String (default: 'button-default'). The type of the Cancel button.
  3138. * okText: '', // String (default: 'OK'). The text of the OK button.
  3139. * okType: '', // String (default: 'button-positive'). The type of the OK button.
  3140. * }
  3141. * ```
  3142. *
  3143. * @returns {object} A promise which is resolved when the popup is closed. Has one additional
  3144. * function `close`, which can be called with any value to programmatically close the popup
  3145. * with the given value.
  3146. */
  3147. confirm: showConfirm,
  3148. /**
  3149. * @ngdoc method
  3150. * @name $ionicPopup#prompt
  3151. * @description Show a simple prompt popup, which has an input, OK button, and Cancel button.
  3152. * Resolves the promise with the value of the input if the user presses OK, and with undefined
  3153. * if the user presses Cancel.
  3154. *
  3155. * ```javascript
  3156. * $ionicPopup.prompt({
  3157. * title: 'Password Check',
  3158. * template: 'Enter your secret password',
  3159. * inputType: 'password',
  3160. * inputPlaceholder: 'Your password'
  3161. * }).then(function(res) {
  3162. * console.log('Your password is', res);
  3163. * });
  3164. * ```
  3165. * @param {object} options The options for showing the prompt popup, of the form:
  3166. *
  3167. * ```
  3168. * {
  3169. * title: '', // String. The title of the popup.
  3170. * cssClass: '', // String, The custom CSS class name
  3171. * subTitle: '', // String (optional). The sub-title of the popup.
  3172. * template: '', // String (optional). The html template to place in the popup body.
  3173. * templateUrl: '', // String (optional). The URL of an html template to place in the popup body.
  3174. * inputType: // String (default: 'text'). The type of input to use
  3175. * defaultText: // String (default: ''). The initial value placed into the input.
  3176. * maxLength: // Integer (default: null). Specify a maxlength attribute for the input.
  3177. * inputPlaceholder: // String (default: ''). A placeholder to use for the input.
  3178. * cancelText: // String (default: 'Cancel'. The text of the Cancel button.
  3179. * cancelType: // String (default: 'button-default'). The type of the Cancel button.
  3180. * okText: // String (default: 'OK'). The text of the OK button.
  3181. * okType: // String (default: 'button-positive'). The type of the OK button.
  3182. * }
  3183. * ```
  3184. *
  3185. * @returns {object} A promise which is resolved when the popup is closed. Has one additional
  3186. * function `close`, which can be called with any value to programmatically close the popup
  3187. * with the given value.
  3188. */
  3189. prompt: showPrompt,
  3190. /**
  3191. * @private for testing
  3192. */
  3193. _createPopup: createPopup,
  3194. _popupStack: popupStack
  3195. };
  3196. return $ionicPopup;
  3197. function createPopup(options) {
  3198. options = extend({
  3199. scope: null,
  3200. title: '',
  3201. buttons: []
  3202. }, options || {});
  3203. var self = {};
  3204. self.scope = (options.scope || $rootScope).$new();
  3205. self.element = jqLite(POPUP_TPL);
  3206. self.responseDeferred = $q.defer();
  3207. $ionicBody.get().appendChild(self.element[0]);
  3208. $compile(self.element)(self.scope);
  3209. extend(self.scope, {
  3210. title: options.title,
  3211. buttons: options.buttons,
  3212. subTitle: options.subTitle,
  3213. cssClass: options.cssClass,
  3214. $buttonTapped: function(button, event) {
  3215. var result = (button.onTap || noop).apply(self, [event]);
  3216. event = event.originalEvent || event; //jquery events
  3217. if (!event.defaultPrevented) {
  3218. self.responseDeferred.resolve(result);
  3219. }
  3220. }
  3221. });
  3222. $q.when(
  3223. options.templateUrl ?
  3224. $ionicTemplateLoader.load(options.templateUrl) :
  3225. (options.template || options.content || '')
  3226. ).then(function(template) {
  3227. var popupBody = jqLite(self.element[0].querySelector('.popup-body'));
  3228. if (template) {
  3229. popupBody.html(template);
  3230. $compile(popupBody.contents())(self.scope);
  3231. } else {
  3232. popupBody.remove();
  3233. }
  3234. });
  3235. self.show = function() {
  3236. if (self.isShown || self.removed) return;
  3237. $ionicModal.stack.add(self);
  3238. self.isShown = true;
  3239. ionic.requestAnimationFrame(function() {
  3240. //if hidden while waiting for raf, don't show
  3241. if (!self.isShown) return;
  3242. self.element.removeClass('popup-hidden');
  3243. self.element.addClass('popup-showing active');
  3244. focusInput(self.element);
  3245. });
  3246. };
  3247. self.hide = function(callback) {
  3248. callback = callback || noop;
  3249. if (!self.isShown) return callback();
  3250. $ionicModal.stack.remove(self);
  3251. self.isShown = false;
  3252. self.element.removeClass('active');
  3253. self.element.addClass('popup-hidden');
  3254. $timeout(callback, 250, false);
  3255. };
  3256. self.remove = function() {
  3257. if (self.removed) return;
  3258. self.hide(function() {
  3259. self.element.remove();
  3260. self.scope.$destroy();
  3261. });
  3262. self.removed = true;
  3263. };
  3264. return self;
  3265. }
  3266. function onHardwareBackButton() {
  3267. var last = popupStack[popupStack.length - 1];
  3268. last && last.responseDeferred.resolve();
  3269. }
  3270. function showPopup(options) {
  3271. var popup = $ionicPopup._createPopup(options);
  3272. var showDelay = 0;
  3273. if (popupStack.length > 0) {
  3274. showDelay = config.stackPushDelay;
  3275. $timeout(popupStack[popupStack.length - 1].hide, showDelay, false);
  3276. } else {
  3277. //Add popup-open & backdrop if this is first popup
  3278. $ionicBody.addClass('popup-open');
  3279. $ionicBackdrop.retain();
  3280. //only show the backdrop on the first popup
  3281. $ionicPopup._backButtonActionDone = $ionicPlatform.registerBackButtonAction(
  3282. onHardwareBackButton,
  3283. IONIC_BACK_PRIORITY.popup
  3284. );
  3285. }
  3286. // Expose a 'close' method on the returned promise
  3287. popup.responseDeferred.promise.close = function popupClose(result) {
  3288. if (!popup.removed) popup.responseDeferred.resolve(result);
  3289. };
  3290. //DEPRECATED: notify the promise with an object with a close method
  3291. popup.responseDeferred.notify({ close: popup.responseDeferred.close });
  3292. doShow();
  3293. return popup.responseDeferred.promise;
  3294. function doShow() {
  3295. popupStack.push(popup);
  3296. $timeout(popup.show, showDelay, false);
  3297. popup.responseDeferred.promise.then(function(result) {
  3298. var index = popupStack.indexOf(popup);
  3299. if (index !== -1) {
  3300. popupStack.splice(index, 1);
  3301. }
  3302. popup.remove();
  3303. if (popupStack.length > 0) {
  3304. popupStack[popupStack.length - 1].show();
  3305. } else {
  3306. $ionicBackdrop.release();
  3307. //Remove popup-open & backdrop if this is last popup
  3308. $timeout(function() {
  3309. // wait to remove this due to a 300ms delay native
  3310. // click which would trigging whatever was underneath this
  3311. if (!popupStack.length) {
  3312. $ionicBody.removeClass('popup-open');
  3313. }
  3314. }, 400, false);
  3315. ($ionicPopup._backButtonActionDone || noop)();
  3316. }
  3317. return result;
  3318. });
  3319. }
  3320. }
  3321. function focusInput(element) {
  3322. var focusOn = element[0].querySelector('[autofocus]');
  3323. if (focusOn) {
  3324. focusOn.focus();
  3325. }
  3326. }
  3327. function showAlert(opts) {
  3328. return showPopup(extend({
  3329. buttons: [{
  3330. text: opts.okText || 'OK',
  3331. type: opts.okType || 'button-positive',
  3332. onTap: function() {
  3333. return true;
  3334. }
  3335. }]
  3336. }, opts || {}));
  3337. }
  3338. function showConfirm(opts) {
  3339. return showPopup(extend({
  3340. buttons: [{
  3341. text: opts.cancelText || 'Cancel',
  3342. type: opts.cancelType || 'button-default',
  3343. onTap: function() { return false; }
  3344. }, {
  3345. text: opts.okText || 'OK',
  3346. type: opts.okType || 'button-positive',
  3347. onTap: function() { return true; }
  3348. }]
  3349. }, opts || {}));
  3350. }
  3351. function showPrompt(opts) {
  3352. var scope = $rootScope.$new(true);
  3353. scope.data = {};
  3354. scope.data.fieldtype = opts.inputType ? opts.inputType : 'text';
  3355. scope.data.response = opts.defaultText ? opts.defaultText : '';
  3356. scope.data.placeholder = opts.inputPlaceholder ? opts.inputPlaceholder : '';
  3357. scope.data.maxlength = opts.maxLength ? parseInt(opts.maxLength) : '';
  3358. var text = '';
  3359. if (opts.template && /<[a-z][\s\S]*>/i.test(opts.template) === false) {
  3360. text = '<span>' + opts.template + '</span>';
  3361. delete opts.template;
  3362. }
  3363. return showPopup(extend({
  3364. template: text + '<input ng-model="data.response" '
  3365. + 'type="{{ data.fieldtype }}"'
  3366. + 'maxlength="{{ data.maxlength }}"'
  3367. + 'placeholder="{{ data.placeholder }}"'
  3368. + '>',
  3369. scope: scope,
  3370. buttons: [{
  3371. text: opts.cancelText || 'Cancel',
  3372. type: opts.cancelType || 'button-default',
  3373. onTap: function() {}
  3374. }, {
  3375. text: opts.okText || 'OK',
  3376. type: opts.okType || 'button-positive',
  3377. onTap: function() {
  3378. return scope.data.response || '';
  3379. }
  3380. }]
  3381. }, opts || {}));
  3382. }
  3383. }]);
  3384. /**
  3385. * @ngdoc service
  3386. * @name $ionicPosition
  3387. * @module ionic
  3388. * @description
  3389. * A set of utility methods that can be use to retrieve position of DOM elements.
  3390. * It is meant to be used where we need to absolute-position DOM elements in
  3391. * relation to other, existing elements (this is the case for tooltips, popovers, etc.).
  3392. *
  3393. * Adapted from [AngularUI Bootstrap](https://github.com/angular-ui/bootstrap/blob/master/src/position/position.js),
  3394. * ([license](https://github.com/angular-ui/bootstrap/blob/master/LICENSE))
  3395. */
  3396. IonicModule
  3397. .factory('$ionicPosition', ['$document', '$window', function($document, $window) {
  3398. function getStyle(el, cssprop) {
  3399. if (el.currentStyle) { //IE
  3400. return el.currentStyle[cssprop];
  3401. } else if ($window.getComputedStyle) {
  3402. return $window.getComputedStyle(el)[cssprop];
  3403. }
  3404. // finally try and get inline style
  3405. return el.style[cssprop];
  3406. }
  3407. /**
  3408. * Checks if a given element is statically positioned
  3409. * @param element - raw DOM element
  3410. */
  3411. function isStaticPositioned(element) {
  3412. return (getStyle(element, 'position') || 'static') === 'static';
  3413. }
  3414. /**
  3415. * returns the closest, non-statically positioned parentOffset of a given element
  3416. * @param element
  3417. */
  3418. var parentOffsetEl = function(element) {
  3419. var docDomEl = $document[0];
  3420. var offsetParent = element.offsetParent || docDomEl;
  3421. while (offsetParent && offsetParent !== docDomEl && isStaticPositioned(offsetParent)) {
  3422. offsetParent = offsetParent.offsetParent;
  3423. }
  3424. return offsetParent || docDomEl;
  3425. };
  3426. return {
  3427. /**
  3428. * @ngdoc method
  3429. * @name $ionicPosition#position
  3430. * @description Get the current coordinates of the element, relative to the offset parent.
  3431. * Read-only equivalent of [jQuery's position function](http://api.jquery.com/position/).
  3432. * @param {element} element The element to get the position of.
  3433. * @returns {object} Returns an object containing the properties top, left, width and height.
  3434. */
  3435. position: function(element) {
  3436. var elBCR = this.offset(element);
  3437. var offsetParentBCR = { top: 0, left: 0 };
  3438. var offsetParentEl = parentOffsetEl(element[0]);
  3439. if (offsetParentEl != $document[0]) {
  3440. offsetParentBCR = this.offset(jqLite(offsetParentEl));
  3441. offsetParentBCR.top += offsetParentEl.clientTop - offsetParentEl.scrollTop;
  3442. offsetParentBCR.left += offsetParentEl.clientLeft - offsetParentEl.scrollLeft;
  3443. }
  3444. var boundingClientRect = element[0].getBoundingClientRect();
  3445. return {
  3446. width: boundingClientRect.width || element.prop('offsetWidth'),
  3447. height: boundingClientRect.height || element.prop('offsetHeight'),
  3448. top: elBCR.top - offsetParentBCR.top,
  3449. left: elBCR.left - offsetParentBCR.left
  3450. };
  3451. },
  3452. /**
  3453. * @ngdoc method
  3454. * @name $ionicPosition#offset
  3455. * @description Get the current coordinates of the element, relative to the document.
  3456. * Read-only equivalent of [jQuery's offset function](http://api.jquery.com/offset/).
  3457. * @param {element} element The element to get the offset of.
  3458. * @returns {object} Returns an object containing the properties top, left, width and height.
  3459. */
  3460. offset: function(element) {
  3461. var boundingClientRect = element[0].getBoundingClientRect();
  3462. return {
  3463. width: boundingClientRect.width || element.prop('offsetWidth'),
  3464. height: boundingClientRect.height || element.prop('offsetHeight'),
  3465. top: boundingClientRect.top + ($window.pageYOffset || $document[0].documentElement.scrollTop),
  3466. left: boundingClientRect.left + ($window.pageXOffset || $document[0].documentElement.scrollLeft)
  3467. };
  3468. }
  3469. };
  3470. }]);
  3471. /**
  3472. * @ngdoc service
  3473. * @name $ionicScrollDelegate
  3474. * @module ionic
  3475. * @description
  3476. * Delegate for controlling scrollViews (created by
  3477. * {@link ionic.directive:ionContent} and
  3478. * {@link ionic.directive:ionScroll} directives).
  3479. *
  3480. * Methods called directly on the $ionicScrollDelegate service will control all scroll
  3481. * views. Use the {@link ionic.service:$ionicScrollDelegate#$getByHandle $getByHandle}
  3482. * method to control specific scrollViews.
  3483. *
  3484. * @usage
  3485. *
  3486. * ```html
  3487. * <body ng-controller="MainCtrl">
  3488. * <ion-content>
  3489. * <button ng-click="scrollTop()">Scroll to Top!</button>
  3490. * </ion-content>
  3491. * </body>
  3492. * ```
  3493. * ```js
  3494. * function MainCtrl($scope, $ionicScrollDelegate) {
  3495. * $scope.scrollTop = function() {
  3496. * $ionicScrollDelegate.scrollTop();
  3497. * };
  3498. * }
  3499. * ```
  3500. *
  3501. * Example of advanced usage, with two scroll areas using `delegate-handle`
  3502. * for fine control.
  3503. *
  3504. * ```html
  3505. * <body ng-controller="MainCtrl">
  3506. * <ion-content delegate-handle="mainScroll">
  3507. * <button ng-click="scrollMainToTop()">
  3508. * Scroll content to top!
  3509. * </button>
  3510. * <ion-scroll delegate-handle="small" style="height: 100px;">
  3511. * <button ng-click="scrollSmallToTop()">
  3512. * Scroll small area to top!
  3513. * </button>
  3514. * </ion-scroll>
  3515. * </ion-content>
  3516. * </body>
  3517. * ```
  3518. * ```js
  3519. * function MainCtrl($scope, $ionicScrollDelegate) {
  3520. * $scope.scrollMainToTop = function() {
  3521. * $ionicScrollDelegate.$getByHandle('mainScroll').scrollTop();
  3522. * };
  3523. * $scope.scrollSmallToTop = function() {
  3524. * $ionicScrollDelegate.$getByHandle('small').scrollTop();
  3525. * };
  3526. * }
  3527. * ```
  3528. */
  3529. IonicModule
  3530. .service('$ionicScrollDelegate', ionic.DelegateService([
  3531. /**
  3532. * @ngdoc method
  3533. * @name $ionicScrollDelegate#resize
  3534. * @description Tell the scrollView to recalculate the size of its container.
  3535. */
  3536. 'resize',
  3537. /**
  3538. * @ngdoc method
  3539. * @name $ionicScrollDelegate#scrollTop
  3540. * @param {boolean=} shouldAnimate Whether the scroll should animate.
  3541. */
  3542. 'scrollTop',
  3543. /**
  3544. * @ngdoc method
  3545. * @name $ionicScrollDelegate#scrollBottom
  3546. * @param {boolean=} shouldAnimate Whether the scroll should animate.
  3547. */
  3548. 'scrollBottom',
  3549. /**
  3550. * @ngdoc method
  3551. * @name $ionicScrollDelegate#scrollTo
  3552. * @param {number} left The x-value to scroll to.
  3553. * @param {number} top The y-value to scroll to.
  3554. * @param {boolean=} shouldAnimate Whether the scroll should animate.
  3555. */
  3556. 'scrollTo',
  3557. /**
  3558. * @ngdoc method
  3559. * @name $ionicScrollDelegate#scrollBy
  3560. * @param {number} left The x-offset to scroll by.
  3561. * @param {number} top The y-offset to scroll by.
  3562. * @param {boolean=} shouldAnimate Whether the scroll should animate.
  3563. */
  3564. 'scrollBy',
  3565. /**
  3566. * @ngdoc method
  3567. * @name $ionicScrollDelegate#zoomTo
  3568. * @param {number} level Level to zoom to.
  3569. * @param {boolean=} animate Whether to animate the zoom.
  3570. * @param {number=} originLeft Zoom in at given left coordinate.
  3571. * @param {number=} originTop Zoom in at given top coordinate.
  3572. */
  3573. 'zoomTo',
  3574. /**
  3575. * @ngdoc method
  3576. * @name $ionicScrollDelegate#zoomBy
  3577. * @param {number} factor The factor to zoom by.
  3578. * @param {boolean=} animate Whether to animate the zoom.
  3579. * @param {number=} originLeft Zoom in at given left coordinate.
  3580. * @param {number=} originTop Zoom in at given top coordinate.
  3581. */
  3582. 'zoomBy',
  3583. /**
  3584. * @ngdoc method
  3585. * @name $ionicScrollDelegate#getScrollPosition
  3586. * @returns {object} The scroll position of this view, with the following properties:
  3587. * - `{number}` `left` The distance the user has scrolled from the left (starts at 0).
  3588. * - `{number}` `top` The distance the user has scrolled from the top (starts at 0).
  3589. * - `{number}` `zoom` The current zoom level.
  3590. */
  3591. 'getScrollPosition',
  3592. /**
  3593. * @ngdoc method
  3594. * @name $ionicScrollDelegate#anchorScroll
  3595. * @description Tell the scrollView to scroll to the element with an id
  3596. * matching window.location.hash.
  3597. *
  3598. * If no matching element is found, it will scroll to top.
  3599. *
  3600. * @param {boolean=} shouldAnimate Whether the scroll should animate.
  3601. */
  3602. 'anchorScroll',
  3603. /**
  3604. * @ngdoc method
  3605. * @name $ionicScrollDelegate#freezeScroll
  3606. * @description Does not allow this scroll view to scroll either x or y.
  3607. * @param {boolean=} shouldFreeze Should this scroll view be prevented from scrolling or not.
  3608. * @returns {boolean} If the scroll view is being prevented from scrolling or not.
  3609. */
  3610. 'freezeScroll',
  3611. /**
  3612. * @ngdoc method
  3613. * @name $ionicScrollDelegate#freezeAllScrolls
  3614. * @description Does not allow any of the app's scroll views to scroll either x or y.
  3615. * @param {boolean=} shouldFreeze Should all app scrolls be prevented from scrolling or not.
  3616. */
  3617. 'freezeAllScrolls',
  3618. /**
  3619. * @ngdoc method
  3620. * @name $ionicScrollDelegate#getScrollView
  3621. * @returns {object} The scrollView associated with this delegate.
  3622. */
  3623. 'getScrollView'
  3624. /**
  3625. * @ngdoc method
  3626. * @name $ionicScrollDelegate#$getByHandle
  3627. * @param {string} handle
  3628. * @returns `delegateInstance` A delegate instance that controls only the
  3629. * scrollViews with `delegate-handle` matching the given handle.
  3630. *
  3631. * Example: `$ionicScrollDelegate.$getByHandle('my-handle').scrollTop();`
  3632. */
  3633. ]));
  3634. /**
  3635. * @ngdoc service
  3636. * @name $ionicSideMenuDelegate
  3637. * @module ionic
  3638. *
  3639. * @description
  3640. * Delegate for controlling the {@link ionic.directive:ionSideMenus} directive.
  3641. *
  3642. * Methods called directly on the $ionicSideMenuDelegate service will control all side
  3643. * menus. Use the {@link ionic.service:$ionicSideMenuDelegate#$getByHandle $getByHandle}
  3644. * method to control specific ionSideMenus instances.
  3645. *
  3646. * @usage
  3647. *
  3648. * ```html
  3649. * <body ng-controller="MainCtrl">
  3650. * <ion-side-menus>
  3651. * <ion-side-menu-content>
  3652. * Content!
  3653. * <button ng-click="toggleLeftSideMenu()">
  3654. * Toggle Left Side Menu
  3655. * </button>
  3656. * </ion-side-menu-content>
  3657. * <ion-side-menu side="left">
  3658. * Left Menu!
  3659. * <ion-side-menu>
  3660. * </ion-side-menus>
  3661. * </body>
  3662. * ```
  3663. * ```js
  3664. * function MainCtrl($scope, $ionicSideMenuDelegate) {
  3665. * $scope.toggleLeftSideMenu = function() {
  3666. * $ionicSideMenuDelegate.toggleLeft();
  3667. * };
  3668. * }
  3669. * ```
  3670. */
  3671. IonicModule
  3672. .service('$ionicSideMenuDelegate', ionic.DelegateService([
  3673. /**
  3674. * @ngdoc method
  3675. * @name $ionicSideMenuDelegate#toggleLeft
  3676. * @description Toggle the left side menu (if it exists).
  3677. * @param {boolean=} isOpen Whether to open or close the menu.
  3678. * Default: Toggles the menu.
  3679. */
  3680. 'toggleLeft',
  3681. /**
  3682. * @ngdoc method
  3683. * @name $ionicSideMenuDelegate#toggleRight
  3684. * @description Toggle the right side menu (if it exists).
  3685. * @param {boolean=} isOpen Whether to open or close the menu.
  3686. * Default: Toggles the menu.
  3687. */
  3688. 'toggleRight',
  3689. /**
  3690. * @ngdoc method
  3691. * @name $ionicSideMenuDelegate#getOpenRatio
  3692. * @description Gets the ratio of open amount over menu width. For example, a
  3693. * menu of width 100 that is opened by 50 pixels is 50% opened, and would return
  3694. * a ratio of 0.5.
  3695. *
  3696. * @returns {float} 0 if nothing is open, between 0 and 1 if left menu is
  3697. * opened/opening, and between 0 and -1 if right menu is opened/opening.
  3698. */
  3699. 'getOpenRatio',
  3700. /**
  3701. * @ngdoc method
  3702. * @name $ionicSideMenuDelegate#isOpen
  3703. * @returns {boolean} Whether either the left or right menu is currently opened.
  3704. */
  3705. 'isOpen',
  3706. /**
  3707. * @ngdoc method
  3708. * @name $ionicSideMenuDelegate#isOpenLeft
  3709. * @returns {boolean} Whether the left menu is currently opened.
  3710. */
  3711. 'isOpenLeft',
  3712. /**
  3713. * @ngdoc method
  3714. * @name $ionicSideMenuDelegate#isOpenRight
  3715. * @returns {boolean} Whether the right menu is currently opened.
  3716. */
  3717. 'isOpenRight',
  3718. /**
  3719. * @ngdoc method
  3720. * @name $ionicSideMenuDelegate#canDragContent
  3721. * @param {boolean=} canDrag Set whether the content can or cannot be dragged to open
  3722. * side menus.
  3723. * @returns {boolean} Whether the content can be dragged to open side menus.
  3724. */
  3725. 'canDragContent',
  3726. /**
  3727. * @ngdoc method
  3728. * @name $ionicSideMenuDelegate#edgeDragThreshold
  3729. * @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:
  3730. * - 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.
  3731. * - If true is given, the default number of pixels (25) is used as the maximum allowed distance.
  3732. * - If false or 0 is given, the edge drag threshold is disabled, and dragging from anywhere on the content is allowed.
  3733. * @returns {boolean} Whether the drag can start only from within the edge of screen threshold.
  3734. */
  3735. 'edgeDragThreshold'
  3736. /**
  3737. * @ngdoc method
  3738. * @name $ionicSideMenuDelegate#$getByHandle
  3739. * @param {string} handle
  3740. * @returns `delegateInstance` A delegate instance that controls only the
  3741. * {@link ionic.directive:ionSideMenus} directives with `delegate-handle` matching
  3742. * the given handle.
  3743. *
  3744. * Example: `$ionicSideMenuDelegate.$getByHandle('my-handle').toggleLeft();`
  3745. */
  3746. ]));
  3747. /**
  3748. * @ngdoc service
  3749. * @name $ionicSlideBoxDelegate
  3750. * @module ionic
  3751. * @description
  3752. * Delegate that controls the {@link ionic.directive:ionSlideBox} directive.
  3753. *
  3754. * Methods called directly on the $ionicSlideBoxDelegate service will control all slide boxes. Use the {@link ionic.service:$ionicSlideBoxDelegate#$getByHandle $getByHandle}
  3755. * method to control specific slide box instances.
  3756. *
  3757. * @usage
  3758. *
  3759. * ```html
  3760. * <ion-view>
  3761. * <ion-slide-box>
  3762. * <ion-slide>
  3763. * <div class="box blue">
  3764. * <button ng-click="nextSlide()">Next slide!</button>
  3765. * </div>
  3766. * </ion-slide>
  3767. * <ion-slide>
  3768. * <div class="box red">
  3769. * Slide 2!
  3770. * </div>
  3771. * </ion-slide>
  3772. * </ion-slide-box>
  3773. * </ion-view>
  3774. * ```
  3775. * ```js
  3776. * function MyCtrl($scope, $ionicSlideBoxDelegate) {
  3777. * $scope.nextSlide = function() {
  3778. * $ionicSlideBoxDelegate.next();
  3779. * }
  3780. * }
  3781. * ```
  3782. */
  3783. IonicModule
  3784. .service('$ionicSlideBoxDelegate', ionic.DelegateService([
  3785. /**
  3786. * @ngdoc method
  3787. * @name $ionicSlideBoxDelegate#update
  3788. * @description
  3789. * Update the slidebox (for example if using Angular with ng-repeat,
  3790. * resize it for the elements inside).
  3791. */
  3792. 'update',
  3793. /**
  3794. * @ngdoc method
  3795. * @name $ionicSlideBoxDelegate#slide
  3796. * @param {number} to The index to slide to.
  3797. * @param {number=} speed The number of milliseconds the change should take.
  3798. */
  3799. 'slide',
  3800. 'select',
  3801. /**
  3802. * @ngdoc method
  3803. * @name $ionicSlideBoxDelegate#enableSlide
  3804. * @param {boolean=} shouldEnable Whether to enable sliding the slidebox.
  3805. * @returns {boolean} Whether sliding is enabled.
  3806. */
  3807. 'enableSlide',
  3808. /**
  3809. * @ngdoc method
  3810. * @name $ionicSlideBoxDelegate#previous
  3811. * @param {number=} speed The number of milliseconds the change should take.
  3812. * @description Go to the previous slide. Wraps around if at the beginning.
  3813. */
  3814. 'previous',
  3815. /**
  3816. * @ngdoc method
  3817. * @name $ionicSlideBoxDelegate#next
  3818. * @param {number=} speed The number of milliseconds the change should take.
  3819. * @description Go to the next slide. Wraps around if at the end.
  3820. */
  3821. 'next',
  3822. /**
  3823. * @ngdoc method
  3824. * @name $ionicSlideBoxDelegate#stop
  3825. * @description Stop sliding. The slideBox will not move again until
  3826. * explicitly told to do so.
  3827. */
  3828. 'stop',
  3829. 'autoPlay',
  3830. /**
  3831. * @ngdoc method
  3832. * @name $ionicSlideBoxDelegate#start
  3833. * @description Start sliding again if the slideBox was stopped.
  3834. */
  3835. 'start',
  3836. /**
  3837. * @ngdoc method
  3838. * @name $ionicSlideBoxDelegate#currentIndex
  3839. * @returns number The index of the current slide.
  3840. */
  3841. 'currentIndex',
  3842. 'selected',
  3843. /**
  3844. * @ngdoc method
  3845. * @name $ionicSlideBoxDelegate#slidesCount
  3846. * @returns number The number of slides there are currently.
  3847. */
  3848. 'slidesCount',
  3849. 'count',
  3850. 'loop'
  3851. /**
  3852. * @ngdoc method
  3853. * @name $ionicSlideBoxDelegate#$getByHandle
  3854. * @param {string} handle
  3855. * @returns `delegateInstance` A delegate instance that controls only the
  3856. * {@link ionic.directive:ionSlideBox} directives with `delegate-handle` matching
  3857. * the given handle.
  3858. *
  3859. * Example: `$ionicSlideBoxDelegate.$getByHandle('my-handle').stop();`
  3860. */
  3861. ]));
  3862. /**
  3863. * @ngdoc service
  3864. * @name $ionicTabsDelegate
  3865. * @module ionic
  3866. *
  3867. * @description
  3868. * Delegate for controlling the {@link ionic.directive:ionTabs} directive.
  3869. *
  3870. * Methods called directly on the $ionicTabsDelegate service will control all ionTabs
  3871. * directives. Use the {@link ionic.service:$ionicTabsDelegate#$getByHandle $getByHandle}
  3872. * method to control specific ionTabs instances.
  3873. *
  3874. * @usage
  3875. *
  3876. * ```html
  3877. * <body ng-controller="MyCtrl">
  3878. * <ion-tabs>
  3879. *
  3880. * <ion-tab title="Tab 1">
  3881. * Hello tab 1!
  3882. * <button ng-click="selectTabWithIndex(1)">Select tab 2!</button>
  3883. * </ion-tab>
  3884. * <ion-tab title="Tab 2">Hello tab 2!</ion-tab>
  3885. *
  3886. * </ion-tabs>
  3887. * </body>
  3888. * ```
  3889. * ```js
  3890. * function MyCtrl($scope, $ionicTabsDelegate) {
  3891. * $scope.selectTabWithIndex = function(index) {
  3892. * $ionicTabsDelegate.select(index);
  3893. * }
  3894. * }
  3895. * ```
  3896. */
  3897. IonicModule
  3898. .service('$ionicTabsDelegate', ionic.DelegateService([
  3899. /**
  3900. * @ngdoc method
  3901. * @name $ionicTabsDelegate#select
  3902. * @description Select the tab matching the given index.
  3903. *
  3904. * @param {number} index Index of the tab to select.
  3905. */
  3906. 'select',
  3907. /**
  3908. * @ngdoc method
  3909. * @name $ionicTabsDelegate#selectedIndex
  3910. * @returns `number` The index of the selected tab, or -1.
  3911. */
  3912. 'selectedIndex',
  3913. /**
  3914. * @ngdoc method
  3915. * @name $ionicTabsDelegate#showBar
  3916. * @description
  3917. * Set/get whether the {@link ionic.directive:ionTabs} is shown
  3918. * @param {boolean} show Whether to show the bar.
  3919. * @returns {boolean} Whether the bar is shown.
  3920. */
  3921. 'showBar'
  3922. /**
  3923. * @ngdoc method
  3924. * @name $ionicTabsDelegate#$getByHandle
  3925. * @param {string} handle
  3926. * @returns `delegateInstance` A delegate instance that controls only the
  3927. * {@link ionic.directive:ionTabs} directives with `delegate-handle` matching
  3928. * the given handle.
  3929. *
  3930. * Example: `$ionicTabsDelegate.$getByHandle('my-handle').select(0);`
  3931. */
  3932. ]));
  3933. // closure to keep things neat
  3934. (function() {
  3935. var templatesToCache = [];
  3936. /**
  3937. * @ngdoc service
  3938. * @name $ionicTemplateCache
  3939. * @module ionic
  3940. * @description A service that preemptively caches template files to eliminate transition flicker and boost performance.
  3941. * @usage
  3942. * State templates are cached automatically, but you can optionally cache other templates.
  3943. *
  3944. * ```js
  3945. * $ionicTemplateCache('myNgIncludeTemplate.html');
  3946. * ```
  3947. *
  3948. * Optionally disable all preemptive caching with the `$ionicConfigProvider` or individual states by setting `prefetchTemplate`
  3949. * in the `$state` definition
  3950. *
  3951. * ```js
  3952. * angular.module('myApp', ['ionic'])
  3953. * .config(function($stateProvider, $ionicConfigProvider) {
  3954. *
  3955. * // disable preemptive template caching globally
  3956. * $ionicConfigProvider.templates.prefetch(false);
  3957. *
  3958. * // disable individual states
  3959. * $stateProvider
  3960. * .state('tabs', {
  3961. * url: "/tab",
  3962. * abstract: true,
  3963. * prefetchTemplate: false,
  3964. * templateUrl: "tabs-templates/tabs.html"
  3965. * })
  3966. * .state('tabs.home', {
  3967. * url: "/home",
  3968. * views: {
  3969. * 'home-tab': {
  3970. * prefetchTemplate: false,
  3971. * templateUrl: "tabs-templates/home.html",
  3972. * controller: 'HomeTabCtrl'
  3973. * }
  3974. * }
  3975. * });
  3976. * });
  3977. * ```
  3978. */
  3979. IonicModule
  3980. .factory('$ionicTemplateCache', [
  3981. '$http',
  3982. '$templateCache',
  3983. '$timeout',
  3984. function($http, $templateCache, $timeout) {
  3985. var toCache = templatesToCache,
  3986. hasRun;
  3987. function $ionicTemplateCache(templates) {
  3988. if (typeof templates === 'undefined') {
  3989. return run();
  3990. }
  3991. if (isString(templates)) {
  3992. templates = [templates];
  3993. }
  3994. forEach(templates, function(template) {
  3995. toCache.push(template);
  3996. });
  3997. if (hasRun) {
  3998. run();
  3999. }
  4000. }
  4001. // run through methods - internal method
  4002. function run() {
  4003. var template;
  4004. $ionicTemplateCache._runCount++;
  4005. hasRun = true;
  4006. // ignore if race condition already zeroed out array
  4007. if (toCache.length === 0) return;
  4008. var i = 0;
  4009. while (i < 4 && (template = toCache.pop())) {
  4010. // note that inline templates are ignored by this request
  4011. if (isString(template)) $http.get(template, { cache: $templateCache });
  4012. i++;
  4013. }
  4014. // only preload 3 templates a second
  4015. if (toCache.length) {
  4016. $timeout(run, 1000);
  4017. }
  4018. }
  4019. // exposing for testing
  4020. $ionicTemplateCache._runCount = 0;
  4021. // default method
  4022. return $ionicTemplateCache;
  4023. }])
  4024. // Intercepts the $stateprovider.state() command to look for templateUrls that can be cached
  4025. .config([
  4026. '$stateProvider',
  4027. '$ionicConfigProvider',
  4028. function($stateProvider, $ionicConfigProvider) {
  4029. var stateProviderState = $stateProvider.state;
  4030. $stateProvider.state = function(stateName, definition) {
  4031. // don't even bother if it's disabled. note, another config may run after this, so it's not a catch-all
  4032. if (typeof definition === 'object') {
  4033. var enabled = definition.prefetchTemplate !== false && templatesToCache.length < $ionicConfigProvider.templates.maxPrefetch();
  4034. if (enabled && isString(definition.templateUrl)) templatesToCache.push(definition.templateUrl);
  4035. if (angular.isObject(definition.views)) {
  4036. for (var key in definition.views) {
  4037. enabled = definition.views[key].prefetchTemplate !== false && templatesToCache.length < $ionicConfigProvider.templates.maxPrefetch();
  4038. if (enabled && isString(definition.views[key].templateUrl)) templatesToCache.push(definition.views[key].templateUrl);
  4039. }
  4040. }
  4041. }
  4042. return stateProviderState.call($stateProvider, stateName, definition);
  4043. };
  4044. }])
  4045. // process the templateUrls collected by the $stateProvider, adding them to the cache
  4046. .run(['$ionicTemplateCache', function($ionicTemplateCache) {
  4047. $ionicTemplateCache();
  4048. }]);
  4049. })();
  4050. IonicModule
  4051. .factory('$ionicTemplateLoader', [
  4052. '$compile',
  4053. '$controller',
  4054. '$http',
  4055. '$q',
  4056. '$rootScope',
  4057. '$templateCache',
  4058. function($compile, $controller, $http, $q, $rootScope, $templateCache) {
  4059. return {
  4060. load: fetchTemplate,
  4061. compile: loadAndCompile
  4062. };
  4063. function fetchTemplate(url) {
  4064. return $http.get(url, {cache: $templateCache})
  4065. .then(function(response) {
  4066. return response.data && response.data.trim();
  4067. });
  4068. }
  4069. function loadAndCompile(options) {
  4070. options = extend({
  4071. template: '',
  4072. templateUrl: '',
  4073. scope: null,
  4074. controller: null,
  4075. locals: {},
  4076. appendTo: null
  4077. }, options || {});
  4078. var templatePromise = options.templateUrl ?
  4079. this.load(options.templateUrl) :
  4080. $q.when(options.template);
  4081. return templatePromise.then(function(template) {
  4082. var controller;
  4083. var scope = options.scope || $rootScope.$new();
  4084. //Incase template doesn't have just one root element, do this
  4085. var element = jqLite('<div>').html(template).contents();
  4086. if (options.controller) {
  4087. controller = $controller(
  4088. options.controller,
  4089. extend(options.locals, {
  4090. $scope: scope
  4091. })
  4092. );
  4093. element.children().data('$ngControllerController', controller);
  4094. }
  4095. if (options.appendTo) {
  4096. jqLite(options.appendTo).append(element);
  4097. }
  4098. $compile(element)(scope);
  4099. return {
  4100. element: element,
  4101. scope: scope
  4102. };
  4103. });
  4104. }
  4105. }]);
  4106. /**
  4107. * @private
  4108. * DEPRECATED, as of v1.0.0-beta14 -------
  4109. */
  4110. IonicModule
  4111. .factory('$ionicViewService', ['$ionicHistory', '$log', function($ionicHistory, $log) {
  4112. function warn(oldMethod, newMethod) {
  4113. $log.warn('$ionicViewService' + oldMethod + ' is deprecated, please use $ionicHistory' + newMethod + ' instead: http://ionicframework.com/docs/nightly/api/service/$ionicHistory/');
  4114. }
  4115. warn('', '');
  4116. var methodsMap = {
  4117. getCurrentView: 'currentView',
  4118. getBackView: 'backView',
  4119. getForwardView: 'forwardView',
  4120. getCurrentStateName: 'currentStateName',
  4121. nextViewOptions: 'nextViewOptions',
  4122. clearHistory: 'clearHistory'
  4123. };
  4124. forEach(methodsMap, function(newMethod, oldMethod) {
  4125. methodsMap[oldMethod] = function() {
  4126. warn('.' + oldMethod, '.' + newMethod);
  4127. return $ionicHistory[newMethod].apply(this, arguments);
  4128. };
  4129. });
  4130. return methodsMap;
  4131. }]);
  4132. /**
  4133. * @private
  4134. * TODO document
  4135. */
  4136. IonicModule.factory('$ionicViewSwitcher', [
  4137. '$timeout',
  4138. '$document',
  4139. '$q',
  4140. '$ionicClickBlock',
  4141. '$ionicConfig',
  4142. '$ionicNavBarDelegate',
  4143. function($timeout, $document, $q, $ionicClickBlock, $ionicConfig, $ionicNavBarDelegate) {
  4144. var TRANSITIONEND_EVENT = 'webkitTransitionEnd transitionend';
  4145. var DATA_NO_CACHE = '$noCache';
  4146. var DATA_DESTROY_ELE = '$destroyEle';
  4147. var DATA_ELE_IDENTIFIER = '$eleId';
  4148. var DATA_VIEW_ACCESSED = '$accessed';
  4149. var DATA_FALLBACK_TIMER = '$fallbackTimer';
  4150. var DATA_VIEW = '$viewData';
  4151. var NAV_VIEW_ATTR = 'nav-view';
  4152. var VIEW_STATUS_ACTIVE = 'active';
  4153. var VIEW_STATUS_CACHED = 'cached';
  4154. var VIEW_STATUS_STAGED = 'stage';
  4155. var transitionCounter = 0;
  4156. var nextTransition, nextDirection;
  4157. ionic.transition = ionic.transition || {};
  4158. ionic.transition.isActive = false;
  4159. var isActiveTimer;
  4160. var cachedAttr = ionic.DomUtil.cachedAttr;
  4161. var transitionPromises = [];
  4162. var defaultTimeout = 1100;
  4163. var ionicViewSwitcher = {
  4164. create: function(navViewCtrl, viewLocals, enteringView, leavingView, renderStart, renderEnd) {
  4165. // get a reference to an entering/leaving element if they exist
  4166. // loop through to see if the view is already in the navViewElement
  4167. var enteringEle, leavingEle;
  4168. var transitionId = ++transitionCounter;
  4169. var alreadyInDom;
  4170. var switcher = {
  4171. init: function(registerData, callback) {
  4172. ionicViewSwitcher.isTransitioning(true);
  4173. switcher.loadViewElements(registerData);
  4174. switcher.render(registerData, function() {
  4175. callback && callback();
  4176. });
  4177. },
  4178. loadViewElements: function(registerData) {
  4179. var x, l, viewEle;
  4180. var viewElements = navViewCtrl.getViewElements();
  4181. var enteringEleIdentifier = getViewElementIdentifier(viewLocals, enteringView);
  4182. var navViewActiveEleId = navViewCtrl.activeEleId();
  4183. for (x = 0, l = viewElements.length; x < l; x++) {
  4184. viewEle = viewElements.eq(x);
  4185. if (viewEle.data(DATA_ELE_IDENTIFIER) === enteringEleIdentifier) {
  4186. // we found an existing element in the DOM that should be entering the view
  4187. if (viewEle.data(DATA_NO_CACHE)) {
  4188. // the existing element should not be cached, don't use it
  4189. viewEle.data(DATA_ELE_IDENTIFIER, enteringEleIdentifier + ionic.Utils.nextUid());
  4190. viewEle.data(DATA_DESTROY_ELE, true);
  4191. } else {
  4192. enteringEle = viewEle;
  4193. }
  4194. } else if (isDefined(navViewActiveEleId) && viewEle.data(DATA_ELE_IDENTIFIER) === navViewActiveEleId) {
  4195. leavingEle = viewEle;
  4196. }
  4197. if (enteringEle && leavingEle) break;
  4198. }
  4199. alreadyInDom = !!enteringEle;
  4200. if (!alreadyInDom) {
  4201. // still no existing element to use
  4202. // create it using existing template/scope/locals
  4203. enteringEle = registerData.ele || ionicViewSwitcher.createViewEle(viewLocals);
  4204. // existing elements in the DOM are looked up by their state name and state id
  4205. enteringEle.data(DATA_ELE_IDENTIFIER, enteringEleIdentifier);
  4206. }
  4207. if (renderEnd) {
  4208. navViewCtrl.activeEleId(enteringEleIdentifier);
  4209. }
  4210. registerData.ele = null;
  4211. },
  4212. render: function(registerData, callback) {
  4213. if (alreadyInDom) {
  4214. // it was already found in the DOM, just reconnect the scope
  4215. ionic.Utils.reconnectScope(enteringEle.scope());
  4216. } else {
  4217. // the entering element is not already in the DOM
  4218. // set that the entering element should be "staged" and its
  4219. // styles of where this element will go before it hits the DOM
  4220. navViewAttr(enteringEle, VIEW_STATUS_STAGED);
  4221. var enteringData = getTransitionData(viewLocals, enteringEle, registerData.direction, enteringView);
  4222. var transitionFn = $ionicConfig.transitions.views[enteringData.transition] || $ionicConfig.transitions.views.none;
  4223. transitionFn(enteringEle, null, enteringData.direction, true).run(0);
  4224. enteringEle.data(DATA_VIEW, {
  4225. viewId: enteringData.viewId,
  4226. historyId: enteringData.historyId,
  4227. stateName: enteringData.stateName,
  4228. stateParams: enteringData.stateParams
  4229. });
  4230. // if the current state has cache:false
  4231. // or the element has cache-view="false" attribute
  4232. if (viewState(viewLocals).cache === false || viewState(viewLocals).cache === 'false' ||
  4233. enteringEle.attr('cache-view') == 'false' || $ionicConfig.views.maxCache() === 0) {
  4234. enteringEle.data(DATA_NO_CACHE, true);
  4235. }
  4236. // append the entering element to the DOM, create a new scope and run link
  4237. var viewScope = navViewCtrl.appendViewElement(enteringEle, viewLocals);
  4238. delete enteringData.direction;
  4239. delete enteringData.transition;
  4240. viewScope.$emit('$ionicView.loaded', enteringData);
  4241. }
  4242. // update that this view was just accessed
  4243. enteringEle.data(DATA_VIEW_ACCESSED, Date.now());
  4244. callback && callback();
  4245. },
  4246. transition: function(direction, enableBack, allowAnimate) {
  4247. var deferred;
  4248. var enteringData = getTransitionData(viewLocals, enteringEle, direction, enteringView);
  4249. var leavingData = extend(extend({}, enteringData), getViewData(leavingView));
  4250. enteringData.transitionId = leavingData.transitionId = transitionId;
  4251. enteringData.fromCache = !!alreadyInDom;
  4252. enteringData.enableBack = !!enableBack;
  4253. enteringData.renderStart = renderStart;
  4254. enteringData.renderEnd = renderEnd;
  4255. cachedAttr(enteringEle.parent(), 'nav-view-transition', enteringData.transition);
  4256. cachedAttr(enteringEle.parent(), 'nav-view-direction', enteringData.direction);
  4257. // cancel any previous transition complete fallbacks
  4258. $timeout.cancel(enteringEle.data(DATA_FALLBACK_TIMER));
  4259. // get the transition ready and see if it'll animate
  4260. var transitionFn = $ionicConfig.transitions.views[enteringData.transition] || $ionicConfig.transitions.views.none;
  4261. var viewTransition = transitionFn(enteringEle, leavingEle, enteringData.direction,
  4262. enteringData.shouldAnimate && allowAnimate && renderEnd);
  4263. if (viewTransition.shouldAnimate) {
  4264. // attach transitionend events (and fallback timer)
  4265. enteringEle.on(TRANSITIONEND_EVENT, completeOnTransitionEnd);
  4266. enteringEle.data(DATA_FALLBACK_TIMER, $timeout(transitionComplete, defaultTimeout));
  4267. $ionicClickBlock.show(defaultTimeout);
  4268. }
  4269. if (renderStart) {
  4270. // notify the views "before" the transition starts
  4271. switcher.emit('before', enteringData, leavingData);
  4272. // stage entering element, opacity 0, no transition duration
  4273. navViewAttr(enteringEle, VIEW_STATUS_STAGED);
  4274. // render the elements in the correct location for their starting point
  4275. viewTransition.run(0);
  4276. }
  4277. if (renderEnd) {
  4278. // create a promise so we can keep track of when all transitions finish
  4279. // only required if this transition should complete
  4280. deferred = $q.defer();
  4281. transitionPromises.push(deferred.promise);
  4282. }
  4283. if (renderStart && renderEnd) {
  4284. // CSS "auto" transitioned, not manually transitioned
  4285. // wait a frame so the styles apply before auto transitioning
  4286. $timeout(function() {
  4287. ionic.requestAnimationFrame(onReflow);
  4288. });
  4289. } else if (!renderEnd) {
  4290. // just the start of a manual transition
  4291. // but it will not render the end of the transition
  4292. navViewAttr(enteringEle, 'entering');
  4293. navViewAttr(leavingEle, 'leaving');
  4294. // return the transition run method so each step can be ran manually
  4295. return {
  4296. run: viewTransition.run,
  4297. cancel: function(shouldAnimate) {
  4298. if (shouldAnimate) {
  4299. enteringEle.on(TRANSITIONEND_EVENT, cancelOnTransitionEnd);
  4300. enteringEle.data(DATA_FALLBACK_TIMER, $timeout(cancelTransition, defaultTimeout));
  4301. $ionicClickBlock.show(defaultTimeout);
  4302. } else {
  4303. cancelTransition();
  4304. }
  4305. viewTransition.shouldAnimate = shouldAnimate;
  4306. viewTransition.run(0);
  4307. viewTransition = null;
  4308. }
  4309. };
  4310. } else if (renderEnd) {
  4311. // just the end of a manual transition
  4312. // happens after the manual transition has completed
  4313. // and a full history change has happened
  4314. onReflow();
  4315. }
  4316. function onReflow() {
  4317. // remove that we're staging the entering element so it can auto transition
  4318. navViewAttr(enteringEle, viewTransition.shouldAnimate ? 'entering' : VIEW_STATUS_ACTIVE);
  4319. navViewAttr(leavingEle, viewTransition.shouldAnimate ? 'leaving' : VIEW_STATUS_CACHED);
  4320. // start the auto transition and let the CSS take over
  4321. viewTransition.run(1);
  4322. // trigger auto transitions on the associated nav bars
  4323. $ionicNavBarDelegate._instances.forEach(function(instance) {
  4324. instance.triggerTransitionStart(transitionId);
  4325. });
  4326. if (!viewTransition.shouldAnimate) {
  4327. // no animated auto transition
  4328. transitionComplete();
  4329. }
  4330. }
  4331. // Make sure that transitionend events bubbling up from children won't fire
  4332. // transitionComplete. Will only go forward if ev.target == the element listening.
  4333. function completeOnTransitionEnd(ev) {
  4334. if (ev.target !== this) return;
  4335. transitionComplete();
  4336. }
  4337. function transitionComplete() {
  4338. if (transitionComplete.x) return;
  4339. transitionComplete.x = true;
  4340. enteringEle.off(TRANSITIONEND_EVENT, completeOnTransitionEnd);
  4341. $timeout.cancel(enteringEle.data(DATA_FALLBACK_TIMER));
  4342. leavingEle && $timeout.cancel(leavingEle.data(DATA_FALLBACK_TIMER));
  4343. // resolve that this one transition (there could be many w/ nested views)
  4344. deferred && deferred.resolve(navViewCtrl);
  4345. // the most recent transition added has completed and all the active
  4346. // transition promises should be added to the services array of promises
  4347. if (transitionId === transitionCounter) {
  4348. $q.all(transitionPromises).then(ionicViewSwitcher.transitionEnd);
  4349. // emit that the views have finished transitioning
  4350. // each parent nav-view will update which views are active and cached
  4351. switcher.emit('after', enteringData, leavingData);
  4352. switcher.cleanup(enteringData);
  4353. }
  4354. // tell the nav bars that the transition has ended
  4355. $ionicNavBarDelegate._instances.forEach(function(instance) {
  4356. instance.triggerTransitionEnd();
  4357. });
  4358. // remove any references that could cause memory issues
  4359. nextTransition = nextDirection = enteringView = leavingView = enteringEle = leavingEle = null;
  4360. }
  4361. // Make sure that transitionend events bubbling up from children won't fire
  4362. // transitionComplete. Will only go forward if ev.target == the element listening.
  4363. function cancelOnTransitionEnd(ev) {
  4364. if (ev.target !== this) return;
  4365. cancelTransition();
  4366. }
  4367. function cancelTransition() {
  4368. navViewAttr(enteringEle, VIEW_STATUS_CACHED);
  4369. navViewAttr(leavingEle, VIEW_STATUS_ACTIVE);
  4370. enteringEle.off(TRANSITIONEND_EVENT, cancelOnTransitionEnd);
  4371. $timeout.cancel(enteringEle.data(DATA_FALLBACK_TIMER));
  4372. ionicViewSwitcher.transitionEnd([navViewCtrl]);
  4373. }
  4374. },
  4375. emit: function(step, enteringData, leavingData) {
  4376. var enteringScope = getScopeForElement(enteringEle, enteringData);
  4377. var leavingScope = getScopeForElement(leavingEle, leavingData);
  4378. var prefixesAreEqual;
  4379. if ( !enteringData.viewId || enteringData.abstractView ) {
  4380. // it's an abstract view, so treat it accordingly
  4381. // we only get access to the leaving scope once in the transition,
  4382. // so dispatch all events right away if it exists
  4383. if ( leavingScope ) {
  4384. leavingScope.$emit('$ionicView.beforeLeave', leavingData);
  4385. leavingScope.$emit('$ionicView.leave', leavingData);
  4386. leavingScope.$emit('$ionicView.afterLeave', leavingData);
  4387. leavingScope.$broadcast('$ionicParentView.beforeLeave', leavingData);
  4388. leavingScope.$broadcast('$ionicParentView.leave', leavingData);
  4389. leavingScope.$broadcast('$ionicParentView.afterLeave', leavingData);
  4390. }
  4391. }
  4392. else {
  4393. // it's a regular view, so do the normal process
  4394. if (step == 'after') {
  4395. if (enteringScope) {
  4396. enteringScope.$emit('$ionicView.enter', enteringData);
  4397. enteringScope.$broadcast('$ionicParentView.enter', enteringData);
  4398. }
  4399. if (leavingScope) {
  4400. leavingScope.$emit('$ionicView.leave', leavingData);
  4401. leavingScope.$broadcast('$ionicParentView.leave', leavingData);
  4402. }
  4403. else if (enteringScope && leavingData && leavingData.viewId && enteringData.stateName !== leavingData.stateName) {
  4404. // we only want to dispatch this when we are doing a single-tier
  4405. // state change such as changing a tab, so compare the state
  4406. // for the same state-prefix but different suffix
  4407. prefixesAreEqual = compareStatePrefixes(enteringData.stateName, leavingData.stateName);
  4408. if ( prefixesAreEqual ) {
  4409. enteringScope.$emit('$ionicNavView.leave', leavingData);
  4410. }
  4411. }
  4412. }
  4413. if (enteringScope) {
  4414. enteringScope.$emit('$ionicView.' + step + 'Enter', enteringData);
  4415. enteringScope.$broadcast('$ionicParentView.' + step + 'Enter', enteringData);
  4416. }
  4417. if (leavingScope) {
  4418. leavingScope.$emit('$ionicView.' + step + 'Leave', leavingData);
  4419. leavingScope.$broadcast('$ionicParentView.' + step + 'Leave', leavingData);
  4420. } else if (enteringScope && leavingData && leavingData.viewId && enteringData.stateName !== leavingData.stateName) {
  4421. // we only want to dispatch this when we are doing a single-tier
  4422. // state change such as changing a tab, so compare the state
  4423. // for the same state-prefix but different suffix
  4424. prefixesAreEqual = compareStatePrefixes(enteringData.stateName, leavingData.stateName);
  4425. if ( prefixesAreEqual ) {
  4426. enteringScope.$emit('$ionicNavView.' + step + 'Leave', leavingData);
  4427. }
  4428. }
  4429. }
  4430. },
  4431. cleanup: function(transData) {
  4432. // check if any views should be removed
  4433. if (leavingEle && transData.direction == 'back' && !$ionicConfig.views.forwardCache()) {
  4434. // if they just navigated back we can destroy the forward view
  4435. // do not remove forward views if cacheForwardViews config is true
  4436. destroyViewEle(leavingEle);
  4437. }
  4438. var viewElements = navViewCtrl.getViewElements();
  4439. var viewElementsLength = viewElements.length;
  4440. var x, viewElement;
  4441. var removeOldestAccess = (viewElementsLength - 1) > $ionicConfig.views.maxCache();
  4442. var removableEle;
  4443. var oldestAccess = Date.now();
  4444. for (x = 0; x < viewElementsLength; x++) {
  4445. viewElement = viewElements.eq(x);
  4446. if (removeOldestAccess && viewElement.data(DATA_VIEW_ACCESSED) < oldestAccess) {
  4447. // remember what was the oldest element to be accessed so it can be destroyed
  4448. oldestAccess = viewElement.data(DATA_VIEW_ACCESSED);
  4449. removableEle = viewElements.eq(x);
  4450. } else if (viewElement.data(DATA_DESTROY_ELE) && navViewAttr(viewElement) != VIEW_STATUS_ACTIVE) {
  4451. destroyViewEle(viewElement);
  4452. }
  4453. }
  4454. destroyViewEle(removableEle);
  4455. if (enteringEle.data(DATA_NO_CACHE)) {
  4456. enteringEle.data(DATA_DESTROY_ELE, true);
  4457. }
  4458. },
  4459. enteringEle: function() { return enteringEle; },
  4460. leavingEle: function() { return leavingEle; }
  4461. };
  4462. return switcher;
  4463. },
  4464. transitionEnd: function(navViewCtrls) {
  4465. forEach(navViewCtrls, function(navViewCtrl) {
  4466. navViewCtrl.transitionEnd();
  4467. });
  4468. ionicViewSwitcher.isTransitioning(false);
  4469. $ionicClickBlock.hide();
  4470. transitionPromises = [];
  4471. },
  4472. nextTransition: function(val) {
  4473. nextTransition = val;
  4474. },
  4475. nextDirection: function(val) {
  4476. nextDirection = val;
  4477. },
  4478. isTransitioning: function(val) {
  4479. if (arguments.length) {
  4480. ionic.transition.isActive = !!val;
  4481. $timeout.cancel(isActiveTimer);
  4482. if (val) {
  4483. isActiveTimer = $timeout(function() {
  4484. ionicViewSwitcher.isTransitioning(false);
  4485. }, 999);
  4486. }
  4487. }
  4488. return ionic.transition.isActive;
  4489. },
  4490. createViewEle: function(viewLocals) {
  4491. var containerEle = $document[0].createElement('div');
  4492. if (viewLocals && viewLocals.$template) {
  4493. containerEle.innerHTML = viewLocals.$template;
  4494. if (containerEle.children.length === 1) {
  4495. containerEle.children[0].classList.add('pane');
  4496. if ( viewLocals.$$state && viewLocals.$$state.self && viewLocals.$$state.self['abstract'] ) {
  4497. angular.element(containerEle.children[0]).attr("abstract", "true");
  4498. }
  4499. else {
  4500. if ( viewLocals.$$state && viewLocals.$$state.self ) {
  4501. angular.element(containerEle.children[0]).attr("state", viewLocals.$$state.self.name);
  4502. }
  4503. }
  4504. return jqLite(containerEle.children[0]);
  4505. }
  4506. }
  4507. containerEle.className = "pane";
  4508. return jqLite(containerEle);
  4509. },
  4510. viewEleIsActive: function(viewEle, isActiveAttr) {
  4511. navViewAttr(viewEle, isActiveAttr ? VIEW_STATUS_ACTIVE : VIEW_STATUS_CACHED);
  4512. },
  4513. getTransitionData: getTransitionData,
  4514. navViewAttr: navViewAttr,
  4515. destroyViewEle: destroyViewEle
  4516. };
  4517. return ionicViewSwitcher;
  4518. function getViewElementIdentifier(locals, view) {
  4519. if (viewState(locals)['abstract']) return viewState(locals).name;
  4520. if (view) return view.stateId || view.viewId;
  4521. return ionic.Utils.nextUid();
  4522. }
  4523. function viewState(locals) {
  4524. return locals && locals.$$state && locals.$$state.self || {};
  4525. }
  4526. function getTransitionData(viewLocals, enteringEle, direction, view) {
  4527. // Priority
  4528. // 1) attribute directive on the button/link to this view
  4529. // 2) entering element's attribute
  4530. // 3) entering view's $state config property
  4531. // 4) view registration data
  4532. // 5) global config
  4533. // 6) fallback value
  4534. var state = viewState(viewLocals);
  4535. var viewTransition = nextTransition || cachedAttr(enteringEle, 'view-transition') || state.viewTransition || $ionicConfig.views.transition() || 'ios';
  4536. var navBarTransition = $ionicConfig.navBar.transition();
  4537. direction = nextDirection || cachedAttr(enteringEle, 'view-direction') || state.viewDirection || direction || 'none';
  4538. return extend(getViewData(view), {
  4539. transition: viewTransition,
  4540. navBarTransition: navBarTransition === 'view' ? viewTransition : navBarTransition,
  4541. direction: direction,
  4542. shouldAnimate: (viewTransition !== 'none' && direction !== 'none')
  4543. });
  4544. }
  4545. function getViewData(view) {
  4546. view = view || {};
  4547. return {
  4548. viewId: view.viewId,
  4549. historyId: view.historyId,
  4550. stateId: view.stateId,
  4551. stateName: view.stateName,
  4552. stateParams: view.stateParams
  4553. };
  4554. }
  4555. function navViewAttr(ele, value) {
  4556. if (arguments.length > 1) {
  4557. cachedAttr(ele, NAV_VIEW_ATTR, value);
  4558. } else {
  4559. return cachedAttr(ele, NAV_VIEW_ATTR);
  4560. }
  4561. }
  4562. function destroyViewEle(ele) {
  4563. // we found an element that should be removed
  4564. // destroy its scope, then remove the element
  4565. if (ele && ele.length) {
  4566. var viewScope = ele.scope();
  4567. if (viewScope) {
  4568. viewScope.$emit('$ionicView.unloaded', ele.data(DATA_VIEW));
  4569. viewScope.$destroy();
  4570. }
  4571. ele.remove();
  4572. }
  4573. }
  4574. function compareStatePrefixes(enteringStateName, exitingStateName) {
  4575. var enteringStateSuffixIndex = enteringStateName.lastIndexOf('.');
  4576. var exitingStateSuffixIndex = exitingStateName.lastIndexOf('.');
  4577. // if either of the prefixes are empty, just return false
  4578. if ( enteringStateSuffixIndex < 0 || exitingStateSuffixIndex < 0 ) {
  4579. return false;
  4580. }
  4581. var enteringPrefix = enteringStateName.substring(0, enteringStateSuffixIndex);
  4582. var exitingPrefix = exitingStateName.substring(0, exitingStateSuffixIndex);
  4583. return enteringPrefix === exitingPrefix;
  4584. }
  4585. function getScopeForElement(element, stateData) {
  4586. if ( !element ) {
  4587. return null;
  4588. }
  4589. // check if it's abstract
  4590. var attributeValue = angular.element(element).attr("abstract");
  4591. var stateValue = angular.element(element).attr("state");
  4592. if ( attributeValue !== "true" ) {
  4593. // it's not an abstract view, so make sure the element
  4594. // matches the state. Due to abstract view weirdness,
  4595. // sometimes it doesn't. If it doesn't, don't dispatch events
  4596. // so leave the scope undefined
  4597. if ( stateValue === stateData.stateName ) {
  4598. return angular.element(element).scope();
  4599. }
  4600. return null;
  4601. }
  4602. else {
  4603. // it is an abstract element, so look for element with the "state" attributeValue
  4604. // set to the name of the stateData state
  4605. var elements = aggregateNavViewChildren(element);
  4606. for ( var i = 0; i < elements.length; i++ ) {
  4607. var state = angular.element(elements[i]).attr("state");
  4608. if ( state === stateData.stateName ) {
  4609. stateData.abstractView = true;
  4610. return angular.element(elements[i]).scope();
  4611. }
  4612. }
  4613. // we didn't find a match, so return null
  4614. return null;
  4615. }
  4616. }
  4617. function aggregateNavViewChildren(element) {
  4618. var aggregate = [];
  4619. var navViews = angular.element(element).find("ion-nav-view");
  4620. for ( var i = 0; i < navViews.length; i++ ) {
  4621. var children = angular.element(navViews[i]).children();
  4622. var childrenAggregated = [];
  4623. for ( var j = 0; j < children.length; j++ ) {
  4624. childrenAggregated = childrenAggregated.concat(children[j]);
  4625. }
  4626. aggregate = aggregate.concat(childrenAggregated);
  4627. }
  4628. return aggregate;
  4629. }
  4630. }]);
  4631. /**
  4632. * ================== angular-ios9-uiwebview.patch.js v1.1.1 ==================
  4633. *
  4634. * This patch works around iOS9 UIWebView regression that causes infinite digest
  4635. * errors in Angular.
  4636. *
  4637. * The patch can be applied to Angular 1.2.0 – 1.4.5. Newer versions of Angular
  4638. * have the workaround baked in.
  4639. *
  4640. * To apply this patch load/bundle this file with your application and add a
  4641. * dependency on the "ngIOS9UIWebViewPatch" module to your main app module.
  4642. *
  4643. * For example:
  4644. *
  4645. * ```
  4646. * angular.module('myApp', ['ngRoute'])`
  4647. * ```
  4648. *
  4649. * becomes
  4650. *
  4651. * ```
  4652. * angular.module('myApp', ['ngRoute', 'ngIOS9UIWebViewPatch'])
  4653. * ```
  4654. *
  4655. *
  4656. * More info:
  4657. * - https://openradar.appspot.com/22186109
  4658. * - https://github.com/angular/angular.js/issues/12241
  4659. * - https://github.com/driftyco/ionic/issues/4082
  4660. *
  4661. *
  4662. * @license AngularJS
  4663. * (c) 2010-2015 Google, Inc. http://angularjs.org
  4664. * License: MIT
  4665. */
  4666. angular.module('ngIOS9UIWebViewPatch', ['ng']).config(['$provide', function($provide) {
  4667. 'use strict';
  4668. $provide.decorator('$browser', ['$delegate', '$window', function($delegate, $window) {
  4669. if (isIOS9UIWebView($window.navigator.userAgent)) {
  4670. return applyIOS9Shim($delegate);
  4671. }
  4672. return $delegate;
  4673. function isIOS9UIWebView(userAgent) {
  4674. return /(iPhone|iPad|iPod).* OS 9_\d/.test(userAgent) && !/Version\/9\./.test(userAgent);
  4675. }
  4676. function applyIOS9Shim(browser) {
  4677. var pendingLocationUrl = null;
  4678. var originalUrlFn = browser.url;
  4679. browser.url = function() {
  4680. if (arguments.length) {
  4681. pendingLocationUrl = arguments[0];
  4682. return originalUrlFn.apply(browser, arguments);
  4683. }
  4684. return pendingLocationUrl || originalUrlFn.apply(browser, arguments);
  4685. };
  4686. window.addEventListener('popstate', clearPendingLocationUrl, false);
  4687. window.addEventListener('hashchange', clearPendingLocationUrl, false);
  4688. function clearPendingLocationUrl() {
  4689. pendingLocationUrl = null;
  4690. }
  4691. return browser;
  4692. }
  4693. }]);
  4694. }]);
  4695. /**
  4696. * @private
  4697. * Parts of Ionic requires that $scope data is attached to the element.
  4698. * We do not want to disable adding $scope data to the $element when
  4699. * $compileProvider.debugInfoEnabled(false) is used.
  4700. */
  4701. IonicModule.config(['$provide', function($provide) {
  4702. $provide.decorator('$compile', ['$delegate', function($compile) {
  4703. $compile.$$addScopeInfo = function $$addScopeInfo($element, scope, isolated, noTemplate) {
  4704. var dataName = isolated ? (noTemplate ? '$isolateScopeNoTemplate' : '$isolateScope') : '$scope';
  4705. $element.data(dataName, scope);
  4706. };
  4707. return $compile;
  4708. }]);
  4709. }]);
  4710. /**
  4711. * @private
  4712. */
  4713. IonicModule.config([
  4714. '$provide',
  4715. function($provide) {
  4716. function $LocationDecorator($location, $timeout) {
  4717. $location.__hash = $location.hash;
  4718. //Fix: when window.location.hash is set, the scrollable area
  4719. //found nearest to body's scrollTop is set to scroll to an element
  4720. //with that ID.
  4721. $location.hash = function(value) {
  4722. if (isDefined(value) && value.length > 0) {
  4723. $timeout(function() {
  4724. var scroll = document.querySelector('.scroll-content');
  4725. if (scroll) {
  4726. scroll.scrollTop = 0;
  4727. }
  4728. }, 0, false);
  4729. }
  4730. return $location.__hash(value);
  4731. };
  4732. return $location;
  4733. }
  4734. $provide.decorator('$location', ['$delegate', '$timeout', $LocationDecorator]);
  4735. }]);
  4736. IonicModule
  4737. .controller('$ionicHeaderBar', [
  4738. '$scope',
  4739. '$element',
  4740. '$attrs',
  4741. '$q',
  4742. '$ionicConfig',
  4743. '$ionicHistory',
  4744. function($scope, $element, $attrs, $q, $ionicConfig, $ionicHistory) {
  4745. var TITLE = 'title';
  4746. var BACK_TEXT = 'back-text';
  4747. var BACK_BUTTON = 'back-button';
  4748. var DEFAULT_TITLE = 'default-title';
  4749. var PREVIOUS_TITLE = 'previous-title';
  4750. var HIDE = 'hide';
  4751. var self = this;
  4752. var titleText = '';
  4753. var previousTitleText = '';
  4754. var titleLeft = 0;
  4755. var titleRight = 0;
  4756. var titleCss = '';
  4757. var isBackEnabled = false;
  4758. var isBackShown = true;
  4759. var isNavBackShown = true;
  4760. var isBackElementShown = false;
  4761. var titleTextWidth = 0;
  4762. self.beforeEnter = function(viewData) {
  4763. $scope.$broadcast('$ionicView.beforeEnter', viewData);
  4764. };
  4765. self.title = function(newTitleText) {
  4766. if (arguments.length && newTitleText !== titleText) {
  4767. getEle(TITLE).innerHTML = newTitleText;
  4768. titleText = newTitleText;
  4769. titleTextWidth = 0;
  4770. }
  4771. return titleText;
  4772. };
  4773. self.enableBack = function(shouldEnable, disableReset) {
  4774. // whether or not the back button show be visible, according
  4775. // to the navigation and history
  4776. if (arguments.length) {
  4777. isBackEnabled = shouldEnable;
  4778. if (!disableReset) self.updateBackButton();
  4779. }
  4780. return isBackEnabled;
  4781. };
  4782. self.showBack = function(shouldShow, disableReset) {
  4783. // different from enableBack() because this will always have the back
  4784. // visually hidden if false, even if the history says it should show
  4785. if (arguments.length) {
  4786. isBackShown = shouldShow;
  4787. if (!disableReset) self.updateBackButton();
  4788. }
  4789. return isBackShown;
  4790. };
  4791. self.showNavBack = function(shouldShow) {
  4792. // different from showBack() because this is for the entire nav bar's
  4793. // setting for all of it's child headers. For internal use.
  4794. isNavBackShown = shouldShow;
  4795. self.updateBackButton();
  4796. };
  4797. self.updateBackButton = function() {
  4798. var ele;
  4799. if ((isBackShown && isNavBackShown && isBackEnabled) !== isBackElementShown) {
  4800. isBackElementShown = isBackShown && isNavBackShown && isBackEnabled;
  4801. ele = getEle(BACK_BUTTON);
  4802. ele && ele.classList[ isBackElementShown ? 'remove' : 'add' ](HIDE);
  4803. }
  4804. if (isBackEnabled) {
  4805. ele = ele || getEle(BACK_BUTTON);
  4806. if (ele) {
  4807. if (self.backButtonIcon !== $ionicConfig.backButton.icon()) {
  4808. ele = getEle(BACK_BUTTON + ' .icon');
  4809. if (ele) {
  4810. self.backButtonIcon = $ionicConfig.backButton.icon();
  4811. ele.className = 'icon ' + self.backButtonIcon;
  4812. }
  4813. }
  4814. if (self.backButtonText !== $ionicConfig.backButton.text()) {
  4815. ele = getEle(BACK_BUTTON + ' .back-text');
  4816. if (ele) {
  4817. ele.textContent = self.backButtonText = $ionicConfig.backButton.text();
  4818. }
  4819. }
  4820. }
  4821. }
  4822. };
  4823. self.titleTextWidth = function() {
  4824. var element = getEle(TITLE);
  4825. if ( element ) {
  4826. // If the element has a nav-bar-title, use that instead
  4827. // to calculate the width of the title
  4828. var children = angular.element(element).children();
  4829. for ( var i = 0; i < children.length; i++ ) {
  4830. if ( angular.element(children[i]).hasClass('nav-bar-title') ) {
  4831. element = children[i];
  4832. break;
  4833. }
  4834. }
  4835. }
  4836. var bounds = ionic.DomUtil.getTextBounds(element);
  4837. titleTextWidth = Math.min(bounds && bounds.width || 30);
  4838. return titleTextWidth;
  4839. };
  4840. self.titleWidth = function() {
  4841. var titleWidth = self.titleTextWidth();
  4842. var offsetWidth = getEle(TITLE).offsetWidth;
  4843. if (offsetWidth < titleWidth) {
  4844. titleWidth = offsetWidth + (titleLeft - titleRight - 5);
  4845. }
  4846. return titleWidth;
  4847. };
  4848. self.titleTextX = function() {
  4849. return ($element[0].offsetWidth / 2) - (self.titleWidth() / 2);
  4850. };
  4851. self.titleLeftRight = function() {
  4852. return titleLeft - titleRight;
  4853. };
  4854. self.backButtonTextLeft = function() {
  4855. var offsetLeft = 0;
  4856. var ele = getEle(BACK_TEXT);
  4857. while (ele) {
  4858. offsetLeft += ele.offsetLeft;
  4859. ele = ele.parentElement;
  4860. }
  4861. return offsetLeft;
  4862. };
  4863. self.resetBackButton = function(viewData) {
  4864. if ($ionicConfig.backButton.previousTitleText()) {
  4865. var previousTitleEle = getEle(PREVIOUS_TITLE);
  4866. if (previousTitleEle) {
  4867. previousTitleEle.classList.remove(HIDE);
  4868. var view = (viewData && $ionicHistory.getViewById(viewData.viewId));
  4869. var newPreviousTitleText = $ionicHistory.backTitle(view);
  4870. if (newPreviousTitleText !== previousTitleText) {
  4871. previousTitleText = previousTitleEle.innerHTML = newPreviousTitleText;
  4872. }
  4873. }
  4874. var defaultTitleEle = getEle(DEFAULT_TITLE);
  4875. if (defaultTitleEle) {
  4876. defaultTitleEle.classList.remove(HIDE);
  4877. }
  4878. }
  4879. };
  4880. self.align = function(textAlign) {
  4881. var titleEle = getEle(TITLE);
  4882. textAlign = textAlign || $attrs.alignTitle || $ionicConfig.navBar.alignTitle();
  4883. var widths = self.calcWidths(textAlign, false);
  4884. if (isBackShown && previousTitleText && $ionicConfig.backButton.previousTitleText()) {
  4885. var previousTitleWidths = self.calcWidths(textAlign, true);
  4886. var availableTitleWidth = $element[0].offsetWidth - previousTitleWidths.titleLeft - previousTitleWidths.titleRight;
  4887. if (self.titleTextWidth() <= availableTitleWidth) {
  4888. widths = previousTitleWidths;
  4889. }
  4890. }
  4891. return self.updatePositions(titleEle, widths.titleLeft, widths.titleRight, widths.buttonsLeft, widths.buttonsRight, widths.css, widths.showPrevTitle);
  4892. };
  4893. self.calcWidths = function(textAlign, isPreviousTitle) {
  4894. var titleEle = getEle(TITLE);
  4895. var backBtnEle = getEle(BACK_BUTTON);
  4896. var x, y, z, b, c, d, childSize, bounds;
  4897. var childNodes = $element[0].childNodes;
  4898. var buttonsLeft = 0;
  4899. var buttonsRight = 0;
  4900. var isCountRightOfTitle;
  4901. var updateTitleLeft = 0;
  4902. var updateTitleRight = 0;
  4903. var updateCss = '';
  4904. var backButtonWidth = 0;
  4905. // Compute how wide the left children are
  4906. // Skip all titles (there may still be two titles, one leaving the dom)
  4907. // Once we encounter a titleEle, realize we are now counting the right-buttons, not left
  4908. for (x = 0; x < childNodes.length; x++) {
  4909. c = childNodes[x];
  4910. childSize = 0;
  4911. if (c.nodeType == 1) {
  4912. // element node
  4913. if (c === titleEle) {
  4914. isCountRightOfTitle = true;
  4915. continue;
  4916. }
  4917. if (c.classList.contains(HIDE)) {
  4918. continue;
  4919. }
  4920. if (isBackShown && c === backBtnEle) {
  4921. for (y = 0; y < c.childNodes.length; y++) {
  4922. b = c.childNodes[y];
  4923. if (b.nodeType == 1) {
  4924. if (b.classList.contains(BACK_TEXT)) {
  4925. for (z = 0; z < b.children.length; z++) {
  4926. d = b.children[z];
  4927. if (isPreviousTitle) {
  4928. if (d.classList.contains(DEFAULT_TITLE)) continue;
  4929. backButtonWidth += d.offsetWidth;
  4930. } else {
  4931. if (d.classList.contains(PREVIOUS_TITLE)) continue;
  4932. backButtonWidth += d.offsetWidth;
  4933. }
  4934. }
  4935. } else {
  4936. backButtonWidth += b.offsetWidth;
  4937. }
  4938. } else if (b.nodeType == 3 && b.nodeValue.trim()) {
  4939. bounds = ionic.DomUtil.getTextBounds(b);
  4940. backButtonWidth += bounds && bounds.width || 0;
  4941. }
  4942. }
  4943. childSize = backButtonWidth || c.offsetWidth;
  4944. } else {
  4945. // not the title, not the back button, not a hidden element
  4946. childSize = c.offsetWidth;
  4947. }
  4948. } else if (c.nodeType == 3 && c.nodeValue.trim()) {
  4949. // text node
  4950. bounds = ionic.DomUtil.getTextBounds(c);
  4951. childSize = bounds && bounds.width || 0;
  4952. }
  4953. if (isCountRightOfTitle) {
  4954. buttonsRight += childSize;
  4955. } else {
  4956. buttonsLeft += childSize;
  4957. }
  4958. }
  4959. // Size and align the header titleEle based on the sizes of the left and
  4960. // right children, and the desired alignment mode
  4961. if (textAlign == 'left') {
  4962. updateCss = 'title-left';
  4963. if (buttonsLeft) {
  4964. updateTitleLeft = buttonsLeft + 15;
  4965. }
  4966. if (buttonsRight) {
  4967. updateTitleRight = buttonsRight + 15;
  4968. }
  4969. } else if (textAlign == 'right') {
  4970. updateCss = 'title-right';
  4971. if (buttonsLeft) {
  4972. updateTitleLeft = buttonsLeft + 15;
  4973. }
  4974. if (buttonsRight) {
  4975. updateTitleRight = buttonsRight + 15;
  4976. }
  4977. } else {
  4978. // center the default
  4979. var margin = Math.max(buttonsLeft, buttonsRight) + 10;
  4980. if (margin > 10) {
  4981. updateTitleLeft = updateTitleRight = margin;
  4982. }
  4983. }
  4984. return {
  4985. backButtonWidth: backButtonWidth,
  4986. buttonsLeft: buttonsLeft,
  4987. buttonsRight: buttonsRight,
  4988. titleLeft: updateTitleLeft,
  4989. titleRight: updateTitleRight,
  4990. showPrevTitle: isPreviousTitle,
  4991. css: updateCss
  4992. };
  4993. };
  4994. self.updatePositions = function(titleEle, updateTitleLeft, updateTitleRight, buttonsLeft, buttonsRight, updateCss, showPreviousTitle) {
  4995. var deferred = $q.defer();
  4996. // only make DOM updates when there are actual changes
  4997. if (titleEle) {
  4998. if (updateTitleLeft !== titleLeft) {
  4999. titleEle.style.left = updateTitleLeft ? updateTitleLeft + 'px' : '';
  5000. titleLeft = updateTitleLeft;
  5001. }
  5002. if (updateTitleRight !== titleRight) {
  5003. titleEle.style.right = updateTitleRight ? updateTitleRight + 'px' : '';
  5004. titleRight = updateTitleRight;
  5005. }
  5006. if (updateCss !== titleCss) {
  5007. updateCss && titleEle.classList.add(updateCss);
  5008. titleCss && titleEle.classList.remove(titleCss);
  5009. titleCss = updateCss;
  5010. }
  5011. }
  5012. if ($ionicConfig.backButton.previousTitleText()) {
  5013. var prevTitle = getEle(PREVIOUS_TITLE);
  5014. var defaultTitle = getEle(DEFAULT_TITLE);
  5015. prevTitle && prevTitle.classList[ showPreviousTitle ? 'remove' : 'add'](HIDE);
  5016. defaultTitle && defaultTitle.classList[ showPreviousTitle ? 'add' : 'remove'](HIDE);
  5017. }
  5018. ionic.requestAnimationFrame(function() {
  5019. if (titleEle && titleEle.offsetWidth + 10 < titleEle.scrollWidth) {
  5020. var minRight = buttonsRight + 5;
  5021. var testRight = $element[0].offsetWidth - titleLeft - self.titleTextWidth() - 20;
  5022. updateTitleRight = testRight < minRight ? minRight : testRight;
  5023. if (updateTitleRight !== titleRight) {
  5024. titleEle.style.right = updateTitleRight + 'px';
  5025. titleRight = updateTitleRight;
  5026. }
  5027. }
  5028. deferred.resolve();
  5029. });
  5030. return deferred.promise;
  5031. };
  5032. self.setCss = function(elementClassname, css) {
  5033. ionic.DomUtil.cachedStyles(getEle(elementClassname), css);
  5034. };
  5035. var eleCache = {};
  5036. function getEle(className) {
  5037. if (!eleCache[className]) {
  5038. eleCache[className] = $element[0].querySelector('.' + className);
  5039. }
  5040. return eleCache[className];
  5041. }
  5042. $scope.$on('$destroy', function() {
  5043. for (var n in eleCache) eleCache[n] = null;
  5044. });
  5045. }]);
  5046. IonicModule
  5047. .controller('$ionInfiniteScroll', [
  5048. '$scope',
  5049. '$attrs',
  5050. '$element',
  5051. '$timeout',
  5052. function($scope, $attrs, $element, $timeout) {
  5053. var self = this;
  5054. self.isLoading = false;
  5055. $scope.icon = function() {
  5056. return isDefined($attrs.icon) ? $attrs.icon : 'ion-load-d';
  5057. };
  5058. $scope.spinner = function() {
  5059. return isDefined($attrs.spinner) ? $attrs.spinner : '';
  5060. };
  5061. $scope.$on('scroll.infiniteScrollComplete', function() {
  5062. finishInfiniteScroll();
  5063. });
  5064. $scope.$on('$destroy', function() {
  5065. if (self.scrollCtrl && self.scrollCtrl.$element) self.scrollCtrl.$element.off('scroll', self.checkBounds);
  5066. if (self.scrollEl && self.scrollEl.removeEventListener) {
  5067. self.scrollEl.removeEventListener('scroll', self.checkBounds);
  5068. }
  5069. });
  5070. // debounce checking infinite scroll events
  5071. self.checkBounds = ionic.Utils.throttle(checkInfiniteBounds, 300);
  5072. function onInfinite() {
  5073. ionic.requestAnimationFrame(function() {
  5074. $element[0].classList.add('active');
  5075. });
  5076. self.isLoading = true;
  5077. $scope.$parent && $scope.$parent.$apply($attrs.onInfinite || '');
  5078. }
  5079. function finishInfiniteScroll() {
  5080. ionic.requestAnimationFrame(function() {
  5081. $element[0].classList.remove('active');
  5082. });
  5083. $timeout(function() {
  5084. if (self.jsScrolling) self.scrollView.resize();
  5085. // only check bounds again immediately if the page isn't cached (scroll el has height)
  5086. if ((self.jsScrolling && self.scrollView.__container && self.scrollView.__container.offsetHeight > 0) ||
  5087. !self.jsScrolling) {
  5088. self.checkBounds();
  5089. }
  5090. }, 30, false);
  5091. self.isLoading = false;
  5092. }
  5093. // check if we've scrolled far enough to trigger an infinite scroll
  5094. function checkInfiniteBounds() {
  5095. if (self.isLoading) return;
  5096. var maxScroll = {};
  5097. if (self.jsScrolling) {
  5098. maxScroll = self.getJSMaxScroll();
  5099. var scrollValues = self.scrollView.getValues();
  5100. if ((maxScroll.left !== -1 && scrollValues.left >= maxScroll.left) ||
  5101. (maxScroll.top !== -1 && scrollValues.top >= maxScroll.top)) {
  5102. onInfinite();
  5103. }
  5104. } else {
  5105. maxScroll = self.getNativeMaxScroll();
  5106. if ((
  5107. maxScroll.left !== -1 &&
  5108. self.scrollEl.scrollLeft >= maxScroll.left - self.scrollEl.clientWidth
  5109. ) || (
  5110. maxScroll.top !== -1 &&
  5111. self.scrollEl.scrollTop >= maxScroll.top - self.scrollEl.clientHeight
  5112. )) {
  5113. onInfinite();
  5114. }
  5115. }
  5116. }
  5117. // determine the threshold at which we should fire an infinite scroll
  5118. // note: this gets processed every scroll event, can it be cached?
  5119. self.getJSMaxScroll = function() {
  5120. var maxValues = self.scrollView.getScrollMax();
  5121. return {
  5122. left: self.scrollView.options.scrollingX ?
  5123. calculateMaxValue(maxValues.left) :
  5124. -1,
  5125. top: self.scrollView.options.scrollingY ?
  5126. calculateMaxValue(maxValues.top) :
  5127. -1
  5128. };
  5129. };
  5130. self.getNativeMaxScroll = function() {
  5131. var maxValues = {
  5132. left: self.scrollEl.scrollWidth,
  5133. top: self.scrollEl.scrollHeight
  5134. };
  5135. var computedStyle = window.getComputedStyle(self.scrollEl) || {};
  5136. return {
  5137. left: maxValues.left &&
  5138. (computedStyle.overflowX === 'scroll' ||
  5139. computedStyle.overflowX === 'auto' ||
  5140. self.scrollEl.style['overflow-x'] === 'scroll') ?
  5141. calculateMaxValue(maxValues.left) : -1,
  5142. top: maxValues.top &&
  5143. (computedStyle.overflowY === 'scroll' ||
  5144. computedStyle.overflowY === 'auto' ||
  5145. self.scrollEl.style['overflow-y'] === 'scroll' ) ?
  5146. calculateMaxValue(maxValues.top) : -1
  5147. };
  5148. };
  5149. // determine pixel refresh distance based on % or value
  5150. function calculateMaxValue(maximum) {
  5151. var distance = ($attrs.distance || '2.5%').trim();
  5152. var isPercent = distance.indexOf('%') !== -1;
  5153. return isPercent ?
  5154. maximum * (1 - parseFloat(distance) / 100) :
  5155. maximum - parseFloat(distance);
  5156. }
  5157. //for testing
  5158. self.__finishInfiniteScroll = finishInfiniteScroll;
  5159. }]);
  5160. /**
  5161. * @ngdoc service
  5162. * @name $ionicListDelegate
  5163. * @module ionic
  5164. *
  5165. * @description
  5166. * Delegate for controlling the {@link ionic.directive:ionList} directive.
  5167. *
  5168. * Methods called directly on the $ionicListDelegate service will control all lists.
  5169. * Use the {@link ionic.service:$ionicListDelegate#$getByHandle $getByHandle}
  5170. * method to control specific ionList instances.
  5171. *
  5172. * @usage
  5173. * ```html
  5174. * {% raw %}
  5175. * <ion-content ng-controller="MyCtrl">
  5176. * <button class="button" ng-click="showDeleteButtons()"></button>
  5177. * <ion-list>
  5178. * <ion-item ng-repeat="i in items">
  5179. * Hello, {{i}}!
  5180. * <ion-delete-button class="ion-minus-circled"></ion-delete-button>
  5181. * </ion-item>
  5182. * </ion-list>
  5183. * </ion-content>
  5184. * {% endraw %}
  5185. * ```
  5186. * ```js
  5187. * function MyCtrl($scope, $ionicListDelegate) {
  5188. * $scope.showDeleteButtons = function() {
  5189. * $ionicListDelegate.showDelete(true);
  5190. * };
  5191. * }
  5192. * ```
  5193. */
  5194. IonicModule.service('$ionicListDelegate', ionic.DelegateService([
  5195. /**
  5196. * @ngdoc method
  5197. * @name $ionicListDelegate#showReorder
  5198. * @param {boolean=} showReorder Set whether or not this list is showing its reorder buttons.
  5199. * @returns {boolean} Whether the reorder buttons are shown.
  5200. */
  5201. 'showReorder',
  5202. /**
  5203. * @ngdoc method
  5204. * @name $ionicListDelegate#showDelete
  5205. * @param {boolean=} showDelete Set whether or not this list is showing its delete buttons.
  5206. * @returns {boolean} Whether the delete buttons are shown.
  5207. */
  5208. 'showDelete',
  5209. /**
  5210. * @ngdoc method
  5211. * @name $ionicListDelegate#canSwipeItems
  5212. * @param {boolean=} canSwipeItems Set whether or not this list is able to swipe to show
  5213. * option buttons.
  5214. * @returns {boolean} Whether the list is able to swipe to show option buttons.
  5215. */
  5216. 'canSwipeItems',
  5217. /**
  5218. * @ngdoc method
  5219. * @name $ionicListDelegate#closeOptionButtons
  5220. * @description Closes any option buttons on the list that are swiped open.
  5221. */
  5222. 'closeOptionButtons'
  5223. /**
  5224. * @ngdoc method
  5225. * @name $ionicListDelegate#$getByHandle
  5226. * @param {string} handle
  5227. * @returns `delegateInstance` A delegate instance that controls only the
  5228. * {@link ionic.directive:ionList} directives with `delegate-handle` matching
  5229. * the given handle.
  5230. *
  5231. * Example: `$ionicListDelegate.$getByHandle('my-handle').showReorder(true);`
  5232. */
  5233. ]))
  5234. .controller('$ionicList', [
  5235. '$scope',
  5236. '$attrs',
  5237. '$ionicListDelegate',
  5238. '$ionicHistory',
  5239. function($scope, $attrs, $ionicListDelegate, $ionicHistory) {
  5240. var self = this;
  5241. var isSwipeable = true;
  5242. var isReorderShown = false;
  5243. var isDeleteShown = false;
  5244. var deregisterInstance = $ionicListDelegate._registerInstance(
  5245. self, $attrs.delegateHandle, function() {
  5246. return $ionicHistory.isActiveScope($scope);
  5247. }
  5248. );
  5249. $scope.$on('$destroy', deregisterInstance);
  5250. self.showReorder = function(show) {
  5251. if (arguments.length) {
  5252. isReorderShown = !!show;
  5253. }
  5254. return isReorderShown;
  5255. };
  5256. self.showDelete = function(show) {
  5257. if (arguments.length) {
  5258. isDeleteShown = !!show;
  5259. }
  5260. return isDeleteShown;
  5261. };
  5262. self.canSwipeItems = function(can) {
  5263. if (arguments.length) {
  5264. isSwipeable = !!can;
  5265. }
  5266. return isSwipeable;
  5267. };
  5268. self.closeOptionButtons = function() {
  5269. self.listView && self.listView.clearDragEffects();
  5270. };
  5271. }]);
  5272. IonicModule
  5273. .controller('$ionicNavBar', [
  5274. '$scope',
  5275. '$element',
  5276. '$attrs',
  5277. '$compile',
  5278. '$timeout',
  5279. '$ionicNavBarDelegate',
  5280. '$ionicConfig',
  5281. '$ionicHistory',
  5282. function($scope, $element, $attrs, $compile, $timeout, $ionicNavBarDelegate, $ionicConfig, $ionicHistory) {
  5283. var CSS_HIDE = 'hide';
  5284. var DATA_NAV_BAR_CTRL = '$ionNavBarController';
  5285. var PRIMARY_BUTTONS = 'primaryButtons';
  5286. var SECONDARY_BUTTONS = 'secondaryButtons';
  5287. var BACK_BUTTON = 'backButton';
  5288. var ITEM_TYPES = 'primaryButtons secondaryButtons leftButtons rightButtons title'.split(' ');
  5289. var self = this;
  5290. var headerBars = [];
  5291. var navElementHtml = {};
  5292. var isVisible = true;
  5293. var queuedTransitionStart, queuedTransitionEnd, latestTransitionId;
  5294. $element.parent().data(DATA_NAV_BAR_CTRL, self);
  5295. var delegateHandle = $attrs.delegateHandle || 'navBar' + ionic.Utils.nextUid();
  5296. var deregisterInstance = $ionicNavBarDelegate._registerInstance(self, delegateHandle);
  5297. self.init = function() {
  5298. $element.addClass('nav-bar-container');
  5299. ionic.DomUtil.cachedAttr($element, 'nav-bar-transition', $ionicConfig.views.transition());
  5300. // create two nav bar blocks which will trade out which one is shown
  5301. self.createHeaderBar(false);
  5302. self.createHeaderBar(true);
  5303. $scope.$emit('ionNavBar.init', delegateHandle);
  5304. };
  5305. self.createHeaderBar = function(isActive) {
  5306. var containerEle = jqLite('<div class="nav-bar-block">');
  5307. ionic.DomUtil.cachedAttr(containerEle, 'nav-bar', isActive ? 'active' : 'cached');
  5308. var alignTitle = $attrs.alignTitle || $ionicConfig.navBar.alignTitle();
  5309. var headerBarEle = jqLite('<ion-header-bar>').addClass($attrs['class']).attr('align-title', alignTitle);
  5310. if (isDefined($attrs.noTapScroll)) headerBarEle.attr('no-tap-scroll', $attrs.noTapScroll);
  5311. var titleEle = jqLite('<div class="title title-' + alignTitle + '">');
  5312. var navEle = {};
  5313. var lastViewItemEle = {};
  5314. var leftButtonsEle, rightButtonsEle;
  5315. navEle[BACK_BUTTON] = createNavElement(BACK_BUTTON);
  5316. navEle[BACK_BUTTON] && headerBarEle.append(navEle[BACK_BUTTON]);
  5317. // append title in the header, this is the rock to where buttons append
  5318. headerBarEle.append(titleEle);
  5319. forEach(ITEM_TYPES, function(itemType) {
  5320. // create default button elements
  5321. navEle[itemType] = createNavElement(itemType);
  5322. // append and position buttons
  5323. positionItem(navEle[itemType], itemType);
  5324. });
  5325. // add header-item to the root children
  5326. for (var x = 0; x < headerBarEle[0].children.length; x++) {
  5327. headerBarEle[0].children[x].classList.add('header-item');
  5328. }
  5329. // compile header and append to the DOM
  5330. containerEle.append(headerBarEle);
  5331. $element.append($compile(containerEle)($scope.$new()));
  5332. var headerBarCtrl = headerBarEle.data('$ionHeaderBarController');
  5333. headerBarCtrl.backButtonIcon = $ionicConfig.backButton.icon();
  5334. headerBarCtrl.backButtonText = $ionicConfig.backButton.text();
  5335. var headerBarInstance = {
  5336. isActive: isActive,
  5337. title: function(newTitleText) {
  5338. headerBarCtrl.title(newTitleText);
  5339. },
  5340. setItem: function(navBarItemEle, itemType) {
  5341. // first make sure any exiting nav bar item has been removed
  5342. headerBarInstance.removeItem(itemType);
  5343. if (navBarItemEle) {
  5344. if (itemType === 'title') {
  5345. // clear out the text based title
  5346. headerBarInstance.title("");
  5347. }
  5348. // there's a custom nav bar item
  5349. positionItem(navBarItemEle, itemType);
  5350. if (navEle[itemType]) {
  5351. // make sure the default on this itemType is hidden
  5352. navEle[itemType].addClass(CSS_HIDE);
  5353. }
  5354. lastViewItemEle[itemType] = navBarItemEle;
  5355. } else if (navEle[itemType]) {
  5356. // there's a default button for this side and no view button
  5357. navEle[itemType].removeClass(CSS_HIDE);
  5358. }
  5359. },
  5360. removeItem: function(itemType) {
  5361. if (lastViewItemEle[itemType]) {
  5362. lastViewItemEle[itemType].scope().$destroy();
  5363. lastViewItemEle[itemType].remove();
  5364. lastViewItemEle[itemType] = null;
  5365. }
  5366. },
  5367. containerEle: function() {
  5368. return containerEle;
  5369. },
  5370. headerBarEle: function() {
  5371. return headerBarEle;
  5372. },
  5373. afterLeave: function() {
  5374. forEach(ITEM_TYPES, function(itemType) {
  5375. headerBarInstance.removeItem(itemType);
  5376. });
  5377. headerBarCtrl.resetBackButton();
  5378. },
  5379. controller: function() {
  5380. return headerBarCtrl;
  5381. },
  5382. destroy: function() {
  5383. forEach(ITEM_TYPES, function(itemType) {
  5384. headerBarInstance.removeItem(itemType);
  5385. });
  5386. containerEle.scope().$destroy();
  5387. for (var n in navEle) {
  5388. if (navEle[n]) {
  5389. navEle[n].removeData();
  5390. navEle[n] = null;
  5391. }
  5392. }
  5393. leftButtonsEle && leftButtonsEle.removeData();
  5394. rightButtonsEle && rightButtonsEle.removeData();
  5395. titleEle.removeData();
  5396. headerBarEle.removeData();
  5397. containerEle.remove();
  5398. containerEle = headerBarEle = titleEle = leftButtonsEle = rightButtonsEle = null;
  5399. }
  5400. };
  5401. function positionItem(ele, itemType) {
  5402. if (!ele) return;
  5403. if (itemType === 'title') {
  5404. // title element
  5405. titleEle.append(ele);
  5406. } else if (itemType == 'rightButtons' ||
  5407. (itemType == SECONDARY_BUTTONS && $ionicConfig.navBar.positionSecondaryButtons() != 'left') ||
  5408. (itemType == PRIMARY_BUTTONS && $ionicConfig.navBar.positionPrimaryButtons() == 'right')) {
  5409. // right side
  5410. if (!rightButtonsEle) {
  5411. rightButtonsEle = jqLite('<div class="buttons buttons-right">');
  5412. headerBarEle.append(rightButtonsEle);
  5413. }
  5414. if (itemType == SECONDARY_BUTTONS) {
  5415. rightButtonsEle.append(ele);
  5416. } else {
  5417. rightButtonsEle.prepend(ele);
  5418. }
  5419. } else {
  5420. // left side
  5421. if (!leftButtonsEle) {
  5422. leftButtonsEle = jqLite('<div class="buttons buttons-left">');
  5423. if (navEle[BACK_BUTTON]) {
  5424. navEle[BACK_BUTTON].after(leftButtonsEle);
  5425. } else {
  5426. headerBarEle.prepend(leftButtonsEle);
  5427. }
  5428. }
  5429. if (itemType == SECONDARY_BUTTONS) {
  5430. leftButtonsEle.append(ele);
  5431. } else {
  5432. leftButtonsEle.prepend(ele);
  5433. }
  5434. }
  5435. }
  5436. headerBars.push(headerBarInstance);
  5437. return headerBarInstance;
  5438. };
  5439. self.navElement = function(type, html) {
  5440. if (isDefined(html)) {
  5441. navElementHtml[type] = html;
  5442. }
  5443. return navElementHtml[type];
  5444. };
  5445. self.update = function(viewData) {
  5446. var showNavBar = !viewData.hasHeaderBar && viewData.showNavBar;
  5447. viewData.transition = $ionicConfig.views.transition();
  5448. if (!showNavBar) {
  5449. viewData.direction = 'none';
  5450. }
  5451. self.enable(showNavBar);
  5452. var enteringHeaderBar = self.isInitialized ? getOffScreenHeaderBar() : getOnScreenHeaderBar();
  5453. var leavingHeaderBar = self.isInitialized ? getOnScreenHeaderBar() : null;
  5454. var enteringHeaderCtrl = enteringHeaderBar.controller();
  5455. // update if the entering header should show the back button or not
  5456. enteringHeaderCtrl.enableBack(viewData.enableBack, true);
  5457. enteringHeaderCtrl.showBack(viewData.showBack, true);
  5458. enteringHeaderCtrl.updateBackButton();
  5459. // update the entering header bar's title
  5460. self.title(viewData.title, enteringHeaderBar);
  5461. self.showBar(showNavBar);
  5462. // update the nav bar items, depending if the view has their own or not
  5463. if (viewData.navBarItems) {
  5464. forEach(ITEM_TYPES, function(itemType) {
  5465. enteringHeaderBar.setItem(viewData.navBarItems[itemType], itemType);
  5466. });
  5467. }
  5468. // begin transition of entering and leaving header bars
  5469. self.transition(enteringHeaderBar, leavingHeaderBar, viewData);
  5470. self.isInitialized = true;
  5471. navSwipeAttr('');
  5472. };
  5473. self.transition = function(enteringHeaderBar, leavingHeaderBar, viewData) {
  5474. var enteringHeaderBarCtrl = enteringHeaderBar.controller();
  5475. var transitionFn = $ionicConfig.transitions.navBar[viewData.navBarTransition] || $ionicConfig.transitions.navBar.none;
  5476. var transitionId = viewData.transitionId;
  5477. enteringHeaderBarCtrl.beforeEnter(viewData);
  5478. var navBarTransition = transitionFn(enteringHeaderBar, leavingHeaderBar, viewData.direction, viewData.shouldAnimate && self.isInitialized);
  5479. ionic.DomUtil.cachedAttr($element, 'nav-bar-transition', viewData.navBarTransition);
  5480. ionic.DomUtil.cachedAttr($element, 'nav-bar-direction', viewData.direction);
  5481. if (navBarTransition.shouldAnimate && viewData.renderEnd) {
  5482. navBarAttr(enteringHeaderBar, 'stage');
  5483. } else {
  5484. navBarAttr(enteringHeaderBar, 'entering');
  5485. navBarAttr(leavingHeaderBar, 'leaving');
  5486. }
  5487. enteringHeaderBarCtrl.resetBackButton(viewData);
  5488. navBarTransition.run(0);
  5489. self.activeTransition = {
  5490. run: function(step) {
  5491. navBarTransition.shouldAnimate = false;
  5492. navBarTransition.direction = 'back';
  5493. navBarTransition.run(step);
  5494. },
  5495. cancel: function(shouldAnimate, speed, cancelData) {
  5496. navSwipeAttr(speed);
  5497. navBarAttr(leavingHeaderBar, 'active');
  5498. navBarAttr(enteringHeaderBar, 'cached');
  5499. navBarTransition.shouldAnimate = shouldAnimate;
  5500. navBarTransition.run(0);
  5501. self.activeTransition = navBarTransition = null;
  5502. var runApply;
  5503. if (cancelData.showBar !== self.showBar()) {
  5504. self.showBar(cancelData.showBar);
  5505. }
  5506. if (cancelData.showBackButton !== self.showBackButton()) {
  5507. self.showBackButton(cancelData.showBackButton);
  5508. }
  5509. if (runApply) {
  5510. $scope.$apply();
  5511. }
  5512. },
  5513. complete: function(shouldAnimate, speed) {
  5514. navSwipeAttr(speed);
  5515. navBarTransition.shouldAnimate = shouldAnimate;
  5516. navBarTransition.run(1);
  5517. queuedTransitionEnd = transitionEnd;
  5518. }
  5519. };
  5520. $timeout(enteringHeaderBarCtrl.align, 16);
  5521. queuedTransitionStart = function() {
  5522. if (latestTransitionId !== transitionId) return;
  5523. navBarAttr(enteringHeaderBar, 'entering');
  5524. navBarAttr(leavingHeaderBar, 'leaving');
  5525. navBarTransition.run(1);
  5526. queuedTransitionEnd = function() {
  5527. if (latestTransitionId == transitionId || !navBarTransition.shouldAnimate) {
  5528. transitionEnd();
  5529. }
  5530. };
  5531. queuedTransitionStart = null;
  5532. };
  5533. function transitionEnd() {
  5534. for (var x = 0; x < headerBars.length; x++) {
  5535. headerBars[x].isActive = false;
  5536. }
  5537. enteringHeaderBar.isActive = true;
  5538. navBarAttr(enteringHeaderBar, 'active');
  5539. navBarAttr(leavingHeaderBar, 'cached');
  5540. self.activeTransition = navBarTransition = queuedTransitionEnd = null;
  5541. }
  5542. queuedTransitionStart();
  5543. };
  5544. self.triggerTransitionStart = function(triggerTransitionId) {
  5545. latestTransitionId = triggerTransitionId;
  5546. queuedTransitionStart && queuedTransitionStart();
  5547. };
  5548. self.triggerTransitionEnd = function() {
  5549. queuedTransitionEnd && queuedTransitionEnd();
  5550. };
  5551. self.showBar = function(shouldShow) {
  5552. if (arguments.length) {
  5553. self.visibleBar(shouldShow);
  5554. $scope.$parent.$hasHeader = !!shouldShow;
  5555. }
  5556. return !!$scope.$parent.$hasHeader;
  5557. };
  5558. self.visibleBar = function(shouldShow) {
  5559. if (shouldShow && !isVisible) {
  5560. $element.removeClass(CSS_HIDE);
  5561. self.align();
  5562. } else if (!shouldShow && isVisible) {
  5563. $element.addClass(CSS_HIDE);
  5564. }
  5565. isVisible = shouldShow;
  5566. };
  5567. self.enable = function(val) {
  5568. // set primary to show first
  5569. self.visibleBar(val);
  5570. // set non primary to hide second
  5571. for (var x = 0; x < $ionicNavBarDelegate._instances.length; x++) {
  5572. if ($ionicNavBarDelegate._instances[x] !== self) $ionicNavBarDelegate._instances[x].visibleBar(false);
  5573. }
  5574. };
  5575. /**
  5576. * @ngdoc method
  5577. * @name $ionicNavBar#showBackButton
  5578. * @description Show/hide the nav bar back button when there is a
  5579. * back view. If the back button is not possible, for example, the
  5580. * first view in the stack, then this will not force the back button
  5581. * to show.
  5582. */
  5583. self.showBackButton = function(shouldShow) {
  5584. if (arguments.length) {
  5585. for (var x = 0; x < headerBars.length; x++) {
  5586. headerBars[x].controller().showNavBack(!!shouldShow);
  5587. }
  5588. $scope.$isBackButtonShown = !!shouldShow;
  5589. }
  5590. return $scope.$isBackButtonShown;
  5591. };
  5592. /**
  5593. * @ngdoc method
  5594. * @name $ionicNavBar#showActiveBackButton
  5595. * @description Show/hide only the active header bar's back button.
  5596. */
  5597. self.showActiveBackButton = function(shouldShow) {
  5598. var headerBar = getOnScreenHeaderBar();
  5599. if (headerBar) {
  5600. if (arguments.length) {
  5601. return headerBar.controller().showBack(shouldShow);
  5602. }
  5603. return headerBar.controller().showBack();
  5604. }
  5605. };
  5606. self.title = function(newTitleText, headerBar) {
  5607. if (isDefined(newTitleText)) {
  5608. newTitleText = newTitleText || '';
  5609. headerBar = headerBar || getOnScreenHeaderBar();
  5610. headerBar && headerBar.title(newTitleText);
  5611. $scope.$title = newTitleText;
  5612. $ionicHistory.currentTitle(newTitleText);
  5613. }
  5614. return $scope.$title;
  5615. };
  5616. self.align = function(val, headerBar) {
  5617. headerBar = headerBar || getOnScreenHeaderBar();
  5618. headerBar && headerBar.controller().align(val);
  5619. };
  5620. self.hasTabsTop = function(isTabsTop) {
  5621. $element[isTabsTop ? 'addClass' : 'removeClass']('nav-bar-tabs-top');
  5622. };
  5623. self.hasBarSubheader = function(isBarSubheader) {
  5624. $element[isBarSubheader ? 'addClass' : 'removeClass']('nav-bar-has-subheader');
  5625. };
  5626. // DEPRECATED, as of v1.0.0-beta14 -------
  5627. self.changeTitle = function(val) {
  5628. deprecatedWarning('changeTitle(val)', 'title(val)');
  5629. self.title(val);
  5630. };
  5631. self.setTitle = function(val) {
  5632. deprecatedWarning('setTitle(val)', 'title(val)');
  5633. self.title(val);
  5634. };
  5635. self.getTitle = function() {
  5636. deprecatedWarning('getTitle()', 'title()');
  5637. return self.title();
  5638. };
  5639. self.back = function() {
  5640. deprecatedWarning('back()', '$ionicHistory.goBack()');
  5641. $ionicHistory.goBack();
  5642. };
  5643. self.getPreviousTitle = function() {
  5644. deprecatedWarning('getPreviousTitle()', '$ionicHistory.backTitle()');
  5645. $ionicHistory.goBack();
  5646. };
  5647. function deprecatedWarning(oldMethod, newMethod) {
  5648. var warn = console.warn || console.log;
  5649. warn && warn.call(console, 'navBarController.' + oldMethod + ' is deprecated, please use ' + newMethod + ' instead');
  5650. }
  5651. // END DEPRECATED -------
  5652. function createNavElement(type) {
  5653. if (navElementHtml[type]) {
  5654. return jqLite(navElementHtml[type]);
  5655. }
  5656. }
  5657. function getOnScreenHeaderBar() {
  5658. for (var x = 0; x < headerBars.length; x++) {
  5659. if (headerBars[x].isActive) return headerBars[x];
  5660. }
  5661. }
  5662. function getOffScreenHeaderBar() {
  5663. for (var x = 0; x < headerBars.length; x++) {
  5664. if (!headerBars[x].isActive) return headerBars[x];
  5665. }
  5666. }
  5667. function navBarAttr(ctrl, val) {
  5668. ctrl && ionic.DomUtil.cachedAttr(ctrl.containerEle(), 'nav-bar', val);
  5669. }
  5670. function navSwipeAttr(val) {
  5671. ionic.DomUtil.cachedAttr($element, 'nav-swipe', val);
  5672. }
  5673. $scope.$on('$destroy', function() {
  5674. $scope.$parent.$hasHeader = false;
  5675. $element.parent().removeData(DATA_NAV_BAR_CTRL);
  5676. for (var x = 0; x < headerBars.length; x++) {
  5677. headerBars[x].destroy();
  5678. }
  5679. $element.remove();
  5680. $element = headerBars = null;
  5681. deregisterInstance();
  5682. });
  5683. }]);
  5684. IonicModule
  5685. .controller('$ionicNavView', [
  5686. '$scope',
  5687. '$element',
  5688. '$attrs',
  5689. '$compile',
  5690. '$controller',
  5691. '$ionicNavBarDelegate',
  5692. '$ionicNavViewDelegate',
  5693. '$ionicHistory',
  5694. '$ionicViewSwitcher',
  5695. '$ionicConfig',
  5696. '$ionicScrollDelegate',
  5697. '$ionicSideMenuDelegate',
  5698. function($scope, $element, $attrs, $compile, $controller, $ionicNavBarDelegate, $ionicNavViewDelegate, $ionicHistory, $ionicViewSwitcher, $ionicConfig, $ionicScrollDelegate, $ionicSideMenuDelegate) {
  5699. var DATA_ELE_IDENTIFIER = '$eleId';
  5700. var DATA_DESTROY_ELE = '$destroyEle';
  5701. var DATA_NO_CACHE = '$noCache';
  5702. var VIEW_STATUS_ACTIVE = 'active';
  5703. var VIEW_STATUS_CACHED = 'cached';
  5704. var self = this;
  5705. var direction;
  5706. var isPrimary = false;
  5707. var navBarDelegate;
  5708. var activeEleId;
  5709. var navViewAttr = $ionicViewSwitcher.navViewAttr;
  5710. var disableRenderStartViewId, disableAnimation;
  5711. self.scope = $scope;
  5712. self.element = $element;
  5713. self.init = function() {
  5714. var navViewName = $attrs.name || '';
  5715. // Find the details of the parent view directive (if any) and use it
  5716. // to derive our own qualified view name, then hang our own details
  5717. // off the DOM so child directives can find it.
  5718. var parent = $element.parent().inheritedData('$uiView');
  5719. var parentViewName = ((parent && parent.state) ? parent.state.name : '');
  5720. if (navViewName.indexOf('@') < 0) navViewName = navViewName + '@' + parentViewName;
  5721. var viewData = { name: navViewName, state: null };
  5722. $element.data('$uiView', viewData);
  5723. var deregisterInstance = $ionicNavViewDelegate._registerInstance(self, $attrs.delegateHandle);
  5724. $scope.$on('$destroy', function() {
  5725. deregisterInstance();
  5726. // ensure no scrolls have been left frozen
  5727. if (self.isSwipeFreeze) {
  5728. $ionicScrollDelegate.freezeAllScrolls(false);
  5729. }
  5730. });
  5731. $scope.$on('$ionicHistory.deselect', self.cacheCleanup);
  5732. $scope.$on('$ionicTabs.top', onTabsTop);
  5733. $scope.$on('$ionicSubheader', onBarSubheader);
  5734. $scope.$on('$ionicTabs.beforeLeave', onTabsLeave);
  5735. $scope.$on('$ionicTabs.afterLeave', onTabsLeave);
  5736. $scope.$on('$ionicTabs.leave', onTabsLeave);
  5737. ionic.Platform.ready(function() {
  5738. if ( ionic.Platform.isWebView() && ionic.Platform.isIOS() ) {
  5739. self.initSwipeBack();
  5740. }
  5741. });
  5742. return viewData;
  5743. };
  5744. self.register = function(viewLocals) {
  5745. var leavingView = extend({}, $ionicHistory.currentView());
  5746. // register that a view is coming in and get info on how it should transition
  5747. var registerData = $ionicHistory.register($scope, viewLocals);
  5748. // update which direction
  5749. self.update(registerData);
  5750. // begin rendering and transitioning
  5751. var enteringView = $ionicHistory.getViewById(registerData.viewId) || {};
  5752. var renderStart = (disableRenderStartViewId !== registerData.viewId);
  5753. self.render(registerData, viewLocals, enteringView, leavingView, renderStart, true);
  5754. };
  5755. self.update = function(registerData) {
  5756. // always reset that this is the primary navView
  5757. isPrimary = true;
  5758. // remember what direction this navView should use
  5759. // this may get updated later by a child navView
  5760. direction = registerData.direction;
  5761. var parentNavViewCtrl = $element.parent().inheritedData('$ionNavViewController');
  5762. if (parentNavViewCtrl) {
  5763. // this navView is nested inside another one
  5764. // update the parent to use this direction and not
  5765. // the other it originally was set to
  5766. // inform the parent navView that it is not the primary navView
  5767. parentNavViewCtrl.isPrimary(false);
  5768. if (direction === 'enter' || direction === 'exit') {
  5769. // they're entering/exiting a history
  5770. // find parent navViewController
  5771. parentNavViewCtrl.direction(direction);
  5772. if (direction === 'enter') {
  5773. // reset the direction so this navView doesn't animate
  5774. // because it's parent will
  5775. direction = 'none';
  5776. }
  5777. }
  5778. }
  5779. };
  5780. self.render = function(registerData, viewLocals, enteringView, leavingView, renderStart, renderEnd) {
  5781. // register the view and figure out where it lives in the various
  5782. // histories and nav stacks, along with how views should enter/leave
  5783. var switcher = $ionicViewSwitcher.create(self, viewLocals, enteringView, leavingView, renderStart, renderEnd);
  5784. // init the rendering of views for this navView directive
  5785. switcher.init(registerData, function() {
  5786. // the view is now compiled, in the dom and linked, now lets transition the views.
  5787. // this uses a callback incase THIS nav-view has a nested nav-view, and after the NESTED
  5788. // nav-view links, the NESTED nav-view would update which direction THIS nav-view should use
  5789. // kick off the transition of views
  5790. switcher.transition(self.direction(), registerData.enableBack, !disableAnimation);
  5791. // reset private vars for next time
  5792. disableRenderStartViewId = disableAnimation = null;
  5793. });
  5794. };
  5795. self.beforeEnter = function(transitionData) {
  5796. if (isPrimary) {
  5797. // only update this nav-view's nav-bar if this is the primary nav-view
  5798. navBarDelegate = transitionData.navBarDelegate;
  5799. var associatedNavBarCtrl = getAssociatedNavBarCtrl();
  5800. associatedNavBarCtrl && associatedNavBarCtrl.update(transitionData);
  5801. navSwipeAttr('');
  5802. }
  5803. };
  5804. self.activeEleId = function(eleId) {
  5805. if (arguments.length) {
  5806. activeEleId = eleId;
  5807. }
  5808. return activeEleId;
  5809. };
  5810. self.transitionEnd = function() {
  5811. var viewElements = $element.children();
  5812. var x, l, viewElement;
  5813. for (x = 0, l = viewElements.length; x < l; x++) {
  5814. viewElement = viewElements.eq(x);
  5815. if (viewElement.data(DATA_ELE_IDENTIFIER) === activeEleId) {
  5816. // this is the active element
  5817. navViewAttr(viewElement, VIEW_STATUS_ACTIVE);
  5818. } else if (navViewAttr(viewElement) === 'leaving' || navViewAttr(viewElement) === VIEW_STATUS_ACTIVE || navViewAttr(viewElement) === VIEW_STATUS_CACHED) {
  5819. // this is a leaving element or was the former active element, or is an cached element
  5820. if (viewElement.data(DATA_DESTROY_ELE) || viewElement.data(DATA_NO_CACHE)) {
  5821. // this element shouldn't stay cached
  5822. $ionicViewSwitcher.destroyViewEle(viewElement);
  5823. } else {
  5824. // keep in the DOM, mark as cached
  5825. navViewAttr(viewElement, VIEW_STATUS_CACHED);
  5826. // disconnect the leaving scope
  5827. ionic.Utils.disconnectScope(viewElement.scope());
  5828. }
  5829. }
  5830. }
  5831. navSwipeAttr('');
  5832. // ensure no scrolls have been left frozen
  5833. if (self.isSwipeFreeze) {
  5834. $ionicScrollDelegate.freezeAllScrolls(false);
  5835. }
  5836. };
  5837. function onTabsLeave(ev, data) {
  5838. var viewElements = $element.children();
  5839. var viewElement, viewScope;
  5840. for (var x = 0, l = viewElements.length; x < l; x++) {
  5841. viewElement = viewElements.eq(x);
  5842. if (navViewAttr(viewElement) == VIEW_STATUS_ACTIVE) {
  5843. viewScope = viewElement.scope();
  5844. viewScope && viewScope.$emit(ev.name.replace('Tabs', 'View'), data);
  5845. viewScope && viewScope.$broadcast(ev.name.replace('Tabs', 'ParentView'), data);
  5846. break;
  5847. }
  5848. }
  5849. }
  5850. self.cacheCleanup = function() {
  5851. var viewElements = $element.children();
  5852. for (var x = 0, l = viewElements.length; x < l; x++) {
  5853. if (viewElements.eq(x).data(DATA_DESTROY_ELE)) {
  5854. $ionicViewSwitcher.destroyViewEle(viewElements.eq(x));
  5855. }
  5856. }
  5857. };
  5858. self.clearCache = function(stateIds) {
  5859. var viewElements = $element.children();
  5860. var viewElement, viewScope, x, l, y, eleIdentifier;
  5861. for (x = 0, l = viewElements.length; x < l; x++) {
  5862. viewElement = viewElements.eq(x);
  5863. if (stateIds) {
  5864. eleIdentifier = viewElement.data(DATA_ELE_IDENTIFIER);
  5865. for (y = 0; y < stateIds.length; y++) {
  5866. if (eleIdentifier === stateIds[y]) {
  5867. $ionicViewSwitcher.destroyViewEle(viewElement);
  5868. }
  5869. }
  5870. continue;
  5871. }
  5872. if (navViewAttr(viewElement) == VIEW_STATUS_CACHED) {
  5873. $ionicViewSwitcher.destroyViewEle(viewElement);
  5874. } else if (navViewAttr(viewElement) == VIEW_STATUS_ACTIVE) {
  5875. viewScope = viewElement.scope();
  5876. viewScope && viewScope.$broadcast('$ionicView.clearCache');
  5877. }
  5878. }
  5879. };
  5880. self.getViewElements = function() {
  5881. return $element.children();
  5882. };
  5883. self.appendViewElement = function(viewEle, viewLocals) {
  5884. // compile the entering element and get the link function
  5885. var linkFn = $compile(viewEle);
  5886. $element.append(viewEle);
  5887. var viewScope = $scope.$new();
  5888. if (viewLocals && viewLocals.$$controller) {
  5889. viewLocals.$scope = viewScope;
  5890. var controller = $controller(viewLocals.$$controller, viewLocals);
  5891. if (viewLocals.$$controllerAs) {
  5892. viewScope[viewLocals.$$controllerAs] = controller;
  5893. }
  5894. $element.children().data('$ngControllerController', controller);
  5895. }
  5896. linkFn(viewScope);
  5897. return viewScope;
  5898. };
  5899. self.title = function(val) {
  5900. var associatedNavBarCtrl = getAssociatedNavBarCtrl();
  5901. associatedNavBarCtrl && associatedNavBarCtrl.title(val);
  5902. };
  5903. /**
  5904. * @ngdoc method
  5905. * @name $ionicNavView#enableBackButton
  5906. * @description Enable/disable if the back button can be shown or not. For
  5907. * example, the very first view in the navigation stack would not have a
  5908. * back view, so the back button would be disabled.
  5909. */
  5910. self.enableBackButton = function(shouldEnable) {
  5911. var associatedNavBarCtrl = getAssociatedNavBarCtrl();
  5912. associatedNavBarCtrl && associatedNavBarCtrl.enableBackButton(shouldEnable);
  5913. };
  5914. /**
  5915. * @ngdoc method
  5916. * @name $ionicNavView#showBackButton
  5917. * @description Show/hide the nav bar active back button. If the back button
  5918. * is not possible this will not force the back button to show. The
  5919. * `enableBackButton()` method handles if a back button is even possible or not.
  5920. */
  5921. self.showBackButton = function(shouldShow) {
  5922. var associatedNavBarCtrl = getAssociatedNavBarCtrl();
  5923. if (associatedNavBarCtrl) {
  5924. if (arguments.length) {
  5925. return associatedNavBarCtrl.showActiveBackButton(shouldShow);
  5926. }
  5927. return associatedNavBarCtrl.showActiveBackButton();
  5928. }
  5929. return true;
  5930. };
  5931. self.showBar = function(val) {
  5932. var associatedNavBarCtrl = getAssociatedNavBarCtrl();
  5933. if (associatedNavBarCtrl) {
  5934. if (arguments.length) {
  5935. return associatedNavBarCtrl.showBar(val);
  5936. }
  5937. return associatedNavBarCtrl.showBar();
  5938. }
  5939. return true;
  5940. };
  5941. self.isPrimary = function(val) {
  5942. if (arguments.length) {
  5943. isPrimary = val;
  5944. }
  5945. return isPrimary;
  5946. };
  5947. self.direction = function(val) {
  5948. if (arguments.length) {
  5949. direction = val;
  5950. }
  5951. return direction;
  5952. };
  5953. self.initSwipeBack = function() {
  5954. var swipeBackHitWidth = $ionicConfig.views.swipeBackHitWidth();
  5955. var viewTransition, associatedNavBarCtrl, backView;
  5956. var deregDragStart, deregDrag, deregRelease;
  5957. var windowWidth, startDragX, dragPoints;
  5958. var cancelData = {};
  5959. function onDragStart(ev) {
  5960. if (!isPrimary || !$ionicConfig.views.swipeBackEnabled() || $ionicSideMenuDelegate.isOpenRight() ) return;
  5961. startDragX = getDragX(ev);
  5962. if (startDragX > swipeBackHitWidth) return;
  5963. backView = $ionicHistory.backView();
  5964. var currentView = $ionicHistory.currentView();
  5965. if (!backView || backView.historyId !== currentView.historyId || currentView.canSwipeBack === false) return;
  5966. if (!windowWidth) windowWidth = window.innerWidth;
  5967. self.isSwipeFreeze = $ionicScrollDelegate.freezeAllScrolls(true);
  5968. var registerData = {
  5969. direction: 'back'
  5970. };
  5971. dragPoints = [];
  5972. cancelData = {
  5973. showBar: self.showBar(),
  5974. showBackButton: self.showBackButton()
  5975. };
  5976. var switcher = $ionicViewSwitcher.create(self, registerData, backView, currentView, true, false);
  5977. switcher.loadViewElements(registerData);
  5978. switcher.render(registerData);
  5979. viewTransition = switcher.transition('back', $ionicHistory.enabledBack(backView), true);
  5980. associatedNavBarCtrl = getAssociatedNavBarCtrl();
  5981. deregDrag = ionic.onGesture('drag', onDrag, $element[0]);
  5982. deregRelease = ionic.onGesture('release', onRelease, $element[0]);
  5983. }
  5984. function onDrag(ev) {
  5985. if (isPrimary && viewTransition) {
  5986. var dragX = getDragX(ev);
  5987. dragPoints.push({
  5988. t: Date.now(),
  5989. x: dragX
  5990. });
  5991. if (dragX >= windowWidth - 15) {
  5992. onRelease(ev);
  5993. } else {
  5994. var step = Math.min(Math.max(getSwipeCompletion(dragX), 0), 1);
  5995. viewTransition.run(step);
  5996. associatedNavBarCtrl && associatedNavBarCtrl.activeTransition && associatedNavBarCtrl.activeTransition.run(step);
  5997. }
  5998. }
  5999. }
  6000. function onRelease(ev) {
  6001. if (isPrimary && viewTransition && dragPoints && dragPoints.length > 1) {
  6002. var now = Date.now();
  6003. var releaseX = getDragX(ev);
  6004. var startDrag = dragPoints[dragPoints.length - 1];
  6005. for (var x = dragPoints.length - 2; x >= 0; x--) {
  6006. if (now - startDrag.t > 200) {
  6007. break;
  6008. }
  6009. startDrag = dragPoints[x];
  6010. }
  6011. var isSwipingRight = (releaseX >= dragPoints[dragPoints.length - 2].x);
  6012. var releaseSwipeCompletion = getSwipeCompletion(releaseX);
  6013. var velocity = Math.abs(startDrag.x - releaseX) / (now - startDrag.t);
  6014. // private variables because ui-router has no way to pass custom data using $state.go
  6015. disableRenderStartViewId = backView.viewId;
  6016. disableAnimation = (releaseSwipeCompletion < 0.03 || releaseSwipeCompletion > 0.97);
  6017. if (isSwipingRight && (releaseSwipeCompletion > 0.5 || velocity > 0.1)) {
  6018. // complete view transition on release
  6019. var speed = (velocity > 0.5 || velocity < 0.05 || releaseX > windowWidth - 45) ? 'fast' : 'slow';
  6020. navSwipeAttr(disableAnimation ? '' : speed);
  6021. backView.go();
  6022. associatedNavBarCtrl && associatedNavBarCtrl.activeTransition && associatedNavBarCtrl.activeTransition.complete(!disableAnimation, speed);
  6023. } else {
  6024. // cancel view transition on release
  6025. navSwipeAttr(disableAnimation ? '' : 'fast');
  6026. disableRenderStartViewId = null;
  6027. viewTransition.cancel(!disableAnimation);
  6028. associatedNavBarCtrl && associatedNavBarCtrl.activeTransition && associatedNavBarCtrl.activeTransition.cancel(!disableAnimation, 'fast', cancelData);
  6029. disableAnimation = null;
  6030. }
  6031. }
  6032. ionic.offGesture(deregDrag, 'drag', onDrag);
  6033. ionic.offGesture(deregRelease, 'release', onRelease);
  6034. windowWidth = viewTransition = dragPoints = null;
  6035. self.isSwipeFreeze = $ionicScrollDelegate.freezeAllScrolls(false);
  6036. }
  6037. function getDragX(ev) {
  6038. return ionic.tap.pointerCoord(ev.gesture.srcEvent).x;
  6039. }
  6040. function getSwipeCompletion(dragX) {
  6041. return (dragX - startDragX) / windowWidth;
  6042. }
  6043. deregDragStart = ionic.onGesture('dragstart', onDragStart, $element[0]);
  6044. $scope.$on('$destroy', function() {
  6045. ionic.offGesture(deregDragStart, 'dragstart', onDragStart);
  6046. ionic.offGesture(deregDrag, 'drag', onDrag);
  6047. ionic.offGesture(deregRelease, 'release', onRelease);
  6048. self.element = viewTransition = associatedNavBarCtrl = null;
  6049. });
  6050. };
  6051. function navSwipeAttr(val) {
  6052. ionic.DomUtil.cachedAttr($element, 'nav-swipe', val);
  6053. }
  6054. function onTabsTop(ev, isTabsTop) {
  6055. var associatedNavBarCtrl = getAssociatedNavBarCtrl();
  6056. associatedNavBarCtrl && associatedNavBarCtrl.hasTabsTop(isTabsTop);
  6057. }
  6058. function onBarSubheader(ev, isBarSubheader) {
  6059. var associatedNavBarCtrl = getAssociatedNavBarCtrl();
  6060. associatedNavBarCtrl && associatedNavBarCtrl.hasBarSubheader(isBarSubheader);
  6061. }
  6062. function getAssociatedNavBarCtrl() {
  6063. if (navBarDelegate) {
  6064. for (var x = 0; x < $ionicNavBarDelegate._instances.length; x++) {
  6065. if ($ionicNavBarDelegate._instances[x].$$delegateHandle == navBarDelegate) {
  6066. return $ionicNavBarDelegate._instances[x];
  6067. }
  6068. }
  6069. }
  6070. return $element.inheritedData('$ionNavBarController');
  6071. }
  6072. }]);
  6073. IonicModule
  6074. .controller('$ionicRefresher', [
  6075. '$scope',
  6076. '$attrs',
  6077. '$element',
  6078. '$ionicBind',
  6079. '$timeout',
  6080. function($scope, $attrs, $element, $ionicBind, $timeout) {
  6081. var self = this,
  6082. isDragging = false,
  6083. isOverscrolling = false,
  6084. dragOffset = 0,
  6085. lastOverscroll = 0,
  6086. ptrThreshold = 60,
  6087. activated = false,
  6088. scrollTime = 500,
  6089. startY = null,
  6090. deltaY = null,
  6091. canOverscroll = true,
  6092. scrollParent,
  6093. scrollChild;
  6094. if (!isDefined($attrs.pullingIcon)) {
  6095. $attrs.$set('pullingIcon', 'ion-android-arrow-down');
  6096. }
  6097. $scope.showSpinner = !isDefined($attrs.refreshingIcon) && $attrs.spinner != 'none';
  6098. $scope.showIcon = isDefined($attrs.refreshingIcon);
  6099. $ionicBind($scope, $attrs, {
  6100. pullingIcon: '@',
  6101. pullingText: '@',
  6102. refreshingIcon: '@',
  6103. refreshingText: '@',
  6104. spinner: '@',
  6105. disablePullingRotation: '@',
  6106. $onRefresh: '&onRefresh',
  6107. $onPulling: '&onPulling'
  6108. });
  6109. function handleMousedown(e) {
  6110. e.touches = e.touches || [{
  6111. screenX: e.screenX,
  6112. screenY: e.screenY
  6113. }];
  6114. // Mouse needs this
  6115. startY = Math.floor(e.touches[0].screenY);
  6116. }
  6117. function handleTouchstart(e) {
  6118. e.touches = e.touches || [{
  6119. screenX: e.screenX,
  6120. screenY: e.screenY
  6121. }];
  6122. startY = e.touches[0].screenY;
  6123. }
  6124. function handleTouchend() {
  6125. // reset Y
  6126. startY = null;
  6127. // if this wasn't an overscroll, get out immediately
  6128. if (!canOverscroll && !isDragging) {
  6129. return;
  6130. }
  6131. // the user has overscrolled but went back to native scrolling
  6132. if (!isDragging) {
  6133. dragOffset = 0;
  6134. isOverscrolling = false;
  6135. setScrollLock(false);
  6136. } else {
  6137. isDragging = false;
  6138. dragOffset = 0;
  6139. // the user has scroll far enough to trigger a refresh
  6140. if (lastOverscroll > ptrThreshold) {
  6141. start();
  6142. scrollTo(ptrThreshold, scrollTime);
  6143. // the user has overscrolled but not far enough to trigger a refresh
  6144. } else {
  6145. scrollTo(0, scrollTime, deactivate);
  6146. isOverscrolling = false;
  6147. }
  6148. }
  6149. }
  6150. function handleTouchmove(e) {
  6151. e.touches = e.touches || [{
  6152. screenX: e.screenX,
  6153. screenY: e.screenY
  6154. }];
  6155. // Force mouse events to have had a down event first
  6156. if (!startY && e.type == 'mousemove') {
  6157. return;
  6158. }
  6159. // if multitouch or regular scroll event, get out immediately
  6160. if (!canOverscroll || e.touches.length > 1) {
  6161. return;
  6162. }
  6163. //if this is a new drag, keep track of where we start
  6164. if (startY === null) {
  6165. startY = e.touches[0].screenY;
  6166. }
  6167. deltaY = e.touches[0].screenY - startY;
  6168. // how far have we dragged so far?
  6169. // kitkat fix for touchcancel events http://updates.html5rocks.com/2014/05/A-More-Compatible-Smoother-Touch
  6170. // Only do this if we're not on crosswalk
  6171. if (ionic.Platform.isAndroid() && ionic.Platform.version() === 4.4 && !ionic.Platform.isCrosswalk() && scrollParent.scrollTop === 0 && deltaY > 0) {
  6172. isDragging = true;
  6173. e.preventDefault();
  6174. }
  6175. // if we've dragged up and back down in to native scroll territory
  6176. if (deltaY - dragOffset <= 0 || scrollParent.scrollTop !== 0) {
  6177. if (isOverscrolling) {
  6178. isOverscrolling = false;
  6179. setScrollLock(false);
  6180. }
  6181. if (isDragging) {
  6182. nativescroll(scrollParent, deltaY - dragOffset * -1);
  6183. }
  6184. // if we're not at overscroll 0 yet, 0 out
  6185. if (lastOverscroll !== 0) {
  6186. overscroll(0);
  6187. }
  6188. return;
  6189. } else if (deltaY > 0 && scrollParent.scrollTop === 0 && !isOverscrolling) {
  6190. // starting overscroll, but drag started below scrollTop 0, so we need to offset the position
  6191. dragOffset = deltaY;
  6192. }
  6193. // prevent native scroll events while overscrolling
  6194. e.preventDefault();
  6195. // if not overscrolling yet, initiate overscrolling
  6196. if (!isOverscrolling) {
  6197. isOverscrolling = true;
  6198. setScrollLock(true);
  6199. }
  6200. isDragging = true;
  6201. // overscroll according to the user's drag so far
  6202. overscroll((deltaY - dragOffset) / 3);
  6203. // update the icon accordingly
  6204. if (!activated && lastOverscroll > ptrThreshold) {
  6205. activated = true;
  6206. ionic.requestAnimationFrame(activate);
  6207. } else if (activated && lastOverscroll < ptrThreshold) {
  6208. activated = false;
  6209. ionic.requestAnimationFrame(deactivate);
  6210. }
  6211. }
  6212. function handleScroll(e) {
  6213. // canOverscrol is used to greatly simplify the drag handler during normal scrolling
  6214. canOverscroll = (e.target.scrollTop === 0) || isDragging;
  6215. }
  6216. function overscroll(val) {
  6217. scrollChild.style[ionic.CSS.TRANSFORM] = 'translate3d(0px, ' + val + 'px, 0px)';
  6218. lastOverscroll = val;
  6219. }
  6220. function nativescroll(target, newScrollTop) {
  6221. // creates a scroll event that bubbles, can be cancelled, and with its view
  6222. // and detail property initialized to window and 1, respectively
  6223. target.scrollTop = newScrollTop;
  6224. var e = document.createEvent("UIEvents");
  6225. e.initUIEvent("scroll", true, true, window, 1);
  6226. target.dispatchEvent(e);
  6227. }
  6228. function setScrollLock(enabled) {
  6229. // set the scrollbar to be position:fixed in preparation to overscroll
  6230. // or remove it so the app can be natively scrolled
  6231. if (enabled) {
  6232. ionic.requestAnimationFrame(function() {
  6233. scrollChild.classList.add('overscroll');
  6234. show();
  6235. });
  6236. } else {
  6237. ionic.requestAnimationFrame(function() {
  6238. scrollChild.classList.remove('overscroll');
  6239. hide();
  6240. deactivate();
  6241. });
  6242. }
  6243. }
  6244. $scope.$on('scroll.refreshComplete', function() {
  6245. // prevent the complete from firing before the scroll has started
  6246. $timeout(function() {
  6247. ionic.requestAnimationFrame(tail);
  6248. // scroll back to home during tail animation
  6249. scrollTo(0, scrollTime, deactivate);
  6250. // return to native scrolling after tail animation has time to finish
  6251. $timeout(function() {
  6252. if (isOverscrolling) {
  6253. isOverscrolling = false;
  6254. setScrollLock(false);
  6255. }
  6256. }, scrollTime);
  6257. }, scrollTime);
  6258. });
  6259. function scrollTo(Y, duration, callback) {
  6260. // scroll animation loop w/ easing
  6261. // credit https://gist.github.com/dezinezync/5487119
  6262. var start = Date.now(),
  6263. from = lastOverscroll;
  6264. if (from === Y) {
  6265. callback();
  6266. return; /* Prevent scrolling to the Y point if already there */
  6267. }
  6268. // decelerating to zero velocity
  6269. function easeOutCubic(t) {
  6270. return (--t) * t * t + 1;
  6271. }
  6272. // scroll loop
  6273. function scroll() {
  6274. var currentTime = Date.now(),
  6275. time = Math.min(1, ((currentTime - start) / duration)),
  6276. // where .5 would be 50% of time on a linear scale easedT gives a
  6277. // fraction based on the easing method
  6278. easedT = easeOutCubic(time);
  6279. overscroll(Math.floor((easedT * (Y - from)) + from));
  6280. if (time < 1) {
  6281. ionic.requestAnimationFrame(scroll);
  6282. } else {
  6283. if (Y < 5 && Y > -5) {
  6284. isOverscrolling = false;
  6285. setScrollLock(false);
  6286. }
  6287. callback && callback();
  6288. }
  6289. }
  6290. // start scroll loop
  6291. ionic.requestAnimationFrame(scroll);
  6292. }
  6293. var touchStartEvent, touchMoveEvent, touchEndEvent;
  6294. if (window.navigator.pointerEnabled) {
  6295. touchStartEvent = 'pointerdown';
  6296. touchMoveEvent = 'pointermove';
  6297. touchEndEvent = 'pointerup';
  6298. } else if (window.navigator.msPointerEnabled) {
  6299. touchStartEvent = 'MSPointerDown';
  6300. touchMoveEvent = 'MSPointerMove';
  6301. touchEndEvent = 'MSPointerUp';
  6302. } else {
  6303. touchStartEvent = 'touchstart';
  6304. touchMoveEvent = 'touchmove';
  6305. touchEndEvent = 'touchend';
  6306. }
  6307. self.init = function() {
  6308. scrollParent = $element.parent().parent()[0];
  6309. scrollChild = $element.parent()[0];
  6310. if (!scrollParent || !scrollParent.classList.contains('ionic-scroll') ||
  6311. !scrollChild || !scrollChild.classList.contains('scroll')) {
  6312. throw new Error('Refresher must be immediate child of ion-content or ion-scroll');
  6313. }
  6314. ionic.on(touchStartEvent, handleTouchstart, scrollChild);
  6315. ionic.on(touchMoveEvent, handleTouchmove, scrollChild);
  6316. ionic.on(touchEndEvent, handleTouchend, scrollChild);
  6317. ionic.on('mousedown', handleMousedown, scrollChild);
  6318. ionic.on('mousemove', handleTouchmove, scrollChild);
  6319. ionic.on('mouseup', handleTouchend, scrollChild);
  6320. ionic.on('scroll', handleScroll, scrollParent);
  6321. // cleanup when done
  6322. $scope.$on('$destroy', destroy);
  6323. };
  6324. function destroy() {
  6325. if ( scrollChild ) {
  6326. ionic.off(touchStartEvent, handleTouchstart, scrollChild);
  6327. ionic.off(touchMoveEvent, handleTouchmove, scrollChild);
  6328. ionic.off(touchEndEvent, handleTouchend, scrollChild);
  6329. ionic.off('mousedown', handleMousedown, scrollChild);
  6330. ionic.off('mousemove', handleTouchmove, scrollChild);
  6331. ionic.off('mouseup', handleTouchend, scrollChild);
  6332. }
  6333. if ( scrollParent ) {
  6334. ionic.off('scroll', handleScroll, scrollParent);
  6335. }
  6336. scrollParent = null;
  6337. scrollChild = null;
  6338. }
  6339. // DOM manipulation and broadcast methods shared by JS and Native Scrolling
  6340. // getter used by JS Scrolling
  6341. self.getRefresherDomMethods = function() {
  6342. return {
  6343. activate: activate,
  6344. deactivate: deactivate,
  6345. start: start,
  6346. show: show,
  6347. hide: hide,
  6348. tail: tail
  6349. };
  6350. };
  6351. function activate() {
  6352. $element[0].classList.add('active');
  6353. $scope.$onPulling();
  6354. }
  6355. function deactivate() {
  6356. // give tail 150ms to finish
  6357. $timeout(function() {
  6358. // deactivateCallback
  6359. $element.removeClass('active refreshing refreshing-tail');
  6360. if (activated) activated = false;
  6361. }, 150);
  6362. }
  6363. function start() {
  6364. // startCallback
  6365. $element[0].classList.add('refreshing');
  6366. var q = $scope.$onRefresh();
  6367. if (q && q.then) {
  6368. q['finally'](function() {
  6369. $scope.$broadcast('scroll.refreshComplete');
  6370. });
  6371. }
  6372. }
  6373. function show() {
  6374. // showCallback
  6375. $element[0].classList.remove('invisible');
  6376. }
  6377. function hide() {
  6378. // showCallback
  6379. $element[0].classList.add('invisible');
  6380. }
  6381. function tail() {
  6382. // tailCallback
  6383. $element[0].classList.add('refreshing-tail');
  6384. }
  6385. // for testing
  6386. self.__handleTouchmove = handleTouchmove;
  6387. self.__getScrollChild = function() { return scrollChild; };
  6388. self.__getScrollParent = function() { return scrollParent; };
  6389. }
  6390. ]);
  6391. /**
  6392. * @private
  6393. */
  6394. IonicModule
  6395. .controller('$ionicScroll', [
  6396. '$scope',
  6397. 'scrollViewOptions',
  6398. '$timeout',
  6399. '$window',
  6400. '$location',
  6401. '$document',
  6402. '$ionicScrollDelegate',
  6403. '$ionicHistory',
  6404. function($scope,
  6405. scrollViewOptions,
  6406. $timeout,
  6407. $window,
  6408. $location,
  6409. $document,
  6410. $ionicScrollDelegate,
  6411. $ionicHistory) {
  6412. var self = this;
  6413. // for testing
  6414. self.__timeout = $timeout;
  6415. self._scrollViewOptions = scrollViewOptions; //for testing
  6416. self.isNative = function() {
  6417. return !!scrollViewOptions.nativeScrolling;
  6418. };
  6419. var element = self.element = scrollViewOptions.el;
  6420. var $element = self.$element = jqLite(element);
  6421. var scrollView;
  6422. if (self.isNative()) {
  6423. scrollView = self.scrollView = new ionic.views.ScrollNative(scrollViewOptions);
  6424. } else {
  6425. scrollView = self.scrollView = new ionic.views.Scroll(scrollViewOptions);
  6426. }
  6427. //Attach self to element as a controller so other directives can require this controller
  6428. //through `require: '$ionicScroll'
  6429. //Also attach to parent so that sibling elements can require this
  6430. ($element.parent().length ? $element.parent() : $element)
  6431. .data('$$ionicScrollController', self);
  6432. var deregisterInstance = $ionicScrollDelegate._registerInstance(
  6433. self, scrollViewOptions.delegateHandle, function() {
  6434. return $ionicHistory.isActiveScope($scope);
  6435. }
  6436. );
  6437. if (!isDefined(scrollViewOptions.bouncing)) {
  6438. ionic.Platform.ready(function() {
  6439. if (scrollView && scrollView.options) {
  6440. scrollView.options.bouncing = true;
  6441. if (ionic.Platform.isAndroid()) {
  6442. // No bouncing by default on Android
  6443. scrollView.options.bouncing = false;
  6444. // Faster scroll decel
  6445. scrollView.options.deceleration = 0.95;
  6446. }
  6447. }
  6448. });
  6449. }
  6450. var resize = angular.bind(scrollView, scrollView.resize);
  6451. angular.element($window).on('resize', resize);
  6452. var scrollFunc = function(e) {
  6453. var detail = (e.originalEvent || e).detail || {};
  6454. $scope.$onScroll && $scope.$onScroll({
  6455. event: e,
  6456. scrollTop: detail.scrollTop || 0,
  6457. scrollLeft: detail.scrollLeft || 0
  6458. });
  6459. };
  6460. $element.on('scroll', scrollFunc);
  6461. $scope.$on('$destroy', function() {
  6462. deregisterInstance();
  6463. scrollView && scrollView.__cleanup && scrollView.__cleanup();
  6464. angular.element($window).off('resize', resize);
  6465. if ( $element ) {
  6466. $element.off('scroll', scrollFunc);
  6467. }
  6468. if ( self._scrollViewOptions ) {
  6469. self._scrollViewOptions.el = null;
  6470. }
  6471. if ( scrollViewOptions ) {
  6472. scrollViewOptions.el = null;
  6473. }
  6474. scrollView = self.scrollView = scrollViewOptions = self._scrollViewOptions = element = self.$element = $element = null;
  6475. });
  6476. $timeout(function() {
  6477. scrollView && scrollView.run && scrollView.run();
  6478. });
  6479. self.getScrollView = function() {
  6480. return scrollView;
  6481. };
  6482. self.getScrollPosition = function() {
  6483. return scrollView.getValues();
  6484. };
  6485. self.resize = function() {
  6486. return $timeout(resize, 0, false).then(function() {
  6487. $element && $element.triggerHandler('scroll-resize');
  6488. });
  6489. };
  6490. self.scrollTop = function(shouldAnimate) {
  6491. self.resize().then(function() {
  6492. if (!scrollView) {
  6493. return;
  6494. }
  6495. scrollView.scrollTo(0, 0, !!shouldAnimate);
  6496. });
  6497. };
  6498. self.scrollBottom = function(shouldAnimate) {
  6499. self.resize().then(function() {
  6500. if (!scrollView) {
  6501. return;
  6502. }
  6503. var max = scrollView.getScrollMax();
  6504. scrollView.scrollTo(max.left, max.top, !!shouldAnimate);
  6505. });
  6506. };
  6507. self.scrollTo = function(left, top, shouldAnimate) {
  6508. self.resize().then(function() {
  6509. if (!scrollView) {
  6510. return;
  6511. }
  6512. scrollView.scrollTo(left, top, !!shouldAnimate);
  6513. });
  6514. };
  6515. self.zoomTo = function(zoom, shouldAnimate, originLeft, originTop) {
  6516. self.resize().then(function() {
  6517. if (!scrollView) {
  6518. return;
  6519. }
  6520. scrollView.zoomTo(zoom, !!shouldAnimate, originLeft, originTop);
  6521. });
  6522. };
  6523. self.zoomBy = function(zoom, shouldAnimate, originLeft, originTop) {
  6524. self.resize().then(function() {
  6525. if (!scrollView) {
  6526. return;
  6527. }
  6528. scrollView.zoomBy(zoom, !!shouldAnimate, originLeft, originTop);
  6529. });
  6530. };
  6531. self.scrollBy = function(left, top, shouldAnimate) {
  6532. self.resize().then(function() {
  6533. if (!scrollView) {
  6534. return;
  6535. }
  6536. scrollView.scrollBy(left, top, !!shouldAnimate);
  6537. });
  6538. };
  6539. self.anchorScroll = function(shouldAnimate) {
  6540. self.resize().then(function() {
  6541. if (!scrollView) {
  6542. return;
  6543. }
  6544. var hash = $location.hash();
  6545. var elm = hash && $document[0].getElementById(hash);
  6546. if (!(hash && elm)) {
  6547. scrollView.scrollTo(0, 0, !!shouldAnimate);
  6548. return;
  6549. }
  6550. var curElm = elm;
  6551. var scrollLeft = 0, scrollTop = 0;
  6552. do {
  6553. if (curElm !== null) scrollLeft += curElm.offsetLeft;
  6554. if (curElm !== null) scrollTop += curElm.offsetTop;
  6555. curElm = curElm.offsetParent;
  6556. } while (curElm.attributes != self.element.attributes && curElm.offsetParent);
  6557. scrollView.scrollTo(scrollLeft, scrollTop, !!shouldAnimate);
  6558. });
  6559. };
  6560. self.freezeScroll = scrollView.freeze;
  6561. self.freezeScrollShut = scrollView.freezeShut;
  6562. self.freezeAllScrolls = function(shouldFreeze) {
  6563. for (var i = 0; i < $ionicScrollDelegate._instances.length; i++) {
  6564. $ionicScrollDelegate._instances[i].freezeScroll(shouldFreeze);
  6565. }
  6566. };
  6567. /**
  6568. * @private
  6569. */
  6570. self._setRefresher = function(refresherScope, refresherElement, refresherMethods) {
  6571. self.refresher = refresherElement;
  6572. var refresherHeight = self.refresher.clientHeight || 60;
  6573. scrollView.activatePullToRefresh(
  6574. refresherHeight,
  6575. refresherMethods
  6576. );
  6577. };
  6578. }]);
  6579. IonicModule
  6580. .controller('$ionicSideMenus', [
  6581. '$scope',
  6582. '$attrs',
  6583. '$ionicSideMenuDelegate',
  6584. '$ionicPlatform',
  6585. '$ionicBody',
  6586. '$ionicHistory',
  6587. '$ionicScrollDelegate',
  6588. 'IONIC_BACK_PRIORITY',
  6589. '$rootScope',
  6590. function($scope, $attrs, $ionicSideMenuDelegate, $ionicPlatform, $ionicBody, $ionicHistory, $ionicScrollDelegate, IONIC_BACK_PRIORITY, $rootScope) {
  6591. var self = this;
  6592. var rightShowing, leftShowing, isDragging;
  6593. var startX, lastX, offsetX, isAsideExposed;
  6594. var enableMenuWithBackViews = true;
  6595. self.$scope = $scope;
  6596. self.initialize = function(options) {
  6597. self.left = options.left;
  6598. self.right = options.right;
  6599. self.setContent(options.content);
  6600. self.dragThresholdX = options.dragThresholdX || 10;
  6601. $ionicHistory.registerHistory(self.$scope);
  6602. };
  6603. /**
  6604. * Set the content view controller if not passed in the constructor options.
  6605. *
  6606. * @param {object} content
  6607. */
  6608. self.setContent = function(content) {
  6609. if (content) {
  6610. self.content = content;
  6611. self.content.onDrag = function(e) {
  6612. self._handleDrag(e);
  6613. };
  6614. self.content.endDrag = function(e) {
  6615. self._endDrag(e);
  6616. };
  6617. }
  6618. };
  6619. self.isOpenLeft = function() {
  6620. return self.getOpenAmount() > 0;
  6621. };
  6622. self.isOpenRight = function() {
  6623. return self.getOpenAmount() < 0;
  6624. };
  6625. /**
  6626. * Toggle the left menu to open 100%
  6627. */
  6628. self.toggleLeft = function(shouldOpen) {
  6629. if (isAsideExposed || !self.left.isEnabled) return;
  6630. var openAmount = self.getOpenAmount();
  6631. if (arguments.length === 0) {
  6632. shouldOpen = openAmount <= 0;
  6633. }
  6634. self.content.enableAnimation();
  6635. if (!shouldOpen) {
  6636. self.openPercentage(0);
  6637. $rootScope.$emit('$ionicSideMenuClose', 'left');
  6638. } else {
  6639. self.openPercentage(100);
  6640. $rootScope.$emit('$ionicSideMenuOpen', 'left');
  6641. }
  6642. };
  6643. /**
  6644. * Toggle the right menu to open 100%
  6645. */
  6646. self.toggleRight = function(shouldOpen) {
  6647. if (isAsideExposed || !self.right.isEnabled) return;
  6648. var openAmount = self.getOpenAmount();
  6649. if (arguments.length === 0) {
  6650. shouldOpen = openAmount >= 0;
  6651. }
  6652. self.content.enableAnimation();
  6653. if (!shouldOpen) {
  6654. self.openPercentage(0);
  6655. $rootScope.$emit('$ionicSideMenuClose', 'right');
  6656. } else {
  6657. self.openPercentage(-100);
  6658. $rootScope.$emit('$ionicSideMenuOpen', 'right');
  6659. }
  6660. };
  6661. self.toggle = function(side) {
  6662. if (side == 'right') {
  6663. self.toggleRight();
  6664. } else {
  6665. self.toggleLeft();
  6666. }
  6667. };
  6668. /**
  6669. * Close all menus.
  6670. */
  6671. self.close = function() {
  6672. self.openPercentage(0);
  6673. $rootScope.$emit('$ionicSideMenuClose', 'left');
  6674. $rootScope.$emit('$ionicSideMenuClose', 'right');
  6675. };
  6676. /**
  6677. * @return {float} The amount the side menu is open, either positive or negative for left (positive), or right (negative)
  6678. */
  6679. self.getOpenAmount = function() {
  6680. return self.content && self.content.getTranslateX() || 0;
  6681. };
  6682. /**
  6683. * @return {float} The ratio of open amount over menu width. For example, a
  6684. * menu of width 100 open 50 pixels would be open 50% or a ratio of 0.5. Value is negative
  6685. * for right menu.
  6686. */
  6687. self.getOpenRatio = function() {
  6688. var amount = self.getOpenAmount();
  6689. if (amount >= 0) {
  6690. return amount / self.left.width;
  6691. }
  6692. return amount / self.right.width;
  6693. };
  6694. self.isOpen = function() {
  6695. return self.getOpenAmount() !== 0;
  6696. };
  6697. /**
  6698. * @return {float} The percentage of open amount over menu width. For example, a
  6699. * menu of width 100 open 50 pixels would be open 50%. Value is negative
  6700. * for right menu.
  6701. */
  6702. self.getOpenPercentage = function() {
  6703. return self.getOpenRatio() * 100;
  6704. };
  6705. /**
  6706. * Open the menu with a given percentage amount.
  6707. * @param {float} percentage The percentage (positive or negative for left/right) to open the menu.
  6708. */
  6709. self.openPercentage = function(percentage) {
  6710. var p = percentage / 100;
  6711. if (self.left && percentage >= 0) {
  6712. self.openAmount(self.left.width * p);
  6713. } else if (self.right && percentage < 0) {
  6714. self.openAmount(self.right.width * p);
  6715. }
  6716. // add the CSS class "menu-open" if the percentage does not
  6717. // equal 0, otherwise remove the class from the body element
  6718. $ionicBody.enableClass((percentage !== 0), 'menu-open');
  6719. self.content.setCanScroll(percentage == 0);
  6720. };
  6721. /*
  6722. function freezeAllScrolls(shouldFreeze) {
  6723. if (shouldFreeze && !self.isScrollFreeze) {
  6724. $ionicScrollDelegate.freezeAllScrolls(shouldFreeze);
  6725. } else if (!shouldFreeze && self.isScrollFreeze) {
  6726. $ionicScrollDelegate.freezeAllScrolls(false);
  6727. }
  6728. self.isScrollFreeze = shouldFreeze;
  6729. }
  6730. */
  6731. /**
  6732. * Open the menu the given pixel amount.
  6733. * @param {float} amount the pixel amount to open the menu. Positive value for left menu,
  6734. * negative value for right menu (only one menu will be visible at a time).
  6735. */
  6736. self.openAmount = function(amount) {
  6737. var maxLeft = self.left && self.left.width || 0;
  6738. var maxRight = self.right && self.right.width || 0;
  6739. // Check if we can move to that side, depending if the left/right panel is enabled
  6740. if (!(self.left && self.left.isEnabled) && amount > 0) {
  6741. self.content.setTranslateX(0);
  6742. return;
  6743. }
  6744. if (!(self.right && self.right.isEnabled) && amount < 0) {
  6745. self.content.setTranslateX(0);
  6746. return;
  6747. }
  6748. if (leftShowing && amount > maxLeft) {
  6749. self.content.setTranslateX(maxLeft);
  6750. return;
  6751. }
  6752. if (rightShowing && amount < -maxRight) {
  6753. self.content.setTranslateX(-maxRight);
  6754. return;
  6755. }
  6756. self.content.setTranslateX(amount);
  6757. leftShowing = amount > 0;
  6758. rightShowing = amount < 0;
  6759. if (amount > 0) {
  6760. // Push the z-index of the right menu down
  6761. self.right && self.right.pushDown && self.right.pushDown();
  6762. // Bring the z-index of the left menu up
  6763. self.left && self.left.bringUp && self.left.bringUp();
  6764. } else {
  6765. // Bring the z-index of the right menu up
  6766. self.right && self.right.bringUp && self.right.bringUp();
  6767. // Push the z-index of the left menu down
  6768. self.left && self.left.pushDown && self.left.pushDown();
  6769. }
  6770. };
  6771. /**
  6772. * Given an event object, find the final resting position of this side
  6773. * menu. For example, if the user "throws" the content to the right and
  6774. * releases the touch, the left menu should snap open (animated, of course).
  6775. *
  6776. * @param {Event} e the gesture event to use for snapping
  6777. */
  6778. self.snapToRest = function(e) {
  6779. // We want to animate at the end of this
  6780. self.content.enableAnimation();
  6781. isDragging = false;
  6782. // Check how much the panel is open after the drag, and
  6783. // what the drag velocity is
  6784. var ratio = self.getOpenRatio();
  6785. if (ratio === 0) {
  6786. // Just to be safe
  6787. self.openPercentage(0);
  6788. return;
  6789. }
  6790. var velocityThreshold = 0.3;
  6791. var velocityX = e.gesture.velocityX;
  6792. var direction = e.gesture.direction;
  6793. // Going right, less than half, too slow (snap back)
  6794. if (ratio > 0 && ratio < 0.5 && direction == 'right' && velocityX < velocityThreshold) {
  6795. self.openPercentage(0);
  6796. }
  6797. // Going left, more than half, too slow (snap back)
  6798. else if (ratio > 0.5 && direction == 'left' && velocityX < velocityThreshold) {
  6799. self.openPercentage(100);
  6800. }
  6801. // Going left, less than half, too slow (snap back)
  6802. else if (ratio < 0 && ratio > -0.5 && direction == 'left' && velocityX < velocityThreshold) {
  6803. self.openPercentage(0);
  6804. }
  6805. // Going right, more than half, too slow (snap back)
  6806. else if (ratio < 0.5 && direction == 'right' && velocityX < velocityThreshold) {
  6807. self.openPercentage(-100);
  6808. }
  6809. // Going right, more than half, or quickly (snap open)
  6810. else if (direction == 'right' && ratio >= 0 && (ratio >= 0.5 || velocityX > velocityThreshold)) {
  6811. self.openPercentage(100);
  6812. }
  6813. // Going left, more than half, or quickly (span open)
  6814. else if (direction == 'left' && ratio <= 0 && (ratio <= -0.5 || velocityX > velocityThreshold)) {
  6815. self.openPercentage(-100);
  6816. }
  6817. // Snap back for safety
  6818. else {
  6819. self.openPercentage(0);
  6820. }
  6821. };
  6822. self.enableMenuWithBackViews = function(val) {
  6823. if (arguments.length) {
  6824. enableMenuWithBackViews = !!val;
  6825. }
  6826. return enableMenuWithBackViews;
  6827. };
  6828. self.isAsideExposed = function() {
  6829. return !!isAsideExposed;
  6830. };
  6831. self.exposeAside = function(shouldExposeAside) {
  6832. if (!(self.left && self.left.isEnabled) && !(self.right && self.right.isEnabled)) return;
  6833. self.close();
  6834. isAsideExposed = shouldExposeAside;
  6835. if ((self.left && self.left.isEnabled) && (self.right && self.right.isEnabled)) {
  6836. self.content.setMarginLeftAndRight(isAsideExposed ? self.left.width : 0, isAsideExposed ? self.right.width : 0);
  6837. } else if (self.left && self.left.isEnabled) {
  6838. // set the left marget width if it should be exposed
  6839. // otherwise set false so there's no left margin
  6840. self.content.setMarginLeft(isAsideExposed ? self.left.width : 0);
  6841. } else if (self.right && self.right.isEnabled) {
  6842. self.content.setMarginRight(isAsideExposed ? self.right.width : 0);
  6843. }
  6844. self.$scope.$emit('$ionicExposeAside', isAsideExposed);
  6845. };
  6846. self.activeAsideResizing = function(isResizing) {
  6847. $ionicBody.enableClass(isResizing, 'aside-resizing');
  6848. };
  6849. // End a drag with the given event
  6850. self._endDrag = function(e) {
  6851. if (isAsideExposed) return;
  6852. if (isDragging) {
  6853. self.snapToRest(e);
  6854. }
  6855. startX = null;
  6856. lastX = null;
  6857. offsetX = null;
  6858. };
  6859. // Handle a drag event
  6860. self._handleDrag = function(e) {
  6861. if (isAsideExposed || !$scope.dragContent) return;
  6862. // If we don't have start coords, grab and store them
  6863. if (!startX) {
  6864. startX = e.gesture.touches[0].pageX;
  6865. lastX = startX;
  6866. } else {
  6867. // Grab the current tap coords
  6868. lastX = e.gesture.touches[0].pageX;
  6869. }
  6870. // Calculate difference from the tap points
  6871. if (!isDragging && Math.abs(lastX - startX) > self.dragThresholdX) {
  6872. // if the difference is greater than threshold, start dragging using the current
  6873. // point as the starting point
  6874. startX = lastX;
  6875. isDragging = true;
  6876. // Initialize dragging
  6877. self.content.disableAnimation();
  6878. offsetX = self.getOpenAmount();
  6879. }
  6880. if (isDragging) {
  6881. self.openAmount(offsetX + (lastX - startX));
  6882. //self.content.setCanScroll(false);
  6883. }
  6884. };
  6885. self.canDragContent = function(canDrag) {
  6886. if (arguments.length) {
  6887. $scope.dragContent = !!canDrag;
  6888. }
  6889. return $scope.dragContent;
  6890. };
  6891. self.edgeThreshold = 25;
  6892. self.edgeThresholdEnabled = false;
  6893. self.edgeDragThreshold = function(value) {
  6894. if (arguments.length) {
  6895. if (isNumber(value) && value > 0) {
  6896. self.edgeThreshold = value;
  6897. self.edgeThresholdEnabled = true;
  6898. } else {
  6899. self.edgeThresholdEnabled = !!value;
  6900. }
  6901. }
  6902. return self.edgeThresholdEnabled;
  6903. };
  6904. self.isDraggableTarget = function(e) {
  6905. //Only restrict edge when sidemenu is closed and restriction is enabled
  6906. var shouldOnlyAllowEdgeDrag = self.edgeThresholdEnabled && !self.isOpen();
  6907. var startX = e.gesture.startEvent && e.gesture.startEvent.center &&
  6908. e.gesture.startEvent.center.pageX;
  6909. var dragIsWithinBounds = !shouldOnlyAllowEdgeDrag ||
  6910. startX <= self.edgeThreshold ||
  6911. startX >= self.content.element.offsetWidth - self.edgeThreshold;
  6912. var backView = $ionicHistory.backView();
  6913. var menuEnabled = enableMenuWithBackViews ? true : !backView;
  6914. if (!menuEnabled) {
  6915. var currentView = $ionicHistory.currentView() || {};
  6916. return (dragIsWithinBounds && (backView.historyId !== currentView.historyId));
  6917. }
  6918. return ($scope.dragContent || self.isOpen()) &&
  6919. dragIsWithinBounds &&
  6920. !e.gesture.srcEvent.defaultPrevented &&
  6921. menuEnabled &&
  6922. !e.target.tagName.match(/input|textarea|select|object|embed/i) &&
  6923. !e.target.isContentEditable &&
  6924. !(e.target.dataset ? e.target.dataset.preventScroll : e.target.getAttribute('data-prevent-scroll') == 'true');
  6925. };
  6926. $scope.sideMenuContentTranslateX = 0;
  6927. var deregisterBackButtonAction = noop;
  6928. var closeSideMenu = angular.bind(self, self.close);
  6929. $scope.$watch(function() {
  6930. return self.getOpenAmount() !== 0;
  6931. }, function(isOpen) {
  6932. deregisterBackButtonAction();
  6933. if (isOpen) {
  6934. deregisterBackButtonAction = $ionicPlatform.registerBackButtonAction(
  6935. closeSideMenu,
  6936. IONIC_BACK_PRIORITY.sideMenu
  6937. );
  6938. }
  6939. });
  6940. var deregisterInstance = $ionicSideMenuDelegate._registerInstance(
  6941. self, $attrs.delegateHandle, function() {
  6942. return $ionicHistory.isActiveScope($scope);
  6943. }
  6944. );
  6945. $scope.$on('$destroy', function() {
  6946. deregisterInstance();
  6947. deregisterBackButtonAction();
  6948. self.$scope = null;
  6949. if (self.content) {
  6950. self.content.setCanScroll(true);
  6951. self.content.element = null;
  6952. self.content = null;
  6953. }
  6954. });
  6955. self.initialize({
  6956. left: {
  6957. width: 275
  6958. },
  6959. right: {
  6960. width: 275
  6961. }
  6962. });
  6963. }]);
  6964. (function(ionic) {
  6965. var TRANSLATE32 = 'translate(32,32)';
  6966. var STROKE_OPACITY = 'stroke-opacity';
  6967. var ROUND = 'round';
  6968. var INDEFINITE = 'indefinite';
  6969. var DURATION = '750ms';
  6970. var NONE = 'none';
  6971. var SHORTCUTS = {
  6972. a: 'animate',
  6973. an: 'attributeName',
  6974. at: 'animateTransform',
  6975. c: 'circle',
  6976. da: 'stroke-dasharray',
  6977. os: 'stroke-dashoffset',
  6978. f: 'fill',
  6979. lc: 'stroke-linecap',
  6980. rc: 'repeatCount',
  6981. sw: 'stroke-width',
  6982. t: 'transform',
  6983. v: 'values'
  6984. };
  6985. var SPIN_ANIMATION = {
  6986. v: '0,32,32;360,32,32',
  6987. an: 'transform',
  6988. type: 'rotate',
  6989. rc: INDEFINITE,
  6990. dur: DURATION
  6991. };
  6992. function createSvgElement(tagName, data, parent, spinnerName) {
  6993. var ele = document.createElement(SHORTCUTS[tagName] || tagName);
  6994. var k, x, y;
  6995. for (k in data) {
  6996. if (angular.isArray(data[k])) {
  6997. for (x = 0; x < data[k].length; x++) {
  6998. if (data[k][x].fn) {
  6999. for (y = 0; y < data[k][x].t; y++) {
  7000. createSvgElement(k, data[k][x].fn(y, spinnerName), ele, spinnerName);
  7001. }
  7002. } else {
  7003. createSvgElement(k, data[k][x], ele, spinnerName);
  7004. }
  7005. }
  7006. } else {
  7007. setSvgAttribute(ele, k, data[k]);
  7008. }
  7009. }
  7010. parent.appendChild(ele);
  7011. }
  7012. function setSvgAttribute(ele, k, v) {
  7013. ele.setAttribute(SHORTCUTS[k] || k, v);
  7014. }
  7015. function animationValues(strValues, i) {
  7016. var values = strValues.split(';');
  7017. var back = values.slice(i);
  7018. var front = values.slice(0, values.length - back.length);
  7019. values = back.concat(front).reverse();
  7020. return values.join(';') + ';' + values[0];
  7021. }
  7022. var IOS_SPINNER = {
  7023. sw: 4,
  7024. lc: ROUND,
  7025. line: [{
  7026. fn: function(i, spinnerName) {
  7027. return {
  7028. y1: spinnerName == 'ios' ? 17 : 12,
  7029. y2: spinnerName == 'ios' ? 29 : 20,
  7030. t: TRANSLATE32 + ' rotate(' + (30 * i + (i < 6 ? 180 : -180)) + ')',
  7031. a: [{
  7032. fn: function() {
  7033. return {
  7034. an: STROKE_OPACITY,
  7035. dur: DURATION,
  7036. v: animationValues('0;.1;.15;.25;.35;.45;.55;.65;.7;.85;1', i),
  7037. rc: INDEFINITE
  7038. };
  7039. },
  7040. t: 1
  7041. }]
  7042. };
  7043. },
  7044. t: 12
  7045. }]
  7046. };
  7047. var spinners = {
  7048. android: {
  7049. c: [{
  7050. sw: 6,
  7051. da: 128,
  7052. os: 82,
  7053. r: 26,
  7054. cx: 32,
  7055. cy: 32,
  7056. f: NONE
  7057. }]
  7058. },
  7059. ios: IOS_SPINNER,
  7060. 'ios-small': IOS_SPINNER,
  7061. bubbles: {
  7062. sw: 0,
  7063. c: [{
  7064. fn: function(i) {
  7065. return {
  7066. cx: 24 * Math.cos(2 * Math.PI * i / 8),
  7067. cy: 24 * Math.sin(2 * Math.PI * i / 8),
  7068. t: TRANSLATE32,
  7069. a: [{
  7070. fn: function() {
  7071. return {
  7072. an: 'r',
  7073. dur: DURATION,
  7074. v: animationValues('1;2;3;4;5;6;7;8', i),
  7075. rc: INDEFINITE
  7076. };
  7077. },
  7078. t: 1
  7079. }]
  7080. };
  7081. },
  7082. t: 8
  7083. }]
  7084. },
  7085. circles: {
  7086. c: [{
  7087. fn: function(i) {
  7088. return {
  7089. r: 5,
  7090. cx: 24 * Math.cos(2 * Math.PI * i / 8),
  7091. cy: 24 * Math.sin(2 * Math.PI * i / 8),
  7092. t: TRANSLATE32,
  7093. sw: 0,
  7094. a: [{
  7095. fn: function() {
  7096. return {
  7097. an: 'fill-opacity',
  7098. dur: DURATION,
  7099. v: animationValues('.3;.3;.3;.4;.7;.85;.9;1', i),
  7100. rc: INDEFINITE
  7101. };
  7102. },
  7103. t: 1
  7104. }]
  7105. };
  7106. },
  7107. t: 8
  7108. }]
  7109. },
  7110. crescent: {
  7111. c: [{
  7112. sw: 4,
  7113. da: 128,
  7114. os: 82,
  7115. r: 26,
  7116. cx: 32,
  7117. cy: 32,
  7118. f: NONE,
  7119. at: [SPIN_ANIMATION]
  7120. }]
  7121. },
  7122. dots: {
  7123. c: [{
  7124. fn: function(i) {
  7125. return {
  7126. cx: 16 + (16 * i),
  7127. cy: 32,
  7128. sw: 0,
  7129. a: [{
  7130. fn: function() {
  7131. return {
  7132. an: 'fill-opacity',
  7133. dur: DURATION,
  7134. v: animationValues('.5;.6;.8;1;.8;.6;.5', i),
  7135. rc: INDEFINITE
  7136. };
  7137. },
  7138. t: 1
  7139. }, {
  7140. fn: function() {
  7141. return {
  7142. an: 'r',
  7143. dur: DURATION,
  7144. v: animationValues('4;5;6;5;4;3;3', i),
  7145. rc: INDEFINITE
  7146. };
  7147. },
  7148. t: 1
  7149. }]
  7150. };
  7151. },
  7152. t: 3
  7153. }]
  7154. },
  7155. lines: {
  7156. sw: 7,
  7157. lc: ROUND,
  7158. line: [{
  7159. fn: function(i) {
  7160. return {
  7161. x1: 10 + (i * 14),
  7162. x2: 10 + (i * 14),
  7163. a: [{
  7164. fn: function() {
  7165. return {
  7166. an: 'y1',
  7167. dur: DURATION,
  7168. v: animationValues('16;18;28;18;16', i),
  7169. rc: INDEFINITE
  7170. };
  7171. },
  7172. t: 1
  7173. }, {
  7174. fn: function() {
  7175. return {
  7176. an: 'y2',
  7177. dur: DURATION,
  7178. v: animationValues('48;44;36;46;48', i),
  7179. rc: INDEFINITE
  7180. };
  7181. },
  7182. t: 1
  7183. }, {
  7184. fn: function() {
  7185. return {
  7186. an: STROKE_OPACITY,
  7187. dur: DURATION,
  7188. v: animationValues('1;.8;.5;.4;1', i),
  7189. rc: INDEFINITE
  7190. };
  7191. },
  7192. t: 1
  7193. }]
  7194. };
  7195. },
  7196. t: 4
  7197. }]
  7198. },
  7199. ripple: {
  7200. f: NONE,
  7201. 'fill-rule': 'evenodd',
  7202. sw: 3,
  7203. circle: [{
  7204. fn: function(i) {
  7205. return {
  7206. cx: 32,
  7207. cy: 32,
  7208. a: [{
  7209. fn: function() {
  7210. return {
  7211. an: 'r',
  7212. begin: (i * -1) + 's',
  7213. dur: '2s',
  7214. v: '0;24',
  7215. keyTimes: '0;1',
  7216. keySplines: '0.1,0.2,0.3,1',
  7217. calcMode: 'spline',
  7218. rc: INDEFINITE
  7219. };
  7220. },
  7221. t: 1
  7222. }, {
  7223. fn: function() {
  7224. return {
  7225. an: STROKE_OPACITY,
  7226. begin: (i * -1) + 's',
  7227. dur: '2s',
  7228. v: '.2;1;.2;0',
  7229. rc: INDEFINITE
  7230. };
  7231. },
  7232. t: 1
  7233. }]
  7234. };
  7235. },
  7236. t: 2
  7237. }]
  7238. },
  7239. spiral: {
  7240. defs: [{
  7241. linearGradient: [{
  7242. id: 'sGD',
  7243. gradientUnits: 'userSpaceOnUse',
  7244. x1: 55, y1: 46, x2: 2, y2: 46,
  7245. stop: [{
  7246. offset: 0.1,
  7247. class: 'stop1'
  7248. }, {
  7249. offset: 1,
  7250. class: 'stop2'
  7251. }]
  7252. }]
  7253. }],
  7254. g: [{
  7255. sw: 4,
  7256. lc: ROUND,
  7257. f: NONE,
  7258. path: [{
  7259. stroke: 'url(#sGD)',
  7260. d: 'M4,32 c0,15,12,28,28,28c8,0,16-4,21-9'
  7261. }, {
  7262. d: 'M60,32 C60,16,47.464,4,32,4S4,16,4,32'
  7263. }],
  7264. at: [SPIN_ANIMATION]
  7265. }]
  7266. }
  7267. };
  7268. var animations = {
  7269. android: function(ele) {
  7270. // Note that this is called as a function, not a constructor.
  7271. var self = {};
  7272. this.stop = false;
  7273. var rIndex = 0;
  7274. var rotateCircle = 0;
  7275. var startTime;
  7276. var svgEle = ele.querySelector('g');
  7277. var circleEle = ele.querySelector('circle');
  7278. function run() {
  7279. if (self.stop) return;
  7280. var v = easeInOutCubic(Date.now() - startTime, 650);
  7281. var scaleX = 1;
  7282. var translateX = 0;
  7283. var dasharray = (188 - (58 * v));
  7284. var dashoffset = (182 - (182 * v));
  7285. if (rIndex % 2) {
  7286. scaleX = -1;
  7287. translateX = -64;
  7288. dasharray = (128 - (-58 * v));
  7289. dashoffset = (182 * v);
  7290. }
  7291. var rotateLine = [0, -101, -90, -11, -180, 79, -270, -191][rIndex];
  7292. setSvgAttribute(circleEle, 'da', Math.max(Math.min(dasharray, 188), 128));
  7293. setSvgAttribute(circleEle, 'os', Math.max(Math.min(dashoffset, 182), 0));
  7294. setSvgAttribute(circleEle, 't', 'scale(' + scaleX + ',1) translate(' + translateX + ',0) rotate(' + rotateLine + ',32,32)');
  7295. rotateCircle += 4.1;
  7296. if (rotateCircle > 359) rotateCircle = 0;
  7297. setSvgAttribute(svgEle, 't', 'rotate(' + rotateCircle + ',32,32)');
  7298. if (v >= 1) {
  7299. rIndex++;
  7300. if (rIndex > 7) rIndex = 0;
  7301. startTime = Date.now();
  7302. }
  7303. ionic.requestAnimationFrame(run);
  7304. }
  7305. return function() {
  7306. startTime = Date.now();
  7307. run();
  7308. return self;
  7309. };
  7310. }
  7311. };
  7312. function easeInOutCubic(t, c) {
  7313. t /= c / 2;
  7314. if (t < 1) return 1 / 2 * t * t * t;
  7315. t -= 2;
  7316. return 1 / 2 * (t * t * t + 2);
  7317. }
  7318. IonicModule
  7319. .controller('$ionicSpinner', [
  7320. '$element',
  7321. '$attrs',
  7322. '$ionicConfig',
  7323. function($element, $attrs, $ionicConfig) {
  7324. var spinnerName, anim;
  7325. this.init = function() {
  7326. spinnerName = $attrs.icon || $ionicConfig.spinner.icon();
  7327. var container = document.createElement('div');
  7328. createSvgElement('svg', {
  7329. viewBox: '0 0 64 64',
  7330. g: [spinners[spinnerName]]
  7331. }, container, spinnerName);
  7332. // Specifically for animations to work,
  7333. // Android 4.3 and below requires the element to be
  7334. // added as an html string, rather than dynmically
  7335. // building up the svg element and appending it.
  7336. $element.html(container.innerHTML);
  7337. this.start();
  7338. return spinnerName;
  7339. };
  7340. this.start = function() {
  7341. animations[spinnerName] && (anim = animations[spinnerName]($element[0])());
  7342. };
  7343. this.stop = function() {
  7344. animations[spinnerName] && (anim.stop = true);
  7345. };
  7346. }]);
  7347. })(ionic);
  7348. IonicModule
  7349. .controller('$ionicTab', [
  7350. '$scope',
  7351. '$ionicHistory',
  7352. '$attrs',
  7353. '$location',
  7354. '$state',
  7355. function($scope, $ionicHistory, $attrs, $location, $state) {
  7356. this.$scope = $scope;
  7357. //All of these exposed for testing
  7358. this.hrefMatchesState = function() {
  7359. return $attrs.href && $location.path().indexOf(
  7360. $attrs.href.replace(/^#/, '').replace(/\/$/, '')
  7361. ) === 0;
  7362. };
  7363. this.srefMatchesState = function() {
  7364. return $attrs.uiSref && $state.includes($attrs.uiSref.split('(')[0]);
  7365. };
  7366. this.navNameMatchesState = function() {
  7367. return this.navViewName && $ionicHistory.isCurrentStateNavView(this.navViewName);
  7368. };
  7369. this.tabMatchesState = function() {
  7370. return this.hrefMatchesState() || this.srefMatchesState() || this.navNameMatchesState();
  7371. };
  7372. }]);
  7373. IonicModule
  7374. .controller('$ionicTabs', [
  7375. '$scope',
  7376. '$element',
  7377. '$ionicHistory',
  7378. function($scope, $element, $ionicHistory) {
  7379. var self = this;
  7380. var selectedTab = null;
  7381. var previousSelectedTab = null;
  7382. var selectedTabIndex;
  7383. var isVisible = true;
  7384. self.tabs = [];
  7385. self.selectedIndex = function() {
  7386. return self.tabs.indexOf(selectedTab);
  7387. };
  7388. self.selectedTab = function() {
  7389. return selectedTab;
  7390. };
  7391. self.previousSelectedTab = function() {
  7392. return previousSelectedTab;
  7393. };
  7394. self.add = function(tab) {
  7395. $ionicHistory.registerHistory(tab);
  7396. self.tabs.push(tab);
  7397. };
  7398. self.remove = function(tab) {
  7399. var tabIndex = self.tabs.indexOf(tab);
  7400. if (tabIndex === -1) {
  7401. return;
  7402. }
  7403. //Use a field like '$tabSelected' so developers won't accidentally set it in controllers etc
  7404. if (tab.$tabSelected) {
  7405. self.deselect(tab);
  7406. //Try to select a new tab if we're removing a tab
  7407. if (self.tabs.length === 1) {
  7408. //Do nothing if there are no other tabs to select
  7409. } else {
  7410. //Select previous tab if it's the last tab, else select next tab
  7411. var newTabIndex = tabIndex === self.tabs.length - 1 ? tabIndex - 1 : tabIndex + 1;
  7412. self.select(self.tabs[newTabIndex]);
  7413. }
  7414. }
  7415. self.tabs.splice(tabIndex, 1);
  7416. };
  7417. self.deselect = function(tab) {
  7418. if (tab.$tabSelected) {
  7419. previousSelectedTab = selectedTab;
  7420. selectedTab = selectedTabIndex = null;
  7421. tab.$tabSelected = false;
  7422. (tab.onDeselect || noop)();
  7423. tab.$broadcast && tab.$broadcast('$ionicHistory.deselect');
  7424. }
  7425. };
  7426. self.select = function(tab, shouldEmitEvent) {
  7427. var tabIndex;
  7428. if (isNumber(tab)) {
  7429. tabIndex = tab;
  7430. if (tabIndex >= self.tabs.length) return;
  7431. tab = self.tabs[tabIndex];
  7432. } else {
  7433. tabIndex = self.tabs.indexOf(tab);
  7434. }
  7435. if (arguments.length === 1) {
  7436. shouldEmitEvent = !!(tab.navViewName || tab.uiSref);
  7437. }
  7438. if (selectedTab && selectedTab.$historyId == tab.$historyId) {
  7439. if (shouldEmitEvent) {
  7440. $ionicHistory.goToHistoryRoot(tab.$historyId);
  7441. }
  7442. } else if (selectedTabIndex !== tabIndex) {
  7443. forEach(self.tabs, function(tab) {
  7444. self.deselect(tab);
  7445. });
  7446. selectedTab = tab;
  7447. selectedTabIndex = tabIndex;
  7448. if (self.$scope && self.$scope.$parent) {
  7449. self.$scope.$parent.$activeHistoryId = tab.$historyId;
  7450. }
  7451. //Use a funny name like $tabSelected so the developer doesn't overwrite the var in a child scope
  7452. tab.$tabSelected = true;
  7453. (tab.onSelect || noop)();
  7454. if (shouldEmitEvent) {
  7455. $scope.$emit('$ionicHistory.change', {
  7456. type: 'tab',
  7457. tabIndex: tabIndex,
  7458. historyId: tab.$historyId,
  7459. navViewName: tab.navViewName,
  7460. hasNavView: !!tab.navViewName,
  7461. title: tab.title,
  7462. url: tab.href,
  7463. uiSref: tab.uiSref
  7464. });
  7465. }
  7466. $scope.$broadcast("tabSelected", { selectedTab: tab, selectedTabIndex: tabIndex});
  7467. }
  7468. };
  7469. self.hasActiveScope = function() {
  7470. for (var x = 0; x < self.tabs.length; x++) {
  7471. if ($ionicHistory.isActiveScope(self.tabs[x])) {
  7472. return true;
  7473. }
  7474. }
  7475. return false;
  7476. };
  7477. self.showBar = function(show) {
  7478. if (arguments.length) {
  7479. if (show) {
  7480. $element.removeClass('tabs-item-hide');
  7481. } else {
  7482. $element.addClass('tabs-item-hide');
  7483. }
  7484. isVisible = !!show;
  7485. }
  7486. return isVisible;
  7487. };
  7488. }]);
  7489. IonicModule
  7490. .controller('$ionicView', [
  7491. '$scope',
  7492. '$element',
  7493. '$attrs',
  7494. '$compile',
  7495. '$rootScope',
  7496. function($scope, $element, $attrs, $compile, $rootScope) {
  7497. var self = this;
  7498. var navElementHtml = {};
  7499. var navViewCtrl;
  7500. var navBarDelegateHandle;
  7501. var hasViewHeaderBar;
  7502. var deregisters = [];
  7503. var viewTitle;
  7504. var deregIonNavBarInit = $scope.$on('ionNavBar.init', function(ev, delegateHandle) {
  7505. // this view has its own ion-nav-bar, remember the navBarDelegateHandle for this view
  7506. ev.stopPropagation();
  7507. navBarDelegateHandle = delegateHandle;
  7508. });
  7509. self.init = function() {
  7510. deregIonNavBarInit();
  7511. var modalCtrl = $element.inheritedData('$ionModalController');
  7512. navViewCtrl = $element.inheritedData('$ionNavViewController');
  7513. // don't bother if inside a modal or there's no parent navView
  7514. if (!navViewCtrl || modalCtrl) return;
  7515. // add listeners for when this view changes
  7516. $scope.$on('$ionicView.beforeEnter', self.beforeEnter);
  7517. $scope.$on('$ionicView.afterEnter', afterEnter);
  7518. $scope.$on('$ionicView.beforeLeave', deregisterFns);
  7519. };
  7520. self.beforeEnter = function(ev, transData) {
  7521. // this event was emitted, starting at intial ion-view, then bubbles up
  7522. // only the first ion-view should do something with it, parent ion-views should ignore
  7523. if (transData && !transData.viewNotified) {
  7524. transData.viewNotified = true;
  7525. if (!$rootScope.$$phase) $scope.$digest();
  7526. viewTitle = isDefined($attrs.viewTitle) ? $attrs.viewTitle : $attrs.title;
  7527. var navBarItems = {};
  7528. for (var n in navElementHtml) {
  7529. navBarItems[n] = generateNavBarItem(navElementHtml[n]);
  7530. }
  7531. navViewCtrl.beforeEnter(extend(transData, {
  7532. title: viewTitle,
  7533. showBack: !attrTrue('hideBackButton'),
  7534. navBarItems: navBarItems,
  7535. navBarDelegate: navBarDelegateHandle || null,
  7536. showNavBar: !attrTrue('hideNavBar'),
  7537. hasHeaderBar: !!hasViewHeaderBar
  7538. }));
  7539. // make sure any existing observers are cleaned up
  7540. deregisterFns();
  7541. }
  7542. };
  7543. function afterEnter() {
  7544. // only listen for title updates after it has entered
  7545. // but also deregister the observe before it leaves
  7546. var viewTitleAttr = isDefined($attrs.viewTitle) && 'viewTitle' || isDefined($attrs.title) && 'title';
  7547. if (viewTitleAttr) {
  7548. titleUpdate($attrs[viewTitleAttr]);
  7549. deregisters.push($attrs.$observe(viewTitleAttr, titleUpdate));
  7550. }
  7551. if (isDefined($attrs.hideBackButton)) {
  7552. deregisters.push($scope.$watch($attrs.hideBackButton, function(val) {
  7553. navViewCtrl.showBackButton(!val);
  7554. }));
  7555. }
  7556. if (isDefined($attrs.hideNavBar)) {
  7557. deregisters.push($scope.$watch($attrs.hideNavBar, function(val) {
  7558. navViewCtrl.showBar(!val);
  7559. }));
  7560. }
  7561. }
  7562. function titleUpdate(newTitle) {
  7563. if (isDefined(newTitle) && newTitle !== viewTitle) {
  7564. viewTitle = newTitle;
  7565. navViewCtrl.title(viewTitle);
  7566. }
  7567. }
  7568. function deregisterFns() {
  7569. // remove all existing $attrs.$observe's
  7570. for (var x = 0; x < deregisters.length; x++) {
  7571. deregisters[x]();
  7572. }
  7573. deregisters = [];
  7574. }
  7575. function generateNavBarItem(html) {
  7576. if (html) {
  7577. // every time a view enters we need to recreate its view buttons if they exist
  7578. return $compile(html)($scope.$new());
  7579. }
  7580. }
  7581. function attrTrue(key) {
  7582. return !!$scope.$eval($attrs[key]);
  7583. }
  7584. self.navElement = function(type, html) {
  7585. navElementHtml[type] = html;
  7586. };
  7587. }]);
  7588. /*
  7589. * We don't document the ionActionSheet directive, we instead document
  7590. * the $ionicActionSheet service
  7591. */
  7592. IonicModule
  7593. .directive('ionActionSheet', ['$document', function($document) {
  7594. return {
  7595. restrict: 'E',
  7596. scope: true,
  7597. replace: true,
  7598. link: function($scope, $element) {
  7599. var keyUp = function(e) {
  7600. if (e.which == 27) {
  7601. $scope.cancel();
  7602. $scope.$apply();
  7603. }
  7604. };
  7605. var backdropClick = function(e) {
  7606. if (e.target == $element[0]) {
  7607. $scope.cancel();
  7608. $scope.$apply();
  7609. }
  7610. };
  7611. $scope.$on('$destroy', function() {
  7612. $element.remove();
  7613. $document.unbind('keyup', keyUp);
  7614. });
  7615. $document.bind('keyup', keyUp);
  7616. $element.bind('click', backdropClick);
  7617. },
  7618. template: '<div class="action-sheet-backdrop">' +
  7619. '<div class="action-sheet-wrapper">' +
  7620. '<div class="action-sheet" ng-class="{\'action-sheet-has-icons\': $actionSheetHasIcon}">' +
  7621. '<div class="action-sheet-group action-sheet-options">' +
  7622. '<div class="action-sheet-title" ng-if="titleText" ng-bind-html="titleText"></div>' +
  7623. '<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>' +
  7624. '<button class="button destructive action-sheet-destructive" ng-if="destructiveText" ng-click="destructiveButtonClicked()" ng-bind-html="destructiveText"></button>' +
  7625. '</div>' +
  7626. '<div class="action-sheet-group action-sheet-cancel" ng-if="cancelText">' +
  7627. '<button class="button" ng-click="cancel()" ng-bind-html="cancelText"></button>' +
  7628. '</div>' +
  7629. '</div>' +
  7630. '</div>' +
  7631. '</div>'
  7632. };
  7633. }]);
  7634. /**
  7635. * @ngdoc directive
  7636. * @name ionCheckbox
  7637. * @module ionic
  7638. * @restrict E
  7639. * @codepen hqcju
  7640. * @description
  7641. * The checkbox is no different than the HTML checkbox input, except it's styled differently.
  7642. *
  7643. * The checkbox behaves like any [AngularJS checkbox](http://docs.angularjs.org/api/ng/input/input[checkbox]).
  7644. *
  7645. * @usage
  7646. * ```html
  7647. * <ion-checkbox ng-model="isChecked">Checkbox Label</ion-checkbox>
  7648. * ```
  7649. */
  7650. IonicModule
  7651. .directive('ionCheckbox', ['$ionicConfig', function($ionicConfig) {
  7652. return {
  7653. restrict: 'E',
  7654. replace: true,
  7655. require: '?ngModel',
  7656. transclude: true,
  7657. template:
  7658. '<label class="item item-checkbox">' +
  7659. '<div class="checkbox checkbox-input-hidden disable-pointer-events">' +
  7660. '<input type="checkbox">' +
  7661. '<i class="checkbox-icon"></i>' +
  7662. '</div>' +
  7663. '<div class="item-content disable-pointer-events" ng-transclude></div>' +
  7664. '</label>',
  7665. compile: function(element, attr) {
  7666. var input = element.find('input');
  7667. forEach({
  7668. 'name': attr.name,
  7669. 'ng-value': attr.ngValue,
  7670. 'ng-model': attr.ngModel,
  7671. 'ng-checked': attr.ngChecked,
  7672. 'ng-disabled': attr.ngDisabled,
  7673. 'ng-true-value': attr.ngTrueValue,
  7674. 'ng-false-value': attr.ngFalseValue,
  7675. 'ng-change': attr.ngChange,
  7676. 'ng-required': attr.ngRequired,
  7677. 'required': attr.required
  7678. }, function(value, name) {
  7679. if (isDefined(value)) {
  7680. input.attr(name, value);
  7681. }
  7682. });
  7683. var checkboxWrapper = element[0].querySelector('.checkbox');
  7684. checkboxWrapper.classList.add('checkbox-' + $ionicConfig.form.checkbox());
  7685. }
  7686. };
  7687. }]);
  7688. /**
  7689. * @ngdoc directive
  7690. * @restrict A
  7691. * @name collectionRepeat
  7692. * @module ionic
  7693. * @codepen 7ec1ec58f2489ab8f359fa1a0fe89c15
  7694. * @description
  7695. * `collection-repeat` allows an app to show huge lists of items much more performantly than
  7696. * `ng-repeat`.
  7697. *
  7698. * It renders into the DOM only as many items as are currently visible.
  7699. *
  7700. * This means that on a phone screen that can fit eight items, only the eight items matching
  7701. * the current scroll position will be rendered.
  7702. *
  7703. * **The Basics**:
  7704. *
  7705. * - The data given to collection-repeat must be an array.
  7706. * - If the `item-height` and `item-width` attributes are not supplied, it will be assumed that
  7707. * every item in the list has the same dimensions as the first item.
  7708. * - Don't use angular one-time binding (`::`) with collection-repeat. The scope of each item is
  7709. * assigned new data and re-digested as you scroll. Bindings need to update, and one-time bindings
  7710. * won't.
  7711. *
  7712. * **Performance Tips**:
  7713. *
  7714. * - The iOS webview has a performance bottleneck when switching out `<img src>` attributes.
  7715. * To increase performance of images on iOS, cache your images in advance and,
  7716. * if possible, lower the number of unique images. We're working on [a solution](https://github.com/driftyco/ionic/issues/3194).
  7717. *
  7718. * @usage
  7719. * #### Basic Item List ([codepen](http://codepen.io/ionic/pen/0c2c35a34a8b18ad4d793fef0b081693))
  7720. * ```html
  7721. * <ion-content>
  7722. * <ion-item collection-repeat="item in items">
  7723. * {% raw %}{{item}}{% endraw %}
  7724. * </ion-item>
  7725. * </ion-content>
  7726. * ```
  7727. *
  7728. * #### Grid of Images ([codepen](http://codepen.io/ionic/pen/5515d4efd9d66f780e96787387f41664))
  7729. * ```html
  7730. * <ion-content>
  7731. * <img collection-repeat="photo in photos"
  7732. * item-width="33%"
  7733. * item-height="200px"
  7734. * ng-src="{% raw %}{{photo.url}}{% endraw %}">
  7735. * </ion-content>
  7736. * ```
  7737. *
  7738. * #### Horizontal Scroller, Dynamic Item Width ([codepen](http://codepen.io/ionic/pen/67cc56b349124a349acb57a0740e030e))
  7739. * ```html
  7740. * <ion-content>
  7741. * <h2>Available Kittens:</h2>
  7742. * <ion-scroll direction="x" class="available-scroller">
  7743. * <div class="photo" collection-repeat="photo in main.photos"
  7744. * item-height="250" item-width="photo.width + 30">
  7745. * <img ng-src="{% raw %}{{photo.src}}{% endraw %}">
  7746. * </div>
  7747. * </ion-scroll>
  7748. * </ion-content>
  7749. * ```
  7750. *
  7751. * @param {expression} collection-repeat The expression indicating how to enumerate a collection,
  7752. * of the format `variable in expression` – where variable is the user defined loop variable
  7753. * and `expression` is a scope expression giving the collection to enumerate.
  7754. * For example: `album in artist.albums` or `album in artist.albums | orderBy:'name'`.
  7755. * @param {expression=} item-width The width of the repeated element. The expression must return
  7756. * a number (pixels) or a percentage. Defaults to the width of the first item in the list.
  7757. * (previously named collection-item-width)
  7758. * @param {expression=} item-height The height of the repeated element. The expression must return
  7759. * a number (pixels) or a percentage. Defaults to the height of the first item in the list.
  7760. * (previously named collection-item-height)
  7761. * @param {number=} item-render-buffer The number of items to load before and after the visible
  7762. * items in the list. Default 3. Tip: set this higher if you have lots of images to preload, but
  7763. * don't set it too high or you'll see performance loss.
  7764. * @param {boolean=} force-refresh-images Force images to refresh as you scroll. This fixes a problem
  7765. * where, when an element is interchanged as scrolling, its image will still have the old src
  7766. * while the new src loads. Setting this to true comes with a small performance loss.
  7767. */
  7768. IonicModule
  7769. .directive('collectionRepeat', CollectionRepeatDirective)
  7770. .factory('$ionicCollectionManager', RepeatManagerFactory);
  7771. var ONE_PX_TRANSPARENT_IMG_SRC = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
  7772. var WIDTH_HEIGHT_REGEX = /height:.*?px;\s*width:.*?px/;
  7773. var DEFAULT_RENDER_BUFFER = 3;
  7774. CollectionRepeatDirective.$inject = ['$ionicCollectionManager', '$parse', '$window', '$$rAF', '$rootScope', '$timeout'];
  7775. function CollectionRepeatDirective($ionicCollectionManager, $parse, $window, $$rAF, $rootScope, $timeout) {
  7776. return {
  7777. restrict: 'A',
  7778. priority: 1000,
  7779. transclude: 'element',
  7780. $$tlb: true,
  7781. require: '^^$ionicScroll',
  7782. link: postLink
  7783. };
  7784. function postLink(scope, element, attr, scrollCtrl, transclude) {
  7785. var scrollView = scrollCtrl.scrollView;
  7786. var node = element[0];
  7787. var containerNode = angular.element('<div class="collection-repeat-container">')[0];
  7788. node.parentNode.replaceChild(containerNode, node);
  7789. if (scrollView.options.scrollingX && scrollView.options.scrollingY) {
  7790. throw new Error("collection-repeat expected a parent x or y scrollView, not " +
  7791. "an xy scrollView.");
  7792. }
  7793. var repeatExpr = attr.collectionRepeat;
  7794. var match = repeatExpr.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?\s*$/);
  7795. if (!match) {
  7796. throw new Error("collection-repeat expected expression in form of '_item_ in " +
  7797. "_collection_[ track by _id_]' but got '" + attr.collectionRepeat + "'.");
  7798. }
  7799. var keyExpr = match[1];
  7800. var listExpr = match[2];
  7801. var listGetter = $parse(listExpr);
  7802. var heightData = {};
  7803. var widthData = {};
  7804. var computedStyleDimensions = {};
  7805. var data = [];
  7806. var repeatManager;
  7807. // attr.collectionBufferSize is deprecated
  7808. var renderBufferExpr = attr.itemRenderBuffer || attr.collectionBufferSize;
  7809. var renderBuffer = angular.isDefined(renderBufferExpr) ?
  7810. parseInt(renderBufferExpr) :
  7811. DEFAULT_RENDER_BUFFER;
  7812. // attr.collectionItemHeight is deprecated
  7813. var heightExpr = attr.itemHeight || attr.collectionItemHeight;
  7814. // attr.collectionItemWidth is deprecated
  7815. var widthExpr = attr.itemWidth || attr.collectionItemWidth;
  7816. var afterItemsContainer = initAfterItemsContainer();
  7817. var changeValidator = makeChangeValidator();
  7818. initDimensions();
  7819. // Dimensions are refreshed on resize or data change.
  7820. scrollCtrl.$element.on('scroll-resize', refreshDimensions);
  7821. angular.element($window).on('resize', onResize);
  7822. var unlistenToExposeAside = $rootScope.$on('$ionicExposeAside', ionic.animationFrameThrottle(function() {
  7823. scrollCtrl.scrollView.resize();
  7824. onResize();
  7825. }));
  7826. $timeout(refreshDimensions, 0, false);
  7827. function onResize() {
  7828. if (changeValidator.resizeRequiresRefresh(scrollView.__clientWidth, scrollView.__clientHeight)) {
  7829. refreshDimensions();
  7830. }
  7831. }
  7832. scope.$watchCollection(listGetter, function(newValue) {
  7833. data = newValue || (newValue = []);
  7834. if (!angular.isArray(newValue)) {
  7835. throw new Error("collection-repeat expected an array for '" + listExpr + "', " +
  7836. "but got a " + typeof value);
  7837. }
  7838. // Wait for this digest to end before refreshing everything.
  7839. scope.$$postDigest(function() {
  7840. getRepeatManager().setData(data);
  7841. if (changeValidator.dataChangeRequiresRefresh(data)) refreshDimensions();
  7842. });
  7843. });
  7844. scope.$on('$destroy', function() {
  7845. angular.element($window).off('resize', onResize);
  7846. unlistenToExposeAside();
  7847. scrollCtrl.$element && scrollCtrl.$element.off('scroll-resize', refreshDimensions);
  7848. computedStyleNode && computedStyleNode.parentNode &&
  7849. computedStyleNode.parentNode.removeChild(computedStyleNode);
  7850. computedStyleScope && computedStyleScope.$destroy();
  7851. computedStyleScope = computedStyleNode = null;
  7852. repeatManager && repeatManager.destroy();
  7853. repeatManager = null;
  7854. });
  7855. function makeChangeValidator() {
  7856. var self;
  7857. return (self = {
  7858. dataLength: 0,
  7859. width: 0,
  7860. height: 0,
  7861. // A resize triggers a refresh only if we have data, the scrollView has size,
  7862. // and the size has changed.
  7863. resizeRequiresRefresh: function(newWidth, newHeight) {
  7864. var requiresRefresh = self.dataLength && newWidth && newHeight &&
  7865. (newWidth !== self.width || newHeight !== self.height);
  7866. self.width = newWidth;
  7867. self.height = newHeight;
  7868. return !!requiresRefresh;
  7869. },
  7870. // A change in data only triggers a refresh if the data has length, or if the data's
  7871. // length is less than before.
  7872. dataChangeRequiresRefresh: function(newData) {
  7873. var requiresRefresh = newData.length > 0 || newData.length < self.dataLength;
  7874. self.dataLength = newData.length;
  7875. return !!requiresRefresh;
  7876. }
  7877. });
  7878. }
  7879. function getRepeatManager() {
  7880. return repeatManager || (repeatManager = new $ionicCollectionManager({
  7881. afterItemsNode: afterItemsContainer[0],
  7882. containerNode: containerNode,
  7883. heightData: heightData,
  7884. widthData: widthData,
  7885. forceRefreshImages: !!(isDefined(attr.forceRefreshImages) && attr.forceRefreshImages !== 'false'),
  7886. keyExpression: keyExpr,
  7887. renderBuffer: renderBuffer,
  7888. scope: scope,
  7889. scrollView: scrollCtrl.scrollView,
  7890. transclude: transclude
  7891. }));
  7892. }
  7893. function initAfterItemsContainer() {
  7894. var container = angular.element(
  7895. scrollView.__content.querySelector('.collection-repeat-after-container')
  7896. );
  7897. // Put everything in the view after the repeater into a container.
  7898. if (!container.length) {
  7899. var elementIsAfterRepeater = false;
  7900. var afterNodes = [].filter.call(scrollView.__content.childNodes, function(node) {
  7901. if (ionic.DomUtil.contains(node, containerNode)) {
  7902. elementIsAfterRepeater = true;
  7903. return false;
  7904. }
  7905. return elementIsAfterRepeater;
  7906. });
  7907. container = angular.element('<span class="collection-repeat-after-container">');
  7908. if (scrollView.options.scrollingX) {
  7909. container.addClass('horizontal');
  7910. }
  7911. container.append(afterNodes);
  7912. scrollView.__content.appendChild(container[0]);
  7913. }
  7914. return container;
  7915. }
  7916. function initDimensions() {
  7917. //Height and width have four 'modes':
  7918. //1) Computed Mode
  7919. // - Nothing is supplied, so we getComputedStyle() on one element in the list and use
  7920. // that width and height value for the width and height of every item. This is re-computed
  7921. // every resize.
  7922. //2) Constant Mode, Static Integer
  7923. // - The user provides a constant number for width or height, in pixels. We parse it,
  7924. // store it on the `value` field, and it never changes
  7925. //3) Constant Mode, Percent
  7926. // - The user provides a percent string for width or height. The getter for percent is
  7927. // stored on the `getValue()` field, and is re-evaluated once every resize. The result
  7928. // is stored on the `value` field.
  7929. //4) Dynamic Mode
  7930. // - The user provides a dynamic expression for the width or height. This is re-evaluated
  7931. // for every item, stored on the `.getValue()` field.
  7932. if (heightExpr) {
  7933. parseDimensionAttr(heightExpr, heightData);
  7934. } else {
  7935. heightData.computed = true;
  7936. }
  7937. if (widthExpr) {
  7938. parseDimensionAttr(widthExpr, widthData);
  7939. } else {
  7940. widthData.computed = true;
  7941. }
  7942. }
  7943. function refreshDimensions() {
  7944. var hasData = data.length > 0;
  7945. if (hasData && (heightData.computed || widthData.computed)) {
  7946. computeStyleDimensions();
  7947. }
  7948. if (hasData && heightData.computed) {
  7949. heightData.value = computedStyleDimensions.height;
  7950. if (!heightData.value) {
  7951. throw new Error('collection-repeat tried to compute the height of repeated elements "' +
  7952. repeatExpr + '", but was unable to. Please provide the "item-height" attribute. ' +
  7953. 'http://ionicframework.com/docs/api/directive/collectionRepeat/');
  7954. }
  7955. } else if (!heightData.dynamic && heightData.getValue) {
  7956. // If it's a constant with a getter (eg percent), we just refresh .value after resize
  7957. heightData.value = heightData.getValue();
  7958. }
  7959. if (hasData && widthData.computed) {
  7960. widthData.value = computedStyleDimensions.width;
  7961. if (!widthData.value) {
  7962. throw new Error('collection-repeat tried to compute the width of repeated elements "' +
  7963. repeatExpr + '", but was unable to. Please provide the "item-width" attribute. ' +
  7964. 'http://ionicframework.com/docs/api/directive/collectionRepeat/');
  7965. }
  7966. } else if (!widthData.dynamic && widthData.getValue) {
  7967. // If it's a constant with a getter (eg percent), we just refresh .value after resize
  7968. widthData.value = widthData.getValue();
  7969. }
  7970. // Dynamic dimensions aren't updated on resize. Since they're already dynamic anyway,
  7971. // .getValue() will be used.
  7972. getRepeatManager().refreshLayout();
  7973. }
  7974. function parseDimensionAttr(attrValue, dimensionData) {
  7975. if (!attrValue) return;
  7976. var parsedValue;
  7977. // Try to just parse the plain attr value
  7978. try {
  7979. parsedValue = $parse(attrValue);
  7980. } catch (e) {
  7981. // If the parse fails and the value has `px` or `%` in it, surround the attr in
  7982. // quotes, to attempt to let the user provide a simple `attr="100%"` or `attr="100px"`
  7983. if (attrValue.trim().match(/\d+(px|%)$/)) {
  7984. attrValue = '"' + attrValue + '"';
  7985. }
  7986. parsedValue = $parse(attrValue);
  7987. }
  7988. var constantAttrValue = attrValue.replace(/(\'|\"|px|%)/g, '').trim();
  7989. var isConstant = constantAttrValue.length && !/([a-zA-Z]|\$|:|\?)/.test(constantAttrValue);
  7990. dimensionData.attrValue = attrValue;
  7991. // If it's a constant, it's either a percent or just a constant pixel number.
  7992. if (isConstant) {
  7993. // For percents, store the percent getter on .getValue()
  7994. if (attrValue.indexOf('%') > -1) {
  7995. var decimalValue = parseFloat(parsedValue()) / 100;
  7996. dimensionData.getValue = dimensionData === heightData ?
  7997. function() { return Math.floor(decimalValue * scrollView.__clientHeight); } :
  7998. function() { return Math.floor(decimalValue * scrollView.__clientWidth); };
  7999. } else {
  8000. // For static constants, just store the static constant.
  8001. dimensionData.value = parseInt(parsedValue());
  8002. }
  8003. } else {
  8004. dimensionData.dynamic = true;
  8005. dimensionData.getValue = dimensionData === heightData ?
  8006. function heightGetter(scope, locals) {
  8007. var result = parsedValue(scope, locals);
  8008. if (result.charAt && result.charAt(result.length - 1) === '%') {
  8009. return Math.floor(parseFloat(result) / 100 * scrollView.__clientHeight);
  8010. }
  8011. return parseInt(result);
  8012. } :
  8013. function widthGetter(scope, locals) {
  8014. var result = parsedValue(scope, locals);
  8015. if (result.charAt && result.charAt(result.length - 1) === '%') {
  8016. return Math.floor(parseFloat(result) / 100 * scrollView.__clientWidth);
  8017. }
  8018. return parseInt(result);
  8019. };
  8020. }
  8021. }
  8022. var computedStyleNode;
  8023. var computedStyleScope;
  8024. function computeStyleDimensions() {
  8025. if (!computedStyleNode) {
  8026. transclude(computedStyleScope = scope.$new(), function(clone) {
  8027. clone[0].removeAttribute('collection-repeat'); // remove absolute position styling
  8028. computedStyleNode = clone[0];
  8029. });
  8030. }
  8031. computedStyleScope[keyExpr] = (listGetter(scope) || [])[0];
  8032. if (!$rootScope.$$phase) computedStyleScope.$digest();
  8033. containerNode.appendChild(computedStyleNode);
  8034. var style = $window.getComputedStyle(computedStyleNode);
  8035. computedStyleDimensions.width = parseInt(style.width);
  8036. computedStyleDimensions.height = parseInt(style.height);
  8037. containerNode.removeChild(computedStyleNode);
  8038. }
  8039. }
  8040. }
  8041. RepeatManagerFactory.$inject = ['$rootScope', '$window', '$$rAF'];
  8042. function RepeatManagerFactory($rootScope, $window, $$rAF) {
  8043. var EMPTY_DIMENSION = { primaryPos: 0, secondaryPos: 0, primarySize: 0, secondarySize: 0, rowPrimarySize: 0 };
  8044. return function RepeatController(options) {
  8045. var afterItemsNode = options.afterItemsNode;
  8046. var containerNode = options.containerNode;
  8047. var forceRefreshImages = options.forceRefreshImages;
  8048. var heightData = options.heightData;
  8049. var widthData = options.widthData;
  8050. var keyExpression = options.keyExpression;
  8051. var renderBuffer = options.renderBuffer;
  8052. var scope = options.scope;
  8053. var scrollView = options.scrollView;
  8054. var transclude = options.transclude;
  8055. var data = [];
  8056. var getterLocals = {};
  8057. var heightFn = heightData.getValue || function() { return heightData.value; };
  8058. var heightGetter = function(index, value) {
  8059. getterLocals[keyExpression] = value;
  8060. getterLocals.$index = index;
  8061. return heightFn(scope, getterLocals);
  8062. };
  8063. var widthFn = widthData.getValue || function() { return widthData.value; };
  8064. var widthGetter = function(index, value) {
  8065. getterLocals[keyExpression] = value;
  8066. getterLocals.$index = index;
  8067. return widthFn(scope, getterLocals);
  8068. };
  8069. var isVertical = !!scrollView.options.scrollingY;
  8070. // We say it's a grid view if we're either dynamic or not 100% width
  8071. var isGridView = isVertical ?
  8072. (widthData.dynamic || widthData.value !== scrollView.__clientWidth) :
  8073. (heightData.dynamic || heightData.value !== scrollView.__clientHeight);
  8074. var isStaticView = !heightData.dynamic && !widthData.dynamic;
  8075. var PRIMARY = 'PRIMARY';
  8076. var SECONDARY = 'SECONDARY';
  8077. var TRANSLATE_TEMPLATE_STR = isVertical ?
  8078. 'translate3d(SECONDARYpx,PRIMARYpx,0)' :
  8079. 'translate3d(PRIMARYpx,SECONDARYpx,0)';
  8080. var WIDTH_HEIGHT_TEMPLATE_STR = isVertical ?
  8081. 'height: PRIMARYpx; width: SECONDARYpx;' :
  8082. 'height: SECONDARYpx; width: PRIMARYpx;';
  8083. var estimatedHeight;
  8084. var estimatedWidth;
  8085. var repeaterBeforeSize = 0;
  8086. var repeaterAfterSize = 0;
  8087. var renderStartIndex = -1;
  8088. var renderEndIndex = -1;
  8089. var renderAfterBoundary = -1;
  8090. var renderBeforeBoundary = -1;
  8091. var itemsPool = [];
  8092. var itemsLeaving = [];
  8093. var itemsEntering = [];
  8094. var itemsShownMap = {};
  8095. var nextItemId = 0;
  8096. var scrollViewSetDimensions = isVertical ?
  8097. function() { scrollView.setDimensions(null, null, null, view.getContentSize(), true); } :
  8098. function() { scrollView.setDimensions(null, null, view.getContentSize(), null, true); };
  8099. // view is a mix of list/grid methods + static/dynamic methods.
  8100. // See bottom for implementations. Available methods:
  8101. //
  8102. // getEstimatedPrimaryPos(i), getEstimatedSecondaryPos(i), getEstimatedIndex(scrollTop),
  8103. // calculateDimensions(toIndex), getDimensions(index),
  8104. // updateRenderRange(scrollTop, scrollValueEnd), onRefreshLayout(), onRefreshData()
  8105. var view = isVertical ? new VerticalViewType() : new HorizontalViewType();
  8106. (isGridView ? GridViewType : ListViewType).call(view);
  8107. (isStaticView ? StaticViewType : DynamicViewType).call(view);
  8108. var contentSizeStr = isVertical ? 'getContentHeight' : 'getContentWidth';
  8109. var originalGetContentSize = scrollView.options[contentSizeStr];
  8110. scrollView.options[contentSizeStr] = angular.bind(view, view.getContentSize);
  8111. scrollView.__$callback = scrollView.__callback;
  8112. scrollView.__callback = function(transformLeft, transformTop, zoom, wasResize) {
  8113. var scrollValue = view.getScrollValue();
  8114. if (renderStartIndex === -1 ||
  8115. scrollValue + view.scrollPrimarySize > renderAfterBoundary ||
  8116. scrollValue < renderBeforeBoundary) {
  8117. render();
  8118. }
  8119. scrollView.__$callback(transformLeft, transformTop, zoom, wasResize);
  8120. };
  8121. var isLayoutReady = false;
  8122. var isDataReady = false;
  8123. this.refreshLayout = function() {
  8124. if (data.length) {
  8125. estimatedHeight = heightGetter(0, data[0]);
  8126. estimatedWidth = widthGetter(0, data[0]);
  8127. } else {
  8128. // If we don't have any data in our array, just guess.
  8129. estimatedHeight = 100;
  8130. estimatedWidth = 100;
  8131. }
  8132. // Get the size of every element AFTER the repeater. We have to get the margin before and
  8133. // after the first/last element to fix a browser bug with getComputedStyle() not counting
  8134. // the first/last child's margins into height.
  8135. var style = getComputedStyle(afterItemsNode) || {};
  8136. var firstStyle = afterItemsNode.firstElementChild && getComputedStyle(afterItemsNode.firstElementChild) || {};
  8137. var lastStyle = afterItemsNode.lastElementChild && getComputedStyle(afterItemsNode.lastElementChild) || {};
  8138. repeaterAfterSize = (parseInt(style[isVertical ? 'height' : 'width']) || 0) +
  8139. (firstStyle && parseInt(firstStyle[isVertical ? 'marginTop' : 'marginLeft']) || 0) +
  8140. (lastStyle && parseInt(lastStyle[isVertical ? 'marginBottom' : 'marginRight']) || 0);
  8141. // Get the offsetTop of the repeater.
  8142. repeaterBeforeSize = 0;
  8143. var current = containerNode;
  8144. do {
  8145. repeaterBeforeSize += current[isVertical ? 'offsetTop' : 'offsetLeft'];
  8146. } while ( ionic.DomUtil.contains(scrollView.__content, current = current.offsetParent) );
  8147. var containerPrevNode = containerNode.previousElementSibling;
  8148. var beforeStyle = containerPrevNode ? $window.getComputedStyle(containerPrevNode) : {};
  8149. var beforeMargin = parseInt(beforeStyle[isVertical ? 'marginBottom' : 'marginRight'] || 0);
  8150. // Because we position the collection container with position: relative, it doesn't take
  8151. // into account where to position itself relative to the previous element's marginBottom.
  8152. // To compensate, we translate the container up by the previous element's margin.
  8153. containerNode.style[ionic.CSS.TRANSFORM] = TRANSLATE_TEMPLATE_STR
  8154. .replace(PRIMARY, -beforeMargin)
  8155. .replace(SECONDARY, 0);
  8156. repeaterBeforeSize -= beforeMargin;
  8157. if (!scrollView.__clientHeight || !scrollView.__clientWidth) {
  8158. scrollView.__clientWidth = scrollView.__container.clientWidth;
  8159. scrollView.__clientHeight = scrollView.__container.clientHeight;
  8160. }
  8161. (view.onRefreshLayout || angular.noop)();
  8162. view.refreshDirection();
  8163. scrollViewSetDimensions();
  8164. // Create the pool of items for reuse, setting the size to (estimatedItemsOnScreen) * 2,
  8165. // plus the size of the renderBuffer.
  8166. if (!isLayoutReady) {
  8167. var poolSize = Math.max(20, renderBuffer * 3);
  8168. for (var i = 0; i < poolSize; i++) {
  8169. itemsPool.push(new RepeatItem());
  8170. }
  8171. }
  8172. isLayoutReady = true;
  8173. if (isLayoutReady && isDataReady) {
  8174. // If the resize or latest data change caused the scrollValue to
  8175. // now be out of bounds, resize the scrollView.
  8176. if (scrollView.__scrollLeft > scrollView.__maxScrollLeft ||
  8177. scrollView.__scrollTop > scrollView.__maxScrollTop) {
  8178. scrollView.resize();
  8179. }
  8180. forceRerender(true);
  8181. }
  8182. };
  8183. this.setData = function(newData) {
  8184. data = newData;
  8185. (view.onRefreshData || angular.noop)();
  8186. isDataReady = true;
  8187. };
  8188. this.destroy = function() {
  8189. render.destroyed = true;
  8190. itemsPool.forEach(function(item) {
  8191. item.scope.$destroy();
  8192. item.scope = item.element = item.node = item.images = null;
  8193. });
  8194. itemsPool.length = itemsEntering.length = itemsLeaving.length = 0;
  8195. itemsShownMap = {};
  8196. //Restore the scrollView's normal behavior and resize it to normal size.
  8197. scrollView.options[contentSizeStr] = originalGetContentSize;
  8198. scrollView.__callback = scrollView.__$callback;
  8199. scrollView.resize();
  8200. (view.onDestroy || angular.noop)();
  8201. };
  8202. function forceRerender() {
  8203. return render(true);
  8204. }
  8205. function render(forceRerender) {
  8206. if (render.destroyed) return;
  8207. var i;
  8208. var ii;
  8209. var item;
  8210. var dim;
  8211. var scope;
  8212. var scrollValue = view.getScrollValue();
  8213. var scrollValueEnd = scrollValue + view.scrollPrimarySize;
  8214. view.updateRenderRange(scrollValue, scrollValueEnd);
  8215. renderStartIndex = Math.max(0, renderStartIndex - renderBuffer);
  8216. renderEndIndex = Math.min(data.length - 1, renderEndIndex + renderBuffer);
  8217. for (i in itemsShownMap) {
  8218. if (i < renderStartIndex || i > renderEndIndex) {
  8219. item = itemsShownMap[i];
  8220. delete itemsShownMap[i];
  8221. itemsLeaving.push(item);
  8222. item.isShown = false;
  8223. }
  8224. }
  8225. // Render indicies that aren't shown yet
  8226. //
  8227. // NOTE(ajoslin): this may sound crazy, but calling any other functions during this render
  8228. // loop will often push the render time over the edge from less than one frame to over
  8229. // one frame, causing visible jank.
  8230. // DON'T call any other functions inside this loop unless it's vital.
  8231. for (i = renderStartIndex; i <= renderEndIndex; i++) {
  8232. // We only go forward with render if the index is in data, the item isn't already shown,
  8233. // or forceRerender is on.
  8234. if (i >= data.length || (itemsShownMap[i] && !forceRerender)) continue;
  8235. item = itemsShownMap[i] || (itemsShownMap[i] = itemsLeaving.length ? itemsLeaving.pop() :
  8236. itemsPool.length ? itemsPool.shift() :
  8237. new RepeatItem());
  8238. itemsEntering.push(item);
  8239. item.isShown = true;
  8240. scope = item.scope;
  8241. scope.$index = i;
  8242. scope[keyExpression] = data[i];
  8243. scope.$first = (i === 0);
  8244. scope.$last = (i === (data.length - 1));
  8245. scope.$middle = !(scope.$first || scope.$last);
  8246. scope.$odd = !(scope.$even = (i & 1) === 0);
  8247. if (scope.$$disconnected) ionic.Utils.reconnectScope(item.scope);
  8248. dim = view.getDimensions(i);
  8249. if (item.secondaryPos !== dim.secondaryPos || item.primaryPos !== dim.primaryPos) {
  8250. item.node.style[ionic.CSS.TRANSFORM] = TRANSLATE_TEMPLATE_STR
  8251. .replace(PRIMARY, (item.primaryPos = dim.primaryPos))
  8252. .replace(SECONDARY, (item.secondaryPos = dim.secondaryPos));
  8253. }
  8254. if (item.secondarySize !== dim.secondarySize || item.primarySize !== dim.primarySize) {
  8255. item.node.style.cssText = item.node.style.cssText
  8256. .replace(WIDTH_HEIGHT_REGEX, WIDTH_HEIGHT_TEMPLATE_STR
  8257. //TODO fix item.primarySize + 1 hack
  8258. .replace(PRIMARY, (item.primarySize = dim.primarySize) + 1)
  8259. .replace(SECONDARY, (item.secondarySize = dim.secondarySize))
  8260. );
  8261. }
  8262. }
  8263. // If we reach the end of the list, render the afterItemsNode - this contains all the
  8264. // elements the developer placed after the collection-repeat
  8265. if (renderEndIndex === data.length - 1) {
  8266. dim = view.getDimensions(data.length - 1) || EMPTY_DIMENSION;
  8267. afterItemsNode.style[ionic.CSS.TRANSFORM] = TRANSLATE_TEMPLATE_STR
  8268. .replace(PRIMARY, dim.primaryPos + dim.primarySize)
  8269. .replace(SECONDARY, 0);
  8270. }
  8271. while (itemsLeaving.length) {
  8272. item = itemsLeaving.pop();
  8273. item.scope.$broadcast('$collectionRepeatLeave');
  8274. ionic.Utils.disconnectScope(item.scope);
  8275. itemsPool.push(item);
  8276. item.node.style[ionic.CSS.TRANSFORM] = 'translate3d(-9999px,-9999px,0)';
  8277. item.primaryPos = item.secondaryPos = null;
  8278. }
  8279. if (forceRefreshImages) {
  8280. for (i = 0, ii = itemsEntering.length; i < ii && (item = itemsEntering[i]); i++) {
  8281. if (!item.images) continue;
  8282. for (var j = 0, jj = item.images.length, img; j < jj && (img = item.images[j]); j++) {
  8283. var src = img.src;
  8284. img.src = ONE_PX_TRANSPARENT_IMG_SRC;
  8285. img.src = src;
  8286. }
  8287. }
  8288. }
  8289. if (forceRerender) {
  8290. var rootScopePhase = $rootScope.$$phase;
  8291. while (itemsEntering.length) {
  8292. item = itemsEntering.pop();
  8293. if (!rootScopePhase) item.scope.$digest();
  8294. }
  8295. } else {
  8296. digestEnteringItems();
  8297. }
  8298. }
  8299. function digestEnteringItems() {
  8300. var item;
  8301. if (digestEnteringItems.running) return;
  8302. digestEnteringItems.running = true;
  8303. $$rAF(function process() {
  8304. var rootScopePhase = $rootScope.$$phase;
  8305. while (itemsEntering.length) {
  8306. item = itemsEntering.pop();
  8307. if (item.isShown) {
  8308. if (!rootScopePhase) item.scope.$digest();
  8309. }
  8310. }
  8311. digestEnteringItems.running = false;
  8312. });
  8313. }
  8314. function RepeatItem() {
  8315. var self = this;
  8316. this.scope = scope.$new();
  8317. this.id = 'item' + (nextItemId++);
  8318. transclude(this.scope, function(clone) {
  8319. self.element = clone;
  8320. self.element.data('$$collectionRepeatItem', self);
  8321. // TODO destroy
  8322. self.node = clone[0];
  8323. // Batch style setting to lower repaints
  8324. self.node.style[ionic.CSS.TRANSFORM] = 'translate3d(-9999px,-9999px,0)';
  8325. self.node.style.cssText += ' height: 0px; width: 0px;';
  8326. ionic.Utils.disconnectScope(self.scope);
  8327. containerNode.appendChild(self.node);
  8328. self.images = clone[0].getElementsByTagName('img');
  8329. });
  8330. }
  8331. function VerticalViewType() {
  8332. this.getItemPrimarySize = heightGetter;
  8333. this.getItemSecondarySize = widthGetter;
  8334. this.getScrollValue = function() {
  8335. return Math.max(0, Math.min(scrollView.__scrollTop - repeaterBeforeSize,
  8336. scrollView.__maxScrollTop - repeaterBeforeSize - repeaterAfterSize));
  8337. };
  8338. this.refreshDirection = function() {
  8339. this.scrollPrimarySize = scrollView.__clientHeight;
  8340. this.scrollSecondarySize = scrollView.__clientWidth;
  8341. this.estimatedPrimarySize = estimatedHeight;
  8342. this.estimatedSecondarySize = estimatedWidth;
  8343. this.estimatedItemsAcross = isGridView &&
  8344. Math.floor(scrollView.__clientWidth / estimatedWidth) ||
  8345. 1;
  8346. };
  8347. }
  8348. function HorizontalViewType() {
  8349. this.getItemPrimarySize = widthGetter;
  8350. this.getItemSecondarySize = heightGetter;
  8351. this.getScrollValue = function() {
  8352. return Math.max(0, Math.min(scrollView.__scrollLeft - repeaterBeforeSize,
  8353. scrollView.__maxScrollLeft - repeaterBeforeSize - repeaterAfterSize));
  8354. };
  8355. this.refreshDirection = function() {
  8356. this.scrollPrimarySize = scrollView.__clientWidth;
  8357. this.scrollSecondarySize = scrollView.__clientHeight;
  8358. this.estimatedPrimarySize = estimatedWidth;
  8359. this.estimatedSecondarySize = estimatedHeight;
  8360. this.estimatedItemsAcross = isGridView &&
  8361. Math.floor(scrollView.__clientHeight / estimatedHeight) ||
  8362. 1;
  8363. };
  8364. }
  8365. function GridViewType() {
  8366. this.getEstimatedSecondaryPos = function(index) {
  8367. return (index % this.estimatedItemsAcross) * this.estimatedSecondarySize;
  8368. };
  8369. this.getEstimatedPrimaryPos = function(index) {
  8370. return Math.floor(index / this.estimatedItemsAcross) * this.estimatedPrimarySize;
  8371. };
  8372. this.getEstimatedIndex = function(scrollValue) {
  8373. return Math.floor(scrollValue / this.estimatedPrimarySize) *
  8374. this.estimatedItemsAcross;
  8375. };
  8376. }
  8377. function ListViewType() {
  8378. this.getEstimatedSecondaryPos = function() {
  8379. return 0;
  8380. };
  8381. this.getEstimatedPrimaryPos = function(index) {
  8382. return index * this.estimatedPrimarySize;
  8383. };
  8384. this.getEstimatedIndex = function(scrollValue) {
  8385. return Math.floor((scrollValue) / this.estimatedPrimarySize);
  8386. };
  8387. }
  8388. function StaticViewType() {
  8389. this.getContentSize = function() {
  8390. return this.getEstimatedPrimaryPos(data.length - 1) + this.estimatedPrimarySize +
  8391. repeaterBeforeSize + repeaterAfterSize;
  8392. };
  8393. // static view always returns the same object for getDimensions, to avoid memory allocation
  8394. // while scrolling. This could be dangerous if this was a public function, but it's not.
  8395. // Only we use it.
  8396. var dim = {};
  8397. this.getDimensions = function(index) {
  8398. dim.primaryPos = this.getEstimatedPrimaryPos(index);
  8399. dim.secondaryPos = this.getEstimatedSecondaryPos(index);
  8400. dim.primarySize = this.estimatedPrimarySize;
  8401. dim.secondarySize = this.estimatedSecondarySize;
  8402. return dim;
  8403. };
  8404. this.updateRenderRange = function(scrollValue, scrollValueEnd) {
  8405. renderStartIndex = Math.max(0, this.getEstimatedIndex(scrollValue));
  8406. // Make sure the renderEndIndex takes into account all the items on the row
  8407. renderEndIndex = Math.min(data.length - 1,
  8408. this.getEstimatedIndex(scrollValueEnd) + this.estimatedItemsAcross - 1);
  8409. renderBeforeBoundary = Math.max(0,
  8410. this.getEstimatedPrimaryPos(renderStartIndex));
  8411. renderAfterBoundary = this.getEstimatedPrimaryPos(renderEndIndex) +
  8412. this.estimatedPrimarySize;
  8413. };
  8414. }
  8415. function DynamicViewType() {
  8416. var self = this;
  8417. var debouncedScrollViewSetDimensions = ionic.debounce(scrollViewSetDimensions, 25, true);
  8418. var calculateDimensions = isGridView ? calculateDimensionsGrid : calculateDimensionsList;
  8419. var dimensionsIndex;
  8420. var dimensions = [];
  8421. // Get the dimensions at index. {width, height, left, top}.
  8422. // We start with no dimensions calculated, then any time dimensions are asked for at an
  8423. // index we calculate dimensions up to there.
  8424. function calculateDimensionsList(toIndex) {
  8425. var i, prevDimension, dim;
  8426. for (i = Math.max(0, dimensionsIndex); i <= toIndex && (dim = dimensions[i]); i++) {
  8427. prevDimension = dimensions[i - 1] || EMPTY_DIMENSION;
  8428. dim.primarySize = self.getItemPrimarySize(i, data[i]);
  8429. dim.secondarySize = self.scrollSecondarySize;
  8430. dim.primaryPos = prevDimension.primaryPos + prevDimension.primarySize;
  8431. dim.secondaryPos = 0;
  8432. }
  8433. }
  8434. function calculateDimensionsGrid(toIndex) {
  8435. var i, prevDimension, dim;
  8436. for (i = Math.max(dimensionsIndex, 0); i <= toIndex && (dim = dimensions[i]); i++) {
  8437. prevDimension = dimensions[i - 1] || EMPTY_DIMENSION;
  8438. dim.secondarySize = Math.min(
  8439. self.getItemSecondarySize(i, data[i]),
  8440. self.scrollSecondarySize
  8441. );
  8442. dim.secondaryPos = prevDimension.secondaryPos + prevDimension.secondarySize;
  8443. if (i === 0 || dim.secondaryPos + dim.secondarySize > self.scrollSecondarySize) {
  8444. dim.secondaryPos = 0;
  8445. dim.primarySize = self.getItemPrimarySize(i, data[i]);
  8446. dim.primaryPos = prevDimension.primaryPos + prevDimension.rowPrimarySize;
  8447. dim.rowStartIndex = i;
  8448. dim.rowPrimarySize = dim.primarySize;
  8449. } else {
  8450. dim.primarySize = self.getItemPrimarySize(i, data[i]);
  8451. dim.primaryPos = prevDimension.primaryPos;
  8452. dim.rowStartIndex = prevDimension.rowStartIndex;
  8453. dimensions[dim.rowStartIndex].rowPrimarySize = dim.rowPrimarySize = Math.max(
  8454. dimensions[dim.rowStartIndex].rowPrimarySize,
  8455. dim.primarySize
  8456. );
  8457. dim.rowPrimarySize = Math.max(dim.primarySize, dim.rowPrimarySize);
  8458. }
  8459. }
  8460. }
  8461. this.getContentSize = function() {
  8462. var dim = dimensions[dimensionsIndex] || EMPTY_DIMENSION;
  8463. return ((dim.primaryPos + dim.primarySize) || 0) +
  8464. this.getEstimatedPrimaryPos(data.length - dimensionsIndex - 1) +
  8465. repeaterBeforeSize + repeaterAfterSize;
  8466. };
  8467. this.onDestroy = function() {
  8468. dimensions.length = 0;
  8469. };
  8470. this.onRefreshData = function() {
  8471. var i;
  8472. var ii;
  8473. // Make sure dimensions has as many items as data.length.
  8474. // This is to be sure we don't have to allocate objects while scrolling.
  8475. for (i = dimensions.length, ii = data.length; i < ii; i++) {
  8476. dimensions.push({});
  8477. }
  8478. dimensionsIndex = -1;
  8479. };
  8480. this.onRefreshLayout = function() {
  8481. dimensionsIndex = -1;
  8482. };
  8483. this.getDimensions = function(index) {
  8484. index = Math.min(index, data.length - 1);
  8485. if (dimensionsIndex < index) {
  8486. // Once we start asking for dimensions near the end of the list, go ahead and calculate
  8487. // everything. This is to make sure when the user gets to the end of the list, the
  8488. // scroll height of the list is 100% accurate (not estimated anymore).
  8489. if (index > data.length * 0.9) {
  8490. calculateDimensions(data.length - 1);
  8491. dimensionsIndex = data.length - 1;
  8492. scrollViewSetDimensions();
  8493. } else {
  8494. calculateDimensions(index);
  8495. dimensionsIndex = index;
  8496. debouncedScrollViewSetDimensions();
  8497. }
  8498. }
  8499. return dimensions[index];
  8500. };
  8501. var oldRenderStartIndex = -1;
  8502. var oldScrollValue = -1;
  8503. this.updateRenderRange = function(scrollValue, scrollValueEnd) {
  8504. var i;
  8505. var len;
  8506. var dim;
  8507. // Calculate more dimensions than we estimate we'll need, to be sure.
  8508. this.getDimensions( this.getEstimatedIndex(scrollValueEnd) * 2 );
  8509. // -- Calculate renderStartIndex
  8510. // base case: start at 0
  8511. if (oldRenderStartIndex === -1 || scrollValue === 0) {
  8512. i = 0;
  8513. // scrolling down
  8514. } else if (scrollValue >= oldScrollValue) {
  8515. for (i = oldRenderStartIndex, len = data.length; i < len; i++) {
  8516. if ((dim = this.getDimensions(i)) && dim.primaryPos + dim.rowPrimarySize >= scrollValue) {
  8517. break;
  8518. }
  8519. }
  8520. // scrolling up
  8521. } else {
  8522. for (i = oldRenderStartIndex; i >= 0; i--) {
  8523. if ((dim = this.getDimensions(i)) && dim.primaryPos <= scrollValue) {
  8524. // when grid view, make sure the render starts at the beginning of a row.
  8525. i = isGridView ? dim.rowStartIndex : i;
  8526. break;
  8527. }
  8528. }
  8529. }
  8530. renderStartIndex = Math.min(Math.max(0, i), data.length - 1);
  8531. renderBeforeBoundary = renderStartIndex !== -1 ? this.getDimensions(renderStartIndex).primaryPos : -1;
  8532. // -- Calculate renderEndIndex
  8533. var lastRowDim;
  8534. for (i = renderStartIndex + 1, len = data.length; i < len; i++) {
  8535. if ((dim = this.getDimensions(i)) && dim.primaryPos + dim.rowPrimarySize > scrollValueEnd) {
  8536. // Go all the way to the end of the row if we're in a grid
  8537. if (isGridView) {
  8538. lastRowDim = dim;
  8539. while (i < len - 1 &&
  8540. (dim = this.getDimensions(i + 1)).primaryPos === lastRowDim.primaryPos) {
  8541. i++;
  8542. }
  8543. }
  8544. break;
  8545. }
  8546. }
  8547. renderEndIndex = Math.min(i, data.length - 1);
  8548. renderAfterBoundary = renderEndIndex !== -1 ?
  8549. ((dim = this.getDimensions(renderEndIndex)).primaryPos + (dim.rowPrimarySize || dim.primarySize)) :
  8550. -1;
  8551. oldScrollValue = scrollValue;
  8552. oldRenderStartIndex = renderStartIndex;
  8553. };
  8554. }
  8555. };
  8556. }
  8557. /**
  8558. * @ngdoc directive
  8559. * @name ionContent
  8560. * @module ionic
  8561. * @delegate ionic.service:$ionicScrollDelegate
  8562. * @restrict E
  8563. *
  8564. * @description
  8565. * The ionContent directive provides an easy to use content area that can be configured
  8566. * to use Ionic's custom Scroll View, or the built in overflow scrolling of the browser.
  8567. *
  8568. * While we recommend using the custom Scroll features in Ionic in most cases, sometimes
  8569. * (for performance reasons) only the browser's native overflow scrolling will suffice,
  8570. * and so we've made it easy to toggle between the Ionic scroll implementation and
  8571. * overflow scrolling.
  8572. *
  8573. * You can implement pull-to-refresh with the {@link ionic.directive:ionRefresher}
  8574. * directive, and infinite scrolling with the {@link ionic.directive:ionInfiniteScroll}
  8575. * directive.
  8576. *
  8577. * If there is any dynamic content inside the ion-content, be sure to call `.resize()` with {@link ionic.service:$ionicScrollDelegate}
  8578. * after the content has been added.
  8579. *
  8580. * Be aware that this directive gets its own child scope. If you do not understand why this
  8581. * is important, you can read [https://docs.angularjs.org/guide/scope](https://docs.angularjs.org/guide/scope).
  8582. *
  8583. * @param {string=} delegate-handle The handle used to identify this scrollView
  8584. * with {@link ionic.service:$ionicScrollDelegate}.
  8585. * @param {string=} direction Which way to scroll. 'x' or 'y' or 'xy'. Default 'y'.
  8586. * @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.
  8587. * @param {boolean=} padding Whether to add padding to the content.
  8588. * Defaults to true on iOS, false on Android.
  8589. * @param {boolean=} scroll Whether to allow scrolling of content. Defaults to true.
  8590. * @param {boolean=} overflow-scroll Whether to use overflow-scrolling instead of
  8591. * Ionic scroll. See {@link ionic.provider:$ionicConfigProvider} to set this as the global default.
  8592. * @param {boolean=} scrollbar-x Whether to show the horizontal scrollbar. Default true.
  8593. * @param {boolean=} scrollbar-y Whether to show the vertical scrollbar. Default true.
  8594. * @param {string=} start-x Initial horizontal scroll position. Default 0.
  8595. * @param {string=} start-y Initial vertical scroll position. Default 0.
  8596. * @param {expression=} on-scroll Expression to evaluate when the content is scrolled.
  8597. * @param {expression=} on-scroll-complete Expression to evaluate when a scroll action completes. Has access to 'scrollLeft' and 'scrollTop' locals.
  8598. * @param {boolean=} has-bouncing Whether to allow scrolling to bounce past the edges
  8599. * of the content. Defaults to true on iOS, false on Android.
  8600. * @param {number=} scroll-event-interval Number of milliseconds between each firing of the 'on-scroll' expression. Default 10.
  8601. */
  8602. IonicModule
  8603. .directive('ionContent', [
  8604. '$timeout',
  8605. '$controller',
  8606. '$ionicBind',
  8607. '$ionicConfig',
  8608. function($timeout, $controller, $ionicBind, $ionicConfig) {
  8609. return {
  8610. restrict: 'E',
  8611. require: '^?ionNavView',
  8612. scope: true,
  8613. priority: 800,
  8614. compile: function(element, attr) {
  8615. var innerElement;
  8616. var scrollCtrl;
  8617. element.addClass('scroll-content ionic-scroll');
  8618. if (attr.scroll != 'false') {
  8619. //We cannot use normal transclude here because it breaks element.data()
  8620. //inheritance on compile
  8621. innerElement = jqLite('<div class="scroll"></div>');
  8622. innerElement.append(element.contents());
  8623. element.append(innerElement);
  8624. } else {
  8625. element.addClass('scroll-content-false');
  8626. }
  8627. var nativeScrolling = attr.overflowScroll !== "false" && (attr.overflowScroll === "true" || !$ionicConfig.scrolling.jsScrolling());
  8628. // collection-repeat requires JS scrolling
  8629. if (nativeScrolling) {
  8630. nativeScrolling = !element[0].querySelector('[collection-repeat]');
  8631. }
  8632. return { pre: prelink };
  8633. function prelink($scope, $element, $attr) {
  8634. var parentScope = $scope.$parent;
  8635. $scope.$watch(function() {
  8636. return (parentScope.$hasHeader ? ' has-header' : '') +
  8637. (parentScope.$hasSubheader ? ' has-subheader' : '') +
  8638. (parentScope.$hasFooter ? ' has-footer' : '') +
  8639. (parentScope.$hasSubfooter ? ' has-subfooter' : '') +
  8640. (parentScope.$hasTabs ? ' has-tabs' : '') +
  8641. (parentScope.$hasTabsTop ? ' has-tabs-top' : '');
  8642. }, function(className, oldClassName) {
  8643. $element.removeClass(oldClassName);
  8644. $element.addClass(className);
  8645. });
  8646. //Only this ionContent should use these variables from parent scopes
  8647. $scope.$hasHeader = $scope.$hasSubheader =
  8648. $scope.$hasFooter = $scope.$hasSubfooter =
  8649. $scope.$hasTabs = $scope.$hasTabsTop =
  8650. false;
  8651. $ionicBind($scope, $attr, {
  8652. $onScroll: '&onScroll',
  8653. $onScrollComplete: '&onScrollComplete',
  8654. hasBouncing: '@',
  8655. padding: '@',
  8656. direction: '@',
  8657. scrollbarX: '@',
  8658. scrollbarY: '@',
  8659. startX: '@',
  8660. startY: '@',
  8661. scrollEventInterval: '@'
  8662. });
  8663. $scope.direction = $scope.direction || 'y';
  8664. if (isDefined($attr.padding)) {
  8665. $scope.$watch($attr.padding, function(newVal) {
  8666. (innerElement || $element).toggleClass('padding', !!newVal);
  8667. });
  8668. }
  8669. if ($attr.scroll === "false") {
  8670. //do nothing
  8671. } else {
  8672. var scrollViewOptions = {};
  8673. // determined in compile phase above
  8674. if (nativeScrolling) {
  8675. // use native scrolling
  8676. $element.addClass('overflow-scroll');
  8677. scrollViewOptions = {
  8678. el: $element[0],
  8679. delegateHandle: attr.delegateHandle,
  8680. startX: $scope.$eval($scope.startX) || 0,
  8681. startY: $scope.$eval($scope.startY) || 0,
  8682. nativeScrolling: true
  8683. };
  8684. } else {
  8685. // Use JS scrolling
  8686. scrollViewOptions = {
  8687. el: $element[0],
  8688. delegateHandle: attr.delegateHandle,
  8689. locking: (attr.locking || 'true') === 'true',
  8690. bouncing: $scope.$eval($scope.hasBouncing),
  8691. startX: $scope.$eval($scope.startX) || 0,
  8692. startY: $scope.$eval($scope.startY) || 0,
  8693. scrollbarX: $scope.$eval($scope.scrollbarX) !== false,
  8694. scrollbarY: $scope.$eval($scope.scrollbarY) !== false,
  8695. scrollingX: $scope.direction.indexOf('x') >= 0,
  8696. scrollingY: $scope.direction.indexOf('y') >= 0,
  8697. scrollEventInterval: parseInt($scope.scrollEventInterval, 10) || 10,
  8698. scrollingComplete: onScrollComplete
  8699. };
  8700. }
  8701. // init scroll controller with appropriate options
  8702. scrollCtrl = $controller('$ionicScroll', {
  8703. $scope: $scope,
  8704. scrollViewOptions: scrollViewOptions
  8705. });
  8706. $scope.scrollCtrl = scrollCtrl;
  8707. $scope.$on('$destroy', function() {
  8708. if (scrollViewOptions) {
  8709. scrollViewOptions.scrollingComplete = noop;
  8710. delete scrollViewOptions.el;
  8711. }
  8712. innerElement = null;
  8713. $element = null;
  8714. attr.$$element = null;
  8715. });
  8716. }
  8717. function onScrollComplete() {
  8718. $scope.$onScrollComplete({
  8719. scrollTop: scrollCtrl.scrollView.__scrollTop,
  8720. scrollLeft: scrollCtrl.scrollView.__scrollLeft
  8721. });
  8722. }
  8723. }
  8724. }
  8725. };
  8726. }]);
  8727. /**
  8728. * @ngdoc directive
  8729. * @name exposeAsideWhen
  8730. * @module ionic
  8731. * @restrict A
  8732. * @parent ionic.directive:ionSideMenus
  8733. *
  8734. * @description
  8735. * It is common for a tablet application to hide a menu when in portrait mode, but to show the
  8736. * same menu on the left side when the tablet is in landscape mode. The `exposeAsideWhen` attribute
  8737. * directive can be used to accomplish a similar interface.
  8738. *
  8739. * By default, side menus are hidden underneath its side menu content, and can be opened by either
  8740. * swiping the content left or right, or toggling a button to show the side menu. However, by adding the
  8741. * `exposeAsideWhen` attribute directive to an {@link ionic.directive:ionSideMenu} element directive,
  8742. * a side menu can be given instructions on "when" the menu should be exposed (always viewable). For
  8743. * example, the `expose-aside-when="large"` attribute will keep the side menu hidden when the viewport's
  8744. * width is less than `768px`, but when the viewport's width is `768px` or greater, the menu will then
  8745. * always be shown and can no longer be opened or closed like it could when it was hidden for smaller
  8746. * viewports.
  8747. *
  8748. * Using `large` as the attribute's value is a shortcut value to `(min-width:768px)` since it is
  8749. * the most common use-case. However, for added flexibility, any valid media query could be added
  8750. * as the value, such as `(min-width:600px)` or even multiple queries such as
  8751. * `(min-width:750px) and (max-width:1200px)`.
  8752. * @usage
  8753. * ```html
  8754. * <ion-side-menus>
  8755. * <!-- Center content -->
  8756. * <ion-side-menu-content>
  8757. * </ion-side-menu-content>
  8758. *
  8759. * <!-- Left menu -->
  8760. * <ion-side-menu expose-aside-when="large">
  8761. * </ion-side-menu>
  8762. * </ion-side-menus>
  8763. * ```
  8764. * For a complete side menu example, see the
  8765. * {@link ionic.directive:ionSideMenus} documentation.
  8766. */
  8767. IonicModule.directive('exposeAsideWhen', ['$window', function($window) {
  8768. return {
  8769. restrict: 'A',
  8770. require: '^ionSideMenus',
  8771. link: function($scope, $element, $attr, sideMenuCtrl) {
  8772. var prevInnerWidth = $window.innerWidth;
  8773. var prevInnerHeight = $window.innerHeight;
  8774. ionic.on('resize', function() {
  8775. if (prevInnerWidth === $window.innerWidth && prevInnerHeight === $window.innerHeight) {
  8776. return;
  8777. }
  8778. prevInnerWidth = $window.innerWidth;
  8779. prevInnerHeight = $window.innerHeight;
  8780. onResize();
  8781. }, $window);
  8782. function checkAsideExpose() {
  8783. var mq = $attr.exposeAsideWhen == 'large' ? '(min-width:768px)' : $attr.exposeAsideWhen;
  8784. sideMenuCtrl.exposeAside($window.matchMedia(mq).matches);
  8785. sideMenuCtrl.activeAsideResizing(false);
  8786. }
  8787. function onResize() {
  8788. sideMenuCtrl.activeAsideResizing(true);
  8789. debouncedCheck();
  8790. }
  8791. var debouncedCheck = ionic.debounce(function() {
  8792. $scope.$apply(checkAsideExpose);
  8793. }, 300, false);
  8794. $scope.$evalAsync(checkAsideExpose);
  8795. }
  8796. };
  8797. }]);
  8798. var GESTURE_DIRECTIVES = 'onHold onTap onDoubleTap onTouch onRelease onDragStart onDrag onDragEnd onDragUp onDragRight onDragDown onDragLeft onSwipe onSwipeUp onSwipeRight onSwipeDown onSwipeLeft'.split(' ');
  8799. GESTURE_DIRECTIVES.forEach(function(name) {
  8800. IonicModule.directive(name, gestureDirective(name));
  8801. });
  8802. /**
  8803. * @ngdoc directive
  8804. * @name onHold
  8805. * @module ionic
  8806. * @restrict A
  8807. *
  8808. * @description
  8809. * Touch stays at the same location for 500ms. Similar to long touch events available for AngularJS and jQuery.
  8810. *
  8811. * @usage
  8812. * ```html
  8813. * <button on-hold="onHold()" class="button">Test</button>
  8814. * ```
  8815. */
  8816. /**
  8817. * @ngdoc directive
  8818. * @name onTap
  8819. * @module ionic
  8820. * @restrict A
  8821. *
  8822. * @description
  8823. * Quick touch at a location. If the duration of the touch goes
  8824. * longer than 250ms it is no longer a tap gesture.
  8825. *
  8826. * @usage
  8827. * ```html
  8828. * <button on-tap="onTap()" class="button">Test</button>
  8829. * ```
  8830. */
  8831. /**
  8832. * @ngdoc directive
  8833. * @name onDoubleTap
  8834. * @module ionic
  8835. * @restrict A
  8836. *
  8837. * @description
  8838. * Double tap touch at a location.
  8839. *
  8840. * @usage
  8841. * ```html
  8842. * <button on-double-tap="onDoubleTap()" class="button">Test</button>
  8843. * ```
  8844. */
  8845. /**
  8846. * @ngdoc directive
  8847. * @name onTouch
  8848. * @module ionic
  8849. * @restrict A
  8850. *
  8851. * @description
  8852. * Called immediately when the user first begins a touch. This
  8853. * gesture does not wait for a touchend/mouseup.
  8854. *
  8855. * @usage
  8856. * ```html
  8857. * <button on-touch="onTouch()" class="button">Test</button>
  8858. * ```
  8859. */
  8860. /**
  8861. * @ngdoc directive
  8862. * @name onRelease
  8863. * @module ionic
  8864. * @restrict A
  8865. *
  8866. * @description
  8867. * Called when the user ends a touch.
  8868. *
  8869. * @usage
  8870. * ```html
  8871. * <button on-release="onRelease()" class="button">Test</button>
  8872. * ```
  8873. */
  8874. /**
  8875. * @ngdoc directive
  8876. * @name onDragStart
  8877. * @module ionic
  8878. * @restrict A
  8879. *
  8880. * @description
  8881. * Called when a drag gesture has started.
  8882. *
  8883. * @usage
  8884. * ```html
  8885. * <button on-drag-start="onDragStart()" class="button">Test</button>
  8886. * ```
  8887. */
  8888. /**
  8889. * @ngdoc directive
  8890. * @name onDrag
  8891. * @module ionic
  8892. * @restrict A
  8893. *
  8894. * @description
  8895. * Move with one touch around on the page. Blocking the scrolling when
  8896. * moving left and right is a good practice. When all the drag events are
  8897. * blocking you disable scrolling on that area.
  8898. *
  8899. * @usage
  8900. * ```html
  8901. * <button on-drag="onDrag()" class="button">Test</button>
  8902. * ```
  8903. */
  8904. /**
  8905. * @ngdoc directive
  8906. * @name onDragEnd
  8907. * @module ionic
  8908. * @restrict A
  8909. *
  8910. * @description
  8911. * Called when a drag gesture has ended.
  8912. *
  8913. * @usage
  8914. * ```html
  8915. * <button on-drag-end="onDragEnd()" class="button">Test</button>
  8916. * ```
  8917. */
  8918. /**
  8919. * @ngdoc directive
  8920. * @name onDragUp
  8921. * @module ionic
  8922. * @restrict A
  8923. *
  8924. * @description
  8925. * Called when the element is dragged up.
  8926. *
  8927. * @usage
  8928. * ```html
  8929. * <button on-drag-up="onDragUp()" class="button">Test</button>
  8930. * ```
  8931. */
  8932. /**
  8933. * @ngdoc directive
  8934. * @name onDragRight
  8935. * @module ionic
  8936. * @restrict A
  8937. *
  8938. * @description
  8939. * Called when the element is dragged to the right.
  8940. *
  8941. * @usage
  8942. * ```html
  8943. * <button on-drag-right="onDragRight()" class="button">Test</button>
  8944. * ```
  8945. */
  8946. /**
  8947. * @ngdoc directive
  8948. * @name onDragDown
  8949. * @module ionic
  8950. * @restrict A
  8951. *
  8952. * @description
  8953. * Called when the element is dragged down.
  8954. *
  8955. * @usage
  8956. * ```html
  8957. * <button on-drag-down="onDragDown()" class="button">Test</button>
  8958. * ```
  8959. */
  8960. /**
  8961. * @ngdoc directive
  8962. * @name onDragLeft
  8963. * @module ionic
  8964. * @restrict A
  8965. *
  8966. * @description
  8967. * Called when the element is dragged to the left.
  8968. *
  8969. * @usage
  8970. * ```html
  8971. * <button on-drag-left="onDragLeft()" class="button">Test</button>
  8972. * ```
  8973. */
  8974. /**
  8975. * @ngdoc directive
  8976. * @name onSwipe
  8977. * @module ionic
  8978. * @restrict A
  8979. *
  8980. * @description
  8981. * Called when a moving touch has a high velocity in any direction.
  8982. *
  8983. * @usage
  8984. * ```html
  8985. * <button on-swipe="onSwipe()" class="button">Test</button>
  8986. * ```
  8987. */
  8988. /**
  8989. * @ngdoc directive
  8990. * @name onSwipeUp
  8991. * @module ionic
  8992. * @restrict A
  8993. *
  8994. * @description
  8995. * Called when a moving touch has a high velocity moving up.
  8996. *
  8997. * @usage
  8998. * ```html
  8999. * <button on-swipe-up="onSwipeUp()" class="button">Test</button>
  9000. * ```
  9001. */
  9002. /**
  9003. * @ngdoc directive
  9004. * @name onSwipeRight
  9005. * @module ionic
  9006. * @restrict A
  9007. *
  9008. * @description
  9009. * Called when a moving touch has a high velocity moving to the right.
  9010. *
  9011. * @usage
  9012. * ```html
  9013. * <button on-swipe-right="onSwipeRight()" class="button">Test</button>
  9014. * ```
  9015. */
  9016. /**
  9017. * @ngdoc directive
  9018. * @name onSwipeDown
  9019. * @module ionic
  9020. * @restrict A
  9021. *
  9022. * @description
  9023. * Called when a moving touch has a high velocity moving down.
  9024. *
  9025. * @usage
  9026. * ```html
  9027. * <button on-swipe-down="onSwipeDown()" class="button">Test</button>
  9028. * ```
  9029. */
  9030. /**
  9031. * @ngdoc directive
  9032. * @name onSwipeLeft
  9033. * @module ionic
  9034. * @restrict A
  9035. *
  9036. * @description
  9037. * Called when a moving touch has a high velocity moving to the left.
  9038. *
  9039. * @usage
  9040. * ```html
  9041. * <button on-swipe-left="onSwipeLeft()" class="button">Test</button>
  9042. * ```
  9043. */
  9044. function gestureDirective(directiveName) {
  9045. return ['$ionicGesture', '$parse', function($ionicGesture, $parse) {
  9046. var eventType = directiveName.substr(2).toLowerCase();
  9047. return function(scope, element, attr) {
  9048. var fn = $parse( attr[directiveName] );
  9049. var listener = function(ev) {
  9050. scope.$apply(function() {
  9051. fn(scope, {
  9052. $event: ev
  9053. });
  9054. });
  9055. };
  9056. var gesture = $ionicGesture.on(eventType, listener, element);
  9057. scope.$on('$destroy', function() {
  9058. $ionicGesture.off(gesture, eventType, listener);
  9059. });
  9060. };
  9061. }];
  9062. }
  9063. IonicModule
  9064. //.directive('ionHeaderBar', tapScrollToTopDirective())
  9065. /**
  9066. * @ngdoc directive
  9067. * @name ionHeaderBar
  9068. * @module ionic
  9069. * @restrict E
  9070. *
  9071. * @description
  9072. * Adds a fixed header bar above some content.
  9073. *
  9074. * Can also be a subheader (lower down) if the 'bar-subheader' class is applied.
  9075. * See [the header CSS docs](/docs/components/#subheader).
  9076. *
  9077. * @param {string=} align-title How to align the title. By default the title
  9078. * will be aligned the same as how the platform aligns its titles (iOS centers
  9079. * titles, Android aligns them left).
  9080. * Available: 'left', 'right', or 'center'. Defaults to the same as the platform.
  9081. * @param {boolean=} no-tap-scroll By default, the header bar will scroll the
  9082. * content to the top when tapped. Set no-tap-scroll to true to disable this
  9083. * behavior.
  9084. * Available: true or false. Defaults to false.
  9085. *
  9086. * @usage
  9087. * ```html
  9088. * <ion-header-bar align-title="left" class="bar-positive">
  9089. * <div class="buttons">
  9090. * <button class="button" ng-click="doSomething()">Left Button</button>
  9091. * </div>
  9092. * <h1 class="title">Title!</h1>
  9093. * <div class="buttons">
  9094. * <button class="button">Right Button</button>
  9095. * </div>
  9096. * </ion-header-bar>
  9097. * <ion-content class="has-header">
  9098. * Some content!
  9099. * </ion-content>
  9100. * ```
  9101. */
  9102. .directive('ionHeaderBar', headerFooterBarDirective(true))
  9103. /**
  9104. * @ngdoc directive
  9105. * @name ionFooterBar
  9106. * @module ionic
  9107. * @restrict E
  9108. *
  9109. * @description
  9110. * Adds a fixed footer bar below some content.
  9111. *
  9112. * Can also be a subfooter (higher up) if the 'bar-subfooter' class is applied.
  9113. * See [the footer CSS docs](/docs/components/#footer).
  9114. *
  9115. * Note: If you use ionFooterBar in combination with ng-if, the surrounding content
  9116. * will not align correctly. This will be fixed soon.
  9117. *
  9118. * @param {string=} align-title Where to align the title.
  9119. * Available: 'left', 'right', or 'center'. Defaults to 'center'.
  9120. *
  9121. * @usage
  9122. * ```html
  9123. * <ion-content class="has-footer">
  9124. * Some content!
  9125. * </ion-content>
  9126. * <ion-footer-bar align-title="left" class="bar-assertive">
  9127. * <div class="buttons">
  9128. * <button class="button">Left Button</button>
  9129. * </div>
  9130. * <h1 class="title">Title!</h1>
  9131. * <div class="buttons" ng-click="doSomething()">
  9132. * <button class="button">Right Button</button>
  9133. * </div>
  9134. * </ion-footer-bar>
  9135. * ```
  9136. */
  9137. .directive('ionFooterBar', headerFooterBarDirective(false));
  9138. function tapScrollToTopDirective() { //eslint-disable-line no-unused-vars
  9139. return ['$ionicScrollDelegate', function($ionicScrollDelegate) {
  9140. return {
  9141. restrict: 'E',
  9142. link: function($scope, $element, $attr) {
  9143. if ($attr.noTapScroll == 'true') {
  9144. return;
  9145. }
  9146. ionic.on('tap', onTap, $element[0]);
  9147. $scope.$on('$destroy', function() {
  9148. ionic.off('tap', onTap, $element[0]);
  9149. });
  9150. function onTap(e) {
  9151. var depth = 3;
  9152. var current = e.target;
  9153. //Don't scroll to top in certain cases
  9154. while (depth-- && current) {
  9155. if (current.classList.contains('button') ||
  9156. current.tagName.match(/input|textarea|select/i) ||
  9157. current.isContentEditable) {
  9158. return;
  9159. }
  9160. current = current.parentNode;
  9161. }
  9162. var touch = e.gesture && e.gesture.touches[0] || e.detail.touches[0];
  9163. var bounds = $element[0].getBoundingClientRect();
  9164. if (ionic.DomUtil.rectContains(
  9165. touch.pageX, touch.pageY,
  9166. bounds.left, bounds.top - 20,
  9167. bounds.left + bounds.width, bounds.top + bounds.height
  9168. )) {
  9169. $ionicScrollDelegate.scrollTop(true);
  9170. }
  9171. }
  9172. }
  9173. };
  9174. }];
  9175. }
  9176. function headerFooterBarDirective(isHeader) {
  9177. return ['$document', '$timeout', function($document, $timeout) {
  9178. return {
  9179. restrict: 'E',
  9180. controller: '$ionicHeaderBar',
  9181. compile: function(tElement) {
  9182. tElement.addClass(isHeader ? 'bar bar-header' : 'bar bar-footer');
  9183. // top style tabs? if so, remove bottom border for seamless display
  9184. $timeout(function() {
  9185. if (isHeader && $document[0].getElementsByClassName('tabs-top').length) tElement.addClass('has-tabs-top');
  9186. });
  9187. return { pre: prelink };
  9188. function prelink($scope, $element, $attr, ctrl) {
  9189. if (isHeader) {
  9190. $scope.$watch(function() { return $element[0].className; }, function(value) {
  9191. var isShown = value.indexOf('ng-hide') === -1;
  9192. var isSubheader = value.indexOf('bar-subheader') !== -1;
  9193. $scope.$hasHeader = isShown && !isSubheader;
  9194. $scope.$hasSubheader = isShown && isSubheader;
  9195. $scope.$emit('$ionicSubheader', $scope.$hasSubheader);
  9196. });
  9197. $scope.$on('$destroy', function() {
  9198. delete $scope.$hasHeader;
  9199. delete $scope.$hasSubheader;
  9200. });
  9201. ctrl.align();
  9202. $scope.$on('$ionicHeader.align', function() {
  9203. ionic.requestAnimationFrame(function() {
  9204. ctrl.align();
  9205. });
  9206. });
  9207. } else {
  9208. $scope.$watch(function() { return $element[0].className; }, function(value) {
  9209. var isShown = value.indexOf('ng-hide') === -1;
  9210. var isSubfooter = value.indexOf('bar-subfooter') !== -1;
  9211. $scope.$hasFooter = isShown && !isSubfooter;
  9212. $scope.$hasSubfooter = isShown && isSubfooter;
  9213. });
  9214. $scope.$on('$destroy', function() {
  9215. delete $scope.$hasFooter;
  9216. delete $scope.$hasSubfooter;
  9217. });
  9218. $scope.$watch('$hasTabs', function(val) {
  9219. $element.toggleClass('has-tabs', !!val);
  9220. });
  9221. ctrl.align();
  9222. $scope.$on('$ionicFooter.align', function() {
  9223. ionic.requestAnimationFrame(function() {
  9224. ctrl.align();
  9225. });
  9226. });
  9227. }
  9228. }
  9229. }
  9230. };
  9231. }];
  9232. }
  9233. /**
  9234. * @ngdoc directive
  9235. * @name ionInfiniteScroll
  9236. * @module ionic
  9237. * @parent ionic.directive:ionContent, ionic.directive:ionScroll
  9238. * @restrict E
  9239. *
  9240. * @description
  9241. * The ionInfiniteScroll directive allows you to call a function whenever
  9242. * the user gets to the bottom of the page or near the bottom of the page.
  9243. *
  9244. * The expression you pass in for `on-infinite` is called when the user scrolls
  9245. * greater than `distance` away from the bottom of the content. Once `on-infinite`
  9246. * is done loading new data, it should broadcast the `scroll.infiniteScrollComplete`
  9247. * event from your controller (see below example).
  9248. *
  9249. * @param {expression} on-infinite What to call when the scroller reaches the
  9250. * bottom.
  9251. * @param {string=} distance The distance from the bottom that the scroll must
  9252. * reach to trigger the on-infinite expression. Default: 1%.
  9253. * @param {string=} spinner The {@link ionic.directive:ionSpinner} to show while loading. The SVG
  9254. * {@link ionic.directive:ionSpinner} is now the default, replacing rotating font icons.
  9255. * @param {string=} icon The icon to show while loading. Default: 'ion-load-d'. This is depreicated
  9256. * in favor of the SVG {@link ionic.directive:ionSpinner}.
  9257. * @param {boolean=} immediate-check Whether to check the infinite scroll bounds immediately on load.
  9258. *
  9259. * @usage
  9260. * ```html
  9261. * <ion-content ng-controller="MyController">
  9262. * <ion-list>
  9263. * ....
  9264. * ....
  9265. * </ion-list>
  9266. *
  9267. * <ion-infinite-scroll
  9268. * on-infinite="loadMore()"
  9269. * distance="1%">
  9270. * </ion-infinite-scroll>
  9271. * </ion-content>
  9272. * ```
  9273. * ```js
  9274. * function MyController($scope, $http) {
  9275. * $scope.items = [];
  9276. * $scope.loadMore = function() {
  9277. * $http.get('/more-items').success(function(items) {
  9278. * useItems(items);
  9279. * $scope.$broadcast('scroll.infiniteScrollComplete');
  9280. * });
  9281. * };
  9282. *
  9283. * $scope.$on('$stateChangeSuccess', function() {
  9284. * $scope.loadMore();
  9285. * });
  9286. * }
  9287. * ```
  9288. *
  9289. * An easy to way to stop infinite scroll once there is no more data to load
  9290. * is to use angular's `ng-if` directive:
  9291. *
  9292. * ```html
  9293. * <ion-infinite-scroll
  9294. * ng-if="moreDataCanBeLoaded()"
  9295. * icon="ion-loading-c"
  9296. * on-infinite="loadMoreData()">
  9297. * </ion-infinite-scroll>
  9298. * ```
  9299. */
  9300. IonicModule
  9301. .directive('ionInfiniteScroll', ['$timeout', function($timeout) {
  9302. return {
  9303. restrict: 'E',
  9304. require: ['?^$ionicScroll', 'ionInfiniteScroll'],
  9305. template: function($element, $attrs) {
  9306. if ($attrs.icon) return '<i class="icon {{icon()}} icon-refreshing {{scrollingType}}"></i>';
  9307. return '<ion-spinner icon="{{spinner()}}"></ion-spinner>';
  9308. },
  9309. scope: true,
  9310. controller: '$ionInfiniteScroll',
  9311. link: function($scope, $element, $attrs, ctrls) {
  9312. var infiniteScrollCtrl = ctrls[1];
  9313. var scrollCtrl = infiniteScrollCtrl.scrollCtrl = ctrls[0];
  9314. var jsScrolling = infiniteScrollCtrl.jsScrolling = !scrollCtrl.isNative();
  9315. // if this view is not beneath a scrollCtrl, it can't be injected, proceed w/ native scrolling
  9316. if (jsScrolling) {
  9317. infiniteScrollCtrl.scrollView = scrollCtrl.scrollView;
  9318. $scope.scrollingType = 'js-scrolling';
  9319. //bind to JS scroll events
  9320. scrollCtrl.$element.on('scroll', infiniteScrollCtrl.checkBounds);
  9321. } else {
  9322. // grabbing the scrollable element, to determine dimensions, and current scroll pos
  9323. var scrollEl = ionic.DomUtil.getParentOrSelfWithClass($element[0].parentNode, 'overflow-scroll');
  9324. infiniteScrollCtrl.scrollEl = scrollEl;
  9325. // if there's no scroll controller, and no overflow scroll div, infinite scroll wont work
  9326. if (!scrollEl) {
  9327. throw 'Infinite scroll must be used inside a scrollable div';
  9328. }
  9329. //bind to native scroll events
  9330. infiniteScrollCtrl.scrollEl.addEventListener('scroll', infiniteScrollCtrl.checkBounds);
  9331. }
  9332. // Optionally check bounds on start after scrollView is fully rendered
  9333. var doImmediateCheck = isDefined($attrs.immediateCheck) ? $scope.$eval($attrs.immediateCheck) : true;
  9334. if (doImmediateCheck) {
  9335. $timeout(function() { infiniteScrollCtrl.checkBounds(); });
  9336. }
  9337. }
  9338. };
  9339. }]);
  9340. /**
  9341. * @ngdoc directive
  9342. * @name ionInput
  9343. * @parent ionic.directive:ionList
  9344. * @module ionic
  9345. * @restrict E
  9346. * Creates a text input group that can easily be focused
  9347. *
  9348. * @usage
  9349. *
  9350. * ```html
  9351. * <ion-list>
  9352. * <ion-input>
  9353. * <input type="text" placeholder="First Name">
  9354. * </ion-input>
  9355. *
  9356. * <ion-input>
  9357. * <ion-label>Username</ion-label>
  9358. * <input type="text">
  9359. * </ion-input>
  9360. * </ion-list>
  9361. * ```
  9362. */
  9363. var labelIds = -1;
  9364. IonicModule
  9365. .directive('ionInput', [function() {
  9366. return {
  9367. restrict: 'E',
  9368. controller: ['$scope', '$element', function($scope, $element) {
  9369. this.$scope = $scope;
  9370. this.$element = $element;
  9371. this.setInputAriaLabeledBy = function(id) {
  9372. var inputs = $element[0].querySelectorAll('input,textarea');
  9373. inputs.length && inputs[0].setAttribute('aria-labelledby', id);
  9374. };
  9375. this.focus = function() {
  9376. var inputs = $element[0].querySelectorAll('input,textarea');
  9377. inputs.length && inputs[0].focus();
  9378. };
  9379. }]
  9380. };
  9381. }]);
  9382. /**
  9383. * @ngdoc directive
  9384. * @name ionLabel
  9385. * @parent ionic.directive:ionList
  9386. * @module ionic
  9387. * @restrict E
  9388. *
  9389. * New in Ionic 1.2. It is strongly recommended that you use `<ion-label>` in place
  9390. * of any `<label>` elements for maximum cross-browser support and performance.
  9391. *
  9392. * Creates a label for a form input.
  9393. *
  9394. * @usage
  9395. *
  9396. * ```html
  9397. * <ion-list>
  9398. * <ion-input>
  9399. * <ion-label>Username</ion-label>
  9400. * <input type="text">
  9401. * </ion-input>
  9402. * </ion-list>
  9403. * ```
  9404. */
  9405. IonicModule
  9406. .directive('ionLabel', [function() {
  9407. return {
  9408. restrict: 'E',
  9409. require: '?^ionInput',
  9410. compile: function() {
  9411. return function link($scope, $element, $attrs, ionInputCtrl) {
  9412. var element = $element[0];
  9413. $element.addClass('input-label');
  9414. $element.attr('aria-label', $element.text());
  9415. var id = element.id || '_label-' + ++labelIds;
  9416. if (!element.id) {
  9417. $element.attr('id', id);
  9418. }
  9419. if (ionInputCtrl) {
  9420. ionInputCtrl.setInputAriaLabeledBy(id);
  9421. $element.on('click', function() {
  9422. ionInputCtrl.focus();
  9423. });
  9424. }
  9425. };
  9426. }
  9427. };
  9428. }]);
  9429. /**
  9430. * Input label adds accessibility to <span class="input-label">.
  9431. */
  9432. IonicModule
  9433. .directive('inputLabel', [function() {
  9434. return {
  9435. restrict: 'C',
  9436. require: '?^ionInput',
  9437. compile: function() {
  9438. return function link($scope, $element, $attrs, ionInputCtrl) {
  9439. var element = $element[0];
  9440. $element.attr('aria-label', $element.text());
  9441. var id = element.id || '_label-' + ++labelIds;
  9442. if (!element.id) {
  9443. $element.attr('id', id);
  9444. }
  9445. if (ionInputCtrl) {
  9446. ionInputCtrl.setInputAriaLabeledBy(id);
  9447. }
  9448. };
  9449. }
  9450. };
  9451. }]);
  9452. /**
  9453. * @ngdoc directive
  9454. * @name ionItem
  9455. * @parent ionic.directive:ionList
  9456. * @module ionic
  9457. * @restrict E
  9458. * Creates a list-item that can easily be swiped,
  9459. * deleted, reordered, edited, and more.
  9460. *
  9461. * See {@link ionic.directive:ionList} for a complete example & explanation.
  9462. *
  9463. * Can be assigned any item class name. See the
  9464. * [list CSS documentation](/docs/components/#list).
  9465. *
  9466. * @usage
  9467. *
  9468. * ```html
  9469. * <ion-list>
  9470. * <ion-item>Hello!</ion-item>
  9471. * <ion-item href="#/detail">
  9472. * Link to detail page
  9473. * </ion-item>
  9474. * </ion-list>
  9475. * ```
  9476. */
  9477. IonicModule
  9478. .directive('ionItem', ['$$rAF', function($$rAF) {
  9479. return {
  9480. restrict: 'E',
  9481. controller: ['$scope', '$element', function($scope, $element) {
  9482. this.$scope = $scope;
  9483. this.$element = $element;
  9484. }],
  9485. scope: true,
  9486. compile: function($element, $attrs) {
  9487. var isAnchor = isDefined($attrs.href) ||
  9488. isDefined($attrs.ngHref) ||
  9489. isDefined($attrs.uiSref);
  9490. var isComplexItem = isAnchor ||
  9491. //Lame way of testing, but we have to know at compile what to do with the element
  9492. /ion-(delete|option|reorder)-button/i.test($element.html());
  9493. if (isComplexItem) {
  9494. var innerElement = jqLite(isAnchor ? '<a></a>' : '<div></div>');
  9495. innerElement.addClass('item-content');
  9496. if (isDefined($attrs.href) || isDefined($attrs.ngHref)) {
  9497. innerElement.attr('ng-href', '{{$href()}}');
  9498. if (isDefined($attrs.target)) {
  9499. innerElement.attr('target', '{{$target()}}');
  9500. }
  9501. }
  9502. innerElement.append($element.contents());
  9503. $element.addClass('item item-complex')
  9504. .append(innerElement);
  9505. } else {
  9506. $element.addClass('item');
  9507. }
  9508. return function link($scope, $element, $attrs) {
  9509. $scope.$href = function() {
  9510. return $attrs.href || $attrs.ngHref;
  9511. };
  9512. $scope.$target = function() {
  9513. return $attrs.target;
  9514. };
  9515. var content = $element[0].querySelector('.item-content');
  9516. if (content) {
  9517. $scope.$on('$collectionRepeatLeave', function() {
  9518. if (content && content.$$ionicOptionsOpen) {
  9519. content.style[ionic.CSS.TRANSFORM] = '';
  9520. content.style[ionic.CSS.TRANSITION] = 'none';
  9521. $$rAF(function() {
  9522. content.style[ionic.CSS.TRANSITION] = '';
  9523. });
  9524. content.$$ionicOptionsOpen = false;
  9525. }
  9526. });
  9527. }
  9528. };
  9529. }
  9530. };
  9531. }]);
  9532. var ITEM_TPL_DELETE_BUTTON =
  9533. '<div class="item-left-edit item-delete enable-pointer-events">' +
  9534. '</div>';
  9535. /**
  9536. * @ngdoc directive
  9537. * @name ionDeleteButton
  9538. * @parent ionic.directive:ionItem
  9539. * @module ionic
  9540. * @restrict E
  9541. * Creates a delete button inside a list item, that is visible when the
  9542. * {@link ionic.directive:ionList ionList parent's} `show-delete` evaluates to true or
  9543. * `$ionicListDelegate.showDelete(true)` is called.
  9544. *
  9545. * Takes any ionicon as a class.
  9546. *
  9547. * See {@link ionic.directive:ionList} for a complete example & explanation.
  9548. *
  9549. * @usage
  9550. *
  9551. * ```html
  9552. * <ion-list show-delete="shouldShowDelete">
  9553. * <ion-item>
  9554. * <ion-delete-button class="ion-minus-circled"></ion-delete-button>
  9555. * Hello, list item!
  9556. * </ion-item>
  9557. * </ion-list>
  9558. * <ion-toggle ng-model="shouldShowDelete">
  9559. * Show Delete?
  9560. * </ion-toggle>
  9561. * ```
  9562. */
  9563. IonicModule
  9564. .directive('ionDeleteButton', function() {
  9565. function stopPropagation(ev) {
  9566. ev.stopPropagation();
  9567. }
  9568. return {
  9569. restrict: 'E',
  9570. require: ['^^ionItem', '^?ionList'],
  9571. //Run before anything else, so we can move it before other directives process
  9572. //its location (eg ngIf relies on the location of the directive in the dom)
  9573. priority: Number.MAX_VALUE,
  9574. compile: function($element, $attr) {
  9575. //Add the classes we need during the compile phase, so that they stay
  9576. //even if something else like ngIf removes the element and re-addss it
  9577. $attr.$set('class', ($attr['class'] || '') + ' button icon button-icon', true);
  9578. return function($scope, $element, $attr, ctrls) {
  9579. var itemCtrl = ctrls[0];
  9580. var listCtrl = ctrls[1];
  9581. var container = jqLite(ITEM_TPL_DELETE_BUTTON);
  9582. container.append($element);
  9583. itemCtrl.$element.append(container).addClass('item-left-editable');
  9584. //Don't bubble click up to main .item
  9585. $element.on('click', stopPropagation);
  9586. init();
  9587. $scope.$on('$ionic.reconnectScope', init);
  9588. function init() {
  9589. listCtrl = listCtrl || $element.controller('ionList');
  9590. if (listCtrl && listCtrl.showDelete()) {
  9591. container.addClass('visible active');
  9592. }
  9593. }
  9594. };
  9595. }
  9596. };
  9597. });
  9598. IonicModule
  9599. .directive('itemFloatingLabel', function() {
  9600. return {
  9601. restrict: 'C',
  9602. link: function(scope, element) {
  9603. var el = element[0];
  9604. var input = el.querySelector('input, textarea');
  9605. var inputLabel = el.querySelector('.input-label');
  9606. if (!input || !inputLabel) return;
  9607. var onInput = function() {
  9608. if (input.value) {
  9609. inputLabel.classList.add('has-input');
  9610. } else {
  9611. inputLabel.classList.remove('has-input');
  9612. }
  9613. };
  9614. input.addEventListener('input', onInput);
  9615. var ngModelCtrl = jqLite(input).controller('ngModel');
  9616. if (ngModelCtrl) {
  9617. ngModelCtrl.$render = function() {
  9618. input.value = ngModelCtrl.$viewValue || '';
  9619. onInput();
  9620. };
  9621. }
  9622. scope.$on('$destroy', function() {
  9623. input.removeEventListener('input', onInput);
  9624. });
  9625. }
  9626. };
  9627. });
  9628. var ITEM_TPL_OPTION_BUTTONS =
  9629. '<div class="item-options invisible">' +
  9630. '</div>';
  9631. /**
  9632. * @ngdoc directive
  9633. * @name ionOptionButton
  9634. * @parent ionic.directive:ionItem
  9635. * @module ionic
  9636. * @restrict E
  9637. * @description
  9638. * Creates an option button inside a list item, that is visible when the item is swiped
  9639. * to the left by the user. Swiped open option buttons can be hidden with
  9640. * {@link ionic.service:$ionicListDelegate#closeOptionButtons $ionicListDelegate.closeOptionButtons}.
  9641. *
  9642. * Can be assigned any button class.
  9643. *
  9644. * See {@link ionic.directive:ionList} for a complete example & explanation.
  9645. *
  9646. * @usage
  9647. *
  9648. * ```html
  9649. * <ion-list>
  9650. * <ion-item>
  9651. * I love kittens!
  9652. * <ion-option-button class="button-positive">Share</ion-option-button>
  9653. * <ion-option-button class="button-assertive">Edit</ion-option-button>
  9654. * </ion-item>
  9655. * </ion-list>
  9656. * ```
  9657. */
  9658. IonicModule.directive('ionOptionButton', [function() {
  9659. function stopPropagation(e) {
  9660. e.stopPropagation();
  9661. }
  9662. return {
  9663. restrict: 'E',
  9664. require: '^ionItem',
  9665. priority: Number.MAX_VALUE,
  9666. compile: function($element, $attr) {
  9667. $attr.$set('class', ($attr['class'] || '') + ' button', true);
  9668. return function($scope, $element, $attr, itemCtrl) {
  9669. if (!itemCtrl.optionsContainer) {
  9670. itemCtrl.optionsContainer = jqLite(ITEM_TPL_OPTION_BUTTONS);
  9671. itemCtrl.$element.prepend(itemCtrl.optionsContainer);
  9672. }
  9673. itemCtrl.optionsContainer.prepend($element);
  9674. itemCtrl.$element.addClass('item-right-editable');
  9675. //Don't bubble click up to main .item
  9676. $element.on('click', stopPropagation);
  9677. };
  9678. }
  9679. };
  9680. }]);
  9681. var ITEM_TPL_REORDER_BUTTON =
  9682. '<div data-prevent-scroll="true" class="item-right-edit item-reorder enable-pointer-events">' +
  9683. '</div>';
  9684. /**
  9685. * @ngdoc directive
  9686. * @name ionReorderButton
  9687. * @parent ionic.directive:ionItem
  9688. * @module ionic
  9689. * @restrict E
  9690. * Creates a reorder button inside a list item, that is visible when the
  9691. * {@link ionic.directive:ionList ionList parent's} `show-reorder` evaluates to true or
  9692. * `$ionicListDelegate.showReorder(true)` is called.
  9693. *
  9694. * Can be dragged to reorder items in the list. Takes any ionicon class.
  9695. *
  9696. * 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.
  9697. *
  9698. * 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.
  9699. *
  9700. * Look at {@link ionic.directive:ionList} for more examples.
  9701. *
  9702. * @usage
  9703. *
  9704. * ```html
  9705. * <ion-list ng-controller="MyCtrl" show-reorder="true">
  9706. * <ion-item ng-repeat="item in items">
  9707. * Item {{item}}
  9708. * <ion-reorder-button class="ion-navicon"
  9709. * on-reorder="moveItem(item, $fromIndex, $toIndex)">
  9710. * </ion-reorder-button>
  9711. * </ion-item>
  9712. * </ion-list>
  9713. * ```
  9714. * ```js
  9715. * function MyCtrl($scope) {
  9716. * $scope.items = [1, 2, 3, 4];
  9717. * $scope.moveItem = function(item, fromIndex, toIndex) {
  9718. * //Move the item in the array
  9719. * $scope.items.splice(fromIndex, 1);
  9720. * $scope.items.splice(toIndex, 0, item);
  9721. * };
  9722. * }
  9723. * ```
  9724. *
  9725. * @param {expression=} on-reorder Expression to call when an item is reordered.
  9726. * Parameters given: $fromIndex, $toIndex.
  9727. */
  9728. IonicModule
  9729. .directive('ionReorderButton', ['$parse', function($parse) {
  9730. return {
  9731. restrict: 'E',
  9732. require: ['^ionItem', '^?ionList'],
  9733. priority: Number.MAX_VALUE,
  9734. compile: function($element, $attr) {
  9735. $attr.$set('class', ($attr['class'] || '') + ' button icon button-icon', true);
  9736. $element[0].setAttribute('data-prevent-scroll', true);
  9737. return function($scope, $element, $attr, ctrls) {
  9738. var itemCtrl = ctrls[0];
  9739. var listCtrl = ctrls[1];
  9740. var onReorderFn = $parse($attr.onReorder);
  9741. $scope.$onReorder = function(oldIndex, newIndex) {
  9742. onReorderFn($scope, {
  9743. $fromIndex: oldIndex,
  9744. $toIndex: newIndex
  9745. });
  9746. };
  9747. // prevent clicks from bubbling up to the item
  9748. if (!$attr.ngClick && !$attr.onClick && !$attr.onclick) {
  9749. $element[0].onclick = function(e) {
  9750. e.stopPropagation();
  9751. return false;
  9752. };
  9753. }
  9754. var container = jqLite(ITEM_TPL_REORDER_BUTTON);
  9755. container.append($element);
  9756. itemCtrl.$element.append(container).addClass('item-right-editable');
  9757. if (listCtrl && listCtrl.showReorder()) {
  9758. container.addClass('visible active');
  9759. }
  9760. };
  9761. }
  9762. };
  9763. }]);
  9764. /**
  9765. * @ngdoc directive
  9766. * @name keyboardAttach
  9767. * @module ionic
  9768. * @restrict A
  9769. *
  9770. * @description
  9771. * keyboard-attach is an attribute directive which will cause an element to float above
  9772. * the keyboard when the keyboard shows. Currently only supports the
  9773. * [ion-footer-bar]({{ page.versionHref }}/api/directive/ionFooterBar/) directive.
  9774. *
  9775. * ### Notes
  9776. * - This directive requires the
  9777. * [Ionic Keyboard Plugin](https://github.com/driftyco/ionic-plugins-keyboard).
  9778. * - On Android not in fullscreen mode, i.e. you have
  9779. * `<preference name="Fullscreen" value="false" />` or no preference in your `config.xml` file,
  9780. * this directive is unnecessary since it is the default behavior.
  9781. * - On iOS, if there is an input in your footer, you will need to set
  9782. * `cordova.plugins.Keyboard.disableScroll(true)`.
  9783. *
  9784. * @usage
  9785. *
  9786. * ```html
  9787. * <ion-footer-bar align-title="left" keyboard-attach class="bar-assertive">
  9788. * <h1 class="title">Title!</h1>
  9789. * </ion-footer-bar>
  9790. * ```
  9791. */
  9792. IonicModule
  9793. .directive('keyboardAttach', function() {
  9794. return function(scope, element) {
  9795. ionic.on('native.keyboardshow', onShow, window);
  9796. ionic.on('native.keyboardhide', onHide, window);
  9797. //deprecated
  9798. ionic.on('native.showkeyboard', onShow, window);
  9799. ionic.on('native.hidekeyboard', onHide, window);
  9800. var scrollCtrl;
  9801. function onShow(e) {
  9802. if (ionic.Platform.isAndroid() && !ionic.Platform.isFullScreen) {
  9803. return;
  9804. }
  9805. //for testing
  9806. var keyboardHeight = e.keyboardHeight || (e.detail && e.detail.keyboardHeight);
  9807. element.css('bottom', keyboardHeight + "px");
  9808. scrollCtrl = element.controller('$ionicScroll');
  9809. if (scrollCtrl) {
  9810. scrollCtrl.scrollView.__container.style.bottom = keyboardHeight + keyboardAttachGetClientHeight(element[0]) + "px";
  9811. }
  9812. }
  9813. function onHide() {
  9814. if (ionic.Platform.isAndroid() && !ionic.Platform.isFullScreen) {
  9815. return;
  9816. }
  9817. element.css('bottom', '');
  9818. if (scrollCtrl) {
  9819. scrollCtrl.scrollView.__container.style.bottom = '';
  9820. }
  9821. }
  9822. scope.$on('$destroy', function() {
  9823. ionic.off('native.keyboardshow', onShow, window);
  9824. ionic.off('native.keyboardhide', onHide, window);
  9825. //deprecated
  9826. ionic.off('native.showkeyboard', onShow, window);
  9827. ionic.off('native.hidekeyboard', onHide, window);
  9828. });
  9829. };
  9830. });
  9831. function keyboardAttachGetClientHeight(element) {
  9832. return element.clientHeight;
  9833. }
  9834. /**
  9835. * @ngdoc directive
  9836. * @name ionList
  9837. * @module ionic
  9838. * @delegate ionic.service:$ionicListDelegate
  9839. * @codepen JsHjf
  9840. * @restrict E
  9841. * @description
  9842. * The List is a widely used interface element in almost any mobile app, and can include
  9843. * content ranging from basic text all the way to buttons, toggles, icons, and thumbnails.
  9844. *
  9845. * Both the list, which contains items, and the list items themselves can be any HTML
  9846. * element. The containing element requires the `list` class and each list item requires
  9847. * the `item` class.
  9848. *
  9849. * However, using the ionList and ionItem directives make it easy to support various
  9850. * interaction modes such as swipe to edit, drag to reorder, and removing items.
  9851. *
  9852. * Related: {@link ionic.directive:ionItem}, {@link ionic.directive:ionOptionButton}
  9853. * {@link ionic.directive:ionReorderButton}, {@link ionic.directive:ionDeleteButton}, [`list CSS documentation`](/docs/components/#list).
  9854. *
  9855. * @usage
  9856. *
  9857. * Basic Usage:
  9858. *
  9859. * ```html
  9860. * <ion-list>
  9861. * <ion-item ng-repeat="item in items">
  9862. * {% raw %}Hello, {{item}}!{% endraw %}
  9863. * </ion-item>
  9864. * </ion-list>
  9865. * ```
  9866. *
  9867. * Advanced Usage: Thumbnails, Delete buttons, Reordering, Swiping
  9868. *
  9869. * ```html
  9870. * <ion-list ng-controller="MyCtrl"
  9871. * show-delete="shouldShowDelete"
  9872. * show-reorder="shouldShowReorder"
  9873. * can-swipe="listCanSwipe">
  9874. * <ion-item ng-repeat="item in items"
  9875. * class="item-thumbnail-left">
  9876. *
  9877. * {% raw %}<img ng-src="{{item.img}}">
  9878. * <h2>{{item.title}}</h2>
  9879. * <p>{{item.description}}</p>{% endraw %}
  9880. * <ion-option-button class="button-positive"
  9881. * ng-click="share(item)">
  9882. * Share
  9883. * </ion-option-button>
  9884. * <ion-option-button class="button-info"
  9885. * ng-click="edit(item)">
  9886. * Edit
  9887. * </ion-option-button>
  9888. * <ion-delete-button class="ion-minus-circled"
  9889. * ng-click="items.splice($index, 1)">
  9890. * </ion-delete-button>
  9891. * <ion-reorder-button class="ion-navicon"
  9892. * on-reorder="reorderItem(item, $fromIndex, $toIndex)">
  9893. * </ion-reorder-button>
  9894. *
  9895. * </ion-item>
  9896. * </ion-list>
  9897. * ```
  9898. *
  9899. *```javascript
  9900. * app.controller('MyCtrl', function($scope) {
  9901. * $scope.shouldShowDelete = false;
  9902. * $scope.shouldShowReorder = false;
  9903. * $scope.listCanSwipe = true
  9904. * });
  9905. *```
  9906. *
  9907. * @param {string=} delegate-handle The handle used to identify this list with
  9908. * {@link ionic.service:$ionicListDelegate}.
  9909. * @param type {string=} The type of list to use (list-inset or card)
  9910. * @param show-delete {boolean=} Whether the delete buttons for the items in the list are
  9911. * currently shown or hidden.
  9912. * @param show-reorder {boolean=} Whether the reorder buttons for the items in the list are
  9913. * currently shown or hidden.
  9914. * @param can-swipe {boolean=} Whether the items in the list are allowed to be swiped to reveal
  9915. * option buttons. Default: true.
  9916. */
  9917. IonicModule
  9918. .directive('ionList', [
  9919. '$timeout',
  9920. function($timeout) {
  9921. return {
  9922. restrict: 'E',
  9923. require: ['ionList', '^?$ionicScroll'],
  9924. controller: '$ionicList',
  9925. compile: function($element, $attr) {
  9926. var listEl = jqLite('<div class="list">')
  9927. .append($element.contents())
  9928. .addClass($attr.type);
  9929. $element.append(listEl);
  9930. return function($scope, $element, $attrs, ctrls) {
  9931. var listCtrl = ctrls[0];
  9932. var scrollCtrl = ctrls[1];
  9933. // Wait for child elements to render...
  9934. $timeout(init);
  9935. function init() {
  9936. var listView = listCtrl.listView = new ionic.views.ListView({
  9937. el: $element[0],
  9938. listEl: $element.children()[0],
  9939. scrollEl: scrollCtrl && scrollCtrl.element,
  9940. scrollView: scrollCtrl && scrollCtrl.scrollView,
  9941. onReorder: function(el, oldIndex, newIndex) {
  9942. var itemScope = jqLite(el).scope();
  9943. if (itemScope && itemScope.$onReorder) {
  9944. // Make sure onReorder is called in apply cycle,
  9945. // but also make sure it has no conflicts by doing
  9946. // $evalAsync
  9947. $timeout(function() {
  9948. itemScope.$onReorder(oldIndex, newIndex);
  9949. });
  9950. }
  9951. },
  9952. canSwipe: function() {
  9953. return listCtrl.canSwipeItems();
  9954. }
  9955. });
  9956. $scope.$on('$destroy', function() {
  9957. if (listView) {
  9958. listView.deregister && listView.deregister();
  9959. listView = null;
  9960. }
  9961. });
  9962. if (isDefined($attr.canSwipe)) {
  9963. $scope.$watch('!!(' + $attr.canSwipe + ')', function(value) {
  9964. listCtrl.canSwipeItems(value);
  9965. });
  9966. }
  9967. if (isDefined($attr.showDelete)) {
  9968. $scope.$watch('!!(' + $attr.showDelete + ')', function(value) {
  9969. listCtrl.showDelete(value);
  9970. });
  9971. }
  9972. if (isDefined($attr.showReorder)) {
  9973. $scope.$watch('!!(' + $attr.showReorder + ')', function(value) {
  9974. listCtrl.showReorder(value);
  9975. });
  9976. }
  9977. $scope.$watch(function() {
  9978. return listCtrl.showDelete();
  9979. }, function(isShown, wasShown) {
  9980. //Only use isShown=false if it was already shown
  9981. if (!isShown && !wasShown) { return; }
  9982. if (isShown) listCtrl.closeOptionButtons();
  9983. listCtrl.canSwipeItems(!isShown);
  9984. $element.children().toggleClass('list-left-editing', isShown);
  9985. $element.toggleClass('disable-pointer-events', isShown);
  9986. var deleteButton = jqLite($element[0].getElementsByClassName('item-delete'));
  9987. setButtonShown(deleteButton, listCtrl.showDelete);
  9988. });
  9989. $scope.$watch(function() {
  9990. return listCtrl.showReorder();
  9991. }, function(isShown, wasShown) {
  9992. //Only use isShown=false if it was already shown
  9993. if (!isShown && !wasShown) { return; }
  9994. if (isShown) listCtrl.closeOptionButtons();
  9995. listCtrl.canSwipeItems(!isShown);
  9996. $element.children().toggleClass('list-right-editing', isShown);
  9997. $element.toggleClass('disable-pointer-events', isShown);
  9998. var reorderButton = jqLite($element[0].getElementsByClassName('item-reorder'));
  9999. setButtonShown(reorderButton, listCtrl.showReorder);
  10000. });
  10001. function setButtonShown(el, shown) {
  10002. shown() && el.addClass('visible') || el.removeClass('active');
  10003. ionic.requestAnimationFrame(function() {
  10004. shown() && el.addClass('active') || el.removeClass('visible');
  10005. });
  10006. }
  10007. }
  10008. };
  10009. }
  10010. };
  10011. }]);
  10012. /**
  10013. * @ngdoc directive
  10014. * @name menuClose
  10015. * @module ionic
  10016. * @restrict AC
  10017. *
  10018. * @description
  10019. * `menu-close` is an attribute directive that closes a currently opened side menu.
  10020. * Note that by default, navigation transitions will not animate between views when
  10021. * the menu is open. Additionally, this directive will reset the entering view's
  10022. * history stack, making the new page the root of the history stack. This is done
  10023. * to replicate the user experience seen in most side menu implementations, which is
  10024. * to not show the back button at the root of the stack and show only the
  10025. * menu button. We recommend that you also use the `enable-menu-with-back-views="false"`
  10026. * {@link ionic.directive:ionSideMenus} attribute when using the menuClose directive.
  10027. *
  10028. * @usage
  10029. * Below is an example of a link within a side menu. Tapping this link would
  10030. * automatically close the currently opened menu.
  10031. *
  10032. * ```html
  10033. * <a menu-close href="#/home" class="item">Home</a>
  10034. * ```
  10035. *
  10036. * Note that if your destination state uses a resolve and that resolve asynchronously
  10037. * takes longer than a standard transition (300ms), you'll need to set the
  10038. * `nextViewOptions` manually as your resolve completes.
  10039. *
  10040. * ```js
  10041. * $ionicHistory.nextViewOptions({
  10042. * historyRoot: true,
  10043. * disableAnimate: true,
  10044. * expire: 300
  10045. * });
  10046. * ```
  10047. */
  10048. IonicModule
  10049. .directive('menuClose', ['$ionicHistory', '$timeout', function($ionicHistory, $timeout) {
  10050. return {
  10051. restrict: 'AC',
  10052. link: function($scope, $element) {
  10053. $element.bind('click', function() {
  10054. var sideMenuCtrl = $element.inheritedData('$ionSideMenusController');
  10055. if (sideMenuCtrl) {
  10056. $ionicHistory.nextViewOptions({
  10057. historyRoot: true,
  10058. disableAnimate: true,
  10059. expire: 300
  10060. });
  10061. // if no transition in 300ms, reset nextViewOptions
  10062. // the expire should take care of it, but will be cancelled in some
  10063. // cases. This directive is an exception to the rules of history.js
  10064. $timeout( function() {
  10065. $ionicHistory.nextViewOptions({
  10066. historyRoot: false,
  10067. disableAnimate: false
  10068. });
  10069. }, 300);
  10070. sideMenuCtrl.close();
  10071. }
  10072. });
  10073. }
  10074. };
  10075. }]);
  10076. /**
  10077. * @ngdoc directive
  10078. * @name menuToggle
  10079. * @module ionic
  10080. * @restrict AC
  10081. *
  10082. * @description
  10083. * Toggle a side menu on the given side.
  10084. *
  10085. * @usage
  10086. * Below is an example of a link within a nav bar. Tapping this button
  10087. * would open the given side menu, and tapping it again would close it.
  10088. *
  10089. * ```html
  10090. * <ion-nav-bar>
  10091. * <ion-nav-buttons side="left">
  10092. * <!-- Toggle left side menu -->
  10093. * <button menu-toggle="left" class="button button-icon icon ion-navicon"></button>
  10094. * </ion-nav-buttons>
  10095. * <ion-nav-buttons side="right">
  10096. * <!-- Toggle right side menu -->
  10097. * <button menu-toggle="right" class="button button-icon icon ion-navicon"></button>
  10098. * </ion-nav-buttons>
  10099. * </ion-nav-bar>
  10100. * ```
  10101. *
  10102. * ### Button Hidden On Child Views
  10103. * By default, the menu toggle button will only appear on a root
  10104. * level side-menu page. Navigating in to child views will hide the menu-
  10105. * toggle button. They can be made visible on child pages by setting the
  10106. * enable-menu-with-back-views attribute of the {@link ionic.directive:ionSideMenus}
  10107. * directive to true.
  10108. *
  10109. * ```html
  10110. * <ion-side-menus enable-menu-with-back-views="true">
  10111. * ```
  10112. */
  10113. IonicModule
  10114. .directive('menuToggle', function() {
  10115. return {
  10116. restrict: 'AC',
  10117. link: function($scope, $element, $attr) {
  10118. $scope.$on('$ionicView.beforeEnter', function(ev, viewData) {
  10119. if (viewData.enableBack) {
  10120. var sideMenuCtrl = $element.inheritedData('$ionSideMenusController');
  10121. if (!sideMenuCtrl.enableMenuWithBackViews()) {
  10122. $element.addClass('hide');
  10123. }
  10124. } else {
  10125. $element.removeClass('hide');
  10126. }
  10127. });
  10128. $element.bind('click', function() {
  10129. var sideMenuCtrl = $element.inheritedData('$ionSideMenusController');
  10130. sideMenuCtrl && sideMenuCtrl.toggle($attr.menuToggle);
  10131. });
  10132. }
  10133. };
  10134. });
  10135. /*
  10136. * We don't document the ionModal directive, we instead document
  10137. * the $ionicModal service
  10138. */
  10139. IonicModule
  10140. .directive('ionModal', [function() {
  10141. return {
  10142. restrict: 'E',
  10143. transclude: true,
  10144. replace: true,
  10145. controller: [function() {}],
  10146. template: '<div class="modal-backdrop">' +
  10147. '<div class="modal-backdrop-bg"></div>' +
  10148. '<div class="modal-wrapper" ng-transclude></div>' +
  10149. '</div>'
  10150. };
  10151. }]);
  10152. IonicModule
  10153. .directive('ionModalView', function() {
  10154. return {
  10155. restrict: 'E',
  10156. compile: function(element) {
  10157. element.addClass('modal');
  10158. }
  10159. };
  10160. });
  10161. /**
  10162. * @ngdoc directive
  10163. * @name ionNavBackButton
  10164. * @module ionic
  10165. * @restrict E
  10166. * @parent ionNavBar
  10167. * @description
  10168. * Creates a back button inside an {@link ionic.directive:ionNavBar}.
  10169. *
  10170. * The back button will appear when the user is able to go back in the current navigation stack. By
  10171. * default, the markup of the back button is automatically built using platform-appropriate defaults
  10172. * (iOS back button icon on iOS and Android icon on Android).
  10173. *
  10174. * Additionally, the button is automatically set to `$ionicGoBack()` on click/tap. By default, the
  10175. * app will navigate back one view when the back button is clicked. More advanced behavior is also
  10176. * possible, as outlined below.
  10177. *
  10178. * @usage
  10179. *
  10180. * Recommended markup for default settings:
  10181. *
  10182. * ```html
  10183. * <ion-nav-bar>
  10184. * <ion-nav-back-button>
  10185. * </ion-nav-back-button>
  10186. * </ion-nav-bar>
  10187. * ```
  10188. *
  10189. * With custom inner markup, and automatically adds a default click action:
  10190. *
  10191. * ```html
  10192. * <ion-nav-bar>
  10193. * <ion-nav-back-button class="button-clear">
  10194. * <i class="ion-arrow-left-c"></i> Back
  10195. * </ion-nav-back-button>
  10196. * </ion-nav-bar>
  10197. * ```
  10198. *
  10199. * With custom inner markup and custom click action, using {@link ionic.service:$ionicHistory}:
  10200. *
  10201. * ```html
  10202. * <ion-nav-bar ng-controller="MyCtrl">
  10203. * <ion-nav-back-button class="button-clear"
  10204. * ng-click="myGoBack()">
  10205. * <i class="ion-arrow-left-c"></i> Back
  10206. * </ion-nav-back-button>
  10207. * </ion-nav-bar>
  10208. * ```
  10209. * ```js
  10210. * function MyCtrl($scope, $ionicHistory) {
  10211. * $scope.myGoBack = function() {
  10212. * $ionicHistory.goBack();
  10213. * };
  10214. * }
  10215. * ```
  10216. */
  10217. IonicModule
  10218. .directive('ionNavBackButton', ['$ionicConfig', '$document', function($ionicConfig, $document) {
  10219. return {
  10220. restrict: 'E',
  10221. require: '^ionNavBar',
  10222. compile: function(tElement, tAttrs) {
  10223. // clone the back button, but as a <div>
  10224. var buttonEle = $document[0].createElement('button');
  10225. for (var n in tAttrs.$attr) {
  10226. buttonEle.setAttribute(tAttrs.$attr[n], tAttrs[n]);
  10227. }
  10228. if (!tAttrs.ngClick) {
  10229. buttonEle.setAttribute('ng-click', '$ionicGoBack()');
  10230. }
  10231. buttonEle.className = 'button back-button hide buttons ' + (tElement.attr('class') || '');
  10232. buttonEle.innerHTML = tElement.html() || '';
  10233. var childNode;
  10234. var hasIcon = hasIconClass(tElement[0]);
  10235. var hasInnerText;
  10236. var hasButtonText;
  10237. var hasPreviousTitle;
  10238. for (var x = 0; x < tElement[0].childNodes.length; x++) {
  10239. childNode = tElement[0].childNodes[x];
  10240. if (childNode.nodeType === 1) {
  10241. if (hasIconClass(childNode)) {
  10242. hasIcon = true;
  10243. } else if (childNode.classList.contains('default-title')) {
  10244. hasButtonText = true;
  10245. } else if (childNode.classList.contains('previous-title')) {
  10246. hasPreviousTitle = true;
  10247. }
  10248. } else if (!hasInnerText && childNode.nodeType === 3) {
  10249. hasInnerText = !!childNode.nodeValue.trim();
  10250. }
  10251. }
  10252. function hasIconClass(ele) {
  10253. return /ion-|icon/.test(ele.className);
  10254. }
  10255. var defaultIcon = $ionicConfig.backButton.icon();
  10256. if (!hasIcon && defaultIcon && defaultIcon !== 'none') {
  10257. buttonEle.innerHTML = '<i class="icon ' + defaultIcon + '"></i> ' + buttonEle.innerHTML;
  10258. buttonEle.className += ' button-clear';
  10259. }
  10260. if (!hasInnerText) {
  10261. var buttonTextEle = $document[0].createElement('span');
  10262. buttonTextEle.className = 'back-text';
  10263. if (!hasButtonText && $ionicConfig.backButton.text()) {
  10264. buttonTextEle.innerHTML += '<span class="default-title">' + $ionicConfig.backButton.text() + '</span>';
  10265. }
  10266. if (!hasPreviousTitle && $ionicConfig.backButton.previousTitleText()) {
  10267. buttonTextEle.innerHTML += '<span class="previous-title"></span>';
  10268. }
  10269. buttonEle.appendChild(buttonTextEle);
  10270. }
  10271. tElement.attr('class', 'hide');
  10272. tElement.empty();
  10273. return {
  10274. pre: function($scope, $element, $attr, navBarCtrl) {
  10275. // only register the plain HTML, the navBarCtrl takes care of scope/compile/link
  10276. navBarCtrl.navElement('backButton', buttonEle.outerHTML);
  10277. buttonEle = null;
  10278. }
  10279. };
  10280. }
  10281. };
  10282. }]);
  10283. /**
  10284. * @ngdoc directive
  10285. * @name ionNavBar
  10286. * @module ionic
  10287. * @delegate ionic.service:$ionicNavBarDelegate
  10288. * @restrict E
  10289. *
  10290. * @description
  10291. * If we have an {@link ionic.directive:ionNavView} directive, we can also create an
  10292. * `<ion-nav-bar>`, which will create a topbar that updates as the application state changes.
  10293. *
  10294. * We can add a back button by putting an {@link ionic.directive:ionNavBackButton} inside.
  10295. *
  10296. * We can add buttons depending on the currently visible view using
  10297. * {@link ionic.directive:ionNavButtons}.
  10298. *
  10299. * Note that the ion-nav-bar element will only work correctly if your content has an
  10300. * ionView around it.
  10301. *
  10302. * @usage
  10303. *
  10304. * ```html
  10305. * <body ng-app="starter">
  10306. * <!-- The nav bar that will be updated as we navigate -->
  10307. * <ion-nav-bar class="bar-positive">
  10308. * </ion-nav-bar>
  10309. *
  10310. * <!-- where the initial view template will be rendered -->
  10311. * <ion-nav-view>
  10312. * <ion-view>
  10313. * <ion-content>Hello!</ion-content>
  10314. * </ion-view>
  10315. * </ion-nav-view>
  10316. * </body>
  10317. * ```
  10318. *
  10319. * @param {string=} delegate-handle The handle used to identify this navBar
  10320. * with {@link ionic.service:$ionicNavBarDelegate}.
  10321. * @param align-title {string=} Where to align the title of the navbar.
  10322. * Available: 'left', 'right', 'center'. Defaults to 'center'.
  10323. * @param {boolean=} no-tap-scroll By default, the navbar will scroll the content
  10324. * to the top when tapped. Set no-tap-scroll to true to disable this behavior.
  10325. *
  10326. */
  10327. IonicModule
  10328. .directive('ionNavBar', function() {
  10329. return {
  10330. restrict: 'E',
  10331. controller: '$ionicNavBar',
  10332. scope: true,
  10333. link: function($scope, $element, $attr, ctrl) {
  10334. ctrl.init();
  10335. }
  10336. };
  10337. });
  10338. /**
  10339. * @ngdoc directive
  10340. * @name ionNavButtons
  10341. * @module ionic
  10342. * @restrict E
  10343. * @parent ionNavView
  10344. *
  10345. * @description
  10346. * Use nav buttons to set the buttons on your {@link ionic.directive:ionNavBar}
  10347. * from within an {@link ionic.directive:ionView}. This gives each
  10348. * view template the ability to specify which buttons should show in the nav bar,
  10349. * overriding any default buttons already placed in the nav bar.
  10350. *
  10351. * Any buttons you declare will be positioned on the navbar's corresponding side. Primary
  10352. * buttons generally map to the left side of the header, and secondary buttons are
  10353. * generally on the right side. However, their exact locations are platform-specific.
  10354. * For example, in iOS, the primary buttons are on the far left of the header, and
  10355. * secondary buttons are on the far right, with the header title centered between them.
  10356. * For Android, however, both groups of buttons are on the far right of the header,
  10357. * with the header title aligned left.
  10358. *
  10359. * We recommend always using `primary` and `secondary`, so the buttons correctly map
  10360. * to the side familiar to users of each platform. However, in cases where buttons should
  10361. * always be on an exact side, both `left` and `right` sides are still available. For
  10362. * example, a toggle button for a left side menu should be on the left side; in this case,
  10363. * we'd recommend using `side="left"`, so it's always on the left, no matter the platform.
  10364. *
  10365. * ***Note*** that `ion-nav-buttons` must be immediate descendants of the `ion-view` or
  10366. * `ion-nav-bar` element (basically, don't wrap it in another div).
  10367. *
  10368. * @usage
  10369. * ```html
  10370. * <ion-nav-bar>
  10371. * </ion-nav-bar>
  10372. * <ion-nav-view>
  10373. * <ion-view>
  10374. * <ion-nav-buttons side="primary">
  10375. * <button class="button" ng-click="doSomething()">
  10376. * I'm a button on the primary of the navbar!
  10377. * </button>
  10378. * </ion-nav-buttons>
  10379. * <ion-content>
  10380. * Some super content here!
  10381. * </ion-content>
  10382. * </ion-view>
  10383. * </ion-nav-view>
  10384. * ```
  10385. *
  10386. * @param {string} side The side to place the buttons in the
  10387. * {@link ionic.directive:ionNavBar}. Available sides: `primary`, `secondary`, `left`, and `right`.
  10388. */
  10389. IonicModule
  10390. .directive('ionNavButtons', ['$document', function($document) {
  10391. return {
  10392. require: '^ionNavBar',
  10393. restrict: 'E',
  10394. compile: function(tElement, tAttrs) {
  10395. var side = 'left';
  10396. if (/^primary|secondary|right$/i.test(tAttrs.side || '')) {
  10397. side = tAttrs.side.toLowerCase();
  10398. }
  10399. var spanEle = $document[0].createElement('span');
  10400. spanEle.className = side + '-buttons';
  10401. spanEle.innerHTML = tElement.html();
  10402. var navElementType = side + 'Buttons';
  10403. tElement.attr('class', 'hide');
  10404. tElement.empty();
  10405. return {
  10406. pre: function($scope, $element, $attrs, navBarCtrl) {
  10407. // only register the plain HTML, the navBarCtrl takes care of scope/compile/link
  10408. var parentViewCtrl = $element.parent().data('$ionViewController');
  10409. if (parentViewCtrl) {
  10410. // if the parent is an ion-view, then these are ion-nav-buttons for JUST this ion-view
  10411. parentViewCtrl.navElement(navElementType, spanEle.outerHTML);
  10412. } else {
  10413. // these are buttons for all views that do not have their own ion-nav-buttons
  10414. navBarCtrl.navElement(navElementType, spanEle.outerHTML);
  10415. }
  10416. spanEle = null;
  10417. }
  10418. };
  10419. }
  10420. };
  10421. }]);
  10422. /**
  10423. * @ngdoc directive
  10424. * @name navDirection
  10425. * @module ionic
  10426. * @restrict A
  10427. *
  10428. * @description
  10429. * The direction which the nav view transition should animate. Available options
  10430. * are: `forward`, `back`, `enter`, `exit`, `swap`.
  10431. *
  10432. * @usage
  10433. *
  10434. * ```html
  10435. * <a nav-direction="forward" href="#/home">Home</a>
  10436. * ```
  10437. */
  10438. IonicModule
  10439. .directive('navDirection', ['$ionicViewSwitcher', function($ionicViewSwitcher) {
  10440. return {
  10441. restrict: 'A',
  10442. priority: 1000,
  10443. link: function($scope, $element, $attr) {
  10444. $element.bind('click', function() {
  10445. $ionicViewSwitcher.nextDirection($attr.navDirection);
  10446. });
  10447. }
  10448. };
  10449. }]);
  10450. /**
  10451. * @ngdoc directive
  10452. * @name ionNavTitle
  10453. * @module ionic
  10454. * @restrict E
  10455. * @parent ionNavView
  10456. *
  10457. * @description
  10458. *
  10459. * The nav title directive replaces an {@link ionic.directive:ionNavBar} title text with
  10460. * custom HTML from within an {@link ionic.directive:ionView} template. This gives each
  10461. * view the ability to specify its own custom title element, such as an image or any HTML,
  10462. * rather than being text-only. Alternatively, text-only titles can be updated using the
  10463. * `view-title` {@link ionic.directive:ionView} attribute.
  10464. *
  10465. * Note that `ion-nav-title` must be an immediate descendant of the `ion-view` or
  10466. * `ion-nav-bar` element (basically don't wrap it in another div).
  10467. *
  10468. * @usage
  10469. * ```html
  10470. * <ion-nav-bar>
  10471. * </ion-nav-bar>
  10472. * <ion-nav-view>
  10473. * <ion-view>
  10474. * <ion-nav-title>
  10475. * <img src="logo.svg">
  10476. * </ion-nav-title>
  10477. * <ion-content>
  10478. * Some super content here!
  10479. * </ion-content>
  10480. * </ion-view>
  10481. * </ion-nav-view>
  10482. * ```
  10483. *
  10484. */
  10485. IonicModule
  10486. .directive('ionNavTitle', ['$document', function($document) {
  10487. return {
  10488. require: '^ionNavBar',
  10489. restrict: 'E',
  10490. compile: function(tElement, tAttrs) {
  10491. var navElementType = 'title';
  10492. var spanEle = $document[0].createElement('span');
  10493. for (var n in tAttrs.$attr) {
  10494. spanEle.setAttribute(tAttrs.$attr[n], tAttrs[n]);
  10495. }
  10496. spanEle.classList.add('nav-bar-title');
  10497. spanEle.innerHTML = tElement.html();
  10498. tElement.attr('class', 'hide');
  10499. tElement.empty();
  10500. return {
  10501. pre: function($scope, $element, $attrs, navBarCtrl) {
  10502. // only register the plain HTML, the navBarCtrl takes care of scope/compile/link
  10503. var parentViewCtrl = $element.parent().data('$ionViewController');
  10504. if (parentViewCtrl) {
  10505. // if the parent is an ion-view, then these are ion-nav-buttons for JUST this ion-view
  10506. parentViewCtrl.navElement(navElementType, spanEle.outerHTML);
  10507. } else {
  10508. // these are buttons for all views that do not have their own ion-nav-buttons
  10509. navBarCtrl.navElement(navElementType, spanEle.outerHTML);
  10510. }
  10511. spanEle = null;
  10512. }
  10513. };
  10514. }
  10515. };
  10516. }]);
  10517. /**
  10518. * @ngdoc directive
  10519. * @name navTransition
  10520. * @module ionic
  10521. * @restrict A
  10522. *
  10523. * @description
  10524. * The transition type which the nav view transition should use when it animates.
  10525. * Current, options are `ios`, `android`, and `none`. More options coming soon.
  10526. *
  10527. * @usage
  10528. *
  10529. * ```html
  10530. * <a nav-transition="none" href="#/home">Home</a>
  10531. * ```
  10532. */
  10533. IonicModule
  10534. .directive('navTransition', ['$ionicViewSwitcher', function($ionicViewSwitcher) {
  10535. return {
  10536. restrict: 'A',
  10537. priority: 1000,
  10538. link: function($scope, $element, $attr) {
  10539. $element.bind('click', function() {
  10540. $ionicViewSwitcher.nextTransition($attr.navTransition);
  10541. });
  10542. }
  10543. };
  10544. }]);
  10545. /**
  10546. * @ngdoc directive
  10547. * @name ionNavView
  10548. * @module ionic
  10549. * @restrict E
  10550. * @codepen odqCz
  10551. *
  10552. * @description
  10553. * As a user navigates throughout your app, Ionic is able to keep track of their
  10554. * navigation history. By knowing their history, transitions between views
  10555. * correctly enter and exit using the platform's transition style. An additional
  10556. * benefit to Ionic's navigation system is its ability to manage multiple
  10557. * histories. For example, each tab can have it's own navigation history stack.
  10558. *
  10559. * Ionic uses the AngularUI Router module so app interfaces can be organized
  10560. * into various "states". Like Angular's core $route service, URLs can be used
  10561. * to control the views. However, the AngularUI Router provides a more powerful
  10562. * state manager in that states are bound to named, nested, and parallel views,
  10563. * allowing more than one template to be rendered on the same page.
  10564. * Additionally, each state is not required to be bound to a URL, and data can
  10565. * be pushed to each state which allows much flexibility.
  10566. *
  10567. * The ionNavView directive is used to render templates in your application. Each template
  10568. * is part of a state. States are usually mapped to a url, and are defined programatically
  10569. * using angular-ui-router (see [their docs](https://github.com/angular-ui/ui-router/wiki),
  10570. * and remember to replace ui-view with ion-nav-view in examples).
  10571. *
  10572. * @usage
  10573. * In this example, we will create a navigation view that contains our different states for the app.
  10574. *
  10575. * To do this, in our markup we use ionNavView top level directive. To display a header bar we use
  10576. * the {@link ionic.directive:ionNavBar} directive that updates as we navigate through the
  10577. * navigation stack.
  10578. *
  10579. * Next, we need to setup our states that will be rendered.
  10580. *
  10581. * ```js
  10582. * var app = angular.module('myApp', ['ionic']);
  10583. * app.config(function($stateProvider) {
  10584. * $stateProvider
  10585. * .state('index', {
  10586. * url: '/',
  10587. * templateUrl: 'home.html'
  10588. * })
  10589. * .state('music', {
  10590. * url: '/music',
  10591. * templateUrl: 'music.html'
  10592. * });
  10593. * });
  10594. * ```
  10595. * Then on app start, $stateProvider will look at the url, see if it matches the index state,
  10596. * and then try to load home.html into the `<ion-nav-view>`.
  10597. *
  10598. * Pages are loaded by the URLs given. One simple way to create templates in Angular is to put
  10599. * them directly into your HTML file and use the `<script type="text/ng-template">` syntax.
  10600. * So here is one way to put home.html into our app:
  10601. *
  10602. * ```html
  10603. * <script id="home" type="text/ng-template">
  10604. * <!-- The title of the ion-view will be shown on the navbar -->
  10605. * <ion-view view-title="Home">
  10606. * <ion-content ng-controller="HomeCtrl">
  10607. * <!-- The content of the page -->
  10608. * <a href="#/music">Go to music page!</a>
  10609. * </ion-content>
  10610. * </ion-view>
  10611. * </script>
  10612. * ```
  10613. *
  10614. * This is good to do because the template will be cached for very fast loading, instead of
  10615. * having to fetch them from the network.
  10616. *
  10617. * ## Caching
  10618. *
  10619. * By default, views are cached to improve performance. When a view is navigated away from, its
  10620. * element is left in the DOM, and its scope is disconnected from the `$watch` cycle. When
  10621. * navigating to a view that is already cached, its scope is then reconnected, and the existing
  10622. * element that was left in the DOM becomes the active view. This also allows for the scroll
  10623. * position of previous views to be maintained.
  10624. *
  10625. * Caching can be disabled and enabled in multiple ways. By default, Ionic will cache a maximum of
  10626. * 10 views, and not only can this be configured, but apps can also explicitly state which views
  10627. * should and should not be cached.
  10628. *
  10629. * Note that because we are caching these views, *we aren’t destroying scopes*. Instead, scopes
  10630. * are being disconnected from the watch cycle. Because scopes are not being destroyed and
  10631. * recreated, controllers are not loading again on a subsequent viewing. If the app/controller
  10632. * needs to know when a view has entered or has left, then view events emitted from the
  10633. * {@link ionic.directive:ionView} scope, such as `$ionicView.enter`, may be useful.
  10634. *
  10635. * By default, when navigating back in the history, the "forward" views are removed from the cache.
  10636. * If you navigate forward to the same view again, it'll create a new DOM element and controller
  10637. * instance. Basically, any forward views are reset each time. This can be configured using the
  10638. * {@link ionic.provider:$ionicConfigProvider}:
  10639. *
  10640. * ```js
  10641. * $ionicConfigProvider.views.forwardCache(true);
  10642. * ```
  10643. *
  10644. * #### Disable cache globally
  10645. *
  10646. * The {@link ionic.provider:$ionicConfigProvider} can be used to set the maximum allowable views
  10647. * which can be cached, but this can also be use to disable all caching by setting it to 0.
  10648. *
  10649. * ```js
  10650. * $ionicConfigProvider.views.maxCache(0);
  10651. * ```
  10652. *
  10653. * #### Disable cache within state provider
  10654. *
  10655. * ```js
  10656. * $stateProvider.state('myState', {
  10657. * cache: false,
  10658. * url : '/myUrl',
  10659. * templateUrl : 'my-template.html'
  10660. * })
  10661. * ```
  10662. *
  10663. * #### Disable cache with an attribute
  10664. *
  10665. * ```html
  10666. * <ion-view cache-view="false" view-title="My Title!">
  10667. * ...
  10668. * </ion-view>
  10669. * ```
  10670. *
  10671. *
  10672. * ## AngularUI Router
  10673. *
  10674. * Please visit [AngularUI Router's docs](https://github.com/angular-ui/ui-router/wiki) for
  10675. * more info. Below is a great video by the AngularUI Router team that may help to explain
  10676. * how it all works:
  10677. *
  10678. * <iframe width="560" height="315" src="//www.youtube.com/embed/dqJRoh8MnBo"
  10679. * frameborder="0" allowfullscreen></iframe>
  10680. *
  10681. * Note: We do not recommend using [resolve](https://github.com/angular-ui/ui-router/wiki#resolve)
  10682. * of AngularUI Router. The recommended approach is to execute any logic needed before beginning the state transition.
  10683. *
  10684. * @param {string=} name A view name. The name should be unique amongst the other views in the
  10685. * same state. You can have views of the same name that live in different states. For more
  10686. * information, see ui-router's
  10687. * [ui-view documentation](http://angular-ui.github.io/ui-router/site/#/api/ui.router.state.directive:ui-view).
  10688. */
  10689. IonicModule
  10690. .directive('ionNavView', [
  10691. '$state',
  10692. '$ionicConfig',
  10693. function($state, $ionicConfig) {
  10694. // IONIC's fork of Angular UI Router, v0.2.10
  10695. // the navView handles registering views in the history and how to transition between them
  10696. return {
  10697. restrict: 'E',
  10698. terminal: true,
  10699. priority: 2000,
  10700. transclude: true,
  10701. controller: '$ionicNavView',
  10702. compile: function(tElement, tAttrs, transclude) {
  10703. // a nav view element is a container for numerous views
  10704. tElement.addClass('view-container');
  10705. ionic.DomUtil.cachedAttr(tElement, 'nav-view-transition', $ionicConfig.views.transition());
  10706. return function($scope, $element, $attr, navViewCtrl) {
  10707. var latestLocals;
  10708. // Put in the compiled initial view
  10709. transclude($scope, function(clone) {
  10710. $element.append(clone);
  10711. });
  10712. var viewData = navViewCtrl.init();
  10713. // listen for $stateChangeSuccess
  10714. $scope.$on('$stateChangeSuccess', function() {
  10715. updateView(false);
  10716. });
  10717. $scope.$on('$viewContentLoading', function() {
  10718. updateView(false);
  10719. });
  10720. // initial load, ready go
  10721. updateView(true);
  10722. function updateView(firstTime) {
  10723. // get the current local according to the $state
  10724. var viewLocals = $state.$current && $state.$current.locals[viewData.name];
  10725. // do not update THIS nav-view if its is not the container for the given state
  10726. // if the viewLocals are the same as THIS latestLocals, then nothing to do
  10727. if (!viewLocals || (!firstTime && viewLocals === latestLocals)) return;
  10728. // update the latestLocals
  10729. latestLocals = viewLocals;
  10730. viewData.state = viewLocals.$$state;
  10731. // register, update and transition to the new view
  10732. navViewCtrl.register(viewLocals);
  10733. }
  10734. };
  10735. }
  10736. };
  10737. }]);
  10738. IonicModule
  10739. .config(['$provide', function($provide) {
  10740. $provide.decorator('ngClickDirective', ['$delegate', function($delegate) {
  10741. // drop the default ngClick directive
  10742. $delegate.shift();
  10743. return $delegate;
  10744. }]);
  10745. }])
  10746. /**
  10747. * @private
  10748. */
  10749. .factory('$ionicNgClick', ['$parse', function($parse) {
  10750. return function(scope, element, clickExpr) {
  10751. var clickHandler = angular.isFunction(clickExpr) ?
  10752. clickExpr :
  10753. $parse(clickExpr);
  10754. element.on('click', function(event) {
  10755. scope.$apply(function() {
  10756. clickHandler(scope, {$event: (event)});
  10757. });
  10758. });
  10759. // Hack for iOS Safari's benefit. It goes searching for onclick handlers and is liable to click
  10760. // something else nearby.
  10761. element.onclick = noop;
  10762. };
  10763. }])
  10764. .directive('ngClick', ['$ionicNgClick', function($ionicNgClick) {
  10765. return function(scope, element, attr) {
  10766. $ionicNgClick(scope, element, attr.ngClick);
  10767. };
  10768. }])
  10769. .directive('ionStopEvent', function() {
  10770. return {
  10771. restrict: 'A',
  10772. link: function(scope, element, attr) {
  10773. element.bind(attr.ionStopEvent, eventStopPropagation);
  10774. }
  10775. };
  10776. });
  10777. function eventStopPropagation(e) {
  10778. e.stopPropagation();
  10779. }
  10780. /**
  10781. * @ngdoc directive
  10782. * @name ionPane
  10783. * @module ionic
  10784. * @restrict E
  10785. *
  10786. * @description A simple container that fits content, with no side effects. Adds the 'pane' class to the element.
  10787. */
  10788. IonicModule
  10789. .directive('ionPane', function() {
  10790. return {
  10791. restrict: 'E',
  10792. link: function(scope, element) {
  10793. element.addClass('pane');
  10794. }
  10795. };
  10796. });
  10797. /*
  10798. * We don't document the ionPopover directive, we instead document
  10799. * the $ionicPopover service
  10800. */
  10801. IonicModule
  10802. .directive('ionPopover', [function() {
  10803. return {
  10804. restrict: 'E',
  10805. transclude: true,
  10806. replace: true,
  10807. controller: [function() {}],
  10808. template: '<div class="popover-backdrop">' +
  10809. '<div class="popover-wrapper" ng-transclude></div>' +
  10810. '</div>'
  10811. };
  10812. }]);
  10813. IonicModule
  10814. .directive('ionPopoverView', function() {
  10815. return {
  10816. restrict: 'E',
  10817. compile: function(element) {
  10818. element.append(jqLite('<div class="popover-arrow">'));
  10819. element.addClass('popover');
  10820. }
  10821. };
  10822. });
  10823. /**
  10824. * @ngdoc directive
  10825. * @name ionRadio
  10826. * @module ionic
  10827. * @restrict E
  10828. * @codepen saoBG
  10829. * @description
  10830. * The radio directive is no different than the HTML radio input, except it's styled differently.
  10831. *
  10832. * Radio behaves like [AngularJS radio](http://docs.angularjs.org/api/ng/input/input[radio]).
  10833. *
  10834. * @usage
  10835. * ```html
  10836. * <ion-radio ng-model="choice" ng-value="'A'">Choose A</ion-radio>
  10837. * <ion-radio ng-model="choice" ng-value="'B'">Choose B</ion-radio>
  10838. * <ion-radio ng-model="choice" ng-value="'C'">Choose C</ion-radio>
  10839. * ```
  10840. *
  10841. * @param {string=} name The name of the radio input.
  10842. * @param {expression=} value The value of the radio input.
  10843. * @param {boolean=} disabled The state of the radio input.
  10844. * @param {string=} icon The icon to use when the radio input is selected.
  10845. * @param {expression=} ng-value Angular equivalent of the value attribute.
  10846. * @param {expression=} ng-model The angular model for the radio input.
  10847. * @param {boolean=} ng-disabled Angular equivalent of the disabled attribute.
  10848. * @param {expression=} ng-change Triggers given expression when radio input's model changes
  10849. */
  10850. IonicModule
  10851. .directive('ionRadio', function() {
  10852. return {
  10853. restrict: 'E',
  10854. replace: true,
  10855. require: '?ngModel',
  10856. transclude: true,
  10857. template:
  10858. '<label class="item item-radio">' +
  10859. '<input type="radio" name="radio-group">' +
  10860. '<div class="radio-content">' +
  10861. '<div class="item-content disable-pointer-events" ng-transclude></div>' +
  10862. '<i class="radio-icon disable-pointer-events icon ion-checkmark"></i>' +
  10863. '</div>' +
  10864. '</label>',
  10865. compile: function(element, attr) {
  10866. if (attr.icon) {
  10867. var iconElm = element.find('i');
  10868. iconElm.removeClass('ion-checkmark').addClass(attr.icon);
  10869. }
  10870. var input = element.find('input');
  10871. forEach({
  10872. 'name': attr.name,
  10873. 'value': attr.value,
  10874. 'disabled': attr.disabled,
  10875. 'ng-value': attr.ngValue,
  10876. 'ng-model': attr.ngModel,
  10877. 'ng-disabled': attr.ngDisabled,
  10878. 'ng-change': attr.ngChange,
  10879. 'ng-required': attr.ngRequired,
  10880. 'required': attr.required
  10881. }, function(value, name) {
  10882. if (isDefined(value)) {
  10883. input.attr(name, value);
  10884. }
  10885. });
  10886. return function(scope, element, attr) {
  10887. scope.getValue = function() {
  10888. return scope.ngValue || attr.value;
  10889. };
  10890. };
  10891. }
  10892. };
  10893. });
  10894. /**
  10895. * @ngdoc directive
  10896. * @name ionRefresher
  10897. * @module ionic
  10898. * @restrict E
  10899. * @parent ionic.directive:ionContent, ionic.directive:ionScroll
  10900. * @description
  10901. * Allows you to add pull-to-refresh to a scrollView.
  10902. *
  10903. * Place it as the first child of your {@link ionic.directive:ionContent} or
  10904. * {@link ionic.directive:ionScroll} element.
  10905. *
  10906. * When refreshing is complete, $broadcast the 'scroll.refreshComplete' event
  10907. * from your controller.
  10908. *
  10909. * @usage
  10910. *
  10911. * ```html
  10912. * <ion-content ng-controller="MyController">
  10913. * <ion-refresher
  10914. * pulling-text="Pull to refresh..."
  10915. * on-refresh="doRefresh()">
  10916. * </ion-refresher>
  10917. * <ion-list>
  10918. * <ion-item ng-repeat="item in items"></ion-item>
  10919. * </ion-list>
  10920. * </ion-content>
  10921. * ```
  10922. * ```js
  10923. * angular.module('testApp', ['ionic'])
  10924. * .controller('MyController', function($scope, $http) {
  10925. * $scope.items = [1,2,3];
  10926. * $scope.doRefresh = function() {
  10927. * $http.get('/new-items')
  10928. * .success(function(newItems) {
  10929. * $scope.items = newItems;
  10930. * })
  10931. * .finally(function() {
  10932. * // Stop the ion-refresher from spinning
  10933. * $scope.$broadcast('scroll.refreshComplete');
  10934. * });
  10935. * };
  10936. * });
  10937. * ```
  10938. *
  10939. * @param {expression=} on-refresh Called when the user pulls down enough and lets go
  10940. * of the refresher.
  10941. * @param {expression=} on-pulling Called when the user starts to pull down
  10942. * on the refresher.
  10943. * @param {string=} pulling-text The text to display while the user is pulling down.
  10944. * @param {string=} pulling-icon The icon to display while the user is pulling down.
  10945. * Default: 'ion-android-arrow-down'.
  10946. * @param {string=} spinner The {@link ionic.directive:ionSpinner} icon to display
  10947. * after user lets go of the refresher. The SVG {@link ionic.directive:ionSpinner}
  10948. * is now the default, replacing rotating font icons. Set to `none` to disable both the
  10949. * spinner and the icon.
  10950. * @param {string=} refreshing-icon The font icon to display after user lets go of the
  10951. * refresher. This is deprecated in favor of the SVG {@link ionic.directive:ionSpinner}.
  10952. * @param {boolean=} disable-pulling-rotation Disables the rotation animation of the pulling
  10953. * icon when it reaches its activated threshold. To be used with a custom `pulling-icon`.
  10954. *
  10955. */
  10956. IonicModule
  10957. .directive('ionRefresher', [function() {
  10958. return {
  10959. restrict: 'E',
  10960. replace: true,
  10961. require: ['?^$ionicScroll', 'ionRefresher'],
  10962. controller: '$ionicRefresher',
  10963. template:
  10964. '<div class="scroll-refresher invisible" collection-repeat-ignore>' +
  10965. '<div class="ionic-refresher-content" ' +
  10966. 'ng-class="{\'ionic-refresher-with-text\': pullingText || refreshingText}">' +
  10967. '<div class="icon-pulling" ng-class="{\'pulling-rotation-disabled\':disablePullingRotation}">' +
  10968. '<i class="icon {{pullingIcon}}"></i>' +
  10969. '</div>' +
  10970. '<div class="text-pulling" ng-bind-html="pullingText"></div>' +
  10971. '<div class="icon-refreshing">' +
  10972. '<ion-spinner ng-if="showSpinner" icon="{{spinner}}"></ion-spinner>' +
  10973. '<i ng-if="showIcon" class="icon {{refreshingIcon}}"></i>' +
  10974. '</div>' +
  10975. '<div class="text-refreshing" ng-bind-html="refreshingText"></div>' +
  10976. '</div>' +
  10977. '</div>',
  10978. link: function($scope, $element, $attrs, ctrls) {
  10979. // JS Scrolling uses the scroll controller
  10980. var scrollCtrl = ctrls[0],
  10981. refresherCtrl = ctrls[1];
  10982. if (!scrollCtrl || scrollCtrl.isNative()) {
  10983. // Kick off native scrolling
  10984. refresherCtrl.init();
  10985. } else {
  10986. $element[0].classList.add('js-scrolling');
  10987. scrollCtrl._setRefresher(
  10988. $scope,
  10989. $element[0],
  10990. refresherCtrl.getRefresherDomMethods()
  10991. );
  10992. $scope.$on('scroll.refreshComplete', function() {
  10993. $scope.$evalAsync(function() {
  10994. if(scrollCtrl.scrollView){
  10995. scrollCtrl.scrollView.finishPullToRefresh();
  10996. }
  10997. });
  10998. });
  10999. }
  11000. }
  11001. };
  11002. }]);
  11003. /**
  11004. * @ngdoc directive
  11005. * @name ionScroll
  11006. * @module ionic
  11007. * @delegate ionic.service:$ionicScrollDelegate
  11008. * @codepen mwFuh
  11009. * @restrict E
  11010. *
  11011. * @description
  11012. * Creates a scrollable container for all content inside.
  11013. *
  11014. * @usage
  11015. *
  11016. * Basic usage:
  11017. *
  11018. * ```html
  11019. * <ion-scroll zooming="true" direction="xy" style="width: 500px; height: 500px">
  11020. * <div style="width: 5000px; height: 5000px; background: url('https://upload.wikimedia.org/wikipedia/commons/a/ad/Europe_geological_map-en.jpg') repeat"></div>
  11021. * </ion-scroll>
  11022. * ```
  11023. *
  11024. * Note that it's important to set the height of the scroll box as well as the height of the inner
  11025. * content to enable scrolling. This makes it possible to have full control over scrollable areas.
  11026. *
  11027. * If you'd just like to have a center content scrolling area, use {@link ionic.directive:ionContent} instead.
  11028. *
  11029. * @param {string=} delegate-handle The handle used to identify this scrollView
  11030. * with {@link ionic.service:$ionicScrollDelegate}.
  11031. * @param {string=} direction Which way to scroll. 'x' or 'y' or 'xy'. Default 'y'.
  11032. * @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.
  11033. * @param {boolean=} paging Whether to scroll with paging.
  11034. * @param {expression=} on-refresh Called on pull-to-refresh, triggered by an {@link ionic.directive:ionRefresher}.
  11035. * @param {expression=} on-scroll Called whenever the user scrolls.
  11036. * @param {expression=} on-scroll-complete Called whenever the scrolling paging is completed.
  11037. * @param {boolean=} scrollbar-x Whether to show the horizontal scrollbar. Default true.
  11038. * @param {boolean=} scrollbar-y Whether to show the vertical scrollbar. Default true.
  11039. * @param {boolean=} zooming Whether to support pinch-to-zoom
  11040. * @param {integer=} min-zoom The smallest zoom amount allowed (default is 0.5)
  11041. * @param {integer=} max-zoom The largest zoom amount allowed (default is 3)
  11042. * @param {boolean=} has-bouncing Whether to allow scrolling to bounce past the edges
  11043. * of the content. Defaults to true on iOS, false on Android.
  11044. */
  11045. IonicModule
  11046. .directive('ionScroll', [
  11047. '$timeout',
  11048. '$controller',
  11049. '$ionicBind',
  11050. '$ionicConfig',
  11051. function($timeout, $controller, $ionicBind, $ionicConfig) {
  11052. return {
  11053. restrict: 'E',
  11054. scope: true,
  11055. controller: function() {},
  11056. compile: function(element, attr) {
  11057. element.addClass('scroll-view ionic-scroll');
  11058. //We cannot transclude here because it breaks element.data() inheritance on compile
  11059. var innerElement = jqLite('<div class="scroll"></div>');
  11060. innerElement.append(element.contents());
  11061. element.append(innerElement);
  11062. var nativeScrolling = attr.overflowScroll !== "false" && (attr.overflowScroll === "true" || !$ionicConfig.scrolling.jsScrolling());
  11063. return { pre: prelink };
  11064. function prelink($scope, $element, $attr) {
  11065. $ionicBind($scope, $attr, {
  11066. direction: '@',
  11067. paging: '@',
  11068. $onScroll: '&onScroll',
  11069. $onScrollComplete: '&onScrollComplete',
  11070. scroll: '@',
  11071. scrollbarX: '@',
  11072. scrollbarY: '@',
  11073. zooming: '@',
  11074. minZoom: '@',
  11075. maxZoom: '@'
  11076. });
  11077. $scope.direction = $scope.direction || 'y';
  11078. if (isDefined($attr.padding)) {
  11079. $scope.$watch($attr.padding, function(newVal) {
  11080. innerElement.toggleClass('padding', !!newVal);
  11081. });
  11082. }
  11083. if ($scope.$eval($scope.paging) === true) {
  11084. innerElement.addClass('scroll-paging');
  11085. }
  11086. if (!$scope.direction) { $scope.direction = 'y'; }
  11087. var isPaging = $scope.$eval($scope.paging) === true;
  11088. if (nativeScrolling) {
  11089. $element.addClass('overflow-scroll');
  11090. }
  11091. $element.addClass('scroll-' + $scope.direction);
  11092. var scrollViewOptions = {
  11093. el: $element[0],
  11094. delegateHandle: $attr.delegateHandle,
  11095. locking: ($attr.locking || 'true') === 'true',
  11096. bouncing: $scope.$eval($attr.hasBouncing),
  11097. paging: isPaging,
  11098. scrollbarX: $scope.$eval($scope.scrollbarX) !== false,
  11099. scrollbarY: $scope.$eval($scope.scrollbarY) !== false,
  11100. scrollingX: $scope.direction.indexOf('x') >= 0,
  11101. scrollingY: $scope.direction.indexOf('y') >= 0,
  11102. zooming: $scope.$eval($scope.zooming) === true,
  11103. maxZoom: $scope.$eval($scope.maxZoom) || 3,
  11104. minZoom: $scope.$eval($scope.minZoom) || 0.5,
  11105. preventDefault: true,
  11106. nativeScrolling: nativeScrolling,
  11107. scrollingComplete: onScrollComplete
  11108. };
  11109. if (isPaging) {
  11110. scrollViewOptions.speedMultiplier = 0.8;
  11111. scrollViewOptions.bouncing = false;
  11112. }
  11113. var scrollCtrl = $controller('$ionicScroll', {
  11114. $scope: $scope,
  11115. scrollViewOptions: scrollViewOptions
  11116. });
  11117. function onScrollComplete() {
  11118. $scope.$onScrollComplete && $scope.$onScrollComplete({
  11119. scrollTop: scrollCtrl.scrollView.__scrollTop,
  11120. scrollLeft: scrollCtrl.scrollView.__scrollLeft
  11121. });
  11122. }
  11123. }
  11124. }
  11125. };
  11126. }]);
  11127. /**
  11128. * @ngdoc directive
  11129. * @name ionSideMenu
  11130. * @module ionic
  11131. * @restrict E
  11132. * @parent ionic.directive:ionSideMenus
  11133. *
  11134. * @description
  11135. * A container for a side menu, sibling to an {@link ionic.directive:ionSideMenuContent} directive.
  11136. *
  11137. * @usage
  11138. * ```html
  11139. * <ion-side-menu
  11140. * side="left"
  11141. * width="myWidthValue + 20"
  11142. * is-enabled="shouldLeftSideMenuBeEnabled()">
  11143. * </ion-side-menu>
  11144. * ```
  11145. * For a complete side menu example, see the
  11146. * {@link ionic.directive:ionSideMenus} documentation.
  11147. *
  11148. * @param {string} side Which side the side menu is currently on. Allowed values: 'left' or 'right'.
  11149. * @param {boolean=} is-enabled Whether this side menu is enabled.
  11150. * @param {number=} width How many pixels wide the side menu should be. Defaults to 275.
  11151. */
  11152. IonicModule
  11153. .directive('ionSideMenu', function() {
  11154. return {
  11155. restrict: 'E',
  11156. require: '^ionSideMenus',
  11157. scope: true,
  11158. compile: function(element, attr) {
  11159. angular.isUndefined(attr.isEnabled) && attr.$set('isEnabled', 'true');
  11160. angular.isUndefined(attr.width) && attr.$set('width', '275');
  11161. element.addClass('menu menu-' + attr.side);
  11162. return function($scope, $element, $attr, sideMenuCtrl) {
  11163. $scope.side = $attr.side || 'left';
  11164. var sideMenu = sideMenuCtrl[$scope.side] = new ionic.views.SideMenu({
  11165. width: attr.width,
  11166. el: $element[0],
  11167. isEnabled: true
  11168. });
  11169. $scope.$watch($attr.width, function(val) {
  11170. var numberVal = +val;
  11171. if (numberVal && numberVal == val) {
  11172. sideMenu.setWidth(+val);
  11173. }
  11174. });
  11175. $scope.$watch($attr.isEnabled, function(val) {
  11176. sideMenu.setIsEnabled(!!val);
  11177. });
  11178. };
  11179. }
  11180. };
  11181. });
  11182. /**
  11183. * @ngdoc directive
  11184. * @name ionSideMenuContent
  11185. * @module ionic
  11186. * @restrict E
  11187. * @parent ionic.directive:ionSideMenus
  11188. *
  11189. * @description
  11190. * A container for the main visible content, sibling to one or more
  11191. * {@link ionic.directive:ionSideMenu} directives.
  11192. *
  11193. * @usage
  11194. * ```html
  11195. * <ion-side-menu-content
  11196. * edge-drag-threshold="true"
  11197. * drag-content="true">
  11198. * </ion-side-menu-content>
  11199. * ```
  11200. * For a complete side menu example, see the
  11201. * {@link ionic.directive:ionSideMenus} documentation.
  11202. *
  11203. * @param {boolean=} drag-content Whether the content can be dragged. Default true.
  11204. * @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:
  11205. * - 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.
  11206. * - If true is given, the default number of pixels (25) is used as the maximum allowed distance.
  11207. * - If false or 0 is given, the edge drag threshold is disabled, and dragging from anywhere on the content is allowed.
  11208. *
  11209. */
  11210. IonicModule
  11211. .directive('ionSideMenuContent', [
  11212. '$timeout',
  11213. '$ionicGesture',
  11214. '$window',
  11215. function($timeout, $ionicGesture, $window) {
  11216. return {
  11217. restrict: 'EA', //DEPRECATED 'A'
  11218. require: '^ionSideMenus',
  11219. scope: true,
  11220. compile: function(element, attr) {
  11221. element.addClass('menu-content pane');
  11222. return { pre: prelink };
  11223. function prelink($scope, $element, $attr, sideMenuCtrl) {
  11224. var startCoord = null;
  11225. var primaryScrollAxis = null;
  11226. if (isDefined(attr.dragContent)) {
  11227. $scope.$watch(attr.dragContent, function(value) {
  11228. sideMenuCtrl.canDragContent(value);
  11229. });
  11230. } else {
  11231. sideMenuCtrl.canDragContent(true);
  11232. }
  11233. if (isDefined(attr.edgeDragThreshold)) {
  11234. $scope.$watch(attr.edgeDragThreshold, function(value) {
  11235. sideMenuCtrl.edgeDragThreshold(value);
  11236. });
  11237. }
  11238. // Listen for taps on the content to close the menu
  11239. function onContentTap(gestureEvt) {
  11240. if (sideMenuCtrl.getOpenAmount() !== 0) {
  11241. sideMenuCtrl.close();
  11242. gestureEvt.gesture.srcEvent.preventDefault();
  11243. startCoord = null;
  11244. primaryScrollAxis = null;
  11245. } else if (!startCoord) {
  11246. startCoord = ionic.tap.pointerCoord(gestureEvt.gesture.srcEvent);
  11247. }
  11248. }
  11249. function onDragX(e) {
  11250. if (!sideMenuCtrl.isDraggableTarget(e)) return;
  11251. if (getPrimaryScrollAxis(e) == 'x') {
  11252. sideMenuCtrl._handleDrag(e);
  11253. e.gesture.srcEvent.preventDefault();
  11254. }
  11255. }
  11256. function onDragY(e) {
  11257. if (getPrimaryScrollAxis(e) == 'x') {
  11258. e.gesture.srcEvent.preventDefault();
  11259. }
  11260. }
  11261. function onDragRelease(e) {
  11262. sideMenuCtrl._endDrag(e);
  11263. startCoord = null;
  11264. primaryScrollAxis = null;
  11265. }
  11266. function getPrimaryScrollAxis(gestureEvt) {
  11267. // gets whether the user is primarily scrolling on the X or Y
  11268. // If a majority of the drag has been on the Y since the start of
  11269. // the drag, but the X has moved a little bit, it's still a Y drag
  11270. if (primaryScrollAxis) {
  11271. // we already figured out which way they're scrolling
  11272. return primaryScrollAxis;
  11273. }
  11274. if (gestureEvt && gestureEvt.gesture) {
  11275. if (!startCoord) {
  11276. // get the starting point
  11277. startCoord = ionic.tap.pointerCoord(gestureEvt.gesture.srcEvent);
  11278. } else {
  11279. // we already have a starting point, figure out which direction they're going
  11280. var endCoord = ionic.tap.pointerCoord(gestureEvt.gesture.srcEvent);
  11281. var xDistance = Math.abs(endCoord.x - startCoord.x);
  11282. var yDistance = Math.abs(endCoord.y - startCoord.y);
  11283. var scrollAxis = (xDistance < yDistance ? 'y' : 'x');
  11284. if (Math.max(xDistance, yDistance) > 30) {
  11285. // ok, we pretty much know which way they're going
  11286. // let's lock it in
  11287. primaryScrollAxis = scrollAxis;
  11288. }
  11289. return scrollAxis;
  11290. }
  11291. }
  11292. return 'y';
  11293. }
  11294. var content = {
  11295. element: element[0],
  11296. onDrag: function() {},
  11297. endDrag: function() {},
  11298. setCanScroll: function(canScroll) {
  11299. var c = $element[0].querySelector('.scroll');
  11300. if (!c) {
  11301. return;
  11302. }
  11303. var content = angular.element(c.parentElement);
  11304. if (!content) {
  11305. return;
  11306. }
  11307. // freeze our scroll container if we have one
  11308. var scrollScope = content.scope();
  11309. scrollScope.scrollCtrl && scrollScope.scrollCtrl.freezeScrollShut(!canScroll);
  11310. },
  11311. getTranslateX: function() {
  11312. return $scope.sideMenuContentTranslateX || 0;
  11313. },
  11314. setTranslateX: ionic.animationFrameThrottle(function(amount) {
  11315. var xTransform = content.offsetX + amount;
  11316. $element[0].style[ionic.CSS.TRANSFORM] = 'translate3d(' + xTransform + 'px,0,0)';
  11317. $timeout(function() {
  11318. $scope.sideMenuContentTranslateX = amount;
  11319. });
  11320. }),
  11321. setMarginLeft: ionic.animationFrameThrottle(function(amount) {
  11322. if (amount) {
  11323. amount = parseInt(amount, 10);
  11324. $element[0].style[ionic.CSS.TRANSFORM] = 'translate3d(' + amount + 'px,0,0)';
  11325. $element[0].style.width = ($window.innerWidth - amount) + 'px';
  11326. content.offsetX = amount;
  11327. } else {
  11328. $element[0].style[ionic.CSS.TRANSFORM] = 'translate3d(0,0,0)';
  11329. $element[0].style.width = '';
  11330. content.offsetX = 0;
  11331. }
  11332. }),
  11333. setMarginRight: ionic.animationFrameThrottle(function(amount) {
  11334. if (amount) {
  11335. amount = parseInt(amount, 10);
  11336. $element[0].style.width = ($window.innerWidth - amount) + 'px';
  11337. content.offsetX = amount;
  11338. } else {
  11339. $element[0].style.width = '';
  11340. content.offsetX = 0;
  11341. }
  11342. // reset incase left gets grabby
  11343. $element[0].style[ionic.CSS.TRANSFORM] = 'translate3d(0,0,0)';
  11344. }),
  11345. setMarginLeftAndRight: ionic.animationFrameThrottle(function(amountLeft, amountRight) {
  11346. amountLeft = amountLeft && parseInt(amountLeft, 10) || 0;
  11347. amountRight = amountRight && parseInt(amountRight, 10) || 0;
  11348. var amount = amountLeft + amountRight;
  11349. if (amount > 0) {
  11350. $element[0].style[ionic.CSS.TRANSFORM] = 'translate3d(' + amountLeft + 'px,0,0)';
  11351. $element[0].style.width = ($window.innerWidth - amount) + 'px';
  11352. content.offsetX = amountLeft;
  11353. } else {
  11354. $element[0].style[ionic.CSS.TRANSFORM] = 'translate3d(0,0,0)';
  11355. $element[0].style.width = '';
  11356. content.offsetX = 0;
  11357. }
  11358. // reset incase left gets grabby
  11359. //$element[0].style[ionic.CSS.TRANSFORM] = 'translate3d(0,0,0)';
  11360. }),
  11361. enableAnimation: function() {
  11362. $scope.animationEnabled = true;
  11363. $element[0].classList.add('menu-animated');
  11364. },
  11365. disableAnimation: function() {
  11366. $scope.animationEnabled = false;
  11367. $element[0].classList.remove('menu-animated');
  11368. },
  11369. offsetX: 0
  11370. };
  11371. sideMenuCtrl.setContent(content);
  11372. // add gesture handlers
  11373. var gestureOpts = { stop_browser_behavior: false };
  11374. gestureOpts.prevent_default_directions = ['left', 'right'];
  11375. var contentTapGesture = $ionicGesture.on('tap', onContentTap, $element, gestureOpts);
  11376. var dragRightGesture = $ionicGesture.on('dragright', onDragX, $element, gestureOpts);
  11377. var dragLeftGesture = $ionicGesture.on('dragleft', onDragX, $element, gestureOpts);
  11378. var dragUpGesture = $ionicGesture.on('dragup', onDragY, $element, gestureOpts);
  11379. var dragDownGesture = $ionicGesture.on('dragdown', onDragY, $element, gestureOpts);
  11380. var releaseGesture = $ionicGesture.on('release', onDragRelease, $element, gestureOpts);
  11381. // Cleanup
  11382. $scope.$on('$destroy', function() {
  11383. if (content) {
  11384. content.element = null;
  11385. content = null;
  11386. }
  11387. $ionicGesture.off(dragLeftGesture, 'dragleft', onDragX);
  11388. $ionicGesture.off(dragRightGesture, 'dragright', onDragX);
  11389. $ionicGesture.off(dragUpGesture, 'dragup', onDragY);
  11390. $ionicGesture.off(dragDownGesture, 'dragdown', onDragY);
  11391. $ionicGesture.off(releaseGesture, 'release', onDragRelease);
  11392. $ionicGesture.off(contentTapGesture, 'tap', onContentTap);
  11393. });
  11394. }
  11395. }
  11396. };
  11397. }]);
  11398. IonicModule
  11399. /**
  11400. * @ngdoc directive
  11401. * @name ionSideMenus
  11402. * @module ionic
  11403. * @delegate ionic.service:$ionicSideMenuDelegate
  11404. * @restrict E
  11405. *
  11406. * @description
  11407. * A container element for side menu(s) and the main content. Allows the left and/or right side menu
  11408. * to be toggled by dragging the main content area side to side.
  11409. *
  11410. * To automatically close an opened menu, you can add the {@link ionic.directive:menuClose} attribute
  11411. * directive. The `menu-close` attribute is usually added to links and buttons within
  11412. * `ion-side-menu-content`, so that when the element is clicked, the opened side menu will
  11413. * automatically close.
  11414. *
  11415. * "Burger Icon" toggles can be added to the header with the {@link ionic.directive:menuToggle}
  11416. * attribute directive. Clicking the toggle will open and close the side menu like the `menu-close`
  11417. * directive. The side menu will automatically hide on child pages, but can be overridden with the
  11418. * enable-menu-with-back-views attribute mentioned below.
  11419. *
  11420. * By default, side menus are hidden underneath their side menu content and can be opened by swiping
  11421. * the content left or right or by toggling a button to show the side menu. Additionally, by adding the
  11422. * {@link ionic.directive:exposeAsideWhen} attribute directive to an
  11423. * {@link ionic.directive:ionSideMenu} element directive, a side menu can be given instructions about
  11424. * "when" the menu should be exposed (always viewable).
  11425. *
  11426. * ![Side Menu](http://ionicframework.com.s3.amazonaws.com/docs/controllers/sidemenu.gif)
  11427. *
  11428. * For more information on side menus, check out:
  11429. *
  11430. * - {@link ionic.directive:ionSideMenuContent}
  11431. * - {@link ionic.directive:ionSideMenu}
  11432. * - {@link ionic.directive:menuToggle}
  11433. * - {@link ionic.directive:menuClose}
  11434. * - {@link ionic.directive:exposeAsideWhen}
  11435. *
  11436. * @usage
  11437. * To use side menus, add an `<ion-side-menus>` parent element. This will encompass all pages that have a
  11438. * side menu, and have at least 2 child elements: 1 `<ion-side-menu-content>` for the center content,
  11439. * and one or more `<ion-side-menu>` directives for each side menu(left/right) that you wish to place.
  11440. *
  11441. * ```html
  11442. * <ion-side-menus>
  11443. * <!-- Left menu -->
  11444. * <ion-side-menu side="left">
  11445. * </ion-side-menu>
  11446. *
  11447. * <ion-side-menu-content>
  11448. * <!-- Main content, usually <ion-nav-view> -->
  11449. * </ion-side-menu-content>
  11450. *
  11451. * <!-- Right menu -->
  11452. * <ion-side-menu side="right">
  11453. * </ion-side-menu>
  11454. *
  11455. * </ion-side-menus>
  11456. * ```
  11457. * ```js
  11458. * function ContentController($scope, $ionicSideMenuDelegate) {
  11459. * $scope.toggleLeft = function() {
  11460. * $ionicSideMenuDelegate.toggleLeft();
  11461. * };
  11462. * }
  11463. * ```
  11464. *
  11465. * @param {bool=} enable-menu-with-back-views Determines whether the side menu is enabled when the
  11466. * back button is showing. When set to `false`, any {@link ionic.directive:menuToggle} will be hidden,
  11467. * and the user cannot swipe to open the menu. When going back to the root page of the side menu (the
  11468. * page without a back button visible), then any menuToggle buttons will show again, and menus will be
  11469. * enabled again.
  11470. * @param {string=} delegate-handle The handle used to identify this side menu
  11471. * with {@link ionic.service:$ionicSideMenuDelegate}.
  11472. *
  11473. */
  11474. .directive('ionSideMenus', ['$ionicBody', function($ionicBody) {
  11475. return {
  11476. restrict: 'ECA',
  11477. controller: '$ionicSideMenus',
  11478. compile: function(element, attr) {
  11479. attr.$set('class', (attr['class'] || '') + ' view');
  11480. return { pre: prelink };
  11481. function prelink($scope, $element, $attrs, ctrl) {
  11482. ctrl.enableMenuWithBackViews($scope.$eval($attrs.enableMenuWithBackViews));
  11483. $scope.$on('$ionicExposeAside', function(evt, isAsideExposed) {
  11484. if (!$scope.$exposeAside) $scope.$exposeAside = {};
  11485. $scope.$exposeAside.active = isAsideExposed;
  11486. $ionicBody.enableClass(isAsideExposed, 'aside-open');
  11487. });
  11488. $scope.$on('$ionicView.beforeEnter', function(ev, d) {
  11489. if (d.historyId) {
  11490. $scope.$activeHistoryId = d.historyId;
  11491. }
  11492. });
  11493. $scope.$on('$destroy', function() {
  11494. $ionicBody.removeClass('menu-open', 'aside-open');
  11495. });
  11496. }
  11497. }
  11498. };
  11499. }]);
  11500. /**
  11501. * @ngdoc directive
  11502. * @name ionSlideBox
  11503. * @module ionic
  11504. * @codepen AjgEB
  11505. * @deprecated will be removed in the next Ionic release in favor of the new ion-slides component.
  11506. * Don't depend on the internal behavior of this widget.
  11507. * @delegate ionic.service:$ionicSlideBoxDelegate
  11508. * @restrict E
  11509. * @description
  11510. * The Slide Box is a multi-page container where each page can be swiped or dragged between:
  11511. *
  11512. *
  11513. * @usage
  11514. * ```html
  11515. * <ion-slide-box on-slide-changed="slideHasChanged($index)">
  11516. * <ion-slide>
  11517. * <div class="box blue"><h1>BLUE</h1></div>
  11518. * </ion-slide>
  11519. * <ion-slide>
  11520. * <div class="box yellow"><h1>YELLOW</h1></div>
  11521. * </ion-slide>
  11522. * <ion-slide>
  11523. * <div class="box pink"><h1>PINK</h1></div>
  11524. * </ion-slide>
  11525. * </ion-slide-box>
  11526. * ```
  11527. *
  11528. * @param {string=} delegate-handle The handle used to identify this slideBox
  11529. * with {@link ionic.service:$ionicSlideBoxDelegate}.
  11530. * @param {boolean=} does-continue Whether the slide box should loop.
  11531. * @param {boolean=} auto-play Whether the slide box should automatically slide. Default true if does-continue is true.
  11532. * @param {number=} slide-interval How many milliseconds to wait to change slides (if does-continue is true). Defaults to 4000.
  11533. * @param {boolean=} show-pager Whether a pager should be shown for this slide box. Accepts expressions via `show-pager="{{shouldShow()}}"`. Defaults to true.
  11534. * @param {expression=} pager-click Expression to call when a pager is clicked (if show-pager is true). Is passed the 'index' variable.
  11535. * @param {expression=} on-slide-changed Expression called whenever the slide is changed. Is passed an '$index' variable.
  11536. * @param {expression=} active-slide Model to bind the current slide index to.
  11537. */
  11538. IonicModule
  11539. .directive('ionSlideBox', [
  11540. '$animate',
  11541. '$timeout',
  11542. '$compile',
  11543. '$ionicSlideBoxDelegate',
  11544. '$ionicHistory',
  11545. '$ionicScrollDelegate',
  11546. function($animate, $timeout, $compile, $ionicSlideBoxDelegate, $ionicHistory, $ionicScrollDelegate) {
  11547. return {
  11548. restrict: 'E',
  11549. replace: true,
  11550. transclude: true,
  11551. scope: {
  11552. autoPlay: '=',
  11553. doesContinue: '@',
  11554. slideInterval: '@',
  11555. showPager: '@',
  11556. pagerClick: '&',
  11557. disableScroll: '@',
  11558. onSlideChanged: '&',
  11559. activeSlide: '=?',
  11560. bounce: '@'
  11561. },
  11562. controller: ['$scope', '$element', '$attrs', function($scope, $element, $attrs) {
  11563. var _this = this;
  11564. var continuous = $scope.$eval($scope.doesContinue) === true;
  11565. var bouncing = ($scope.$eval($scope.bounce) !== false); //Default to true
  11566. var shouldAutoPlay = isDefined($attrs.autoPlay) ? !!$scope.autoPlay : false;
  11567. var slideInterval = shouldAutoPlay ? $scope.$eval($scope.slideInterval) || 4000 : 0;
  11568. var slider = new ionic.views.Slider({
  11569. el: $element[0],
  11570. auto: slideInterval,
  11571. continuous: continuous,
  11572. startSlide: $scope.activeSlide,
  11573. bouncing: bouncing,
  11574. slidesChanged: function() {
  11575. $scope.currentSlide = slider.currentIndex();
  11576. // Try to trigger a digest
  11577. $timeout(function() {});
  11578. },
  11579. callback: function(slideIndex) {
  11580. $scope.currentSlide = slideIndex;
  11581. $scope.onSlideChanged({ index: $scope.currentSlide, $index: $scope.currentSlide});
  11582. $scope.$parent.$broadcast('slideBox.slideChanged', slideIndex);
  11583. $scope.activeSlide = slideIndex;
  11584. // Try to trigger a digest
  11585. $timeout(function() {});
  11586. },
  11587. onDrag: function() {
  11588. freezeAllScrolls(true);
  11589. },
  11590. onDragEnd: function() {
  11591. freezeAllScrolls(false);
  11592. }
  11593. });
  11594. function freezeAllScrolls(shouldFreeze) {
  11595. if (shouldFreeze && !_this.isScrollFreeze) {
  11596. $ionicScrollDelegate.freezeAllScrolls(shouldFreeze);
  11597. } else if (!shouldFreeze && _this.isScrollFreeze) {
  11598. $ionicScrollDelegate.freezeAllScrolls(false);
  11599. }
  11600. _this.isScrollFreeze = shouldFreeze;
  11601. }
  11602. slider.enableSlide($scope.$eval($attrs.disableScroll) !== true);
  11603. $scope.$watch('activeSlide', function(nv) {
  11604. if (isDefined(nv)) {
  11605. slider.slide(nv);
  11606. }
  11607. });
  11608. $scope.$on('slideBox.nextSlide', function() {
  11609. slider.next();
  11610. });
  11611. $scope.$on('slideBox.prevSlide', function() {
  11612. slider.prev();
  11613. });
  11614. $scope.$on('slideBox.setSlide', function(e, index) {
  11615. slider.slide(index);
  11616. });
  11617. //Exposed for testing
  11618. this.__slider = slider;
  11619. var deregisterInstance = $ionicSlideBoxDelegate._registerInstance(
  11620. slider, $attrs.delegateHandle, function() {
  11621. return $ionicHistory.isActiveScope($scope);
  11622. }
  11623. );
  11624. $scope.$on('$destroy', function() {
  11625. deregisterInstance();
  11626. slider.kill();
  11627. });
  11628. this.slidesCount = function() {
  11629. return slider.slidesCount();
  11630. };
  11631. this.onPagerClick = function(index) {
  11632. $scope.pagerClick({index: index});
  11633. };
  11634. $timeout(function() {
  11635. slider.load();
  11636. });
  11637. }],
  11638. template: '<div class="slider">' +
  11639. '<div class="slider-slides" ng-transclude>' +
  11640. '</div>' +
  11641. '</div>',
  11642. link: function($scope, $element, $attr) {
  11643. // Disable ngAnimate for slidebox and its children
  11644. $animate.enabled($element, false);
  11645. // if showPager is undefined, show the pager
  11646. if (!isDefined($attr.showPager)) {
  11647. $scope.showPager = true;
  11648. getPager().toggleClass('hide', !true);
  11649. }
  11650. $attr.$observe('showPager', function(show) {
  11651. if (show === undefined) return;
  11652. show = $scope.$eval(show);
  11653. getPager().toggleClass('hide', !show);
  11654. });
  11655. var pager;
  11656. function getPager() {
  11657. if (!pager) {
  11658. var childScope = $scope.$new();
  11659. pager = jqLite('<ion-pager></ion-pager>');
  11660. $element.append(pager);
  11661. pager = $compile(pager)(childScope);
  11662. }
  11663. return pager;
  11664. }
  11665. }
  11666. };
  11667. }])
  11668. .directive('ionSlide', function() {
  11669. return {
  11670. restrict: 'E',
  11671. require: '?^ionSlideBox',
  11672. compile: function(element) {
  11673. element.addClass('slider-slide');
  11674. }
  11675. };
  11676. })
  11677. .directive('ionPager', function() {
  11678. return {
  11679. restrict: 'E',
  11680. replace: true,
  11681. require: '^ionSlideBox',
  11682. 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>',
  11683. link: function($scope, $element, $attr, slideBox) {
  11684. var selectPage = function(index) {
  11685. var children = $element[0].children;
  11686. var length = children.length;
  11687. for (var i = 0; i < length; i++) {
  11688. if (i == index) {
  11689. children[i].classList.add('active');
  11690. } else {
  11691. children[i].classList.remove('active');
  11692. }
  11693. }
  11694. };
  11695. $scope.pagerClick = function(index) {
  11696. slideBox.onPagerClick(index);
  11697. };
  11698. $scope.numSlides = function() {
  11699. return new Array(slideBox.slidesCount());
  11700. };
  11701. $scope.$watch('currentSlide', function(v) {
  11702. selectPage(v);
  11703. });
  11704. }
  11705. };
  11706. });
  11707. /**
  11708. * @ngdoc directive
  11709. * @name ionSlides
  11710. * @module ionic
  11711. * @delegate ionic.service:$ionicSlideBoxDelegate
  11712. * @restrict E
  11713. * @description
  11714. * The Slides component is a powerful multi-page container where each page can be swiped or dragged between.
  11715. *
  11716. * Note: this is a new version of the Ionic Slide Box based on the [Swiper](http://www.idangero.us/swiper/#.Vmc1J-ODFBc) widget from
  11717. * [idangerous](http://www.idangero.us/).
  11718. *
  11719. * ![SlideBox](http://ionicframework.com.s3.amazonaws.com/docs/controllers/slideBox.gif)
  11720. *
  11721. * @usage
  11722. * ```html
  11723. * <ion-content scroll="false">
  11724. * <ion-slides options="options" slider="data.slider">
  11725. * <ion-slide-page>
  11726. * <div class="box blue"><h1>BLUE</h1></div>
  11727. * </ion-slide-page>
  11728. * <ion-slide-page>
  11729. * <div class="box yellow"><h1>YELLOW</h1></div>
  11730. * </ion-slide-page>
  11731. * <ion-slide-page>
  11732. * <div class="box pink"><h1>PINK</h1></div>
  11733. * </ion-slide-page>
  11734. * </ion-slides>
  11735. * </ion-content>
  11736. * ```
  11737. *
  11738. * ```js
  11739. * $scope.options = {
  11740. * loop: false,
  11741. * effect: 'fade',
  11742. * speed: 500,
  11743. * }
  11744. *
  11745. * $scope.$on("$ionicSlides.sliderInitialized", function(event, data){
  11746. * // data.slider is the instance of Swiper
  11747. * $scope.slider = data.slider;
  11748. * });
  11749. *
  11750. * $scope.$on("$ionicSlides.slideChangeStart", function(event, data){
  11751. * console.log('Slide change is beginning');
  11752. * });
  11753. *
  11754. * $scope.$on("$ionicSlides.slideChangeEnd", function(event, data){
  11755. * // note: the indexes are 0-based
  11756. * $scope.activeIndex = data.slider.activeIndex;
  11757. * $scope.previousIndex = data.slider.previousIndex;
  11758. * });
  11759. *
  11760. * ```
  11761. *
  11762. * ## Slide Events
  11763. *
  11764. * The slides component dispatches events when the active slide changes
  11765. *
  11766. * <table class="table">
  11767. * <tr>
  11768. * <td><code>$ionicSlides.slideChangeStart</code></td>
  11769. * <td>This event is emitted when a slide change begins</td>
  11770. * </tr>
  11771. * <tr>
  11772. * <td><code>$ionicSlides.slideChangeEnd</code></td>
  11773. * <td>This event is emitted when a slide change completes</td>
  11774. * </tr>
  11775. * <tr>
  11776. * <td><code>$ionicSlides.sliderInitialized</code></td>
  11777. * <td>This event is emitted when the slider is initialized. It provides access to an instance of the slider.</td>
  11778. * </tr>
  11779. * </table>
  11780. *
  11781. *
  11782. * ## Updating Slides Dynamically
  11783. * When applying data to the slider at runtime, typically everything will work as expected.
  11784. *
  11785. * In the event that the slides are looped, use the `updateLoop` method on the slider to ensure the slides update correctly.
  11786. *
  11787. * ```
  11788. * $scope.$on("$ionicSlides.sliderInitialized", function(event, data){
  11789. * // grab an instance of the slider
  11790. * $scope.slider = data.slider;
  11791. * });
  11792. *
  11793. * function dataChangeHandler(){
  11794. * // call this function when data changes, such as an HTTP request, etc
  11795. * if ( $scope.slider ){
  11796. * $scope.slider.updateLoop();
  11797. * }
  11798. * }
  11799. * ```
  11800. *
  11801. */
  11802. IonicModule
  11803. .directive('ionSlides', [
  11804. '$animate',
  11805. '$timeout',
  11806. '$compile',
  11807. function($animate, $timeout, $compile) {
  11808. return {
  11809. restrict: 'E',
  11810. transclude: true,
  11811. scope: {
  11812. options: '=',
  11813. slider: '='
  11814. },
  11815. template: '<div class="swiper-container">' +
  11816. '<div class="swiper-wrapper" ng-transclude>' +
  11817. '</div>' +
  11818. '<div ng-hide="!showPager" class="swiper-pagination"></div>' +
  11819. '</div>',
  11820. controller: ['$scope', '$element', function($scope, $element) {
  11821. var _this = this;
  11822. this.update = function() {
  11823. $timeout(function() {
  11824. if (!_this.__slider) {
  11825. return;
  11826. }
  11827. _this.__slider.update();
  11828. if (_this._options.loop) {
  11829. _this.__slider.createLoop();
  11830. }
  11831. var slidesLength = _this.__slider.slides.length;
  11832. // Don't allow pager to show with > 10 slides
  11833. if (slidesLength > 10) {
  11834. $scope.showPager = false;
  11835. }
  11836. // When slide index is greater than total then slide to last index
  11837. if (_this.__slider.activeIndex > slidesLength - 1) {
  11838. _this.__slider.slideTo(slidesLength - 1);
  11839. }
  11840. });
  11841. };
  11842. this.rapidUpdate = ionic.debounce(function() {
  11843. _this.update();
  11844. }, 50);
  11845. this.getSlider = function() {
  11846. return _this.__slider;
  11847. };
  11848. var options = $scope.options || {};
  11849. var newOptions = angular.extend({
  11850. pagination: $element.children().children()[1],
  11851. paginationClickable: true,
  11852. lazyLoading: true,
  11853. preloadImages: false
  11854. }, options);
  11855. this._options = newOptions;
  11856. $timeout(function() {
  11857. var slider = new ionic.views.Swiper($element.children()[0], newOptions, $scope, $compile);
  11858. $scope.$emit("$ionicSlides.sliderInitialized", { slider: slider });
  11859. _this.__slider = slider;
  11860. $scope.slider = _this.__slider;
  11861. $scope.$on('$destroy', function() {
  11862. slider.destroy();
  11863. _this.__slider = null;
  11864. });
  11865. });
  11866. $timeout(function() {
  11867. // if it's a loop, render the slides again just incase
  11868. _this.rapidUpdate();
  11869. }, 200);
  11870. }],
  11871. link: function($scope) {
  11872. $scope.showPager = true;
  11873. // Disable ngAnimate for slidebox and its children
  11874. //$animate.enabled(false, $element);
  11875. }
  11876. };
  11877. }])
  11878. .directive('ionSlidePage', [function() {
  11879. return {
  11880. restrict: 'E',
  11881. require: '?^ionSlides',
  11882. transclude: true,
  11883. replace: true,
  11884. template: '<div class="swiper-slide" ng-transclude></div>',
  11885. link: function($scope, $element, $attr, ionSlidesCtrl) {
  11886. ionSlidesCtrl.rapidUpdate();
  11887. $scope.$on('$destroy', function() {
  11888. ionSlidesCtrl.rapidUpdate();
  11889. });
  11890. }
  11891. };
  11892. }]);
  11893. /**
  11894. * @ngdoc directive
  11895. * @name ionSpinner
  11896. * @module ionic
  11897. * @restrict E
  11898. *
  11899. * @description
  11900. * The `ionSpinner` directive provides a variety of animated spinners.
  11901. * Spinners enables you to give your users feedback that the app is
  11902. * processing/thinking/waiting/chillin' out, or whatever you'd like it to indicate.
  11903. * By default, the {@link ionic.directive:ionRefresher} feature uses this spinner, rather
  11904. * than rotating font icons (previously included in [ionicons](http://ionicons.com/)).
  11905. * While font icons are great for simple or stationary graphics, they're not suited to
  11906. * provide great animations, which is why Ionic uses SVG instead.
  11907. *
  11908. * Ionic offers ten spinners out of the box, and by default, it will use the appropriate spinner
  11909. * for the platform on which it's running. Under the hood, the `ionSpinner` directive dynamically
  11910. * builds the required SVG element, which allows Ionic to provide all ten of the animated SVGs
  11911. * within 3KB.
  11912. *
  11913. * <style>
  11914. * .spinner-table {
  11915. * max-width: 280px;
  11916. * }
  11917. * .spinner-table tbody > tr > th, .spinner-table tbody > tr > td {
  11918. * vertical-align: middle;
  11919. * width: 42px;
  11920. * height: 42px;
  11921. * }
  11922. * .spinner {
  11923. * stroke: #444;
  11924. * fill: #444; }
  11925. * .spinner svg {
  11926. * width: 28px;
  11927. * height: 28px; }
  11928. * .spinner.spinner-inverse {
  11929. * stroke: #fff;
  11930. * fill: #fff; }
  11931. *
  11932. * .spinner-android {
  11933. * stroke: #4b8bf4; }
  11934. *
  11935. * .spinner-ios, .spinner-ios-small {
  11936. * stroke: #69717d; }
  11937. *
  11938. * .spinner-spiral .stop1 {
  11939. * stop-color: #fff;
  11940. * stop-opacity: 0; }
  11941. * .spinner-spiral.spinner-inverse .stop1 {
  11942. * stop-color: #000; }
  11943. * .spinner-spiral.spinner-inverse .stop2 {
  11944. * stop-color: #fff; }
  11945. * </style>
  11946. *
  11947. * <script src="http://code.ionicframework.com/nightly/js/ionic.bundle.min.js"></script>
  11948. * <table class="table spinner-table" ng-app="ionic">
  11949. * <tr>
  11950. * <th>
  11951. * <code>android</code>
  11952. * </th>
  11953. * <td>
  11954. * <ion-spinner icon="android"></ion-spinner>
  11955. * </td>
  11956. * </tr>
  11957. * <tr>
  11958. * <th>
  11959. * <code>ios</code>
  11960. * </th>
  11961. * <td>
  11962. * <ion-spinner icon="ios"></ion-spinner>
  11963. * </td>
  11964. * </tr>
  11965. * <tr>
  11966. * <th>
  11967. * <code>ios-small</code>
  11968. * </th>
  11969. * <td>
  11970. * <ion-spinner icon="ios-small"></ion-spinner>
  11971. * </td>
  11972. * </tr>
  11973. * <tr>
  11974. * <th>
  11975. * <code>bubbles</code>
  11976. * </th>
  11977. * <td>
  11978. * <ion-spinner icon="bubbles"></ion-spinner>
  11979. * </td>
  11980. * </tr>
  11981. * <tr>
  11982. * <th>
  11983. * <code>circles</code>
  11984. * </th>
  11985. * <td>
  11986. * <ion-spinner icon="circles"></ion-spinner>
  11987. * </td>
  11988. * </tr>
  11989. * <tr>
  11990. * <th>
  11991. * <code>crescent</code>
  11992. * </th>
  11993. * <td>
  11994. * <ion-spinner icon="crescent"></ion-spinner>
  11995. * </td>
  11996. * </tr>
  11997. * <tr>
  11998. * <th>
  11999. * <code>dots</code>
  12000. * </th>
  12001. * <td>
  12002. * <ion-spinner icon="dots"></ion-spinner>
  12003. * </td>
  12004. * </tr>
  12005. * <tr>
  12006. * <th>
  12007. * <code>lines</code>
  12008. * </th>
  12009. * <td>
  12010. * <ion-spinner icon="lines"></ion-spinner>
  12011. * </td>
  12012. * </tr>
  12013. * <tr>
  12014. * <th>
  12015. * <code>ripple</code>
  12016. * </th>
  12017. * <td>
  12018. * <ion-spinner icon="ripple"></ion-spinner>
  12019. * </td>
  12020. * </tr>
  12021. * <tr>
  12022. * <th>
  12023. * <code>spiral</code>
  12024. * </th>
  12025. * <td>
  12026. * <ion-spinner icon="spiral"></ion-spinner>
  12027. * </td>
  12028. * </tr>
  12029. * </table>
  12030. *
  12031. * Each spinner uses SVG with SMIL animations, however, the Android spinner also uses JavaScript
  12032. * so it also works on Android 4.0-4.3. Additionally, each spinner can be styled with CSS,
  12033. * and scaled to any size.
  12034. *
  12035. *
  12036. * @usage
  12037. * The following code would use the default spinner for the platform it's running from. If it's neither
  12038. * iOS or Android, it'll default to use `ios`.
  12039. *
  12040. * ```html
  12041. * <ion-spinner></ion-spinner>
  12042. * ```
  12043. *
  12044. * By setting the `icon` attribute, you can specify which spinner to use, no matter what
  12045. * the platform is.
  12046. *
  12047. * ```html
  12048. * <ion-spinner icon="spiral"></ion-spinner>
  12049. * ```
  12050. *
  12051. * ## Spinner Colors
  12052. * Like with most of Ionic's other components, spinners can also be styled using
  12053. * Ionic's standard color naming convention. For example:
  12054. *
  12055. * ```html
  12056. * <ion-spinner class="spinner-energized"></ion-spinner>
  12057. * ```
  12058. *
  12059. *
  12060. * ## Styling SVG with CSS
  12061. * One cool thing about SVG is its ability to be styled with CSS! Some of the properties
  12062. * have different names, for example, SVG uses the term `stroke` instead of `border`, and
  12063. * `fill` instead of `background-color`.
  12064. *
  12065. * ```css
  12066. * .spinner svg {
  12067. * width: 28px;
  12068. * height: 28px;
  12069. * stroke: #444;
  12070. * fill: #444;
  12071. * }
  12072. * ```
  12073. *
  12074. */
  12075. IonicModule
  12076. .directive('ionSpinner', function() {
  12077. return {
  12078. restrict: 'E',
  12079. controller: '$ionicSpinner',
  12080. link: function($scope, $element, $attrs, ctrl) {
  12081. var spinnerName = ctrl.init();
  12082. $element.addClass('spinner spinner-' + spinnerName);
  12083. $element.on('$destroy', function onDestroy() {
  12084. ctrl.stop();
  12085. });
  12086. }
  12087. };
  12088. });
  12089. /**
  12090. * @ngdoc directive
  12091. * @name ionTab
  12092. * @module ionic
  12093. * @restrict E
  12094. * @parent ionic.directive:ionTabs
  12095. *
  12096. * @description
  12097. * Contains a tab's content. The content only exists while the given tab is selected.
  12098. *
  12099. * Each ionTab has its own view history.
  12100. *
  12101. * @usage
  12102. * ```html
  12103. * <ion-tab
  12104. * title="Tab!"
  12105. * icon="my-icon"
  12106. * href="#/tab/tab-link"
  12107. * on-select="onTabSelected()"
  12108. * on-deselect="onTabDeselected()">
  12109. * </ion-tab>
  12110. * ```
  12111. * For a complete, working tab bar example, see the {@link ionic.directive:ionTabs} documentation.
  12112. *
  12113. * @param {string} title The title of the tab.
  12114. * @param {string=} href The link that this tab will navigate to when tapped.
  12115. * @param {string=} icon The icon of the tab. If given, this will become the default for icon-on and icon-off.
  12116. * @param {string=} icon-on The icon of the tab while it is selected.
  12117. * @param {string=} icon-off The icon of the tab while it is not selected.
  12118. * @param {expression=} badge The badge to put on this tab (usually a number).
  12119. * @param {expression=} badge-style The style of badge to put on this tab (eg: badge-positive).
  12120. * @param {expression=} on-select Called when this tab is selected.
  12121. * @param {expression=} on-deselect Called when this tab is deselected.
  12122. * @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()}.
  12123. * @param {expression=} hidden Whether the tab is to be hidden or not.
  12124. * @param {expression=} disabled Whether the tab is to be disabled or not.
  12125. */
  12126. IonicModule
  12127. .directive('ionTab', [
  12128. '$compile',
  12129. '$ionicConfig',
  12130. '$ionicBind',
  12131. '$ionicViewSwitcher',
  12132. function($compile, $ionicConfig, $ionicBind, $ionicViewSwitcher) {
  12133. //Returns ' key="value"' if value exists
  12134. function attrStr(k, v) {
  12135. return isDefined(v) ? ' ' + k + '="' + v + '"' : '';
  12136. }
  12137. return {
  12138. restrict: 'E',
  12139. require: ['^ionTabs', 'ionTab'],
  12140. controller: '$ionicTab',
  12141. scope: true,
  12142. compile: function(element, attr) {
  12143. //We create the tabNavTemplate in the compile phase so that the
  12144. //attributes we pass down won't be interpolated yet - we want
  12145. //to pass down the 'raw' versions of the attributes
  12146. var tabNavTemplate = '<ion-tab-nav' +
  12147. attrStr('ng-click', attr.ngClick) +
  12148. attrStr('title', attr.title) +
  12149. attrStr('icon', attr.icon) +
  12150. attrStr('icon-on', attr.iconOn) +
  12151. attrStr('icon-off', attr.iconOff) +
  12152. attrStr('badge', attr.badge) +
  12153. attrStr('badge-style', attr.badgeStyle) +
  12154. attrStr('hidden', attr.hidden) +
  12155. attrStr('disabled', attr.disabled) +
  12156. attrStr('class', attr['class']) +
  12157. '></ion-tab-nav>';
  12158. //Remove the contents of the element so we can compile them later, if tab is selected
  12159. var tabContentEle = document.createElement('div');
  12160. for (var x = 0; x < element[0].children.length; x++) {
  12161. tabContentEle.appendChild(element[0].children[x].cloneNode(true));
  12162. }
  12163. var childElementCount = tabContentEle.childElementCount;
  12164. element.empty();
  12165. var navViewName, isNavView;
  12166. if (childElementCount) {
  12167. if (tabContentEle.children[0].tagName === 'ION-NAV-VIEW') {
  12168. // get the name if it's a nav-view
  12169. navViewName = tabContentEle.children[0].getAttribute('name');
  12170. tabContentEle.children[0].classList.add('view-container');
  12171. isNavView = true;
  12172. }
  12173. if (childElementCount === 1) {
  12174. // make the 1 child element the primary tab content container
  12175. tabContentEle = tabContentEle.children[0];
  12176. }
  12177. if (!isNavView) tabContentEle.classList.add('pane');
  12178. tabContentEle.classList.add('tab-content');
  12179. }
  12180. return function link($scope, $element, $attr, ctrls) {
  12181. var childScope;
  12182. var childElement;
  12183. var tabsCtrl = ctrls[0];
  12184. var tabCtrl = ctrls[1];
  12185. var isTabContentAttached = false;
  12186. $scope.$tabSelected = false;
  12187. $ionicBind($scope, $attr, {
  12188. onSelect: '&',
  12189. onDeselect: '&',
  12190. title: '@',
  12191. uiSref: '@',
  12192. href: '@'
  12193. });
  12194. tabsCtrl.add($scope);
  12195. $scope.$on('$destroy', function() {
  12196. if (!$scope.$tabsDestroy) {
  12197. // if the containing ionTabs directive is being destroyed
  12198. // then don't bother going through the controllers remove
  12199. // method, since remove will reset the active tab as each tab
  12200. // is being destroyed, causing unnecessary view loads and transitions
  12201. tabsCtrl.remove($scope);
  12202. }
  12203. tabNavElement.isolateScope().$destroy();
  12204. tabNavElement.remove();
  12205. tabNavElement = tabContentEle = childElement = null;
  12206. });
  12207. //Remove title attribute so browser-tooltip does not apear
  12208. $element[0].removeAttribute('title');
  12209. if (navViewName) {
  12210. tabCtrl.navViewName = $scope.navViewName = navViewName;
  12211. }
  12212. $scope.$on('$stateChangeSuccess', selectIfMatchesState);
  12213. selectIfMatchesState();
  12214. function selectIfMatchesState() {
  12215. if (tabCtrl.tabMatchesState()) {
  12216. tabsCtrl.select($scope, false);
  12217. }
  12218. }
  12219. var tabNavElement = jqLite(tabNavTemplate);
  12220. tabNavElement.data('$ionTabsController', tabsCtrl);
  12221. tabNavElement.data('$ionTabController', tabCtrl);
  12222. tabsCtrl.$tabsElement.append($compile(tabNavElement)($scope));
  12223. function tabSelected(isSelected) {
  12224. if (isSelected && childElementCount) {
  12225. // this tab is being selected
  12226. // check if the tab is already in the DOM
  12227. // only do this if the tab has child elements
  12228. if (!isTabContentAttached) {
  12229. // tab should be selected and is NOT in the DOM
  12230. // create a new scope and append it
  12231. childScope = $scope.$new();
  12232. childElement = jqLite(tabContentEle);
  12233. $ionicViewSwitcher.viewEleIsActive(childElement, true);
  12234. tabsCtrl.$element.append(childElement);
  12235. $compile(childElement)(childScope);
  12236. isTabContentAttached = true;
  12237. }
  12238. // remove the hide class so the tabs content shows up
  12239. $ionicViewSwitcher.viewEleIsActive(childElement, true);
  12240. } else if (isTabContentAttached && childElement) {
  12241. // this tab should NOT be selected, and it is already in the DOM
  12242. if ($ionicConfig.views.maxCache() > 0) {
  12243. // keep the tabs in the DOM, only css hide it
  12244. $ionicViewSwitcher.viewEleIsActive(childElement, false);
  12245. } else {
  12246. // do not keep tabs in the DOM
  12247. destroyTab();
  12248. }
  12249. }
  12250. }
  12251. function destroyTab() {
  12252. childScope && childScope.$destroy();
  12253. isTabContentAttached && childElement && childElement.remove();
  12254. tabContentEle.innerHTML = '';
  12255. isTabContentAttached = childScope = childElement = null;
  12256. }
  12257. $scope.$watch('$tabSelected', tabSelected);
  12258. $scope.$on('$ionicView.afterEnter', function() {
  12259. $ionicViewSwitcher.viewEleIsActive(childElement, $scope.$tabSelected);
  12260. });
  12261. $scope.$on('$ionicView.clearCache', function() {
  12262. if (!$scope.$tabSelected) {
  12263. destroyTab();
  12264. }
  12265. });
  12266. };
  12267. }
  12268. };
  12269. }]);
  12270. IonicModule
  12271. .directive('ionTabNav', [function() {
  12272. return {
  12273. restrict: 'E',
  12274. replace: true,
  12275. require: ['^ionTabs', '^ionTab'],
  12276. template:
  12277. '<a ng-class="{\'has-badge\':badge, \'tab-hidden\':isHidden(), \'tab-item-active\': isTabActive()}" ' +
  12278. ' ng-disabled="disabled()" class="tab-item">' +
  12279. '<span class="badge {{badgeStyle}}" ng-if="badge">{{badge}}</span>' +
  12280. '<i class="icon {{getIcon()}}" ng-if="getIcon()"></i>' +
  12281. '<span class="tab-title" ng-bind-html="title"></span>' +
  12282. '</a>',
  12283. scope: {
  12284. title: '@',
  12285. icon: '@',
  12286. iconOn: '@',
  12287. iconOff: '@',
  12288. badge: '=',
  12289. hidden: '@',
  12290. disabled: '&',
  12291. badgeStyle: '@',
  12292. 'class': '@'
  12293. },
  12294. link: function($scope, $element, $attrs, ctrls) {
  12295. var tabsCtrl = ctrls[0],
  12296. tabCtrl = ctrls[1];
  12297. //Remove title attribute so browser-tooltip does not apear
  12298. $element[0].removeAttribute('title');
  12299. $scope.selectTab = function(e) {
  12300. e.preventDefault();
  12301. tabsCtrl.select(tabCtrl.$scope, true);
  12302. };
  12303. if (!$attrs.ngClick) {
  12304. $element.on('click', function(event) {
  12305. $scope.$apply(function() {
  12306. $scope.selectTab(event);
  12307. });
  12308. });
  12309. }
  12310. $scope.isHidden = function() {
  12311. if ($attrs.hidden === 'true' || $attrs.hidden === true) return true;
  12312. return false;
  12313. };
  12314. $scope.getIconOn = function() {
  12315. return $scope.iconOn || $scope.icon;
  12316. };
  12317. $scope.getIconOff = function() {
  12318. return $scope.iconOff || $scope.icon;
  12319. };
  12320. $scope.isTabActive = function() {
  12321. return tabsCtrl.selectedTab() === tabCtrl.$scope;
  12322. };
  12323. $scope.getIcon = function() {
  12324. if ( tabsCtrl.selectedTab() === tabCtrl.$scope ) {
  12325. // active
  12326. return $scope.iconOn || $scope.icon;
  12327. }
  12328. else {
  12329. // inactive
  12330. return $scope.iconOff || $scope.icon;
  12331. }
  12332. };
  12333. }
  12334. };
  12335. }]);
  12336. /**
  12337. * @ngdoc directive
  12338. * @name ionTabs
  12339. * @module ionic
  12340. * @delegate ionic.service:$ionicTabsDelegate
  12341. * @restrict E
  12342. * @codepen odqCz
  12343. *
  12344. * @description
  12345. * Powers a multi-tabbed interface with a Tab Bar and a set of "pages" that can be tabbed
  12346. * through.
  12347. *
  12348. * Assign any [tabs class](/docs/components#tabs) to the element to define
  12349. * its look and feel.
  12350. *
  12351. * For iOS, tabs will appear at the bottom of the screen. For Android, tabs will be at the top
  12352. * of the screen, below the nav-bar. This follows each OS's design specification, but can be
  12353. * configured with the {@link ionic.provider:$ionicConfigProvider}.
  12354. *
  12355. * See the {@link ionic.directive:ionTab} directive's documentation for more details on
  12356. * individual tabs.
  12357. *
  12358. * Note: do not place ion-tabs inside of an ion-content element; it has been known to cause a
  12359. * certain CSS bug.
  12360. *
  12361. * @usage
  12362. * ```html
  12363. * <ion-tabs class="tabs-positive tabs-icon-top">
  12364. *
  12365. * <ion-tab title="Home" icon-on="ion-ios-filing" icon-off="ion-ios-filing-outline">
  12366. * <!-- Tab 1 content -->
  12367. * </ion-tab>
  12368. *
  12369. * <ion-tab title="About" icon-on="ion-ios-clock" icon-off="ion-ios-clock-outline">
  12370. * <!-- Tab 2 content -->
  12371. * </ion-tab>
  12372. *
  12373. * <ion-tab title="Settings" icon-on="ion-ios-gear" icon-off="ion-ios-gear-outline">
  12374. * <!-- Tab 3 content -->
  12375. * </ion-tab>
  12376. *
  12377. * </ion-tabs>
  12378. * ```
  12379. *
  12380. * @param {string=} delegate-handle The handle used to identify these tabs
  12381. * with {@link ionic.service:$ionicTabsDelegate}.
  12382. */
  12383. IonicModule
  12384. .directive('ionTabs', [
  12385. '$ionicTabsDelegate',
  12386. '$ionicConfig',
  12387. function($ionicTabsDelegate, $ionicConfig) {
  12388. return {
  12389. restrict: 'E',
  12390. scope: true,
  12391. controller: '$ionicTabs',
  12392. compile: function(tElement) {
  12393. //We cannot use regular transclude here because it breaks element.data()
  12394. //inheritance on compile
  12395. var innerElement = jqLite('<div class="tab-nav tabs">');
  12396. innerElement.append(tElement.contents());
  12397. tElement.append(innerElement)
  12398. .addClass('tabs-' + $ionicConfig.tabs.position() + ' tabs-' + $ionicConfig.tabs.style());
  12399. return { pre: prelink, post: postLink };
  12400. function prelink($scope, $element, $attr, tabsCtrl) {
  12401. var deregisterInstance = $ionicTabsDelegate._registerInstance(
  12402. tabsCtrl, $attr.delegateHandle, tabsCtrl.hasActiveScope
  12403. );
  12404. tabsCtrl.$scope = $scope;
  12405. tabsCtrl.$element = $element;
  12406. tabsCtrl.$tabsElement = jqLite($element[0].querySelector('.tabs'));
  12407. $scope.$watch(function() { return $element[0].className; }, function(value) {
  12408. var isTabsTop = value.indexOf('tabs-top') !== -1;
  12409. var isHidden = value.indexOf('tabs-item-hide') !== -1;
  12410. $scope.$hasTabs = !isTabsTop && !isHidden;
  12411. $scope.$hasTabsTop = isTabsTop && !isHidden;
  12412. $scope.$emit('$ionicTabs.top', $scope.$hasTabsTop);
  12413. });
  12414. function emitLifecycleEvent(ev, data) {
  12415. ev.stopPropagation();
  12416. var previousSelectedTab = tabsCtrl.previousSelectedTab();
  12417. if (previousSelectedTab) {
  12418. previousSelectedTab.$broadcast(ev.name.replace('NavView', 'Tabs'), data);
  12419. }
  12420. }
  12421. $scope.$on('$ionicNavView.beforeLeave', emitLifecycleEvent);
  12422. $scope.$on('$ionicNavView.afterLeave', emitLifecycleEvent);
  12423. $scope.$on('$ionicNavView.leave', emitLifecycleEvent);
  12424. $scope.$on('$destroy', function() {
  12425. // variable to inform child tabs that they're all being blown away
  12426. // used so that while destorying an individual tab, each one
  12427. // doesn't select the next tab as the active one, which causes unnecessary
  12428. // loading of tab views when each will eventually all go away anyway
  12429. $scope.$tabsDestroy = true;
  12430. deregisterInstance();
  12431. tabsCtrl.$tabsElement = tabsCtrl.$element = tabsCtrl.$scope = innerElement = null;
  12432. delete $scope.$hasTabs;
  12433. delete $scope.$hasTabsTop;
  12434. });
  12435. }
  12436. function postLink($scope, $element, $attr, tabsCtrl) {
  12437. if (!tabsCtrl.selectedTab()) {
  12438. // all the tabs have been added
  12439. // but one hasn't been selected yet
  12440. tabsCtrl.select(0);
  12441. }
  12442. }
  12443. }
  12444. };
  12445. }]);
  12446. /**
  12447. * @ngdoc directive
  12448. * @name ionTitle
  12449. * @module ionic
  12450. * @restrict E
  12451. *
  12452. * Used for titles in header and nav bars. New in 1.2
  12453. *
  12454. * Identical to <div class="title"> but with future compatibility for Ionic 2
  12455. *
  12456. * @usage
  12457. *
  12458. * ```html
  12459. * <ion-nav-bar>
  12460. * <ion-title>Hello</ion-title>
  12461. * <ion-nav-bar>
  12462. * ```
  12463. */
  12464. IonicModule
  12465. .directive('ionTitle', [function() {
  12466. return {
  12467. restrict: 'E',
  12468. compile: function(element) {
  12469. element.addClass('title');
  12470. }
  12471. };
  12472. }]);
  12473. /**
  12474. * @ngdoc directive
  12475. * @name ionToggle
  12476. * @module ionic
  12477. * @codepen tfAzj
  12478. * @restrict E
  12479. *
  12480. * @description
  12481. * A toggle is an animated switch which binds a given model to a boolean.
  12482. *
  12483. * Allows dragging of the switch's nub.
  12484. *
  12485. * The toggle behaves like any [AngularJS checkbox](http://docs.angularjs.org/api/ng/input/input[checkbox]) otherwise.
  12486. *
  12487. * @param toggle-class {string=} Sets the CSS class on the inner `label.toggle` element created by the directive.
  12488. *
  12489. * @usage
  12490. * Below is an example of a toggle directive which is wired up to the `airplaneMode` model
  12491. * and has the `toggle-calm` CSS class assigned to the inner element.
  12492. *
  12493. * ```html
  12494. * <ion-toggle ng-model="airplaneMode" toggle-class="toggle-calm">Airplane Mode</ion-toggle>
  12495. * ```
  12496. */
  12497. IonicModule
  12498. .directive('ionToggle', [
  12499. '$timeout',
  12500. '$ionicConfig',
  12501. function($timeout, $ionicConfig) {
  12502. return {
  12503. restrict: 'E',
  12504. replace: true,
  12505. require: '?ngModel',
  12506. transclude: true,
  12507. template:
  12508. '<div class="item item-toggle">' +
  12509. '<div ng-transclude></div>' +
  12510. '<label class="toggle">' +
  12511. '<input type="checkbox">' +
  12512. '<div class="track">' +
  12513. '<div class="handle"></div>' +
  12514. '</div>' +
  12515. '</label>' +
  12516. '</div>',
  12517. compile: function(element, attr) {
  12518. var input = element.find('input');
  12519. forEach({
  12520. 'name': attr.name,
  12521. 'ng-value': attr.ngValue,
  12522. 'ng-model': attr.ngModel,
  12523. 'ng-checked': attr.ngChecked,
  12524. 'ng-disabled': attr.ngDisabled,
  12525. 'ng-true-value': attr.ngTrueValue,
  12526. 'ng-false-value': attr.ngFalseValue,
  12527. 'ng-change': attr.ngChange,
  12528. 'ng-required': attr.ngRequired,
  12529. 'required': attr.required
  12530. }, function(value, name) {
  12531. if (isDefined(value)) {
  12532. input.attr(name, value);
  12533. }
  12534. });
  12535. if (attr.toggleClass) {
  12536. element[0].getElementsByTagName('label')[0].classList.add(attr.toggleClass);
  12537. }
  12538. element.addClass('toggle-' + $ionicConfig.form.toggle());
  12539. return function($scope, $element) {
  12540. var el = $element[0].getElementsByTagName('label')[0];
  12541. var checkbox = el.children[0];
  12542. var track = el.children[1];
  12543. var handle = track.children[0];
  12544. var ngModelController = jqLite(checkbox).controller('ngModel');
  12545. $scope.toggle = new ionic.views.Toggle({
  12546. el: el,
  12547. track: track,
  12548. checkbox: checkbox,
  12549. handle: handle,
  12550. onChange: function() {
  12551. if (ngModelController) {
  12552. ngModelController.$setViewValue(checkbox.checked);
  12553. $scope.$apply();
  12554. }
  12555. }
  12556. });
  12557. $scope.$on('$destroy', function() {
  12558. $scope.toggle.destroy();
  12559. });
  12560. };
  12561. }
  12562. };
  12563. }]);
  12564. /**
  12565. * @ngdoc directive
  12566. * @name ionView
  12567. * @module ionic
  12568. * @restrict E
  12569. * @parent ionNavView
  12570. *
  12571. * @description
  12572. * A container for view content and any navigational and header bar information. When a view
  12573. * enters and exits its parent {@link ionic.directive:ionNavView}, the view also emits view
  12574. * information, such as its title, whether the back button should be displayed or not, whether the
  12575. * corresponding {@link ionic.directive:ionNavBar} should be displayed or not, which transition the view
  12576. * should use to animate, and which direction to animate.
  12577. *
  12578. * *Views are cached to improve performance.* When a view is navigated away from, its element is
  12579. * left in the DOM, and its scope is disconnected from the `$watch` cycle. When navigating to a
  12580. * view that is already cached, its scope is reconnected, and the existing element, which was
  12581. * left in the DOM, becomes active again. This can be disabled, or the maximum number of cached
  12582. * views changed in {@link ionic.provider:$ionicConfigProvider}, in the view's `$state` configuration, or
  12583. * as an attribute on the view itself (see below).
  12584. *
  12585. * @usage
  12586. * Below is an example where our page will load with a {@link ionic.directive:ionNavBar} containing
  12587. * "My Page" as the title.
  12588. *
  12589. * ```html
  12590. * <ion-nav-bar></ion-nav-bar>
  12591. * <ion-nav-view>
  12592. * <ion-view view-title="My Page">
  12593. * <ion-content>
  12594. * Hello!
  12595. * </ion-content>
  12596. * </ion-view>
  12597. * </ion-nav-view>
  12598. * ```
  12599. *
  12600. * ## View LifeCycle and Events
  12601. *
  12602. * Views can be cached, which means ***controllers normally only load once***, which may
  12603. * affect your controller logic. To know when a view has entered or left, events
  12604. * have been added that are emitted from the view's scope. These events also
  12605. * contain data about the view, such as the title and whether the back button should
  12606. * show. Also contained is transition data, such as the transition type and
  12607. * direction that will be or was used.
  12608. *
  12609. * Life cycle events are emitted upwards from the transitioning view's scope. In some cases, it is
  12610. * desirable for a child/nested view to be notified of the event.
  12611. * For this use case, `$ionicParentView` life cycle events are broadcast downwards.
  12612. *
  12613. * <table class="table">
  12614. * <tr>
  12615. * <td><code>$ionicView.loaded</code></td>
  12616. * <td>The view has loaded. This event only happens once per
  12617. * view being created and added to the DOM. If a view leaves but is cached,
  12618. * then this event will not fire again on a subsequent viewing. The loaded event
  12619. * is good place to put your setup code for the view; however, it is not the
  12620. * recommended event to listen to when a view becomes active.</td>
  12621. * </tr>
  12622. * <tr>
  12623. * <td><code>$ionicView.enter</code></td>
  12624. * <td>The view has fully entered and is now the active view.
  12625. * This event will fire, whether it was the first load or a cached view.</td>
  12626. * </tr>
  12627. * <tr>
  12628. * <td><code>$ionicView.leave</code></td>
  12629. * <td>The view has finished leaving and is no longer the
  12630. * active view. This event will fire, whether it is cached or destroyed.</td>
  12631. * </tr>
  12632. * <tr>
  12633. * <td><code>$ionicView.beforeEnter</code></td>
  12634. * <td>The view is about to enter and become the active view.</td>
  12635. * </tr>
  12636. * <tr>
  12637. * <td><code>$ionicView.beforeLeave</code></td>
  12638. * <td>The view is about to leave and no longer be the active view.</td>
  12639. * </tr>
  12640. * <tr>
  12641. * <td><code>$ionicView.afterEnter</code></td>
  12642. * <td>The view has fully entered and is now the active view.</td>
  12643. * </tr>
  12644. * <tr>
  12645. * <td><code>$ionicView.afterLeave</code></td>
  12646. * <td>The view has finished leaving and is no longer the active view.</td>
  12647. * </tr>
  12648. * <tr>
  12649. * <td><code>$ionicView.unloaded</code></td>
  12650. * <td>The view's controller has been destroyed and its element has been
  12651. * removed from the DOM.</td>
  12652. * </tr>
  12653. * <tr>
  12654. * <td><code>$ionicParentView.enter</code></td>
  12655. * <td>The parent view has fully entered and is now the active view.
  12656. * This event will fire, whether it was the first load or a cached view.</td>
  12657. * </tr>
  12658. * <tr>
  12659. * <td><code>$ionicParentView.leave</code></td>
  12660. * <td>The parent view has finished leaving and is no longer the
  12661. * active view. This event will fire, whether it is cached or destroyed.</td>
  12662. * </tr>
  12663. * <tr>
  12664. * <td><code>$ionicParentView.beforeEnter</code></td>
  12665. * <td>The parent view is about to enter and become the active view.</td>
  12666. * </tr>
  12667. * <tr>
  12668. * <td><code>$ionicParentView.beforeLeave</code></td>
  12669. * <td>The parent view is about to leave and no longer be the active view.</td>
  12670. * </tr>
  12671. * <tr>
  12672. * <td><code>$ionicParentView.afterEnter</code></td>
  12673. * <td>The parent view has fully entered and is now the active view.</td>
  12674. * </tr>
  12675. * <tr>
  12676. * <td><code>$ionicParentView.afterLeave</code></td>
  12677. * <td>The parent view has finished leaving and is no longer the active view.</td>
  12678. * </tr>
  12679. * </table>
  12680. *
  12681. * ## LifeCycle Event Usage
  12682. *
  12683. * Below is an example of how to listen to life cycle events and
  12684. * access state parameter data
  12685. *
  12686. * ```js
  12687. * $scope.$on("$ionicView.beforeEnter", function(event, data){
  12688. * // handle event
  12689. * console.log("State Params: ", data.stateParams);
  12690. * });
  12691. *
  12692. * $scope.$on("$ionicView.enter", function(event, data){
  12693. * // handle event
  12694. * console.log("State Params: ", data.stateParams);
  12695. * });
  12696. *
  12697. * $scope.$on("$ionicView.afterEnter", function(event, data){
  12698. * // handle event
  12699. * console.log("State Params: ", data.stateParams);
  12700. * });
  12701. * ```
  12702. *
  12703. * ## Caching
  12704. *
  12705. * Caching can be disabled and enabled in multiple ways. By default, Ionic will
  12706. * cache a maximum of 10 views. You can optionally choose to disable caching at
  12707. * either an individual view basis, or by global configuration. Please see the
  12708. * _Caching_ section in {@link ionic.directive:ionNavView} for more info.
  12709. *
  12710. * @param {string=} view-title A text-only title to display on the parent {@link ionic.directive:ionNavBar}.
  12711. * For an HTML title, such as an image, see {@link ionic.directive:ionNavTitle} instead.
  12712. * @param {boolean=} cache-view If this view should be allowed to be cached or not.
  12713. * Please see the _Caching_ section in {@link ionic.directive:ionNavView} for
  12714. * more info. Default `true`
  12715. * @param {boolean=} can-swipe-back If this view should be allowed to use the swipe to go back gesture or not.
  12716. * This does not enable the swipe to go back feature if it is not available for the platform it's running
  12717. * from, or there isn't a previous view. Default `true`
  12718. * @param {boolean=} hide-back-button Whether to hide the back button on the parent
  12719. * {@link ionic.directive:ionNavBar} by default.
  12720. * @param {boolean=} hide-nav-bar Whether to hide the parent
  12721. * {@link ionic.directive:ionNavBar} by default.
  12722. */
  12723. IonicModule
  12724. .directive('ionView', function() {
  12725. return {
  12726. restrict: 'EA',
  12727. priority: 1000,
  12728. controller: '$ionicView',
  12729. compile: function(tElement) {
  12730. tElement.addClass('pane');
  12731. tElement[0].removeAttribute('title');
  12732. return function link($scope, $element, $attrs, viewCtrl) {
  12733. viewCtrl.init();
  12734. };
  12735. }
  12736. };
  12737. });
  12738. })();