ionic-angular.js 450 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521552255235524552555265527552855295530553155325533553455355536553755385539554055415542554355445545554655475548554955505551555255535554555555565557555855595560556155625563556455655566556755685569557055715572557355745575557655775578557955805581558255835584558555865587558855895590559155925593559455955596559755985599560056015602560356045605560656075608560956105611561256135614561556165617561856195620562156225623562456255626562756285629563056315632563356345635563656375638563956405641564256435644564556465647564856495650565156525653565456555656565756585659566056615662566356645665566656675668566956705671567256735674567556765677567856795680568156825683568456855686568756885689569056915692569356945695569656975698569957005701570257035704570557065707570857095710571157125713571457155716571757185719572057215722572357245725572657275728572957305731573257335734573557365737573857395740574157425743574457455746574757485749575057515752575357545755575657575758575957605761576257635764576557665767576857695770577157725773577457755776577757785779578057815782578357845785578657875788578957905791579257935794579557965797579857995800580158025803580458055806580758085809581058115812581358145815581658175818581958205821582258235824582558265827582858295830583158325833583458355836583758385839584058415842584358445845584658475848584958505851585258535854585558565857585858595860586158625863586458655866586758685869587058715872587358745875587658775878587958805881588258835884588558865887588858895890589158925893589458955896589758985899590059015902590359045905590659075908590959105911591259135914591559165917591859195920592159225923592459255926592759285929593059315932593359345935593659375938593959405941594259435944594559465947594859495950595159525953595459555956595759585959596059615962596359645965596659675968596959705971597259735974597559765977597859795980598159825983598459855986598759885989599059915992599359945995599659975998599960006001600260036004600560066007600860096010601160126013601460156016601760186019602060216022602360246025602660276028602960306031603260336034603560366037603860396040604160426043604460456046604760486049605060516052605360546055605660576058605960606061606260636064606560666067606860696070607160726073607460756076607760786079608060816082608360846085608660876088608960906091609260936094609560966097609860996100610161026103610461056106610761086109611061116112611361146115611661176118611961206121612261236124612561266127612861296130613161326133613461356136613761386139614061416142614361446145614661476148614961506151615261536154615561566157615861596160616161626163616461656166616761686169617061716172617361746175617661776178617961806181618261836184618561866187618861896190619161926193619461956196619761986199620062016202620362046205620662076208620962106211621262136214621562166217621862196220622162226223622462256226622762286229623062316232623362346235623662376238623962406241624262436244624562466247624862496250625162526253625462556256625762586259626062616262626362646265626662676268626962706271627262736274627562766277627862796280628162826283628462856286628762886289629062916292629362946295629662976298629963006301630263036304630563066307630863096310631163126313631463156316631763186319632063216322632363246325632663276328632963306331633263336334633563366337633863396340634163426343634463456346634763486349635063516352635363546355635663576358635963606361636263636364636563666367636863696370637163726373637463756376637763786379638063816382638363846385638663876388638963906391639263936394639563966397639863996400640164026403640464056406640764086409641064116412641364146415641664176418641964206421642264236424642564266427642864296430643164326433643464356436643764386439644064416442644364446445644664476448644964506451645264536454645564566457645864596460646164626463646464656466646764686469647064716472647364746475647664776478647964806481648264836484648564866487648864896490649164926493649464956496649764986499650065016502650365046505650665076508650965106511651265136514651565166517651865196520652165226523652465256526652765286529653065316532653365346535653665376538653965406541654265436544654565466547654865496550655165526553655465556556655765586559656065616562656365646565656665676568656965706571657265736574657565766577657865796580658165826583658465856586658765886589659065916592659365946595659665976598659966006601660266036604660566066607660866096610661166126613661466156616661766186619662066216622662366246625662666276628662966306631663266336634663566366637663866396640664166426643664466456646664766486649665066516652665366546655665666576658665966606661666266636664666566666667666866696670667166726673667466756676667766786679668066816682668366846685668666876688668966906691669266936694669566966697669866996700670167026703670467056706670767086709671067116712671367146715671667176718671967206721672267236724672567266727672867296730673167326733673467356736673767386739674067416742674367446745674667476748674967506751675267536754675567566757675867596760676167626763676467656766676767686769677067716772677367746775677667776778677967806781678267836784678567866787678867896790679167926793679467956796679767986799680068016802680368046805680668076808680968106811681268136814681568166817681868196820682168226823682468256826682768286829683068316832683368346835683668376838683968406841684268436844684568466847684868496850685168526853685468556856685768586859686068616862686368646865686668676868686968706871687268736874687568766877687868796880688168826883688468856886688768886889689068916892689368946895689668976898689969006901690269036904690569066907690869096910691169126913691469156916691769186919692069216922692369246925692669276928692969306931693269336934693569366937693869396940694169426943694469456946694769486949695069516952695369546955695669576958695969606961696269636964696569666967696869696970697169726973697469756976697769786979698069816982698369846985698669876988698969906991699269936994699569966997699869997000700170027003700470057006700770087009701070117012701370147015701670177018701970207021702270237024702570267027702870297030703170327033703470357036703770387039704070417042704370447045704670477048704970507051705270537054705570567057705870597060706170627063706470657066706770687069707070717072707370747075707670777078707970807081708270837084708570867087708870897090709170927093709470957096709770987099710071017102710371047105710671077108710971107111711271137114711571167117711871197120712171227123712471257126712771287129713071317132713371347135713671377138713971407141714271437144714571467147714871497150715171527153715471557156715771587159716071617162716371647165716671677168716971707171717271737174717571767177717871797180718171827183718471857186718771887189719071917192719371947195719671977198719972007201720272037204720572067207720872097210721172127213721472157216721772187219722072217222722372247225722672277228722972307231723272337234723572367237723872397240724172427243724472457246724772487249725072517252725372547255725672577258725972607261726272637264726572667267726872697270727172727273727472757276727772787279728072817282728372847285728672877288728972907291729272937294729572967297729872997300730173027303730473057306730773087309731073117312731373147315731673177318731973207321732273237324732573267327732873297330733173327333733473357336733773387339734073417342734373447345734673477348734973507351735273537354735573567357735873597360736173627363736473657366736773687369737073717372737373747375737673777378737973807381738273837384738573867387738873897390739173927393739473957396739773987399740074017402740374047405740674077408740974107411741274137414741574167417741874197420742174227423742474257426742774287429743074317432743374347435743674377438743974407441744274437444744574467447744874497450745174527453745474557456745774587459746074617462746374647465746674677468746974707471747274737474747574767477747874797480748174827483748474857486748774887489749074917492749374947495749674977498749975007501750275037504750575067507750875097510751175127513751475157516751775187519752075217522752375247525752675277528752975307531753275337534753575367537753875397540754175427543754475457546754775487549755075517552755375547555755675577558755975607561756275637564756575667567756875697570757175727573757475757576757775787579758075817582758375847585758675877588758975907591759275937594759575967597759875997600760176027603760476057606760776087609761076117612761376147615761676177618761976207621762276237624762576267627762876297630763176327633763476357636763776387639764076417642764376447645764676477648764976507651765276537654765576567657765876597660766176627663766476657666766776687669767076717672767376747675767676777678767976807681768276837684768576867687768876897690769176927693769476957696769776987699770077017702770377047705770677077708770977107711771277137714771577167717771877197720772177227723772477257726772777287729773077317732773377347735773677377738773977407741774277437744774577467747774877497750775177527753775477557756775777587759776077617762776377647765776677677768776977707771777277737774777577767777777877797780778177827783778477857786778777887789779077917792779377947795779677977798779978007801780278037804780578067807780878097810781178127813781478157816781778187819782078217822782378247825782678277828782978307831783278337834783578367837783878397840784178427843784478457846784778487849785078517852785378547855785678577858785978607861786278637864786578667867786878697870787178727873787478757876787778787879788078817882788378847885788678877888788978907891789278937894789578967897789878997900790179027903790479057906790779087909791079117912791379147915791679177918791979207921792279237924792579267927792879297930793179327933793479357936793779387939794079417942794379447945794679477948794979507951795279537954795579567957795879597960796179627963796479657966796779687969797079717972797379747975797679777978797979807981798279837984798579867987798879897990799179927993799479957996799779987999800080018002800380048005800680078008800980108011801280138014801580168017801880198020802180228023802480258026802780288029803080318032803380348035803680378038803980408041804280438044804580468047804880498050805180528053805480558056805780588059806080618062806380648065806680678068806980708071807280738074807580768077807880798080808180828083808480858086808780888089809080918092809380948095809680978098809981008101810281038104810581068107810881098110811181128113811481158116811781188119812081218122812381248125812681278128812981308131813281338134813581368137813881398140814181428143814481458146814781488149815081518152815381548155815681578158815981608161816281638164816581668167816881698170817181728173817481758176817781788179818081818182818381848185818681878188818981908191819281938194819581968197819881998200820182028203820482058206820782088209821082118212821382148215821682178218821982208221822282238224822582268227822882298230823182328233823482358236823782388239824082418242824382448245824682478248824982508251825282538254825582568257825882598260826182628263826482658266826782688269827082718272827382748275827682778278827982808281828282838284828582868287828882898290829182928293829482958296829782988299830083018302830383048305830683078308830983108311831283138314831583168317831883198320832183228323832483258326832783288329833083318332833383348335833683378338833983408341834283438344834583468347834883498350835183528353835483558356835783588359836083618362836383648365836683678368836983708371837283738374837583768377837883798380838183828383838483858386838783888389839083918392839383948395839683978398839984008401840284038404840584068407840884098410841184128413841484158416841784188419842084218422842384248425842684278428842984308431843284338434843584368437843884398440844184428443844484458446844784488449845084518452845384548455845684578458845984608461846284638464846584668467846884698470847184728473847484758476847784788479848084818482848384848485848684878488848984908491849284938494849584968497849884998500850185028503850485058506850785088509851085118512851385148515851685178518851985208521852285238524852585268527852885298530853185328533853485358536853785388539854085418542854385448545854685478548854985508551855285538554855585568557855885598560856185628563856485658566856785688569857085718572857385748575857685778578857985808581858285838584858585868587858885898590859185928593859485958596859785988599860086018602860386048605860686078608860986108611861286138614861586168617861886198620862186228623862486258626862786288629863086318632863386348635863686378638863986408641864286438644864586468647864886498650865186528653865486558656865786588659866086618662866386648665866686678668866986708671867286738674867586768677867886798680868186828683868486858686868786888689869086918692869386948695869686978698869987008701870287038704870587068707870887098710871187128713871487158716871787188719872087218722872387248725872687278728872987308731873287338734873587368737873887398740874187428743874487458746874787488749875087518752875387548755875687578758875987608761876287638764876587668767876887698770877187728773877487758776877787788779878087818782878387848785878687878788878987908791879287938794879587968797879887998800880188028803880488058806880788088809881088118812881388148815881688178818881988208821882288238824882588268827882888298830883188328833883488358836883788388839884088418842884388448845884688478848884988508851885288538854885588568857885888598860886188628863886488658866886788688869887088718872887388748875887688778878887988808881888288838884888588868887888888898890889188928893889488958896889788988899890089018902890389048905890689078908890989108911891289138914891589168917891889198920892189228923892489258926892789288929893089318932893389348935893689378938893989408941894289438944894589468947894889498950895189528953895489558956895789588959896089618962896389648965896689678968896989708971897289738974897589768977897889798980898189828983898489858986898789888989899089918992899389948995899689978998899990009001900290039004900590069007900890099010901190129013901490159016901790189019902090219022902390249025902690279028902990309031903290339034903590369037903890399040904190429043904490459046904790489049905090519052905390549055905690579058905990609061906290639064906590669067906890699070907190729073907490759076907790789079908090819082908390849085908690879088908990909091909290939094909590969097909890999100910191029103910491059106910791089109911091119112911391149115911691179118911991209121912291239124912591269127912891299130913191329133913491359136913791389139914091419142914391449145914691479148914991509151915291539154915591569157915891599160916191629163916491659166916791689169917091719172917391749175917691779178917991809181918291839184918591869187918891899190919191929193919491959196919791989199920092019202920392049205920692079208920992109211921292139214921592169217921892199220922192229223922492259226922792289229923092319232923392349235923692379238923992409241924292439244924592469247924892499250925192529253925492559256925792589259926092619262926392649265926692679268926992709271927292739274927592769277927892799280928192829283928492859286928792889289929092919292929392949295929692979298929993009301930293039304930593069307930893099310931193129313931493159316931793189319932093219322932393249325932693279328932993309331933293339334933593369337933893399340934193429343934493459346934793489349935093519352935393549355935693579358935993609361936293639364936593669367936893699370937193729373937493759376937793789379938093819382938393849385938693879388938993909391939293939394939593969397939893999400940194029403940494059406940794089409941094119412941394149415941694179418941994209421942294239424942594269427942894299430943194329433943494359436943794389439944094419442944394449445944694479448944994509451945294539454945594569457945894599460946194629463946494659466946794689469947094719472947394749475947694779478947994809481948294839484948594869487948894899490949194929493949494959496949794989499950095019502950395049505950695079508950995109511951295139514951595169517951895199520952195229523952495259526952795289529953095319532953395349535953695379538953995409541954295439544954595469547954895499550955195529553955495559556955795589559956095619562956395649565956695679568956995709571957295739574957595769577957895799580958195829583958495859586958795889589959095919592959395949595959695979598959996009601960296039604960596069607960896099610961196129613961496159616961796189619962096219622962396249625962696279628962996309631963296339634963596369637963896399640964196429643964496459646964796489649965096519652965396549655965696579658965996609661966296639664966596669667966896699670967196729673967496759676967796789679968096819682968396849685968696879688968996909691969296939694969596969697969896999700970197029703970497059706970797089709971097119712971397149715971697179718971997209721972297239724972597269727972897299730973197329733973497359736973797389739974097419742974397449745974697479748974997509751975297539754975597569757975897599760976197629763976497659766976797689769977097719772977397749775977697779778977997809781978297839784978597869787978897899790979197929793979497959796979797989799980098019802980398049805980698079808980998109811981298139814981598169817981898199820982198229823982498259826982798289829983098319832983398349835983698379838983998409841984298439844984598469847984898499850985198529853985498559856985798589859986098619862986398649865986698679868986998709871987298739874987598769877987898799880988198829883988498859886988798889889989098919892989398949895989698979898989999009901990299039904990599069907990899099910991199129913991499159916991799189919992099219922992399249925992699279928992999309931993299339934993599369937993899399940994199429943994499459946994799489949995099519952995399549955995699579958995999609961996299639964996599669967996899699970997199729973997499759976997799789979998099819982998399849985998699879988998999909991999299939994999599969997999899991000010001100021000310004100051000610007100081000910010100111001210013100141001510016100171001810019100201002110022100231002410025100261002710028100291003010031100321003310034100351003610037100381003910040100411004210043100441004510046100471004810049100501005110052100531005410055100561005710058100591006010061100621006310064100651006610067100681006910070100711007210073100741007510076100771007810079100801008110082100831008410085100861008710088100891009010091100921009310094100951009610097100981009910100101011010210103101041010510106101071010810109101101011110112101131011410115101161011710118101191012010121101221012310124101251012610127101281012910130101311013210133101341013510136101371013810139101401014110142101431014410145101461014710148101491015010151101521015310154101551015610157101581015910160101611016210163101641016510166101671016810169101701017110172101731017410175101761017710178101791018010181101821018310184101851018610187101881018910190101911019210193101941019510196101971019810199102001020110202102031020410205102061020710208102091021010211102121021310214102151021610217102181021910220102211022210223102241022510226102271022810229102301023110232102331023410235102361023710238102391024010241102421024310244102451024610247102481024910250102511025210253102541025510256102571025810259102601026110262102631026410265102661026710268102691027010271102721027310274102751027610277102781027910280102811028210283102841028510286102871028810289102901029110292102931029410295102961029710298102991030010301103021030310304103051030610307103081030910310103111031210313103141031510316103171031810319103201032110322103231032410325103261032710328103291033010331103321033310334103351033610337103381033910340103411034210343103441034510346103471034810349103501035110352103531035410355103561035710358103591036010361103621036310364103651036610367103681036910370103711037210373103741037510376103771037810379103801038110382103831038410385103861038710388103891039010391103921039310394103951039610397103981039910400104011040210403104041040510406104071040810409104101041110412104131041410415104161041710418104191042010421104221042310424104251042610427104281042910430104311043210433104341043510436104371043810439104401044110442104431044410445104461044710448104491045010451104521045310454104551045610457104581045910460104611046210463104641046510466104671046810469104701047110472104731047410475104761047710478104791048010481104821048310484104851048610487104881048910490104911049210493104941049510496104971049810499105001050110502105031050410505105061050710508105091051010511105121051310514105151051610517105181051910520105211052210523105241052510526105271052810529105301053110532105331053410535105361053710538105391054010541105421054310544105451054610547105481054910550105511055210553105541055510556105571055810559105601056110562105631056410565105661056710568105691057010571105721057310574105751057610577105781057910580105811058210583105841058510586105871058810589105901059110592105931059410595105961059710598105991060010601106021060310604106051060610607106081060910610106111061210613106141061510616106171061810619106201062110622106231062410625106261062710628106291063010631106321063310634106351063610637106381063910640106411064210643106441064510646106471064810649106501065110652106531065410655106561065710658106591066010661106621066310664106651066610667106681066910670106711067210673106741067510676106771067810679106801068110682106831068410685106861068710688106891069010691106921069310694106951069610697106981069910700107011070210703107041070510706107071070810709107101071110712107131071410715107161071710718107191072010721107221072310724107251072610727107281072910730107311073210733107341073510736107371073810739107401074110742107431074410745107461074710748107491075010751107521075310754107551075610757107581075910760107611076210763107641076510766107671076810769107701077110772107731077410775107761077710778107791078010781107821078310784107851078610787107881078910790107911079210793107941079510796107971079810799108001080110802108031080410805108061080710808108091081010811108121081310814108151081610817108181081910820108211082210823108241082510826108271082810829108301083110832108331083410835108361083710838108391084010841108421084310844108451084610847108481084910850108511085210853108541085510856108571085810859108601086110862108631086410865108661086710868108691087010871108721087310874108751087610877108781087910880108811088210883108841088510886108871088810889108901089110892108931089410895108961089710898108991090010901109021090310904109051090610907109081090910910109111091210913109141091510916109171091810919109201092110922109231092410925109261092710928109291093010931109321093310934109351093610937109381093910940109411094210943109441094510946109471094810949109501095110952109531095410955109561095710958109591096010961109621096310964109651096610967109681096910970109711097210973109741097510976109771097810979109801098110982109831098410985109861098710988109891099010991109921099310994109951099610997109981099911000110011100211003110041100511006110071100811009110101101111012110131101411015110161101711018110191102011021110221102311024110251102611027110281102911030110311103211033110341103511036110371103811039110401104111042110431104411045110461104711048110491105011051110521105311054110551105611057110581105911060110611106211063110641106511066110671106811069110701107111072110731107411075110761107711078110791108011081110821108311084110851108611087110881108911090110911109211093110941109511096110971109811099111001110111102111031110411105111061110711108111091111011111111121111311114111151111611117111181111911120111211112211123111241112511126111271112811129111301113111132111331113411135111361113711138111391114011141111421114311144111451114611147111481114911150111511115211153111541115511156111571115811159111601116111162111631116411165111661116711168111691117011171111721117311174111751117611177111781117911180111811118211183111841118511186111871118811189111901119111192111931119411195111961119711198111991120011201112021120311204112051120611207112081120911210112111121211213112141121511216112171121811219112201122111222112231122411225112261122711228112291123011231112321123311234112351123611237112381123911240112411124211243112441124511246112471124811249112501125111252112531125411255112561125711258112591126011261112621126311264112651126611267112681126911270112711127211273112741127511276112771127811279112801128111282112831128411285112861128711288112891129011291112921129311294112951129611297112981129911300113011130211303113041130511306113071130811309113101131111312113131131411315113161131711318113191132011321113221132311324113251132611327113281132911330113311133211333113341133511336113371133811339113401134111342113431134411345113461134711348113491135011351113521135311354113551135611357113581135911360113611136211363113641136511366113671136811369113701137111372113731137411375113761137711378113791138011381113821138311384113851138611387113881138911390113911139211393113941139511396113971139811399114001140111402114031140411405114061140711408114091141011411114121141311414114151141611417114181141911420114211142211423114241142511426114271142811429114301143111432114331143411435114361143711438114391144011441114421144311444114451144611447114481144911450114511145211453114541145511456114571145811459114601146111462114631146411465114661146711468114691147011471114721147311474114751147611477114781147911480114811148211483114841148511486114871148811489114901149111492114931149411495114961149711498114991150011501115021150311504115051150611507115081150911510115111151211513115141151511516115171151811519115201152111522115231152411525115261152711528115291153011531115321153311534115351153611537115381153911540115411154211543115441154511546115471154811549115501155111552115531155411555115561155711558115591156011561115621156311564115651156611567115681156911570115711157211573115741157511576115771157811579115801158111582115831158411585115861158711588115891159011591115921159311594115951159611597115981159911600116011160211603116041160511606116071160811609116101161111612116131161411615116161161711618116191162011621116221162311624116251162611627116281162911630116311163211633116341163511636116371163811639116401164111642116431164411645116461164711648116491165011651116521165311654116551165611657116581165911660116611166211663116641166511666116671166811669116701167111672116731167411675116761167711678116791168011681116821168311684116851168611687116881168911690116911169211693116941169511696116971169811699117001170111702117031170411705117061170711708117091171011711117121171311714117151171611717117181171911720117211172211723117241172511726117271172811729117301173111732117331173411735117361173711738117391174011741117421174311744117451174611747117481174911750117511175211753117541175511756117571175811759117601176111762117631176411765117661176711768117691177011771117721177311774117751177611777117781177911780117811178211783117841178511786117871178811789117901179111792117931179411795117961179711798117991180011801118021180311804118051180611807118081180911810118111181211813118141181511816118171181811819118201182111822118231182411825118261182711828118291183011831118321183311834118351183611837118381183911840118411184211843118441184511846118471184811849118501185111852118531185411855118561185711858118591186011861118621186311864118651186611867118681186911870118711187211873118741187511876118771187811879118801188111882118831188411885118861188711888118891189011891118921189311894118951189611897118981189911900119011190211903119041190511906119071190811909119101191111912119131191411915119161191711918119191192011921119221192311924119251192611927119281192911930119311193211933119341193511936119371193811939119401194111942119431194411945119461194711948119491195011951119521195311954119551195611957119581195911960119611196211963119641196511966119671196811969119701197111972119731197411975119761197711978119791198011981119821198311984119851198611987119881198911990119911199211993119941199511996119971199811999120001200112002120031200412005120061200712008120091201012011120121201312014120151201612017120181201912020120211202212023120241202512026120271202812029120301203112032120331203412035120361203712038120391204012041120421204312044120451204612047120481204912050120511205212053120541205512056120571205812059120601206112062120631206412065120661206712068120691207012071120721207312074120751207612077120781207912080120811208212083120841208512086120871208812089120901209112092120931209412095120961209712098120991210012101121021210312104121051210612107121081210912110121111211212113121141211512116121171211812119121201212112122121231212412125121261212712128121291213012131121321213312134121351213612137121381213912140121411214212143121441214512146121471214812149121501215112152121531215412155121561215712158121591216012161121621216312164121651216612167121681216912170121711217212173121741217512176121771217812179121801218112182121831218412185121861218712188121891219012191121921219312194121951219612197121981219912200122011220212203122041220512206122071220812209122101221112212122131221412215122161221712218122191222012221122221222312224122251222612227122281222912230122311223212233122341223512236122371223812239122401224112242122431224412245122461224712248122491225012251122521225312254122551225612257122581225912260122611226212263122641226512266122671226812269122701227112272122731227412275122761227712278122791228012281122821228312284122851228612287122881228912290122911229212293122941229512296122971229812299123001230112302123031230412305123061230712308123091231012311123121231312314123151231612317123181231912320123211232212323123241232512326123271232812329123301233112332123331233412335123361233712338123391234012341123421234312344123451234612347123481234912350123511235212353123541235512356123571235812359123601236112362123631236412365123661236712368123691237012371123721237312374123751237612377123781237912380123811238212383123841238512386123871238812389123901239112392123931239412395123961239712398123991240012401124021240312404124051240612407124081240912410124111241212413124141241512416124171241812419124201242112422124231242412425124261242712428124291243012431124321243312434124351243612437124381243912440124411244212443124441244512446124471244812449124501245112452124531245412455124561245712458124591246012461124621246312464124651246612467124681246912470124711247212473124741247512476124771247812479124801248112482124831248412485124861248712488124891249012491124921249312494124951249612497124981249912500125011250212503125041250512506125071250812509125101251112512125131251412515125161251712518125191252012521125221252312524125251252612527125281252912530125311253212533125341253512536125371253812539125401254112542125431254412545125461254712548125491255012551125521255312554125551255612557125581255912560125611256212563125641256512566125671256812569125701257112572125731257412575125761257712578125791258012581125821258312584125851258612587125881258912590125911259212593125941259512596125971259812599126001260112602126031260412605126061260712608126091261012611126121261312614126151261612617126181261912620126211262212623126241262512626126271262812629126301263112632126331263412635126361263712638126391264012641126421264312644126451264612647126481264912650126511265212653126541265512656126571265812659126601266112662126631266412665126661266712668126691267012671126721267312674126751267612677126781267912680126811268212683126841268512686126871268812689126901269112692126931269412695126961269712698126991270012701127021270312704127051270612707127081270912710127111271212713127141271512716127171271812719127201272112722127231272412725127261272712728127291273012731127321273312734127351273612737127381273912740127411274212743127441274512746127471274812749127501275112752127531275412755127561275712758127591276012761127621276312764127651276612767127681276912770127711277212773127741277512776127771277812779127801278112782127831278412785127861278712788127891279012791127921279312794127951279612797127981279912800128011280212803128041280512806128071280812809128101281112812128131281412815128161281712818128191282012821128221282312824128251282612827128281282912830128311283212833128341283512836128371283812839128401284112842128431284412845128461284712848128491285012851128521285312854128551285612857128581285912860128611286212863128641286512866128671286812869128701287112872128731287412875128761287712878128791288012881128821288312884128851288612887128881288912890128911289212893128941289512896128971289812899129001290112902129031290412905129061290712908129091291012911129121291312914129151291612917129181291912920129211292212923129241292512926129271292812929129301293112932129331293412935129361293712938129391294012941129421294312944129451294612947129481294912950129511295212953129541295512956129571295812959129601296112962129631296412965129661296712968129691297012971129721297312974129751297612977129781297912980129811298212983129841298512986129871298812989129901299112992129931299412995129961299712998129991300013001130021300313004130051300613007130081300913010130111301213013130141301513016130171301813019130201302113022130231302413025130261302713028130291303013031130321303313034130351303613037130381303913040130411304213043130441304513046130471304813049130501305113052130531305413055130561305713058130591306013061130621306313064130651306613067130681306913070130711307213073130741307513076130771307813079130801308113082130831308413085130861308713088130891309013091130921309313094130951309613097130981309913100131011310213103131041310513106131071310813109131101311113112131131311413115131161311713118131191312013121131221312313124131251312613127131281312913130131311313213133131341313513136131371313813139131401314113142131431314413145131461314713148131491315013151131521315313154131551315613157131581315913160131611316213163131641316513166131671316813169131701317113172131731317413175131761317713178131791318013181131821318313184131851318613187131881318913190131911319213193131941319513196131971319813199132001320113202132031320413205132061320713208132091321013211132121321313214132151321613217132181321913220132211322213223132241322513226132271322813229132301323113232132331323413235132361323713238132391324013241132421324313244132451324613247132481324913250132511325213253132541325513256132571325813259132601326113262132631326413265132661326713268132691327013271132721327313274132751327613277132781327913280132811328213283132841328513286132871328813289132901329113292132931329413295132961329713298132991330013301133021330313304133051330613307133081330913310133111331213313133141331513316133171331813319133201332113322133231332413325133261332713328133291333013331133321333313334133351333613337133381333913340133411334213343133441334513346133471334813349133501335113352133531335413355133561335713358133591336013361133621336313364133651336613367133681336913370133711337213373133741337513376133771337813379133801338113382133831338413385133861338713388133891339013391133921339313394133951339613397133981339913400134011340213403134041340513406134071340813409134101341113412134131341413415134161341713418134191342013421134221342313424134251342613427134281342913430134311343213433134341343513436134371343813439134401344113442134431344413445134461344713448134491345013451134521345313454134551345613457134581345913460134611346213463134641346513466134671346813469134701347113472134731347413475134761347713478134791348013481134821348313484134851348613487134881348913490134911349213493134941349513496134971349813499135001350113502135031350413505135061350713508135091351013511135121351313514135151351613517135181351913520135211352213523135241352513526135271352813529135301353113532135331353413535135361353713538135391354013541135421354313544135451354613547135481354913550135511355213553135541355513556135571355813559135601356113562135631356413565135661356713568135691357013571135721357313574135751357613577135781357913580135811358213583135841358513586135871358813589135901359113592135931359413595135961359713598135991360013601136021360313604136051360613607136081360913610136111361213613136141361513616136171361813619136201362113622136231362413625136261362713628136291363013631136321363313634136351363613637136381363913640136411364213643136441364513646136471364813649136501365113652136531365413655136561365713658136591366013661136621366313664136651366613667136681366913670136711367213673136741367513676136771367813679136801368113682136831368413685136861368713688136891369013691136921369313694136951369613697136981369913700137011370213703137041370513706137071370813709137101371113712137131371413715137161371713718137191372013721137221372313724137251372613727137281372913730137311373213733137341373513736137371373813739137401374113742137431374413745137461374713748137491375013751137521375313754137551375613757137581375913760137611376213763137641376513766137671376813769137701377113772137731377413775137761377713778137791378013781137821378313784137851378613787137881378913790137911379213793137941379513796137971379813799138001380113802138031380413805138061380713808138091381013811138121381313814138151381613817138181381913820138211382213823138241382513826138271382813829138301383113832138331383413835138361383713838138391384013841138421384313844138451384613847138481384913850138511385213853138541385513856138571385813859138601386113862138631386413865138661386713868138691387013871138721387313874138751387613877138781387913880138811388213883138841388513886138871388813889138901389113892138931389413895138961389713898138991390013901139021390313904139051390613907139081390913910139111391213913139141391513916139171391813919139201392113922139231392413925139261392713928139291393013931139321393313934139351393613937139381393913940139411394213943139441394513946139471394813949139501395113952139531395413955139561395713958139591396013961139621396313964139651396613967139681396913970139711397213973139741397513976139771397813979139801398113982139831398413985139861398713988139891399013991139921399313994139951399613997139981399914000140011400214003140041400514006140071400814009140101401114012140131401414015140161401714018140191402014021140221402314024140251402614027140281402914030140311403214033140341403514036140371403814039140401404114042140431404414045140461404714048140491405014051140521405314054140551405614057140581405914060140611406214063140641406514066140671406814069140701407114072140731407414075140761407714078140791408014081140821408314084140851408614087140881408914090140911409214093140941409514096140971409814099141001410114102141031410414105141061410714108141091411014111141121411314114141151411614117141181411914120141211412214123141241412514126141271412814129141301413114132141331413414135141361413714138141391414014141141421414314144141451414614147141481414914150141511415214153141541415514156141571415814159141601416114162141631416414165141661416714168141691417014171141721417314174141751417614177141781417914180141811418214183141841418514186141871418814189141901419114192141931419414195141961419714198141991420014201142021420314204142051420614207142081420914210142111421214213142141421514216142171421814219142201422114222142231422414225142261422714228142291423014231142321423314234142351423614237142381423914240142411424214243142441424514246142471424814249142501425114252142531425414255142561425714258142591426014261142621426314264142651426614267142681426914270142711427214273142741427514276142771427814279142801428114282142831428414285142861428714288142891429014291142921429314294142951429614297142981429914300143011430214303143041430514306143071430814309143101431114312143131431414315143161431714318143191432014321143221432314324143251432614327143281432914330143311433214333143341433514336143371433814339143401434114342143431434414345143461434714348143491435014351143521435314354143551435614357143581435914360143611436214363143641436514366143671436814369143701437114372143731437414375143761437714378143791438014381143821438314384143851438614387143881438914390143911439214393143941439514396143971439814399
  1. /*!
  2. * Copyright 2015 Drifty Co.
  3. * http://drifty.com/
  4. *
  5. * Ionic, v1.3.3
  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) && (view.historyId !== switchToView.historyId)) {
  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 its 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 depreciated
  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. * @restrict E
  11712. * @description
  11713. * The Slides component is a powerful multi-page container where each page can be swiped or dragged between.
  11714. *
  11715. * Note: this is a new version of the Ionic Slide Box based on the [Swiper](http://www.idangero.us/swiper/#.Vmc1J-ODFBc) widget from
  11716. * [idangerous](http://www.idangero.us/).
  11717. *
  11718. * ![SlideBox](http://ionicframework.com.s3.amazonaws.com/docs/controllers/slideBox.gif)
  11719. *
  11720. * @usage
  11721. * ```html
  11722. * <ion-content scroll="false">
  11723. * <ion-slides options="options" slider="data.slider">
  11724. * <ion-slide-page>
  11725. * <div class="box blue"><h1>BLUE</h1></div>
  11726. * </ion-slide-page>
  11727. * <ion-slide-page>
  11728. * <div class="box yellow"><h1>YELLOW</h1></div>
  11729. * </ion-slide-page>
  11730. * <ion-slide-page>
  11731. * <div class="box pink"><h1>PINK</h1></div>
  11732. * </ion-slide-page>
  11733. * </ion-slides>
  11734. * </ion-content>
  11735. * ```
  11736. *
  11737. * ```js
  11738. * $scope.options = {
  11739. * loop: false,
  11740. * effect: 'fade',
  11741. * speed: 500,
  11742. * }
  11743. *
  11744. * $scope.$on("$ionicSlides.sliderInitialized", function(event, data){
  11745. * // data.slider is the instance of Swiper
  11746. * $scope.slider = data.slider;
  11747. * });
  11748. *
  11749. * $scope.$on("$ionicSlides.slideChangeStart", function(event, data){
  11750. * console.log('Slide change is beginning');
  11751. * });
  11752. *
  11753. * $scope.$on("$ionicSlides.slideChangeEnd", function(event, data){
  11754. * // note: the indexes are 0-based
  11755. * $scope.activeIndex = data.slider.activeIndex;
  11756. * $scope.previousIndex = data.slider.previousIndex;
  11757. * });
  11758. *
  11759. * ```
  11760. *
  11761. * ## Slide Events
  11762. *
  11763. * The slides component dispatches events when the active slide changes
  11764. *
  11765. * <table class="table">
  11766. * <tr>
  11767. * <td><code>$ionicSlides.slideChangeStart</code></td>
  11768. * <td>This event is emitted when a slide change begins</td>
  11769. * </tr>
  11770. * <tr>
  11771. * <td><code>$ionicSlides.slideChangeEnd</code></td>
  11772. * <td>This event is emitted when a slide change completes</td>
  11773. * </tr>
  11774. * <tr>
  11775. * <td><code>$ionicSlides.sliderInitialized</code></td>
  11776. * <td>This event is emitted when the slider is initialized. It provides access to an instance of the slider.</td>
  11777. * </tr>
  11778. * </table>
  11779. *
  11780. *
  11781. * ## Updating Slides Dynamically
  11782. * When applying data to the slider at runtime, typically everything will work as expected.
  11783. *
  11784. * In the event that the slides are looped, use the `updateLoop` method on the slider to ensure the slides update correctly.
  11785. *
  11786. * ```
  11787. * $scope.$on("$ionicSlides.sliderInitialized", function(event, data){
  11788. * // grab an instance of the slider
  11789. * $scope.slider = data.slider;
  11790. * });
  11791. *
  11792. * function dataChangeHandler(){
  11793. * // call this function when data changes, such as an HTTP request, etc
  11794. * if ( $scope.slider ){
  11795. * $scope.slider.updateLoop();
  11796. * }
  11797. * }
  11798. * ```
  11799. *
  11800. */
  11801. IonicModule
  11802. .directive('ionSlides', [
  11803. '$animate',
  11804. '$timeout',
  11805. '$compile',
  11806. function($animate, $timeout, $compile) {
  11807. return {
  11808. restrict: 'E',
  11809. transclude: true,
  11810. scope: {
  11811. options: '=',
  11812. slider: '='
  11813. },
  11814. template: '<div class="swiper-container">' +
  11815. '<div class="swiper-wrapper" ng-transclude>' +
  11816. '</div>' +
  11817. '<div ng-hide="!showPager" class="swiper-pagination"></div>' +
  11818. '</div>',
  11819. controller: ['$scope', '$element', function($scope, $element) {
  11820. var _this = this;
  11821. this.update = function() {
  11822. $timeout(function() {
  11823. if (!_this.__slider) {
  11824. return;
  11825. }
  11826. _this.__slider.update();
  11827. if (_this._options.loop) {
  11828. _this.__slider.createLoop();
  11829. }
  11830. var slidesLength = _this.__slider.slides.length;
  11831. // Don't allow pager to show with > 10 slides
  11832. if (slidesLength > 10) {
  11833. $scope.showPager = false;
  11834. }
  11835. // When slide index is greater than total then slide to last index
  11836. if (_this.__slider.activeIndex > slidesLength - 1) {
  11837. _this.__slider.slideTo(slidesLength - 1);
  11838. }
  11839. });
  11840. };
  11841. this.rapidUpdate = ionic.debounce(function() {
  11842. _this.update();
  11843. }, 50);
  11844. this.getSlider = function() {
  11845. return _this.__slider;
  11846. };
  11847. var options = $scope.options || {};
  11848. var newOptions = angular.extend({
  11849. pagination: $element.children().children()[1],
  11850. paginationClickable: true,
  11851. lazyLoading: true,
  11852. preloadImages: false
  11853. }, options);
  11854. this._options = newOptions;
  11855. $timeout(function() {
  11856. var slider = new ionic.views.Swiper($element.children()[0], newOptions, $scope, $compile);
  11857. $scope.$emit("$ionicSlides.sliderInitialized", { slider: slider });
  11858. _this.__slider = slider;
  11859. $scope.slider = _this.__slider;
  11860. $scope.$on('$destroy', function() {
  11861. slider.destroy();
  11862. _this.__slider = null;
  11863. });
  11864. });
  11865. $timeout(function() {
  11866. // if it's a loop, render the slides again just incase
  11867. _this.rapidUpdate();
  11868. }, 200);
  11869. }],
  11870. link: function($scope) {
  11871. $scope.showPager = true;
  11872. // Disable ngAnimate for slidebox and its children
  11873. //$animate.enabled(false, $element);
  11874. }
  11875. };
  11876. }])
  11877. .directive('ionSlidePage', [function() {
  11878. return {
  11879. restrict: 'E',
  11880. require: '?^ionSlides',
  11881. transclude: true,
  11882. replace: true,
  11883. template: '<div class="swiper-slide" ng-transclude></div>',
  11884. link: function($scope, $element, $attr, ionSlidesCtrl) {
  11885. ionSlidesCtrl.rapidUpdate();
  11886. $scope.$on('$destroy', function() {
  11887. ionSlidesCtrl.rapidUpdate();
  11888. });
  11889. }
  11890. };
  11891. }]);
  11892. /**
  11893. * @ngdoc directive
  11894. * @name ionSpinner
  11895. * @module ionic
  11896. * @restrict E
  11897. *
  11898. * @description
  11899. * The `ionSpinner` directive provides a variety of animated spinners.
  11900. * Spinners enables you to give your users feedback that the app is
  11901. * processing/thinking/waiting/chillin' out, or whatever you'd like it to indicate.
  11902. * By default, the {@link ionic.directive:ionRefresher} feature uses this spinner, rather
  11903. * than rotating font icons (previously included in [ionicons](http://ionicons.com/)).
  11904. * While font icons are great for simple or stationary graphics, they're not suited to
  11905. * provide great animations, which is why Ionic uses SVG instead.
  11906. *
  11907. * Ionic offers ten spinners out of the box, and by default, it will use the appropriate spinner
  11908. * for the platform on which it's running. Under the hood, the `ionSpinner` directive dynamically
  11909. * builds the required SVG element, which allows Ionic to provide all ten of the animated SVGs
  11910. * within 3KB.
  11911. *
  11912. * <style>
  11913. * .spinner-table {
  11914. * max-width: 280px;
  11915. * }
  11916. * .spinner-table tbody > tr > th, .spinner-table tbody > tr > td {
  11917. * vertical-align: middle;
  11918. * width: 42px;
  11919. * height: 42px;
  11920. * }
  11921. * .spinner {
  11922. * stroke: #444;
  11923. * fill: #444; }
  11924. * .spinner svg {
  11925. * width: 28px;
  11926. * height: 28px; }
  11927. * .spinner.spinner-inverse {
  11928. * stroke: #fff;
  11929. * fill: #fff; }
  11930. *
  11931. * .spinner-android {
  11932. * stroke: #4b8bf4; }
  11933. *
  11934. * .spinner-ios, .spinner-ios-small {
  11935. * stroke: #69717d; }
  11936. *
  11937. * .spinner-spiral .stop1 {
  11938. * stop-color: #fff;
  11939. * stop-opacity: 0; }
  11940. * .spinner-spiral.spinner-inverse .stop1 {
  11941. * stop-color: #000; }
  11942. * .spinner-spiral.spinner-inverse .stop2 {
  11943. * stop-color: #fff; }
  11944. * </style>
  11945. *
  11946. * <script src="http://code.ionicframework.com/nightly/js/ionic.bundle.min.js"></script>
  11947. * <table class="table spinner-table" ng-app="ionic">
  11948. * <tr>
  11949. * <th>
  11950. * <code>android</code>
  11951. * </th>
  11952. * <td>
  11953. * <ion-spinner icon="android"></ion-spinner>
  11954. * </td>
  11955. * </tr>
  11956. * <tr>
  11957. * <th>
  11958. * <code>ios</code>
  11959. * </th>
  11960. * <td>
  11961. * <ion-spinner icon="ios"></ion-spinner>
  11962. * </td>
  11963. * </tr>
  11964. * <tr>
  11965. * <th>
  11966. * <code>ios-small</code>
  11967. * </th>
  11968. * <td>
  11969. * <ion-spinner icon="ios-small"></ion-spinner>
  11970. * </td>
  11971. * </tr>
  11972. * <tr>
  11973. * <th>
  11974. * <code>bubbles</code>
  11975. * </th>
  11976. * <td>
  11977. * <ion-spinner icon="bubbles"></ion-spinner>
  11978. * </td>
  11979. * </tr>
  11980. * <tr>
  11981. * <th>
  11982. * <code>circles</code>
  11983. * </th>
  11984. * <td>
  11985. * <ion-spinner icon="circles"></ion-spinner>
  11986. * </td>
  11987. * </tr>
  11988. * <tr>
  11989. * <th>
  11990. * <code>crescent</code>
  11991. * </th>
  11992. * <td>
  11993. * <ion-spinner icon="crescent"></ion-spinner>
  11994. * </td>
  11995. * </tr>
  11996. * <tr>
  11997. * <th>
  11998. * <code>dots</code>
  11999. * </th>
  12000. * <td>
  12001. * <ion-spinner icon="dots"></ion-spinner>
  12002. * </td>
  12003. * </tr>
  12004. * <tr>
  12005. * <th>
  12006. * <code>lines</code>
  12007. * </th>
  12008. * <td>
  12009. * <ion-spinner icon="lines"></ion-spinner>
  12010. * </td>
  12011. * </tr>
  12012. * <tr>
  12013. * <th>
  12014. * <code>ripple</code>
  12015. * </th>
  12016. * <td>
  12017. * <ion-spinner icon="ripple"></ion-spinner>
  12018. * </td>
  12019. * </tr>
  12020. * <tr>
  12021. * <th>
  12022. * <code>spiral</code>
  12023. * </th>
  12024. * <td>
  12025. * <ion-spinner icon="spiral"></ion-spinner>
  12026. * </td>
  12027. * </tr>
  12028. * </table>
  12029. *
  12030. * Each spinner uses SVG with SMIL animations, however, the Android spinner also uses JavaScript
  12031. * so it also works on Android 4.0-4.3. Additionally, each spinner can be styled with CSS,
  12032. * and scaled to any size.
  12033. *
  12034. *
  12035. * @usage
  12036. * The following code would use the default spinner for the platform it's running from. If it's neither
  12037. * iOS or Android, it'll default to use `ios`.
  12038. *
  12039. * ```html
  12040. * <ion-spinner></ion-spinner>
  12041. * ```
  12042. *
  12043. * By setting the `icon` attribute, you can specify which spinner to use, no matter what
  12044. * the platform is.
  12045. *
  12046. * ```html
  12047. * <ion-spinner icon="spiral"></ion-spinner>
  12048. * ```
  12049. *
  12050. * ## Spinner Colors
  12051. * Like with most of Ionic's other components, spinners can also be styled using
  12052. * Ionic's standard color naming convention. For example:
  12053. *
  12054. * ```html
  12055. * <ion-spinner class="spinner-energized"></ion-spinner>
  12056. * ```
  12057. *
  12058. *
  12059. * ## Styling SVG with CSS
  12060. * One cool thing about SVG is its ability to be styled with CSS! Some of the properties
  12061. * have different names, for example, SVG uses the term `stroke` instead of `border`, and
  12062. * `fill` instead of `background-color`.
  12063. *
  12064. * ```css
  12065. * .spinner svg {
  12066. * width: 28px;
  12067. * height: 28px;
  12068. * stroke: #444;
  12069. * fill: #444;
  12070. * }
  12071. * ```
  12072. *
  12073. */
  12074. IonicModule
  12075. .directive('ionSpinner', function() {
  12076. return {
  12077. restrict: 'E',
  12078. controller: '$ionicSpinner',
  12079. link: function($scope, $element, $attrs, ctrl) {
  12080. var spinnerName = ctrl.init();
  12081. $element.addClass('spinner spinner-' + spinnerName);
  12082. $element.on('$destroy', function onDestroy() {
  12083. ctrl.stop();
  12084. });
  12085. }
  12086. };
  12087. });
  12088. /**
  12089. * @ngdoc directive
  12090. * @name ionTab
  12091. * @module ionic
  12092. * @restrict E
  12093. * @parent ionic.directive:ionTabs
  12094. *
  12095. * @description
  12096. * Contains a tab's content. The content only exists while the given tab is selected.
  12097. *
  12098. * Each ionTab has its own view history.
  12099. *
  12100. * @usage
  12101. * ```html
  12102. * <ion-tab
  12103. * title="Tab!"
  12104. * icon="my-icon"
  12105. * href="#/tab/tab-link"
  12106. * on-select="onTabSelected()"
  12107. * on-deselect="onTabDeselected()">
  12108. * </ion-tab>
  12109. * ```
  12110. * For a complete, working tab bar example, see the {@link ionic.directive:ionTabs} documentation.
  12111. *
  12112. * @param {string} title The title of the tab.
  12113. * @param {string=} href The link that this tab will navigate to when tapped.
  12114. * @param {string=} icon The icon of the tab. If given, this will become the default for icon-on and icon-off.
  12115. * @param {string=} icon-on The icon of the tab while it is selected.
  12116. * @param {string=} icon-off The icon of the tab while it is not selected.
  12117. * @param {expression=} badge The badge to put on this tab (usually a number).
  12118. * @param {expression=} badge-style The style of badge to put on this tab (eg: badge-positive).
  12119. * @param {expression=} on-select Called when this tab is selected.
  12120. * @param {expression=} on-deselect Called when this tab is deselected.
  12121. * @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()}.
  12122. * @param {expression=} hidden Whether the tab is to be hidden or not.
  12123. * @param {expression=} disabled Whether the tab is to be disabled or not.
  12124. */
  12125. IonicModule
  12126. .directive('ionTab', [
  12127. '$compile',
  12128. '$ionicConfig',
  12129. '$ionicBind',
  12130. '$ionicViewSwitcher',
  12131. function($compile, $ionicConfig, $ionicBind, $ionicViewSwitcher) {
  12132. //Returns ' key="value"' if value exists
  12133. function attrStr(k, v) {
  12134. return isDefined(v) ? ' ' + k + '="' + v + '"' : '';
  12135. }
  12136. return {
  12137. restrict: 'E',
  12138. require: ['^ionTabs', 'ionTab'],
  12139. controller: '$ionicTab',
  12140. scope: true,
  12141. compile: function(element, attr) {
  12142. //We create the tabNavTemplate in the compile phase so that the
  12143. //attributes we pass down won't be interpolated yet - we want
  12144. //to pass down the 'raw' versions of the attributes
  12145. var tabNavTemplate = '<ion-tab-nav' +
  12146. attrStr('ng-click', attr.ngClick) +
  12147. attrStr('title', attr.title) +
  12148. attrStr('icon', attr.icon) +
  12149. attrStr('icon-on', attr.iconOn) +
  12150. attrStr('icon-off', attr.iconOff) +
  12151. attrStr('badge', attr.badge) +
  12152. attrStr('badge-style', attr.badgeStyle) +
  12153. attrStr('hidden', attr.hidden) +
  12154. attrStr('disabled', attr.disabled) +
  12155. attrStr('class', attr['class']) +
  12156. '></ion-tab-nav>';
  12157. //Remove the contents of the element so we can compile them later, if tab is selected
  12158. var tabContentEle = document.createElement('div');
  12159. for (var x = 0; x < element[0].children.length; x++) {
  12160. tabContentEle.appendChild(element[0].children[x].cloneNode(true));
  12161. }
  12162. var childElementCount = tabContentEle.childElementCount;
  12163. element.empty();
  12164. var navViewName, isNavView;
  12165. if (childElementCount) {
  12166. if (tabContentEle.children[0].tagName === 'ION-NAV-VIEW') {
  12167. // get the name if it's a nav-view
  12168. navViewName = tabContentEle.children[0].getAttribute('name');
  12169. tabContentEle.children[0].classList.add('view-container');
  12170. isNavView = true;
  12171. }
  12172. if (childElementCount === 1) {
  12173. // make the 1 child element the primary tab content container
  12174. tabContentEle = tabContentEle.children[0];
  12175. }
  12176. if (!isNavView) tabContentEle.classList.add('pane');
  12177. tabContentEle.classList.add('tab-content');
  12178. }
  12179. return function link($scope, $element, $attr, ctrls) {
  12180. var childScope;
  12181. var childElement;
  12182. var tabsCtrl = ctrls[0];
  12183. var tabCtrl = ctrls[1];
  12184. var isTabContentAttached = false;
  12185. $scope.$tabSelected = false;
  12186. $ionicBind($scope, $attr, {
  12187. onSelect: '&',
  12188. onDeselect: '&',
  12189. title: '@',
  12190. uiSref: '@',
  12191. href: '@'
  12192. });
  12193. tabsCtrl.add($scope);
  12194. $scope.$on('$destroy', function() {
  12195. if (!$scope.$tabsDestroy) {
  12196. // if the containing ionTabs directive is being destroyed
  12197. // then don't bother going through the controllers remove
  12198. // method, since remove will reset the active tab as each tab
  12199. // is being destroyed, causing unnecessary view loads and transitions
  12200. tabsCtrl.remove($scope);
  12201. }
  12202. tabNavElement.isolateScope().$destroy();
  12203. tabNavElement.remove();
  12204. tabNavElement = tabContentEle = childElement = null;
  12205. });
  12206. //Remove title attribute so browser-tooltip does not apear
  12207. $element[0].removeAttribute('title');
  12208. if (navViewName) {
  12209. tabCtrl.navViewName = $scope.navViewName = navViewName;
  12210. }
  12211. $scope.$on('$stateChangeSuccess', selectIfMatchesState);
  12212. selectIfMatchesState();
  12213. function selectIfMatchesState() {
  12214. if (tabCtrl.tabMatchesState()) {
  12215. tabsCtrl.select($scope, false);
  12216. }
  12217. }
  12218. var tabNavElement = jqLite(tabNavTemplate);
  12219. tabNavElement.data('$ionTabsController', tabsCtrl);
  12220. tabNavElement.data('$ionTabController', tabCtrl);
  12221. tabsCtrl.$tabsElement.append($compile(tabNavElement)($scope));
  12222. function tabSelected(isSelected) {
  12223. if (isSelected && childElementCount) {
  12224. // this tab is being selected
  12225. // check if the tab is already in the DOM
  12226. // only do this if the tab has child elements
  12227. if (!isTabContentAttached) {
  12228. // tab should be selected and is NOT in the DOM
  12229. // create a new scope and append it
  12230. childScope = $scope.$new();
  12231. childElement = jqLite(tabContentEle);
  12232. $ionicViewSwitcher.viewEleIsActive(childElement, true);
  12233. tabsCtrl.$element.append(childElement);
  12234. $compile(childElement)(childScope);
  12235. isTabContentAttached = true;
  12236. }
  12237. // remove the hide class so the tabs content shows up
  12238. $ionicViewSwitcher.viewEleIsActive(childElement, true);
  12239. } else if (isTabContentAttached && childElement) {
  12240. // this tab should NOT be selected, and it is already in the DOM
  12241. if ($ionicConfig.views.maxCache() > 0) {
  12242. // keep the tabs in the DOM, only css hide it
  12243. $ionicViewSwitcher.viewEleIsActive(childElement, false);
  12244. } else {
  12245. // do not keep tabs in the DOM
  12246. destroyTab();
  12247. }
  12248. }
  12249. }
  12250. function destroyTab() {
  12251. childScope && childScope.$destroy();
  12252. isTabContentAttached && childElement && childElement.remove();
  12253. tabContentEle.innerHTML = '';
  12254. isTabContentAttached = childScope = childElement = null;
  12255. }
  12256. $scope.$watch('$tabSelected', tabSelected);
  12257. $scope.$on('$ionicView.afterEnter', function() {
  12258. $ionicViewSwitcher.viewEleIsActive(childElement, $scope.$tabSelected);
  12259. });
  12260. $scope.$on('$ionicView.clearCache', function() {
  12261. if (!$scope.$tabSelected) {
  12262. destroyTab();
  12263. }
  12264. });
  12265. };
  12266. }
  12267. };
  12268. }]);
  12269. IonicModule
  12270. .directive('ionTabNav', [function() {
  12271. return {
  12272. restrict: 'E',
  12273. replace: true,
  12274. require: ['^ionTabs', '^ionTab'],
  12275. template:
  12276. '<a ng-class="{\'has-badge\':badge, \'tab-hidden\':isHidden(), \'tab-item-active\': isTabActive()}" ' +
  12277. ' ng-disabled="disabled()" class="tab-item">' +
  12278. '<span class="badge {{badgeStyle}}" ng-if="badge">{{badge}}</span>' +
  12279. '<i class="icon {{getIcon()}}" ng-if="getIcon()"></i>' +
  12280. '<span class="tab-title" ng-bind-html="title"></span>' +
  12281. '</a>',
  12282. scope: {
  12283. title: '@',
  12284. icon: '@',
  12285. iconOn: '@',
  12286. iconOff: '@',
  12287. badge: '=',
  12288. hidden: '@',
  12289. disabled: '&',
  12290. badgeStyle: '@',
  12291. 'class': '@'
  12292. },
  12293. link: function($scope, $element, $attrs, ctrls) {
  12294. var tabsCtrl = ctrls[0],
  12295. tabCtrl = ctrls[1];
  12296. //Remove title attribute so browser-tooltip does not apear
  12297. $element[0].removeAttribute('title');
  12298. $scope.selectTab = function(e) {
  12299. e.preventDefault();
  12300. tabsCtrl.select(tabCtrl.$scope, true);
  12301. };
  12302. if (!$attrs.ngClick) {
  12303. $element.on('click', function(event) {
  12304. $scope.$apply(function() {
  12305. $scope.selectTab(event);
  12306. });
  12307. });
  12308. }
  12309. $scope.isHidden = function() {
  12310. if ($attrs.hidden === 'true' || $attrs.hidden === true) return true;
  12311. return false;
  12312. };
  12313. $scope.getIconOn = function() {
  12314. return $scope.iconOn || $scope.icon;
  12315. };
  12316. $scope.getIconOff = function() {
  12317. return $scope.iconOff || $scope.icon;
  12318. };
  12319. $scope.isTabActive = function() {
  12320. return tabsCtrl.selectedTab() === tabCtrl.$scope;
  12321. };
  12322. $scope.getIcon = function() {
  12323. if ( tabsCtrl.selectedTab() === tabCtrl.$scope ) {
  12324. // active
  12325. return $scope.iconOn || $scope.icon;
  12326. }
  12327. else {
  12328. // inactive
  12329. return $scope.iconOff || $scope.icon;
  12330. }
  12331. };
  12332. }
  12333. };
  12334. }]);
  12335. /**
  12336. * @ngdoc directive
  12337. * @name ionTabs
  12338. * @module ionic
  12339. * @delegate ionic.service:$ionicTabsDelegate
  12340. * @restrict E
  12341. * @codepen odqCz
  12342. *
  12343. * @description
  12344. * Powers a multi-tabbed interface with a Tab Bar and a set of "pages" that can be tabbed
  12345. * through.
  12346. *
  12347. * Assign any [tabs class](/docs/components#tabs) to the element to define
  12348. * its look and feel.
  12349. *
  12350. * For iOS, tabs will appear at the bottom of the screen. For Android, tabs will be at the top
  12351. * of the screen, below the nav-bar. This follows each OS's design specification, but can be
  12352. * configured with the {@link ionic.provider:$ionicConfigProvider}.
  12353. *
  12354. * See the {@link ionic.directive:ionTab} directive's documentation for more details on
  12355. * individual tabs.
  12356. *
  12357. * Note: do not place ion-tabs inside of an ion-content element; it has been known to cause a
  12358. * certain CSS bug.
  12359. *
  12360. * @usage
  12361. * ```html
  12362. * <ion-tabs class="tabs-positive tabs-icon-top">
  12363. *
  12364. * <ion-tab title="Home" icon-on="ion-ios-filing" icon-off="ion-ios-filing-outline">
  12365. * <!-- Tab 1 content -->
  12366. * </ion-tab>
  12367. *
  12368. * <ion-tab title="About" icon-on="ion-ios-clock" icon-off="ion-ios-clock-outline">
  12369. * <!-- Tab 2 content -->
  12370. * </ion-tab>
  12371. *
  12372. * <ion-tab title="Settings" icon-on="ion-ios-gear" icon-off="ion-ios-gear-outline">
  12373. * <!-- Tab 3 content -->
  12374. * </ion-tab>
  12375. *
  12376. * </ion-tabs>
  12377. * ```
  12378. *
  12379. * @param {string=} delegate-handle The handle used to identify these tabs
  12380. * with {@link ionic.service:$ionicTabsDelegate}.
  12381. */
  12382. IonicModule
  12383. .directive('ionTabs', [
  12384. '$ionicTabsDelegate',
  12385. '$ionicConfig',
  12386. function($ionicTabsDelegate, $ionicConfig) {
  12387. return {
  12388. restrict: 'E',
  12389. scope: true,
  12390. controller: '$ionicTabs',
  12391. compile: function(tElement) {
  12392. //We cannot use regular transclude here because it breaks element.data()
  12393. //inheritance on compile
  12394. var innerElement = jqLite('<div class="tab-nav tabs">');
  12395. innerElement.append(tElement.contents());
  12396. tElement.append(innerElement)
  12397. .addClass('tabs-' + $ionicConfig.tabs.position() + ' tabs-' + $ionicConfig.tabs.style());
  12398. return { pre: prelink, post: postLink };
  12399. function prelink($scope, $element, $attr, tabsCtrl) {
  12400. var deregisterInstance = $ionicTabsDelegate._registerInstance(
  12401. tabsCtrl, $attr.delegateHandle, tabsCtrl.hasActiveScope
  12402. );
  12403. tabsCtrl.$scope = $scope;
  12404. tabsCtrl.$element = $element;
  12405. tabsCtrl.$tabsElement = jqLite($element[0].querySelector('.tabs'));
  12406. $scope.$watch(function() { return $element[0].className; }, function(value) {
  12407. var isTabsTop = value.indexOf('tabs-top') !== -1;
  12408. var isHidden = value.indexOf('tabs-item-hide') !== -1;
  12409. $scope.$hasTabs = !isTabsTop && !isHidden;
  12410. $scope.$hasTabsTop = isTabsTop && !isHidden;
  12411. $scope.$emit('$ionicTabs.top', $scope.$hasTabsTop);
  12412. });
  12413. function emitLifecycleEvent(ev, data) {
  12414. ev.stopPropagation();
  12415. var previousSelectedTab = tabsCtrl.previousSelectedTab();
  12416. if (previousSelectedTab) {
  12417. previousSelectedTab.$broadcast(ev.name.replace('NavView', 'Tabs'), data);
  12418. }
  12419. }
  12420. $scope.$on('$ionicNavView.beforeLeave', emitLifecycleEvent);
  12421. $scope.$on('$ionicNavView.afterLeave', emitLifecycleEvent);
  12422. $scope.$on('$ionicNavView.leave', emitLifecycleEvent);
  12423. $scope.$on('$destroy', function() {
  12424. // variable to inform child tabs that they're all being blown away
  12425. // used so that while destorying an individual tab, each one
  12426. // doesn't select the next tab as the active one, which causes unnecessary
  12427. // loading of tab views when each will eventually all go away anyway
  12428. $scope.$tabsDestroy = true;
  12429. deregisterInstance();
  12430. tabsCtrl.$tabsElement = tabsCtrl.$element = tabsCtrl.$scope = innerElement = null;
  12431. delete $scope.$hasTabs;
  12432. delete $scope.$hasTabsTop;
  12433. });
  12434. }
  12435. function postLink($scope, $element, $attr, tabsCtrl) {
  12436. if (!tabsCtrl.selectedTab()) {
  12437. // all the tabs have been added
  12438. // but one hasn't been selected yet
  12439. tabsCtrl.select(0);
  12440. }
  12441. }
  12442. }
  12443. };
  12444. }]);
  12445. /**
  12446. * @ngdoc directive
  12447. * @name ionTitle
  12448. * @module ionic
  12449. * @restrict E
  12450. *
  12451. * Used for titles in header and nav bars. New in 1.2
  12452. *
  12453. * Identical to <div class="title"> but with future compatibility for Ionic 2
  12454. *
  12455. * @usage
  12456. *
  12457. * ```html
  12458. * <ion-nav-bar>
  12459. * <ion-title>Hello</ion-title>
  12460. * <ion-nav-bar>
  12461. * ```
  12462. */
  12463. IonicModule
  12464. .directive('ionTitle', [function() {
  12465. return {
  12466. restrict: 'E',
  12467. compile: function(element) {
  12468. element.addClass('title');
  12469. }
  12470. };
  12471. }]);
  12472. /**
  12473. * @ngdoc directive
  12474. * @name ionToggle
  12475. * @module ionic
  12476. * @codepen tfAzj
  12477. * @restrict E
  12478. *
  12479. * @description
  12480. * A toggle is an animated switch which binds a given model to a boolean.
  12481. *
  12482. * Allows dragging of the switch's nub.
  12483. *
  12484. * The toggle behaves like any [AngularJS checkbox](http://docs.angularjs.org/api/ng/input/input[checkbox]) otherwise.
  12485. *
  12486. * @param toggle-class {string=} Sets the CSS class on the inner `label.toggle` element created by the directive.
  12487. *
  12488. * @usage
  12489. * Below is an example of a toggle directive which is wired up to the `airplaneMode` model
  12490. * and has the `toggle-calm` CSS class assigned to the inner element.
  12491. *
  12492. * ```html
  12493. * <ion-toggle ng-model="airplaneMode" toggle-class="toggle-calm">Airplane Mode</ion-toggle>
  12494. * ```
  12495. */
  12496. IonicModule
  12497. .directive('ionToggle', [
  12498. '$timeout',
  12499. '$ionicConfig',
  12500. function($timeout, $ionicConfig) {
  12501. return {
  12502. restrict: 'E',
  12503. replace: true,
  12504. require: '?ngModel',
  12505. transclude: true,
  12506. template:
  12507. '<div class="item item-toggle">' +
  12508. '<div ng-transclude></div>' +
  12509. '<label class="toggle">' +
  12510. '<input type="checkbox">' +
  12511. '<div class="track">' +
  12512. '<div class="handle"></div>' +
  12513. '</div>' +
  12514. '</label>' +
  12515. '</div>',
  12516. compile: function(element, attr) {
  12517. var input = element.find('input');
  12518. forEach({
  12519. 'name': attr.name,
  12520. 'ng-value': attr.ngValue,
  12521. 'ng-model': attr.ngModel,
  12522. 'ng-checked': attr.ngChecked,
  12523. 'ng-disabled': attr.ngDisabled,
  12524. 'ng-true-value': attr.ngTrueValue,
  12525. 'ng-false-value': attr.ngFalseValue,
  12526. 'ng-change': attr.ngChange,
  12527. 'ng-required': attr.ngRequired,
  12528. 'required': attr.required
  12529. }, function(value, name) {
  12530. if (isDefined(value)) {
  12531. input.attr(name, value);
  12532. }
  12533. });
  12534. if (attr.toggleClass) {
  12535. element[0].getElementsByTagName('label')[0].classList.add(attr.toggleClass);
  12536. }
  12537. element.addClass('toggle-' + $ionicConfig.form.toggle());
  12538. return function($scope, $element) {
  12539. var el = $element[0].getElementsByTagName('label')[0];
  12540. var checkbox = el.children[0];
  12541. var track = el.children[1];
  12542. var handle = track.children[0];
  12543. var ngModelController = jqLite(checkbox).controller('ngModel');
  12544. $scope.toggle = new ionic.views.Toggle({
  12545. el: el,
  12546. track: track,
  12547. checkbox: checkbox,
  12548. handle: handle,
  12549. onChange: function() {
  12550. if (ngModelController) {
  12551. ngModelController.$setViewValue(checkbox.checked);
  12552. $scope.$apply();
  12553. }
  12554. }
  12555. });
  12556. $scope.$on('$destroy', function() {
  12557. $scope.toggle.destroy();
  12558. });
  12559. };
  12560. }
  12561. };
  12562. }]);
  12563. /**
  12564. * @ngdoc directive
  12565. * @name ionView
  12566. * @module ionic
  12567. * @restrict E
  12568. * @parent ionNavView
  12569. *
  12570. * @description
  12571. * A container for view content and any navigational and header bar information. When a view
  12572. * enters and exits its parent {@link ionic.directive:ionNavView}, the view also emits view
  12573. * information, such as its title, whether the back button should be displayed or not, whether the
  12574. * corresponding {@link ionic.directive:ionNavBar} should be displayed or not, which transition the view
  12575. * should use to animate, and which direction to animate.
  12576. *
  12577. * *Views are cached to improve performance.* When a view is navigated away from, its element is
  12578. * left in the DOM, and its scope is disconnected from the `$watch` cycle. When navigating to a
  12579. * view that is already cached, its scope is reconnected, and the existing element, which was
  12580. * left in the DOM, becomes active again. This can be disabled, or the maximum number of cached
  12581. * views changed in {@link ionic.provider:$ionicConfigProvider}, in the view's `$state` configuration, or
  12582. * as an attribute on the view itself (see below).
  12583. *
  12584. * @usage
  12585. * Below is an example where our page will load with a {@link ionic.directive:ionNavBar} containing
  12586. * "My Page" as the title.
  12587. *
  12588. * ```html
  12589. * <ion-nav-bar></ion-nav-bar>
  12590. * <ion-nav-view>
  12591. * <ion-view view-title="My Page">
  12592. * <ion-content>
  12593. * Hello!
  12594. * </ion-content>
  12595. * </ion-view>
  12596. * </ion-nav-view>
  12597. * ```
  12598. *
  12599. * ## View LifeCycle and Events
  12600. *
  12601. * Views can be cached, which means ***controllers normally only load once***, which may
  12602. * affect your controller logic. To know when a view has entered or left, events
  12603. * have been added that are emitted from the view's scope. These events also
  12604. * contain data about the view, such as the title and whether the back button should
  12605. * show. Also contained is transition data, such as the transition type and
  12606. * direction that will be or was used.
  12607. *
  12608. * Life cycle events are emitted upwards from the transitioning view's scope. In some cases, it is
  12609. * desirable for a child/nested view to be notified of the event.
  12610. * For this use case, `$ionicParentView` life cycle events are broadcast downwards.
  12611. *
  12612. * <table class="table">
  12613. * <tr>
  12614. * <td><code>$ionicView.loaded</code></td>
  12615. * <td>The view has loaded. This event only happens once per
  12616. * view being created and added to the DOM. If a view leaves but is cached,
  12617. * then this event will not fire again on a subsequent viewing. The loaded event
  12618. * is good place to put your setup code for the view; however, it is not the
  12619. * recommended event to listen to when a view becomes active.</td>
  12620. * </tr>
  12621. * <tr>
  12622. * <td><code>$ionicView.enter</code></td>
  12623. * <td>The view has fully entered and is now the active view.
  12624. * This event will fire, whether it was the first load or a cached view.</td>
  12625. * </tr>
  12626. * <tr>
  12627. * <td><code>$ionicView.leave</code></td>
  12628. * <td>The view has finished leaving and is no longer the
  12629. * active view. This event will fire, whether it is cached or destroyed.</td>
  12630. * </tr>
  12631. * <tr>
  12632. * <td><code>$ionicView.beforeEnter</code></td>
  12633. * <td>The view is about to enter and become the active view.</td>
  12634. * </tr>
  12635. * <tr>
  12636. * <td><code>$ionicView.beforeLeave</code></td>
  12637. * <td>The view is about to leave and no longer be the active view.</td>
  12638. * </tr>
  12639. * <tr>
  12640. * <td><code>$ionicView.afterEnter</code></td>
  12641. * <td>The view has fully entered and is now the active view.</td>
  12642. * </tr>
  12643. * <tr>
  12644. * <td><code>$ionicView.afterLeave</code></td>
  12645. * <td>The view has finished leaving and is no longer the active view.</td>
  12646. * </tr>
  12647. * <tr>
  12648. * <td><code>$ionicView.unloaded</code></td>
  12649. * <td>The view's controller has been destroyed and its element has been
  12650. * removed from the DOM.</td>
  12651. * </tr>
  12652. * <tr>
  12653. * <td><code>$ionicParentView.enter</code></td>
  12654. * <td>The parent view has fully entered and is now the active view.
  12655. * This event will fire, whether it was the first load or a cached view.</td>
  12656. * </tr>
  12657. * <tr>
  12658. * <td><code>$ionicParentView.leave</code></td>
  12659. * <td>The parent view has finished leaving and is no longer the
  12660. * active view. This event will fire, whether it is cached or destroyed.</td>
  12661. * </tr>
  12662. * <tr>
  12663. * <td><code>$ionicParentView.beforeEnter</code></td>
  12664. * <td>The parent view is about to enter and become the active view.</td>
  12665. * </tr>
  12666. * <tr>
  12667. * <td><code>$ionicParentView.beforeLeave</code></td>
  12668. * <td>The parent view is about to leave and no longer be the active view.</td>
  12669. * </tr>
  12670. * <tr>
  12671. * <td><code>$ionicParentView.afterEnter</code></td>
  12672. * <td>The parent view has fully entered and is now the active view.</td>
  12673. * </tr>
  12674. * <tr>
  12675. * <td><code>$ionicParentView.afterLeave</code></td>
  12676. * <td>The parent view has finished leaving and is no longer the active view.</td>
  12677. * </tr>
  12678. * </table>
  12679. *
  12680. * ## LifeCycle Event Usage
  12681. *
  12682. * Below is an example of how to listen to life cycle events and
  12683. * access state parameter data
  12684. *
  12685. * ```js
  12686. * $scope.$on("$ionicView.beforeEnter", function(event, data){
  12687. * // handle event
  12688. * console.log("State Params: ", data.stateParams);
  12689. * });
  12690. *
  12691. * $scope.$on("$ionicView.enter", function(event, data){
  12692. * // handle event
  12693. * console.log("State Params: ", data.stateParams);
  12694. * });
  12695. *
  12696. * $scope.$on("$ionicView.afterEnter", function(event, data){
  12697. * // handle event
  12698. * console.log("State Params: ", data.stateParams);
  12699. * });
  12700. * ```
  12701. *
  12702. * ## Caching
  12703. *
  12704. * Caching can be disabled and enabled in multiple ways. By default, Ionic will
  12705. * cache a maximum of 10 views. You can optionally choose to disable caching at
  12706. * either an individual view basis, or by global configuration. Please see the
  12707. * _Caching_ section in {@link ionic.directive:ionNavView} for more info.
  12708. *
  12709. * @param {string=} view-title A text-only title to display on the parent {@link ionic.directive:ionNavBar}.
  12710. * For an HTML title, such as an image, see {@link ionic.directive:ionNavTitle} instead.
  12711. * @param {boolean=} cache-view If this view should be allowed to be cached or not.
  12712. * Please see the _Caching_ section in {@link ionic.directive:ionNavView} for
  12713. * more info. Default `true`
  12714. * @param {boolean=} can-swipe-back If this view should be allowed to use the swipe to go back gesture or not.
  12715. * This does not enable the swipe to go back feature if it is not available for the platform it's running
  12716. * from, or there isn't a previous view. Default `true`
  12717. * @param {boolean=} hide-back-button Whether to hide the back button on the parent
  12718. * {@link ionic.directive:ionNavBar} by default.
  12719. * @param {boolean=} hide-nav-bar Whether to hide the parent
  12720. * {@link ionic.directive:ionNavBar} by default.
  12721. */
  12722. IonicModule
  12723. .directive('ionView', function() {
  12724. return {
  12725. restrict: 'EA',
  12726. priority: 1000,
  12727. controller: '$ionicView',
  12728. compile: function(tElement) {
  12729. tElement.addClass('pane');
  12730. tElement[0].removeAttribute('title');
  12731. return function link($scope, $element, $attrs, viewCtrl) {
  12732. viewCtrl.init();
  12733. };
  12734. }
  12735. };
  12736. });
  12737. })();