ionic.js 461 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521552255235524552555265527552855295530553155325533553455355536553755385539554055415542554355445545554655475548554955505551555255535554555555565557555855595560556155625563556455655566556755685569557055715572557355745575557655775578557955805581558255835584558555865587558855895590559155925593559455955596559755985599560056015602560356045605560656075608560956105611561256135614561556165617561856195620562156225623562456255626562756285629563056315632563356345635563656375638563956405641564256435644564556465647564856495650565156525653565456555656565756585659566056615662566356645665566656675668566956705671567256735674567556765677567856795680568156825683568456855686568756885689569056915692569356945695569656975698569957005701570257035704570557065707570857095710571157125713571457155716571757185719572057215722572357245725572657275728572957305731573257335734573557365737573857395740574157425743574457455746574757485749575057515752575357545755575657575758575957605761576257635764576557665767576857695770577157725773577457755776577757785779578057815782578357845785578657875788578957905791579257935794579557965797579857995800580158025803580458055806580758085809581058115812581358145815581658175818581958205821582258235824582558265827582858295830583158325833583458355836583758385839584058415842584358445845584658475848584958505851585258535854585558565857585858595860586158625863586458655866586758685869587058715872587358745875587658775878587958805881588258835884588558865887588858895890589158925893589458955896589758985899590059015902590359045905590659075908590959105911591259135914591559165917591859195920592159225923592459255926592759285929593059315932593359345935593659375938593959405941594259435944594559465947594859495950595159525953595459555956595759585959596059615962596359645965596659675968596959705971597259735974597559765977597859795980598159825983598459855986598759885989599059915992599359945995599659975998599960006001600260036004600560066007600860096010601160126013601460156016601760186019602060216022602360246025602660276028602960306031603260336034603560366037603860396040604160426043604460456046604760486049605060516052605360546055605660576058605960606061606260636064606560666067606860696070607160726073607460756076607760786079608060816082608360846085608660876088608960906091609260936094609560966097609860996100610161026103610461056106610761086109611061116112611361146115611661176118611961206121612261236124612561266127612861296130613161326133613461356136613761386139614061416142614361446145614661476148614961506151615261536154615561566157615861596160616161626163616461656166616761686169617061716172617361746175617661776178617961806181618261836184618561866187618861896190619161926193619461956196619761986199620062016202620362046205620662076208620962106211621262136214621562166217621862196220622162226223622462256226622762286229623062316232623362346235623662376238623962406241624262436244624562466247624862496250625162526253625462556256625762586259626062616262626362646265626662676268626962706271627262736274627562766277627862796280628162826283628462856286628762886289629062916292629362946295629662976298629963006301630263036304630563066307630863096310631163126313631463156316631763186319632063216322632363246325632663276328632963306331633263336334633563366337633863396340634163426343634463456346634763486349635063516352635363546355635663576358635963606361636263636364636563666367636863696370637163726373637463756376637763786379638063816382638363846385638663876388638963906391639263936394639563966397639863996400640164026403640464056406640764086409641064116412641364146415641664176418641964206421642264236424642564266427642864296430643164326433643464356436643764386439644064416442644364446445644664476448644964506451645264536454645564566457645864596460646164626463646464656466646764686469647064716472647364746475647664776478647964806481648264836484648564866487648864896490649164926493649464956496649764986499650065016502650365046505650665076508650965106511651265136514651565166517651865196520652165226523652465256526652765286529653065316532653365346535653665376538653965406541654265436544654565466547654865496550655165526553655465556556655765586559656065616562656365646565656665676568656965706571657265736574657565766577657865796580658165826583658465856586658765886589659065916592659365946595659665976598659966006601660266036604660566066607660866096610661166126613661466156616661766186619662066216622662366246625662666276628662966306631663266336634663566366637663866396640664166426643664466456646664766486649665066516652665366546655665666576658665966606661666266636664666566666667666866696670667166726673667466756676667766786679668066816682668366846685668666876688668966906691669266936694669566966697669866996700670167026703670467056706670767086709671067116712671367146715671667176718671967206721672267236724672567266727672867296730673167326733673467356736673767386739674067416742674367446745674667476748674967506751675267536754675567566757675867596760676167626763676467656766676767686769677067716772677367746775677667776778677967806781678267836784678567866787678867896790679167926793679467956796679767986799680068016802680368046805680668076808680968106811681268136814681568166817681868196820682168226823682468256826682768286829683068316832683368346835683668376838683968406841684268436844684568466847684868496850685168526853685468556856685768586859686068616862686368646865686668676868686968706871687268736874687568766877687868796880688168826883688468856886688768886889689068916892689368946895689668976898689969006901690269036904690569066907690869096910691169126913691469156916691769186919692069216922692369246925692669276928692969306931693269336934693569366937693869396940694169426943694469456946694769486949695069516952695369546955695669576958695969606961696269636964696569666967696869696970697169726973697469756976697769786979698069816982698369846985698669876988698969906991699269936994699569966997699869997000700170027003700470057006700770087009701070117012701370147015701670177018701970207021702270237024702570267027702870297030703170327033703470357036703770387039704070417042704370447045704670477048704970507051705270537054705570567057705870597060706170627063706470657066706770687069707070717072707370747075707670777078707970807081708270837084708570867087708870897090709170927093709470957096709770987099710071017102710371047105710671077108710971107111711271137114711571167117711871197120712171227123712471257126712771287129713071317132713371347135713671377138713971407141714271437144714571467147714871497150715171527153715471557156715771587159716071617162716371647165716671677168716971707171717271737174717571767177717871797180718171827183718471857186718771887189719071917192719371947195719671977198719972007201720272037204720572067207720872097210721172127213721472157216721772187219722072217222722372247225722672277228722972307231723272337234723572367237723872397240724172427243724472457246724772487249725072517252725372547255725672577258725972607261726272637264726572667267726872697270727172727273727472757276727772787279728072817282728372847285728672877288728972907291729272937294729572967297729872997300730173027303730473057306730773087309731073117312731373147315731673177318731973207321732273237324732573267327732873297330733173327333733473357336733773387339734073417342734373447345734673477348734973507351735273537354735573567357735873597360736173627363736473657366736773687369737073717372737373747375737673777378737973807381738273837384738573867387738873897390739173927393739473957396739773987399740074017402740374047405740674077408740974107411741274137414741574167417741874197420742174227423742474257426742774287429743074317432743374347435743674377438743974407441744274437444744574467447744874497450745174527453745474557456745774587459746074617462746374647465746674677468746974707471747274737474747574767477747874797480748174827483748474857486748774887489749074917492749374947495749674977498749975007501750275037504750575067507750875097510751175127513751475157516751775187519752075217522752375247525752675277528752975307531753275337534753575367537753875397540754175427543754475457546754775487549755075517552755375547555755675577558755975607561756275637564756575667567756875697570757175727573757475757576757775787579758075817582758375847585758675877588758975907591759275937594759575967597759875997600760176027603760476057606760776087609761076117612761376147615761676177618761976207621762276237624762576267627762876297630763176327633763476357636763776387639764076417642764376447645764676477648764976507651765276537654765576567657765876597660766176627663766476657666766776687669767076717672767376747675767676777678767976807681768276837684768576867687768876897690769176927693769476957696769776987699770077017702770377047705770677077708770977107711771277137714771577167717771877197720772177227723772477257726772777287729773077317732773377347735773677377738773977407741774277437744774577467747774877497750775177527753775477557756775777587759776077617762776377647765776677677768776977707771777277737774777577767777777877797780778177827783778477857786778777887789779077917792779377947795779677977798779978007801780278037804780578067807780878097810781178127813781478157816781778187819782078217822782378247825782678277828782978307831783278337834783578367837783878397840784178427843784478457846784778487849785078517852785378547855785678577858785978607861786278637864786578667867786878697870787178727873787478757876787778787879788078817882788378847885788678877888788978907891789278937894789578967897789878997900790179027903790479057906790779087909791079117912791379147915791679177918791979207921792279237924792579267927792879297930793179327933793479357936793779387939794079417942794379447945794679477948794979507951795279537954795579567957795879597960796179627963796479657966796779687969797079717972797379747975797679777978797979807981798279837984798579867987798879897990799179927993799479957996799779987999800080018002800380048005800680078008800980108011801280138014801580168017801880198020802180228023802480258026802780288029803080318032803380348035803680378038803980408041804280438044804580468047804880498050805180528053805480558056805780588059806080618062806380648065806680678068806980708071807280738074807580768077807880798080808180828083808480858086808780888089809080918092809380948095809680978098809981008101810281038104810581068107810881098110811181128113811481158116811781188119812081218122812381248125812681278128812981308131813281338134813581368137813881398140814181428143814481458146814781488149815081518152815381548155815681578158815981608161816281638164816581668167816881698170817181728173817481758176817781788179818081818182818381848185818681878188818981908191819281938194819581968197819881998200820182028203820482058206820782088209821082118212821382148215821682178218821982208221822282238224822582268227822882298230823182328233823482358236823782388239824082418242824382448245824682478248824982508251825282538254825582568257825882598260826182628263826482658266826782688269827082718272827382748275827682778278827982808281828282838284828582868287828882898290829182928293829482958296829782988299830083018302830383048305830683078308830983108311831283138314831583168317831883198320832183228323832483258326832783288329833083318332833383348335833683378338833983408341834283438344834583468347834883498350835183528353835483558356835783588359836083618362836383648365836683678368836983708371837283738374837583768377837883798380838183828383838483858386838783888389839083918392839383948395839683978398839984008401840284038404840584068407840884098410841184128413841484158416841784188419842084218422842384248425842684278428842984308431843284338434843584368437843884398440844184428443844484458446844784488449845084518452845384548455845684578458845984608461846284638464846584668467846884698470847184728473847484758476847784788479848084818482848384848485848684878488848984908491849284938494849584968497849884998500850185028503850485058506850785088509851085118512851385148515851685178518851985208521852285238524852585268527852885298530853185328533853485358536853785388539854085418542854385448545854685478548854985508551855285538554855585568557855885598560856185628563856485658566856785688569857085718572857385748575857685778578857985808581858285838584858585868587858885898590859185928593859485958596859785988599860086018602860386048605860686078608860986108611861286138614861586168617861886198620862186228623862486258626862786288629863086318632863386348635863686378638863986408641864286438644864586468647864886498650865186528653865486558656865786588659866086618662866386648665866686678668866986708671867286738674867586768677867886798680868186828683868486858686868786888689869086918692869386948695869686978698869987008701870287038704870587068707870887098710871187128713871487158716871787188719872087218722872387248725872687278728872987308731873287338734873587368737873887398740874187428743874487458746874787488749875087518752875387548755875687578758875987608761876287638764876587668767876887698770877187728773877487758776877787788779878087818782878387848785878687878788878987908791879287938794879587968797879887998800880188028803880488058806880788088809881088118812881388148815881688178818881988208821882288238824882588268827882888298830883188328833883488358836883788388839884088418842884388448845884688478848884988508851885288538854885588568857885888598860886188628863886488658866886788688869887088718872887388748875887688778878887988808881888288838884888588868887888888898890889188928893889488958896889788988899890089018902890389048905890689078908890989108911891289138914891589168917891889198920892189228923892489258926892789288929893089318932893389348935893689378938893989408941894289438944894589468947894889498950895189528953895489558956895789588959896089618962896389648965896689678968896989708971897289738974897589768977897889798980898189828983898489858986898789888989899089918992899389948995899689978998899990009001900290039004900590069007900890099010901190129013901490159016901790189019902090219022902390249025902690279028902990309031903290339034903590369037903890399040904190429043904490459046904790489049905090519052905390549055905690579058905990609061906290639064906590669067906890699070907190729073907490759076907790789079908090819082908390849085908690879088908990909091909290939094909590969097909890999100910191029103910491059106910791089109911091119112911391149115911691179118911991209121912291239124912591269127912891299130913191329133913491359136913791389139914091419142914391449145914691479148914991509151915291539154915591569157915891599160916191629163916491659166916791689169917091719172917391749175917691779178917991809181918291839184918591869187918891899190919191929193919491959196919791989199920092019202920392049205920692079208920992109211921292139214921592169217921892199220922192229223922492259226922792289229923092319232923392349235923692379238923992409241924292439244924592469247924892499250925192529253925492559256925792589259926092619262926392649265926692679268926992709271927292739274927592769277927892799280928192829283928492859286928792889289929092919292929392949295929692979298929993009301930293039304930593069307930893099310931193129313931493159316931793189319932093219322932393249325932693279328932993309331933293339334933593369337933893399340934193429343934493459346934793489349935093519352935393549355935693579358935993609361936293639364936593669367936893699370937193729373937493759376937793789379938093819382938393849385938693879388938993909391939293939394939593969397939893999400940194029403940494059406940794089409941094119412941394149415941694179418941994209421942294239424942594269427942894299430943194329433943494359436943794389439944094419442944394449445944694479448944994509451945294539454945594569457945894599460946194629463946494659466946794689469947094719472947394749475947694779478947994809481948294839484948594869487948894899490949194929493949494959496949794989499950095019502950395049505950695079508950995109511951295139514951595169517951895199520952195229523952495259526952795289529953095319532953395349535953695379538953995409541954295439544954595469547954895499550955195529553955495559556955795589559956095619562956395649565956695679568956995709571957295739574957595769577957895799580958195829583958495859586958795889589959095919592959395949595959695979598959996009601960296039604960596069607960896099610961196129613961496159616961796189619962096219622962396249625962696279628962996309631963296339634963596369637963896399640964196429643964496459646964796489649965096519652965396549655965696579658965996609661966296639664966596669667966896699670967196729673967496759676967796789679968096819682968396849685968696879688968996909691969296939694969596969697969896999700970197029703970497059706970797089709971097119712971397149715971697179718971997209721972297239724972597269727972897299730973197329733973497359736973797389739974097419742974397449745974697479748974997509751975297539754975597569757975897599760976197629763976497659766976797689769977097719772977397749775977697779778977997809781978297839784978597869787978897899790979197929793979497959796979797989799980098019802980398049805980698079808980998109811981298139814981598169817981898199820982198229823982498259826982798289829983098319832983398349835983698379838983998409841984298439844984598469847984898499850985198529853985498559856985798589859986098619862986398649865986698679868986998709871987298739874987598769877987898799880988198829883988498859886988798889889989098919892989398949895989698979898989999009901990299039904990599069907990899099910991199129913991499159916991799189919992099219922992399249925992699279928992999309931993299339934993599369937993899399940994199429943994499459946994799489949995099519952995399549955995699579958995999609961996299639964996599669967996899699970997199729973997499759976997799789979998099819982998399849985998699879988998999909991999299939994999599969997999899991000010001100021000310004100051000610007100081000910010100111001210013100141001510016100171001810019100201002110022100231002410025100261002710028100291003010031100321003310034100351003610037100381003910040100411004210043100441004510046100471004810049100501005110052100531005410055100561005710058100591006010061100621006310064100651006610067100681006910070100711007210073100741007510076100771007810079100801008110082100831008410085100861008710088100891009010091100921009310094100951009610097100981009910100101011010210103101041010510106101071010810109101101011110112101131011410115101161011710118101191012010121101221012310124101251012610127101281012910130101311013210133101341013510136101371013810139101401014110142101431014410145101461014710148101491015010151101521015310154101551015610157101581015910160101611016210163101641016510166101671016810169101701017110172101731017410175101761017710178101791018010181101821018310184101851018610187101881018910190101911019210193101941019510196101971019810199102001020110202102031020410205102061020710208102091021010211102121021310214102151021610217102181021910220102211022210223102241022510226102271022810229102301023110232102331023410235102361023710238102391024010241102421024310244102451024610247102481024910250102511025210253102541025510256102571025810259102601026110262102631026410265102661026710268102691027010271102721027310274102751027610277102781027910280102811028210283102841028510286102871028810289102901029110292102931029410295102961029710298102991030010301103021030310304103051030610307103081030910310103111031210313103141031510316103171031810319103201032110322103231032410325103261032710328103291033010331103321033310334103351033610337103381033910340103411034210343103441034510346103471034810349103501035110352103531035410355103561035710358103591036010361103621036310364103651036610367103681036910370103711037210373103741037510376103771037810379103801038110382103831038410385103861038710388103891039010391103921039310394103951039610397103981039910400104011040210403104041040510406104071040810409104101041110412104131041410415104161041710418104191042010421104221042310424104251042610427104281042910430104311043210433104341043510436104371043810439104401044110442104431044410445104461044710448104491045010451104521045310454104551045610457104581045910460104611046210463104641046510466104671046810469104701047110472104731047410475104761047710478104791048010481104821048310484104851048610487104881048910490104911049210493104941049510496104971049810499105001050110502105031050410505105061050710508105091051010511105121051310514105151051610517105181051910520105211052210523105241052510526105271052810529105301053110532105331053410535105361053710538105391054010541105421054310544105451054610547105481054910550105511055210553105541055510556105571055810559105601056110562105631056410565105661056710568105691057010571105721057310574105751057610577105781057910580105811058210583105841058510586105871058810589105901059110592105931059410595105961059710598105991060010601106021060310604106051060610607106081060910610106111061210613106141061510616106171061810619106201062110622106231062410625106261062710628106291063010631106321063310634106351063610637106381063910640106411064210643106441064510646106471064810649106501065110652106531065410655106561065710658106591066010661106621066310664106651066610667106681066910670106711067210673106741067510676106771067810679106801068110682106831068410685106861068710688106891069010691106921069310694106951069610697106981069910700107011070210703107041070510706107071070810709107101071110712107131071410715107161071710718107191072010721107221072310724107251072610727107281072910730107311073210733107341073510736107371073810739107401074110742107431074410745107461074710748107491075010751107521075310754107551075610757107581075910760107611076210763107641076510766107671076810769107701077110772107731077410775107761077710778107791078010781107821078310784107851078610787107881078910790107911079210793107941079510796107971079810799108001080110802108031080410805108061080710808108091081010811108121081310814108151081610817108181081910820108211082210823108241082510826108271082810829108301083110832108331083410835108361083710838108391084010841108421084310844108451084610847108481084910850108511085210853108541085510856108571085810859108601086110862108631086410865108661086710868108691087010871108721087310874108751087610877108781087910880108811088210883108841088510886108871088810889108901089110892108931089410895108961089710898108991090010901109021090310904109051090610907109081090910910109111091210913109141091510916109171091810919109201092110922109231092410925109261092710928109291093010931109321093310934109351093610937109381093910940109411094210943109441094510946109471094810949109501095110952109531095410955109561095710958109591096010961109621096310964109651096610967109681096910970109711097210973109741097510976109771097810979109801098110982109831098410985109861098710988109891099010991109921099310994109951099610997109981099911000110011100211003110041100511006110071100811009110101101111012110131101411015110161101711018110191102011021110221102311024110251102611027110281102911030110311103211033110341103511036110371103811039110401104111042110431104411045110461104711048110491105011051110521105311054110551105611057110581105911060110611106211063110641106511066110671106811069110701107111072110731107411075110761107711078110791108011081110821108311084110851108611087110881108911090110911109211093110941109511096110971109811099111001110111102111031110411105111061110711108111091111011111111121111311114111151111611117111181111911120111211112211123111241112511126111271112811129111301113111132111331113411135111361113711138111391114011141111421114311144111451114611147111481114911150111511115211153111541115511156111571115811159111601116111162111631116411165111661116711168111691117011171111721117311174111751117611177111781117911180111811118211183111841118511186111871118811189111901119111192111931119411195111961119711198111991120011201112021120311204112051120611207112081120911210112111121211213112141121511216112171121811219112201122111222112231122411225112261122711228112291123011231112321123311234112351123611237112381123911240112411124211243112441124511246112471124811249112501125111252112531125411255112561125711258112591126011261112621126311264112651126611267112681126911270112711127211273112741127511276112771127811279112801128111282112831128411285112861128711288112891129011291112921129311294112951129611297112981129911300113011130211303113041130511306113071130811309113101131111312113131131411315113161131711318113191132011321113221132311324113251132611327113281132911330113311133211333113341133511336113371133811339113401134111342113431134411345113461134711348113491135011351113521135311354113551135611357113581135911360113611136211363113641136511366113671136811369113701137111372113731137411375113761137711378113791138011381113821138311384113851138611387113881138911390113911139211393113941139511396113971139811399114001140111402114031140411405114061140711408114091141011411114121141311414114151141611417114181141911420114211142211423114241142511426114271142811429114301143111432114331143411435114361143711438114391144011441114421144311444114451144611447114481144911450114511145211453114541145511456114571145811459114601146111462114631146411465114661146711468114691147011471114721147311474114751147611477114781147911480114811148211483114841148511486114871148811489114901149111492114931149411495114961149711498114991150011501115021150311504115051150611507115081150911510115111151211513115141151511516115171151811519115201152111522115231152411525115261152711528115291153011531115321153311534115351153611537115381153911540115411154211543115441154511546115471154811549115501155111552115531155411555115561155711558115591156011561115621156311564115651156611567115681156911570115711157211573115741157511576115771157811579115801158111582115831158411585115861158711588115891159011591115921159311594115951159611597115981159911600116011160211603116041160511606116071160811609116101161111612116131161411615116161161711618116191162011621116221162311624116251162611627116281162911630116311163211633116341163511636116371163811639116401164111642116431164411645116461164711648116491165011651116521165311654116551165611657116581165911660116611166211663116641166511666116671166811669116701167111672116731167411675116761167711678116791168011681116821168311684116851168611687116881168911690116911169211693116941169511696116971169811699117001170111702117031170411705117061170711708117091171011711117121171311714117151171611717117181171911720117211172211723117241172511726117271172811729117301173111732117331173411735117361173711738117391174011741117421174311744117451174611747117481174911750117511175211753117541175511756117571175811759117601176111762117631176411765117661176711768117691177011771117721177311774117751177611777117781177911780117811178211783117841178511786117871178811789117901179111792117931179411795117961179711798117991180011801118021180311804118051180611807118081180911810118111181211813118141181511816118171181811819118201182111822118231182411825118261182711828118291183011831118321183311834118351183611837118381183911840118411184211843118441184511846118471184811849118501185111852118531185411855118561185711858118591186011861118621186311864118651186611867118681186911870118711187211873118741187511876118771187811879118801188111882118831188411885118861188711888118891189011891118921189311894118951189611897118981189911900119011190211903119041190511906119071190811909119101191111912119131191411915119161191711918119191192011921119221192311924119251192611927119281192911930119311193211933119341193511936119371193811939119401194111942119431194411945119461194711948119491195011951119521195311954119551195611957119581195911960119611196211963119641196511966119671196811969119701197111972119731197411975119761197711978119791198011981119821198311984119851198611987119881198911990119911199211993119941199511996119971199811999120001200112002120031200412005120061200712008120091201012011120121201312014120151201612017120181201912020120211202212023120241202512026120271202812029120301203112032120331203412035120361203712038120391204012041120421204312044120451204612047120481204912050120511205212053120541205512056120571205812059120601206112062120631206412065120661206712068120691207012071120721207312074120751207612077120781207912080120811208212083120841208512086120871208812089120901209112092120931209412095120961209712098120991210012101121021210312104121051210612107121081210912110121111211212113121141211512116121171211812119121201212112122121231212412125121261212712128121291213012131121321213312134121351213612137121381213912140121411214212143121441214512146121471214812149121501215112152121531215412155121561215712158121591216012161121621216312164121651216612167121681216912170121711217212173121741217512176121771217812179121801218112182121831218412185121861218712188121891219012191121921219312194121951219612197121981219912200122011220212203122041220512206122071220812209122101221112212122131221412215122161221712218122191222012221122221222312224122251222612227122281222912230122311223212233122341223512236122371223812239122401224112242122431224412245122461224712248122491225012251122521225312254122551225612257122581225912260122611226212263122641226512266122671226812269122701227112272122731227412275122761227712278122791228012281122821228312284122851228612287122881228912290122911229212293122941229512296122971229812299123001230112302123031230412305123061230712308123091231012311123121231312314123151231612317123181231912320123211232212323123241232512326123271232812329123301233112332123331233412335123361233712338123391234012341123421234312344123451234612347123481234912350123511235212353123541235512356123571235812359123601236112362123631236412365123661236712368123691237012371123721237312374123751237612377123781237912380123811238212383123841238512386123871238812389123901239112392123931239412395123961239712398123991240012401124021240312404124051240612407124081240912410124111241212413124141241512416124171241812419124201242112422124231242412425124261242712428124291243012431124321243312434124351243612437124381243912440124411244212443124441244512446124471244812449124501245112452124531245412455124561245712458124591246012461124621246312464124651246612467124681246912470124711247212473124741247512476124771247812479124801248112482124831248412485124861248712488124891249012491124921249312494124951249612497124981249912500125011250212503125041250512506125071250812509125101251112512125131251412515125161251712518125191252012521125221252312524125251252612527125281252912530125311253212533125341253512536125371253812539125401254112542125431254412545125461254712548125491255012551125521255312554125551255612557125581255912560125611256212563125641256512566125671256812569125701257112572125731257412575125761257712578125791258012581125821258312584125851258612587125881258912590125911259212593125941259512596125971259812599126001260112602126031260412605126061260712608126091261012611126121261312614126151261612617126181261912620126211262212623126241262512626126271262812629126301263112632126331263412635126361263712638126391264012641126421264312644126451264612647126481264912650126511265212653126541265512656126571265812659126601266112662126631266412665126661266712668126691267012671126721267312674126751267612677126781267912680126811268212683126841268512686126871268812689126901269112692126931269412695126961269712698126991270012701127021270312704127051270612707127081270912710127111271212713127141271512716127171271812719127201272112722127231272412725127261272712728127291273012731127321273312734127351273612737127381273912740127411274212743127441274512746127471274812749127501275112752127531275412755127561275712758127591276012761127621276312764127651276612767127681276912770127711277212773127741277512776127771277812779127801278112782127831278412785127861278712788127891279012791127921279312794127951279612797127981279912800128011280212803128041280512806128071280812809128101281112812128131281412815128161281712818128191282012821128221282312824128251282612827128281282912830128311283212833128341283512836128371283812839128401284112842128431284412845128461284712848128491285012851128521285312854128551285612857128581285912860128611286212863128641286512866128671286812869128701287112872128731287412875128761287712878128791288012881128821288312884128851288612887128881288912890128911289212893128941289512896128971289812899129001290112902129031290412905129061290712908129091291012911129121291312914129151291612917129181291912920129211292212923129241292512926129271292812929129301293112932129331293412935129361293712938129391294012941129421294312944129451294612947129481294912950129511295212953129541295512956129571295812959129601296112962129631296412965129661296712968129691297012971129721297312974129751297612977129781297912980129811298212983129841298512986129871298812989129901299112992129931299412995129961299712998129991300013001130021300313004130051300613007130081300913010130111301213013130141301513016130171301813019130201302113022130231302413025130261302713028130291303013031130321303313034130351303613037130381303913040130411304213043130441304513046130471304813049130501305113052130531305413055130561305713058130591306013061130621306313064130651306613067130681306913070130711307213073130741307513076130771307813079130801308113082130831308413085130861308713088130891309013091130921309313094130951309613097130981309913100131011310213103131041310513106131071310813109131101311113112131131311413115131161311713118131191312013121131221312313124131251312613127131281312913130131311313213133131341313513136131371313813139131401314113142131431314413145131461314713148131491315013151131521315313154131551315613157131581315913160131611316213163131641316513166131671316813169131701317113172131731317413175131761317713178131791318013181131821318313184131851318613187131881318913190131911319213193131941319513196131971319813199132001320113202132031320413205132061320713208132091321013211132121321313214132151321613217132181321913220132211322213223132241322513226132271322813229132301323113232132331323413235132361323713238132391324013241132421324313244132451324613247132481324913250132511325213253132541325513256132571325813259132601326113262132631326413265132661326713268132691327013271132721327313274132751327613277132781327913280132811328213283132841328513286132871328813289132901329113292132931329413295132961329713298132991330013301133021330313304133051330613307133081330913310133111331213313133141331513316133171331813319133201332113322133231332413325133261332713328133291333013331133321333313334133351333613337133381333913340133411334213343133441334513346133471334813349133501335113352133531335413355133561335713358133591336013361
  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. // Create global ionic obj and its namespaces
  16. // build processes may have already created an ionic obj
  17. window.ionic = window.ionic || {};
  18. window.ionic.views = {};
  19. window.ionic.version = '1.3.3';
  20. (function (ionic) {
  21. ionic.DelegateService = function(methodNames) {
  22. if (methodNames.indexOf('$getByHandle') > -1) {
  23. throw new Error("Method '$getByHandle' is implicitly added to each delegate service. Do not list it as a method.");
  24. }
  25. function trueFn() { return true; }
  26. return ['$log', function($log) {
  27. /*
  28. * Creates a new object that will have all the methodNames given,
  29. * and call them on the given the controller instance matching given
  30. * handle.
  31. * The reason we don't just let $getByHandle return the controller instance
  32. * itself is that the controller instance might not exist yet.
  33. *
  34. * We want people to be able to do
  35. * `var instance = $ionicScrollDelegate.$getByHandle('foo')` on controller
  36. * instantiation, but on controller instantiation a child directive
  37. * may not have been compiled yet!
  38. *
  39. * So this is our way of solving this problem: we create an object
  40. * that will only try to fetch the controller with given handle
  41. * once the methods are actually called.
  42. */
  43. function DelegateInstance(instances, handle) {
  44. this._instances = instances;
  45. this.handle = handle;
  46. }
  47. methodNames.forEach(function(methodName) {
  48. DelegateInstance.prototype[methodName] = instanceMethodCaller(methodName);
  49. });
  50. /**
  51. * The delegate service (eg $ionicNavBarDelegate) is just an instance
  52. * with a non-defined handle, a couple extra methods for registering
  53. * and narrowing down to a specific handle.
  54. */
  55. function DelegateService() {
  56. this._instances = [];
  57. }
  58. DelegateService.prototype = DelegateInstance.prototype;
  59. DelegateService.prototype._registerInstance = function(instance, handle, filterFn) {
  60. var instances = this._instances;
  61. instance.$$delegateHandle = handle;
  62. instance.$$filterFn = filterFn || trueFn;
  63. instances.push(instance);
  64. return function deregister() {
  65. var index = instances.indexOf(instance);
  66. if (index !== -1) {
  67. instances.splice(index, 1);
  68. }
  69. };
  70. };
  71. DelegateService.prototype.$getByHandle = function(handle) {
  72. return new DelegateInstance(this._instances, handle);
  73. };
  74. return new DelegateService();
  75. function instanceMethodCaller(methodName) {
  76. return function caller() {
  77. var handle = this.handle;
  78. var args = arguments;
  79. var foundInstancesCount = 0;
  80. var returnValue;
  81. this._instances.forEach(function(instance) {
  82. if ((!handle || handle == instance.$$delegateHandle) && instance.$$filterFn(instance)) {
  83. foundInstancesCount++;
  84. var ret = instance[methodName].apply(instance, args);
  85. //Only return the value from the first call
  86. if (foundInstancesCount === 1) {
  87. returnValue = ret;
  88. }
  89. }
  90. });
  91. if (!foundInstancesCount && handle) {
  92. return $log.warn(
  93. 'Delegate for handle "' + handle + '" could not find a ' +
  94. 'corresponding element with delegate-handle="' + handle + '"! ' +
  95. methodName + '() was not called!\n' +
  96. 'Possible cause: If you are calling ' + methodName + '() immediately, and ' +
  97. 'your element with delegate-handle="' + handle + '" is a child of your ' +
  98. 'controller, then your element may not be compiled yet. Put a $timeout ' +
  99. 'around your call to ' + methodName + '() and try again.'
  100. );
  101. }
  102. return returnValue;
  103. };
  104. }
  105. }];
  106. };
  107. })(window.ionic);
  108. (function(window, document, ionic) {
  109. var readyCallbacks = [];
  110. var isDomReady = document.readyState === 'complete' || document.readyState === 'interactive';
  111. function domReady() {
  112. isDomReady = true;
  113. for (var x = 0; x < readyCallbacks.length; x++) {
  114. ionic.requestAnimationFrame(readyCallbacks[x]);
  115. }
  116. readyCallbacks = [];
  117. document.removeEventListener('DOMContentLoaded', domReady);
  118. }
  119. if (!isDomReady) {
  120. document.addEventListener('DOMContentLoaded', domReady);
  121. }
  122. // From the man himself, Mr. Paul Irish.
  123. // The requestAnimationFrame polyfill
  124. // Put it on window just to preserve its context
  125. // without having to use .call
  126. window._rAF = (function() {
  127. return window.requestAnimationFrame ||
  128. window.webkitRequestAnimationFrame ||
  129. window.mozRequestAnimationFrame ||
  130. function(callback) {
  131. window.setTimeout(callback, 16);
  132. };
  133. })();
  134. var cancelAnimationFrame = window.cancelAnimationFrame ||
  135. window.webkitCancelAnimationFrame ||
  136. window.mozCancelAnimationFrame ||
  137. window.webkitCancelRequestAnimationFrame;
  138. /**
  139. * @ngdoc utility
  140. * @name ionic.DomUtil
  141. * @module ionic
  142. */
  143. ionic.DomUtil = {
  144. //Call with proper context
  145. /**
  146. * @ngdoc method
  147. * @name ionic.DomUtil#requestAnimationFrame
  148. * @alias ionic.requestAnimationFrame
  149. * @description Calls [requestAnimationFrame](https://developer.mozilla.org/en-US/docs/Web/API/window.requestAnimationFrame), or a polyfill if not available.
  150. * @param {function} callback The function to call when the next frame
  151. * happens.
  152. */
  153. requestAnimationFrame: function(cb) {
  154. return window._rAF(cb);
  155. },
  156. cancelAnimationFrame: function(requestId) {
  157. cancelAnimationFrame(requestId);
  158. },
  159. /**
  160. * @ngdoc method
  161. * @name ionic.DomUtil#animationFrameThrottle
  162. * @alias ionic.animationFrameThrottle
  163. * @description
  164. * When given a callback, if that callback is called 100 times between
  165. * animation frames, adding Throttle will make it only run the last of
  166. * the 100 calls.
  167. *
  168. * @param {function} callback a function which will be throttled to
  169. * requestAnimationFrame
  170. * @returns {function} A function which will then call the passed in callback.
  171. * The passed in callback will receive the context the returned function is
  172. * called with.
  173. */
  174. animationFrameThrottle: function(cb) {
  175. var args, isQueued, context;
  176. return function() {
  177. args = arguments;
  178. context = this;
  179. if (!isQueued) {
  180. isQueued = true;
  181. ionic.requestAnimationFrame(function() {
  182. cb.apply(context, args);
  183. isQueued = false;
  184. });
  185. }
  186. };
  187. },
  188. contains: function(parentNode, otherNode) {
  189. var current = otherNode;
  190. while (current) {
  191. if (current === parentNode) return true;
  192. current = current.parentNode;
  193. }
  194. },
  195. /**
  196. * @ngdoc method
  197. * @name ionic.DomUtil#getPositionInParent
  198. * @description
  199. * Find an element's scroll offset within its container.
  200. * @param {DOMElement} element The element to find the offset of.
  201. * @returns {object} A position object with the following properties:
  202. * - `{number}` `left` The left offset of the element.
  203. * - `{number}` `top` The top offset of the element.
  204. */
  205. getPositionInParent: function(el) {
  206. return {
  207. left: el.offsetLeft,
  208. top: el.offsetTop
  209. };
  210. },
  211. getOffsetTop: function(el) {
  212. var curtop = 0;
  213. if (el.offsetParent) {
  214. do {
  215. curtop += el.offsetTop;
  216. el = el.offsetParent;
  217. } while (el)
  218. return curtop;
  219. }
  220. },
  221. /**
  222. * @ngdoc method
  223. * @name ionic.DomUtil#ready
  224. * @description
  225. * Call a function when the DOM is ready, or if it is already ready
  226. * call the function immediately.
  227. * @param {function} callback The function to be called.
  228. */
  229. ready: function(cb) {
  230. if (isDomReady) {
  231. ionic.requestAnimationFrame(cb);
  232. } else {
  233. readyCallbacks.push(cb);
  234. }
  235. },
  236. /**
  237. * @ngdoc method
  238. * @name ionic.DomUtil#getTextBounds
  239. * @description
  240. * Get a rect representing the bounds of the given textNode.
  241. * @param {DOMElement} textNode The textNode to find the bounds of.
  242. * @returns {object} An object representing the bounds of the node. Properties:
  243. * - `{number}` `left` The left position of the textNode.
  244. * - `{number}` `right` The right position of the textNode.
  245. * - `{number}` `top` The top position of the textNode.
  246. * - `{number}` `bottom` The bottom position of the textNode.
  247. * - `{number}` `width` The width of the textNode.
  248. * - `{number}` `height` The height of the textNode.
  249. */
  250. getTextBounds: function(textNode) {
  251. if (document.createRange) {
  252. var range = document.createRange();
  253. range.selectNodeContents(textNode);
  254. if (range.getBoundingClientRect) {
  255. var rect = range.getBoundingClientRect();
  256. if (rect) {
  257. var sx = window.scrollX;
  258. var sy = window.scrollY;
  259. return {
  260. top: rect.top + sy,
  261. left: rect.left + sx,
  262. right: rect.left + sx + rect.width,
  263. bottom: rect.top + sy + rect.height,
  264. width: rect.width,
  265. height: rect.height
  266. };
  267. }
  268. }
  269. }
  270. return null;
  271. },
  272. /**
  273. * @ngdoc method
  274. * @name ionic.DomUtil#getChildIndex
  275. * @description
  276. * Get the first index of a child node within the given element of the
  277. * specified type.
  278. * @param {DOMElement} element The element to find the index of.
  279. * @param {string} type The nodeName to match children of element against.
  280. * @returns {number} The index, or -1, of a child with nodeName matching type.
  281. */
  282. getChildIndex: function(element, type) {
  283. if (type) {
  284. var ch = element.parentNode.children;
  285. var c;
  286. for (var i = 0, k = 0, j = ch.length; i < j; i++) {
  287. c = ch[i];
  288. if (c.nodeName && c.nodeName.toLowerCase() == type) {
  289. if (c == element) {
  290. return k;
  291. }
  292. k++;
  293. }
  294. }
  295. }
  296. return Array.prototype.slice.call(element.parentNode.children).indexOf(element);
  297. },
  298. /**
  299. * @private
  300. */
  301. swapNodes: function(src, dest) {
  302. dest.parentNode.insertBefore(src, dest);
  303. },
  304. elementIsDescendant: function(el, parent, stopAt) {
  305. var current = el;
  306. do {
  307. if (current === parent) return true;
  308. current = current.parentNode;
  309. } while (current && current !== stopAt);
  310. return false;
  311. },
  312. /**
  313. * @ngdoc method
  314. * @name ionic.DomUtil#getParentWithClass
  315. * @param {DOMElement} element
  316. * @param {string} className
  317. * @returns {DOMElement} The closest parent of element matching the
  318. * className, or null.
  319. */
  320. getParentWithClass: function(e, className, depth) {
  321. depth = depth || 10;
  322. while (e.parentNode && depth--) {
  323. if (e.parentNode.classList && e.parentNode.classList.contains(className)) {
  324. return e.parentNode;
  325. }
  326. e = e.parentNode;
  327. }
  328. return null;
  329. },
  330. /**
  331. * @ngdoc method
  332. * @name ionic.DomUtil#getParentOrSelfWithClass
  333. * @param {DOMElement} element
  334. * @param {string} className
  335. * @returns {DOMElement} The closest parent or self matching the
  336. * className, or null.
  337. */
  338. getParentOrSelfWithClass: function(e, className, depth) {
  339. depth = depth || 10;
  340. while (e && depth--) {
  341. if (e.classList && e.classList.contains(className)) {
  342. return e;
  343. }
  344. e = e.parentNode;
  345. }
  346. return null;
  347. },
  348. /**
  349. * @ngdoc method
  350. * @name ionic.DomUtil#rectContains
  351. * @param {number} x
  352. * @param {number} y
  353. * @param {number} x1
  354. * @param {number} y1
  355. * @param {number} x2
  356. * @param {number} y2
  357. * @returns {boolean} Whether {x,y} fits within the rectangle defined by
  358. * {x1,y1,x2,y2}.
  359. */
  360. rectContains: function(x, y, x1, y1, x2, y2) {
  361. if (x < x1 || x > x2) return false;
  362. if (y < y1 || y > y2) return false;
  363. return true;
  364. },
  365. /**
  366. * @ngdoc method
  367. * @name ionic.DomUtil#blurAll
  368. * @description
  369. * Blurs any currently focused input element
  370. * @returns {DOMElement} The element blurred or null
  371. */
  372. blurAll: function() {
  373. if (document.activeElement && document.activeElement != document.body) {
  374. document.activeElement.blur();
  375. return document.activeElement;
  376. }
  377. return null;
  378. },
  379. cachedAttr: function(ele, key, value) {
  380. ele = ele && ele.length && ele[0] || ele;
  381. if (ele && ele.setAttribute) {
  382. var dataKey = '$attr-' + key;
  383. if (arguments.length > 2) {
  384. if (ele[dataKey] !== value) {
  385. ele.setAttribute(key, value);
  386. ele[dataKey] = value;
  387. }
  388. } else if (typeof ele[dataKey] == 'undefined') {
  389. ele[dataKey] = ele.getAttribute(key);
  390. }
  391. return ele[dataKey];
  392. }
  393. },
  394. cachedStyles: function(ele, styles) {
  395. ele = ele && ele.length && ele[0] || ele;
  396. if (ele && ele.style) {
  397. for (var prop in styles) {
  398. if (ele['$style-' + prop] !== styles[prop]) {
  399. ele.style[prop] = ele['$style-' + prop] = styles[prop];
  400. }
  401. }
  402. }
  403. }
  404. };
  405. //Shortcuts
  406. ionic.requestAnimationFrame = ionic.DomUtil.requestAnimationFrame;
  407. ionic.cancelAnimationFrame = ionic.DomUtil.cancelAnimationFrame;
  408. ionic.animationFrameThrottle = ionic.DomUtil.animationFrameThrottle;
  409. })(window, document, ionic);
  410. /**
  411. * ion-events.js
  412. *
  413. * Author: Max Lynch <max@drifty.com>
  414. *
  415. * Framework events handles various mobile browser events, and
  416. * detects special events like tap/swipe/etc. and emits them
  417. * as custom events that can be used in an app.
  418. *
  419. * Portions lovingly adapted from github.com/maker/ratchet and github.com/alexgibson/tap.js - thanks guys!
  420. */
  421. (function(ionic) {
  422. // Custom event polyfill
  423. ionic.CustomEvent = (function() {
  424. if( typeof window.CustomEvent === 'function' ) return CustomEvent;
  425. var customEvent = function(event, params) {
  426. var evt;
  427. params = params || {
  428. bubbles: false,
  429. cancelable: false,
  430. detail: undefined
  431. };
  432. try {
  433. evt = document.createEvent("CustomEvent");
  434. evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
  435. } catch (error) {
  436. // fallback for browsers that don't support createEvent('CustomEvent')
  437. evt = document.createEvent("Event");
  438. for (var param in params) {
  439. evt[param] = params[param];
  440. }
  441. evt.initEvent(event, params.bubbles, params.cancelable);
  442. }
  443. return evt;
  444. };
  445. customEvent.prototype = window.Event.prototype;
  446. return customEvent;
  447. })();
  448. /**
  449. * @ngdoc utility
  450. * @name ionic.EventController
  451. * @module ionic
  452. */
  453. ionic.EventController = {
  454. VIRTUALIZED_EVENTS: ['tap', 'swipe', 'swiperight', 'swipeleft', 'drag', 'hold', 'release'],
  455. /**
  456. * @ngdoc method
  457. * @name ionic.EventController#trigger
  458. * @alias ionic.trigger
  459. * @param {string} eventType The event to trigger.
  460. * @param {object} data The data for the event. Hint: pass in
  461. * `{target: targetElement}`
  462. * @param {boolean=} bubbles Whether the event should bubble up the DOM.
  463. * @param {boolean=} cancelable Whether the event should be cancelable.
  464. */
  465. // Trigger a new event
  466. trigger: function(eventType, data, bubbles, cancelable) {
  467. var event = new ionic.CustomEvent(eventType, {
  468. detail: data,
  469. bubbles: !!bubbles,
  470. cancelable: !!cancelable
  471. });
  472. // Make sure to trigger the event on the given target, or dispatch it from
  473. // the window if we don't have an event target
  474. data && data.target && data.target.dispatchEvent && data.target.dispatchEvent(event) || window.dispatchEvent(event);
  475. },
  476. /**
  477. * @ngdoc method
  478. * @name ionic.EventController#on
  479. * @alias ionic.on
  480. * @description Listen to an event on an element.
  481. * @param {string} type The event to listen for.
  482. * @param {function} callback The listener to be called.
  483. * @param {DOMElement} element The element to listen for the event on.
  484. */
  485. on: function(type, callback, element) {
  486. var e = element || window;
  487. // Bind a gesture if it's a virtual event
  488. for(var i = 0, j = this.VIRTUALIZED_EVENTS.length; i < j; i++) {
  489. if(type == this.VIRTUALIZED_EVENTS[i]) {
  490. var gesture = new ionic.Gesture(element);
  491. gesture.on(type, callback);
  492. return gesture;
  493. }
  494. }
  495. // Otherwise bind a normal event
  496. e.addEventListener(type, callback);
  497. },
  498. /**
  499. * @ngdoc method
  500. * @name ionic.EventController#off
  501. * @alias ionic.off
  502. * @description Remove an event listener.
  503. * @param {string} type
  504. * @param {function} callback
  505. * @param {DOMElement} element
  506. */
  507. off: function(type, callback, element) {
  508. element.removeEventListener(type, callback);
  509. },
  510. /**
  511. * @ngdoc method
  512. * @name ionic.EventController#onGesture
  513. * @alias ionic.onGesture
  514. * @description Add an event listener for a gesture on an element.
  515. *
  516. * Available eventTypes (from [hammer.js](http://eightmedia.github.io/hammer.js/)):
  517. *
  518. * `hold`, `tap`, `doubletap`, `drag`, `dragstart`, `dragend`, `dragup`, `dragdown`, <br/>
  519. * `dragleft`, `dragright`, `swipe`, `swipeup`, `swipedown`, `swipeleft`, `swiperight`, <br/>
  520. * `transform`, `transformstart`, `transformend`, `rotate`, `pinch`, `pinchin`, `pinchout`, <br/>
  521. * `touch`, `release`
  522. *
  523. * @param {string} eventType The gesture event to listen for.
  524. * @param {function(e)} callback The function to call when the gesture
  525. * happens.
  526. * @param {DOMElement} element The angular element to listen for the event on.
  527. * @param {object} options object.
  528. * @returns {ionic.Gesture} The gesture object (use this to remove the gesture later on).
  529. */
  530. onGesture: function(type, callback, element, options) {
  531. var gesture = new ionic.Gesture(element, options);
  532. gesture.on(type, callback);
  533. return gesture;
  534. },
  535. /**
  536. * @ngdoc method
  537. * @name ionic.EventController#offGesture
  538. * @alias ionic.offGesture
  539. * @description Remove an event listener for a gesture created on an element.
  540. * @param {ionic.Gesture} gesture The gesture that should be removed.
  541. * @param {string} eventType The gesture event to remove the listener for.
  542. * @param {function(e)} callback The listener to remove.
  543. */
  544. offGesture: function(gesture, type, callback) {
  545. gesture && gesture.off(type, callback);
  546. },
  547. handlePopState: function() {}
  548. };
  549. // Map some convenient top-level functions for event handling
  550. ionic.on = function() { ionic.EventController.on.apply(ionic.EventController, arguments); };
  551. ionic.off = function() { ionic.EventController.off.apply(ionic.EventController, arguments); };
  552. ionic.trigger = ionic.EventController.trigger;//function() { ionic.EventController.trigger.apply(ionic.EventController.trigger, arguments); };
  553. ionic.onGesture = function() { return ionic.EventController.onGesture.apply(ionic.EventController.onGesture, arguments); };
  554. ionic.offGesture = function() { return ionic.EventController.offGesture.apply(ionic.EventController.offGesture, arguments); };
  555. })(window.ionic);
  556. /* eslint camelcase:0 */
  557. /**
  558. * Simple gesture controllers with some common gestures that emit
  559. * gesture events.
  560. *
  561. * Ported from github.com/EightMedia/hammer.js Gestures - thanks!
  562. */
  563. (function(ionic) {
  564. /**
  565. * ionic.Gestures
  566. * use this to create instances
  567. * @param {HTMLElement} element
  568. * @param {Object} options
  569. * @returns {ionic.Gestures.Instance}
  570. * @constructor
  571. */
  572. ionic.Gesture = function(element, options) {
  573. return new ionic.Gestures.Instance(element, options || {});
  574. };
  575. ionic.Gestures = {};
  576. // default settings
  577. ionic.Gestures.defaults = {
  578. // add css to the element to prevent the browser from doing
  579. // its native behavior. this doesnt prevent the scrolling,
  580. // but cancels the contextmenu, tap highlighting etc
  581. // set to false to disable this
  582. stop_browser_behavior: 'disable-user-behavior'
  583. };
  584. // detect touchevents
  585. ionic.Gestures.HAS_POINTEREVENTS = window.navigator.pointerEnabled || window.navigator.msPointerEnabled;
  586. ionic.Gestures.HAS_TOUCHEVENTS = ('ontouchstart' in window);
  587. // dont use mouseevents on mobile devices
  588. ionic.Gestures.MOBILE_REGEX = /mobile|tablet|ip(ad|hone|od)|android|silk/i;
  589. ionic.Gestures.NO_MOUSEEVENTS = ionic.Gestures.HAS_TOUCHEVENTS && window.navigator.userAgent.match(ionic.Gestures.MOBILE_REGEX);
  590. // eventtypes per touchevent (start, move, end)
  591. // are filled by ionic.Gestures.event.determineEventTypes on setup
  592. ionic.Gestures.EVENT_TYPES = {};
  593. // direction defines
  594. ionic.Gestures.DIRECTION_DOWN = 'down';
  595. ionic.Gestures.DIRECTION_LEFT = 'left';
  596. ionic.Gestures.DIRECTION_UP = 'up';
  597. ionic.Gestures.DIRECTION_RIGHT = 'right';
  598. // pointer type
  599. ionic.Gestures.POINTER_MOUSE = 'mouse';
  600. ionic.Gestures.POINTER_TOUCH = 'touch';
  601. ionic.Gestures.POINTER_PEN = 'pen';
  602. // touch event defines
  603. ionic.Gestures.EVENT_START = 'start';
  604. ionic.Gestures.EVENT_MOVE = 'move';
  605. ionic.Gestures.EVENT_END = 'end';
  606. // hammer document where the base events are added at
  607. ionic.Gestures.DOCUMENT = window.document;
  608. // plugins namespace
  609. ionic.Gestures.plugins = {};
  610. // if the window events are set...
  611. ionic.Gestures.READY = false;
  612. /**
  613. * setup events to detect gestures on the document
  614. */
  615. function setup() {
  616. if(ionic.Gestures.READY) {
  617. return;
  618. }
  619. // find what eventtypes we add listeners to
  620. ionic.Gestures.event.determineEventTypes();
  621. // Register all gestures inside ionic.Gestures.gestures
  622. for(var name in ionic.Gestures.gestures) {
  623. if(ionic.Gestures.gestures.hasOwnProperty(name)) {
  624. ionic.Gestures.detection.register(ionic.Gestures.gestures[name]);
  625. }
  626. }
  627. // Add touch events on the document
  628. ionic.Gestures.event.onTouch(ionic.Gestures.DOCUMENT, ionic.Gestures.EVENT_MOVE, ionic.Gestures.detection.detect);
  629. ionic.Gestures.event.onTouch(ionic.Gestures.DOCUMENT, ionic.Gestures.EVENT_END, ionic.Gestures.detection.detect);
  630. // ionic.Gestures is ready...!
  631. ionic.Gestures.READY = true;
  632. }
  633. /**
  634. * create new hammer instance
  635. * all methods should return the instance itself, so it is chainable.
  636. * @param {HTMLElement} element
  637. * @param {Object} [options={}]
  638. * @returns {ionic.Gestures.Instance}
  639. * @name Gesture.Instance
  640. * @constructor
  641. */
  642. ionic.Gestures.Instance = function(element, options) {
  643. var self = this;
  644. // A null element was passed into the instance, which means
  645. // whatever lookup was done to find this element failed to find it
  646. // so we can't listen for events on it.
  647. if(element === null) {
  648. void 0;
  649. return this;
  650. }
  651. // setup ionic.GesturesJS window events and register all gestures
  652. // this also sets up the default options
  653. setup();
  654. this.element = element;
  655. // start/stop detection option
  656. this.enabled = true;
  657. // merge options
  658. this.options = ionic.Gestures.utils.extend(
  659. ionic.Gestures.utils.extend({}, ionic.Gestures.defaults),
  660. options || {});
  661. // add some css to the element to prevent the browser from doing its native behavoir
  662. if(this.options.stop_browser_behavior) {
  663. ionic.Gestures.utils.stopDefaultBrowserBehavior(this.element, this.options.stop_browser_behavior);
  664. }
  665. // start detection on touchstart
  666. ionic.Gestures.event.onTouch(element, ionic.Gestures.EVENT_START, function(ev) {
  667. if(self.enabled) {
  668. ionic.Gestures.detection.startDetect(self, ev);
  669. }
  670. });
  671. // return instance
  672. return this;
  673. };
  674. ionic.Gestures.Instance.prototype = {
  675. /**
  676. * bind events to the instance
  677. * @param {String} gesture
  678. * @param {Function} handler
  679. * @returns {ionic.Gestures.Instance}
  680. */
  681. on: function onEvent(gesture, handler){
  682. var gestures = gesture.split(' ');
  683. for(var t = 0; t < gestures.length; t++) {
  684. this.element.addEventListener(gestures[t], handler, false);
  685. }
  686. return this;
  687. },
  688. /**
  689. * unbind events to the instance
  690. * @param {String} gesture
  691. * @param {Function} handler
  692. * @returns {ionic.Gestures.Instance}
  693. */
  694. off: function offEvent(gesture, handler){
  695. var gestures = gesture.split(' ');
  696. for(var t = 0; t < gestures.length; t++) {
  697. this.element.removeEventListener(gestures[t], handler, false);
  698. }
  699. return this;
  700. },
  701. /**
  702. * trigger gesture event
  703. * @param {String} gesture
  704. * @param {Object} eventData
  705. * @returns {ionic.Gestures.Instance}
  706. */
  707. trigger: function triggerEvent(gesture, eventData){
  708. // create DOM event
  709. var event = ionic.Gestures.DOCUMENT.createEvent('Event');
  710. event.initEvent(gesture, true, true);
  711. event.gesture = eventData;
  712. // trigger on the target if it is in the instance element,
  713. // this is for event delegation tricks
  714. var element = this.element;
  715. if(ionic.Gestures.utils.hasParent(eventData.target, element)) {
  716. element = eventData.target;
  717. }
  718. element.dispatchEvent(event);
  719. return this;
  720. },
  721. /**
  722. * enable of disable hammer.js detection
  723. * @param {Boolean} state
  724. * @returns {ionic.Gestures.Instance}
  725. */
  726. enable: function enable(state) {
  727. this.enabled = state;
  728. return this;
  729. }
  730. };
  731. /**
  732. * this holds the last move event,
  733. * used to fix empty touchend issue
  734. * see the onTouch event for an explanation
  735. * type {Object}
  736. */
  737. var last_move_event = null;
  738. /**
  739. * when the mouse is hold down, this is true
  740. * type {Boolean}
  741. */
  742. var enable_detect = false;
  743. /**
  744. * when touch events have been fired, this is true
  745. * type {Boolean}
  746. */
  747. var touch_triggered = false;
  748. ionic.Gestures.event = {
  749. /**
  750. * simple addEventListener
  751. * @param {HTMLElement} element
  752. * @param {String} type
  753. * @param {Function} handler
  754. */
  755. bindDom: function(element, type, handler) {
  756. var types = type.split(' ');
  757. for(var t = 0; t < types.length; t++) {
  758. element.addEventListener(types[t], handler, false);
  759. }
  760. },
  761. /**
  762. * touch events with mouse fallback
  763. * @param {HTMLElement} element
  764. * @param {String} eventType like ionic.Gestures.EVENT_MOVE
  765. * @param {Function} handler
  766. */
  767. onTouch: function onTouch(element, eventType, handler) {
  768. var self = this;
  769. this.bindDom(element, ionic.Gestures.EVENT_TYPES[eventType], function bindDomOnTouch(ev) {
  770. var sourceEventType = ev.type.toLowerCase();
  771. // onmouseup, but when touchend has been fired we do nothing.
  772. // this is for touchdevices which also fire a mouseup on touchend
  773. if(sourceEventType.match(/mouse/) && touch_triggered) {
  774. return;
  775. }
  776. // mousebutton must be down or a touch event
  777. else if( sourceEventType.match(/touch/) || // touch events are always on screen
  778. sourceEventType.match(/pointerdown/) || // pointerevents touch
  779. (sourceEventType.match(/mouse/) && ev.which === 1) // mouse is pressed
  780. ){
  781. enable_detect = true;
  782. }
  783. // mouse isn't pressed
  784. else if(sourceEventType.match(/mouse/) && ev.which !== 1) {
  785. enable_detect = false;
  786. }
  787. // we are in a touch event, set the touch triggered bool to true,
  788. // this for the conflicts that may occur on ios and android
  789. if(sourceEventType.match(/touch|pointer/)) {
  790. touch_triggered = true;
  791. }
  792. // count the total touches on the screen
  793. var count_touches = 0;
  794. // when touch has been triggered in this detection session
  795. // and we are now handling a mouse event, we stop that to prevent conflicts
  796. if(enable_detect) {
  797. // update pointerevent
  798. if(ionic.Gestures.HAS_POINTEREVENTS && eventType != ionic.Gestures.EVENT_END) {
  799. count_touches = ionic.Gestures.PointerEvent.updatePointer(eventType, ev);
  800. }
  801. // touch
  802. else if(sourceEventType.match(/touch/)) {
  803. count_touches = ev.touches.length;
  804. }
  805. // mouse
  806. else if(!touch_triggered) {
  807. count_touches = sourceEventType.match(/up/) ? 0 : 1;
  808. }
  809. // if we are in a end event, but when we remove one touch and
  810. // we still have enough, set eventType to move
  811. if(count_touches > 0 && eventType == ionic.Gestures.EVENT_END) {
  812. eventType = ionic.Gestures.EVENT_MOVE;
  813. }
  814. // no touches, force the end event
  815. else if(!count_touches) {
  816. eventType = ionic.Gestures.EVENT_END;
  817. }
  818. // store the last move event
  819. if(count_touches || last_move_event === null) {
  820. last_move_event = ev;
  821. }
  822. // trigger the handler
  823. handler.call(ionic.Gestures.detection, self.collectEventData(element, eventType, self.getTouchList(last_move_event, eventType), ev));
  824. // remove pointerevent from list
  825. if(ionic.Gestures.HAS_POINTEREVENTS && eventType == ionic.Gestures.EVENT_END) {
  826. count_touches = ionic.Gestures.PointerEvent.updatePointer(eventType, ev);
  827. }
  828. }
  829. //debug(sourceEventType +" "+ eventType);
  830. // on the end we reset everything
  831. if(!count_touches) {
  832. last_move_event = null;
  833. enable_detect = false;
  834. touch_triggered = false;
  835. ionic.Gestures.PointerEvent.reset();
  836. }
  837. });
  838. },
  839. /**
  840. * we have different events for each device/browser
  841. * determine what we need and set them in the ionic.Gestures.EVENT_TYPES constant
  842. */
  843. determineEventTypes: function determineEventTypes() {
  844. // determine the eventtype we want to set
  845. var types;
  846. // pointerEvents magic
  847. if(ionic.Gestures.HAS_POINTEREVENTS) {
  848. types = ionic.Gestures.PointerEvent.getEvents();
  849. }
  850. // on Android, iOS, blackberry, windows mobile we dont want any mouseevents
  851. else if(ionic.Gestures.NO_MOUSEEVENTS) {
  852. types = [
  853. 'touchstart',
  854. 'touchmove',
  855. 'touchend touchcancel'];
  856. }
  857. // for non pointer events browsers and mixed browsers,
  858. // like chrome on windows8 touch laptop
  859. else {
  860. types = [
  861. 'touchstart mousedown',
  862. 'touchmove mousemove',
  863. 'touchend touchcancel mouseup'];
  864. }
  865. ionic.Gestures.EVENT_TYPES[ionic.Gestures.EVENT_START] = types[0];
  866. ionic.Gestures.EVENT_TYPES[ionic.Gestures.EVENT_MOVE] = types[1];
  867. ionic.Gestures.EVENT_TYPES[ionic.Gestures.EVENT_END] = types[2];
  868. },
  869. /**
  870. * create touchlist depending on the event
  871. * @param {Object} ev
  872. * @param {String} eventType used by the fakemultitouch plugin
  873. */
  874. getTouchList: function getTouchList(ev/*, eventType*/) {
  875. // get the fake pointerEvent touchlist
  876. if(ionic.Gestures.HAS_POINTEREVENTS) {
  877. return ionic.Gestures.PointerEvent.getTouchList();
  878. }
  879. // get the touchlist
  880. else if(ev.touches) {
  881. return ev.touches;
  882. }
  883. // make fake touchlist from mouse position
  884. else {
  885. ev.identifier = 1;
  886. return [ev];
  887. }
  888. },
  889. /**
  890. * collect event data for ionic.Gestures js
  891. * @param {HTMLElement} element
  892. * @param {String} eventType like ionic.Gestures.EVENT_MOVE
  893. * @param {Object} eventData
  894. */
  895. collectEventData: function collectEventData(element, eventType, touches, ev) {
  896. // find out pointerType
  897. var pointerType = ionic.Gestures.POINTER_TOUCH;
  898. if(ev.type.match(/mouse/) || ionic.Gestures.PointerEvent.matchType(ionic.Gestures.POINTER_MOUSE, ev)) {
  899. pointerType = ionic.Gestures.POINTER_MOUSE;
  900. }
  901. return {
  902. center: ionic.Gestures.utils.getCenter(touches),
  903. timeStamp: new Date().getTime(),
  904. target: ev.target,
  905. touches: touches,
  906. eventType: eventType,
  907. pointerType: pointerType,
  908. srcEvent: ev,
  909. /**
  910. * prevent the browser default actions
  911. * mostly used to disable scrolling of the browser
  912. */
  913. preventDefault: function() {
  914. if(this.srcEvent.preventManipulation) {
  915. this.srcEvent.preventManipulation();
  916. }
  917. if(this.srcEvent.preventDefault) {
  918. // this.srcEvent.preventDefault();
  919. }
  920. },
  921. /**
  922. * stop bubbling the event up to its parents
  923. */
  924. stopPropagation: function() {
  925. this.srcEvent.stopPropagation();
  926. },
  927. /**
  928. * immediately stop gesture detection
  929. * might be useful after a swipe was detected
  930. * @return {*}
  931. */
  932. stopDetect: function() {
  933. return ionic.Gestures.detection.stopDetect();
  934. }
  935. };
  936. }
  937. };
  938. ionic.Gestures.PointerEvent = {
  939. /**
  940. * holds all pointers
  941. * type {Object}
  942. */
  943. pointers: {},
  944. /**
  945. * get a list of pointers
  946. * @returns {Array} touchlist
  947. */
  948. getTouchList: function() {
  949. var self = this;
  950. var touchlist = [];
  951. // we can use forEach since pointerEvents only is in IE10
  952. Object.keys(self.pointers).sort().forEach(function(id) {
  953. touchlist.push(self.pointers[id]);
  954. });
  955. return touchlist;
  956. },
  957. /**
  958. * update the position of a pointer
  959. * @param {String} type ionic.Gestures.EVENT_END
  960. * @param {Object} pointerEvent
  961. */
  962. updatePointer: function(type, pointerEvent) {
  963. if(type == ionic.Gestures.EVENT_END) {
  964. this.pointers = {};
  965. }
  966. else {
  967. pointerEvent.identifier = pointerEvent.pointerId;
  968. this.pointers[pointerEvent.pointerId] = pointerEvent;
  969. }
  970. return Object.keys(this.pointers).length;
  971. },
  972. /**
  973. * check if ev matches pointertype
  974. * @param {String} pointerType ionic.Gestures.POINTER_MOUSE
  975. * @param {PointerEvent} ev
  976. */
  977. matchType: function(pointerType, ev) {
  978. if(!ev.pointerType) {
  979. return false;
  980. }
  981. var types = {};
  982. types[ionic.Gestures.POINTER_MOUSE] = (ev.pointerType == ev.MSPOINTER_TYPE_MOUSE || ev.pointerType == ionic.Gestures.POINTER_MOUSE);
  983. types[ionic.Gestures.POINTER_TOUCH] = (ev.pointerType == ev.MSPOINTER_TYPE_TOUCH || ev.pointerType == ionic.Gestures.POINTER_TOUCH);
  984. types[ionic.Gestures.POINTER_PEN] = (ev.pointerType == ev.MSPOINTER_TYPE_PEN || ev.pointerType == ionic.Gestures.POINTER_PEN);
  985. return types[pointerType];
  986. },
  987. /**
  988. * get events
  989. */
  990. getEvents: function() {
  991. return [
  992. 'pointerdown MSPointerDown',
  993. 'pointermove MSPointerMove',
  994. 'pointerup pointercancel MSPointerUp MSPointerCancel'
  995. ];
  996. },
  997. /**
  998. * reset the list
  999. */
  1000. reset: function() {
  1001. this.pointers = {};
  1002. }
  1003. };
  1004. ionic.Gestures.utils = {
  1005. /**
  1006. * extend method,
  1007. * also used for cloning when dest is an empty object
  1008. * @param {Object} dest
  1009. * @param {Object} src
  1010. * @param {Boolean} merge do a merge
  1011. * @returns {Object} dest
  1012. */
  1013. extend: function extend(dest, src, merge) {
  1014. for (var key in src) {
  1015. if(dest[key] !== undefined && merge) {
  1016. continue;
  1017. }
  1018. dest[key] = src[key];
  1019. }
  1020. return dest;
  1021. },
  1022. /**
  1023. * find if a node is in the given parent
  1024. * used for event delegation tricks
  1025. * @param {HTMLElement} node
  1026. * @param {HTMLElement} parent
  1027. * @returns {boolean} has_parent
  1028. */
  1029. hasParent: function(node, parent) {
  1030. while(node){
  1031. if(node == parent) {
  1032. return true;
  1033. }
  1034. node = node.parentNode;
  1035. }
  1036. return false;
  1037. },
  1038. /**
  1039. * get the center of all the touches
  1040. * @param {Array} touches
  1041. * @returns {Object} center
  1042. */
  1043. getCenter: function getCenter(touches) {
  1044. var valuesX = [], valuesY = [];
  1045. for(var t = 0, len = touches.length; t < len; t++) {
  1046. valuesX.push(touches[t].pageX);
  1047. valuesY.push(touches[t].pageY);
  1048. }
  1049. return {
  1050. pageX: ((Math.min.apply(Math, valuesX) + Math.max.apply(Math, valuesX)) / 2),
  1051. pageY: ((Math.min.apply(Math, valuesY) + Math.max.apply(Math, valuesY)) / 2)
  1052. };
  1053. },
  1054. /**
  1055. * calculate the velocity between two points
  1056. * @param {Number} delta_time
  1057. * @param {Number} delta_x
  1058. * @param {Number} delta_y
  1059. * @returns {Object} velocity
  1060. */
  1061. getVelocity: function getVelocity(delta_time, delta_x, delta_y) {
  1062. return {
  1063. x: Math.abs(delta_x / delta_time) || 0,
  1064. y: Math.abs(delta_y / delta_time) || 0
  1065. };
  1066. },
  1067. /**
  1068. * calculate the angle between two coordinates
  1069. * @param {Touch} touch1
  1070. * @param {Touch} touch2
  1071. * @returns {Number} angle
  1072. */
  1073. getAngle: function getAngle(touch1, touch2) {
  1074. var y = touch2.pageY - touch1.pageY,
  1075. x = touch2.pageX - touch1.pageX;
  1076. return Math.atan2(y, x) * 180 / Math.PI;
  1077. },
  1078. /**
  1079. * angle to direction define
  1080. * @param {Touch} touch1
  1081. * @param {Touch} touch2
  1082. * @returns {String} direction constant, like ionic.Gestures.DIRECTION_LEFT
  1083. */
  1084. getDirection: function getDirection(touch1, touch2) {
  1085. var x = Math.abs(touch1.pageX - touch2.pageX),
  1086. y = Math.abs(touch1.pageY - touch2.pageY);
  1087. if(x >= y) {
  1088. return touch1.pageX - touch2.pageX > 0 ? ionic.Gestures.DIRECTION_LEFT : ionic.Gestures.DIRECTION_RIGHT;
  1089. }
  1090. else {
  1091. return touch1.pageY - touch2.pageY > 0 ? ionic.Gestures.DIRECTION_UP : ionic.Gestures.DIRECTION_DOWN;
  1092. }
  1093. },
  1094. /**
  1095. * calculate the distance between two touches
  1096. * @param {Touch} touch1
  1097. * @param {Touch} touch2
  1098. * @returns {Number} distance
  1099. */
  1100. getDistance: function getDistance(touch1, touch2) {
  1101. var x = touch2.pageX - touch1.pageX,
  1102. y = touch2.pageY - touch1.pageY;
  1103. return Math.sqrt((x * x) + (y * y));
  1104. },
  1105. /**
  1106. * calculate the scale factor between two touchLists (fingers)
  1107. * no scale is 1, and goes down to 0 when pinched together, and bigger when pinched out
  1108. * @param {Array} start
  1109. * @param {Array} end
  1110. * @returns {Number} scale
  1111. */
  1112. getScale: function getScale(start, end) {
  1113. // need two fingers...
  1114. if(start.length >= 2 && end.length >= 2) {
  1115. return this.getDistance(end[0], end[1]) /
  1116. this.getDistance(start[0], start[1]);
  1117. }
  1118. return 1;
  1119. },
  1120. /**
  1121. * calculate the rotation degrees between two touchLists (fingers)
  1122. * @param {Array} start
  1123. * @param {Array} end
  1124. * @returns {Number} rotation
  1125. */
  1126. getRotation: function getRotation(start, end) {
  1127. // need two fingers
  1128. if(start.length >= 2 && end.length >= 2) {
  1129. return this.getAngle(end[1], end[0]) -
  1130. this.getAngle(start[1], start[0]);
  1131. }
  1132. return 0;
  1133. },
  1134. /**
  1135. * boolean if the direction is vertical
  1136. * @param {String} direction
  1137. * @returns {Boolean} is_vertical
  1138. */
  1139. isVertical: function isVertical(direction) {
  1140. return (direction == ionic.Gestures.DIRECTION_UP || direction == ionic.Gestures.DIRECTION_DOWN);
  1141. },
  1142. /**
  1143. * stop browser default behavior with css class
  1144. * @param {HtmlElement} element
  1145. * @param {Object} css_class
  1146. */
  1147. stopDefaultBrowserBehavior: function stopDefaultBrowserBehavior(element, css_class) {
  1148. // changed from making many style changes to just adding a preset classname
  1149. // less DOM manipulations, less code, and easier to control in the CSS side of things
  1150. // hammer.js doesn't come with CSS, but ionic does, which is why we prefer this method
  1151. if(element && element.classList) {
  1152. element.classList.add(css_class);
  1153. element.onselectstart = function() {
  1154. return false;
  1155. };
  1156. }
  1157. }
  1158. };
  1159. ionic.Gestures.detection = {
  1160. // contains all registred ionic.Gestures.gestures in the correct order
  1161. gestures: [],
  1162. // data of the current ionic.Gestures.gesture detection session
  1163. current: null,
  1164. // the previous ionic.Gestures.gesture session data
  1165. // is a full clone of the previous gesture.current object
  1166. previous: null,
  1167. // when this becomes true, no gestures are fired
  1168. stopped: false,
  1169. /**
  1170. * start ionic.Gestures.gesture detection
  1171. * @param {ionic.Gestures.Instance} inst
  1172. * @param {Object} eventData
  1173. */
  1174. startDetect: function startDetect(inst, eventData) {
  1175. // already busy with a ionic.Gestures.gesture detection on an element
  1176. if(this.current) {
  1177. return;
  1178. }
  1179. this.stopped = false;
  1180. this.current = {
  1181. inst: inst, // reference to ionic.GesturesInstance we're working for
  1182. startEvent: ionic.Gestures.utils.extend({}, eventData), // start eventData for distances, timing etc
  1183. lastEvent: false, // last eventData
  1184. name: '' // current gesture we're in/detected, can be 'tap', 'hold' etc
  1185. };
  1186. this.detect(eventData);
  1187. },
  1188. /**
  1189. * ionic.Gestures.gesture detection
  1190. * @param {Object} eventData
  1191. */
  1192. detect: function detect(eventData) {
  1193. if(!this.current || this.stopped) {
  1194. return null;
  1195. }
  1196. // extend event data with calculations about scale, distance etc
  1197. eventData = this.extendEventData(eventData);
  1198. // instance options
  1199. var inst_options = this.current.inst.options;
  1200. // call ionic.Gestures.gesture handlers
  1201. for(var g = 0, len = this.gestures.length; g < len; g++) {
  1202. var gesture = this.gestures[g];
  1203. // only when the instance options have enabled this gesture
  1204. if(!this.stopped && inst_options[gesture.name] !== false) {
  1205. // if a handler returns false, we stop with the detection
  1206. if(gesture.handler.call(gesture, eventData, this.current.inst) === false) {
  1207. this.stopDetect();
  1208. break;
  1209. }
  1210. }
  1211. }
  1212. // store as previous event event
  1213. if(this.current) {
  1214. this.current.lastEvent = eventData;
  1215. }
  1216. // endevent, but not the last touch, so dont stop
  1217. if(eventData.eventType == ionic.Gestures.EVENT_END && !eventData.touches.length - 1) {
  1218. this.stopDetect();
  1219. }
  1220. return eventData;
  1221. },
  1222. /**
  1223. * clear the ionic.Gestures.gesture vars
  1224. * this is called on endDetect, but can also be used when a final ionic.Gestures.gesture has been detected
  1225. * to stop other ionic.Gestures.gestures from being fired
  1226. */
  1227. stopDetect: function stopDetect() {
  1228. // clone current data to the store as the previous gesture
  1229. // used for the double tap gesture, since this is an other gesture detect session
  1230. this.previous = ionic.Gestures.utils.extend({}, this.current);
  1231. // reset the current
  1232. this.current = null;
  1233. // stopped!
  1234. this.stopped = true;
  1235. },
  1236. /**
  1237. * extend eventData for ionic.Gestures.gestures
  1238. * @param {Object} ev
  1239. * @returns {Object} ev
  1240. */
  1241. extendEventData: function extendEventData(ev) {
  1242. var startEv = this.current.startEvent;
  1243. // if the touches change, set the new touches over the startEvent touches
  1244. // this because touchevents don't have all the touches on touchstart, or the
  1245. // user must place his fingers at the EXACT same time on the screen, which is not realistic
  1246. // but, sometimes it happens that both fingers are touching at the EXACT same time
  1247. if(startEv && (ev.touches.length != startEv.touches.length || ev.touches === startEv.touches)) {
  1248. // extend 1 level deep to get the touchlist with the touch objects
  1249. startEv.touches = [];
  1250. for(var i = 0, len = ev.touches.length; i < len; i++) {
  1251. startEv.touches.push(ionic.Gestures.utils.extend({}, ev.touches[i]));
  1252. }
  1253. }
  1254. var delta_time = ev.timeStamp - startEv.timeStamp,
  1255. delta_x = ev.center.pageX - startEv.center.pageX,
  1256. delta_y = ev.center.pageY - startEv.center.pageY,
  1257. velocity = ionic.Gestures.utils.getVelocity(delta_time, delta_x, delta_y);
  1258. ionic.Gestures.utils.extend(ev, {
  1259. deltaTime: delta_time,
  1260. deltaX: delta_x,
  1261. deltaY: delta_y,
  1262. velocityX: velocity.x,
  1263. velocityY: velocity.y,
  1264. distance: ionic.Gestures.utils.getDistance(startEv.center, ev.center),
  1265. angle: ionic.Gestures.utils.getAngle(startEv.center, ev.center),
  1266. direction: ionic.Gestures.utils.getDirection(startEv.center, ev.center),
  1267. scale: ionic.Gestures.utils.getScale(startEv.touches, ev.touches),
  1268. rotation: ionic.Gestures.utils.getRotation(startEv.touches, ev.touches),
  1269. startEvent: startEv
  1270. });
  1271. return ev;
  1272. },
  1273. /**
  1274. * register new gesture
  1275. * @param {Object} gesture object, see gestures.js for documentation
  1276. * @returns {Array} gestures
  1277. */
  1278. register: function register(gesture) {
  1279. // add an enable gesture options if there is no given
  1280. var options = gesture.defaults || {};
  1281. if(options[gesture.name] === undefined) {
  1282. options[gesture.name] = true;
  1283. }
  1284. // extend ionic.Gestures default options with the ionic.Gestures.gesture options
  1285. ionic.Gestures.utils.extend(ionic.Gestures.defaults, options, true);
  1286. // set its index
  1287. gesture.index = gesture.index || 1000;
  1288. // add ionic.Gestures.gesture to the list
  1289. this.gestures.push(gesture);
  1290. // sort the list by index
  1291. this.gestures.sort(function(a, b) {
  1292. if (a.index < b.index) {
  1293. return -1;
  1294. }
  1295. if (a.index > b.index) {
  1296. return 1;
  1297. }
  1298. return 0;
  1299. });
  1300. return this.gestures;
  1301. }
  1302. };
  1303. ionic.Gestures.gestures = ionic.Gestures.gestures || {};
  1304. /**
  1305. * Custom gestures
  1306. * ==============================
  1307. *
  1308. * Gesture object
  1309. * --------------------
  1310. * The object structure of a gesture:
  1311. *
  1312. * { name: 'mygesture',
  1313. * index: 1337,
  1314. * defaults: {
  1315. * mygesture_option: true
  1316. * }
  1317. * handler: function(type, ev, inst) {
  1318. * // trigger gesture event
  1319. * inst.trigger(this.name, ev);
  1320. * }
  1321. * }
  1322. * @param {String} name
  1323. * this should be the name of the gesture, lowercase
  1324. * it is also being used to disable/enable the gesture per instance config.
  1325. *
  1326. * @param {Number} [index=1000]
  1327. * the index of the gesture, where it is going to be in the stack of gestures detection
  1328. * like when you build an gesture that depends on the drag gesture, it is a good
  1329. * idea to place it after the index of the drag gesture.
  1330. *
  1331. * @param {Object} [defaults={}]
  1332. * the default settings of the gesture. these are added to the instance settings,
  1333. * and can be overruled per instance. you can also add the name of the gesture,
  1334. * but this is also added by default (and set to true).
  1335. *
  1336. * @param {Function} handler
  1337. * this handles the gesture detection of your custom gesture and receives the
  1338. * following arguments:
  1339. *
  1340. * @param {Object} eventData
  1341. * event data containing the following properties:
  1342. * timeStamp {Number} time the event occurred
  1343. * target {HTMLElement} target element
  1344. * touches {Array} touches (fingers, pointers, mouse) on the screen
  1345. * pointerType {String} kind of pointer that was used. matches ionic.Gestures.POINTER_MOUSE|TOUCH
  1346. * center {Object} center position of the touches. contains pageX and pageY
  1347. * deltaTime {Number} the total time of the touches in the screen
  1348. * deltaX {Number} the delta on x axis we haved moved
  1349. * deltaY {Number} the delta on y axis we haved moved
  1350. * velocityX {Number} the velocity on the x
  1351. * velocityY {Number} the velocity on y
  1352. * angle {Number} the angle we are moving
  1353. * direction {String} the direction we are moving. matches ionic.Gestures.DIRECTION_UP|DOWN|LEFT|RIGHT
  1354. * distance {Number} the distance we haved moved
  1355. * scale {Number} scaling of the touches, needs 2 touches
  1356. * rotation {Number} rotation of the touches, needs 2 touches *
  1357. * eventType {String} matches ionic.Gestures.EVENT_START|MOVE|END
  1358. * srcEvent {Object} the source event, like TouchStart or MouseDown *
  1359. * startEvent {Object} contains the same properties as above,
  1360. * but from the first touch. this is used to calculate
  1361. * distances, deltaTime, scaling etc
  1362. *
  1363. * @param {ionic.Gestures.Instance} inst
  1364. * the instance we are doing the detection for. you can get the options from
  1365. * the inst.options object and trigger the gesture event by calling inst.trigger
  1366. *
  1367. *
  1368. * Handle gestures
  1369. * --------------------
  1370. * inside the handler you can get/set ionic.Gestures.detectionic.current. This is the current
  1371. * detection sessionic. It has the following properties
  1372. * @param {String} name
  1373. * contains the name of the gesture we have detected. it has not a real function,
  1374. * only to check in other gestures if something is detected.
  1375. * like in the drag gesture we set it to 'drag' and in the swipe gesture we can
  1376. * check if the current gesture is 'drag' by accessing ionic.Gestures.detectionic.current.name
  1377. *
  1378. * readonly
  1379. * @param {ionic.Gestures.Instance} inst
  1380. * the instance we do the detection for
  1381. *
  1382. * readonly
  1383. * @param {Object} startEvent
  1384. * contains the properties of the first gesture detection in this sessionic.
  1385. * Used for calculations about timing, distance, etc.
  1386. *
  1387. * readonly
  1388. * @param {Object} lastEvent
  1389. * contains all the properties of the last gesture detect in this sessionic.
  1390. *
  1391. * after the gesture detection session has been completed (user has released the screen)
  1392. * the ionic.Gestures.detectionic.current object is copied into ionic.Gestures.detectionic.previous,
  1393. * this is usefull for gestures like doubletap, where you need to know if the
  1394. * previous gesture was a tap
  1395. *
  1396. * options that have been set by the instance can be received by calling inst.options
  1397. *
  1398. * You can trigger a gesture event by calling inst.trigger("mygesture", event).
  1399. * The first param is the name of your gesture, the second the event argument
  1400. *
  1401. *
  1402. * Register gestures
  1403. * --------------------
  1404. * When an gesture is added to the ionic.Gestures.gestures object, it is auto registered
  1405. * at the setup of the first ionic.Gestures instance. You can also call ionic.Gestures.detectionic.register
  1406. * manually and pass your gesture object as a param
  1407. *
  1408. */
  1409. /**
  1410. * Hold
  1411. * Touch stays at the same place for x time
  1412. * events hold
  1413. */
  1414. ionic.Gestures.gestures.Hold = {
  1415. name: 'hold',
  1416. index: 10,
  1417. defaults: {
  1418. hold_timeout: 500,
  1419. hold_threshold: 9
  1420. },
  1421. timer: null,
  1422. handler: function holdGesture(ev, inst) {
  1423. switch(ev.eventType) {
  1424. case ionic.Gestures.EVENT_START:
  1425. // clear any running timers
  1426. clearTimeout(this.timer);
  1427. // set the gesture so we can check in the timeout if it still is
  1428. ionic.Gestures.detection.current.name = this.name;
  1429. // set timer and if after the timeout it still is hold,
  1430. // we trigger the hold event
  1431. this.timer = setTimeout(function() {
  1432. if(ionic.Gestures.detection.current.name == 'hold') {
  1433. ionic.tap.cancelClick();
  1434. inst.trigger('hold', ev);
  1435. }
  1436. }, inst.options.hold_timeout);
  1437. break;
  1438. // when you move or end we clear the timer
  1439. case ionic.Gestures.EVENT_MOVE:
  1440. if(ev.distance > inst.options.hold_threshold) {
  1441. clearTimeout(this.timer);
  1442. }
  1443. break;
  1444. case ionic.Gestures.EVENT_END:
  1445. clearTimeout(this.timer);
  1446. break;
  1447. }
  1448. }
  1449. };
  1450. /**
  1451. * Tap/DoubleTap
  1452. * Quick touch at a place or double at the same place
  1453. * events tap, doubletap
  1454. */
  1455. ionic.Gestures.gestures.Tap = {
  1456. name: 'tap',
  1457. index: 100,
  1458. defaults: {
  1459. tap_max_touchtime: 250,
  1460. tap_max_distance: 10,
  1461. tap_always: true,
  1462. doubletap_distance: 20,
  1463. doubletap_interval: 300
  1464. },
  1465. handler: function tapGesture(ev, inst) {
  1466. if(ev.eventType == ionic.Gestures.EVENT_END && ev.srcEvent.type != 'touchcancel') {
  1467. // previous gesture, for the double tap since these are two different gesture detections
  1468. var prev = ionic.Gestures.detection.previous,
  1469. did_doubletap = false;
  1470. // when the touchtime is higher then the max touch time
  1471. // or when the moving distance is too much
  1472. if(ev.deltaTime > inst.options.tap_max_touchtime ||
  1473. ev.distance > inst.options.tap_max_distance) {
  1474. return;
  1475. }
  1476. // check if double tap
  1477. if(prev && prev.name == 'tap' &&
  1478. (ev.timeStamp - prev.lastEvent.timeStamp) < inst.options.doubletap_interval &&
  1479. ev.distance < inst.options.doubletap_distance) {
  1480. inst.trigger('doubletap', ev);
  1481. did_doubletap = true;
  1482. }
  1483. // do a single tap
  1484. if(!did_doubletap || inst.options.tap_always) {
  1485. ionic.Gestures.detection.current.name = 'tap';
  1486. inst.trigger('tap', ev);
  1487. }
  1488. }
  1489. }
  1490. };
  1491. /**
  1492. * Swipe
  1493. * triggers swipe events when the end velocity is above the threshold
  1494. * events swipe, swipeleft, swiperight, swipeup, swipedown
  1495. */
  1496. ionic.Gestures.gestures.Swipe = {
  1497. name: 'swipe',
  1498. index: 40,
  1499. defaults: {
  1500. // set 0 for unlimited, but this can conflict with transform
  1501. swipe_max_touches: 1,
  1502. swipe_velocity: 0.4
  1503. },
  1504. handler: function swipeGesture(ev, inst) {
  1505. if(ev.eventType == ionic.Gestures.EVENT_END) {
  1506. // max touches
  1507. if(inst.options.swipe_max_touches > 0 &&
  1508. ev.touches.length > inst.options.swipe_max_touches) {
  1509. return;
  1510. }
  1511. // when the distance we moved is too small we skip this gesture
  1512. // or we can be already in dragging
  1513. if(ev.velocityX > inst.options.swipe_velocity ||
  1514. ev.velocityY > inst.options.swipe_velocity) {
  1515. // trigger swipe events
  1516. inst.trigger(this.name, ev);
  1517. inst.trigger(this.name + ev.direction, ev);
  1518. }
  1519. }
  1520. }
  1521. };
  1522. /**
  1523. * Drag
  1524. * Move with x fingers (default 1) around on the page. Blocking the scrolling when
  1525. * moving left and right is a good practice. When all the drag events are blocking
  1526. * you disable scrolling on that area.
  1527. * events drag, drapleft, dragright, dragup, dragdown
  1528. */
  1529. ionic.Gestures.gestures.Drag = {
  1530. name: 'drag',
  1531. index: 50,
  1532. defaults: {
  1533. drag_min_distance: 10,
  1534. // Set correct_for_drag_min_distance to true to make the starting point of the drag
  1535. // be calculated from where the drag was triggered, not from where the touch started.
  1536. // Useful to avoid a jerk-starting drag, which can make fine-adjustments
  1537. // through dragging difficult, and be visually unappealing.
  1538. correct_for_drag_min_distance: true,
  1539. // set 0 for unlimited, but this can conflict with transform
  1540. drag_max_touches: 1,
  1541. // prevent default browser behavior when dragging occurs
  1542. // be careful with it, it makes the element a blocking element
  1543. // when you are using the drag gesture, it is a good practice to set this true
  1544. drag_block_horizontal: true,
  1545. drag_block_vertical: true,
  1546. // drag_lock_to_axis keeps the drag gesture on the axis that it started on,
  1547. // It disallows vertical directions if the initial direction was horizontal, and vice versa.
  1548. drag_lock_to_axis: false,
  1549. // drag lock only kicks in when distance > drag_lock_min_distance
  1550. // This way, locking occurs only when the distance has become large enough to reliably determine the direction
  1551. drag_lock_min_distance: 25,
  1552. // prevent default if the gesture is going the given direction
  1553. prevent_default_directions: []
  1554. },
  1555. triggered: false,
  1556. handler: function dragGesture(ev, inst) {
  1557. if (ev.srcEvent.type == 'touchstart' || ev.srcEvent.type == 'touchend') {
  1558. this.preventedFirstMove = false;
  1559. } else if (!this.preventedFirstMove && ev.srcEvent.type == 'touchmove') {
  1560. // Prevent gestures that are not intended for this event handler from firing subsequent times
  1561. if (inst.options.prevent_default_directions.length > 0
  1562. && inst.options.prevent_default_directions.indexOf(ev.direction) != -1) {
  1563. ev.srcEvent.preventDefault();
  1564. }
  1565. this.preventedFirstMove = true;
  1566. }
  1567. // current gesture isnt drag, but dragged is true
  1568. // this means an other gesture is busy. now call dragend
  1569. if(ionic.Gestures.detection.current.name != this.name && this.triggered) {
  1570. inst.trigger(this.name + 'end', ev);
  1571. this.triggered = false;
  1572. return;
  1573. }
  1574. // max touches
  1575. if(inst.options.drag_max_touches > 0 &&
  1576. ev.touches.length > inst.options.drag_max_touches) {
  1577. return;
  1578. }
  1579. switch(ev.eventType) {
  1580. case ionic.Gestures.EVENT_START:
  1581. this.triggered = false;
  1582. break;
  1583. case ionic.Gestures.EVENT_MOVE:
  1584. // when the distance we moved is too small we skip this gesture
  1585. // or we can be already in dragging
  1586. if(ev.distance < inst.options.drag_min_distance &&
  1587. ionic.Gestures.detection.current.name != this.name) {
  1588. return;
  1589. }
  1590. // we are dragging!
  1591. if(ionic.Gestures.detection.current.name != this.name) {
  1592. ionic.Gestures.detection.current.name = this.name;
  1593. if (inst.options.correct_for_drag_min_distance) {
  1594. // When a drag is triggered, set the event center to drag_min_distance pixels from the original event center.
  1595. // Without this correction, the dragged distance would jumpstart at drag_min_distance pixels instead of at 0.
  1596. // It might be useful to save the original start point somewhere
  1597. var factor = Math.abs(inst.options.drag_min_distance / ev.distance);
  1598. ionic.Gestures.detection.current.startEvent.center.pageX += ev.deltaX * factor;
  1599. ionic.Gestures.detection.current.startEvent.center.pageY += ev.deltaY * factor;
  1600. // recalculate event data using new start point
  1601. ev = ionic.Gestures.detection.extendEventData(ev);
  1602. }
  1603. }
  1604. // lock drag to axis?
  1605. if(ionic.Gestures.detection.current.lastEvent.drag_locked_to_axis || (inst.options.drag_lock_to_axis && inst.options.drag_lock_min_distance <= ev.distance)) {
  1606. ev.drag_locked_to_axis = true;
  1607. }
  1608. var last_direction = ionic.Gestures.detection.current.lastEvent.direction;
  1609. if(ev.drag_locked_to_axis && last_direction !== ev.direction) {
  1610. // keep direction on the axis that the drag gesture started on
  1611. if(ionic.Gestures.utils.isVertical(last_direction)) {
  1612. ev.direction = (ev.deltaY < 0) ? ionic.Gestures.DIRECTION_UP : ionic.Gestures.DIRECTION_DOWN;
  1613. }
  1614. else {
  1615. ev.direction = (ev.deltaX < 0) ? ionic.Gestures.DIRECTION_LEFT : ionic.Gestures.DIRECTION_RIGHT;
  1616. }
  1617. }
  1618. // first time, trigger dragstart event
  1619. if(!this.triggered) {
  1620. inst.trigger(this.name + 'start', ev);
  1621. this.triggered = true;
  1622. }
  1623. // trigger normal event
  1624. inst.trigger(this.name, ev);
  1625. // direction event, like dragdown
  1626. inst.trigger(this.name + ev.direction, ev);
  1627. // block the browser events
  1628. if( (inst.options.drag_block_vertical && ionic.Gestures.utils.isVertical(ev.direction)) ||
  1629. (inst.options.drag_block_horizontal && !ionic.Gestures.utils.isVertical(ev.direction))) {
  1630. ev.preventDefault();
  1631. }
  1632. break;
  1633. case ionic.Gestures.EVENT_END:
  1634. // trigger dragend
  1635. if(this.triggered) {
  1636. inst.trigger(this.name + 'end', ev);
  1637. }
  1638. this.triggered = false;
  1639. break;
  1640. }
  1641. }
  1642. };
  1643. /**
  1644. * Transform
  1645. * User want to scale or rotate with 2 fingers
  1646. * events transform, pinch, pinchin, pinchout, rotate
  1647. */
  1648. ionic.Gestures.gestures.Transform = {
  1649. name: 'transform',
  1650. index: 45,
  1651. defaults: {
  1652. // factor, no scale is 1, zoomin is to 0 and zoomout until higher then 1
  1653. transform_min_scale: 0.01,
  1654. // rotation in degrees
  1655. transform_min_rotation: 1,
  1656. // prevent default browser behavior when two touches are on the screen
  1657. // but it makes the element a blocking element
  1658. // when you are using the transform gesture, it is a good practice to set this true
  1659. transform_always_block: false
  1660. },
  1661. triggered: false,
  1662. handler: function transformGesture(ev, inst) {
  1663. // current gesture isnt drag, but dragged is true
  1664. // this means an other gesture is busy. now call dragend
  1665. if(ionic.Gestures.detection.current.name != this.name && this.triggered) {
  1666. inst.trigger(this.name + 'end', ev);
  1667. this.triggered = false;
  1668. return;
  1669. }
  1670. // atleast multitouch
  1671. if(ev.touches.length < 2) {
  1672. return;
  1673. }
  1674. // prevent default when two fingers are on the screen
  1675. if(inst.options.transform_always_block) {
  1676. ev.preventDefault();
  1677. }
  1678. switch(ev.eventType) {
  1679. case ionic.Gestures.EVENT_START:
  1680. this.triggered = false;
  1681. break;
  1682. case ionic.Gestures.EVENT_MOVE:
  1683. var scale_threshold = Math.abs(1 - ev.scale);
  1684. var rotation_threshold = Math.abs(ev.rotation);
  1685. // when the distance we moved is too small we skip this gesture
  1686. // or we can be already in dragging
  1687. if(scale_threshold < inst.options.transform_min_scale &&
  1688. rotation_threshold < inst.options.transform_min_rotation) {
  1689. return;
  1690. }
  1691. // we are transforming!
  1692. ionic.Gestures.detection.current.name = this.name;
  1693. // first time, trigger dragstart event
  1694. if(!this.triggered) {
  1695. inst.trigger(this.name + 'start', ev);
  1696. this.triggered = true;
  1697. }
  1698. inst.trigger(this.name, ev); // basic transform event
  1699. // trigger rotate event
  1700. if(rotation_threshold > inst.options.transform_min_rotation) {
  1701. inst.trigger('rotate', ev);
  1702. }
  1703. // trigger pinch event
  1704. if(scale_threshold > inst.options.transform_min_scale) {
  1705. inst.trigger('pinch', ev);
  1706. inst.trigger('pinch' + ((ev.scale < 1) ? 'in' : 'out'), ev);
  1707. }
  1708. break;
  1709. case ionic.Gestures.EVENT_END:
  1710. // trigger dragend
  1711. if(this.triggered) {
  1712. inst.trigger(this.name + 'end', ev);
  1713. }
  1714. this.triggered = false;
  1715. break;
  1716. }
  1717. }
  1718. };
  1719. /**
  1720. * Touch
  1721. * Called as first, tells the user has touched the screen
  1722. * events touch
  1723. */
  1724. ionic.Gestures.gestures.Touch = {
  1725. name: 'touch',
  1726. index: -Infinity,
  1727. defaults: {
  1728. // call preventDefault at touchstart, and makes the element blocking by
  1729. // disabling the scrolling of the page, but it improves gestures like
  1730. // transforming and dragging.
  1731. // be careful with using this, it can be very annoying for users to be stuck
  1732. // on the page
  1733. prevent_default: false,
  1734. // disable mouse events, so only touch (or pen!) input triggers events
  1735. prevent_mouseevents: false
  1736. },
  1737. handler: function touchGesture(ev, inst) {
  1738. if(inst.options.prevent_mouseevents && ev.pointerType == ionic.Gestures.POINTER_MOUSE) {
  1739. ev.stopDetect();
  1740. return;
  1741. }
  1742. if(inst.options.prevent_default) {
  1743. ev.preventDefault();
  1744. }
  1745. if(ev.eventType == ionic.Gestures.EVENT_START) {
  1746. inst.trigger(this.name, ev);
  1747. }
  1748. }
  1749. };
  1750. /**
  1751. * Release
  1752. * Called as last, tells the user has released the screen
  1753. * events release
  1754. */
  1755. ionic.Gestures.gestures.Release = {
  1756. name: 'release',
  1757. index: Infinity,
  1758. handler: function releaseGesture(ev, inst) {
  1759. if(ev.eventType == ionic.Gestures.EVENT_END) {
  1760. inst.trigger(this.name, ev);
  1761. }
  1762. }
  1763. };
  1764. })(window.ionic);
  1765. (function(window, document, ionic) {
  1766. function getParameterByName(name) {
  1767. name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
  1768. var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
  1769. results = regex.exec(location.search);
  1770. return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
  1771. }
  1772. var IOS = 'ios';
  1773. var ANDROID = 'android';
  1774. var WINDOWS_PHONE = 'windowsphone';
  1775. var EDGE = 'edge';
  1776. var CROSSWALK = 'crosswalk';
  1777. var requestAnimationFrame = ionic.requestAnimationFrame;
  1778. /**
  1779. * @ngdoc utility
  1780. * @name ionic.Platform
  1781. * @module ionic
  1782. * @description
  1783. * A set of utility methods that can be used to retrieve the device ready state and
  1784. * various other information such as what kind of platform the app is currently installed on.
  1785. *
  1786. * @usage
  1787. * ```js
  1788. * angular.module('PlatformApp', ['ionic'])
  1789. * .controller('PlatformCtrl', function($scope) {
  1790. *
  1791. * ionic.Platform.ready(function(){
  1792. * // will execute when device is ready, or immediately if the device is already ready.
  1793. * });
  1794. *
  1795. * var deviceInformation = ionic.Platform.device();
  1796. *
  1797. * var isWebView = ionic.Platform.isWebView();
  1798. * var isIPad = ionic.Platform.isIPad();
  1799. * var isIOS = ionic.Platform.isIOS();
  1800. * var isAndroid = ionic.Platform.isAndroid();
  1801. * var isWindowsPhone = ionic.Platform.isWindowsPhone();
  1802. *
  1803. * var currentPlatform = ionic.Platform.platform();
  1804. * var currentPlatformVersion = ionic.Platform.version();
  1805. *
  1806. * ionic.Platform.exitApp(); // stops the app
  1807. * });
  1808. * ```
  1809. */
  1810. var self = ionic.Platform = {
  1811. // Put navigator on platform so it can be mocked and set
  1812. // the browser does not allow window.navigator to be set
  1813. navigator: window.navigator,
  1814. /**
  1815. * @ngdoc property
  1816. * @name ionic.Platform#isReady
  1817. * @returns {boolean} Whether the device is ready.
  1818. */
  1819. isReady: false,
  1820. /**
  1821. * @ngdoc property
  1822. * @name ionic.Platform#isFullScreen
  1823. * @returns {boolean} Whether the device is fullscreen.
  1824. */
  1825. isFullScreen: false,
  1826. /**
  1827. * @ngdoc property
  1828. * @name ionic.Platform#platforms
  1829. * @returns {Array(string)} An array of all platforms found.
  1830. */
  1831. platforms: null,
  1832. /**
  1833. * @ngdoc property
  1834. * @name ionic.Platform#grade
  1835. * @returns {string} What grade the current platform is.
  1836. */
  1837. grade: null,
  1838. /**
  1839. * @ngdoc property
  1840. * @name ionic.Platform#ua
  1841. * @returns {string} What User Agent is.
  1842. */
  1843. ua: navigator.userAgent,
  1844. /**
  1845. * @ngdoc method
  1846. * @name ionic.Platform#ready
  1847. * @description
  1848. * Trigger a callback once the device is ready, or immediately
  1849. * if the device is already ready. This method can be run from
  1850. * anywhere and does not need to be wrapped by any additonal methods.
  1851. * When the app is within a WebView (Cordova), it'll fire
  1852. * the callback once the device is ready. If the app is within
  1853. * a web browser, it'll fire the callback after `window.load`.
  1854. * Please remember that Cordova features (Camera, FileSystem, etc) still
  1855. * will not work in a web browser.
  1856. * @param {function} callback The function to call.
  1857. */
  1858. ready: function(cb) {
  1859. // run through tasks to complete now that the device is ready
  1860. if (self.isReady) {
  1861. cb();
  1862. } else {
  1863. // the platform isn't ready yet, add it to this array
  1864. // which will be called once the platform is ready
  1865. readyCallbacks.push(cb);
  1866. }
  1867. },
  1868. /**
  1869. * @private
  1870. */
  1871. detect: function() {
  1872. self._checkPlatforms();
  1873. requestAnimationFrame(function() {
  1874. // only add to the body class if we got platform info
  1875. for (var i = 0; i < self.platforms.length; i++) {
  1876. document.body.classList.add('platform-' + self.platforms[i]);
  1877. }
  1878. });
  1879. },
  1880. /**
  1881. * @ngdoc method
  1882. * @name ionic.Platform#setGrade
  1883. * @description Set the grade of the device: 'a', 'b', or 'c'. 'a' is the best
  1884. * (most css features enabled), 'c' is the worst. By default, sets the grade
  1885. * depending on the current device.
  1886. * @param {string} grade The new grade to set.
  1887. */
  1888. setGrade: function(grade) {
  1889. var oldGrade = self.grade;
  1890. self.grade = grade;
  1891. requestAnimationFrame(function() {
  1892. if (oldGrade) {
  1893. document.body.classList.remove('grade-' + oldGrade);
  1894. }
  1895. document.body.classList.add('grade-' + grade);
  1896. });
  1897. },
  1898. /**
  1899. * @ngdoc method
  1900. * @name ionic.Platform#device
  1901. * @description Return the current device (given by cordova).
  1902. * @returns {object} The device object.
  1903. */
  1904. device: function() {
  1905. return window.device || {};
  1906. },
  1907. _checkPlatforms: function() {
  1908. self.platforms = [];
  1909. var grade = 'a';
  1910. if (self.isWebView()) {
  1911. self.platforms.push('webview');
  1912. if (!(!window.cordova && !window.PhoneGap && !window.phonegap)) {
  1913. self.platforms.push('cordova');
  1914. } else if (typeof window.forge === 'object') {
  1915. self.platforms.push('trigger');
  1916. }
  1917. } else {
  1918. self.platforms.push('browser');
  1919. }
  1920. if (self.isIPad()) self.platforms.push('ipad');
  1921. var platform = self.platform();
  1922. if (platform) {
  1923. self.platforms.push(platform);
  1924. var version = self.version();
  1925. if (version) {
  1926. var v = version.toString();
  1927. if (v.indexOf('.') > 0) {
  1928. v = v.replace('.', '_');
  1929. } else {
  1930. v += '_0';
  1931. }
  1932. self.platforms.push(platform + v.split('_')[0]);
  1933. self.platforms.push(platform + v);
  1934. if (self.isAndroid() && version < 4.4) {
  1935. grade = (version < 4 ? 'c' : 'b');
  1936. } else if (self.isWindowsPhone()) {
  1937. grade = 'b';
  1938. }
  1939. }
  1940. }
  1941. self.setGrade(grade);
  1942. },
  1943. /**
  1944. * @ngdoc method
  1945. * @name ionic.Platform#isWebView
  1946. * @returns {boolean} Check if we are running within a WebView (such as Cordova).
  1947. */
  1948. isWebView: function() {
  1949. return !(!window.cordova && !window.PhoneGap && !window.phonegap && window.forge !== 'object');
  1950. },
  1951. /**
  1952. * @ngdoc method
  1953. * @name ionic.Platform#isIPad
  1954. * @returns {boolean} Whether we are running on iPad.
  1955. */
  1956. isIPad: function() {
  1957. if (/iPad/i.test(self.navigator.platform)) {
  1958. return true;
  1959. }
  1960. return /iPad/i.test(self.ua);
  1961. },
  1962. /**
  1963. * @ngdoc method
  1964. * @name ionic.Platform#isIOS
  1965. * @returns {boolean} Whether we are running on iOS.
  1966. */
  1967. isIOS: function() {
  1968. return self.is(IOS);
  1969. },
  1970. /**
  1971. * @ngdoc method
  1972. * @name ionic.Platform#isAndroid
  1973. * @returns {boolean} Whether we are running on Android.
  1974. */
  1975. isAndroid: function() {
  1976. return self.is(ANDROID);
  1977. },
  1978. /**
  1979. * @ngdoc method
  1980. * @name ionic.Platform#isWindowsPhone
  1981. * @returns {boolean} Whether we are running on Windows Phone.
  1982. */
  1983. isWindowsPhone: function() {
  1984. return self.is(WINDOWS_PHONE);
  1985. },
  1986. /**
  1987. * @ngdoc method
  1988. * @name ionic.Platform#isEdge
  1989. * @returns {boolean} Whether we are running on MS Edge/Windows 10 (inc. Phone)
  1990. */
  1991. isEdge: function() {
  1992. return self.is(EDGE);
  1993. },
  1994. isCrosswalk: function() {
  1995. return self.is(CROSSWALK);
  1996. },
  1997. /**
  1998. * @ngdoc method
  1999. * @name ionic.Platform#platform
  2000. * @returns {string} The name of the current platform.
  2001. */
  2002. platform: function() {
  2003. // singleton to get the platform name
  2004. if (platformName === null) self.setPlatform(self.device().platform);
  2005. return platformName;
  2006. },
  2007. /**
  2008. * @private
  2009. */
  2010. setPlatform: function(n) {
  2011. if (typeof n != 'undefined' && n !== null && n.length) {
  2012. platformName = n.toLowerCase();
  2013. } else if (getParameterByName('ionicplatform')) {
  2014. platformName = getParameterByName('ionicplatform');
  2015. } else if (self.ua.indexOf('Edge') > -1) {
  2016. platformName = EDGE;
  2017. } else if (self.ua.indexOf('Windows Phone') > -1) {
  2018. platformName = WINDOWS_PHONE;
  2019. } else if (self.ua.indexOf('Android') > 0) {
  2020. platformName = ANDROID;
  2021. } else if (/iPhone|iPad|iPod/.test(self.ua)) {
  2022. platformName = IOS;
  2023. } else {
  2024. platformName = self.navigator.platform && navigator.platform.toLowerCase().split(' ')[0] || '';
  2025. }
  2026. },
  2027. /**
  2028. * @ngdoc method
  2029. * @name ionic.Platform#version
  2030. * @returns {number} The version of the current device platform.
  2031. */
  2032. version: function() {
  2033. // singleton to get the platform version
  2034. if (platformVersion === null) self.setVersion(self.device().version);
  2035. return platformVersion;
  2036. },
  2037. /**
  2038. * @private
  2039. */
  2040. setVersion: function(v) {
  2041. if (typeof v != 'undefined' && v !== null) {
  2042. v = v.split('.');
  2043. v = parseFloat(v[0] + '.' + (v.length > 1 ? v[1] : 0));
  2044. if (!isNaN(v)) {
  2045. platformVersion = v;
  2046. return;
  2047. }
  2048. }
  2049. platformVersion = 0;
  2050. // fallback to user-agent checking
  2051. var pName = self.platform();
  2052. var versionMatch = {
  2053. 'android': /Android (\d+).(\d+)?/,
  2054. 'ios': /OS (\d+)_(\d+)?/,
  2055. 'windowsphone': /Windows Phone (\d+).(\d+)?/
  2056. };
  2057. if (versionMatch[pName]) {
  2058. v = self.ua.match(versionMatch[pName]);
  2059. if (v && v.length > 2) {
  2060. platformVersion = parseFloat(v[1] + '.' + v[2]);
  2061. }
  2062. }
  2063. },
  2064. /**
  2065. * @ngdoc method
  2066. * @name ionic.Platform#is
  2067. * @param {string} Platform name.
  2068. * @returns {boolean} Whether the platform name provided is detected.
  2069. */
  2070. is: function(type) {
  2071. type = type.toLowerCase();
  2072. // check if it has an array of platforms
  2073. if (self.platforms) {
  2074. for (var x = 0; x < self.platforms.length; x++) {
  2075. if (self.platforms[x] === type) return true;
  2076. }
  2077. }
  2078. // exact match
  2079. var pName = self.platform();
  2080. if (pName) {
  2081. return pName === type.toLowerCase();
  2082. }
  2083. // A quick hack for to check userAgent
  2084. return self.ua.toLowerCase().indexOf(type) >= 0;
  2085. },
  2086. /**
  2087. * @ngdoc method
  2088. * @name ionic.Platform#exitApp
  2089. * @description Exit the app.
  2090. */
  2091. exitApp: function() {
  2092. self.ready(function() {
  2093. navigator.app && navigator.app.exitApp && navigator.app.exitApp();
  2094. });
  2095. },
  2096. /**
  2097. * @ngdoc method
  2098. * @name ionic.Platform#showStatusBar
  2099. * @description Shows or hides the device status bar (in Cordova). Requires `ionic plugin add cordova-plugin-statusbar`
  2100. * @param {boolean} shouldShow Whether or not to show the status bar.
  2101. */
  2102. showStatusBar: function(val) {
  2103. // Only useful when run within cordova
  2104. self._showStatusBar = val;
  2105. self.ready(function() {
  2106. // run this only when or if the platform (cordova) is ready
  2107. requestAnimationFrame(function() {
  2108. if (self._showStatusBar) {
  2109. // they do not want it to be full screen
  2110. window.StatusBar && window.StatusBar.show();
  2111. document.body.classList.remove('status-bar-hide');
  2112. } else {
  2113. // it should be full screen
  2114. window.StatusBar && window.StatusBar.hide();
  2115. document.body.classList.add('status-bar-hide');
  2116. }
  2117. });
  2118. });
  2119. },
  2120. /**
  2121. * @ngdoc method
  2122. * @name ionic.Platform#fullScreen
  2123. * @description
  2124. * Sets whether the app is fullscreen or not (in Cordova).
  2125. * @param {boolean=} showFullScreen Whether or not to set the app to fullscreen. Defaults to true. Requires `ionic plugin add cordova-plugin-statusbar`
  2126. * @param {boolean=} showStatusBar Whether or not to show the device's status bar. Defaults to false.
  2127. */
  2128. fullScreen: function(showFullScreen, showStatusBar) {
  2129. // showFullScreen: default is true if no param provided
  2130. self.isFullScreen = (showFullScreen !== false);
  2131. // add/remove the fullscreen classname to the body
  2132. ionic.DomUtil.ready(function() {
  2133. // run this only when or if the DOM is ready
  2134. requestAnimationFrame(function() {
  2135. if (self.isFullScreen) {
  2136. document.body.classList.add('fullscreen');
  2137. } else {
  2138. document.body.classList.remove('fullscreen');
  2139. }
  2140. });
  2141. // showStatusBar: default is false if no param provided
  2142. self.showStatusBar((showStatusBar === true));
  2143. });
  2144. }
  2145. };
  2146. var platformName = null, // just the name, like iOS or Android
  2147. platformVersion = null, // a float of the major and minor, like 7.1
  2148. readyCallbacks = [],
  2149. windowLoadListenderAttached,
  2150. platformReadyTimer = 2000; // How long to wait for platform ready before emitting a warning
  2151. verifyPlatformReady();
  2152. // Warn the user if deviceready did not fire in a reasonable amount of time, and how to fix it.
  2153. function verifyPlatformReady() {
  2154. setTimeout(function() {
  2155. if(!self.isReady && self.isWebView()) {
  2156. void 0;
  2157. }
  2158. }, platformReadyTimer);
  2159. }
  2160. // setup listeners to know when the device is ready to go
  2161. function onWindowLoad() {
  2162. if (self.isWebView()) {
  2163. // the window and scripts are fully loaded, and a cordova/phonegap
  2164. // object exists then let's listen for the deviceready
  2165. document.addEventListener("deviceready", onPlatformReady, false);
  2166. } else {
  2167. // the window and scripts are fully loaded, but the window object doesn't have the
  2168. // cordova/phonegap object, so its just a browser, not a webview wrapped w/ cordova
  2169. onPlatformReady();
  2170. }
  2171. if (windowLoadListenderAttached) {
  2172. window.removeEventListener("load", onWindowLoad, false);
  2173. }
  2174. }
  2175. if (document.readyState === 'complete') {
  2176. onWindowLoad();
  2177. } else {
  2178. windowLoadListenderAttached = true;
  2179. window.addEventListener("load", onWindowLoad, false);
  2180. }
  2181. function onPlatformReady() {
  2182. // the device is all set to go, init our own stuff then fire off our event
  2183. self.isReady = true;
  2184. self.detect();
  2185. for (var x = 0; x < readyCallbacks.length; x++) {
  2186. // fire off all the callbacks that were added before the platform was ready
  2187. readyCallbacks[x]();
  2188. }
  2189. readyCallbacks = [];
  2190. ionic.trigger('platformready', { target: document });
  2191. requestAnimationFrame(function() {
  2192. document.body.classList.add('platform-ready');
  2193. });
  2194. }
  2195. })(window, document, ionic);
  2196. (function(document, ionic) {
  2197. 'use strict';
  2198. // Ionic CSS polyfills
  2199. ionic.CSS = {};
  2200. ionic.CSS.TRANSITION = [];
  2201. ionic.CSS.TRANSFORM = [];
  2202. ionic.EVENTS = {};
  2203. (function() {
  2204. // transform
  2205. var i, keys = ['webkitTransform', 'transform', '-webkit-transform', 'webkit-transform',
  2206. '-moz-transform', 'moz-transform', 'MozTransform', 'mozTransform', 'msTransform'];
  2207. for (i = 0; i < keys.length; i++) {
  2208. if (document.documentElement.style[keys[i]] !== undefined) {
  2209. ionic.CSS.TRANSFORM = keys[i];
  2210. break;
  2211. }
  2212. }
  2213. // transition
  2214. keys = ['webkitTransition', 'mozTransition', 'msTransition', 'transition'];
  2215. for (i = 0; i < keys.length; i++) {
  2216. if (document.documentElement.style[keys[i]] !== undefined) {
  2217. ionic.CSS.TRANSITION = keys[i];
  2218. break;
  2219. }
  2220. }
  2221. // Fallback in case the keys don't exist at all
  2222. ionic.CSS.TRANSITION = ionic.CSS.TRANSITION || 'transition';
  2223. // The only prefix we care about is webkit for transitions.
  2224. var isWebkit = ionic.CSS.TRANSITION.indexOf('webkit') > -1;
  2225. // transition duration
  2226. ionic.CSS.TRANSITION_DURATION = (isWebkit ? '-webkit-' : '') + 'transition-duration';
  2227. // To be sure transitionend works everywhere, include *both* the webkit and non-webkit events
  2228. ionic.CSS.TRANSITIONEND = (isWebkit ? 'webkitTransitionEnd ' : '') + 'transitionend';
  2229. })();
  2230. (function() {
  2231. var touchStartEvent = 'touchstart';
  2232. var touchMoveEvent = 'touchmove';
  2233. var touchEndEvent = 'touchend';
  2234. var touchCancelEvent = 'touchcancel';
  2235. if (window.navigator.pointerEnabled) {
  2236. touchStartEvent = 'pointerdown';
  2237. touchMoveEvent = 'pointermove';
  2238. touchEndEvent = 'pointerup';
  2239. touchCancelEvent = 'pointercancel';
  2240. } else if (window.navigator.msPointerEnabled) {
  2241. touchStartEvent = 'MSPointerDown';
  2242. touchMoveEvent = 'MSPointerMove';
  2243. touchEndEvent = 'MSPointerUp';
  2244. touchCancelEvent = 'MSPointerCancel';
  2245. }
  2246. ionic.EVENTS.touchstart = touchStartEvent;
  2247. ionic.EVENTS.touchmove = touchMoveEvent;
  2248. ionic.EVENTS.touchend = touchEndEvent;
  2249. ionic.EVENTS.touchcancel = touchCancelEvent;
  2250. })();
  2251. // classList polyfill for them older Androids
  2252. // https://gist.github.com/devongovett/1381839
  2253. if (!("classList" in document.documentElement) && Object.defineProperty && typeof HTMLElement !== 'undefined') {
  2254. Object.defineProperty(HTMLElement.prototype, 'classList', {
  2255. get: function() {
  2256. var self = this;
  2257. function update(fn) {
  2258. return function() {
  2259. var x, classes = self.className.split(/\s+/);
  2260. for (x = 0; x < arguments.length; x++) {
  2261. fn(classes, classes.indexOf(arguments[x]), arguments[x]);
  2262. }
  2263. self.className = classes.join(" ");
  2264. };
  2265. }
  2266. return {
  2267. add: update(function(classes, index, value) {
  2268. ~index || classes.push(value);
  2269. }),
  2270. remove: update(function(classes, index) {
  2271. ~index && classes.splice(index, 1);
  2272. }),
  2273. toggle: update(function(classes, index, value) {
  2274. ~index ? classes.splice(index, 1) : classes.push(value);
  2275. }),
  2276. contains: function(value) {
  2277. return !!~self.className.split(/\s+/).indexOf(value);
  2278. },
  2279. item: function(i) {
  2280. return self.className.split(/\s+/)[i] || null;
  2281. }
  2282. };
  2283. }
  2284. });
  2285. }
  2286. })(document, ionic);
  2287. /**
  2288. * @ngdoc page
  2289. * @name tap
  2290. * @module ionic
  2291. * @description
  2292. * On touch devices such as a phone or tablet, some browsers implement a 300ms delay between
  2293. * the time the user stops touching the display and the moment the browser executes the
  2294. * click. This delay was initially introduced so the browser can know whether the user wants to
  2295. * double-tap to zoom in on the webpage. Basically, the browser waits roughly 300ms to see if
  2296. * the user is double-tapping, or just tapping on the display once.
  2297. *
  2298. * Out of the box, Ionic automatically removes the 300ms delay in order to make Ionic apps
  2299. * feel more "native" like. Resultingly, other solutions such as
  2300. * [fastclick](https://github.com/ftlabs/fastclick) and Angular's
  2301. * [ngTouch](https://docs.angularjs.org/api/ngTouch) should not be included, to avoid conflicts.
  2302. *
  2303. * Some browsers already remove the delay with certain settings, such as the CSS property
  2304. * `touch-events: none` or with specific meta tag viewport values. However, each of these
  2305. * browsers still handle clicks differently, such as when to fire off or cancel the event
  2306. * (like scrolling when the target is a button, or holding a button down).
  2307. * For browsers that already remove the 300ms delay, consider Ionic's tap system as a way to
  2308. * normalize how clicks are handled across the various devices so there's an expected response
  2309. * no matter what the device, platform or version. Additionally, Ionic will prevent
  2310. * ghostclicks which even browsers that remove the delay still experience.
  2311. *
  2312. * In some cases, third-party libraries may also be working with touch events which can interfere
  2313. * with the tap system. For example, mapping libraries like Google or Leaflet Maps often implement
  2314. * a touch detection system which conflicts with Ionic's tap system.
  2315. *
  2316. * ### Disabling the tap system
  2317. *
  2318. * To disable the tap for an element and all of its children elements,
  2319. * add the attribute `data-tap-disabled="true"`.
  2320. *
  2321. * ```html
  2322. * <div data-tap-disabled="true">
  2323. * <div id="google-map"></div>
  2324. * </div>
  2325. * ```
  2326. *
  2327. * ### Additional Notes:
  2328. *
  2329. * - Ionic tap works with Ionic's JavaScript scrolling
  2330. * - Elements can come and go from the DOM and Ionic tap doesn't keep adding and removing
  2331. * listeners
  2332. * - No "tap delay" after the first "tap" (you can tap as fast as you want, they all click)
  2333. * - Minimal events listeners, only being added to document
  2334. * - Correct focus in/out on each input type (select, textearea, range) on each platform/device
  2335. * - Shows and hides virtual keyboard correctly for each platform/device
  2336. * - Works with labels surrounding inputs
  2337. * - Does not fire off a click if the user moves the pointer too far
  2338. * - Adds and removes an 'activated' css class
  2339. * - Multiple [unit tests](https://github.com/driftyco/ionic/blob/1.x/test/unit/utils/tap.unit.js) for each scenario
  2340. *
  2341. */
  2342. /*
  2343. IONIC TAP
  2344. ---------------
  2345. - Both touch and mouse events are added to the document.body on DOM ready
  2346. - If a touch event happens, it does not use mouse event listeners
  2347. - On touchend, if the distance between start and end was small, trigger a click
  2348. - In the triggered click event, add a 'isIonicTap' property
  2349. - The triggered click receives the same x,y coordinates as as the end event
  2350. - On document.body click listener (with useCapture=true), only allow clicks with 'isIonicTap'
  2351. - Triggering clicks with mouse events work the same as touch, except with mousedown/mouseup
  2352. - Tapping inputs is disabled during scrolling
  2353. */
  2354. var tapDoc; // the element which the listeners are on (document.body)
  2355. var tapActiveEle; // the element which is active (probably has focus)
  2356. var tapEnabledTouchEvents;
  2357. var tapMouseResetTimer;
  2358. var tapPointerMoved;
  2359. var tapPointerStart;
  2360. var tapTouchFocusedInput;
  2361. var tapLastTouchTarget;
  2362. var tapTouchMoveListener = 'touchmove';
  2363. // how much the coordinates can be off between start/end, but still a click
  2364. var TAP_RELEASE_TOLERANCE = 12; // default tolerance
  2365. var TAP_RELEASE_BUTTON_TOLERANCE = 50; // button elements should have a larger tolerance
  2366. var tapEventListeners = {
  2367. 'click': tapClickGateKeeper,
  2368. 'mousedown': tapMouseDown,
  2369. 'mouseup': tapMouseUp,
  2370. 'mousemove': tapMouseMove,
  2371. 'touchstart': tapTouchStart,
  2372. 'touchend': tapTouchEnd,
  2373. 'touchcancel': tapTouchCancel,
  2374. 'touchmove': tapTouchMove,
  2375. 'pointerdown': tapTouchStart,
  2376. 'pointerup': tapTouchEnd,
  2377. 'pointercancel': tapTouchCancel,
  2378. 'pointermove': tapTouchMove,
  2379. 'MSPointerDown': tapTouchStart,
  2380. 'MSPointerUp': tapTouchEnd,
  2381. 'MSPointerCancel': tapTouchCancel,
  2382. 'MSPointerMove': tapTouchMove,
  2383. 'focusin': tapFocusIn,
  2384. 'focusout': tapFocusOut
  2385. };
  2386. ionic.tap = {
  2387. register: function(ele) {
  2388. tapDoc = ele;
  2389. tapEventListener('click', true, true);
  2390. tapEventListener('mouseup');
  2391. tapEventListener('mousedown');
  2392. if (window.navigator.pointerEnabled) {
  2393. tapEventListener('pointerdown');
  2394. tapEventListener('pointerup');
  2395. tapEventListener('pointercancel');
  2396. tapTouchMoveListener = 'pointermove';
  2397. } else if (window.navigator.msPointerEnabled) {
  2398. tapEventListener('MSPointerDown');
  2399. tapEventListener('MSPointerUp');
  2400. tapEventListener('MSPointerCancel');
  2401. tapTouchMoveListener = 'MSPointerMove';
  2402. } else {
  2403. tapEventListener('touchstart');
  2404. tapEventListener('touchend');
  2405. tapEventListener('touchcancel');
  2406. }
  2407. tapEventListener('focusin');
  2408. tapEventListener('focusout');
  2409. return function() {
  2410. for (var type in tapEventListeners) {
  2411. tapEventListener(type, false);
  2412. }
  2413. tapDoc = null;
  2414. tapActiveEle = null;
  2415. tapEnabledTouchEvents = false;
  2416. tapPointerMoved = false;
  2417. tapPointerStart = null;
  2418. };
  2419. },
  2420. ignoreScrollStart: function(e) {
  2421. return (e.defaultPrevented) || // defaultPrevented has been assigned by another component handling the event
  2422. (/^(file|range)$/i).test(e.target.type) ||
  2423. (e.target.dataset ? e.target.dataset.preventScroll : e.target.getAttribute('data-prevent-scroll')) == 'true' || // manually set within an elements attributes
  2424. (!!(/^(object|embed)$/i).test(e.target.tagName)) || // flash/movie/object touches should not try to scroll
  2425. ionic.tap.isElementTapDisabled(e.target); // check if this element, or an ancestor, has `data-tap-disabled` attribute
  2426. },
  2427. isTextInput: function(ele) {
  2428. return !!ele &&
  2429. (ele.tagName == 'TEXTAREA' ||
  2430. ele.contentEditable === 'true' ||
  2431. (ele.tagName == 'INPUT' && !(/^(radio|checkbox|range|file|submit|reset|color|image|button)$/i).test(ele.type)));
  2432. },
  2433. isDateInput: function(ele) {
  2434. return !!ele &&
  2435. (ele.tagName == 'INPUT' && (/^(date|time|datetime-local|month|week)$/i).test(ele.type));
  2436. },
  2437. isVideo: function(ele) {
  2438. return !!ele &&
  2439. (ele.tagName == 'VIDEO');
  2440. },
  2441. isKeyboardElement: function(ele) {
  2442. if ( !ionic.Platform.isIOS() || ionic.Platform.isIPad() ) {
  2443. return ionic.tap.isTextInput(ele) && !ionic.tap.isDateInput(ele);
  2444. } else {
  2445. return ionic.tap.isTextInput(ele) || ( !!ele && ele.tagName == "SELECT");
  2446. }
  2447. },
  2448. isLabelWithTextInput: function(ele) {
  2449. var container = tapContainingElement(ele, false);
  2450. return !!container &&
  2451. ionic.tap.isTextInput(tapTargetElement(container));
  2452. },
  2453. containsOrIsTextInput: function(ele) {
  2454. return ionic.tap.isTextInput(ele) || ionic.tap.isLabelWithTextInput(ele);
  2455. },
  2456. cloneFocusedInput: function(container) {
  2457. if (ionic.tap.hasCheckedClone) return;
  2458. ionic.tap.hasCheckedClone = true;
  2459. ionic.requestAnimationFrame(function() {
  2460. var focusInput = container.querySelector(':focus');
  2461. if (ionic.tap.isTextInput(focusInput) && !ionic.tap.isDateInput(focusInput)) {
  2462. var clonedInput = focusInput.cloneNode(true);
  2463. clonedInput.value = focusInput.value;
  2464. clonedInput.classList.add('cloned-text-input');
  2465. clonedInput.readOnly = true;
  2466. if (focusInput.isContentEditable) {
  2467. clonedInput.contentEditable = focusInput.contentEditable;
  2468. clonedInput.innerHTML = focusInput.innerHTML;
  2469. }
  2470. focusInput.parentElement.insertBefore(clonedInput, focusInput);
  2471. focusInput.classList.add('previous-input-focus');
  2472. clonedInput.scrollTop = focusInput.scrollTop;
  2473. }
  2474. });
  2475. },
  2476. hasCheckedClone: false,
  2477. removeClonedInputs: function(container) {
  2478. ionic.tap.hasCheckedClone = false;
  2479. ionic.requestAnimationFrame(function() {
  2480. var clonedInputs = container.querySelectorAll('.cloned-text-input');
  2481. var previousInputFocus = container.querySelectorAll('.previous-input-focus');
  2482. var x;
  2483. for (x = 0; x < clonedInputs.length; x++) {
  2484. clonedInputs[x].parentElement.removeChild(clonedInputs[x]);
  2485. }
  2486. for (x = 0; x < previousInputFocus.length; x++) {
  2487. previousInputFocus[x].classList.remove('previous-input-focus');
  2488. previousInputFocus[x].style.top = '';
  2489. if ( ionic.keyboard.isOpen && !ionic.keyboard.isClosing ) previousInputFocus[x].focus();
  2490. }
  2491. });
  2492. },
  2493. requiresNativeClick: function(ele) {
  2494. if (ionic.Platform.isWindowsPhone() && (ele.tagName == 'A' || ele.tagName == 'BUTTON' || ele.hasAttribute('ng-click') || (ele.tagName == 'INPUT' && (ele.type == 'button' || ele.type == 'submit')))) {
  2495. return true; //Windows Phone edge case, prevent ng-click (and similar) events from firing twice on this platform
  2496. }
  2497. if (!ele || ele.disabled || (/^(file|range)$/i).test(ele.type) || (/^(object|video)$/i).test(ele.tagName) || ionic.tap.isLabelContainingFileInput(ele)) {
  2498. return true;
  2499. }
  2500. return ionic.tap.isElementTapDisabled(ele);
  2501. },
  2502. isLabelContainingFileInput: function(ele) {
  2503. var lbl = tapContainingElement(ele);
  2504. if (lbl.tagName !== 'LABEL') return false;
  2505. var fileInput = lbl.querySelector('input[type=file]');
  2506. if (fileInput && fileInput.disabled === false) return true;
  2507. return false;
  2508. },
  2509. isElementTapDisabled: function(ele) {
  2510. if (ele && ele.nodeType === 1) {
  2511. var element = ele;
  2512. while (element) {
  2513. if (element.getAttribute && element.getAttribute('data-tap-disabled') == 'true') {
  2514. return true;
  2515. }
  2516. element = element.parentElement;
  2517. }
  2518. }
  2519. return false;
  2520. },
  2521. setTolerance: function(releaseTolerance, releaseButtonTolerance) {
  2522. TAP_RELEASE_TOLERANCE = releaseTolerance;
  2523. TAP_RELEASE_BUTTON_TOLERANCE = releaseButtonTolerance;
  2524. },
  2525. cancelClick: function() {
  2526. // used to cancel any simulated clicks which may happen on a touchend/mouseup
  2527. // gestures uses this method within its tap and hold events
  2528. tapPointerMoved = true;
  2529. },
  2530. pointerCoord: function(event) {
  2531. // This method can get coordinates for both a mouse click
  2532. // or a touch depending on the given event
  2533. var c = { x: 0, y: 0 };
  2534. if (event) {
  2535. var touches = event.touches && event.touches.length ? event.touches : [event];
  2536. var e = (event.changedTouches && event.changedTouches[0]) || touches[0];
  2537. if (e) {
  2538. c.x = e.clientX || e.pageX || 0;
  2539. c.y = e.clientY || e.pageY || 0;
  2540. }
  2541. }
  2542. return c;
  2543. }
  2544. };
  2545. function tapEventListener(type, enable, useCapture) {
  2546. if (enable !== false) {
  2547. tapDoc.addEventListener(type, tapEventListeners[type], useCapture);
  2548. } else {
  2549. tapDoc.removeEventListener(type, tapEventListeners[type]);
  2550. }
  2551. }
  2552. function tapClick(e) {
  2553. // simulate a normal click by running the element's click method then focus on it
  2554. var container = tapContainingElement(e.target);
  2555. var ele = tapTargetElement(container);
  2556. if (ionic.tap.requiresNativeClick(ele) || tapPointerMoved) return false;
  2557. var c = ionic.tap.pointerCoord(e);
  2558. //console.log('tapClick', e.type, ele.tagName, '('+c.x+','+c.y+')');
  2559. triggerMouseEvent('click', ele, c.x, c.y);
  2560. // if it's an input, focus in on the target, otherwise blur
  2561. tapHandleFocus(ele);
  2562. }
  2563. function triggerMouseEvent(type, ele, x, y) {
  2564. // using initMouseEvent instead of MouseEvent for our Android friends
  2565. var clickEvent = document.createEvent("MouseEvents");
  2566. clickEvent.initMouseEvent(type, true, true, window, 1, 0, 0, x, y, false, false, false, false, 0, null);
  2567. clickEvent.isIonicTap = true;
  2568. ele.dispatchEvent(clickEvent);
  2569. }
  2570. function tapClickGateKeeper(e) {
  2571. //console.log('click ' + Date.now() + ' isIonicTap: ' + (e.isIonicTap ? true : false));
  2572. if (e.target.type == 'submit' && e.detail === 0) {
  2573. // do not prevent click if it came from an "Enter" or "Go" keypress submit
  2574. return null;
  2575. }
  2576. // do not allow through any click events that were not created by ionic.tap
  2577. if ((ionic.scroll.isScrolling && ionic.tap.containsOrIsTextInput(e.target)) ||
  2578. (!e.isIonicTap && !ionic.tap.requiresNativeClick(e.target))) {
  2579. //console.log('clickPrevent', e.target.tagName);
  2580. e.stopPropagation();
  2581. if (!ionic.tap.isLabelWithTextInput(e.target)) {
  2582. // labels clicks from native should not preventDefault othersize keyboard will not show on input focus
  2583. e.preventDefault();
  2584. }
  2585. return false;
  2586. }
  2587. }
  2588. // MOUSE
  2589. function tapMouseDown(e) {
  2590. //console.log('mousedown ' + Date.now());
  2591. if (e.isIonicTap || tapIgnoreEvent(e)) return null;
  2592. if (tapEnabledTouchEvents) {
  2593. //console.log('mousedown', 'stop event');
  2594. e.stopPropagation();
  2595. if (!ionic.Platform.isEdge() && (!ionic.tap.isTextInput(e.target) || tapLastTouchTarget !== e.target) &&
  2596. !isSelectOrOption(e.target.tagName) && !e.target.isContentEditable && !ionic.tap.isVideo(e.target)) {
  2597. // If you preventDefault on a text input then you cannot move its text caret/cursor.
  2598. // Allow through only the text input default. However, without preventDefault on an
  2599. // input the 300ms delay can change focus on inputs after the keyboard shows up.
  2600. // The focusin event handles the chance of focus changing after the keyboard shows.
  2601. // Windows Phone - if you preventDefault on a video element then you cannot operate
  2602. // its native controls.
  2603. e.preventDefault();
  2604. }
  2605. return false;
  2606. }
  2607. tapPointerMoved = false;
  2608. tapPointerStart = ionic.tap.pointerCoord(e);
  2609. tapEventListener('mousemove');
  2610. ionic.activator.start(e);
  2611. }
  2612. function tapMouseUp(e) {
  2613. //console.log("mouseup " + Date.now());
  2614. if (tapEnabledTouchEvents) {
  2615. e.stopPropagation();
  2616. e.preventDefault();
  2617. return false;
  2618. }
  2619. if (tapIgnoreEvent(e) || isSelectOrOption(e.target.tagName)) return false;
  2620. if (!tapHasPointerMoved(e)) {
  2621. tapClick(e);
  2622. }
  2623. tapEventListener('mousemove', false);
  2624. ionic.activator.end();
  2625. tapPointerMoved = false;
  2626. }
  2627. function tapMouseMove(e) {
  2628. if (tapHasPointerMoved(e)) {
  2629. tapEventListener('mousemove', false);
  2630. ionic.activator.end();
  2631. tapPointerMoved = true;
  2632. return false;
  2633. }
  2634. }
  2635. // TOUCH
  2636. function tapTouchStart(e) {
  2637. //console.log("touchstart " + Date.now());
  2638. if (tapIgnoreEvent(e)) return;
  2639. tapPointerMoved = false;
  2640. tapEnableTouchEvents();
  2641. tapPointerStart = ionic.tap.pointerCoord(e);
  2642. tapEventListener(tapTouchMoveListener);
  2643. ionic.activator.start(e);
  2644. if (ionic.Platform.isIOS() && ionic.tap.isLabelWithTextInput(e.target)) {
  2645. // if the tapped element is a label, which has a child input
  2646. // then preventDefault so iOS doesn't ugly auto scroll to the input
  2647. // but do not prevent default on Android or else you cannot move the text caret
  2648. // and do not prevent default on Android or else no virtual keyboard shows up
  2649. var textInput = tapTargetElement(tapContainingElement(e.target));
  2650. if (textInput !== tapActiveEle) {
  2651. // don't preventDefault on an already focused input or else iOS's text caret isn't usable
  2652. //console.log('Would prevent default here');
  2653. e.preventDefault();
  2654. }
  2655. }
  2656. }
  2657. function tapTouchEnd(e) {
  2658. //console.log('touchend ' + Date.now());
  2659. if (tapIgnoreEvent(e)) return;
  2660. tapEnableTouchEvents();
  2661. if (!tapHasPointerMoved(e)) {
  2662. tapClick(e);
  2663. if (isSelectOrOption(e.target.tagName)) {
  2664. e.preventDefault();
  2665. }
  2666. }
  2667. tapLastTouchTarget = e.target;
  2668. tapTouchCancel();
  2669. }
  2670. function tapTouchMove(e) {
  2671. if (tapHasPointerMoved(e)) {
  2672. tapPointerMoved = true;
  2673. tapEventListener(tapTouchMoveListener, false);
  2674. ionic.activator.end();
  2675. return false;
  2676. }
  2677. }
  2678. function tapTouchCancel() {
  2679. tapEventListener(tapTouchMoveListener, false);
  2680. ionic.activator.end();
  2681. tapPointerMoved = false;
  2682. }
  2683. function tapEnableTouchEvents() {
  2684. tapEnabledTouchEvents = true;
  2685. clearTimeout(tapMouseResetTimer);
  2686. tapMouseResetTimer = setTimeout(function() {
  2687. tapEnabledTouchEvents = false;
  2688. }, 600);
  2689. }
  2690. function tapIgnoreEvent(e) {
  2691. if (e.isTapHandled) return true;
  2692. e.isTapHandled = true;
  2693. if(ionic.tap.isElementTapDisabled(e.target)) {
  2694. return true;
  2695. }
  2696. if(e.target.tagName == 'SELECT') {
  2697. return true;
  2698. }
  2699. if (ionic.scroll.isScrolling && ionic.tap.containsOrIsTextInput(e.target)) {
  2700. e.preventDefault();
  2701. return true;
  2702. }
  2703. }
  2704. function tapHandleFocus(ele) {
  2705. tapTouchFocusedInput = null;
  2706. var triggerFocusIn = false;
  2707. if (ele.tagName == 'SELECT') {
  2708. // trick to force Android options to show up
  2709. triggerMouseEvent('mousedown', ele, 0, 0);
  2710. ele.focus && ele.focus();
  2711. triggerFocusIn = true;
  2712. } else if (tapActiveElement() === ele) {
  2713. // already is the active element and has focus
  2714. triggerFocusIn = true;
  2715. } else if ((/^(input|textarea|ion-label)$/i).test(ele.tagName) || ele.isContentEditable) {
  2716. triggerFocusIn = true;
  2717. ele.focus && ele.focus();
  2718. ele.value = ele.value;
  2719. if (tapEnabledTouchEvents) {
  2720. tapTouchFocusedInput = ele;
  2721. }
  2722. } else {
  2723. tapFocusOutActive();
  2724. }
  2725. if (triggerFocusIn) {
  2726. tapActiveElement(ele);
  2727. ionic.trigger('ionic.focusin', {
  2728. target: ele
  2729. }, true);
  2730. }
  2731. }
  2732. function tapFocusOutActive() {
  2733. var ele = tapActiveElement();
  2734. if (ele && ((/^(input|textarea|select)$/i).test(ele.tagName) || ele.isContentEditable)) {
  2735. //console.log('tapFocusOutActive', ele.tagName);
  2736. ele.blur();
  2737. }
  2738. tapActiveElement(null);
  2739. }
  2740. function tapFocusIn(e) {
  2741. //console.log('focusin ' + Date.now());
  2742. // Because a text input doesn't preventDefault (so the caret still works) there's a chance
  2743. // that its mousedown event 300ms later will change the focus to another element after
  2744. // the keyboard shows up.
  2745. if (tapEnabledTouchEvents &&
  2746. ionic.tap.isTextInput(tapActiveElement()) &&
  2747. ionic.tap.isTextInput(tapTouchFocusedInput) &&
  2748. tapTouchFocusedInput !== e.target) {
  2749. // 1) The pointer is from touch events
  2750. // 2) There is an active element which is a text input
  2751. // 3) A text input was just set to be focused on by a touch event
  2752. // 4) A new focus has been set, however the target isn't the one the touch event wanted
  2753. //console.log('focusin', 'tapTouchFocusedInput');
  2754. tapTouchFocusedInput.focus();
  2755. tapTouchFocusedInput = null;
  2756. }
  2757. ionic.scroll.isScrolling = false;
  2758. }
  2759. function tapFocusOut() {
  2760. //console.log("focusout");
  2761. tapActiveElement(null);
  2762. }
  2763. function tapActiveElement(ele) {
  2764. if (arguments.length) {
  2765. tapActiveEle = ele;
  2766. }
  2767. return tapActiveEle || document.activeElement;
  2768. }
  2769. function tapHasPointerMoved(endEvent) {
  2770. if (!endEvent || endEvent.target.nodeType !== 1 || !tapPointerStart || (tapPointerStart.x === 0 && tapPointerStart.y === 0)) {
  2771. return false;
  2772. }
  2773. var endCoordinates = ionic.tap.pointerCoord(endEvent);
  2774. var hasClassList = !!(endEvent.target.classList && endEvent.target.classList.contains &&
  2775. typeof endEvent.target.classList.contains === 'function');
  2776. var releaseTolerance = hasClassList && endEvent.target.classList.contains('button') ?
  2777. TAP_RELEASE_BUTTON_TOLERANCE :
  2778. TAP_RELEASE_TOLERANCE;
  2779. return Math.abs(tapPointerStart.x - endCoordinates.x) > releaseTolerance ||
  2780. Math.abs(tapPointerStart.y - endCoordinates.y) > releaseTolerance;
  2781. }
  2782. function tapContainingElement(ele, allowSelf) {
  2783. var climbEle = ele;
  2784. for (var x = 0; x < 6; x++) {
  2785. if (!climbEle) break;
  2786. if (climbEle.tagName === 'LABEL') return climbEle;
  2787. climbEle = climbEle.parentElement;
  2788. }
  2789. if (allowSelf !== false) return ele;
  2790. }
  2791. function tapTargetElement(ele) {
  2792. if (ele && ele.tagName === 'LABEL') {
  2793. if (ele.control) return ele.control;
  2794. // older devices do not support the "control" property
  2795. if (ele.querySelector) {
  2796. var control = ele.querySelector('input,textarea,select');
  2797. if (control) return control;
  2798. }
  2799. }
  2800. return ele;
  2801. }
  2802. function isSelectOrOption(tagName){
  2803. return (/^(select|option)$/i).test(tagName);
  2804. }
  2805. ionic.DomUtil.ready(function() {
  2806. var ng = typeof angular !== 'undefined' ? angular : null;
  2807. //do nothing for e2e tests
  2808. if (!ng || (ng && !ng.scenario)) {
  2809. ionic.tap.register(document);
  2810. }
  2811. });
  2812. (function(document, ionic) {
  2813. 'use strict';
  2814. var queueElements = {}; // elements that should get an active state in XX milliseconds
  2815. var activeElements = {}; // elements that are currently active
  2816. var keyId = 0; // a counter for unique keys for the above ojects
  2817. var ACTIVATED_CLASS = 'activated';
  2818. ionic.activator = {
  2819. start: function(e) {
  2820. var hitX = ionic.tap.pointerCoord(e).x;
  2821. if (hitX > 0 && hitX < 30) {
  2822. return;
  2823. }
  2824. // when an element is touched/clicked, it climbs up a few
  2825. // parents to see if it is an .item or .button element
  2826. ionic.requestAnimationFrame(function() {
  2827. if ((ionic.scroll && ionic.scroll.isScrolling) || ionic.tap.requiresNativeClick(e.target)) return;
  2828. var ele = e.target;
  2829. var eleToActivate;
  2830. for (var x = 0; x < 6; x++) {
  2831. if (!ele || ele.nodeType !== 1) break;
  2832. if (eleToActivate && ele.classList && ele.classList.contains('item')) {
  2833. eleToActivate = ele;
  2834. break;
  2835. }
  2836. if (ele.tagName == 'A' || ele.tagName == 'BUTTON' || ele.hasAttribute('ng-click')) {
  2837. eleToActivate = ele;
  2838. break;
  2839. }
  2840. if (ele.classList && ele.classList.contains('button')) {
  2841. eleToActivate = ele;
  2842. break;
  2843. }
  2844. // no sense climbing past these
  2845. if (ele.tagName == 'ION-CONTENT' || (ele.classList && ele.classList.contains('pane')) || ele.tagName == 'BODY') {
  2846. break;
  2847. }
  2848. ele = ele.parentElement;
  2849. }
  2850. if (eleToActivate) {
  2851. // queue that this element should be set to active
  2852. queueElements[keyId] = eleToActivate;
  2853. // on the next frame, set the queued elements to active
  2854. ionic.requestAnimationFrame(activateElements);
  2855. keyId = (keyId > 29 ? 0 : keyId + 1);
  2856. }
  2857. });
  2858. },
  2859. end: function() {
  2860. // clear out any active/queued elements after XX milliseconds
  2861. setTimeout(clear, 200);
  2862. }
  2863. };
  2864. function clear() {
  2865. // clear out any elements that are queued to be set to active
  2866. queueElements = {};
  2867. // in the next frame, remove the active class from all active elements
  2868. ionic.requestAnimationFrame(deactivateElements);
  2869. }
  2870. function activateElements() {
  2871. // activate all elements in the queue
  2872. for (var key in queueElements) {
  2873. if (queueElements[key]) {
  2874. queueElements[key].classList.add(ACTIVATED_CLASS);
  2875. activeElements[key] = queueElements[key];
  2876. }
  2877. }
  2878. queueElements = {};
  2879. }
  2880. function deactivateElements() {
  2881. if (ionic.transition && ionic.transition.isActive) {
  2882. setTimeout(deactivateElements, 400);
  2883. return;
  2884. }
  2885. for (var key in activeElements) {
  2886. if (activeElements[key]) {
  2887. activeElements[key].classList.remove(ACTIVATED_CLASS);
  2888. delete activeElements[key];
  2889. }
  2890. }
  2891. }
  2892. })(document, ionic);
  2893. (function(ionic) {
  2894. /* for nextUid function below */
  2895. var nextId = 0;
  2896. /**
  2897. * Various utilities used throughout Ionic
  2898. *
  2899. * Some of these are adopted from underscore.js and backbone.js, both also MIT licensed.
  2900. */
  2901. ionic.Utils = {
  2902. arrayMove: function(arr, oldIndex, newIndex) {
  2903. if (newIndex >= arr.length) {
  2904. var k = newIndex - arr.length;
  2905. while ((k--) + 1) {
  2906. arr.push(undefined);
  2907. }
  2908. }
  2909. arr.splice(newIndex, 0, arr.splice(oldIndex, 1)[0]);
  2910. return arr;
  2911. },
  2912. /**
  2913. * Return a function that will be called with the given context
  2914. */
  2915. proxy: function(func, context) {
  2916. var args = Array.prototype.slice.call(arguments, 2);
  2917. return function() {
  2918. return func.apply(context, args.concat(Array.prototype.slice.call(arguments)));
  2919. };
  2920. },
  2921. /**
  2922. * Only call a function once in the given interval.
  2923. *
  2924. * @param func {Function} the function to call
  2925. * @param wait {int} how long to wait before/after to allow function calls
  2926. * @param immediate {boolean} whether to call immediately or after the wait interval
  2927. */
  2928. debounce: function(func, wait, immediate) {
  2929. var timeout, args, context, timestamp, result;
  2930. return function() {
  2931. context = this;
  2932. args = arguments;
  2933. timestamp = new Date();
  2934. var later = function() {
  2935. var last = (new Date()) - timestamp;
  2936. if (last < wait) {
  2937. timeout = setTimeout(later, wait - last);
  2938. } else {
  2939. timeout = null;
  2940. if (!immediate) result = func.apply(context, args);
  2941. }
  2942. };
  2943. var callNow = immediate && !timeout;
  2944. if (!timeout) {
  2945. timeout = setTimeout(later, wait);
  2946. }
  2947. if (callNow) result = func.apply(context, args);
  2948. return result;
  2949. };
  2950. },
  2951. /**
  2952. * Throttle the given fun, only allowing it to be
  2953. * called at most every `wait` ms.
  2954. */
  2955. throttle: function(func, wait, options) {
  2956. var context, args, result;
  2957. var timeout = null;
  2958. var previous = 0;
  2959. options || (options = {});
  2960. var later = function() {
  2961. previous = options.leading === false ? 0 : Date.now();
  2962. timeout = null;
  2963. result = func.apply(context, args);
  2964. };
  2965. return function() {
  2966. var now = Date.now();
  2967. if (!previous && options.leading === false) previous = now;
  2968. var remaining = wait - (now - previous);
  2969. context = this;
  2970. args = arguments;
  2971. if (remaining <= 0) {
  2972. clearTimeout(timeout);
  2973. timeout = null;
  2974. previous = now;
  2975. result = func.apply(context, args);
  2976. } else if (!timeout && options.trailing !== false) {
  2977. timeout = setTimeout(later, remaining);
  2978. }
  2979. return result;
  2980. };
  2981. },
  2982. // Borrowed from Backbone.js's extend
  2983. // Helper function to correctly set up the prototype chain, for subclasses.
  2984. // Similar to `goog.inherits`, but uses a hash of prototype properties and
  2985. // class properties to be extended.
  2986. inherit: function(protoProps, staticProps) {
  2987. var parent = this;
  2988. var child;
  2989. // The constructor function for the new subclass is either defined by you
  2990. // (the "constructor" property in your `extend` definition), or defaulted
  2991. // by us to simply call the parent's constructor.
  2992. if (protoProps && protoProps.hasOwnProperty('constructor')) {
  2993. child = protoProps.constructor;
  2994. } else {
  2995. child = function() { return parent.apply(this, arguments); };
  2996. }
  2997. // Add static properties to the constructor function, if supplied.
  2998. ionic.extend(child, parent, staticProps);
  2999. // Set the prototype chain to inherit from `parent`, without calling
  3000. // `parent`'s constructor function.
  3001. var Surrogate = function() { this.constructor = child; };
  3002. Surrogate.prototype = parent.prototype;
  3003. child.prototype = new Surrogate();
  3004. // Add prototype properties (instance properties) to the subclass,
  3005. // if supplied.
  3006. if (protoProps) ionic.extend(child.prototype, protoProps);
  3007. // Set a convenience property in case the parent's prototype is needed
  3008. // later.
  3009. child.__super__ = parent.prototype;
  3010. return child;
  3011. },
  3012. // Extend adapted from Underscore.js
  3013. extend: function(obj) {
  3014. var args = Array.prototype.slice.call(arguments, 1);
  3015. for (var i = 0; i < args.length; i++) {
  3016. var source = args[i];
  3017. if (source) {
  3018. for (var prop in source) {
  3019. obj[prop] = source[prop];
  3020. }
  3021. }
  3022. }
  3023. return obj;
  3024. },
  3025. nextUid: function() {
  3026. return 'ion' + (nextId++);
  3027. },
  3028. disconnectScope: function disconnectScope(scope) {
  3029. if (!scope) return;
  3030. if (scope.$root === scope) {
  3031. return; // we can't disconnect the root node;
  3032. }
  3033. var parent = scope.$parent;
  3034. scope.$$disconnected = true;
  3035. scope.$broadcast('$ionic.disconnectScope', scope);
  3036. // See Scope.$destroy
  3037. if (parent.$$childHead === scope) {
  3038. parent.$$childHead = scope.$$nextSibling;
  3039. }
  3040. if (parent.$$childTail === scope) {
  3041. parent.$$childTail = scope.$$prevSibling;
  3042. }
  3043. if (scope.$$prevSibling) {
  3044. scope.$$prevSibling.$$nextSibling = scope.$$nextSibling;
  3045. }
  3046. if (scope.$$nextSibling) {
  3047. scope.$$nextSibling.$$prevSibling = scope.$$prevSibling;
  3048. }
  3049. scope.$$nextSibling = scope.$$prevSibling = null;
  3050. },
  3051. reconnectScope: function reconnectScope(scope) {
  3052. if (!scope) return;
  3053. if (scope.$root === scope) {
  3054. return; // we can't disconnect the root node;
  3055. }
  3056. if (!scope.$$disconnected) {
  3057. return;
  3058. }
  3059. var parent = scope.$parent;
  3060. scope.$$disconnected = false;
  3061. scope.$broadcast('$ionic.reconnectScope', scope);
  3062. // See Scope.$new for this logic...
  3063. scope.$$prevSibling = parent.$$childTail;
  3064. if (parent.$$childHead) {
  3065. parent.$$childTail.$$nextSibling = scope;
  3066. parent.$$childTail = scope;
  3067. } else {
  3068. parent.$$childHead = parent.$$childTail = scope;
  3069. }
  3070. },
  3071. isScopeDisconnected: function(scope) {
  3072. var climbScope = scope;
  3073. while (climbScope) {
  3074. if (climbScope.$$disconnected) return true;
  3075. climbScope = climbScope.$parent;
  3076. }
  3077. return false;
  3078. }
  3079. };
  3080. // Bind a few of the most useful functions to the ionic scope
  3081. ionic.inherit = ionic.Utils.inherit;
  3082. ionic.extend = ionic.Utils.extend;
  3083. ionic.throttle = ionic.Utils.throttle;
  3084. ionic.proxy = ionic.Utils.proxy;
  3085. ionic.debounce = ionic.Utils.debounce;
  3086. })(window.ionic);
  3087. /**
  3088. * @ngdoc page
  3089. * @name keyboard
  3090. * @module ionic
  3091. * @description
  3092. * On both Android and iOS, Ionic will attempt to prevent the keyboard from
  3093. * obscuring inputs and focusable elements when it appears by scrolling them
  3094. * into view. In order for this to work, any focusable elements must be within
  3095. * a [Scroll View](http://ionicframework.com/docs/api/directive/ionScroll/)
  3096. * or a directive such as [Content](http://ionicframework.com/docs/api/directive/ionContent/)
  3097. * that has a Scroll View.
  3098. *
  3099. * It will also attempt to prevent the native overflow scrolling on focus,
  3100. * which can cause layout issues such as pushing headers up and out of view.
  3101. *
  3102. * The keyboard fixes work best in conjunction with the
  3103. * [Ionic Keyboard Plugin](https://github.com/driftyco/ionic-plugins-keyboard),
  3104. * although it will perform reasonably well without. However, if you are using
  3105. * Cordova there is no reason not to use the plugin.
  3106. *
  3107. * ### Hide when keyboard shows
  3108. *
  3109. * To hide an element when the keyboard is open, add the class `hide-on-keyboard-open`.
  3110. *
  3111. * ```html
  3112. * <div class="hide-on-keyboard-open">
  3113. * <div id="google-map"></div>
  3114. * </div>
  3115. * ```
  3116. *
  3117. * Note: For performance reasons, elements will not be hidden for 400ms after the start of the `native.keyboardshow` event
  3118. * from the Ionic Keyboard plugin. If you would like them to disappear immediately, you could do something
  3119. * like:
  3120. *
  3121. * ```js
  3122. * window.addEventListener('native.keyboardshow', function(){
  3123. * document.body.classList.add('keyboard-open');
  3124. * });
  3125. * ```
  3126. * This adds the same `keyboard-open` class that is normally added by Ionic 400ms after the keyboard
  3127. * opens. However, bear in mind that adding this class to the body immediately may cause jank in any
  3128. * animations on Android that occur when the keyboard opens (for example, scrolling any obscured inputs into view).
  3129. *
  3130. * ----------
  3131. *
  3132. * ### Plugin Usage
  3133. * Information on using the plugin can be found at
  3134. * [https://github.com/driftyco/ionic-plugins-keyboard](https://github.com/driftyco/ionic-plugins-keyboard).
  3135. *
  3136. * ----------
  3137. *
  3138. * ### Android Notes
  3139. * - If your app is running in fullscreen, i.e. you have
  3140. * `<preference name="Fullscreen" value="true" />` in your `config.xml` file
  3141. * you will need to set `ionic.Platform.isFullScreen = true` manually.
  3142. *
  3143. * - You can configure the behavior of the web view when the keyboard shows by setting
  3144. * [android:windowSoftInputMode](http://developer.android.com/reference/android/R.attr.html#windowSoftInputMode)
  3145. * to either `adjustPan`, `adjustResize` or `adjustNothing` in your app's
  3146. * activity in `AndroidManifest.xml`. `adjustResize` is the recommended setting
  3147. * for Ionic, but if for some reason you do use `adjustPan` you will need to
  3148. * set `ionic.Platform.isFullScreen = true`.
  3149. *
  3150. * ```xml
  3151. * <activity android:windowSoftInputMode="adjustResize">
  3152. *
  3153. * ```
  3154. *
  3155. * ### iOS Notes
  3156. * - If the content of your app (including the header) is being pushed up and
  3157. * out of view on input focus, try setting `cordova.plugins.Keyboard.disableScroll(true)`.
  3158. * This does **not** disable scrolling in the Ionic scroll view, rather it
  3159. * disables the native overflow scrolling that happens automatically as a
  3160. * result of focusing on inputs below the keyboard.
  3161. *
  3162. */
  3163. /**
  3164. * The current viewport height.
  3165. */
  3166. var keyboardCurrentViewportHeight = 0;
  3167. /**
  3168. * The viewport height when in portrait orientation.
  3169. */
  3170. var keyboardPortraitViewportHeight = 0;
  3171. /**
  3172. * The viewport height when in landscape orientation.
  3173. */
  3174. var keyboardLandscapeViewportHeight = 0;
  3175. /**
  3176. * The currently focused input.
  3177. */
  3178. var keyboardActiveElement;
  3179. /**
  3180. * The previously focused input used to reset keyboard after focusing on a
  3181. * new non-keyboard element
  3182. */
  3183. var lastKeyboardActiveElement;
  3184. /**
  3185. * The scroll view containing the currently focused input.
  3186. */
  3187. var scrollView;
  3188. /**
  3189. * Timer for the setInterval that polls window.innerHeight to determine whether
  3190. * the layout has updated for the keyboard showing/hiding.
  3191. */
  3192. var waitForResizeTimer;
  3193. /**
  3194. * Sometimes when switching inputs or orientations, focusout will fire before
  3195. * focusin, so this timer is for the small setTimeout to determine if we should
  3196. * really focusout/hide the keyboard.
  3197. */
  3198. var keyboardFocusOutTimer;
  3199. /**
  3200. * on Android, orientationchange will fire before the keyboard plugin notifies
  3201. * the browser that the keyboard will show/is showing, so this flag indicates
  3202. * to nativeShow that there was an orientationChange and we should update
  3203. * the viewport height with an accurate keyboard height value
  3204. */
  3205. var wasOrientationChange = false;
  3206. /**
  3207. * CSS class added to the body indicating the keyboard is open.
  3208. */
  3209. var KEYBOARD_OPEN_CSS = 'keyboard-open';
  3210. /**
  3211. * CSS class that indicates a scroll container.
  3212. */
  3213. var SCROLL_CONTAINER_CSS = 'scroll-content';
  3214. /**
  3215. * Debounced keyboardFocusIn function
  3216. */
  3217. var debouncedKeyboardFocusIn = ionic.debounce(keyboardFocusIn, 200, true);
  3218. /**
  3219. * Debounced keyboardNativeShow function
  3220. */
  3221. var debouncedKeyboardNativeShow = ionic.debounce(keyboardNativeShow, 100, true);
  3222. /**
  3223. * Ionic keyboard namespace.
  3224. * @namespace keyboard
  3225. */
  3226. ionic.keyboard = {
  3227. /**
  3228. * Whether the keyboard is open or not.
  3229. */
  3230. isOpen: false,
  3231. /**
  3232. * Whether the keyboard is closing or not.
  3233. */
  3234. isClosing: false,
  3235. /**
  3236. * Whether the keyboard is opening or not.
  3237. */
  3238. isOpening: false,
  3239. /**
  3240. * The height of the keyboard in pixels, as reported by the keyboard plugin.
  3241. * If the plugin is not available, calculated as the difference in
  3242. * window.innerHeight after the keyboard has shown.
  3243. */
  3244. height: 0,
  3245. /**
  3246. * Whether the device is in landscape orientation or not.
  3247. */
  3248. isLandscape: false,
  3249. /**
  3250. * Whether the keyboard event listeners have been added or not
  3251. */
  3252. isInitialized: false,
  3253. /**
  3254. * Hide the keyboard, if it is open.
  3255. */
  3256. hide: function() {
  3257. if (keyboardHasPlugin()) {
  3258. cordova.plugins.Keyboard.close();
  3259. }
  3260. keyboardActiveElement && keyboardActiveElement.blur();
  3261. },
  3262. /**
  3263. * An alias for cordova.plugins.Keyboard.show(). If the keyboard plugin
  3264. * is installed, show the keyboard.
  3265. */
  3266. show: function() {
  3267. if (keyboardHasPlugin()) {
  3268. cordova.plugins.Keyboard.show();
  3269. }
  3270. },
  3271. /**
  3272. * Remove all keyboard related event listeners, effectively disabling Ionic's
  3273. * keyboard adjustments.
  3274. */
  3275. disable: function() {
  3276. if (keyboardHasPlugin()) {
  3277. window.removeEventListener('native.keyboardshow', debouncedKeyboardNativeShow );
  3278. window.removeEventListener('native.keyboardhide', keyboardFocusOut);
  3279. } else {
  3280. document.body.removeEventListener('focusout', keyboardFocusOut);
  3281. }
  3282. document.body.removeEventListener('ionic.focusin', debouncedKeyboardFocusIn);
  3283. document.body.removeEventListener('focusin', debouncedKeyboardFocusIn);
  3284. window.removeEventListener('orientationchange', keyboardOrientationChange);
  3285. if ( window.navigator.msPointerEnabled ) {
  3286. document.removeEventListener("MSPointerDown", keyboardInit);
  3287. } else {
  3288. document.removeEventListener('touchstart', keyboardInit);
  3289. }
  3290. ionic.keyboard.isInitialized = false;
  3291. },
  3292. /**
  3293. * Alias for keyboardInit, initialize all keyboard related event listeners.
  3294. */
  3295. enable: function() {
  3296. keyboardInit();
  3297. }
  3298. };
  3299. // Initialize the viewport height (after ionic.keyboard.height has been
  3300. // defined).
  3301. keyboardCurrentViewportHeight = getViewportHeight();
  3302. /* Event handlers */
  3303. /* ------------------------------------------------------------------------- */
  3304. /**
  3305. * Event handler for first touch event, initializes all event listeners
  3306. * for keyboard related events. Also aliased by ionic.keyboard.enable.
  3307. */
  3308. function keyboardInit() {
  3309. if (ionic.keyboard.isInitialized) return;
  3310. if (keyboardHasPlugin()) {
  3311. window.addEventListener('native.keyboardshow', debouncedKeyboardNativeShow);
  3312. window.addEventListener('native.keyboardhide', keyboardFocusOut);
  3313. } else {
  3314. document.body.addEventListener('focusout', keyboardFocusOut);
  3315. }
  3316. document.body.addEventListener('ionic.focusin', debouncedKeyboardFocusIn);
  3317. document.body.addEventListener('focusin', debouncedKeyboardFocusIn);
  3318. if (window.navigator.msPointerEnabled) {
  3319. document.removeEventListener("MSPointerDown", keyboardInit);
  3320. } else {
  3321. document.removeEventListener('touchstart', keyboardInit);
  3322. }
  3323. ionic.keyboard.isInitialized = true;
  3324. }
  3325. /**
  3326. * Event handler for 'native.keyboardshow' event, sets keyboard.height to the
  3327. * reported height and keyboard.isOpening to true. Then calls
  3328. * keyboardWaitForResize with keyboardShow or keyboardUpdateViewportHeight as
  3329. * the callback depending on whether the event was triggered by a focusin or
  3330. * an orientationchange.
  3331. */
  3332. function keyboardNativeShow(e) {
  3333. clearTimeout(keyboardFocusOutTimer);
  3334. //console.log("keyboardNativeShow fired at: " + Date.now());
  3335. //console.log("keyboardNativeshow window.innerHeight: " + window.innerHeight);
  3336. if (!ionic.keyboard.isOpen || ionic.keyboard.isClosing) {
  3337. ionic.keyboard.isOpening = true;
  3338. ionic.keyboard.isClosing = false;
  3339. }
  3340. ionic.keyboard.height = e.keyboardHeight;
  3341. //console.log('nativeshow keyboard height:' + e.keyboardHeight);
  3342. if (wasOrientationChange) {
  3343. keyboardWaitForResize(keyboardUpdateViewportHeight, true);
  3344. } else {
  3345. keyboardWaitForResize(keyboardShow, true);
  3346. }
  3347. }
  3348. /**
  3349. * Event handler for 'focusin' and 'ionic.focusin' events. Initializes
  3350. * keyboard state (keyboardActiveElement and keyboard.isOpening) for the
  3351. * appropriate adjustments once the window has resized. If not using the
  3352. * keyboard plugin, calls keyboardWaitForResize with keyboardShow as the
  3353. * callback or keyboardShow right away if the keyboard is already open. If
  3354. * using the keyboard plugin does nothing and lets keyboardNativeShow handle
  3355. * adjustments with a more accurate keyboard height.
  3356. */
  3357. function keyboardFocusIn(e) {
  3358. clearTimeout(keyboardFocusOutTimer);
  3359. //console.log("keyboardFocusIn from: " + e.type + " at: " + Date.now());
  3360. if (!e.target ||
  3361. e.target.readOnly ||
  3362. !ionic.tap.isKeyboardElement(e.target) ||
  3363. !(scrollView = ionic.DomUtil.getParentWithClass(e.target, SCROLL_CONTAINER_CSS))) {
  3364. if (keyboardActiveElement) {
  3365. lastKeyboardActiveElement = keyboardActiveElement;
  3366. }
  3367. keyboardActiveElement = null;
  3368. return;
  3369. }
  3370. keyboardActiveElement = e.target;
  3371. // if using JS scrolling, undo the effects of native overflow scroll so the
  3372. // scroll view is positioned correctly
  3373. if (!scrollView.classList.contains("overflow-scroll")) {
  3374. document.body.scrollTop = 0;
  3375. scrollView.scrollTop = 0;
  3376. ionic.requestAnimationFrame(function(){
  3377. document.body.scrollTop = 0;
  3378. scrollView.scrollTop = 0;
  3379. });
  3380. // any showing part of the document that isn't within the scroll the user
  3381. // could touchmove and cause some ugly changes to the app, so disable
  3382. // any touchmove events while the keyboard is open using e.preventDefault()
  3383. if (window.navigator.msPointerEnabled) {
  3384. document.addEventListener("MSPointerMove", keyboardPreventDefault, false);
  3385. } else {
  3386. document.addEventListener('touchmove', keyboardPreventDefault, false);
  3387. }
  3388. }
  3389. if (!ionic.keyboard.isOpen || ionic.keyboard.isClosing) {
  3390. ionic.keyboard.isOpening = true;
  3391. ionic.keyboard.isClosing = false;
  3392. }
  3393. // attempt to prevent browser from natively scrolling input into view while
  3394. // we are trying to do the same (while we are scrolling) if the user taps the
  3395. // keyboard
  3396. document.addEventListener('keydown', keyboardOnKeyDown, false);
  3397. // if we aren't using the plugin and the keyboard isn't open yet, wait for the
  3398. // window to resize so we can get an accurate estimate of the keyboard size,
  3399. // otherwise we do nothing and let nativeShow call keyboardShow once we have
  3400. // an exact keyboard height
  3401. // if the keyboard is already open, go ahead and scroll the input into view
  3402. // if necessary
  3403. if (!ionic.keyboard.isOpen && !keyboardHasPlugin()) {
  3404. keyboardWaitForResize(keyboardShow, true);
  3405. } else if (ionic.keyboard.isOpen) {
  3406. keyboardShow();
  3407. }
  3408. }
  3409. /**
  3410. * Event handler for 'focusout' events. Sets keyboard.isClosing to true and
  3411. * calls keyboardWaitForResize with keyboardHide as the callback after a small
  3412. * timeout.
  3413. */
  3414. function keyboardFocusOut() {
  3415. clearTimeout(keyboardFocusOutTimer);
  3416. //console.log("keyboardFocusOut fired at: " + Date.now());
  3417. //console.log("keyboardFocusOut event type: " + e.type);
  3418. if (ionic.keyboard.isOpen || ionic.keyboard.isOpening) {
  3419. ionic.keyboard.isClosing = true;
  3420. ionic.keyboard.isOpening = false;
  3421. }
  3422. // Call keyboardHide with a slight delay because sometimes on focus or
  3423. // orientation change focusin is called immediately after, so we give it time
  3424. // to cancel keyboardHide
  3425. keyboardFocusOutTimer = setTimeout(function() {
  3426. ionic.requestAnimationFrame(function() {
  3427. // focusOut during or right after an orientationchange, so we didn't get
  3428. // a chance to update the viewport height yet, do it and keyboardHide
  3429. //console.log("focusOut, wasOrientationChange: " + wasOrientationChange);
  3430. if (wasOrientationChange) {
  3431. keyboardWaitForResize(function(){
  3432. keyboardUpdateViewportHeight();
  3433. keyboardHide();
  3434. }, false);
  3435. } else {
  3436. keyboardWaitForResize(keyboardHide, false);
  3437. }
  3438. });
  3439. }, 50);
  3440. }
  3441. /**
  3442. * Event handler for 'orientationchange' events. If using the keyboard plugin
  3443. * and the keyboard is open on Android, sets wasOrientationChange to true so
  3444. * nativeShow can update the viewport height with an accurate keyboard height.
  3445. * If the keyboard isn't open or keyboard plugin isn't being used,
  3446. * waits for the window to resize before updating the viewport height.
  3447. *
  3448. * On iOS, where orientationchange fires after the keyboard has already shown,
  3449. * updates the viewport immediately, regardless of if the keyboard is already
  3450. * open.
  3451. */
  3452. function keyboardOrientationChange() {
  3453. //console.log("orientationchange fired at: " + Date.now());
  3454. //console.log("orientation was: " + (ionic.keyboard.isLandscape ? "landscape" : "portrait"));
  3455. // toggle orientation
  3456. ionic.keyboard.isLandscape = !ionic.keyboard.isLandscape;
  3457. // //console.log("now orientation is: " + (ionic.keyboard.isLandscape ? "landscape" : "portrait"));
  3458. // no need to wait for resizing on iOS, and orientationchange always fires
  3459. // after the keyboard has opened, so it doesn't matter if it's open or not
  3460. if (ionic.Platform.isIOS()) {
  3461. keyboardUpdateViewportHeight();
  3462. }
  3463. // On Android, if the keyboard isn't open or we aren't using the keyboard
  3464. // plugin, update the viewport height once everything has resized. If the
  3465. // keyboard is open and we are using the keyboard plugin do nothing and let
  3466. // nativeShow handle it using an accurate keyboard height.
  3467. if ( ionic.Platform.isAndroid()) {
  3468. if (!ionic.keyboard.isOpen || !keyboardHasPlugin()) {
  3469. keyboardWaitForResize(keyboardUpdateViewportHeight, false);
  3470. } else {
  3471. wasOrientationChange = true;
  3472. }
  3473. }
  3474. }
  3475. /**
  3476. * Event handler for 'keydown' event. Tries to prevent browser from natively
  3477. * scrolling an input into view when a user taps the keyboard while we are
  3478. * scrolling the input into view ourselves with JS.
  3479. */
  3480. function keyboardOnKeyDown(e) {
  3481. if (ionic.scroll.isScrolling) {
  3482. keyboardPreventDefault(e);
  3483. }
  3484. }
  3485. /**
  3486. * Event for 'touchmove' or 'MSPointerMove'. Prevents native scrolling on
  3487. * elements outside the scroll view while the keyboard is open.
  3488. */
  3489. function keyboardPreventDefault(e) {
  3490. if (e.target.tagName !== 'TEXTAREA') {
  3491. e.preventDefault();
  3492. }
  3493. }
  3494. /* Private API */
  3495. /* -------------------------------------------------------------------------- */
  3496. /**
  3497. * Polls window.innerHeight until it has updated to an expected value (or
  3498. * sufficient time has passed) before calling the specified callback function.
  3499. * Only necessary for non-fullscreen Android which sometimes reports multiple
  3500. * window.innerHeight values during interim layouts while it is resizing.
  3501. *
  3502. * On iOS, the window.innerHeight will already be updated, but we use the 50ms
  3503. * delay as essentially a timeout so that scroll view adjustments happen after
  3504. * the keyboard has shown so there isn't a white flash from us resizing too
  3505. * quickly.
  3506. *
  3507. * @param {Function} callback the function to call once the window has resized
  3508. * @param {boolean} isOpening whether the resize is from the keyboard opening
  3509. * or not
  3510. */
  3511. function keyboardWaitForResize(callback, isOpening) {
  3512. clearInterval(waitForResizeTimer);
  3513. var count = 0;
  3514. var maxCount;
  3515. var initialHeight = getViewportHeight();
  3516. var viewportHeight = initialHeight;
  3517. //console.log("waitForResize initial viewport height: " + viewportHeight);
  3518. //var start = Date.now();
  3519. //console.log("start: " + start);
  3520. // want to fail relatively quickly on modern android devices, since it's much
  3521. // more likely we just have a bad keyboard height
  3522. if (ionic.Platform.isAndroid() && ionic.Platform.version() < 4.4) {
  3523. maxCount = 30;
  3524. } else if (ionic.Platform.isAndroid()) {
  3525. maxCount = 10;
  3526. } else {
  3527. maxCount = 1;
  3528. }
  3529. // poll timer
  3530. waitForResizeTimer = setInterval(function(){
  3531. viewportHeight = getViewportHeight();
  3532. // height hasn't updated yet, try again in 50ms
  3533. // if not using plugin, wait for maxCount to ensure we have waited long enough
  3534. // to get an accurate keyboard height
  3535. if (++count < maxCount &&
  3536. ((!isPortraitViewportHeight(viewportHeight) &&
  3537. !isLandscapeViewportHeight(viewportHeight)) ||
  3538. !ionic.keyboard.height)) {
  3539. return;
  3540. }
  3541. // infer the keyboard height from the resize if not using the keyboard plugin
  3542. if (!keyboardHasPlugin()) {
  3543. ionic.keyboard.height = Math.abs(initialHeight - window.innerHeight);
  3544. }
  3545. // set to true if we were waiting for the keyboard to open
  3546. ionic.keyboard.isOpen = isOpening;
  3547. clearInterval(waitForResizeTimer);
  3548. //var end = Date.now();
  3549. //console.log("waitForResize count: " + count);
  3550. //console.log("end: " + end);
  3551. //console.log("difference: " + ( end - start ) + "ms");
  3552. //console.log("callback: " + callback.name);
  3553. callback();
  3554. }, 50);
  3555. return maxCount; //for tests
  3556. }
  3557. /**
  3558. * On keyboard close sets keyboard state to closed, resets the scroll view,
  3559. * removes CSS from body indicating keyboard was open, removes any event
  3560. * listeners for when the keyboard is open and on Android blurs the active
  3561. * element (which in some cases will still have focus even if the keyboard
  3562. * is closed and can cause it to reappear on subsequent taps).
  3563. */
  3564. function keyboardHide() {
  3565. clearTimeout(keyboardFocusOutTimer);
  3566. //console.log("keyboardHide");
  3567. ionic.keyboard.isOpen = false;
  3568. ionic.keyboard.isClosing = false;
  3569. if (keyboardActiveElement || lastKeyboardActiveElement) {
  3570. ionic.trigger('resetScrollView', {
  3571. target: keyboardActiveElement || lastKeyboardActiveElement
  3572. }, true);
  3573. }
  3574. ionic.requestAnimationFrame(function(){
  3575. document.body.classList.remove(KEYBOARD_OPEN_CSS);
  3576. });
  3577. // the keyboard is gone now, remove the touchmove that disables native scroll
  3578. if (window.navigator.msPointerEnabled) {
  3579. document.removeEventListener("MSPointerMove", keyboardPreventDefault);
  3580. } else {
  3581. document.removeEventListener('touchmove', keyboardPreventDefault);
  3582. }
  3583. document.removeEventListener('keydown', keyboardOnKeyDown);
  3584. if (ionic.Platform.isAndroid()) {
  3585. // on android closing the keyboard with the back/dismiss button won't remove
  3586. // focus and keyboard can re-appear on subsequent taps (like scrolling)
  3587. if (keyboardHasPlugin()) cordova.plugins.Keyboard.close();
  3588. keyboardActiveElement && keyboardActiveElement.blur();
  3589. }
  3590. keyboardActiveElement = null;
  3591. lastKeyboardActiveElement = null;
  3592. }
  3593. /**
  3594. * On keyboard open sets keyboard state to open, adds CSS to the body
  3595. * indicating the keyboard is open and tells the scroll view to resize and
  3596. * the currently focused input into view if necessary.
  3597. */
  3598. function keyboardShow() {
  3599. ionic.keyboard.isOpen = true;
  3600. ionic.keyboard.isOpening = false;
  3601. var details = {
  3602. keyboardHeight: keyboardGetHeight(),
  3603. viewportHeight: keyboardCurrentViewportHeight
  3604. };
  3605. if (keyboardActiveElement) {
  3606. details.target = keyboardActiveElement;
  3607. var elementBounds = keyboardActiveElement.getBoundingClientRect();
  3608. details.elementTop = Math.round(elementBounds.top);
  3609. details.elementBottom = Math.round(elementBounds.bottom);
  3610. details.windowHeight = details.viewportHeight - details.keyboardHeight;
  3611. //console.log("keyboardShow viewportHeight: " + details.viewportHeight +
  3612. //", windowHeight: " + details.windowHeight +
  3613. //", keyboardHeight: " + details.keyboardHeight);
  3614. // figure out if the element is under the keyboard
  3615. details.isElementUnderKeyboard = (details.elementBottom > details.windowHeight);
  3616. //console.log("isUnderKeyboard: " + details.isElementUnderKeyboard);
  3617. //console.log("elementBottom: " + details.elementBottom);
  3618. // send event so the scroll view adjusts
  3619. ionic.trigger('scrollChildIntoView', details, true);
  3620. }
  3621. setTimeout(function(){
  3622. document.body.classList.add(KEYBOARD_OPEN_CSS);
  3623. }, 400);
  3624. return details; //for testing
  3625. }
  3626. /* eslint no-unused-vars:0 */
  3627. function keyboardGetHeight() {
  3628. // check if we already have a keyboard height from the plugin or resize calculations
  3629. if (ionic.keyboard.height) {
  3630. return ionic.keyboard.height;
  3631. }
  3632. if (ionic.Platform.isAndroid()) {
  3633. // should be using the plugin, no way to know how big the keyboard is, so guess
  3634. if ( ionic.Platform.isFullScreen ) {
  3635. return 275;
  3636. }
  3637. // otherwise just calculate it
  3638. var contentHeight = window.innerHeight;
  3639. if (contentHeight < keyboardCurrentViewportHeight) {
  3640. return keyboardCurrentViewportHeight - contentHeight;
  3641. } else {
  3642. return 0;
  3643. }
  3644. }
  3645. // fallback for when it's the webview without the plugin
  3646. // or for just the standard web browser
  3647. // TODO: have these be based on device
  3648. if (ionic.Platform.isIOS()) {
  3649. if (ionic.keyboard.isLandscape) {
  3650. return 206;
  3651. }
  3652. if (!ionic.Platform.isWebView()) {
  3653. return 216;
  3654. }
  3655. return 260;
  3656. }
  3657. // safe guess
  3658. return 275;
  3659. }
  3660. function isPortraitViewportHeight(viewportHeight) {
  3661. return !!(!ionic.keyboard.isLandscape &&
  3662. keyboardPortraitViewportHeight &&
  3663. (Math.abs(keyboardPortraitViewportHeight - viewportHeight) < 2));
  3664. }
  3665. function isLandscapeViewportHeight(viewportHeight) {
  3666. return !!(ionic.keyboard.isLandscape &&
  3667. keyboardLandscapeViewportHeight &&
  3668. (Math.abs(keyboardLandscapeViewportHeight - viewportHeight) < 2));
  3669. }
  3670. function keyboardUpdateViewportHeight() {
  3671. wasOrientationChange = false;
  3672. keyboardCurrentViewportHeight = getViewportHeight();
  3673. if (ionic.keyboard.isLandscape && !keyboardLandscapeViewportHeight) {
  3674. //console.log("saved landscape: " + keyboardCurrentViewportHeight);
  3675. keyboardLandscapeViewportHeight = keyboardCurrentViewportHeight;
  3676. } else if (!ionic.keyboard.isLandscape && !keyboardPortraitViewportHeight) {
  3677. //console.log("saved portrait: " + keyboardCurrentViewportHeight);
  3678. keyboardPortraitViewportHeight = keyboardCurrentViewportHeight;
  3679. }
  3680. if (keyboardActiveElement) {
  3681. ionic.trigger('resetScrollView', {
  3682. target: keyboardActiveElement
  3683. }, true);
  3684. }
  3685. if (ionic.keyboard.isOpen && ionic.tap.isTextInput(keyboardActiveElement)) {
  3686. keyboardShow();
  3687. }
  3688. }
  3689. function keyboardInitViewportHeight() {
  3690. var viewportHeight = getViewportHeight();
  3691. //console.log("Keyboard init VP: " + viewportHeight + " " + window.innerWidth);
  3692. // can't just use window.innerHeight in case the keyboard is opened immediately
  3693. if ((viewportHeight / window.innerWidth) < 1) {
  3694. ionic.keyboard.isLandscape = true;
  3695. }
  3696. //console.log("ionic.keyboard.isLandscape is: " + ionic.keyboard.isLandscape);
  3697. // initialize or update the current viewport height values
  3698. keyboardCurrentViewportHeight = viewportHeight;
  3699. if (ionic.keyboard.isLandscape && !keyboardLandscapeViewportHeight) {
  3700. keyboardLandscapeViewportHeight = keyboardCurrentViewportHeight;
  3701. } else if (!ionic.keyboard.isLandscape && !keyboardPortraitViewportHeight) {
  3702. keyboardPortraitViewportHeight = keyboardCurrentViewportHeight;
  3703. }
  3704. }
  3705. function getViewportHeight() {
  3706. var windowHeight = window.innerHeight;
  3707. //console.log('window.innerHeight is: ' + windowHeight);
  3708. //console.log('kb height is: ' + ionic.keyboard.height);
  3709. //console.log('kb isOpen: ' + ionic.keyboard.isOpen);
  3710. //TODO: add iPad undocked/split kb once kb plugin supports it
  3711. // the keyboard overlays the window on Android fullscreen
  3712. if (!(ionic.Platform.isAndroid() && ionic.Platform.isFullScreen) &&
  3713. (ionic.keyboard.isOpen || ionic.keyboard.isOpening) &&
  3714. !ionic.keyboard.isClosing) {
  3715. return windowHeight + keyboardGetHeight();
  3716. }
  3717. return windowHeight;
  3718. }
  3719. function keyboardHasPlugin() {
  3720. return !!(window.cordova && cordova.plugins && cordova.plugins.Keyboard);
  3721. }
  3722. ionic.Platform.ready(function() {
  3723. keyboardInitViewportHeight();
  3724. window.addEventListener('orientationchange', keyboardOrientationChange);
  3725. // if orientation changes while app is in background, update on resuming
  3726. /*
  3727. if ( ionic.Platform.isWebView() ) {
  3728. document.addEventListener('resume', keyboardInitViewportHeight);
  3729. if (ionic.Platform.isAndroid()) {
  3730. //TODO: onbackpressed to detect keyboard close without focusout or plugin
  3731. }
  3732. }
  3733. */
  3734. // if orientation changes while app is in background, update on resuming
  3735. /* if ( ionic.Platform.isWebView() ) {
  3736. document.addEventListener('pause', function() {
  3737. window.removeEventListener('orientationchange', keyboardOrientationChange);
  3738. })
  3739. document.addEventListener('resume', function() {
  3740. keyboardInitViewportHeight();
  3741. window.addEventListener('orientationchange', keyboardOrientationChange)
  3742. });
  3743. }*/
  3744. // Android sometimes reports bad innerHeight on window.load
  3745. // try it again in a lil bit to play it safe
  3746. setTimeout(keyboardInitViewportHeight, 999);
  3747. // only initialize the adjustments for the virtual keyboard
  3748. // if a touchstart event happens
  3749. if (window.navigator.msPointerEnabled) {
  3750. document.addEventListener("MSPointerDown", keyboardInit, false);
  3751. } else {
  3752. document.addEventListener('touchstart', keyboardInit, false);
  3753. }
  3754. });
  3755. var viewportTag;
  3756. var viewportProperties = {};
  3757. ionic.viewport = {
  3758. orientation: function() {
  3759. // 0 = Portrait
  3760. // 90 = Landscape
  3761. // not using window.orientation because each device has a different implementation
  3762. return (window.innerWidth > window.innerHeight ? 90 : 0);
  3763. }
  3764. };
  3765. function viewportLoadTag() {
  3766. var x;
  3767. for (x = 0; x < document.head.children.length; x++) {
  3768. if (document.head.children[x].name == 'viewport') {
  3769. viewportTag = document.head.children[x];
  3770. break;
  3771. }
  3772. }
  3773. if (viewportTag) {
  3774. var props = viewportTag.content.toLowerCase().replace(/\s+/g, '').split(',');
  3775. var keyValue;
  3776. for (x = 0; x < props.length; x++) {
  3777. if (props[x]) {
  3778. keyValue = props[x].split('=');
  3779. viewportProperties[ keyValue[0] ] = (keyValue.length > 1 ? keyValue[1] : '_');
  3780. }
  3781. }
  3782. viewportUpdate();
  3783. }
  3784. }
  3785. function viewportUpdate() {
  3786. // unit tests in viewport.unit.js
  3787. var initWidth = viewportProperties.width;
  3788. var initHeight = viewportProperties.height;
  3789. var p = ionic.Platform;
  3790. var version = p.version();
  3791. var DEVICE_WIDTH = 'device-width';
  3792. var DEVICE_HEIGHT = 'device-height';
  3793. var orientation = ionic.viewport.orientation();
  3794. // Most times we're removing the height and adding the width
  3795. // So this is the default to start with, then modify per platform/version/oreintation
  3796. delete viewportProperties.height;
  3797. viewportProperties.width = DEVICE_WIDTH;
  3798. if (p.isIPad()) {
  3799. // iPad
  3800. if (version > 7) {
  3801. // iPad >= 7.1
  3802. // https://issues.apache.org/jira/browse/CB-4323
  3803. delete viewportProperties.width;
  3804. } else {
  3805. // iPad <= 7.0
  3806. if (p.isWebView()) {
  3807. // iPad <= 7.0 WebView
  3808. if (orientation == 90) {
  3809. // iPad <= 7.0 WebView Landscape
  3810. viewportProperties.height = '0';
  3811. } else if (version == 7) {
  3812. // iPad <= 7.0 WebView Portait
  3813. viewportProperties.height = DEVICE_HEIGHT;
  3814. }
  3815. } else {
  3816. // iPad <= 6.1 Browser
  3817. if (version < 7) {
  3818. viewportProperties.height = '0';
  3819. }
  3820. }
  3821. }
  3822. } else if (p.isIOS()) {
  3823. // iPhone
  3824. if (p.isWebView()) {
  3825. // iPhone WebView
  3826. if (version > 7) {
  3827. // iPhone >= 7.1 WebView
  3828. delete viewportProperties.width;
  3829. } else if (version < 7) {
  3830. // iPhone <= 6.1 WebView
  3831. // if height was set it needs to get removed with this hack for <= 6.1
  3832. if (initHeight) viewportProperties.height = '0';
  3833. } else if (version == 7) {
  3834. //iPhone == 7.0 WebView
  3835. viewportProperties.height = DEVICE_HEIGHT;
  3836. }
  3837. } else {
  3838. // iPhone Browser
  3839. if (version < 7) {
  3840. // iPhone <= 6.1 Browser
  3841. // if height was set it needs to get removed with this hack for <= 6.1
  3842. if (initHeight) viewportProperties.height = '0';
  3843. }
  3844. }
  3845. }
  3846. // only update the viewport tag if there was a change
  3847. if (initWidth !== viewportProperties.width || initHeight !== viewportProperties.height) {
  3848. viewportTagUpdate();
  3849. }
  3850. }
  3851. function viewportTagUpdate() {
  3852. var key, props = [];
  3853. for (key in viewportProperties) {
  3854. if (viewportProperties[key]) {
  3855. props.push(key + (viewportProperties[key] == '_' ? '' : '=' + viewportProperties[key]));
  3856. }
  3857. }
  3858. viewportTag.content = props.join(', ');
  3859. }
  3860. ionic.Platform.ready(function() {
  3861. viewportLoadTag();
  3862. window.addEventListener("orientationchange", function() {
  3863. setTimeout(viewportUpdate, 1000);
  3864. }, false);
  3865. });
  3866. (function(ionic) {
  3867. 'use strict';
  3868. ionic.views.View = function() {
  3869. this.initialize.apply(this, arguments);
  3870. };
  3871. ionic.views.View.inherit = ionic.inherit;
  3872. ionic.extend(ionic.views.View.prototype, {
  3873. initialize: function() {}
  3874. });
  3875. })(window.ionic);
  3876. /*
  3877. * Scroller
  3878. * http://github.com/zynga/scroller
  3879. *
  3880. * Copyright 2011, Zynga Inc.
  3881. * Licensed under the MIT License.
  3882. * https://raw.github.com/zynga/scroller/master/MIT-LICENSE.txt
  3883. *
  3884. * Based on the work of: Unify Project (unify-project.org)
  3885. * http://unify-project.org
  3886. * Copyright 2011, Deutsche Telekom AG
  3887. * License: MIT + Apache (V2)
  3888. */
  3889. /* jshint eqnull: true */
  3890. /**
  3891. * Generic animation class with support for dropped frames both optional easing and duration.
  3892. *
  3893. * Optional duration is useful when the lifetime is defined by another condition than time
  3894. * e.g. speed of an animating object, etc.
  3895. *
  3896. * Dropped frame logic allows to keep using the same updater logic independent from the actual
  3897. * rendering. This eases a lot of cases where it might be pretty complex to break down a state
  3898. * based on the pure time difference.
  3899. */
  3900. var zyngaCore = { effect: {} };
  3901. (function(global) {
  3902. var time = Date.now || function() {
  3903. return +new Date();
  3904. };
  3905. var desiredFrames = 60;
  3906. var millisecondsPerSecond = 1000;
  3907. var running = {};
  3908. var counter = 1;
  3909. zyngaCore.effect.Animate = {
  3910. /**
  3911. * A requestAnimationFrame wrapper / polyfill.
  3912. *
  3913. * @param callback {Function} The callback to be invoked before the next repaint.
  3914. * @param root {HTMLElement} The root element for the repaint
  3915. */
  3916. requestAnimationFrame: (function() {
  3917. // Check for request animation Frame support
  3918. var requestFrame = global.requestAnimationFrame || global.webkitRequestAnimationFrame || global.mozRequestAnimationFrame || global.oRequestAnimationFrame;
  3919. var isNative = !!requestFrame;
  3920. if (requestFrame && !/requestAnimationFrame\(\)\s*\{\s*\[native code\]\s*\}/i.test(requestFrame.toString())) {
  3921. isNative = false;
  3922. }
  3923. if (isNative) {
  3924. return function(callback, root) {
  3925. requestFrame(callback, root);
  3926. };
  3927. }
  3928. var TARGET_FPS = 60;
  3929. var requests = {};
  3930. var requestCount = 0;
  3931. var rafHandle = 1;
  3932. var intervalHandle = null;
  3933. var lastActive = +new Date();
  3934. return function(callback) {
  3935. var callbackHandle = rafHandle++;
  3936. // Store callback
  3937. requests[callbackHandle] = callback;
  3938. requestCount++;
  3939. // Create timeout at first request
  3940. if (intervalHandle === null) {
  3941. intervalHandle = setInterval(function() {
  3942. var time = +new Date();
  3943. var currentRequests = requests;
  3944. // Reset data structure before executing callbacks
  3945. requests = {};
  3946. requestCount = 0;
  3947. for(var key in currentRequests) {
  3948. if (currentRequests.hasOwnProperty(key)) {
  3949. currentRequests[key](time);
  3950. lastActive = time;
  3951. }
  3952. }
  3953. // Disable the timeout when nothing happens for a certain
  3954. // period of time
  3955. if (time - lastActive > 2500) {
  3956. clearInterval(intervalHandle);
  3957. intervalHandle = null;
  3958. }
  3959. }, 1000 / TARGET_FPS);
  3960. }
  3961. return callbackHandle;
  3962. };
  3963. })(),
  3964. /**
  3965. * Stops the given animation.
  3966. *
  3967. * @param id {Integer} Unique animation ID
  3968. * @return {Boolean} Whether the animation was stopped (aka, was running before)
  3969. */
  3970. stop: function(id) {
  3971. var cleared = running[id] != null;
  3972. if (cleared) {
  3973. running[id] = null;
  3974. }
  3975. return cleared;
  3976. },
  3977. /**
  3978. * Whether the given animation is still running.
  3979. *
  3980. * @param id {Integer} Unique animation ID
  3981. * @return {Boolean} Whether the animation is still running
  3982. */
  3983. isRunning: function(id) {
  3984. return running[id] != null;
  3985. },
  3986. /**
  3987. * Start the animation.
  3988. *
  3989. * @param stepCallback {Function} Pointer to function which is executed on every step.
  3990. * Signature of the method should be `function(percent, now, virtual) { return continueWithAnimation; }`
  3991. * @param verifyCallback {Function} Executed before every animation step.
  3992. * Signature of the method should be `function() { return continueWithAnimation; }`
  3993. * @param completedCallback {Function}
  3994. * Signature of the method should be `function(droppedFrames, finishedAnimation) {}`
  3995. * @param duration {Integer} Milliseconds to run the animation
  3996. * @param easingMethod {Function} Pointer to easing function
  3997. * Signature of the method should be `function(percent) { return modifiedValue; }`
  3998. * @param root {Element} Render root, when available. Used for internal
  3999. * usage of requestAnimationFrame.
  4000. * @return {Integer} Identifier of animation. Can be used to stop it any time.
  4001. */
  4002. start: function(stepCallback, verifyCallback, completedCallback, duration, easingMethod, root) {
  4003. var start = time();
  4004. var lastFrame = start;
  4005. var percent = 0;
  4006. var dropCounter = 0;
  4007. var id = counter++;
  4008. if (!root) {
  4009. root = document.body;
  4010. }
  4011. // Compacting running db automatically every few new animations
  4012. if (id % 20 === 0) {
  4013. var newRunning = {};
  4014. for (var usedId in running) {
  4015. newRunning[usedId] = true;
  4016. }
  4017. running = newRunning;
  4018. }
  4019. // This is the internal step method which is called every few milliseconds
  4020. var step = function(virtual) {
  4021. // Normalize virtual value
  4022. var render = virtual !== true;
  4023. // Get current time
  4024. var now = time();
  4025. // Verification is executed before next animation step
  4026. if (!running[id] || (verifyCallback && !verifyCallback(id))) {
  4027. running[id] = null;
  4028. completedCallback && completedCallback(desiredFrames - (dropCounter / ((now - start) / millisecondsPerSecond)), id, false);
  4029. return;
  4030. }
  4031. // For the current rendering to apply let's update omitted steps in memory.
  4032. // This is important to bring internal state variables up-to-date with progress in time.
  4033. if (render) {
  4034. var droppedFrames = Math.round((now - lastFrame) / (millisecondsPerSecond / desiredFrames)) - 1;
  4035. for (var j = 0; j < Math.min(droppedFrames, 4); j++) {
  4036. step(true);
  4037. dropCounter++;
  4038. }
  4039. }
  4040. // Compute percent value
  4041. if (duration) {
  4042. percent = (now - start) / duration;
  4043. if (percent > 1) {
  4044. percent = 1;
  4045. }
  4046. }
  4047. // Execute step callback, then...
  4048. var value = easingMethod ? easingMethod(percent) : percent;
  4049. if ((stepCallback(value, now, render) === false || percent === 1) && render) {
  4050. running[id] = null;
  4051. completedCallback && completedCallback(desiredFrames - (dropCounter / ((now - start) / millisecondsPerSecond)), id, percent === 1 || duration == null);
  4052. } else if (render) {
  4053. lastFrame = now;
  4054. zyngaCore.effect.Animate.requestAnimationFrame(step, root);
  4055. }
  4056. };
  4057. // Mark as running
  4058. running[id] = true;
  4059. // Init first step
  4060. zyngaCore.effect.Animate.requestAnimationFrame(step, root);
  4061. // Return unique animation ID
  4062. return id;
  4063. }
  4064. };
  4065. })(window);
  4066. /*
  4067. * Scroller
  4068. * http://github.com/zynga/scroller
  4069. *
  4070. * Copyright 2011, Zynga Inc.
  4071. * Licensed under the MIT License.
  4072. * https://raw.github.com/zynga/scroller/master/MIT-LICENSE.txt
  4073. *
  4074. * Based on the work of: Unify Project (unify-project.org)
  4075. * http://unify-project.org
  4076. * Copyright 2011, Deutsche Telekom AG
  4077. * License: MIT + Apache (V2)
  4078. */
  4079. (function(ionic) {
  4080. var NOOP = function(){};
  4081. // Easing Equations (c) 2003 Robert Penner, all rights reserved.
  4082. // Open source under the BSD License.
  4083. /**
  4084. * @param pos {Number} position between 0 (start of effect) and 1 (end of effect)
  4085. **/
  4086. var easeOutCubic = function(pos) {
  4087. return (Math.pow((pos - 1), 3) + 1);
  4088. };
  4089. /**
  4090. * @param pos {Number} position between 0 (start of effect) and 1 (end of effect)
  4091. **/
  4092. var easeInOutCubic = function(pos) {
  4093. if ((pos /= 0.5) < 1) {
  4094. return 0.5 * Math.pow(pos, 3);
  4095. }
  4096. return 0.5 * (Math.pow((pos - 2), 3) + 2);
  4097. };
  4098. /**
  4099. * ionic.views.Scroll
  4100. * A powerful scroll view with support for bouncing, pull to refresh, and paging.
  4101. * @param {Object} options options for the scroll view
  4102. * @class A scroll view system
  4103. * @memberof ionic.views
  4104. */
  4105. ionic.views.Scroll = ionic.views.View.inherit({
  4106. initialize: function(options) {
  4107. var self = this;
  4108. self.__container = options.el;
  4109. self.__content = options.el.firstElementChild;
  4110. //Remove any scrollTop attached to these elements; they are virtual scroll now
  4111. //This also stops on-load-scroll-to-window.location.hash that the browser does
  4112. setTimeout(function() {
  4113. if (self.__container && self.__content) {
  4114. self.__container.scrollTop = 0;
  4115. self.__content.scrollTop = 0;
  4116. }
  4117. });
  4118. self.options = {
  4119. /** Disable scrolling on x-axis by default */
  4120. scrollingX: false,
  4121. scrollbarX: true,
  4122. /** Enable scrolling on y-axis */
  4123. scrollingY: true,
  4124. scrollbarY: true,
  4125. startX: 0,
  4126. startY: 0,
  4127. /** The amount to dampen mousewheel events */
  4128. wheelDampen: 6,
  4129. /** The minimum size the scrollbars scale to while scrolling */
  4130. minScrollbarSizeX: 5,
  4131. minScrollbarSizeY: 5,
  4132. /** Scrollbar fading after scrolling */
  4133. scrollbarsFade: true,
  4134. scrollbarFadeDelay: 300,
  4135. /** The initial fade delay when the pane is resized or initialized */
  4136. scrollbarResizeFadeDelay: 1000,
  4137. /** Enable animations for deceleration, snap back, zooming and scrolling */
  4138. animating: true,
  4139. /** duration for animations triggered by scrollTo/zoomTo */
  4140. animationDuration: 250,
  4141. /** The velocity required to make the scroll view "slide" after touchend */
  4142. decelVelocityThreshold: 4,
  4143. /** The velocity required to make the scroll view "slide" after touchend when using paging */
  4144. decelVelocityThresholdPaging: 4,
  4145. /** Enable bouncing (content can be slowly moved outside and jumps back after releasing) */
  4146. bouncing: true,
  4147. /** Enable locking to the main axis if user moves only slightly on one of them at start */
  4148. locking: true,
  4149. /** Enable pagination mode (switching between full page content panes) */
  4150. paging: false,
  4151. /** Enable snapping of content to a configured pixel grid */
  4152. snapping: false,
  4153. /** Enable zooming of content via API, fingers and mouse wheel */
  4154. zooming: false,
  4155. /** Minimum zoom level */
  4156. minZoom: 0.5,
  4157. /** Maximum zoom level */
  4158. maxZoom: 3,
  4159. /** Multiply or decrease scrolling speed **/
  4160. speedMultiplier: 1,
  4161. deceleration: 0.97,
  4162. /** Whether to prevent default on a scroll operation to capture drag events **/
  4163. preventDefault: false,
  4164. /** Callback that is fired on the later of touch end or deceleration end,
  4165. provided that another scrolling action has not begun. Used to know
  4166. when to fade out a scrollbar. */
  4167. scrollingComplete: NOOP,
  4168. /** This configures the amount of change applied to deceleration when reaching boundaries **/
  4169. penetrationDeceleration: 0.03,
  4170. /** This configures the amount of change applied to acceleration when reaching boundaries **/
  4171. penetrationAcceleration: 0.08,
  4172. // The ms interval for triggering scroll events
  4173. scrollEventInterval: 10,
  4174. freeze: false,
  4175. getContentWidth: function() {
  4176. return Math.max(self.__content.scrollWidth, self.__content.offsetWidth);
  4177. },
  4178. getContentHeight: function() {
  4179. return Math.max(self.__content.scrollHeight, self.__content.offsetHeight + (self.__content.offsetTop * 2));
  4180. }
  4181. };
  4182. for (var key in options) {
  4183. self.options[key] = options[key];
  4184. }
  4185. self.hintResize = ionic.debounce(function() {
  4186. self.resize();
  4187. }, 1000, true);
  4188. self.onScroll = function() {
  4189. if (!ionic.scroll.isScrolling) {
  4190. setTimeout(self.setScrollStart, 50);
  4191. } else {
  4192. clearTimeout(self.scrollTimer);
  4193. self.scrollTimer = setTimeout(self.setScrollStop, 80);
  4194. }
  4195. };
  4196. self.freeze = function(shouldFreeze) {
  4197. if (arguments.length) {
  4198. self.options.freeze = shouldFreeze;
  4199. }
  4200. return self.options.freeze;
  4201. };
  4202. // We can just use the standard freeze pop in our mouth
  4203. self.freezeShut = self.freeze;
  4204. self.setScrollStart = function() {
  4205. ionic.scroll.isScrolling = Math.abs(ionic.scroll.lastTop - self.__scrollTop) > 1;
  4206. clearTimeout(self.scrollTimer);
  4207. self.scrollTimer = setTimeout(self.setScrollStop, 80);
  4208. };
  4209. self.setScrollStop = function() {
  4210. ionic.scroll.isScrolling = false;
  4211. ionic.scroll.lastTop = self.__scrollTop;
  4212. };
  4213. self.triggerScrollEvent = ionic.throttle(function() {
  4214. self.onScroll();
  4215. ionic.trigger('scroll', {
  4216. scrollTop: self.__scrollTop,
  4217. scrollLeft: self.__scrollLeft,
  4218. target: self.__container
  4219. });
  4220. }, self.options.scrollEventInterval);
  4221. self.triggerScrollEndEvent = function() {
  4222. ionic.trigger('scrollend', {
  4223. scrollTop: self.__scrollTop,
  4224. scrollLeft: self.__scrollLeft,
  4225. target: self.__container
  4226. });
  4227. };
  4228. self.__scrollLeft = self.options.startX;
  4229. self.__scrollTop = self.options.startY;
  4230. // Get the render update function, initialize event handlers,
  4231. // and calculate the size of the scroll container
  4232. self.__callback = self.getRenderFn();
  4233. self.__initEventHandlers();
  4234. self.__createScrollbars();
  4235. },
  4236. run: function() {
  4237. this.resize();
  4238. // Fade them out
  4239. this.__fadeScrollbars('out', this.options.scrollbarResizeFadeDelay);
  4240. },
  4241. /*
  4242. ---------------------------------------------------------------------------
  4243. INTERNAL FIELDS :: STATUS
  4244. ---------------------------------------------------------------------------
  4245. */
  4246. /** Whether only a single finger is used in touch handling */
  4247. __isSingleTouch: false,
  4248. /** Whether a touch event sequence is in progress */
  4249. __isTracking: false,
  4250. /** Whether a deceleration animation went to completion. */
  4251. __didDecelerationComplete: false,
  4252. /**
  4253. * Whether a gesture zoom/rotate event is in progress. Activates when
  4254. * a gesturestart event happens. This has higher priority than dragging.
  4255. */
  4256. __isGesturing: false,
  4257. /**
  4258. * Whether the user has moved by such a distance that we have enabled
  4259. * dragging mode. Hint: It's only enabled after some pixels of movement to
  4260. * not interrupt with clicks etc.
  4261. */
  4262. __isDragging: false,
  4263. /**
  4264. * Not touching and dragging anymore, and smoothly animating the
  4265. * touch sequence using deceleration.
  4266. */
  4267. __isDecelerating: false,
  4268. /**
  4269. * Smoothly animating the currently configured change
  4270. */
  4271. __isAnimating: false,
  4272. /*
  4273. ---------------------------------------------------------------------------
  4274. INTERNAL FIELDS :: DIMENSIONS
  4275. ---------------------------------------------------------------------------
  4276. */
  4277. /** Available outer left position (from document perspective) */
  4278. __clientLeft: 0,
  4279. /** Available outer top position (from document perspective) */
  4280. __clientTop: 0,
  4281. /** Available outer width */
  4282. __clientWidth: 0,
  4283. /** Available outer height */
  4284. __clientHeight: 0,
  4285. /** Outer width of content */
  4286. __contentWidth: 0,
  4287. /** Outer height of content */
  4288. __contentHeight: 0,
  4289. /** Snapping width for content */
  4290. __snapWidth: 100,
  4291. /** Snapping height for content */
  4292. __snapHeight: 100,
  4293. /** Height to assign to refresh area */
  4294. __refreshHeight: null,
  4295. /** Whether the refresh process is enabled when the event is released now */
  4296. __refreshActive: false,
  4297. /** Callback to execute on activation. This is for signalling the user about a refresh is about to happen when he release */
  4298. __refreshActivate: null,
  4299. /** Callback to execute on deactivation. This is for signalling the user about the refresh being cancelled */
  4300. __refreshDeactivate: null,
  4301. /** Callback to execute to start the actual refresh. Call {@link #refreshFinish} when done */
  4302. __refreshStart: null,
  4303. /** Zoom level */
  4304. __zoomLevel: 1,
  4305. /** Scroll position on x-axis */
  4306. __scrollLeft: 0,
  4307. /** Scroll position on y-axis */
  4308. __scrollTop: 0,
  4309. /** Maximum allowed scroll position on x-axis */
  4310. __maxScrollLeft: 0,
  4311. /** Maximum allowed scroll position on y-axis */
  4312. __maxScrollTop: 0,
  4313. /* Scheduled left position (final position when animating) */
  4314. __scheduledLeft: 0,
  4315. /* Scheduled top position (final position when animating) */
  4316. __scheduledTop: 0,
  4317. /* Scheduled zoom level (final scale when animating) */
  4318. __scheduledZoom: 0,
  4319. /*
  4320. ---------------------------------------------------------------------------
  4321. INTERNAL FIELDS :: LAST POSITIONS
  4322. ---------------------------------------------------------------------------
  4323. */
  4324. /** Left position of finger at start */
  4325. __lastTouchLeft: null,
  4326. /** Top position of finger at start */
  4327. __lastTouchTop: null,
  4328. /** Timestamp of last move of finger. Used to limit tracking range for deceleration speed. */
  4329. __lastTouchMove: null,
  4330. /** List of positions, uses three indexes for each state: left, top, timestamp */
  4331. __positions: null,
  4332. /*
  4333. ---------------------------------------------------------------------------
  4334. INTERNAL FIELDS :: DECELERATION SUPPORT
  4335. ---------------------------------------------------------------------------
  4336. */
  4337. /** Minimum left scroll position during deceleration */
  4338. __minDecelerationScrollLeft: null,
  4339. /** Minimum top scroll position during deceleration */
  4340. __minDecelerationScrollTop: null,
  4341. /** Maximum left scroll position during deceleration */
  4342. __maxDecelerationScrollLeft: null,
  4343. /** Maximum top scroll position during deceleration */
  4344. __maxDecelerationScrollTop: null,
  4345. /** Current factor to modify horizontal scroll position with on every step */
  4346. __decelerationVelocityX: null,
  4347. /** Current factor to modify vertical scroll position with on every step */
  4348. __decelerationVelocityY: null,
  4349. /** the browser-specific property to use for transforms */
  4350. __transformProperty: null,
  4351. __perspectiveProperty: null,
  4352. /** scrollbar indicators */
  4353. __indicatorX: null,
  4354. __indicatorY: null,
  4355. /** Timeout for scrollbar fading */
  4356. __scrollbarFadeTimeout: null,
  4357. /** whether we've tried to wait for size already */
  4358. __didWaitForSize: null,
  4359. __sizerTimeout: null,
  4360. __initEventHandlers: function() {
  4361. var self = this;
  4362. // Event Handler
  4363. var container = self.__container;
  4364. // save height when scroll view is shrunk so we don't need to reflow
  4365. var scrollViewOffsetHeight;
  4366. /**
  4367. * Shrink the scroll view when the keyboard is up if necessary and if the
  4368. * focused input is below the bottom of the shrunk scroll view, scroll it
  4369. * into view.
  4370. */
  4371. self.scrollChildIntoView = function(e) {
  4372. //console.log("scrollChildIntoView at: " + Date.now());
  4373. // D
  4374. var scrollBottomOffsetToTop = container.getBoundingClientRect().bottom;
  4375. // D - A
  4376. scrollViewOffsetHeight = container.offsetHeight;
  4377. var alreadyShrunk = self.isShrunkForKeyboard;
  4378. var isModal = container.parentNode.classList.contains('modal');
  4379. // 680px is when the media query for 60% modal width kicks in
  4380. var isInsetModal = isModal && window.innerWidth >= 680;
  4381. /*
  4382. * _______
  4383. * |---A---| <- top of scroll view
  4384. * | |
  4385. * |---B---| <- keyboard
  4386. * | C | <- input
  4387. * |---D---| <- initial bottom of scroll view
  4388. * |___E___| <- bottom of viewport
  4389. *
  4390. * All commented calculations relative to the top of the viewport (ie E
  4391. * is the viewport height, not 0)
  4392. */
  4393. if (!alreadyShrunk) {
  4394. // shrink scrollview so we can actually scroll if the input is hidden
  4395. // if it isn't shrink so we can scroll to inputs under the keyboard
  4396. // inset modals won't shrink on Android on their own when the keyboard appears
  4397. if ( ionic.Platform.isIOS() || ionic.Platform.isFullScreen || isInsetModal ) {
  4398. // if there are things below the scroll view account for them and
  4399. // subtract them from the keyboard height when resizing
  4400. // E - D E D
  4401. var scrollBottomOffsetToBottom = e.detail.viewportHeight - scrollBottomOffsetToTop;
  4402. // 0 or D - B if D > B E - B E - D
  4403. var keyboardOffset = Math.max(0, e.detail.keyboardHeight - scrollBottomOffsetToBottom);
  4404. ionic.requestAnimationFrame(function(){
  4405. // D - A or B - A if D > B D - A max(0, D - B)
  4406. scrollViewOffsetHeight = scrollViewOffsetHeight - keyboardOffset;
  4407. container.style.height = scrollViewOffsetHeight + "px";
  4408. container.style.overflow = "visible";
  4409. //update scroll view
  4410. self.resize();
  4411. });
  4412. }
  4413. self.isShrunkForKeyboard = true;
  4414. }
  4415. /*
  4416. * _______
  4417. * |---A---| <- top of scroll view
  4418. * | * | <- where we want to scroll to
  4419. * |--B-D--| <- keyboard, bottom of scroll view
  4420. * | C | <- input
  4421. * | |
  4422. * |___E___| <- bottom of viewport
  4423. *
  4424. * All commented calculations relative to the top of the viewport (ie E
  4425. * is the viewport height, not 0)
  4426. */
  4427. // if the element is positioned under the keyboard scroll it into view
  4428. if (e.detail.isElementUnderKeyboard) {
  4429. ionic.requestAnimationFrame(function(){
  4430. container.scrollTop = 0;
  4431. // update D if we shrunk
  4432. if (self.isShrunkForKeyboard && !alreadyShrunk) {
  4433. scrollBottomOffsetToTop = container.getBoundingClientRect().bottom;
  4434. }
  4435. // middle of the scrollview, this is where we want to scroll to
  4436. // (D - A) / 2
  4437. var scrollMidpointOffset = scrollViewOffsetHeight * 0.5;
  4438. //console.log("container.offsetHeight: " + scrollViewOffsetHeight);
  4439. // middle of the input we want to scroll into view
  4440. // C
  4441. var inputMidpoint = ((e.detail.elementBottom + e.detail.elementTop) / 2);
  4442. // distance from middle of input to the bottom of the scroll view
  4443. // C - D C D
  4444. var inputMidpointOffsetToScrollBottom = inputMidpoint - scrollBottomOffsetToTop;
  4445. //C - D + (D - A)/2 C - D (D - A)/ 2
  4446. var scrollTop = inputMidpointOffsetToScrollBottom + scrollMidpointOffset;
  4447. if ( scrollTop > 0) {
  4448. if (ionic.Platform.isIOS()) ionic.tap.cloneFocusedInput(container, self);
  4449. self.scrollBy(0, scrollTop, true);
  4450. self.onScroll();
  4451. }
  4452. });
  4453. }
  4454. // Only the first scrollView parent of the element that broadcasted this event
  4455. // (the active element that needs to be shown) should receive this event
  4456. e.stopPropagation();
  4457. };
  4458. self.resetScrollView = function() {
  4459. //return scrollview to original height once keyboard has hidden
  4460. if ( self.isShrunkForKeyboard ) {
  4461. self.isShrunkForKeyboard = false;
  4462. container.style.height = "";
  4463. container.style.overflow = "";
  4464. }
  4465. self.resize();
  4466. };
  4467. //Broadcasted when keyboard is shown on some platforms.
  4468. //See js/utils/keyboard.js
  4469. container.addEventListener('scrollChildIntoView', self.scrollChildIntoView);
  4470. // Listen on document because container may not have had the last
  4471. // keyboardActiveElement, for example after closing a modal with a focused
  4472. // input and returning to a previously resized scroll view in an ion-content.
  4473. // Since we can only resize scroll views that are currently visible, just resize
  4474. // the current scroll view when the keyboard is closed.
  4475. document.addEventListener('resetScrollView', self.resetScrollView);
  4476. function getEventTouches(e) {
  4477. return e.touches && e.touches.length ? e.touches : [{
  4478. pageX: e.pageX,
  4479. pageY: e.pageY
  4480. }];
  4481. }
  4482. self.touchStart = function(e) {
  4483. self.startCoordinates = ionic.tap.pointerCoord(e);
  4484. if ( ionic.tap.ignoreScrollStart(e) ) {
  4485. return;
  4486. }
  4487. self.__isDown = true;
  4488. if ( ionic.tap.containsOrIsTextInput(e.target) || e.target.tagName === 'SELECT' ) {
  4489. // do not start if the target is a text input
  4490. // if there is a touchmove on this input, then we can start the scroll
  4491. self.__hasStarted = false;
  4492. return;
  4493. }
  4494. self.__isSelectable = true;
  4495. self.__enableScrollY = true;
  4496. self.__hasStarted = true;
  4497. self.doTouchStart(getEventTouches(e), e.timeStamp);
  4498. e.preventDefault();
  4499. };
  4500. self.touchMove = function(e) {
  4501. if (self.options.freeze || !self.__isDown ||
  4502. (!self.__isDown && e.defaultPrevented) ||
  4503. (e.target.tagName === 'TEXTAREA' && e.target.parentElement.querySelector(':focus')) ) {
  4504. return;
  4505. }
  4506. if ( !self.__hasStarted && ( ionic.tap.containsOrIsTextInput(e.target) || e.target.tagName === 'SELECT' ) ) {
  4507. // the target is a text input and scroll has started
  4508. // since the text input doesn't start on touchStart, do it here
  4509. self.__hasStarted = true;
  4510. self.doTouchStart(getEventTouches(e), e.timeStamp);
  4511. e.preventDefault();
  4512. return;
  4513. }
  4514. if (self.startCoordinates) {
  4515. // we have start coordinates, so get this touch move's current coordinates
  4516. var currentCoordinates = ionic.tap.pointerCoord(e);
  4517. if ( self.__isSelectable &&
  4518. ionic.tap.isTextInput(e.target) &&
  4519. Math.abs(self.startCoordinates.x - currentCoordinates.x) > 20 ) {
  4520. // user slid the text input's caret on its x axis, disable any future y scrolling
  4521. self.__enableScrollY = false;
  4522. self.__isSelectable = true;
  4523. }
  4524. if ( self.__enableScrollY && Math.abs(self.startCoordinates.y - currentCoordinates.y) > 10 ) {
  4525. // user scrolled the entire view on the y axis
  4526. // disabled being able to select text on an input
  4527. // hide the input which has focus, and show a cloned one that doesn't have focus
  4528. self.__isSelectable = false;
  4529. ionic.tap.cloneFocusedInput(container, self);
  4530. }
  4531. }
  4532. self.doTouchMove(getEventTouches(e), e.timeStamp, e.scale);
  4533. self.__isDown = true;
  4534. };
  4535. self.touchMoveBubble = function(e) {
  4536. if(self.__isDown && self.options.preventDefault) {
  4537. e.preventDefault();
  4538. }
  4539. };
  4540. self.touchEnd = function(e) {
  4541. if (!self.__isDown) return;
  4542. self.doTouchEnd(e, e.timeStamp);
  4543. self.__isDown = false;
  4544. self.__hasStarted = false;
  4545. self.__isSelectable = true;
  4546. self.__enableScrollY = true;
  4547. if ( !self.__isDragging && !self.__isDecelerating && !self.__isAnimating ) {
  4548. ionic.tap.removeClonedInputs(container, self);
  4549. }
  4550. };
  4551. self.mouseWheel = ionic.animationFrameThrottle(function(e) {
  4552. var scrollParent = ionic.DomUtil.getParentOrSelfWithClass(e.target, 'ionic-scroll');
  4553. if (!self.options.freeze && scrollParent === self.__container) {
  4554. self.hintResize();
  4555. self.scrollBy(
  4556. (e.wheelDeltaX || e.deltaX || 0) / self.options.wheelDampen,
  4557. (-e.wheelDeltaY || e.deltaY || 0) / self.options.wheelDampen
  4558. );
  4559. self.__fadeScrollbars('in');
  4560. clearTimeout(self.__wheelHideBarTimeout);
  4561. self.__wheelHideBarTimeout = setTimeout(function() {
  4562. self.__fadeScrollbars('out');
  4563. }, 100);
  4564. }
  4565. });
  4566. if ('ontouchstart' in window) {
  4567. // Touch Events
  4568. container.addEventListener("touchstart", self.touchStart, false);
  4569. if(self.options.preventDefault) container.addEventListener("touchmove", self.touchMoveBubble, false);
  4570. document.addEventListener("touchmove", self.touchMove, false);
  4571. document.addEventListener("touchend", self.touchEnd, false);
  4572. document.addEventListener("touchcancel", self.touchEnd, false);
  4573. document.addEventListener("wheel", self.mouseWheel, false);
  4574. } else if (window.navigator.pointerEnabled) {
  4575. // Pointer Events
  4576. container.addEventListener("pointerdown", self.touchStart, false);
  4577. if(self.options.preventDefault) container.addEventListener("pointermove", self.touchMoveBubble, false);
  4578. document.addEventListener("pointermove", self.touchMove, false);
  4579. document.addEventListener("pointerup", self.touchEnd, false);
  4580. document.addEventListener("pointercancel", self.touchEnd, false);
  4581. document.addEventListener("wheel", self.mouseWheel, false);
  4582. } else if (window.navigator.msPointerEnabled) {
  4583. // IE10, WP8 (Pointer Events)
  4584. container.addEventListener("MSPointerDown", self.touchStart, false);
  4585. if(self.options.preventDefault) container.addEventListener("MSPointerMove", self.touchMoveBubble, false);
  4586. document.addEventListener("MSPointerMove", self.touchMove, false);
  4587. document.addEventListener("MSPointerUp", self.touchEnd, false);
  4588. document.addEventListener("MSPointerCancel", self.touchEnd, false);
  4589. document.addEventListener("wheel", self.mouseWheel, false);
  4590. } else {
  4591. // Mouse Events
  4592. var mousedown = false;
  4593. self.mouseDown = function(e) {
  4594. if ( ionic.tap.ignoreScrollStart(e) || e.target.tagName === 'SELECT' ) {
  4595. return;
  4596. }
  4597. self.doTouchStart(getEventTouches(e), e.timeStamp);
  4598. if ( !ionic.tap.isTextInput(e.target) ) {
  4599. e.preventDefault();
  4600. }
  4601. mousedown = true;
  4602. };
  4603. self.mouseMove = function(e) {
  4604. if (self.options.freeze || !mousedown || (!mousedown && e.defaultPrevented)) {
  4605. return;
  4606. }
  4607. self.doTouchMove(getEventTouches(e), e.timeStamp);
  4608. mousedown = true;
  4609. };
  4610. self.mouseMoveBubble = function(e) {
  4611. if (mousedown && self.options.preventDefault) {
  4612. e.preventDefault();
  4613. }
  4614. };
  4615. self.mouseUp = function(e) {
  4616. if (!mousedown) {
  4617. return;
  4618. }
  4619. self.doTouchEnd(e, e.timeStamp);
  4620. mousedown = false;
  4621. };
  4622. container.addEventListener("mousedown", self.mouseDown, false);
  4623. if(self.options.preventDefault) container.addEventListener("mousemove", self.mouseMoveBubble, false);
  4624. document.addEventListener("mousemove", self.mouseMove, false);
  4625. document.addEventListener("mouseup", self.mouseUp, false);
  4626. document.addEventListener('mousewheel', self.mouseWheel, false);
  4627. document.addEventListener('wheel', self.mouseWheel, false);
  4628. }
  4629. },
  4630. __cleanup: function() {
  4631. var self = this;
  4632. var container = self.__container;
  4633. container.removeEventListener('touchstart', self.touchStart);
  4634. container.removeEventListener('touchmove', self.touchMoveBubble);
  4635. document.removeEventListener('touchmove', self.touchMove);
  4636. document.removeEventListener('touchend', self.touchEnd);
  4637. document.removeEventListener('touchcancel', self.touchEnd);
  4638. container.removeEventListener("pointerdown", self.touchStart);
  4639. container.removeEventListener("pointermove", self.touchMoveBubble);
  4640. document.removeEventListener("pointermove", self.touchMove);
  4641. document.removeEventListener("pointerup", self.touchEnd);
  4642. document.removeEventListener("pointercancel", self.touchEnd);
  4643. container.removeEventListener("MSPointerDown", self.touchStart);
  4644. container.removeEventListener("MSPointerMove", self.touchMoveBubble);
  4645. document.removeEventListener("MSPointerMove", self.touchMove);
  4646. document.removeEventListener("MSPointerUp", self.touchEnd);
  4647. document.removeEventListener("MSPointerCancel", self.touchEnd);
  4648. container.removeEventListener("mousedown", self.mouseDown);
  4649. container.removeEventListener("mousemove", self.mouseMoveBubble);
  4650. document.removeEventListener("mousemove", self.mouseMove);
  4651. document.removeEventListener("mouseup", self.mouseUp);
  4652. document.removeEventListener('mousewheel', self.mouseWheel);
  4653. document.removeEventListener('wheel', self.mouseWheel);
  4654. container.removeEventListener('scrollChildIntoView', self.scrollChildIntoView);
  4655. document.removeEventListener('resetScrollView', self.resetScrollView);
  4656. ionic.tap.removeClonedInputs(container, self);
  4657. delete self.__container;
  4658. delete self.__content;
  4659. delete self.__indicatorX;
  4660. delete self.__indicatorY;
  4661. delete self.options.el;
  4662. self.__callback = self.scrollChildIntoView = self.resetScrollView = NOOP;
  4663. self.mouseMove = self.mouseDown = self.mouseUp = self.mouseWheel =
  4664. self.touchStart = self.touchMove = self.touchEnd = self.touchCancel = NOOP;
  4665. self.resize = self.scrollTo = self.zoomTo =
  4666. self.__scrollingComplete = NOOP;
  4667. container = null;
  4668. },
  4669. /** Create a scroll bar div with the given direction **/
  4670. __createScrollbar: function(direction) {
  4671. var bar = document.createElement('div'),
  4672. indicator = document.createElement('div');
  4673. indicator.className = 'scroll-bar-indicator scroll-bar-fade-out';
  4674. if (direction == 'h') {
  4675. bar.className = 'scroll-bar scroll-bar-h';
  4676. } else {
  4677. bar.className = 'scroll-bar scroll-bar-v';
  4678. }
  4679. bar.appendChild(indicator);
  4680. return bar;
  4681. },
  4682. __createScrollbars: function() {
  4683. var self = this;
  4684. var indicatorX, indicatorY;
  4685. if (self.options.scrollingX) {
  4686. indicatorX = {
  4687. el: self.__createScrollbar('h'),
  4688. sizeRatio: 1
  4689. };
  4690. indicatorX.indicator = indicatorX.el.children[0];
  4691. if (self.options.scrollbarX) {
  4692. self.__container.appendChild(indicatorX.el);
  4693. }
  4694. self.__indicatorX = indicatorX;
  4695. }
  4696. if (self.options.scrollingY) {
  4697. indicatorY = {
  4698. el: self.__createScrollbar('v'),
  4699. sizeRatio: 1
  4700. };
  4701. indicatorY.indicator = indicatorY.el.children[0];
  4702. if (self.options.scrollbarY) {
  4703. self.__container.appendChild(indicatorY.el);
  4704. }
  4705. self.__indicatorY = indicatorY;
  4706. }
  4707. },
  4708. __resizeScrollbars: function() {
  4709. var self = this;
  4710. // Update horiz bar
  4711. if (self.__indicatorX) {
  4712. var width = Math.max(Math.round(self.__clientWidth * self.__clientWidth / (self.__contentWidth)), 20);
  4713. if (width > self.__contentWidth) {
  4714. width = 0;
  4715. }
  4716. if (width !== self.__indicatorX.size) {
  4717. ionic.requestAnimationFrame(function(){
  4718. self.__indicatorX.indicator.style.width = width + 'px';
  4719. });
  4720. }
  4721. self.__indicatorX.size = width;
  4722. self.__indicatorX.minScale = self.options.minScrollbarSizeX / width;
  4723. self.__indicatorX.maxPos = self.__clientWidth - width;
  4724. self.__indicatorX.sizeRatio = self.__maxScrollLeft ? self.__indicatorX.maxPos / self.__maxScrollLeft : 1;
  4725. }
  4726. // Update vert bar
  4727. if (self.__indicatorY) {
  4728. var height = Math.max(Math.round(self.__clientHeight * self.__clientHeight / (self.__contentHeight)), 20);
  4729. if (height > self.__contentHeight) {
  4730. height = 0;
  4731. }
  4732. if (height !== self.__indicatorY.size) {
  4733. ionic.requestAnimationFrame(function(){
  4734. self.__indicatorY && (self.__indicatorY.indicator.style.height = height + 'px');
  4735. });
  4736. }
  4737. self.__indicatorY.size = height;
  4738. self.__indicatorY.minScale = self.options.minScrollbarSizeY / height;
  4739. self.__indicatorY.maxPos = self.__clientHeight - height;
  4740. self.__indicatorY.sizeRatio = self.__maxScrollTop ? self.__indicatorY.maxPos / self.__maxScrollTop : 1;
  4741. }
  4742. },
  4743. /**
  4744. * Move and scale the scrollbars as the page scrolls.
  4745. */
  4746. __repositionScrollbars: function() {
  4747. var self = this,
  4748. heightScale, widthScale,
  4749. widthDiff, heightDiff,
  4750. x, y,
  4751. xstop = 0, ystop = 0;
  4752. if (self.__indicatorX) {
  4753. // Handle the X scrollbar
  4754. // Don't go all the way to the right if we have a vertical scrollbar as well
  4755. if (self.__indicatorY) xstop = 10;
  4756. x = Math.round(self.__indicatorX.sizeRatio * self.__scrollLeft) || 0;
  4757. // The the difference between the last content X position, and our overscrolled one
  4758. widthDiff = self.__scrollLeft - (self.__maxScrollLeft - xstop);
  4759. if (self.__scrollLeft < 0) {
  4760. widthScale = Math.max(self.__indicatorX.minScale,
  4761. (self.__indicatorX.size - Math.abs(self.__scrollLeft)) / self.__indicatorX.size);
  4762. // Stay at left
  4763. x = 0;
  4764. // Make sure scale is transformed from the left/center origin point
  4765. self.__indicatorX.indicator.style[self.__transformOriginProperty] = 'left center';
  4766. } else if (widthDiff > 0) {
  4767. widthScale = Math.max(self.__indicatorX.minScale,
  4768. (self.__indicatorX.size - widthDiff) / self.__indicatorX.size);
  4769. // Stay at the furthest x for the scrollable viewport
  4770. x = self.__indicatorX.maxPos - xstop;
  4771. // Make sure scale is transformed from the right/center origin point
  4772. self.__indicatorX.indicator.style[self.__transformOriginProperty] = 'right center';
  4773. } else {
  4774. // Normal motion
  4775. x = Math.min(self.__maxScrollLeft, Math.max(0, x));
  4776. widthScale = 1;
  4777. }
  4778. var translate3dX = 'translate3d(' + x + 'px, 0, 0) scaleX(' + widthScale + ')';
  4779. if (self.__indicatorX.transformProp !== translate3dX) {
  4780. self.__indicatorX.indicator.style[self.__transformProperty] = translate3dX;
  4781. self.__indicatorX.transformProp = translate3dX;
  4782. }
  4783. }
  4784. if (self.__indicatorY) {
  4785. y = Math.round(self.__indicatorY.sizeRatio * self.__scrollTop) || 0;
  4786. // Don't go all the way to the right if we have a vertical scrollbar as well
  4787. if (self.__indicatorX) ystop = 10;
  4788. heightDiff = self.__scrollTop - (self.__maxScrollTop - ystop);
  4789. if (self.__scrollTop < 0) {
  4790. heightScale = Math.max(self.__indicatorY.minScale, (self.__indicatorY.size - Math.abs(self.__scrollTop)) / self.__indicatorY.size);
  4791. // Stay at top
  4792. y = 0;
  4793. // Make sure scale is transformed from the center/top origin point
  4794. if (self.__indicatorY.originProp !== 'center top') {
  4795. self.__indicatorY.indicator.style[self.__transformOriginProperty] = 'center top';
  4796. self.__indicatorY.originProp = 'center top';
  4797. }
  4798. } else if (heightDiff > 0) {
  4799. heightScale = Math.max(self.__indicatorY.minScale, (self.__indicatorY.size - heightDiff) / self.__indicatorY.size);
  4800. // Stay at bottom of scrollable viewport
  4801. y = self.__indicatorY.maxPos - ystop;
  4802. // Make sure scale is transformed from the center/bottom origin point
  4803. if (self.__indicatorY.originProp !== 'center bottom') {
  4804. self.__indicatorY.indicator.style[self.__transformOriginProperty] = 'center bottom';
  4805. self.__indicatorY.originProp = 'center bottom';
  4806. }
  4807. } else {
  4808. // Normal motion
  4809. y = Math.min(self.__maxScrollTop, Math.max(0, y));
  4810. heightScale = 1;
  4811. }
  4812. var translate3dY = 'translate3d(0,' + y + 'px, 0) scaleY(' + heightScale + ')';
  4813. if (self.__indicatorY.transformProp !== translate3dY) {
  4814. self.__indicatorY.indicator.style[self.__transformProperty] = translate3dY;
  4815. self.__indicatorY.transformProp = translate3dY;
  4816. }
  4817. }
  4818. },
  4819. __fadeScrollbars: function(direction, delay) {
  4820. var self = this;
  4821. if (!self.options.scrollbarsFade) {
  4822. return;
  4823. }
  4824. var className = 'scroll-bar-fade-out';
  4825. if (self.options.scrollbarsFade === true) {
  4826. clearTimeout(self.__scrollbarFadeTimeout);
  4827. if (direction == 'in') {
  4828. if (self.__indicatorX) { self.__indicatorX.indicator.classList.remove(className); }
  4829. if (self.__indicatorY) { self.__indicatorY.indicator.classList.remove(className); }
  4830. } else {
  4831. self.__scrollbarFadeTimeout = setTimeout(function() {
  4832. if (self.__indicatorX) { self.__indicatorX.indicator.classList.add(className); }
  4833. if (self.__indicatorY) { self.__indicatorY.indicator.classList.add(className); }
  4834. }, delay || self.options.scrollbarFadeDelay);
  4835. }
  4836. }
  4837. },
  4838. __scrollingComplete: function() {
  4839. this.options.scrollingComplete();
  4840. ionic.tap.removeClonedInputs(this.__container, this);
  4841. this.__fadeScrollbars('out');
  4842. },
  4843. resize: function(continueScrolling) {
  4844. var self = this;
  4845. if (!self.__container || !self.options) return;
  4846. // Update Scroller dimensions for changed content
  4847. // Add padding to bottom of content
  4848. self.setDimensions(
  4849. self.__container.clientWidth,
  4850. self.__container.clientHeight,
  4851. self.options.getContentWidth(),
  4852. self.options.getContentHeight(),
  4853. continueScrolling
  4854. );
  4855. },
  4856. /*
  4857. ---------------------------------------------------------------------------
  4858. PUBLIC API
  4859. ---------------------------------------------------------------------------
  4860. */
  4861. getRenderFn: function() {
  4862. var self = this;
  4863. var content = self.__content;
  4864. var docStyle = document.documentElement.style;
  4865. var engine;
  4866. if ('MozAppearance' in docStyle) {
  4867. engine = 'gecko';
  4868. } else if ('WebkitAppearance' in docStyle) {
  4869. engine = 'webkit';
  4870. } else if (typeof navigator.cpuClass === 'string') {
  4871. engine = 'trident';
  4872. }
  4873. var vendorPrefix = {
  4874. trident: 'ms',
  4875. gecko: 'Moz',
  4876. webkit: 'Webkit',
  4877. presto: 'O'
  4878. }[engine];
  4879. var helperElem = document.createElement("div");
  4880. var undef;
  4881. var perspectiveProperty = vendorPrefix + "Perspective";
  4882. var transformProperty = vendorPrefix + "Transform";
  4883. var transformOriginProperty = vendorPrefix + 'TransformOrigin';
  4884. self.__perspectiveProperty = transformProperty;
  4885. self.__transformProperty = transformProperty;
  4886. self.__transformOriginProperty = transformOriginProperty;
  4887. if (helperElem.style[perspectiveProperty] !== undef) {
  4888. return function(left, top, zoom, wasResize) {
  4889. var translate3d = 'translate3d(' + (-left) + 'px,' + (-top) + 'px,0) scale(' + zoom + ')';
  4890. if (translate3d !== self.contentTransform) {
  4891. content.style[transformProperty] = translate3d;
  4892. self.contentTransform = translate3d;
  4893. }
  4894. self.__repositionScrollbars();
  4895. if (!wasResize) {
  4896. self.triggerScrollEvent();
  4897. }
  4898. };
  4899. } else if (helperElem.style[transformProperty] !== undef) {
  4900. return function(left, top, zoom, wasResize) {
  4901. content.style[transformProperty] = 'translate(' + (-left) + 'px,' + (-top) + 'px) scale(' + zoom + ')';
  4902. self.__repositionScrollbars();
  4903. if (!wasResize) {
  4904. self.triggerScrollEvent();
  4905. }
  4906. };
  4907. } else {
  4908. return function(left, top, zoom, wasResize) {
  4909. content.style.marginLeft = left ? (-left / zoom) + 'px' : '';
  4910. content.style.marginTop = top ? (-top / zoom) + 'px' : '';
  4911. content.style.zoom = zoom || '';
  4912. self.__repositionScrollbars();
  4913. if (!wasResize) {
  4914. self.triggerScrollEvent();
  4915. }
  4916. };
  4917. }
  4918. },
  4919. /**
  4920. * Configures the dimensions of the client (outer) and content (inner) elements.
  4921. * Requires the available space for the outer element and the outer size of the inner element.
  4922. * All values which are falsy (null or zero etc.) are ignored and the old value is kept.
  4923. *
  4924. * @param clientWidth {Integer} Inner width of outer element
  4925. * @param clientHeight {Integer} Inner height of outer element
  4926. * @param contentWidth {Integer} Outer width of inner element
  4927. * @param contentHeight {Integer} Outer height of inner element
  4928. */
  4929. setDimensions: function(clientWidth, clientHeight, contentWidth, contentHeight, continueScrolling) {
  4930. var self = this;
  4931. if (!clientWidth && !clientHeight && !contentWidth && !contentHeight) {
  4932. // this scrollview isn't rendered, don't bother
  4933. return;
  4934. }
  4935. // Only update values which are defined
  4936. if (clientWidth === +clientWidth) {
  4937. self.__clientWidth = clientWidth;
  4938. }
  4939. if (clientHeight === +clientHeight) {
  4940. self.__clientHeight = clientHeight;
  4941. }
  4942. if (contentWidth === +contentWidth) {
  4943. self.__contentWidth = contentWidth;
  4944. }
  4945. if (contentHeight === +contentHeight) {
  4946. self.__contentHeight = contentHeight;
  4947. }
  4948. // Refresh maximums
  4949. self.__computeScrollMax();
  4950. self.__resizeScrollbars();
  4951. // Refresh scroll position
  4952. if (!continueScrolling) {
  4953. self.scrollTo(self.__scrollLeft, self.__scrollTop, true, null, true);
  4954. }
  4955. },
  4956. /**
  4957. * Sets the client coordinates in relation to the document.
  4958. *
  4959. * @param left {Integer} Left position of outer element
  4960. * @param top {Integer} Top position of outer element
  4961. */
  4962. setPosition: function(left, top) {
  4963. this.__clientLeft = left || 0;
  4964. this.__clientTop = top || 0;
  4965. },
  4966. /**
  4967. * Configures the snapping (when snapping is active)
  4968. *
  4969. * @param width {Integer} Snapping width
  4970. * @param height {Integer} Snapping height
  4971. */
  4972. setSnapSize: function(width, height) {
  4973. this.__snapWidth = width;
  4974. this.__snapHeight = height;
  4975. },
  4976. /**
  4977. * Activates pull-to-refresh. A special zone on the top of the list to start a list refresh whenever
  4978. * the user event is released during visibility of this zone. This was introduced by some apps on iOS like
  4979. * the official Twitter client.
  4980. *
  4981. * @param height {Integer} Height of pull-to-refresh zone on top of rendered list
  4982. * @param activateCallback {Function} Callback to execute on activation. This is for signalling the user about a refresh is about to happen when he release.
  4983. * @param deactivateCallback {Function} Callback to execute on deactivation. This is for signalling the user about the refresh being cancelled.
  4984. * @param startCallback {Function} Callback to execute to start the real async refresh action. Call {@link #finishPullToRefresh} after finish of refresh.
  4985. * @param showCallback {Function} Callback to execute when the refresher should be shown. This is for showing the refresher during a negative scrollTop.
  4986. * @param hideCallback {Function} Callback to execute when the refresher should be hidden. This is for hiding the refresher when it's behind the nav bar.
  4987. * @param tailCallback {Function} Callback to execute just before the refresher returns to it's original state. This is for zooming out the refresher.
  4988. * @param pullProgressCallback Callback to state the progress while pulling to refresh
  4989. */
  4990. activatePullToRefresh: function(height, refresherMethods) {
  4991. var self = this;
  4992. self.__refreshHeight = height;
  4993. self.__refreshActivate = function() { ionic.requestAnimationFrame(refresherMethods.activate); };
  4994. self.__refreshDeactivate = function() { ionic.requestAnimationFrame(refresherMethods.deactivate); };
  4995. self.__refreshStart = function() { ionic.requestAnimationFrame(refresherMethods.start); };
  4996. self.__refreshShow = function() { ionic.requestAnimationFrame(refresherMethods.show); };
  4997. self.__refreshHide = function() { ionic.requestAnimationFrame(refresherMethods.hide); };
  4998. self.__refreshTail = function() { ionic.requestAnimationFrame(refresherMethods.tail); };
  4999. self.__refreshTailTime = 100;
  5000. self.__minSpinTime = 600;
  5001. },
  5002. /**
  5003. * Starts pull-to-refresh manually.
  5004. */
  5005. triggerPullToRefresh: function() {
  5006. // Use publish instead of scrollTo to allow scrolling to out of boundary position
  5007. // We don't need to normalize scrollLeft, zoomLevel, etc. here because we only y-scrolling when pull-to-refresh is enabled
  5008. this.__publish(this.__scrollLeft, -this.__refreshHeight, this.__zoomLevel, true);
  5009. var d = new Date();
  5010. this.refreshStartTime = d.getTime();
  5011. if (this.__refreshStart) {
  5012. this.__refreshStart();
  5013. }
  5014. },
  5015. /**
  5016. * Signalizes that pull-to-refresh is finished.
  5017. */
  5018. finishPullToRefresh: function() {
  5019. var self = this;
  5020. // delay to make sure the spinner has a chance to spin for a split second before it's dismissed
  5021. var d = new Date();
  5022. var delay = 0;
  5023. if (self.refreshStartTime + self.__minSpinTime > d.getTime()) {
  5024. delay = self.refreshStartTime + self.__minSpinTime - d.getTime();
  5025. }
  5026. setTimeout(function() {
  5027. if (self.__refreshTail) {
  5028. self.__refreshTail();
  5029. }
  5030. setTimeout(function() {
  5031. self.__refreshActive = false;
  5032. if (self.__refreshDeactivate) {
  5033. self.__refreshDeactivate();
  5034. }
  5035. if (self.__refreshHide) {
  5036. self.__refreshHide();
  5037. }
  5038. self.scrollTo(self.__scrollLeft, self.__scrollTop, true);
  5039. }, self.__refreshTailTime);
  5040. }, delay);
  5041. },
  5042. /**
  5043. * Returns the scroll position and zooming values
  5044. *
  5045. * @return {Map} `left` and `top` scroll position and `zoom` level
  5046. */
  5047. getValues: function() {
  5048. return {
  5049. left: this.__scrollLeft,
  5050. top: this.__scrollTop,
  5051. zoom: this.__zoomLevel
  5052. };
  5053. },
  5054. /**
  5055. * Returns the maximum scroll values
  5056. *
  5057. * @return {Map} `left` and `top` maximum scroll values
  5058. */
  5059. getScrollMax: function() {
  5060. return {
  5061. left: this.__maxScrollLeft,
  5062. top: this.__maxScrollTop
  5063. };
  5064. },
  5065. /**
  5066. * Zooms to the given level. Supports optional animation. Zooms
  5067. * the center when no coordinates are given.
  5068. *
  5069. * @param level {Number} Level to zoom to
  5070. * @param animate {Boolean} Whether to use animation
  5071. * @param originLeft {Number} Zoom in at given left coordinate
  5072. * @param originTop {Number} Zoom in at given top coordinate
  5073. */
  5074. zoomTo: function(level, animate, originLeft, originTop) {
  5075. var self = this;
  5076. if (!self.options.zooming) {
  5077. throw new Error("Zooming is not enabled!");
  5078. }
  5079. // Stop deceleration
  5080. if (self.__isDecelerating) {
  5081. zyngaCore.effect.Animate.stop(self.__isDecelerating);
  5082. self.__isDecelerating = false;
  5083. }
  5084. var oldLevel = self.__zoomLevel;
  5085. // Normalize input origin to center of viewport if not defined
  5086. if (originLeft == null) {
  5087. originLeft = self.__clientWidth / 2;
  5088. }
  5089. if (originTop == null) {
  5090. originTop = self.__clientHeight / 2;
  5091. }
  5092. // Limit level according to configuration
  5093. level = Math.max(Math.min(level, self.options.maxZoom), self.options.minZoom);
  5094. // Recompute maximum values while temporary tweaking maximum scroll ranges
  5095. self.__computeScrollMax(level);
  5096. // Recompute left and top coordinates based on new zoom level
  5097. var left = ((originLeft + self.__scrollLeft) * level / oldLevel) - originLeft;
  5098. var top = ((originTop + self.__scrollTop) * level / oldLevel) - originTop;
  5099. // Limit x-axis
  5100. if (left > self.__maxScrollLeft) {
  5101. left = self.__maxScrollLeft;
  5102. } else if (left < 0) {
  5103. left = 0;
  5104. }
  5105. // Limit y-axis
  5106. if (top > self.__maxScrollTop) {
  5107. top = self.__maxScrollTop;
  5108. } else if (top < 0) {
  5109. top = 0;
  5110. }
  5111. // Push values out
  5112. self.__publish(left, top, level, animate);
  5113. },
  5114. /**
  5115. * Zooms the content by the given factor.
  5116. *
  5117. * @param factor {Number} Zoom by given factor
  5118. * @param animate {Boolean} Whether to use animation
  5119. * @param originLeft {Number} Zoom in at given left coordinate
  5120. * @param originTop {Number} Zoom in at given top coordinate
  5121. */
  5122. zoomBy: function(factor, animate, originLeft, originTop) {
  5123. this.zoomTo(this.__zoomLevel * factor, animate, originLeft, originTop);
  5124. },
  5125. /**
  5126. * Scrolls to the given position. Respect limitations and snapping automatically.
  5127. *
  5128. * @param left {Number} Horizontal scroll position, keeps current if value is <code>null</code>
  5129. * @param top {Number} Vertical scroll position, keeps current if value is <code>null</code>
  5130. * @param animate {Boolean} Whether the scrolling should happen using an animation
  5131. * @param zoom {Number} Zoom level to go to
  5132. */
  5133. scrollTo: function(left, top, animate, zoom, wasResize) {
  5134. var self = this;
  5135. // Stop deceleration
  5136. if (self.__isDecelerating) {
  5137. zyngaCore.effect.Animate.stop(self.__isDecelerating);
  5138. self.__isDecelerating = false;
  5139. }
  5140. // Correct coordinates based on new zoom level
  5141. if (zoom != null && zoom !== self.__zoomLevel) {
  5142. if (!self.options.zooming) {
  5143. throw new Error("Zooming is not enabled!");
  5144. }
  5145. left *= zoom;
  5146. top *= zoom;
  5147. // Recompute maximum values while temporary tweaking maximum scroll ranges
  5148. self.__computeScrollMax(zoom);
  5149. } else {
  5150. // Keep zoom when not defined
  5151. zoom = self.__zoomLevel;
  5152. }
  5153. if (!self.options.scrollingX) {
  5154. left = self.__scrollLeft;
  5155. } else {
  5156. if (self.options.paging) {
  5157. left = Math.round(left / self.__clientWidth) * self.__clientWidth;
  5158. } else if (self.options.snapping) {
  5159. left = Math.round(left / self.__snapWidth) * self.__snapWidth;
  5160. }
  5161. }
  5162. if (!self.options.scrollingY) {
  5163. top = self.__scrollTop;
  5164. } else {
  5165. if (self.options.paging) {
  5166. top = Math.round(top / self.__clientHeight) * self.__clientHeight;
  5167. } else if (self.options.snapping) {
  5168. top = Math.round(top / self.__snapHeight) * self.__snapHeight;
  5169. }
  5170. }
  5171. // Limit for allowed ranges
  5172. left = Math.max(Math.min(self.__maxScrollLeft, left), 0);
  5173. top = Math.max(Math.min(self.__maxScrollTop, top), 0);
  5174. // Don't animate when no change detected, still call publish to make sure
  5175. // that rendered position is really in-sync with internal data
  5176. if (left === self.__scrollLeft && top === self.__scrollTop) {
  5177. animate = false;
  5178. }
  5179. // Publish new values
  5180. self.__publish(left, top, zoom, animate, wasResize);
  5181. },
  5182. /**
  5183. * Scroll by the given offset
  5184. *
  5185. * @param left {Number} Scroll x-axis by given offset
  5186. * @param top {Number} Scroll y-axis by given offset
  5187. * @param animate {Boolean} Whether to animate the given change
  5188. */
  5189. scrollBy: function(left, top, animate) {
  5190. var self = this;
  5191. var startLeft = self.__isAnimating ? self.__scheduledLeft : self.__scrollLeft;
  5192. var startTop = self.__isAnimating ? self.__scheduledTop : self.__scrollTop;
  5193. self.scrollTo(startLeft + (left || 0), startTop + (top || 0), animate);
  5194. },
  5195. /*
  5196. ---------------------------------------------------------------------------
  5197. EVENT CALLBACKS
  5198. ---------------------------------------------------------------------------
  5199. */
  5200. /**
  5201. * Mouse wheel handler for zooming support
  5202. */
  5203. doMouseZoom: function(wheelDelta, timeStamp, pageX, pageY) {
  5204. var change = wheelDelta > 0 ? 0.97 : 1.03;
  5205. return this.zoomTo(this.__zoomLevel * change, false, pageX - this.__clientLeft, pageY - this.__clientTop);
  5206. },
  5207. /**
  5208. * Touch start handler for scrolling support
  5209. */
  5210. doTouchStart: function(touches, timeStamp) {
  5211. var self = this;
  5212. // remember if the deceleration was just stopped
  5213. self.__decStopped = !!(self.__isDecelerating || self.__isAnimating);
  5214. self.hintResize();
  5215. if (timeStamp instanceof Date) {
  5216. timeStamp = timeStamp.valueOf();
  5217. }
  5218. if (typeof timeStamp !== "number") {
  5219. timeStamp = Date.now();
  5220. }
  5221. // Reset interruptedAnimation flag
  5222. self.__interruptedAnimation = true;
  5223. // Stop deceleration
  5224. if (self.__isDecelerating) {
  5225. zyngaCore.effect.Animate.stop(self.__isDecelerating);
  5226. self.__isDecelerating = false;
  5227. self.__interruptedAnimation = true;
  5228. }
  5229. // Stop animation
  5230. if (self.__isAnimating) {
  5231. zyngaCore.effect.Animate.stop(self.__isAnimating);
  5232. self.__isAnimating = false;
  5233. self.__interruptedAnimation = true;
  5234. }
  5235. // Use center point when dealing with two fingers
  5236. var currentTouchLeft, currentTouchTop;
  5237. var isSingleTouch = touches.length === 1;
  5238. if (isSingleTouch) {
  5239. currentTouchLeft = touches[0].pageX;
  5240. currentTouchTop = touches[0].pageY;
  5241. } else {
  5242. currentTouchLeft = Math.abs(touches[0].pageX + touches[1].pageX) / 2;
  5243. currentTouchTop = Math.abs(touches[0].pageY + touches[1].pageY) / 2;
  5244. }
  5245. // Store initial positions
  5246. self.__initialTouchLeft = currentTouchLeft;
  5247. self.__initialTouchTop = currentTouchTop;
  5248. // Store initial touchList for scale calculation
  5249. self.__initialTouches = touches;
  5250. // Store current zoom level
  5251. self.__zoomLevelStart = self.__zoomLevel;
  5252. // Store initial touch positions
  5253. self.__lastTouchLeft = currentTouchLeft;
  5254. self.__lastTouchTop = currentTouchTop;
  5255. // Store initial move time stamp
  5256. self.__lastTouchMove = timeStamp;
  5257. // Reset initial scale
  5258. self.__lastScale = 1;
  5259. // Reset locking flags
  5260. self.__enableScrollX = !isSingleTouch && self.options.scrollingX;
  5261. self.__enableScrollY = !isSingleTouch && self.options.scrollingY;
  5262. // Reset tracking flag
  5263. self.__isTracking = true;
  5264. // Reset deceleration complete flag
  5265. self.__didDecelerationComplete = false;
  5266. // Dragging starts directly with two fingers, otherwise lazy with an offset
  5267. self.__isDragging = !isSingleTouch;
  5268. // Some features are disabled in multi touch scenarios
  5269. self.__isSingleTouch = isSingleTouch;
  5270. // Clearing data structure
  5271. self.__positions = [];
  5272. },
  5273. /**
  5274. * Touch move handler for scrolling support
  5275. */
  5276. doTouchMove: function(touches, timeStamp, scale) {
  5277. if (timeStamp instanceof Date) {
  5278. timeStamp = timeStamp.valueOf();
  5279. }
  5280. if (typeof timeStamp !== "number") {
  5281. timeStamp = Date.now();
  5282. }
  5283. var self = this;
  5284. // Ignore event when tracking is not enabled (event might be outside of element)
  5285. if (!self.__isTracking) {
  5286. return;
  5287. }
  5288. var currentTouchLeft, currentTouchTop;
  5289. // Compute move based around of center of fingers
  5290. if (touches.length === 2) {
  5291. currentTouchLeft = Math.abs(touches[0].pageX + touches[1].pageX) / 2;
  5292. currentTouchTop = Math.abs(touches[0].pageY + touches[1].pageY) / 2;
  5293. // Calculate scale when not present and only when touches are used
  5294. if (!scale && self.options.zooming) {
  5295. scale = self.__getScale(self.__initialTouches, touches);
  5296. }
  5297. } else {
  5298. currentTouchLeft = touches[0].pageX;
  5299. currentTouchTop = touches[0].pageY;
  5300. }
  5301. var positions = self.__positions;
  5302. // Are we already is dragging mode?
  5303. if (self.__isDragging) {
  5304. self.__decStopped = false;
  5305. // Compute move distance
  5306. var moveX = currentTouchLeft - self.__lastTouchLeft;
  5307. var moveY = currentTouchTop - self.__lastTouchTop;
  5308. // Read previous scroll position and zooming
  5309. var scrollLeft = self.__scrollLeft;
  5310. var scrollTop = self.__scrollTop;
  5311. var level = self.__zoomLevel;
  5312. // Work with scaling
  5313. if (scale != null && self.options.zooming) {
  5314. var oldLevel = level;
  5315. // Recompute level based on previous scale and new scale
  5316. level = level / self.__lastScale * scale;
  5317. // Limit level according to configuration
  5318. level = Math.max(Math.min(level, self.options.maxZoom), self.options.minZoom);
  5319. // Only do further compution when change happened
  5320. if (oldLevel !== level) {
  5321. // Compute relative event position to container
  5322. var currentTouchLeftRel = currentTouchLeft - self.__clientLeft;
  5323. var currentTouchTopRel = currentTouchTop - self.__clientTop;
  5324. // Recompute left and top coordinates based on new zoom level
  5325. scrollLeft = ((currentTouchLeftRel + scrollLeft) * level / oldLevel) - currentTouchLeftRel;
  5326. scrollTop = ((currentTouchTopRel + scrollTop) * level / oldLevel) - currentTouchTopRel;
  5327. // Recompute max scroll values
  5328. self.__computeScrollMax(level);
  5329. }
  5330. }
  5331. if (self.__enableScrollX) {
  5332. scrollLeft -= moveX * self.options.speedMultiplier;
  5333. var maxScrollLeft = self.__maxScrollLeft;
  5334. if (scrollLeft > maxScrollLeft || scrollLeft < 0) {
  5335. // Slow down on the edges
  5336. if (self.options.bouncing) {
  5337. scrollLeft += (moveX / 2 * self.options.speedMultiplier);
  5338. } else if (scrollLeft > maxScrollLeft) {
  5339. scrollLeft = maxScrollLeft;
  5340. } else {
  5341. scrollLeft = 0;
  5342. }
  5343. }
  5344. }
  5345. // Compute new vertical scroll position
  5346. if (self.__enableScrollY) {
  5347. scrollTop -= moveY * self.options.speedMultiplier;
  5348. var maxScrollTop = self.__maxScrollTop;
  5349. if (scrollTop > maxScrollTop || scrollTop < 0) {
  5350. // Slow down on the edges
  5351. if (self.options.bouncing || (self.__refreshHeight && scrollTop < 0)) {
  5352. scrollTop += (moveY / 2 * self.options.speedMultiplier);
  5353. // Support pull-to-refresh (only when only y is scrollable)
  5354. if (!self.__enableScrollX && self.__refreshHeight != null) {
  5355. // hide the refresher when it's behind the header bar in case of header transparency
  5356. if (scrollTop < 0) {
  5357. self.__refreshHidden = false;
  5358. self.__refreshShow();
  5359. } else {
  5360. self.__refreshHide();
  5361. self.__refreshHidden = true;
  5362. }
  5363. if (!self.__refreshActive && scrollTop <= -self.__refreshHeight) {
  5364. self.__refreshActive = true;
  5365. if (self.__refreshActivate) {
  5366. self.__refreshActivate();
  5367. }
  5368. } else if (self.__refreshActive && scrollTop > -self.__refreshHeight) {
  5369. self.__refreshActive = false;
  5370. if (self.__refreshDeactivate) {
  5371. self.__refreshDeactivate();
  5372. }
  5373. }
  5374. }
  5375. } else if (scrollTop > maxScrollTop) {
  5376. scrollTop = maxScrollTop;
  5377. } else {
  5378. scrollTop = 0;
  5379. }
  5380. } else if (self.__refreshHeight && !self.__refreshHidden) {
  5381. // if a positive scroll value and the refresher is still not hidden, hide it
  5382. self.__refreshHide();
  5383. self.__refreshHidden = true;
  5384. }
  5385. }
  5386. // Keep list from growing infinitely (holding min 10, max 20 measure points)
  5387. if (positions.length > 60) {
  5388. positions.splice(0, 30);
  5389. }
  5390. // Track scroll movement for decleration
  5391. positions.push(scrollLeft, scrollTop, timeStamp);
  5392. // Sync scroll position
  5393. self.__publish(scrollLeft, scrollTop, level);
  5394. // Otherwise figure out whether we are switching into dragging mode now.
  5395. } else {
  5396. var minimumTrackingForScroll = self.options.locking ? 3 : 0;
  5397. var minimumTrackingForDrag = 5;
  5398. var distanceX = Math.abs(currentTouchLeft - self.__initialTouchLeft);
  5399. var distanceY = Math.abs(currentTouchTop - self.__initialTouchTop);
  5400. self.__enableScrollX = self.options.scrollingX && distanceX >= minimumTrackingForScroll;
  5401. self.__enableScrollY = self.options.scrollingY && distanceY >= minimumTrackingForScroll;
  5402. positions.push(self.__scrollLeft, self.__scrollTop, timeStamp);
  5403. self.__isDragging = (self.__enableScrollX || self.__enableScrollY) && (distanceX >= minimumTrackingForDrag || distanceY >= minimumTrackingForDrag);
  5404. if (self.__isDragging) {
  5405. self.__interruptedAnimation = false;
  5406. self.__fadeScrollbars('in');
  5407. }
  5408. }
  5409. // Update last touch positions and time stamp for next event
  5410. self.__lastTouchLeft = currentTouchLeft;
  5411. self.__lastTouchTop = currentTouchTop;
  5412. self.__lastTouchMove = timeStamp;
  5413. self.__lastScale = scale;
  5414. },
  5415. /**
  5416. * Touch end handler for scrolling support
  5417. */
  5418. doTouchEnd: function(e, timeStamp) {
  5419. if (timeStamp instanceof Date) {
  5420. timeStamp = timeStamp.valueOf();
  5421. }
  5422. if (typeof timeStamp !== "number") {
  5423. timeStamp = Date.now();
  5424. }
  5425. var self = this;
  5426. // Ignore event when tracking is not enabled (no touchstart event on element)
  5427. // This is required as this listener ('touchmove') sits on the document and not on the element itself.
  5428. if (!self.__isTracking) {
  5429. return;
  5430. }
  5431. // Not touching anymore (when two finger hit the screen there are two touch end events)
  5432. self.__isTracking = false;
  5433. // Be sure to reset the dragging flag now. Here we also detect whether
  5434. // the finger has moved fast enough to switch into a deceleration animation.
  5435. if (self.__isDragging) {
  5436. // Reset dragging flag
  5437. self.__isDragging = false;
  5438. // Start deceleration
  5439. // Verify that the last move detected was in some relevant time frame
  5440. if (self.__isSingleTouch && self.options.animating && (timeStamp - self.__lastTouchMove) <= 100) {
  5441. // Then figure out what the scroll position was about 100ms ago
  5442. var positions = self.__positions;
  5443. var endPos = positions.length - 1;
  5444. var startPos = endPos;
  5445. // Move pointer to position measured 100ms ago
  5446. for (var i = endPos; i > 0 && positions[i] > (self.__lastTouchMove - 100); i -= 3) {
  5447. startPos = i;
  5448. }
  5449. // If start and stop position is identical in a 100ms timeframe,
  5450. // we cannot compute any useful deceleration.
  5451. if (startPos !== endPos) {
  5452. // Compute relative movement between these two points
  5453. var timeOffset = positions[endPos] - positions[startPos];
  5454. var movedLeft = self.__scrollLeft - positions[startPos - 2];
  5455. var movedTop = self.__scrollTop - positions[startPos - 1];
  5456. // Based on 50ms compute the movement to apply for each render step
  5457. self.__decelerationVelocityX = movedLeft / timeOffset * (1000 / 60);
  5458. self.__decelerationVelocityY = movedTop / timeOffset * (1000 / 60);
  5459. // How much velocity is required to start the deceleration
  5460. var minVelocityToStartDeceleration = self.options.paging || self.options.snapping ? self.options.decelVelocityThresholdPaging : self.options.decelVelocityThreshold;
  5461. // Verify that we have enough velocity to start deceleration
  5462. if (Math.abs(self.__decelerationVelocityX) > minVelocityToStartDeceleration || Math.abs(self.__decelerationVelocityY) > minVelocityToStartDeceleration) {
  5463. // Deactivate pull-to-refresh when decelerating
  5464. if (!self.__refreshActive) {
  5465. self.__startDeceleration(timeStamp);
  5466. }
  5467. }
  5468. } else {
  5469. self.__scrollingComplete();
  5470. }
  5471. } else if ((timeStamp - self.__lastTouchMove) > 100) {
  5472. self.__scrollingComplete();
  5473. }
  5474. } else if (self.__decStopped) {
  5475. // the deceleration was stopped
  5476. // user flicked the scroll fast, and stop dragging, then did a touchstart to stop the srolling
  5477. // tell the touchend event code to do nothing, we don't want to actually send a click
  5478. e.isTapHandled = true;
  5479. self.__decStopped = false;
  5480. }
  5481. // If this was a slower move it is per default non decelerated, but this
  5482. // still means that we want snap back to the bounds which is done here.
  5483. // This is placed outside the condition above to improve edge case stability
  5484. // e.g. touchend fired without enabled dragging. This should normally do not
  5485. // have modified the scroll positions or even showed the scrollbars though.
  5486. if (!self.__isDecelerating) {
  5487. if (self.__refreshActive && self.__refreshStart) {
  5488. // Use publish instead of scrollTo to allow scrolling to out of boundary position
  5489. // We don't need to normalize scrollLeft, zoomLevel, etc. here because we only y-scrolling when pull-to-refresh is enabled
  5490. self.__publish(self.__scrollLeft, -self.__refreshHeight, self.__zoomLevel, true);
  5491. var d = new Date();
  5492. self.refreshStartTime = d.getTime();
  5493. if (self.__refreshStart) {
  5494. self.__refreshStart();
  5495. }
  5496. // for iOS-ey style scrolling
  5497. if (!ionic.Platform.isAndroid())self.__startDeceleration();
  5498. } else {
  5499. if (self.__interruptedAnimation || self.__isDragging) {
  5500. self.__scrollingComplete();
  5501. }
  5502. self.scrollTo(self.__scrollLeft, self.__scrollTop, true, self.__zoomLevel);
  5503. // Directly signalize deactivation (nothing todo on refresh?)
  5504. if (self.__refreshActive) {
  5505. self.__refreshActive = false;
  5506. if (self.__refreshDeactivate) {
  5507. self.__refreshDeactivate();
  5508. }
  5509. }
  5510. }
  5511. }
  5512. // Fully cleanup list
  5513. self.__positions.length = 0;
  5514. },
  5515. /*
  5516. ---------------------------------------------------------------------------
  5517. PRIVATE API
  5518. ---------------------------------------------------------------------------
  5519. */
  5520. /**
  5521. * Applies the scroll position to the content element
  5522. *
  5523. * @param left {Number} Left scroll position
  5524. * @param top {Number} Top scroll position
  5525. * @param animate {Boolean} Whether animation should be used to move to the new coordinates
  5526. */
  5527. __publish: function(left, top, zoom, animate, wasResize) {
  5528. var self = this;
  5529. // Remember whether we had an animation, then we try to continue based on the current "drive" of the animation
  5530. var wasAnimating = self.__isAnimating;
  5531. if (wasAnimating) {
  5532. zyngaCore.effect.Animate.stop(wasAnimating);
  5533. self.__isAnimating = false;
  5534. }
  5535. if (animate && self.options.animating) {
  5536. // Keep scheduled positions for scrollBy/zoomBy functionality
  5537. self.__scheduledLeft = left;
  5538. self.__scheduledTop = top;
  5539. self.__scheduledZoom = zoom;
  5540. var oldLeft = self.__scrollLeft;
  5541. var oldTop = self.__scrollTop;
  5542. var oldZoom = self.__zoomLevel;
  5543. var diffLeft = left - oldLeft;
  5544. var diffTop = top - oldTop;
  5545. var diffZoom = zoom - oldZoom;
  5546. var step = function(percent, now, render) {
  5547. if (render) {
  5548. self.__scrollLeft = oldLeft + (diffLeft * percent);
  5549. self.__scrollTop = oldTop + (diffTop * percent);
  5550. self.__zoomLevel = oldZoom + (diffZoom * percent);
  5551. // Push values out
  5552. if (self.__callback) {
  5553. self.__callback(self.__scrollLeft, self.__scrollTop, self.__zoomLevel, wasResize);
  5554. }
  5555. }
  5556. };
  5557. var verify = function(id) {
  5558. return self.__isAnimating === id;
  5559. };
  5560. var completed = function(renderedFramesPerSecond, animationId, wasFinished) {
  5561. if (animationId === self.__isAnimating) {
  5562. self.__isAnimating = false;
  5563. }
  5564. if (self.__didDecelerationComplete || wasFinished) {
  5565. self.__scrollingComplete();
  5566. }
  5567. if (self.options.zooming) {
  5568. self.__computeScrollMax();
  5569. }
  5570. };
  5571. // When continuing based on previous animation we choose an ease-out animation instead of ease-in-out
  5572. self.__isAnimating = zyngaCore.effect.Animate.start(step, verify, completed, self.options.animationDuration, wasAnimating ? easeOutCubic : easeInOutCubic);
  5573. } else {
  5574. self.__scheduledLeft = self.__scrollLeft = left;
  5575. self.__scheduledTop = self.__scrollTop = top;
  5576. self.__scheduledZoom = self.__zoomLevel = zoom;
  5577. // Push values out
  5578. if (self.__callback) {
  5579. self.__callback(left, top, zoom, wasResize);
  5580. }
  5581. // Fix max scroll ranges
  5582. if (self.options.zooming) {
  5583. self.__computeScrollMax();
  5584. }
  5585. }
  5586. },
  5587. /**
  5588. * Recomputes scroll minimum values based on client dimensions and content dimensions.
  5589. */
  5590. __computeScrollMax: function(zoomLevel) {
  5591. var self = this;
  5592. if (zoomLevel == null) {
  5593. zoomLevel = self.__zoomLevel;
  5594. }
  5595. self.__maxScrollLeft = Math.max((self.__contentWidth * zoomLevel) - self.__clientWidth, 0);
  5596. self.__maxScrollTop = Math.max((self.__contentHeight * zoomLevel) - self.__clientHeight, 0);
  5597. if (!self.__didWaitForSize && !self.__maxScrollLeft && !self.__maxScrollTop) {
  5598. self.__didWaitForSize = true;
  5599. self.__waitForSize();
  5600. }
  5601. },
  5602. /**
  5603. * If the scroll view isn't sized correctly on start, wait until we have at least some size
  5604. */
  5605. __waitForSize: function() {
  5606. var self = this;
  5607. clearTimeout(self.__sizerTimeout);
  5608. var sizer = function() {
  5609. self.resize(true);
  5610. };
  5611. sizer();
  5612. self.__sizerTimeout = setTimeout(sizer, 500);
  5613. },
  5614. /*
  5615. ---------------------------------------------------------------------------
  5616. ANIMATION (DECELERATION) SUPPORT
  5617. ---------------------------------------------------------------------------
  5618. */
  5619. /**
  5620. * Called when a touch sequence end and the speed of the finger was high enough
  5621. * to switch into deceleration mode.
  5622. */
  5623. __startDeceleration: function() {
  5624. var self = this;
  5625. if (self.options.paging) {
  5626. var scrollLeft = Math.max(Math.min(self.__scrollLeft, self.__maxScrollLeft), 0);
  5627. var scrollTop = Math.max(Math.min(self.__scrollTop, self.__maxScrollTop), 0);
  5628. var clientWidth = self.__clientWidth;
  5629. var clientHeight = self.__clientHeight;
  5630. // We limit deceleration not to the min/max values of the allowed range, but to the size of the visible client area.
  5631. // Each page should have exactly the size of the client area.
  5632. self.__minDecelerationScrollLeft = Math.floor(scrollLeft / clientWidth) * clientWidth;
  5633. self.__minDecelerationScrollTop = Math.floor(scrollTop / clientHeight) * clientHeight;
  5634. self.__maxDecelerationScrollLeft = Math.ceil(scrollLeft / clientWidth) * clientWidth;
  5635. self.__maxDecelerationScrollTop = Math.ceil(scrollTop / clientHeight) * clientHeight;
  5636. } else {
  5637. self.__minDecelerationScrollLeft = 0;
  5638. self.__minDecelerationScrollTop = 0;
  5639. self.__maxDecelerationScrollLeft = self.__maxScrollLeft;
  5640. self.__maxDecelerationScrollTop = self.__maxScrollTop;
  5641. if (self.__refreshActive) self.__minDecelerationScrollTop = self.__refreshHeight * -1;
  5642. }
  5643. // Wrap class method
  5644. var step = function(percent, now, render) {
  5645. self.__stepThroughDeceleration(render);
  5646. };
  5647. // How much velocity is required to keep the deceleration running
  5648. self.__minVelocityToKeepDecelerating = self.options.snapping ? 4 : 0.1;
  5649. // Detect whether it's still worth to continue animating steps
  5650. // If we are already slow enough to not being user perceivable anymore, we stop the whole process here.
  5651. var verify = function() {
  5652. var shouldContinue = Math.abs(self.__decelerationVelocityX) >= self.__minVelocityToKeepDecelerating ||
  5653. Math.abs(self.__decelerationVelocityY) >= self.__minVelocityToKeepDecelerating;
  5654. if (!shouldContinue) {
  5655. self.__didDecelerationComplete = true;
  5656. //Make sure the scroll values are within the boundaries after a bounce,
  5657. //not below 0 or above maximum
  5658. if (self.options.bouncing && !self.__refreshActive) {
  5659. self.scrollTo(
  5660. Math.min( Math.max(self.__scrollLeft, 0), self.__maxScrollLeft ),
  5661. Math.min( Math.max(self.__scrollTop, 0), self.__maxScrollTop ),
  5662. self.__refreshActive
  5663. );
  5664. }
  5665. }
  5666. return shouldContinue;
  5667. };
  5668. var completed = function() {
  5669. self.__isDecelerating = false;
  5670. if (self.__didDecelerationComplete) {
  5671. self.__scrollingComplete();
  5672. }
  5673. // Animate to grid when snapping is active, otherwise just fix out-of-boundary positions
  5674. if (self.options.paging) {
  5675. self.scrollTo(self.__scrollLeft, self.__scrollTop, self.options.snapping);
  5676. }
  5677. };
  5678. // Start animation and switch on flag
  5679. self.__isDecelerating = zyngaCore.effect.Animate.start(step, verify, completed);
  5680. },
  5681. /**
  5682. * Called on every step of the animation
  5683. *
  5684. * @param inMemory {Boolean} Whether to not render the current step, but keep it in memory only. Used internally only!
  5685. */
  5686. __stepThroughDeceleration: function(render) {
  5687. var self = this;
  5688. //
  5689. // COMPUTE NEXT SCROLL POSITION
  5690. //
  5691. // Add deceleration to scroll position
  5692. var scrollLeft = self.__scrollLeft + self.__decelerationVelocityX;// * self.options.deceleration);
  5693. var scrollTop = self.__scrollTop + self.__decelerationVelocityY;// * self.options.deceleration);
  5694. //
  5695. // HARD LIMIT SCROLL POSITION FOR NON BOUNCING MODE
  5696. //
  5697. if (!self.options.bouncing) {
  5698. var scrollLeftFixed = Math.max(Math.min(self.__maxDecelerationScrollLeft, scrollLeft), self.__minDecelerationScrollLeft);
  5699. if (scrollLeftFixed !== scrollLeft) {
  5700. scrollLeft = scrollLeftFixed;
  5701. self.__decelerationVelocityX = 0;
  5702. }
  5703. var scrollTopFixed = Math.max(Math.min(self.__maxDecelerationScrollTop, scrollTop), self.__minDecelerationScrollTop);
  5704. if (scrollTopFixed !== scrollTop) {
  5705. scrollTop = scrollTopFixed;
  5706. self.__decelerationVelocityY = 0;
  5707. }
  5708. }
  5709. //
  5710. // UPDATE SCROLL POSITION
  5711. //
  5712. if (render) {
  5713. self.__publish(scrollLeft, scrollTop, self.__zoomLevel);
  5714. } else {
  5715. self.__scrollLeft = scrollLeft;
  5716. self.__scrollTop = scrollTop;
  5717. }
  5718. //
  5719. // SLOW DOWN
  5720. //
  5721. // Slow down velocity on every iteration
  5722. if (!self.options.paging) {
  5723. // This is the factor applied to every iteration of the animation
  5724. // to slow down the process. This should emulate natural behavior where
  5725. // objects slow down when the initiator of the movement is removed
  5726. var frictionFactor = self.options.deceleration;
  5727. self.__decelerationVelocityX *= frictionFactor;
  5728. self.__decelerationVelocityY *= frictionFactor;
  5729. }
  5730. //
  5731. // BOUNCING SUPPORT
  5732. //
  5733. if (self.options.bouncing) {
  5734. var scrollOutsideX = 0;
  5735. var scrollOutsideY = 0;
  5736. // This configures the amount of change applied to deceleration/acceleration when reaching boundaries
  5737. var penetrationDeceleration = self.options.penetrationDeceleration;
  5738. var penetrationAcceleration = self.options.penetrationAcceleration;
  5739. // Check limits
  5740. if (scrollLeft < self.__minDecelerationScrollLeft) {
  5741. scrollOutsideX = self.__minDecelerationScrollLeft - scrollLeft;
  5742. } else if (scrollLeft > self.__maxDecelerationScrollLeft) {
  5743. scrollOutsideX = self.__maxDecelerationScrollLeft - scrollLeft;
  5744. }
  5745. if (scrollTop < self.__minDecelerationScrollTop) {
  5746. scrollOutsideY = self.__minDecelerationScrollTop - scrollTop;
  5747. } else if (scrollTop > self.__maxDecelerationScrollTop) {
  5748. scrollOutsideY = self.__maxDecelerationScrollTop - scrollTop;
  5749. }
  5750. // Slow down until slow enough, then flip back to snap position
  5751. if (scrollOutsideX !== 0) {
  5752. var isHeadingOutwardsX = scrollOutsideX * self.__decelerationVelocityX <= self.__minDecelerationScrollLeft;
  5753. if (isHeadingOutwardsX) {
  5754. self.__decelerationVelocityX += scrollOutsideX * penetrationDeceleration;
  5755. }
  5756. var isStoppedX = Math.abs(self.__decelerationVelocityX) <= self.__minVelocityToKeepDecelerating;
  5757. //If we're not heading outwards, or if the above statement got us below minDeceleration, go back towards bounds
  5758. if (!isHeadingOutwardsX || isStoppedX) {
  5759. self.__decelerationVelocityX = scrollOutsideX * penetrationAcceleration;
  5760. }
  5761. }
  5762. if (scrollOutsideY !== 0) {
  5763. var isHeadingOutwardsY = scrollOutsideY * self.__decelerationVelocityY <= self.__minDecelerationScrollTop;
  5764. if (isHeadingOutwardsY) {
  5765. self.__decelerationVelocityY += scrollOutsideY * penetrationDeceleration;
  5766. }
  5767. var isStoppedY = Math.abs(self.__decelerationVelocityY) <= self.__minVelocityToKeepDecelerating;
  5768. //If we're not heading outwards, or if the above statement got us below minDeceleration, go back towards bounds
  5769. if (!isHeadingOutwardsY || isStoppedY) {
  5770. self.__decelerationVelocityY = scrollOutsideY * penetrationAcceleration;
  5771. }
  5772. }
  5773. }
  5774. },
  5775. /**
  5776. * calculate the distance between two touches
  5777. * @param {Touch} touch1
  5778. * @param {Touch} touch2
  5779. * @returns {Number} distance
  5780. */
  5781. __getDistance: function getDistance(touch1, touch2) {
  5782. var x = touch2.pageX - touch1.pageX,
  5783. y = touch2.pageY - touch1.pageY;
  5784. return Math.sqrt((x * x) + (y * y));
  5785. },
  5786. /**
  5787. * calculate the scale factor between two touchLists (fingers)
  5788. * no scale is 1, and goes down to 0 when pinched together, and bigger when pinched out
  5789. * @param {Array} start
  5790. * @param {Array} end
  5791. * @returns {Number} scale
  5792. */
  5793. __getScale: function getScale(start, end) {
  5794. // need two fingers...
  5795. if (start.length >= 2 && end.length >= 2) {
  5796. return this.__getDistance(end[0], end[1]) /
  5797. this.__getDistance(start[0], start[1]);
  5798. }
  5799. return 1;
  5800. }
  5801. });
  5802. ionic.scroll = {
  5803. isScrolling: false,
  5804. lastTop: 0
  5805. };
  5806. })(ionic);
  5807. (function(ionic) {
  5808. var NOOP = function() {};
  5809. var deprecated = function(name) {
  5810. void 0;
  5811. };
  5812. ionic.views.ScrollNative = ionic.views.View.inherit({
  5813. initialize: function(options) {
  5814. var self = this;
  5815. self.__container = self.el = options.el;
  5816. self.__content = options.el.firstElementChild;
  5817. // Whether scrolling is frozen or not
  5818. self.__frozen = false;
  5819. self.isNative = true;
  5820. self.__scrollTop = self.el.scrollTop;
  5821. self.__scrollLeft = self.el.scrollLeft;
  5822. self.__clientHeight = self.__content.clientHeight;
  5823. self.__clientWidth = self.__content.clientWidth;
  5824. self.__maxScrollTop = Math.max((self.__contentHeight) - self.__clientHeight, 0);
  5825. self.__maxScrollLeft = Math.max((self.__contentWidth) - self.__clientWidth, 0);
  5826. if(options.startY >= 0 || options.startX >= 0) {
  5827. ionic.requestAnimationFrame(function() {
  5828. self.__originalContainerHeight = self.el.getBoundingClientRect().height;
  5829. self.el.scrollTop = options.startY || 0;
  5830. self.el.scrollLeft = options.startX || 0;
  5831. self.__scrollTop = self.el.scrollTop;
  5832. self.__scrollLeft = self.el.scrollLeft;
  5833. });
  5834. }
  5835. self.options = {
  5836. freeze: false,
  5837. getContentWidth: function() {
  5838. return Math.max(self.__content.scrollWidth, self.__content.offsetWidth);
  5839. },
  5840. getContentHeight: function() {
  5841. return Math.max(self.__content.scrollHeight, self.__content.offsetHeight + (self.__content.offsetTop * 2));
  5842. }
  5843. };
  5844. for (var key in options) {
  5845. self.options[key] = options[key];
  5846. }
  5847. /**
  5848. * Sets isScrolling to true, and automatically deactivates if not called again in 80ms.
  5849. */
  5850. self.onScroll = function() {
  5851. if (!ionic.scroll.isScrolling) {
  5852. ionic.scroll.isScrolling = true;
  5853. }
  5854. clearTimeout(self.scrollTimer);
  5855. self.scrollTimer = setTimeout(function() {
  5856. ionic.scroll.isScrolling = false;
  5857. }, 80);
  5858. };
  5859. self.freeze = function(shouldFreeze) {
  5860. self.__frozen = shouldFreeze;
  5861. };
  5862. // A more powerful freeze pop that dominates all other freeze pops
  5863. self.freezeShut = function(shouldFreezeShut) {
  5864. self.__frozenShut = shouldFreezeShut;
  5865. };
  5866. self.__initEventHandlers();
  5867. },
  5868. /** Methods not used in native scrolling */
  5869. __callback: function() { deprecated('__callback'); },
  5870. zoomTo: function() { deprecated('zoomTo'); },
  5871. zoomBy: function() { deprecated('zoomBy'); },
  5872. activatePullToRefresh: function() { deprecated('activatePullToRefresh'); },
  5873. /**
  5874. * Returns the scroll position and zooming values
  5875. *
  5876. * @return {Map} `left` and `top` scroll position and `zoom` level
  5877. */
  5878. resize: function(continueScrolling) {
  5879. var self = this;
  5880. if (!self.__container || !self.options) return;
  5881. // Update Scroller dimensions for changed content
  5882. // Add padding to bottom of content
  5883. self.setDimensions(
  5884. self.__container.clientWidth,
  5885. self.__container.clientHeight,
  5886. self.options.getContentWidth(),
  5887. self.options.getContentHeight(),
  5888. continueScrolling
  5889. );
  5890. },
  5891. /**
  5892. * Initialize the scrollview
  5893. * In native scrolling, this only means we need to gather size information
  5894. */
  5895. run: function() {
  5896. this.resize();
  5897. },
  5898. /**
  5899. * Returns the scroll position and zooming values
  5900. *
  5901. * @return {Map} `left` and `top` scroll position and `zoom` level
  5902. */
  5903. getValues: function() {
  5904. var self = this;
  5905. self.update();
  5906. return {
  5907. left: self.__scrollLeft,
  5908. top: self.__scrollTop,
  5909. zoom: 1
  5910. };
  5911. },
  5912. /**
  5913. * Updates the __scrollLeft and __scrollTop values to el's current value
  5914. */
  5915. update: function() {
  5916. var self = this;
  5917. self.__scrollLeft = self.el.scrollLeft;
  5918. self.__scrollTop = self.el.scrollTop;
  5919. },
  5920. /**
  5921. * Configures the dimensions of the client (outer) and content (inner) elements.
  5922. * Requires the available space for the outer element and the outer size of the inner element.
  5923. * All values which are falsy (null or zero etc.) are ignored and the old value is kept.
  5924. *
  5925. * @param clientWidth {Integer} Inner width of outer element
  5926. * @param clientHeight {Integer} Inner height of outer element
  5927. * @param contentWidth {Integer} Outer width of inner element
  5928. * @param contentHeight {Integer} Outer height of inner element
  5929. */
  5930. setDimensions: function(clientWidth, clientHeight, contentWidth, contentHeight) {
  5931. var self = this;
  5932. if (!clientWidth && !clientHeight && !contentWidth && !contentHeight) {
  5933. // this scrollview isn't rendered, don't bother
  5934. return;
  5935. }
  5936. // Only update values which are defined
  5937. if (clientWidth === +clientWidth) {
  5938. self.__clientWidth = clientWidth;
  5939. }
  5940. if (clientHeight === +clientHeight) {
  5941. self.__clientHeight = clientHeight;
  5942. }
  5943. if (contentWidth === +contentWidth) {
  5944. self.__contentWidth = contentWidth;
  5945. }
  5946. if (contentHeight === +contentHeight) {
  5947. self.__contentHeight = contentHeight;
  5948. }
  5949. // Refresh maximums
  5950. self.__computeScrollMax();
  5951. },
  5952. /**
  5953. * Returns the maximum scroll values
  5954. *
  5955. * @return {Map} `left` and `top` maximum scroll values
  5956. */
  5957. getScrollMax: function() {
  5958. return {
  5959. left: this.__maxScrollLeft,
  5960. top: this.__maxScrollTop
  5961. };
  5962. },
  5963. /**
  5964. * Scrolls by the given amount in px.
  5965. *
  5966. * @param left {Number} Horizontal scroll position, keeps current if value is <code>null</code>
  5967. * @param top {Number} Vertical scroll position, keeps current if value is <code>null</code>
  5968. * @param animate {Boolean} Whether the scrolling should happen using an animation
  5969. */
  5970. scrollBy: function(left, top, animate) {
  5971. var self = this;
  5972. // update scroll vars before refferencing them
  5973. self.update();
  5974. var startLeft = self.__isAnimating ? self.__scheduledLeft : self.__scrollLeft;
  5975. var startTop = self.__isAnimating ? self.__scheduledTop : self.__scrollTop;
  5976. self.scrollTo(startLeft + (left || 0), startTop + (top || 0), animate);
  5977. },
  5978. /**
  5979. * Scrolls to the given position in px.
  5980. *
  5981. * @param left {Number} Horizontal scroll position, keeps current if value is <code>null</code>
  5982. * @param top {Number} Vertical scroll position, keeps current if value is <code>null</code>
  5983. * @param animate {Boolean} Whether the scrolling should happen using an animation
  5984. */
  5985. scrollTo: function(left, top, animate) {
  5986. var self = this;
  5987. if (!animate) {
  5988. self.el.scrollTop = top;
  5989. self.el.scrollLeft = left;
  5990. self.resize();
  5991. return;
  5992. }
  5993. var oldOverflowX = self.el.style.overflowX;
  5994. var oldOverflowY = self.el.style.overflowY;
  5995. clearTimeout(self.__scrollToCleanupTimeout);
  5996. self.__scrollToCleanupTimeout = setTimeout(function() {
  5997. self.el.style.overflowX = oldOverflowX;
  5998. self.el.style.overflowY = oldOverflowY;
  5999. }, 500);
  6000. self.el.style.overflowY = 'hidden';
  6001. self.el.style.overflowX = 'hidden';
  6002. animateScroll(top, left);
  6003. function animateScroll(Y, X) {
  6004. // scroll animation loop w/ easing
  6005. // credit https://gist.github.com/dezinezync/5487119
  6006. var start = Date.now(),
  6007. duration = 250, //milliseconds
  6008. fromY = self.el.scrollTop,
  6009. fromX = self.el.scrollLeft;
  6010. if (fromY === Y && fromX === X) {
  6011. self.el.style.overflowX = oldOverflowX;
  6012. self.el.style.overflowY = oldOverflowY;
  6013. self.resize();
  6014. return; /* Prevent scrolling to the Y point if already there */
  6015. }
  6016. // decelerating to zero velocity
  6017. function easeOutCubic(t) {
  6018. return (--t) * t * t + 1;
  6019. }
  6020. // scroll loop
  6021. function animateScrollStep() {
  6022. var currentTime = Date.now(),
  6023. time = Math.min(1, ((currentTime - start) / duration)),
  6024. // where .5 would be 50% of time on a linear scale easedT gives a
  6025. // fraction based on the easing method
  6026. easedT = easeOutCubic(time);
  6027. if (fromY != Y) {
  6028. self.el.scrollTop = parseInt((easedT * (Y - fromY)) + fromY, 10);
  6029. }
  6030. if (fromX != X) {
  6031. self.el.scrollLeft = parseInt((easedT * (X - fromX)) + fromX, 10);
  6032. }
  6033. if (time < 1) {
  6034. ionic.requestAnimationFrame(animateScrollStep);
  6035. } else {
  6036. // done
  6037. ionic.tap.removeClonedInputs(self.__container, self);
  6038. self.el.style.overflowX = oldOverflowX;
  6039. self.el.style.overflowY = oldOverflowY;
  6040. self.resize();
  6041. }
  6042. }
  6043. // start scroll loop
  6044. ionic.requestAnimationFrame(animateScrollStep);
  6045. }
  6046. },
  6047. /*
  6048. ---------------------------------------------------------------------------
  6049. PRIVATE API
  6050. ---------------------------------------------------------------------------
  6051. */
  6052. /**
  6053. * If the scroll view isn't sized correctly on start, wait until we have at least some size
  6054. */
  6055. __waitForSize: function() {
  6056. var self = this;
  6057. clearTimeout(self.__sizerTimeout);
  6058. var sizer = function() {
  6059. self.resize(true);
  6060. };
  6061. sizer();
  6062. self.__sizerTimeout = setTimeout(sizer, 500);
  6063. },
  6064. /**
  6065. * Recomputes scroll minimum values based on client dimensions and content dimensions.
  6066. */
  6067. __computeScrollMax: function() {
  6068. var self = this;
  6069. self.__maxScrollLeft = Math.max((self.__contentWidth) - self.__clientWidth, 0);
  6070. self.__maxScrollTop = Math.max((self.__contentHeight) - self.__clientHeight, 0);
  6071. if (!self.__didWaitForSize && !self.__maxScrollLeft && !self.__maxScrollTop) {
  6072. self.__didWaitForSize = true;
  6073. self.__waitForSize();
  6074. }
  6075. },
  6076. __initEventHandlers: function() {
  6077. var self = this;
  6078. // Event Handler
  6079. var container = self.__container;
  6080. // save height when scroll view is shrunk so we don't need to reflow
  6081. var scrollViewOffsetHeight;
  6082. var lastKeyboardHeight;
  6083. /**
  6084. * Shrink the scroll view when the keyboard is up if necessary and if the
  6085. * focused input is below the bottom of the shrunk scroll view, scroll it
  6086. * into view.
  6087. */
  6088. self.scrollChildIntoView = function(e) {
  6089. var rect = container.getBoundingClientRect();
  6090. if(!self.__originalContainerHeight) {
  6091. self.__originalContainerHeight = rect.height;
  6092. }
  6093. // D
  6094. //var scrollBottomOffsetToTop = rect.bottom;
  6095. // D - A
  6096. scrollViewOffsetHeight = self.__originalContainerHeight;
  6097. //console.log('Scroll view offset height', scrollViewOffsetHeight);
  6098. //console.dir(container);
  6099. var alreadyShrunk = self.isShrunkForKeyboard;
  6100. var isModal = container.parentNode.classList.contains('modal');
  6101. var isPopover = container.parentNode.classList.contains('popover');
  6102. // 680px is when the media query for 60% modal width kicks in
  6103. var isInsetModal = isModal && window.innerWidth >= 680;
  6104. /*
  6105. * _______
  6106. * |---A---| <- top of scroll view
  6107. * | |
  6108. * |---B---| <- keyboard
  6109. * | C | <- input
  6110. * |---D---| <- initial bottom of scroll view
  6111. * |___E___| <- bottom of viewport
  6112. *
  6113. * All commented calculations relative to the top of the viewport (ie E
  6114. * is the viewport height, not 0)
  6115. */
  6116. var changedKeyboardHeight = lastKeyboardHeight && (lastKeyboardHeight !== e.detail.keyboardHeight);
  6117. if (!alreadyShrunk || changedKeyboardHeight) {
  6118. // shrink scrollview so we can actually scroll if the input is hidden
  6119. // if it isn't shrink so we can scroll to inputs under the keyboard
  6120. // inset modals won't shrink on Android on their own when the keyboard appears
  6121. if ( !isPopover && (ionic.Platform.isIOS() || ionic.Platform.isFullScreen || isInsetModal) ) {
  6122. // if there are things below the scroll view account for them and
  6123. // subtract them from the keyboard height when resizing
  6124. // E - D E D
  6125. //var scrollBottomOffsetToBottom = e.detail.viewportHeight - scrollBottomOffsetToTop;
  6126. // 0 or D - B if D > B E - B E - D
  6127. //var keyboardOffset = e.detail.keyboardHeight - scrollBottomOffsetToBottom;
  6128. ionic.requestAnimationFrame(function(){
  6129. // D - A or B - A if D > B D - A max(0, D - B)
  6130. scrollViewOffsetHeight = Math.max(0, Math.min(self.__originalContainerHeight, self.__originalContainerHeight - (e.detail.keyboardHeight - 43)));//keyboardOffset >= 0 ? scrollViewOffsetHeight - keyboardOffset : scrollViewOffsetHeight + keyboardOffset;
  6131. //console.log('Old container height', self.__originalContainerHeight, 'New container height', scrollViewOffsetHeight, 'Keyboard height', e.detail.keyboardHeight);
  6132. container.style.height = scrollViewOffsetHeight + "px";
  6133. /*
  6134. if (ionic.Platform.isIOS()) {
  6135. // Force redraw to avoid disappearing content
  6136. var disp = container.style.display;
  6137. container.style.display = 'none';
  6138. var trick = container.offsetHeight;
  6139. container.style.display = disp;
  6140. }
  6141. */
  6142. container.classList.add('keyboard-up');
  6143. //update scroll view
  6144. self.resize();
  6145. });
  6146. }
  6147. self.isShrunkForKeyboard = true;
  6148. }
  6149. lastKeyboardHeight = e.detail.keyboardHeight;
  6150. /*
  6151. * _______
  6152. * |---A---| <- top of scroll view
  6153. * | * | <- where we want to scroll to
  6154. * |--B-D--| <- keyboard, bottom of scroll view
  6155. * | C | <- input
  6156. * | |
  6157. * |___E___| <- bottom of viewport
  6158. *
  6159. * All commented calculations relative to the top of the viewport (ie E
  6160. * is the viewport height, not 0)
  6161. */
  6162. // if the element is positioned under the keyboard scroll it into view
  6163. if (e.detail.isElementUnderKeyboard) {
  6164. ionic.requestAnimationFrame(function(){
  6165. var pos = ionic.DomUtil.getOffsetTop(e.detail.target);
  6166. setTimeout(function() {
  6167. if (ionic.Platform.isIOS()) {
  6168. ionic.tap.cloneFocusedInput(container, self);
  6169. }
  6170. // Scroll the input into view, with a 100px buffer
  6171. self.scrollTo(0, pos - (rect.top + 100), true);
  6172. self.onScroll();
  6173. }, 32);
  6174. /*
  6175. // update D if we shrunk
  6176. if (self.isShrunkForKeyboard && !alreadyShrunk) {
  6177. scrollBottomOffsetToTop = container.getBoundingClientRect().bottom;
  6178. console.log('Scroll bottom', scrollBottomOffsetToTop);
  6179. }
  6180. // middle of the scrollview, this is where we want to scroll to
  6181. // (D - A) / 2
  6182. var scrollMidpointOffset = scrollViewOffsetHeight * 0.5;
  6183. console.log('Midpoint', scrollMidpointOffset);
  6184. //console.log("container.offsetHeight: " + scrollViewOffsetHeight);
  6185. // middle of the input we want to scroll into view
  6186. // C
  6187. var inputMidpoint = ((e.detail.elementBottom + e.detail.elementTop) / 2);
  6188. console.log('Input midpoint');
  6189. // distance from middle of input to the bottom of the scroll view
  6190. // C - D C D
  6191. var inputMidpointOffsetToScrollBottom = inputMidpoint - scrollBottomOffsetToTop;
  6192. console.log('Input midpoint offset', inputMidpointOffsetToScrollBottom);
  6193. //C - D + (D - A)/2 C - D (D - A)/ 2
  6194. var scrollTop = inputMidpointOffsetToScrollBottom + scrollMidpointOffset;
  6195. console.log('Scroll top', scrollTop);
  6196. if ( scrollTop > 0) {
  6197. if (ionic.Platform.isIOS()) {
  6198. //just shrank scroll view, give it some breathing room before scrolling
  6199. setTimeout(function(){
  6200. ionic.tap.cloneFocusedInput(container, self);
  6201. self.scrollBy(0, scrollTop, true);
  6202. self.onScroll();
  6203. }, 32);
  6204. } else {
  6205. self.scrollBy(0, scrollTop, true);
  6206. self.onScroll();
  6207. }
  6208. }
  6209. */
  6210. });
  6211. }
  6212. // Only the first scrollView parent of the element that broadcasted this event
  6213. // (the active element that needs to be shown) should receive this event
  6214. e.stopPropagation();
  6215. };
  6216. self.resetScrollView = function() {
  6217. //return scrollview to original height once keyboard has hidden
  6218. if (self.isShrunkForKeyboard) {
  6219. self.isShrunkForKeyboard = false;
  6220. container.style.height = "";
  6221. /*
  6222. if (ionic.Platform.isIOS()) {
  6223. // Force redraw to avoid disappearing content
  6224. var disp = container.style.display;
  6225. container.style.display = 'none';
  6226. var trick = container.offsetHeight;
  6227. container.style.display = disp;
  6228. }
  6229. */
  6230. self.__originalContainerHeight = container.getBoundingClientRect().height;
  6231. if (ionic.Platform.isIOS()) {
  6232. ionic.requestAnimationFrame(function() {
  6233. container.classList.remove('keyboard-up');
  6234. });
  6235. }
  6236. }
  6237. self.resize();
  6238. };
  6239. self.handleTouchMove = function(e) {
  6240. if (self.__frozenShut) {
  6241. e.preventDefault();
  6242. e.stopPropagation();
  6243. return false;
  6244. } else if ( self.__frozen ){
  6245. e.preventDefault();
  6246. // let it propagate so other events such as drag events can happen,
  6247. // but don't let it actually scroll
  6248. return false;
  6249. }
  6250. return true;
  6251. };
  6252. container.addEventListener('scroll', self.onScroll);
  6253. //Broadcasted when keyboard is shown on some platforms.
  6254. //See js/utils/keyboard.js
  6255. container.addEventListener('scrollChildIntoView', self.scrollChildIntoView);
  6256. container.addEventListener(ionic.EVENTS.touchstart, self.handleTouchMove);
  6257. container.addEventListener(ionic.EVENTS.touchmove, self.handleTouchMove);
  6258. // Listen on document because container may not have had the last
  6259. // keyboardActiveElement, for example after closing a modal with a focused
  6260. // input and returning to a previously resized scroll view in an ion-content.
  6261. // Since we can only resize scroll views that are currently visible, just resize
  6262. // the current scroll view when the keyboard is closed.
  6263. document.addEventListener('resetScrollView', self.resetScrollView);
  6264. },
  6265. __cleanup: function() {
  6266. var self = this;
  6267. var container = self.__container;
  6268. container.removeEventListener('scroll', self.onScroll);
  6269. container.removeEventListener('scrollChildIntoView', self.scrollChildIntoView);
  6270. container.removeEventListener(ionic.EVENTS.touchstart, self.handleTouchMove);
  6271. container.removeEventListener(ionic.EVENTS.touchmove, self.handleTouchMove);
  6272. document.removeEventListener('resetScrollView', self.resetScrollView);
  6273. ionic.tap.removeClonedInputs(container, self);
  6274. delete self.__container;
  6275. delete self.__content;
  6276. delete self.__indicatorX;
  6277. delete self.__indicatorY;
  6278. delete self.options.el;
  6279. self.resize = self.scrollTo = self.onScroll = self.resetScrollView = NOOP;
  6280. self.scrollChildIntoView = NOOP;
  6281. container = null;
  6282. }
  6283. });
  6284. })(ionic);
  6285. (function(ionic) {
  6286. 'use strict';
  6287. var ITEM_CLASS = 'item';
  6288. var ITEM_CONTENT_CLASS = 'item-content';
  6289. var ITEM_SLIDING_CLASS = 'item-sliding';
  6290. var ITEM_OPTIONS_CLASS = 'item-options';
  6291. var ITEM_PLACEHOLDER_CLASS = 'item-placeholder';
  6292. var ITEM_REORDERING_CLASS = 'item-reordering';
  6293. var ITEM_REORDER_BTN_CLASS = 'item-reorder';
  6294. var DragOp = function() {};
  6295. DragOp.prototype = {
  6296. start: function(){},
  6297. drag: function(){},
  6298. end: function(){},
  6299. isSameItem: function() {
  6300. return false;
  6301. }
  6302. };
  6303. var SlideDrag = function(opts) {
  6304. this.dragThresholdX = opts.dragThresholdX || 10;
  6305. this.el = opts.el;
  6306. this.item = opts.item;
  6307. this.canSwipe = opts.canSwipe;
  6308. };
  6309. SlideDrag.prototype = new DragOp();
  6310. SlideDrag.prototype.start = function(e) {
  6311. var content, buttons, offsetX, buttonsWidth;
  6312. if (!this.canSwipe()) {
  6313. return;
  6314. }
  6315. if (e.target.classList.contains(ITEM_CONTENT_CLASS)) {
  6316. content = e.target;
  6317. } else if (e.target.classList.contains(ITEM_CLASS)) {
  6318. content = e.target.querySelector('.' + ITEM_CONTENT_CLASS);
  6319. } else {
  6320. content = ionic.DomUtil.getParentWithClass(e.target, ITEM_CONTENT_CLASS);
  6321. }
  6322. // If we don't have a content area as one of our children (or ourselves), skip
  6323. if (!content) {
  6324. return;
  6325. }
  6326. // Make sure we aren't animating as we slide
  6327. content.classList.remove(ITEM_SLIDING_CLASS);
  6328. // Grab the starting X point for the item (for example, so we can tell whether it is open or closed to start)
  6329. offsetX = parseFloat(content.style[ionic.CSS.TRANSFORM].replace('translate3d(', '').split(',')[0]) || 0;
  6330. // Grab the buttons
  6331. buttons = content.parentNode.querySelector('.' + ITEM_OPTIONS_CLASS);
  6332. if (!buttons) {
  6333. return;
  6334. }
  6335. buttons.classList.remove('invisible');
  6336. buttonsWidth = buttons.offsetWidth;
  6337. this._currentDrag = {
  6338. buttons: buttons,
  6339. buttonsWidth: buttonsWidth,
  6340. content: content,
  6341. startOffsetX: offsetX
  6342. };
  6343. };
  6344. /**
  6345. * Check if this is the same item that was previously dragged.
  6346. */
  6347. SlideDrag.prototype.isSameItem = function(op) {
  6348. if (op._lastDrag && this._currentDrag) {
  6349. return this._currentDrag.content == op._lastDrag.content;
  6350. }
  6351. return false;
  6352. };
  6353. SlideDrag.prototype.clean = function(isInstant) {
  6354. var lastDrag = this._lastDrag;
  6355. if (!lastDrag || !lastDrag.content) return;
  6356. lastDrag.content.style[ionic.CSS.TRANSITION] = '';
  6357. lastDrag.content.style[ionic.CSS.TRANSFORM] = '';
  6358. if (isInstant) {
  6359. lastDrag.content.style[ionic.CSS.TRANSITION] = 'none';
  6360. makeInvisible();
  6361. ionic.requestAnimationFrame(function() {
  6362. lastDrag.content.style[ionic.CSS.TRANSITION] = '';
  6363. });
  6364. } else {
  6365. ionic.requestAnimationFrame(function() {
  6366. setTimeout(makeInvisible, 250);
  6367. });
  6368. }
  6369. function makeInvisible() {
  6370. lastDrag.buttons && lastDrag.buttons.classList.add('invisible');
  6371. }
  6372. };
  6373. SlideDrag.prototype.drag = ionic.animationFrameThrottle(function(e) {
  6374. var buttonsWidth;
  6375. // We really aren't dragging
  6376. if (!this._currentDrag) {
  6377. return;
  6378. }
  6379. // Check if we should start dragging. Check if we've dragged past the threshold,
  6380. // or we are starting from the open state.
  6381. if (!this._isDragging &&
  6382. ((Math.abs(e.gesture.deltaX) > this.dragThresholdX) ||
  6383. (Math.abs(this._currentDrag.startOffsetX) > 0))) {
  6384. this._isDragging = true;
  6385. }
  6386. if (this._isDragging) {
  6387. buttonsWidth = this._currentDrag.buttonsWidth;
  6388. // Grab the new X point, capping it at zero
  6389. var newX = Math.min(0, this._currentDrag.startOffsetX + e.gesture.deltaX);
  6390. // If the new X position is past the buttons, we need to slow down the drag (rubber band style)
  6391. if (newX < -buttonsWidth) {
  6392. // Calculate the new X position, capped at the top of the buttons
  6393. newX = Math.min(-buttonsWidth, -buttonsWidth + (((e.gesture.deltaX + buttonsWidth) * 0.4)));
  6394. }
  6395. this._currentDrag.content.$$ionicOptionsOpen = newX !== 0;
  6396. this._currentDrag.content.style[ionic.CSS.TRANSFORM] = 'translate3d(' + newX + 'px, 0, 0)';
  6397. this._currentDrag.content.style[ionic.CSS.TRANSITION] = 'none';
  6398. }
  6399. });
  6400. SlideDrag.prototype.end = function(e, doneCallback) {
  6401. var self = this;
  6402. // There is no drag, just end immediately
  6403. if (!self._currentDrag) {
  6404. doneCallback && doneCallback();
  6405. return;
  6406. }
  6407. // If we are currently dragging, we want to snap back into place
  6408. // The final resting point X will be the width of the exposed buttons
  6409. var restingPoint = -self._currentDrag.buttonsWidth;
  6410. // Check if the drag didn't clear the buttons mid-point
  6411. // and we aren't moving fast enough to swipe open
  6412. if (e.gesture.deltaX > -(self._currentDrag.buttonsWidth / 2)) {
  6413. // If we are going left but too slow, or going right, go back to resting
  6414. if (e.gesture.direction == "left" && Math.abs(e.gesture.velocityX) < 0.3) {
  6415. restingPoint = 0;
  6416. } else if (e.gesture.direction == "right") {
  6417. restingPoint = 0;
  6418. }
  6419. }
  6420. ionic.requestAnimationFrame(function() {
  6421. if (restingPoint === 0) {
  6422. self._currentDrag.content.style[ionic.CSS.TRANSFORM] = '';
  6423. var buttons = self._currentDrag.buttons;
  6424. setTimeout(function() {
  6425. buttons && buttons.classList.add('invisible');
  6426. }, 250);
  6427. } else {
  6428. self._currentDrag.content.style[ionic.CSS.TRANSFORM] = 'translate3d(' + restingPoint + 'px,0,0)';
  6429. }
  6430. self._currentDrag.content.style[ionic.CSS.TRANSITION] = '';
  6431. // Kill the current drag
  6432. if (!self._lastDrag) {
  6433. self._lastDrag = {};
  6434. }
  6435. ionic.extend(self._lastDrag, self._currentDrag);
  6436. if (self._currentDrag) {
  6437. self._currentDrag.buttons = null;
  6438. self._currentDrag.content = null;
  6439. }
  6440. self._currentDrag = null;
  6441. // We are done, notify caller
  6442. doneCallback && doneCallback();
  6443. });
  6444. };
  6445. var ReorderDrag = function(opts) {
  6446. var self = this;
  6447. self.dragThresholdY = opts.dragThresholdY || 0;
  6448. self.onReorder = opts.onReorder;
  6449. self.listEl = opts.listEl;
  6450. self.el = self.item = opts.el;
  6451. self.scrollEl = opts.scrollEl;
  6452. self.scrollView = opts.scrollView;
  6453. // Get the True Top of the list el http://www.quirksmode.org/js/findpos.html
  6454. self.listElTrueTop = 0;
  6455. if (self.listEl.offsetParent) {
  6456. var obj = self.listEl;
  6457. do {
  6458. self.listElTrueTop += obj.offsetTop;
  6459. obj = obj.offsetParent;
  6460. } while (obj);
  6461. }
  6462. };
  6463. ReorderDrag.prototype = new DragOp();
  6464. ReorderDrag.prototype._moveElement = function(e) {
  6465. var y = e.gesture.center.pageY +
  6466. this.scrollView.getValues().top -
  6467. (this._currentDrag.elementHeight / 2) -
  6468. this.listElTrueTop;
  6469. this.el.style[ionic.CSS.TRANSFORM] = 'translate3d(0, ' + y + 'px, 0)';
  6470. };
  6471. ReorderDrag.prototype.deregister = function() {
  6472. this.listEl = this.el = this.scrollEl = this.scrollView = null;
  6473. };
  6474. ReorderDrag.prototype.start = function(e) {
  6475. var startIndex = ionic.DomUtil.getChildIndex(this.el, this.el.nodeName.toLowerCase());
  6476. var elementHeight = this.el.scrollHeight;
  6477. var placeholder = this.el.cloneNode(true);
  6478. placeholder.classList.add(ITEM_PLACEHOLDER_CLASS);
  6479. this.el.parentNode.insertBefore(placeholder, this.el);
  6480. this.el.classList.add(ITEM_REORDERING_CLASS);
  6481. this._currentDrag = {
  6482. elementHeight: elementHeight,
  6483. startIndex: startIndex,
  6484. placeholder: placeholder,
  6485. scrollHeight: scroll,
  6486. list: placeholder.parentNode
  6487. };
  6488. this._moveElement(e);
  6489. };
  6490. ReorderDrag.prototype.drag = ionic.animationFrameThrottle(function(e) {
  6491. // We really aren't dragging
  6492. var self = this;
  6493. if (!this._currentDrag) {
  6494. return;
  6495. }
  6496. var scrollY = 0;
  6497. var pageY = e.gesture.center.pageY;
  6498. var offset = this.listElTrueTop;
  6499. //If we have a scrollView, check scroll boundaries for dragged element and scroll if necessary
  6500. if (this.scrollView) {
  6501. var container = this.scrollView.__container;
  6502. scrollY = this.scrollView.getValues().top;
  6503. var containerTop = container.offsetTop;
  6504. var pixelsPastTop = containerTop - pageY + this._currentDrag.elementHeight / 2;
  6505. var pixelsPastBottom = pageY + this._currentDrag.elementHeight / 2 - containerTop - container.offsetHeight;
  6506. if (e.gesture.deltaY < 0 && pixelsPastTop > 0 && scrollY > 0) {
  6507. this.scrollView.scrollBy(null, -pixelsPastTop);
  6508. //Trigger another drag so the scrolling keeps going
  6509. ionic.requestAnimationFrame(function() {
  6510. self.drag(e);
  6511. });
  6512. }
  6513. if (e.gesture.deltaY > 0 && pixelsPastBottom > 0) {
  6514. if (scrollY < this.scrollView.getScrollMax().top) {
  6515. this.scrollView.scrollBy(null, pixelsPastBottom);
  6516. //Trigger another drag so the scrolling keeps going
  6517. ionic.requestAnimationFrame(function() {
  6518. self.drag(e);
  6519. });
  6520. }
  6521. }
  6522. }
  6523. // Check if we should start dragging. Check if we've dragged past the threshold,
  6524. // or we are starting from the open state.
  6525. if (!this._isDragging && Math.abs(e.gesture.deltaY) > this.dragThresholdY) {
  6526. this._isDragging = true;
  6527. }
  6528. if (this._isDragging) {
  6529. this._moveElement(e);
  6530. this._currentDrag.currentY = scrollY + pageY - offset;
  6531. // this._reorderItems();
  6532. }
  6533. });
  6534. // When an item is dragged, we need to reorder any items for sorting purposes
  6535. ReorderDrag.prototype._getReorderIndex = function() {
  6536. var self = this;
  6537. var siblings = Array.prototype.slice.call(self._currentDrag.placeholder.parentNode.children)
  6538. .filter(function(el) {
  6539. return el.nodeName === self.el.nodeName && el !== self.el;
  6540. });
  6541. var dragOffsetTop = self._currentDrag.currentY;
  6542. var el;
  6543. for (var i = 0, len = siblings.length; i < len; i++) {
  6544. el = siblings[i];
  6545. if (i === len - 1) {
  6546. if (dragOffsetTop > el.offsetTop) {
  6547. return i;
  6548. }
  6549. } else if (i === 0) {
  6550. if (dragOffsetTop < el.offsetTop + el.offsetHeight) {
  6551. return i;
  6552. }
  6553. } else if (dragOffsetTop > el.offsetTop - el.offsetHeight / 2 &&
  6554. dragOffsetTop < el.offsetTop + el.offsetHeight) {
  6555. return i;
  6556. }
  6557. }
  6558. return self._currentDrag.startIndex;
  6559. };
  6560. ReorderDrag.prototype.end = function(e, doneCallback) {
  6561. if (!this._currentDrag) {
  6562. doneCallback && doneCallback();
  6563. return;
  6564. }
  6565. var placeholder = this._currentDrag.placeholder;
  6566. var finalIndex = this._getReorderIndex();
  6567. // Reposition the element
  6568. this.el.classList.remove(ITEM_REORDERING_CLASS);
  6569. this.el.style[ionic.CSS.TRANSFORM] = '';
  6570. placeholder.parentNode.insertBefore(this.el, placeholder);
  6571. placeholder.parentNode.removeChild(placeholder);
  6572. this.onReorder && this.onReorder(this.el, this._currentDrag.startIndex, finalIndex);
  6573. this._currentDrag = {
  6574. placeholder: null,
  6575. content: null
  6576. };
  6577. this._currentDrag = null;
  6578. doneCallback && doneCallback();
  6579. };
  6580. /**
  6581. * The ListView handles a list of items. It will process drag animations, edit mode,
  6582. * and other operations that are common on mobile lists or table views.
  6583. */
  6584. ionic.views.ListView = ionic.views.View.inherit({
  6585. initialize: function(opts) {
  6586. var self = this;
  6587. opts = ionic.extend({
  6588. onReorder: function() {},
  6589. virtualRemoveThreshold: -200,
  6590. virtualAddThreshold: 200,
  6591. canSwipe: function() {
  6592. return true;
  6593. }
  6594. }, opts);
  6595. ionic.extend(self, opts);
  6596. if (!self.itemHeight && self.listEl) {
  6597. self.itemHeight = self.listEl.children[0] && parseInt(self.listEl.children[0].style.height, 10);
  6598. }
  6599. self.onRefresh = opts.onRefresh || function() {};
  6600. self.onRefreshOpening = opts.onRefreshOpening || function() {};
  6601. self.onRefreshHolding = opts.onRefreshHolding || function() {};
  6602. var gestureOpts = {};
  6603. // don't prevent native scrolling
  6604. if (ionic.DomUtil.getParentOrSelfWithClass(self.el, 'overflow-scroll')) {
  6605. gestureOpts.prevent_default_directions = ['left', 'right'];
  6606. }
  6607. window.ionic.onGesture('release', function(e) {
  6608. self._handleEndDrag(e);
  6609. }, self.el, gestureOpts);
  6610. window.ionic.onGesture('drag', function(e) {
  6611. self._handleDrag(e);
  6612. }, self.el, gestureOpts);
  6613. // Start the drag states
  6614. self._initDrag();
  6615. },
  6616. /**
  6617. * Be sure to cleanup references.
  6618. */
  6619. deregister: function() {
  6620. this.el = this.listEl = this.scrollEl = this.scrollView = null;
  6621. // ensure no scrolls have been left frozen
  6622. if (this.isScrollFreeze) {
  6623. self.scrollView.freeze(false);
  6624. }
  6625. },
  6626. /**
  6627. * Called to tell the list to stop refreshing. This is useful
  6628. * if you are refreshing the list and are done with refreshing.
  6629. */
  6630. stopRefreshing: function() {
  6631. var refresher = this.el.querySelector('.list-refresher');
  6632. refresher.style.height = '0';
  6633. },
  6634. /**
  6635. * If we scrolled and have virtual mode enabled, compute the window
  6636. * of active elements in order to figure out the viewport to render.
  6637. */
  6638. didScroll: function(e) {
  6639. var self = this;
  6640. if (self.isVirtual) {
  6641. var itemHeight = self.itemHeight;
  6642. // Grab the total height of the list
  6643. var scrollHeight = e.target.scrollHeight;
  6644. // Get the viewport height
  6645. var viewportHeight = self.el.parentNode.offsetHeight;
  6646. // High water is the pixel position of the first element to include (everything before
  6647. // that will be removed)
  6648. var highWater = Math.max(0, e.scrollTop + self.virtualRemoveThreshold);
  6649. // Low water is the pixel position of the last element to include (everything after
  6650. // that will be removed)
  6651. var lowWater = Math.min(scrollHeight, Math.abs(e.scrollTop) + viewportHeight + self.virtualAddThreshold);
  6652. // Get the first and last elements in the list based on how many can fit
  6653. // between the pixel range of lowWater and highWater
  6654. var first = parseInt(Math.abs(highWater / itemHeight), 10);
  6655. var last = parseInt(Math.abs(lowWater / itemHeight), 10);
  6656. // Get the items we need to remove
  6657. self._virtualItemsToRemove = Array.prototype.slice.call(self.listEl.children, 0, first);
  6658. self.renderViewport && self.renderViewport(highWater, lowWater, first, last);
  6659. }
  6660. },
  6661. didStopScrolling: function() {
  6662. if (this.isVirtual) {
  6663. for (var i = 0; i < this._virtualItemsToRemove.length; i++) {
  6664. //el.parentNode.removeChild(el);
  6665. this.didHideItem && this.didHideItem(i);
  6666. }
  6667. // Once scrolling stops, check if we need to remove old items
  6668. }
  6669. },
  6670. /**
  6671. * Clear any active drag effects on the list.
  6672. */
  6673. clearDragEffects: function(isInstant) {
  6674. if (this._lastDragOp) {
  6675. this._lastDragOp.clean && this._lastDragOp.clean(isInstant);
  6676. this._lastDragOp.deregister && this._lastDragOp.deregister();
  6677. this._lastDragOp = null;
  6678. }
  6679. },
  6680. _initDrag: function() {
  6681. // Store the last one
  6682. if (this._lastDragOp) {
  6683. this._lastDragOp.deregister && this._lastDragOp.deregister();
  6684. }
  6685. this._lastDragOp = this._dragOp;
  6686. this._dragOp = null;
  6687. },
  6688. // Return the list item from the given target
  6689. _getItem: function(target) {
  6690. while (target) {
  6691. if (target.classList && target.classList.contains(ITEM_CLASS)) {
  6692. return target;
  6693. }
  6694. target = target.parentNode;
  6695. }
  6696. return null;
  6697. },
  6698. _startDrag: function(e) {
  6699. var self = this;
  6700. self._isDragging = false;
  6701. var lastDragOp = self._lastDragOp;
  6702. var item;
  6703. // If we have an open SlideDrag and we're scrolling the list. Clear it.
  6704. if (self._didDragUpOrDown && lastDragOp instanceof SlideDrag) {
  6705. lastDragOp.clean && lastDragOp.clean();
  6706. }
  6707. // Check if this is a reorder drag
  6708. if (ionic.DomUtil.getParentOrSelfWithClass(e.target, ITEM_REORDER_BTN_CLASS) && (e.gesture.direction == 'up' || e.gesture.direction == 'down')) {
  6709. item = self._getItem(e.target);
  6710. if (item) {
  6711. self._dragOp = new ReorderDrag({
  6712. listEl: self.el,
  6713. el: item,
  6714. scrollEl: self.scrollEl,
  6715. scrollView: self.scrollView,
  6716. onReorder: function(el, start, end) {
  6717. self.onReorder && self.onReorder(el, start, end);
  6718. }
  6719. });
  6720. self._dragOp.start(e);
  6721. e.preventDefault();
  6722. }
  6723. }
  6724. // Or check if this is a swipe to the side drag
  6725. else if (!self._didDragUpOrDown && (e.gesture.direction == 'left' || e.gesture.direction == 'right') && Math.abs(e.gesture.deltaX) > 5) {
  6726. // Make sure this is an item with buttons
  6727. item = self._getItem(e.target);
  6728. if (item && item.querySelector('.item-options')) {
  6729. self._dragOp = new SlideDrag({
  6730. el: self.el,
  6731. item: item,
  6732. canSwipe: self.canSwipe
  6733. });
  6734. self._dragOp.start(e);
  6735. e.preventDefault();
  6736. self.isScrollFreeze = self.scrollView.freeze(true);
  6737. }
  6738. }
  6739. // If we had a last drag operation and this is a new one on a different item, clean that last one
  6740. if (lastDragOp && self._dragOp && !self._dragOp.isSameItem(lastDragOp) && e.defaultPrevented) {
  6741. lastDragOp.clean && lastDragOp.clean();
  6742. }
  6743. },
  6744. _handleEndDrag: function(e) {
  6745. var self = this;
  6746. if (self.scrollView) {
  6747. self.isScrollFreeze = self.scrollView.freeze(false);
  6748. }
  6749. self._didDragUpOrDown = false;
  6750. if (!self._dragOp) {
  6751. return;
  6752. }
  6753. self._dragOp.end(e, function() {
  6754. self._initDrag();
  6755. });
  6756. },
  6757. /**
  6758. * Process the drag event to move the item to the left or right.
  6759. */
  6760. _handleDrag: function(e) {
  6761. var self = this;
  6762. if (Math.abs(e.gesture.deltaY) > 5) {
  6763. self._didDragUpOrDown = true;
  6764. }
  6765. // If we get a drag event, make sure we aren't in another drag, then check if we should
  6766. // start one
  6767. if (!self.isDragging && !self._dragOp) {
  6768. self._startDrag(e);
  6769. }
  6770. // No drag still, pass it up
  6771. if (!self._dragOp) {
  6772. return;
  6773. }
  6774. e.gesture.srcEvent.preventDefault();
  6775. self._dragOp.drag(e);
  6776. }
  6777. });
  6778. })(ionic);
  6779. (function(ionic) {
  6780. 'use strict';
  6781. ionic.views.Modal = ionic.views.View.inherit({
  6782. initialize: function(opts) {
  6783. opts = ionic.extend({
  6784. focusFirstInput: false,
  6785. unfocusOnHide: true,
  6786. focusFirstDelay: 600,
  6787. backdropClickToClose: true,
  6788. hardwareBackButtonClose: true,
  6789. }, opts);
  6790. ionic.extend(this, opts);
  6791. this.el = opts.el;
  6792. },
  6793. show: function() {
  6794. var self = this;
  6795. if(self.focusFirstInput) {
  6796. // Let any animations run first
  6797. window.setTimeout(function() {
  6798. var input = self.el.querySelector('input, textarea');
  6799. input && input.focus && input.focus();
  6800. }, self.focusFirstDelay);
  6801. }
  6802. },
  6803. hide: function() {
  6804. // Unfocus all elements
  6805. if(this.unfocusOnHide) {
  6806. var inputs = this.el.querySelectorAll('input, textarea');
  6807. // Let any animations run first
  6808. window.setTimeout(function() {
  6809. for(var i = 0; i < inputs.length; i++) {
  6810. inputs[i].blur && inputs[i].blur();
  6811. }
  6812. });
  6813. }
  6814. }
  6815. });
  6816. })(ionic);
  6817. (function(ionic) {
  6818. 'use strict';
  6819. /**
  6820. * The side menu view handles one of the side menu's in a Side Menu Controller
  6821. * configuration.
  6822. * It takes a DOM reference to that side menu element.
  6823. */
  6824. ionic.views.SideMenu = ionic.views.View.inherit({
  6825. initialize: function(opts) {
  6826. this.el = opts.el;
  6827. this.isEnabled = (typeof opts.isEnabled === 'undefined') ? true : opts.isEnabled;
  6828. this.setWidth(opts.width);
  6829. },
  6830. getFullWidth: function() {
  6831. return this.width;
  6832. },
  6833. setWidth: function(width) {
  6834. this.width = width;
  6835. this.el.style.width = width + 'px';
  6836. },
  6837. setIsEnabled: function(isEnabled) {
  6838. this.isEnabled = isEnabled;
  6839. },
  6840. bringUp: function() {
  6841. if(this.el.style.zIndex !== '0') {
  6842. this.el.style.zIndex = '0';
  6843. }
  6844. },
  6845. pushDown: function() {
  6846. if(this.el.style.zIndex !== '-1') {
  6847. this.el.style.zIndex = '-1';
  6848. }
  6849. }
  6850. });
  6851. ionic.views.SideMenuContent = ionic.views.View.inherit({
  6852. initialize: function(opts) {
  6853. ionic.extend(this, {
  6854. animationClass: 'menu-animated',
  6855. onDrag: function() {},
  6856. onEndDrag: function() {}
  6857. }, opts);
  6858. ionic.onGesture('drag', ionic.proxy(this._onDrag, this), this.el);
  6859. ionic.onGesture('release', ionic.proxy(this._onEndDrag, this), this.el);
  6860. },
  6861. _onDrag: function(e) {
  6862. this.onDrag && this.onDrag(e);
  6863. },
  6864. _onEndDrag: function(e) {
  6865. this.onEndDrag && this.onEndDrag(e);
  6866. },
  6867. disableAnimation: function() {
  6868. this.el.classList.remove(this.animationClass);
  6869. },
  6870. enableAnimation: function() {
  6871. this.el.classList.add(this.animationClass);
  6872. },
  6873. getTranslateX: function() {
  6874. return parseFloat(this.el.style[ionic.CSS.TRANSFORM].replace('translate3d(', '').split(',')[0]);
  6875. },
  6876. setTranslateX: ionic.animationFrameThrottle(function(x) {
  6877. this.el.style[ionic.CSS.TRANSFORM] = 'translate3d(' + x + 'px, 0, 0)';
  6878. })
  6879. });
  6880. })(ionic);
  6881. /*
  6882. * Adapted from Swipe.js 2.0
  6883. *
  6884. * Brad Birdsall
  6885. * Copyright 2013, MIT License
  6886. *
  6887. */
  6888. (function(ionic) {
  6889. 'use strict';
  6890. ionic.views.Slider = ionic.views.View.inherit({
  6891. initialize: function (options) {
  6892. var slider = this;
  6893. var touchStartEvent, touchMoveEvent, touchEndEvent;
  6894. if (window.navigator.pointerEnabled) {
  6895. touchStartEvent = 'pointerdown';
  6896. touchMoveEvent = 'pointermove';
  6897. touchEndEvent = 'pointerup';
  6898. } else if (window.navigator.msPointerEnabled) {
  6899. touchStartEvent = 'MSPointerDown';
  6900. touchMoveEvent = 'MSPointerMove';
  6901. touchEndEvent = 'MSPointerUp';
  6902. } else {
  6903. touchStartEvent = 'touchstart';
  6904. touchMoveEvent = 'touchmove';
  6905. touchEndEvent = 'touchend';
  6906. }
  6907. var mouseStartEvent = 'mousedown';
  6908. var mouseMoveEvent = 'mousemove';
  6909. var mouseEndEvent = 'mouseup';
  6910. // utilities
  6911. var noop = function() {}; // simple no operation function
  6912. var offloadFn = function(fn) { setTimeout(fn || noop, 0); }; // offload a functions execution
  6913. // check browser capabilities
  6914. var browser = {
  6915. addEventListener: !!window.addEventListener,
  6916. transitions: (function(temp) {
  6917. var props = ['transitionProperty', 'WebkitTransition', 'MozTransition', 'OTransition', 'msTransition'];
  6918. for ( var i in props ) if (temp.style[ props[i] ] !== undefined) return true;
  6919. return false;
  6920. })(document.createElement('swipe'))
  6921. };
  6922. var container = options.el;
  6923. // quit if no root element
  6924. if (!container) return;
  6925. var element = container.children[0];
  6926. var slides, slidePos, width, length;
  6927. options = options || {};
  6928. var index = parseInt(options.startSlide, 10) || 0;
  6929. var speed = options.speed || 300;
  6930. options.continuous = options.continuous !== undefined ? options.continuous : true;
  6931. function setup() {
  6932. // do not setup if the container has no width
  6933. if (!container.offsetWidth) {
  6934. return;
  6935. }
  6936. // cache slides
  6937. slides = element.children;
  6938. length = slides.length;
  6939. // set continuous to false if only one slide
  6940. if (slides.length < 2) options.continuous = false;
  6941. //special case if two slides
  6942. if (browser.transitions && options.continuous && slides.length < 3) {
  6943. element.appendChild(slides[0].cloneNode(true));
  6944. element.appendChild(element.children[1].cloneNode(true));
  6945. slides = element.children;
  6946. }
  6947. // create an array to store current positions of each slide
  6948. slidePos = new Array(slides.length);
  6949. // determine width of each slide
  6950. width = container.offsetWidth || container.getBoundingClientRect().width;
  6951. element.style.width = (slides.length * width) + 'px';
  6952. // stack elements
  6953. var pos = slides.length;
  6954. while(pos--) {
  6955. var slide = slides[pos];
  6956. slide.style.width = width + 'px';
  6957. slide.setAttribute('data-index', pos);
  6958. if (browser.transitions) {
  6959. slide.style.left = (pos * -width) + 'px';
  6960. move(pos, index > pos ? -width : (index < pos ? width : 0), 0);
  6961. }
  6962. }
  6963. // reposition elements before and after index
  6964. if (options.continuous && browser.transitions) {
  6965. move(circle(index - 1), -width, 0);
  6966. move(circle(index + 1), width, 0);
  6967. }
  6968. if (!browser.transitions) element.style.left = (index * -width) + 'px';
  6969. container.style.visibility = 'visible';
  6970. options.slidesChanged && options.slidesChanged();
  6971. }
  6972. function prev(slideSpeed) {
  6973. if (options.continuous) slide(index - 1, slideSpeed);
  6974. else if (index) slide(index - 1, slideSpeed);
  6975. }
  6976. function next(slideSpeed) {
  6977. if (options.continuous) slide(index + 1, slideSpeed);
  6978. else if (index < slides.length - 1) slide(index + 1, slideSpeed);
  6979. }
  6980. function circle(index) {
  6981. // a simple positive modulo using slides.length
  6982. return (slides.length + (index % slides.length)) % slides.length;
  6983. }
  6984. function slide(to, slideSpeed) {
  6985. // do nothing if already on requested slide
  6986. if (index == to) return;
  6987. if (!slides) {
  6988. index = to;
  6989. return;
  6990. }
  6991. if (browser.transitions) {
  6992. var direction = Math.abs(index - to) / (index - to); // 1: backward, -1: forward
  6993. // get the actual position of the slide
  6994. if (options.continuous) {
  6995. var naturalDirection = direction;
  6996. direction = -slidePos[circle(to)] / width;
  6997. // if going forward but to < index, use to = slides.length + to
  6998. // if going backward but to > index, use to = -slides.length + to
  6999. if (direction !== naturalDirection) to = -direction * slides.length + to;
  7000. }
  7001. var diff = Math.abs(index - to) - 1;
  7002. // move all the slides between index and to in the right direction
  7003. while (diff--) move( circle((to > index ? to : index) - diff - 1), width * direction, 0);
  7004. to = circle(to);
  7005. move(index, width * direction, slideSpeed || speed);
  7006. move(to, 0, slideSpeed || speed);
  7007. if (options.continuous) move(circle(to - direction), -(width * direction), 0); // we need to get the next in place
  7008. } else {
  7009. to = circle(to);
  7010. animate(index * -width, to * -width, slideSpeed || speed);
  7011. //no fallback for a circular continuous if the browser does not accept transitions
  7012. }
  7013. index = to;
  7014. offloadFn(options.callback && options.callback(index, slides[index]));
  7015. }
  7016. function move(index, dist, speed) {
  7017. translate(index, dist, speed);
  7018. slidePos[index] = dist;
  7019. }
  7020. function translate(index, dist, speed) {
  7021. var slide = slides[index];
  7022. var style = slide && slide.style;
  7023. if (!style) return;
  7024. style.webkitTransitionDuration =
  7025. style.MozTransitionDuration =
  7026. style.msTransitionDuration =
  7027. style.OTransitionDuration =
  7028. style.transitionDuration = speed + 'ms';
  7029. style.webkitTransform = 'translate(' + dist + 'px,0)' + 'translateZ(0)';
  7030. style.msTransform =
  7031. style.MozTransform =
  7032. style.OTransform = 'translateX(' + dist + 'px)';
  7033. }
  7034. function animate(from, to, speed) {
  7035. // if not an animation, just reposition
  7036. if (!speed) {
  7037. element.style.left = to + 'px';
  7038. return;
  7039. }
  7040. var start = +new Date();
  7041. var timer = setInterval(function() {
  7042. var timeElap = +new Date() - start;
  7043. if (timeElap > speed) {
  7044. element.style.left = to + 'px';
  7045. if (delay) begin();
  7046. options.transitionEnd && options.transitionEnd.call(event, index, slides[index]);
  7047. clearInterval(timer);
  7048. return;
  7049. }
  7050. element.style.left = (( (to - from) * (Math.floor((timeElap / speed) * 100) / 100) ) + from) + 'px';
  7051. }, 4);
  7052. }
  7053. // setup auto slideshow
  7054. var delay = options.auto || 0;
  7055. var interval;
  7056. function begin() {
  7057. interval = setTimeout(next, delay);
  7058. }
  7059. function stop() {
  7060. delay = options.auto || 0;
  7061. clearTimeout(interval);
  7062. }
  7063. // setup initial vars
  7064. var start = {};
  7065. var delta = {};
  7066. var isScrolling;
  7067. // setup event capturing
  7068. var events = {
  7069. handleEvent: function(event) {
  7070. if(!event.touches && event.pageX && event.pageY) {
  7071. event.touches = [{
  7072. pageX: event.pageX,
  7073. pageY: event.pageY
  7074. }];
  7075. }
  7076. switch (event.type) {
  7077. case touchStartEvent: this.start(event); break;
  7078. case mouseStartEvent: this.start(event); break;
  7079. case touchMoveEvent: this.touchmove(event); break;
  7080. case mouseMoveEvent: this.touchmove(event); break;
  7081. case touchEndEvent: offloadFn(this.end(event)); break;
  7082. case mouseEndEvent: offloadFn(this.end(event)); break;
  7083. case 'webkitTransitionEnd':
  7084. case 'msTransitionEnd':
  7085. case 'oTransitionEnd':
  7086. case 'otransitionend':
  7087. case 'transitionend': offloadFn(this.transitionEnd(event)); break;
  7088. case 'resize': offloadFn(setup); break;
  7089. }
  7090. if (options.stopPropagation) event.stopPropagation();
  7091. },
  7092. start: function(event) {
  7093. // prevent to start if there is no valid event
  7094. if (!event.touches) {
  7095. return;
  7096. }
  7097. var touches = event.touches[0];
  7098. // measure start values
  7099. start = {
  7100. // get initial touch coords
  7101. x: touches.pageX,
  7102. y: touches.pageY,
  7103. // store time to determine touch duration
  7104. time: +new Date()
  7105. };
  7106. // used for testing first move event
  7107. isScrolling = undefined;
  7108. // reset delta and end measurements
  7109. delta = {};
  7110. // attach touchmove and touchend listeners
  7111. element.addEventListener(touchMoveEvent, this, false);
  7112. element.addEventListener(mouseMoveEvent, this, false);
  7113. element.addEventListener(touchEndEvent, this, false);
  7114. element.addEventListener(mouseEndEvent, this, false);
  7115. document.addEventListener(touchEndEvent, this, false);
  7116. document.addEventListener(mouseEndEvent, this, false);
  7117. },
  7118. touchmove: function(event) {
  7119. // ensure there is a valid event
  7120. // ensure swiping with one touch and not pinching
  7121. // ensure sliding is enabled
  7122. if (!event.touches ||
  7123. event.touches.length > 1 ||
  7124. event.scale && event.scale !== 1 ||
  7125. slider.slideIsDisabled) {
  7126. return;
  7127. }
  7128. if (options.disableScroll) event.preventDefault();
  7129. var touches = event.touches[0];
  7130. // measure change in x and y
  7131. delta = {
  7132. x: touches.pageX - start.x,
  7133. y: touches.pageY - start.y
  7134. };
  7135. // determine if scrolling test has run - one time test
  7136. if ( typeof isScrolling == 'undefined') {
  7137. isScrolling = !!( isScrolling || Math.abs(delta.x) < Math.abs(delta.y) );
  7138. }
  7139. // if user is not trying to scroll vertically
  7140. if (!isScrolling) {
  7141. // prevent native scrolling
  7142. event.preventDefault();
  7143. // stop slideshow
  7144. stop();
  7145. // increase resistance if first or last slide
  7146. if (options.continuous) { // we don't add resistance at the end
  7147. translate(circle(index - 1), delta.x + slidePos[circle(index - 1)], 0);
  7148. translate(index, delta.x + slidePos[index], 0);
  7149. translate(circle(index + 1), delta.x + slidePos[circle(index + 1)], 0);
  7150. } else {
  7151. // If the slider bounces, do the bounce!
  7152. if(options.bouncing) {
  7153. delta.x =
  7154. delta.x /
  7155. ( (!index && delta.x > 0 || // if first slide and sliding left
  7156. index == slides.length - 1 && // or if last slide and sliding right
  7157. delta.x < 0 // and if sliding at all
  7158. ) ?
  7159. ( Math.abs(delta.x) / width + 1 ) // determine resistance level
  7160. : 1 ); // no resistance if false
  7161. } else {
  7162. if(width * index - delta.x < 0) { //We are trying scroll past left boundary
  7163. delta.x = Math.min(delta.x, width * index); //Set delta.x so we don't go past left screen
  7164. }
  7165. if(Math.abs(delta.x) > width * (slides.length - index - 1)){ //We are trying to scroll past right bondary
  7166. delta.x = Math.max( -width * (slides.length - index - 1), delta.x); //Set delta.x so we don't go past right screen
  7167. }
  7168. }
  7169. // translate 1:1
  7170. translate(index - 1, delta.x + slidePos[index - 1], 0);
  7171. translate(index, delta.x + slidePos[index], 0);
  7172. translate(index + 1, delta.x + slidePos[index + 1], 0);
  7173. }
  7174. options.onDrag && options.onDrag();
  7175. }
  7176. },
  7177. end: function() {
  7178. // measure duration
  7179. var duration = +new Date() - start.time;
  7180. // determine if slide attempt triggers next/prev slide
  7181. var isValidSlide =
  7182. Number(duration) < 250 && // if slide duration is less than 250ms
  7183. Math.abs(delta.x) > 20 || // and if slide amt is greater than 20px
  7184. Math.abs(delta.x) > width / 2; // or if slide amt is greater than half the width
  7185. // determine if slide attempt is past start and end
  7186. var isPastBounds = (!index && delta.x > 0) || // if first slide and slide amt is greater than 0
  7187. (index == slides.length - 1 && delta.x < 0); // or if last slide and slide amt is less than 0
  7188. if (options.continuous) isPastBounds = false;
  7189. // determine direction of swipe (true:right, false:left)
  7190. var direction = delta.x < 0;
  7191. // if not scrolling vertically
  7192. if (!isScrolling) {
  7193. if (isValidSlide && !isPastBounds) {
  7194. if (direction) {
  7195. if (options.continuous) { // we need to get the next in this direction in place
  7196. move(circle(index - 1), -width, 0);
  7197. move(circle(index + 2), width, 0);
  7198. } else {
  7199. move(index - 1, -width, 0);
  7200. }
  7201. move(index, slidePos[index] - width, speed);
  7202. move(circle(index + 1), slidePos[circle(index + 1)] - width, speed);
  7203. index = circle(index + 1);
  7204. } else {
  7205. if (options.continuous) { // we need to get the next in this direction in place
  7206. move(circle(index + 1), width, 0);
  7207. move(circle(index - 2), -width, 0);
  7208. } else {
  7209. move(index + 1, width, 0);
  7210. }
  7211. move(index, slidePos[index] + width, speed);
  7212. move(circle(index - 1), slidePos[circle(index - 1)] + width, speed);
  7213. index = circle(index - 1);
  7214. }
  7215. options.callback && options.callback(index, slides[index]);
  7216. } else {
  7217. if (options.continuous) {
  7218. move(circle(index - 1), -width, speed);
  7219. move(index, 0, speed);
  7220. move(circle(index + 1), width, speed);
  7221. } else {
  7222. move(index - 1, -width, speed);
  7223. move(index, 0, speed);
  7224. move(index + 1, width, speed);
  7225. }
  7226. }
  7227. }
  7228. // kill touchmove and touchend event listeners until touchstart called again
  7229. element.removeEventListener(touchMoveEvent, events, false);
  7230. element.removeEventListener(mouseMoveEvent, events, false);
  7231. element.removeEventListener(touchEndEvent, events, false);
  7232. element.removeEventListener(mouseEndEvent, events, false);
  7233. document.removeEventListener(touchEndEvent, events, false);
  7234. document.removeEventListener(mouseEndEvent, events, false);
  7235. options.onDragEnd && options.onDragEnd();
  7236. },
  7237. transitionEnd: function(event) {
  7238. if (parseInt(event.target.getAttribute('data-index'), 10) == index) {
  7239. if (delay) begin();
  7240. options.transitionEnd && options.transitionEnd.call(event, index, slides[index]);
  7241. }
  7242. }
  7243. };
  7244. // Public API
  7245. this.update = function() {
  7246. setTimeout(setup);
  7247. };
  7248. this.setup = function() {
  7249. setup();
  7250. };
  7251. this.loop = function(value) {
  7252. if (arguments.length) options.continuous = !!value;
  7253. return options.continuous;
  7254. };
  7255. this.enableSlide = function(shouldEnable) {
  7256. if (arguments.length) {
  7257. this.slideIsDisabled = !shouldEnable;
  7258. }
  7259. return !this.slideIsDisabled;
  7260. };
  7261. this.slide = this.select = function(to, speed) {
  7262. // cancel slideshow
  7263. stop();
  7264. slide(to, speed);
  7265. };
  7266. this.prev = this.previous = function() {
  7267. // cancel slideshow
  7268. stop();
  7269. prev();
  7270. };
  7271. this.next = function() {
  7272. // cancel slideshow
  7273. stop();
  7274. next();
  7275. };
  7276. this.stop = function() {
  7277. // cancel slideshow
  7278. stop();
  7279. };
  7280. this.start = function() {
  7281. begin();
  7282. };
  7283. this.autoPlay = function(newDelay) {
  7284. if (!delay || delay < 0) {
  7285. stop();
  7286. } else {
  7287. delay = newDelay;
  7288. begin();
  7289. }
  7290. };
  7291. this.currentIndex = this.selected = function() {
  7292. // return current index position
  7293. return index;
  7294. };
  7295. this.slidesCount = this.count = function() {
  7296. // return total number of slides
  7297. return length;
  7298. };
  7299. this.kill = function() {
  7300. // cancel slideshow
  7301. stop();
  7302. // reset element
  7303. element.style.width = '';
  7304. element.style.left = '';
  7305. // reset slides so no refs are held on to
  7306. slides && (slides = []);
  7307. // removed event listeners
  7308. if (browser.addEventListener) {
  7309. // remove current event listeners
  7310. element.removeEventListener(touchStartEvent, events, false);
  7311. element.removeEventListener(mouseStartEvent, events, false);
  7312. element.removeEventListener('webkitTransitionEnd', events, false);
  7313. element.removeEventListener('msTransitionEnd', events, false);
  7314. element.removeEventListener('oTransitionEnd', events, false);
  7315. element.removeEventListener('otransitionend', events, false);
  7316. element.removeEventListener('transitionend', events, false);
  7317. window.removeEventListener('resize', events, false);
  7318. }
  7319. else {
  7320. window.onresize = null;
  7321. }
  7322. };
  7323. this.load = function() {
  7324. // trigger setup
  7325. setup();
  7326. // start auto slideshow if applicable
  7327. if (delay) begin();
  7328. // add event listeners
  7329. if (browser.addEventListener) {
  7330. // set touchstart event on element
  7331. element.addEventListener(touchStartEvent, events, false);
  7332. element.addEventListener(mouseStartEvent, events, false);
  7333. if (browser.transitions) {
  7334. element.addEventListener('webkitTransitionEnd', events, false);
  7335. element.addEventListener('msTransitionEnd', events, false);
  7336. element.addEventListener('oTransitionEnd', events, false);
  7337. element.addEventListener('otransitionend', events, false);
  7338. element.addEventListener('transitionend', events, false);
  7339. }
  7340. // set resize event on window
  7341. window.addEventListener('resize', events, false);
  7342. } else {
  7343. window.onresize = function () { setup(); }; // to play nice with old IE
  7344. }
  7345. };
  7346. }
  7347. });
  7348. })(ionic);
  7349. /*eslint space-after-keywords: 0*/
  7350. /**
  7351. * Swiper 3.2.7
  7352. * Most modern mobile touch slider and framework with hardware accelerated transitions
  7353. *
  7354. * http://www.idangero.us/swiper/
  7355. *
  7356. * Copyright 2015, Vladimir Kharlampidi
  7357. * The iDangero.us
  7358. * http://www.idangero.us/
  7359. *
  7360. * Licensed under MIT
  7361. *
  7362. * Released on: December 7, 2015
  7363. */
  7364. (function () {
  7365. 'use strict';
  7366. var $;
  7367. /*===========================
  7368. Swiper
  7369. ===========================*/
  7370. var Swiper = function (container, params, _scope, $compile) {
  7371. if (!(this instanceof Swiper)) return new Swiper(container, params);
  7372. var defaults = {
  7373. direction: 'horizontal',
  7374. touchEventsTarget: 'container',
  7375. initialSlide: 0,
  7376. speed: 300,
  7377. // autoplay
  7378. autoplay: false,
  7379. autoplayDisableOnInteraction: true,
  7380. // To support iOS's swipe-to-go-back gesture (when being used in-app, with UIWebView).
  7381. iOSEdgeSwipeDetection: false,
  7382. iOSEdgeSwipeThreshold: 20,
  7383. // Free mode
  7384. freeMode: false,
  7385. freeModeMomentum: true,
  7386. freeModeMomentumRatio: 1,
  7387. freeModeMomentumBounce: true,
  7388. freeModeMomentumBounceRatio: 1,
  7389. freeModeSticky: false,
  7390. freeModeMinimumVelocity: 0.02,
  7391. // Autoheight
  7392. autoHeight: false,
  7393. // Set wrapper width
  7394. setWrapperSize: false,
  7395. // Virtual Translate
  7396. virtualTranslate: false,
  7397. // Effects
  7398. effect: 'slide', // 'slide' or 'fade' or 'cube' or 'coverflow'
  7399. coverflow: {
  7400. rotate: 50,
  7401. stretch: 0,
  7402. depth: 100,
  7403. modifier: 1,
  7404. slideShadows : true
  7405. },
  7406. cube: {
  7407. slideShadows: true,
  7408. shadow: true,
  7409. shadowOffset: 20,
  7410. shadowScale: 0.94
  7411. },
  7412. fade: {
  7413. crossFade: false
  7414. },
  7415. // Parallax
  7416. parallax: false,
  7417. // Scrollbar
  7418. scrollbar: null,
  7419. scrollbarHide: true,
  7420. scrollbarDraggable: false,
  7421. scrollbarSnapOnRelease: false,
  7422. // Keyboard Mousewheel
  7423. keyboardControl: false,
  7424. mousewheelControl: false,
  7425. mousewheelReleaseOnEdges: false,
  7426. mousewheelInvert: false,
  7427. mousewheelForceToAxis: false,
  7428. mousewheelSensitivity: 1,
  7429. // Hash Navigation
  7430. hashnav: false,
  7431. // Breakpoints
  7432. breakpoints: undefined,
  7433. // Slides grid
  7434. spaceBetween: 0,
  7435. slidesPerView: 1,
  7436. slidesPerColumn: 1,
  7437. slidesPerColumnFill: 'column',
  7438. slidesPerGroup: 1,
  7439. centeredSlides: false,
  7440. slidesOffsetBefore: 0, // in px
  7441. slidesOffsetAfter: 0, // in px
  7442. // Round length
  7443. roundLengths: false,
  7444. // Touches
  7445. touchRatio: 1,
  7446. touchAngle: 45,
  7447. simulateTouch: true,
  7448. shortSwipes: true,
  7449. longSwipes: true,
  7450. longSwipesRatio: 0.5,
  7451. longSwipesMs: 300,
  7452. followFinger: true,
  7453. onlyExternal: false,
  7454. threshold: 0,
  7455. touchMoveStopPropagation: true,
  7456. // Pagination
  7457. pagination: null,
  7458. paginationElement: 'span',
  7459. paginationClickable: false,
  7460. paginationHide: false,
  7461. paginationBulletRender: null,
  7462. // Resistance
  7463. resistance: true,
  7464. resistanceRatio: 0.85,
  7465. // Next/prev buttons
  7466. nextButton: null,
  7467. prevButton: null,
  7468. // Progress
  7469. watchSlidesProgress: false,
  7470. watchSlidesVisibility: false,
  7471. // Cursor
  7472. grabCursor: false,
  7473. // Clicks
  7474. preventClicks: true,
  7475. preventClicksPropagation: true,
  7476. slideToClickedSlide: false,
  7477. // Lazy Loading
  7478. lazyLoading: false,
  7479. lazyLoadingInPrevNext: false,
  7480. lazyLoadingOnTransitionStart: false,
  7481. // Images
  7482. preloadImages: true,
  7483. updateOnImagesReady: true,
  7484. // loop
  7485. loop: false,
  7486. loopAdditionalSlides: 0,
  7487. loopedSlides: null,
  7488. // Control
  7489. control: undefined,
  7490. controlInverse: false,
  7491. controlBy: 'slide', //or 'container'
  7492. // Swiping/no swiping
  7493. allowSwipeToPrev: true,
  7494. allowSwipeToNext: true,
  7495. swipeHandler: null, //'.swipe-handler',
  7496. noSwiping: true,
  7497. noSwipingClass: 'swiper-no-swiping',
  7498. // NS
  7499. slideClass: 'swiper-slide',
  7500. slideActiveClass: 'swiper-slide-active',
  7501. slideVisibleClass: 'swiper-slide-visible',
  7502. slideDuplicateClass: 'swiper-slide-duplicate',
  7503. slideNextClass: 'swiper-slide-next',
  7504. slidePrevClass: 'swiper-slide-prev',
  7505. wrapperClass: 'swiper-wrapper',
  7506. bulletClass: 'swiper-pagination-bullet',
  7507. bulletActiveClass: 'swiper-pagination-bullet-active',
  7508. buttonDisabledClass: 'swiper-button-disabled',
  7509. paginationHiddenClass: 'swiper-pagination-hidden',
  7510. // Observer
  7511. observer: false,
  7512. observeParents: false,
  7513. // Accessibility
  7514. a11y: false,
  7515. prevSlideMessage: 'Previous slide',
  7516. nextSlideMessage: 'Next slide',
  7517. firstSlideMessage: 'This is the first slide',
  7518. lastSlideMessage: 'This is the last slide',
  7519. paginationBulletMessage: 'Go to slide {{index}}',
  7520. // Callbacks
  7521. runCallbacksOnInit: true
  7522. /*
  7523. Callbacks:
  7524. onInit: function (swiper)
  7525. onDestroy: function (swiper)
  7526. onClick: function (swiper, e)
  7527. onTap: function (swiper, e)
  7528. onDoubleTap: function (swiper, e)
  7529. onSliderMove: function (swiper, e)
  7530. onSlideChangeStart: function (swiper)
  7531. onSlideChangeEnd: function (swiper)
  7532. onTransitionStart: function (swiper)
  7533. onTransitionEnd: function (swiper)
  7534. onImagesReady: function (swiper)
  7535. onProgress: function (swiper, progress)
  7536. onTouchStart: function (swiper, e)
  7537. onTouchMove: function (swiper, e)
  7538. onTouchMoveOpposite: function (swiper, e)
  7539. onTouchEnd: function (swiper, e)
  7540. onReachBeginning: function (swiper)
  7541. onReachEnd: function (swiper)
  7542. onSetTransition: function (swiper, duration)
  7543. onSetTranslate: function (swiper, translate)
  7544. onAutoplayStart: function (swiper)
  7545. onAutoplayStop: function (swiper),
  7546. onLazyImageLoad: function (swiper, slide, image)
  7547. onLazyImageReady: function (swiper, slide, image)
  7548. */
  7549. };
  7550. var initialVirtualTranslate = params && params.virtualTranslate;
  7551. params = params || {};
  7552. var originalParams = {};
  7553. for (var param in params) {
  7554. if (typeof params[param] === 'object' && !(params[param].nodeType || params[param] === window || params[param] === document || (typeof Dom7 !== 'undefined' && params[param] instanceof Dom7) || (typeof jQuery !== 'undefined' && params[param] instanceof jQuery))) {
  7555. originalParams[param] = {};
  7556. for (var deepParam in params[param]) {
  7557. originalParams[param][deepParam] = params[param][deepParam];
  7558. }
  7559. }
  7560. else {
  7561. originalParams[param] = params[param];
  7562. }
  7563. }
  7564. for (var def in defaults) {
  7565. if (typeof params[def] === 'undefined') {
  7566. params[def] = defaults[def];
  7567. }
  7568. else if (typeof params[def] === 'object') {
  7569. for (var deepDef in defaults[def]) {
  7570. if (typeof params[def][deepDef] === 'undefined') {
  7571. params[def][deepDef] = defaults[def][deepDef];
  7572. }
  7573. }
  7574. }
  7575. }
  7576. // Swiper
  7577. var s = this;
  7578. // Params
  7579. s.params = params;
  7580. s.originalParams = originalParams;
  7581. // Classname
  7582. s.classNames = [];
  7583. /*=========================
  7584. Dom Library and plugins
  7585. ===========================*/
  7586. if (typeof $ !== 'undefined' && typeof Dom7 !== 'undefined'){
  7587. $ = Dom7;
  7588. }
  7589. if (typeof $ === 'undefined') {
  7590. if (typeof Dom7 === 'undefined') {
  7591. $ = window.Dom7 || window.Zepto || window.jQuery;
  7592. }
  7593. else {
  7594. $ = Dom7;
  7595. }
  7596. if (!$) return;
  7597. }
  7598. // Export it to Swiper instance
  7599. s.$ = $;
  7600. /*=========================
  7601. Breakpoints
  7602. ===========================*/
  7603. s.currentBreakpoint = undefined;
  7604. s.getActiveBreakpoint = function () {
  7605. //Get breakpoint for window width
  7606. if (!s.params.breakpoints) return false;
  7607. var breakpoint = false;
  7608. var points = [], point;
  7609. for ( point in s.params.breakpoints ) {
  7610. if (s.params.breakpoints.hasOwnProperty(point)) {
  7611. points.push(point);
  7612. }
  7613. }
  7614. points.sort(function (a, b) {
  7615. return parseInt(a, 10) > parseInt(b, 10);
  7616. });
  7617. for (var i = 0; i < points.length; i++) {
  7618. point = points[i];
  7619. if (point >= window.innerWidth && !breakpoint) {
  7620. breakpoint = point;
  7621. }
  7622. }
  7623. return breakpoint || 'max';
  7624. };
  7625. s.setBreakpoint = function () {
  7626. //Set breakpoint for window width and update parameters
  7627. var breakpoint = s.getActiveBreakpoint();
  7628. if (breakpoint && s.currentBreakpoint !== breakpoint) {
  7629. var breakPointsParams = breakpoint in s.params.breakpoints ? s.params.breakpoints[breakpoint] : s.originalParams;
  7630. for ( var param in breakPointsParams ) {
  7631. s.params[param] = breakPointsParams[param];
  7632. }
  7633. s.currentBreakpoint = breakpoint;
  7634. }
  7635. };
  7636. // Set breakpoint on load
  7637. if (s.params.breakpoints) {
  7638. s.setBreakpoint();
  7639. }
  7640. /*=========================
  7641. Preparation - Define Container, Wrapper and Pagination
  7642. ===========================*/
  7643. s.container = $(container);
  7644. if (s.container.length === 0) return;
  7645. if (s.container.length > 1) {
  7646. s.container.each(function () {
  7647. new Swiper(this, params);
  7648. });
  7649. return;
  7650. }
  7651. // Save instance in container HTML Element and in data
  7652. s.container[0].swiper = s;
  7653. s.container.data('swiper', s);
  7654. s.classNames.push('swiper-container-' + s.params.direction);
  7655. if (s.params.freeMode) {
  7656. s.classNames.push('swiper-container-free-mode');
  7657. }
  7658. if (!s.support.flexbox) {
  7659. s.classNames.push('swiper-container-no-flexbox');
  7660. s.params.slidesPerColumn = 1;
  7661. }
  7662. if (s.params.autoHeight) {
  7663. s.classNames.push('swiper-container-autoheight');
  7664. }
  7665. // Enable slides progress when required
  7666. if (s.params.parallax || s.params.watchSlidesVisibility) {
  7667. s.params.watchSlidesProgress = true;
  7668. }
  7669. // Coverflow / 3D
  7670. if (['cube', 'coverflow'].indexOf(s.params.effect) >= 0) {
  7671. if (s.support.transforms3d) {
  7672. s.params.watchSlidesProgress = true;
  7673. s.classNames.push('swiper-container-3d');
  7674. }
  7675. else {
  7676. s.params.effect = 'slide';
  7677. }
  7678. }
  7679. if (s.params.effect !== 'slide') {
  7680. s.classNames.push('swiper-container-' + s.params.effect);
  7681. }
  7682. if (s.params.effect === 'cube') {
  7683. s.params.resistanceRatio = 0;
  7684. s.params.slidesPerView = 1;
  7685. s.params.slidesPerColumn = 1;
  7686. s.params.slidesPerGroup = 1;
  7687. s.params.centeredSlides = false;
  7688. s.params.spaceBetween = 0;
  7689. s.params.virtualTranslate = true;
  7690. s.params.setWrapperSize = false;
  7691. }
  7692. if (s.params.effect === 'fade') {
  7693. s.params.slidesPerView = 1;
  7694. s.params.slidesPerColumn = 1;
  7695. s.params.slidesPerGroup = 1;
  7696. s.params.watchSlidesProgress = true;
  7697. s.params.spaceBetween = 0;
  7698. if (typeof initialVirtualTranslate === 'undefined') {
  7699. s.params.virtualTranslate = true;
  7700. }
  7701. }
  7702. // Grab Cursor
  7703. if (s.params.grabCursor && s.support.touch) {
  7704. s.params.grabCursor = false;
  7705. }
  7706. // Wrapper
  7707. s.wrapper = s.container.children('.' + s.params.wrapperClass);
  7708. // Pagination
  7709. if (s.params.pagination) {
  7710. s.paginationContainer = $(s.params.pagination);
  7711. if (s.params.paginationClickable) {
  7712. s.paginationContainer.addClass('swiper-pagination-clickable');
  7713. }
  7714. }
  7715. // Is Horizontal
  7716. function isH() {
  7717. return s.params.direction === 'horizontal';
  7718. }
  7719. // RTL
  7720. s.rtl = isH() && (s.container[0].dir.toLowerCase() === 'rtl' || s.container.css('direction') === 'rtl');
  7721. if (s.rtl) {
  7722. s.classNames.push('swiper-container-rtl');
  7723. }
  7724. // Wrong RTL support
  7725. if (s.rtl) {
  7726. s.wrongRTL = s.wrapper.css('display') === '-webkit-box';
  7727. }
  7728. // Columns
  7729. if (s.params.slidesPerColumn > 1) {
  7730. s.classNames.push('swiper-container-multirow');
  7731. }
  7732. // Check for Android
  7733. if (s.device.android) {
  7734. s.classNames.push('swiper-container-android');
  7735. }
  7736. // Add classes
  7737. s.container.addClass(s.classNames.join(' '));
  7738. // Translate
  7739. s.translate = 0;
  7740. // Progress
  7741. s.progress = 0;
  7742. // Velocity
  7743. s.velocity = 0;
  7744. /*=========================
  7745. Locks, unlocks
  7746. ===========================*/
  7747. s.lockSwipeToNext = function () {
  7748. s.params.allowSwipeToNext = false;
  7749. };
  7750. s.lockSwipeToPrev = function () {
  7751. s.params.allowSwipeToPrev = false;
  7752. };
  7753. s.lockSwipes = function () {
  7754. s.params.allowSwipeToNext = s.params.allowSwipeToPrev = false;
  7755. };
  7756. s.unlockSwipeToNext = function () {
  7757. s.params.allowSwipeToNext = true;
  7758. };
  7759. s.unlockSwipeToPrev = function () {
  7760. s.params.allowSwipeToPrev = true;
  7761. };
  7762. s.unlockSwipes = function () {
  7763. s.params.allowSwipeToNext = s.params.allowSwipeToPrev = true;
  7764. };
  7765. /*=========================
  7766. Round helper
  7767. ===========================*/
  7768. function round(a) {
  7769. return Math.floor(a);
  7770. }
  7771. /*=========================
  7772. Set grab cursor
  7773. ===========================*/
  7774. if (s.params.grabCursor) {
  7775. s.container[0].style.cursor = 'move';
  7776. s.container[0].style.cursor = '-webkit-grab';
  7777. s.container[0].style.cursor = '-moz-grab';
  7778. s.container[0].style.cursor = 'grab';
  7779. }
  7780. /*=========================
  7781. Update on Images Ready
  7782. ===========================*/
  7783. s.imagesToLoad = [];
  7784. s.imagesLoaded = 0;
  7785. s.loadImage = function (imgElement, src, srcset, checkForComplete, callback) {
  7786. var image;
  7787. function onReady () {
  7788. if (callback) callback();
  7789. }
  7790. if (!imgElement.complete || !checkForComplete) {
  7791. if (src) {
  7792. image = new window.Image();
  7793. image.onload = onReady;
  7794. image.onerror = onReady;
  7795. if (srcset) {
  7796. image.srcset = srcset;
  7797. }
  7798. if (src) {
  7799. image.src = src;
  7800. }
  7801. } else {
  7802. onReady();
  7803. }
  7804. } else {//image already loaded...
  7805. onReady();
  7806. }
  7807. };
  7808. s.preloadImages = function () {
  7809. s.imagesToLoad = s.container.find('img');
  7810. function _onReady() {
  7811. if (typeof s === 'undefined' || s === null) return;
  7812. if (s.imagesLoaded !== undefined) s.imagesLoaded++;
  7813. if (s.imagesLoaded === s.imagesToLoad.length) {
  7814. if (s.params.updateOnImagesReady) s.update();
  7815. s.emit('onImagesReady', s);
  7816. }
  7817. }
  7818. for (var i = 0; i < s.imagesToLoad.length; i++) {
  7819. s.loadImage(s.imagesToLoad[i], (s.imagesToLoad[i].currentSrc || s.imagesToLoad[i].getAttribute('src')), (s.imagesToLoad[i].srcset || s.imagesToLoad[i].getAttribute('srcset')), true, _onReady);
  7820. }
  7821. };
  7822. /*=========================
  7823. Autoplay
  7824. ===========================*/
  7825. s.autoplayTimeoutId = undefined;
  7826. s.autoplaying = false;
  7827. s.autoplayPaused = false;
  7828. function autoplay() {
  7829. s.autoplayTimeoutId = setTimeout(function () {
  7830. if (s.params.loop) {
  7831. s.fixLoop();
  7832. s._slideNext();
  7833. }
  7834. else {
  7835. if (!s.isEnd) {
  7836. s._slideNext();
  7837. }
  7838. else {
  7839. if (!params.autoplayStopOnLast) {
  7840. s._slideTo(0);
  7841. }
  7842. else {
  7843. s.stopAutoplay();
  7844. }
  7845. }
  7846. }
  7847. }, s.params.autoplay);
  7848. }
  7849. s.startAutoplay = function () {
  7850. if (typeof s.autoplayTimeoutId !== 'undefined') return false;
  7851. if (!s.params.autoplay) return false;
  7852. if (s.autoplaying) return false;
  7853. s.autoplaying = true;
  7854. s.emit('onAutoplayStart', s);
  7855. autoplay();
  7856. };
  7857. s.stopAutoplay = function (internal) {
  7858. if (!s.autoplayTimeoutId) return;
  7859. if (s.autoplayTimeoutId) clearTimeout(s.autoplayTimeoutId);
  7860. s.autoplaying = false;
  7861. s.autoplayTimeoutId = undefined;
  7862. s.emit('onAutoplayStop', s);
  7863. };
  7864. s.pauseAutoplay = function (speed) {
  7865. if (s.autoplayPaused) return;
  7866. if (s.autoplayTimeoutId) clearTimeout(s.autoplayTimeoutId);
  7867. s.autoplayPaused = true;
  7868. if (speed === 0) {
  7869. s.autoplayPaused = false;
  7870. autoplay();
  7871. }
  7872. else {
  7873. s.wrapper.transitionEnd(function () {
  7874. if (!s) return;
  7875. s.autoplayPaused = false;
  7876. if (!s.autoplaying) {
  7877. s.stopAutoplay();
  7878. }
  7879. else {
  7880. autoplay();
  7881. }
  7882. });
  7883. }
  7884. };
  7885. /*=========================
  7886. Min/Max Translate
  7887. ===========================*/
  7888. s.minTranslate = function () {
  7889. return (-s.snapGrid[0]);
  7890. };
  7891. s.maxTranslate = function () {
  7892. return (-s.snapGrid[s.snapGrid.length - 1]);
  7893. };
  7894. /*=========================
  7895. Slider/slides sizes
  7896. ===========================*/
  7897. s.updateAutoHeight = function () {
  7898. // Update Height
  7899. var newHeight = s.slides.eq(s.activeIndex)[0].offsetHeight;
  7900. if (newHeight) s.wrapper.css('height', s.slides.eq(s.activeIndex)[0].offsetHeight + 'px');
  7901. };
  7902. s.updateContainerSize = function () {
  7903. var width, height;
  7904. if (typeof s.params.width !== 'undefined') {
  7905. width = s.params.width;
  7906. }
  7907. else {
  7908. width = s.container[0].clientWidth;
  7909. }
  7910. if (typeof s.params.height !== 'undefined') {
  7911. height = s.params.height;
  7912. }
  7913. else {
  7914. height = s.container[0].clientHeight;
  7915. }
  7916. if (width === 0 && isH() || height === 0 && !isH()) {
  7917. return;
  7918. }
  7919. //Subtract paddings
  7920. width = width - parseInt(s.container.css('padding-left'), 10) - parseInt(s.container.css('padding-right'), 10);
  7921. height = height - parseInt(s.container.css('padding-top'), 10) - parseInt(s.container.css('padding-bottom'), 10);
  7922. // Store values
  7923. s.width = width;
  7924. s.height = height;
  7925. s.size = isH() ? s.width : s.height;
  7926. };
  7927. s.updateSlidesSize = function () {
  7928. s.slides = s.wrapper.children('.' + s.params.slideClass);
  7929. s.snapGrid = [];
  7930. s.slidesGrid = [];
  7931. s.slidesSizesGrid = [];
  7932. var spaceBetween = s.params.spaceBetween,
  7933. slidePosition = -s.params.slidesOffsetBefore,
  7934. i,
  7935. prevSlideSize = 0,
  7936. index = 0;
  7937. if (typeof spaceBetween === 'string' && spaceBetween.indexOf('%') >= 0) {
  7938. spaceBetween = parseFloat(spaceBetween.replace('%', '')) / 100 * s.size;
  7939. }
  7940. s.virtualSize = -spaceBetween;
  7941. // reset margins
  7942. if (s.rtl) s.slides.css({marginLeft: '', marginTop: ''});
  7943. else s.slides.css({marginRight: '', marginBottom: ''});
  7944. var slidesNumberEvenToRows;
  7945. if (s.params.slidesPerColumn > 1) {
  7946. if (Math.floor(s.slides.length / s.params.slidesPerColumn) === s.slides.length / s.params.slidesPerColumn) {
  7947. slidesNumberEvenToRows = s.slides.length;
  7948. }
  7949. else {
  7950. slidesNumberEvenToRows = Math.ceil(s.slides.length / s.params.slidesPerColumn) * s.params.slidesPerColumn;
  7951. }
  7952. if (s.params.slidesPerView !== 'auto' && s.params.slidesPerColumnFill === 'row') {
  7953. slidesNumberEvenToRows = Math.max(slidesNumberEvenToRows, s.params.slidesPerView * s.params.slidesPerColumn);
  7954. }
  7955. }
  7956. // Calc slides
  7957. var slideSize;
  7958. var slidesPerColumn = s.params.slidesPerColumn;
  7959. var slidesPerRow = slidesNumberEvenToRows / slidesPerColumn;
  7960. var numFullColumns = slidesPerRow - (s.params.slidesPerColumn * slidesPerRow - s.slides.length);
  7961. for (i = 0; i < s.slides.length; i++) {
  7962. slideSize = 0;
  7963. var slide = s.slides.eq(i);
  7964. if (s.params.slidesPerColumn > 1) {
  7965. // Set slides order
  7966. var newSlideOrderIndex;
  7967. var column, row;
  7968. if (s.params.slidesPerColumnFill === 'column') {
  7969. column = Math.floor(i / slidesPerColumn);
  7970. row = i - column * slidesPerColumn;
  7971. if (column > numFullColumns || (column === numFullColumns && row === slidesPerColumn-1)) {
  7972. if (++row >= slidesPerColumn) {
  7973. row = 0;
  7974. column++;
  7975. }
  7976. }
  7977. newSlideOrderIndex = column + row * slidesNumberEvenToRows / slidesPerColumn;
  7978. slide
  7979. .css({
  7980. '-webkit-box-ordinal-group': newSlideOrderIndex,
  7981. '-moz-box-ordinal-group': newSlideOrderIndex,
  7982. '-ms-flex-order': newSlideOrderIndex,
  7983. '-webkit-order': newSlideOrderIndex,
  7984. 'order': newSlideOrderIndex
  7985. });
  7986. }
  7987. else {
  7988. row = Math.floor(i / slidesPerRow);
  7989. column = i - row * slidesPerRow;
  7990. }
  7991. slide
  7992. .css({
  7993. 'margin-top': (row !== 0 && s.params.spaceBetween) && (s.params.spaceBetween + 'px')
  7994. })
  7995. .attr('data-swiper-column', column)
  7996. .attr('data-swiper-row', row);
  7997. }
  7998. if (slide.css('display') === 'none') continue;
  7999. if (s.params.slidesPerView === 'auto') {
  8000. slideSize = isH() ? slide.outerWidth(true) : slide.outerHeight(true);
  8001. if (s.params.roundLengths) slideSize = round(slideSize);
  8002. }
  8003. else {
  8004. slideSize = (s.size - (s.params.slidesPerView - 1) * spaceBetween) / s.params.slidesPerView;
  8005. if (s.params.roundLengths) slideSize = round(slideSize);
  8006. if (isH()) {
  8007. s.slides[i].style.width = slideSize + 'px';
  8008. }
  8009. else {
  8010. s.slides[i].style.height = slideSize + 'px';
  8011. }
  8012. }
  8013. s.slides[i].swiperSlideSize = slideSize;
  8014. s.slidesSizesGrid.push(slideSize);
  8015. if (s.params.centeredSlides) {
  8016. slidePosition = slidePosition + slideSize / 2 + prevSlideSize / 2 + spaceBetween;
  8017. if (i === 0) slidePosition = slidePosition - s.size / 2 - spaceBetween;
  8018. if (Math.abs(slidePosition) < 1 / 1000) slidePosition = 0;
  8019. if ((index) % s.params.slidesPerGroup === 0) s.snapGrid.push(slidePosition);
  8020. s.slidesGrid.push(slidePosition);
  8021. }
  8022. else {
  8023. if ((index) % s.params.slidesPerGroup === 0) s.snapGrid.push(slidePosition);
  8024. s.slidesGrid.push(slidePosition);
  8025. slidePosition = slidePosition + slideSize + spaceBetween;
  8026. }
  8027. s.virtualSize += slideSize + spaceBetween;
  8028. prevSlideSize = slideSize;
  8029. index ++;
  8030. }
  8031. s.virtualSize = Math.max(s.virtualSize, s.size) + s.params.slidesOffsetAfter;
  8032. var newSlidesGrid;
  8033. if (
  8034. s.rtl && s.wrongRTL && (s.params.effect === 'slide' || s.params.effect === 'coverflow')) {
  8035. s.wrapper.css({width: s.virtualSize + s.params.spaceBetween + 'px'});
  8036. }
  8037. if (!s.support.flexbox || s.params.setWrapperSize) {
  8038. if (isH()) s.wrapper.css({width: s.virtualSize + s.params.spaceBetween + 'px'});
  8039. else s.wrapper.css({height: s.virtualSize + s.params.spaceBetween + 'px'});
  8040. }
  8041. if (s.params.slidesPerColumn > 1) {
  8042. s.virtualSize = (slideSize + s.params.spaceBetween) * slidesNumberEvenToRows;
  8043. s.virtualSize = Math.ceil(s.virtualSize / s.params.slidesPerColumn) - s.params.spaceBetween;
  8044. s.wrapper.css({width: s.virtualSize + s.params.spaceBetween + 'px'});
  8045. if (s.params.centeredSlides) {
  8046. newSlidesGrid = [];
  8047. for (i = 0; i < s.snapGrid.length; i++) {
  8048. if (s.snapGrid[i] < s.virtualSize + s.snapGrid[0]) newSlidesGrid.push(s.snapGrid[i]);
  8049. }
  8050. s.snapGrid = newSlidesGrid;
  8051. }
  8052. }
  8053. // Remove last grid elements depending on width
  8054. if (!s.params.centeredSlides) {
  8055. newSlidesGrid = [];
  8056. for (i = 0; i < s.snapGrid.length; i++) {
  8057. if (s.snapGrid[i] <= s.virtualSize - s.size) {
  8058. newSlidesGrid.push(s.snapGrid[i]);
  8059. }
  8060. }
  8061. s.snapGrid = newSlidesGrid;
  8062. if (Math.floor(s.virtualSize - s.size) > Math.floor(s.snapGrid[s.snapGrid.length - 1])) {
  8063. s.snapGrid.push(s.virtualSize - s.size);
  8064. }
  8065. }
  8066. if (s.snapGrid.length === 0) s.snapGrid = [0];
  8067. if (s.params.spaceBetween !== 0) {
  8068. if (isH()) {
  8069. if (s.rtl) s.slides.css({marginLeft: spaceBetween + 'px'});
  8070. else s.slides.css({marginRight: spaceBetween + 'px'});
  8071. }
  8072. else s.slides.css({marginBottom: spaceBetween + 'px'});
  8073. }
  8074. if (s.params.watchSlidesProgress) {
  8075. s.updateSlidesOffset();
  8076. }
  8077. };
  8078. s.updateSlidesOffset = function () {
  8079. for (var i = 0; i < s.slides.length; i++) {
  8080. s.slides[i].swiperSlideOffset = isH() ? s.slides[i].offsetLeft : s.slides[i].offsetTop;
  8081. }
  8082. };
  8083. /*=========================
  8084. Slider/slides progress
  8085. ===========================*/
  8086. s.updateSlidesProgress = function (translate) {
  8087. if (typeof translate === 'undefined') {
  8088. translate = s.translate || 0;
  8089. }
  8090. if (s.slides.length === 0) return;
  8091. if (typeof s.slides[0].swiperSlideOffset === 'undefined') s.updateSlidesOffset();
  8092. var offsetCenter = -translate;
  8093. if (s.rtl) offsetCenter = translate;
  8094. // Visible Slides
  8095. s.slides.removeClass(s.params.slideVisibleClass);
  8096. for (var i = 0; i < s.slides.length; i++) {
  8097. var slide = s.slides[i];
  8098. var slideProgress = (offsetCenter - slide.swiperSlideOffset) / (slide.swiperSlideSize + s.params.spaceBetween);
  8099. if (s.params.watchSlidesVisibility) {
  8100. var slideBefore = -(offsetCenter - slide.swiperSlideOffset);
  8101. var slideAfter = slideBefore + s.slidesSizesGrid[i];
  8102. var isVisible =
  8103. (slideBefore >= 0 && slideBefore < s.size) ||
  8104. (slideAfter > 0 && slideAfter <= s.size) ||
  8105. (slideBefore <= 0 && slideAfter >= s.size);
  8106. if (isVisible) {
  8107. s.slides.eq(i).addClass(s.params.slideVisibleClass);
  8108. }
  8109. }
  8110. slide.progress = s.rtl ? -slideProgress : slideProgress;
  8111. }
  8112. };
  8113. s.updateProgress = function (translate) {
  8114. if (typeof translate === 'undefined') {
  8115. translate = s.translate || 0;
  8116. }
  8117. var translatesDiff = s.maxTranslate() - s.minTranslate();
  8118. var wasBeginning = s.isBeginning;
  8119. var wasEnd = s.isEnd;
  8120. if (translatesDiff === 0) {
  8121. s.progress = 0;
  8122. s.isBeginning = s.isEnd = true;
  8123. }
  8124. else {
  8125. s.progress = (translate - s.minTranslate()) / (translatesDiff);
  8126. s.isBeginning = s.progress <= 0;
  8127. s.isEnd = s.progress >= 1;
  8128. }
  8129. if (s.isBeginning && !wasBeginning) s.emit('onReachBeginning', s);
  8130. if (s.isEnd && !wasEnd) s.emit('onReachEnd', s);
  8131. if (s.params.watchSlidesProgress) s.updateSlidesProgress(translate);
  8132. s.emit('onProgress', s, s.progress);
  8133. };
  8134. s.updateActiveIndex = function () {
  8135. var translate = s.rtl ? s.translate : -s.translate;
  8136. var newActiveIndex, i, snapIndex;
  8137. for (i = 0; i < s.slidesGrid.length; i ++) {
  8138. if (typeof s.slidesGrid[i + 1] !== 'undefined') {
  8139. if (translate >= s.slidesGrid[i] && translate < s.slidesGrid[i + 1] - (s.slidesGrid[i + 1] - s.slidesGrid[i]) / 2) {
  8140. newActiveIndex = i;
  8141. }
  8142. else if (translate >= s.slidesGrid[i] && translate < s.slidesGrid[i + 1]) {
  8143. newActiveIndex = i + 1;
  8144. }
  8145. }
  8146. else {
  8147. if (translate >= s.slidesGrid[i]) {
  8148. newActiveIndex = i;
  8149. }
  8150. }
  8151. }
  8152. // Normalize slideIndex
  8153. if (newActiveIndex < 0 || typeof newActiveIndex === 'undefined') newActiveIndex = 0;
  8154. // for (i = 0; i < s.slidesGrid.length; i++) {
  8155. // if (- translate >= s.slidesGrid[i]) {
  8156. // newActiveIndex = i;
  8157. // }
  8158. // }
  8159. snapIndex = Math.floor(newActiveIndex / s.params.slidesPerGroup);
  8160. if (snapIndex >= s.snapGrid.length) snapIndex = s.snapGrid.length - 1;
  8161. if (newActiveIndex === s.activeIndex) {
  8162. return;
  8163. }
  8164. s.snapIndex = snapIndex;
  8165. s.previousIndex = s.activeIndex;
  8166. s.activeIndex = newActiveIndex;
  8167. s.updateClasses();
  8168. };
  8169. /*=========================
  8170. Classes
  8171. ===========================*/
  8172. s.updateClasses = function () {
  8173. s.slides.removeClass(s.params.slideActiveClass + ' ' + s.params.slideNextClass + ' ' + s.params.slidePrevClass);
  8174. var activeSlide = s.slides.eq(s.activeIndex);
  8175. // Active classes
  8176. activeSlide.addClass(s.params.slideActiveClass);
  8177. activeSlide.next('.' + s.params.slideClass).addClass(s.params.slideNextClass);
  8178. activeSlide.prev('.' + s.params.slideClass).addClass(s.params.slidePrevClass);
  8179. // Pagination
  8180. if (s.bullets && s.bullets.length > 0) {
  8181. s.bullets.removeClass(s.params.bulletActiveClass);
  8182. var bulletIndex;
  8183. if (s.params.loop) {
  8184. bulletIndex = Math.ceil(s.activeIndex - s.loopedSlides)/s.params.slidesPerGroup;
  8185. if (bulletIndex > s.slides.length - 1 - s.loopedSlides * 2) {
  8186. bulletIndex = bulletIndex - (s.slides.length - s.loopedSlides * 2);
  8187. }
  8188. if (bulletIndex > s.bullets.length - 1) bulletIndex = bulletIndex - s.bullets.length;
  8189. }
  8190. else {
  8191. if (typeof s.snapIndex !== 'undefined') {
  8192. bulletIndex = s.snapIndex;
  8193. }
  8194. else {
  8195. bulletIndex = s.activeIndex || 0;
  8196. }
  8197. }
  8198. if (s.paginationContainer.length > 1) {
  8199. s.bullets.each(function () {
  8200. if ($(this).index() === bulletIndex) $(this).addClass(s.params.bulletActiveClass);
  8201. });
  8202. }
  8203. else {
  8204. s.bullets.eq(bulletIndex).addClass(s.params.bulletActiveClass);
  8205. }
  8206. }
  8207. // Next/active buttons
  8208. if (!s.params.loop) {
  8209. if (s.params.prevButton) {
  8210. if (s.isBeginning) {
  8211. $(s.params.prevButton).addClass(s.params.buttonDisabledClass);
  8212. if (s.params.a11y && s.a11y) s.a11y.disable($(s.params.prevButton));
  8213. }
  8214. else {
  8215. $(s.params.prevButton).removeClass(s.params.buttonDisabledClass);
  8216. if (s.params.a11y && s.a11y) s.a11y.enable($(s.params.prevButton));
  8217. }
  8218. }
  8219. if (s.params.nextButton) {
  8220. if (s.isEnd) {
  8221. $(s.params.nextButton).addClass(s.params.buttonDisabledClass);
  8222. if (s.params.a11y && s.a11y) s.a11y.disable($(s.params.nextButton));
  8223. }
  8224. else {
  8225. $(s.params.nextButton).removeClass(s.params.buttonDisabledClass);
  8226. if (s.params.a11y && s.a11y) s.a11y.enable($(s.params.nextButton));
  8227. }
  8228. }
  8229. }
  8230. };
  8231. /*=========================
  8232. Pagination
  8233. ===========================*/
  8234. s.updatePagination = function () {
  8235. if (!s.params.pagination) return;
  8236. if (s.paginationContainer && s.paginationContainer.length > 0) {
  8237. var bulletsHTML = '';
  8238. var numberOfBullets = s.params.loop ? Math.ceil((s.slides.length - s.loopedSlides * 2) / s.params.slidesPerGroup) : s.snapGrid.length;
  8239. for (var i = 0; i < numberOfBullets; i++) {
  8240. if (s.params.paginationBulletRender) {
  8241. bulletsHTML += s.params.paginationBulletRender(i, s.params.bulletClass);
  8242. }
  8243. else {
  8244. bulletsHTML += '<' + s.params.paginationElement+' class="' + s.params.bulletClass + '"></' + s.params.paginationElement + '>';
  8245. }
  8246. }
  8247. s.paginationContainer.html(bulletsHTML);
  8248. s.bullets = s.paginationContainer.find('.' + s.params.bulletClass);
  8249. if (s.params.paginationClickable && s.params.a11y && s.a11y) {
  8250. s.a11y.initPagination();
  8251. }
  8252. }
  8253. };
  8254. /*=========================
  8255. Common update method
  8256. ===========================*/
  8257. s.update = function (updateTranslate) {
  8258. s.updateContainerSize();
  8259. s.updateSlidesSize();
  8260. s.updateProgress();
  8261. s.updatePagination();
  8262. s.updateClasses();
  8263. if (s.params.scrollbar && s.scrollbar) {
  8264. s.scrollbar.set();
  8265. }
  8266. function forceSetTranslate() {
  8267. newTranslate = Math.min(Math.max(s.translate, s.maxTranslate()), s.minTranslate());
  8268. s.setWrapperTranslate(newTranslate);
  8269. s.updateActiveIndex();
  8270. s.updateClasses();
  8271. }
  8272. if (updateTranslate) {
  8273. var translated, newTranslate;
  8274. if (s.controller && s.controller.spline) {
  8275. s.controller.spline = undefined;
  8276. }
  8277. if (s.params.freeMode) {
  8278. forceSetTranslate();
  8279. if (s.params.autoHeight) {
  8280. s.updateAutoHeight();
  8281. }
  8282. }
  8283. else {
  8284. if ((s.params.slidesPerView === 'auto' || s.params.slidesPerView > 1) && s.isEnd && !s.params.centeredSlides) {
  8285. translated = s.slideTo(s.slides.length - 1, 0, false, true);
  8286. }
  8287. else {
  8288. translated = s.slideTo(s.activeIndex, 0, false, true);
  8289. }
  8290. if (!translated) {
  8291. forceSetTranslate();
  8292. }
  8293. }
  8294. }
  8295. else if (s.params.autoHeight) {
  8296. s.updateAutoHeight();
  8297. }
  8298. };
  8299. /*=========================
  8300. Resize Handler
  8301. ===========================*/
  8302. s.onResize = function (forceUpdatePagination) {
  8303. //Breakpoints
  8304. if (s.params.breakpoints) {
  8305. s.setBreakpoint();
  8306. }
  8307. // Disable locks on resize
  8308. var allowSwipeToPrev = s.params.allowSwipeToPrev;
  8309. var allowSwipeToNext = s.params.allowSwipeToNext;
  8310. s.params.allowSwipeToPrev = s.params.allowSwipeToNext = true;
  8311. s.updateContainerSize();
  8312. s.updateSlidesSize();
  8313. if (s.params.slidesPerView === 'auto' || s.params.freeMode || forceUpdatePagination) s.updatePagination();
  8314. if (s.params.scrollbar && s.scrollbar) {
  8315. s.scrollbar.set();
  8316. }
  8317. if (s.controller && s.controller.spline) {
  8318. s.controller.spline = undefined;
  8319. }
  8320. if (s.params.freeMode) {
  8321. var newTranslate = Math.min(Math.max(s.translate, s.maxTranslate()), s.minTranslate());
  8322. s.setWrapperTranslate(newTranslate);
  8323. s.updateActiveIndex();
  8324. s.updateClasses();
  8325. if (s.params.autoHeight) {
  8326. s.updateAutoHeight();
  8327. }
  8328. }
  8329. else {
  8330. s.updateClasses();
  8331. if ((s.params.slidesPerView === 'auto' || s.params.slidesPerView > 1) && s.isEnd && !s.params.centeredSlides) {
  8332. s.slideTo(s.slides.length - 1, 0, false, true);
  8333. }
  8334. else {
  8335. s.slideTo(s.activeIndex, 0, false, true);
  8336. }
  8337. }
  8338. // Return locks after resize
  8339. s.params.allowSwipeToPrev = allowSwipeToPrev;
  8340. s.params.allowSwipeToNext = allowSwipeToNext;
  8341. };
  8342. /*=========================
  8343. Events
  8344. ===========================*/
  8345. //Define Touch Events
  8346. var desktopEvents = ['mousedown', 'mousemove', 'mouseup'];
  8347. if (window.navigator.pointerEnabled) desktopEvents = ['pointerdown', 'pointermove', 'pointerup'];
  8348. else if (window.navigator.msPointerEnabled) desktopEvents = ['MSPointerDown', 'MSPointerMove', 'MSPointerUp'];
  8349. s.touchEvents = {
  8350. start : s.support.touch || !s.params.simulateTouch ? 'touchstart' : desktopEvents[0],
  8351. move : s.support.touch || !s.params.simulateTouch ? 'touchmove' : desktopEvents[1],
  8352. end : s.support.touch || !s.params.simulateTouch ? 'touchend' : desktopEvents[2]
  8353. };
  8354. // WP8 Touch Events Fix
  8355. if (window.navigator.pointerEnabled || window.navigator.msPointerEnabled) {
  8356. (s.params.touchEventsTarget === 'container' ? s.container : s.wrapper).addClass('swiper-wp8-' + s.params.direction);
  8357. }
  8358. // Attach/detach events
  8359. s.initEvents = function (detach) {
  8360. var actionDom = detach ? 'off' : 'on';
  8361. var action = detach ? 'removeEventListener' : 'addEventListener';
  8362. var touchEventsTarget = s.params.touchEventsTarget === 'container' ? s.container[0] : s.wrapper[0];
  8363. var target = s.support.touch ? touchEventsTarget : document;
  8364. var moveCapture = s.params.nested ? true : false;
  8365. //Touch Events
  8366. if (s.browser.ie) {
  8367. touchEventsTarget[action](s.touchEvents.start, s.onTouchStart, false);
  8368. target[action](s.touchEvents.move, s.onTouchMove, moveCapture);
  8369. target[action](s.touchEvents.end, s.onTouchEnd, false);
  8370. }
  8371. else {
  8372. if (s.support.touch) {
  8373. touchEventsTarget[action](s.touchEvents.start, s.onTouchStart, false);
  8374. touchEventsTarget[action](s.touchEvents.move, s.onTouchMove, moveCapture);
  8375. touchEventsTarget[action](s.touchEvents.end, s.onTouchEnd, false);
  8376. }
  8377. if (params.simulateTouch && !s.device.ios && !s.device.android) {
  8378. touchEventsTarget[action]('mousedown', s.onTouchStart, false);
  8379. document[action]('mousemove', s.onTouchMove, moveCapture);
  8380. document[action]('mouseup', s.onTouchEnd, false);
  8381. }
  8382. }
  8383. window[action]('resize', s.onResize);
  8384. // Next, Prev, Index
  8385. if (s.params.nextButton) {
  8386. $(s.params.nextButton)[actionDom]('click', s.onClickNext);
  8387. if (s.params.a11y && s.a11y) $(s.params.nextButton)[actionDom]('keydown', s.a11y.onEnterKey);
  8388. }
  8389. if (s.params.prevButton) {
  8390. $(s.params.prevButton)[actionDom]('click', s.onClickPrev);
  8391. if (s.params.a11y && s.a11y) $(s.params.prevButton)[actionDom]('keydown', s.a11y.onEnterKey);
  8392. }
  8393. if (s.params.pagination && s.params.paginationClickable) {
  8394. $(s.paginationContainer)[actionDom]('click', '.' + s.params.bulletClass, s.onClickIndex);
  8395. if (s.params.a11y && s.a11y) $(s.paginationContainer)[actionDom]('keydown', '.' + s.params.bulletClass, s.a11y.onEnterKey);
  8396. }
  8397. // Prevent Links Clicks
  8398. if (s.params.preventClicks || s.params.preventClicksPropagation) touchEventsTarget[action]('click', s.preventClicks, true);
  8399. };
  8400. s.attachEvents = function (detach) {
  8401. s.initEvents();
  8402. };
  8403. s.detachEvents = function () {
  8404. s.initEvents(true);
  8405. };
  8406. /*=========================
  8407. Handle Clicks
  8408. ===========================*/
  8409. // Prevent Clicks
  8410. s.allowClick = true;
  8411. s.preventClicks = function (e) {
  8412. if (!s.allowClick) {
  8413. if (s.params.preventClicks) e.preventDefault();
  8414. if (s.params.preventClicksPropagation && s.animating) {
  8415. e.stopPropagation();
  8416. e.stopImmediatePropagation();
  8417. }
  8418. }
  8419. };
  8420. // Clicks
  8421. s.onClickNext = function (e) {
  8422. e.preventDefault();
  8423. if (s.isEnd && !s.params.loop) return;
  8424. s.slideNext();
  8425. };
  8426. s.onClickPrev = function (e) {
  8427. e.preventDefault();
  8428. if (s.isBeginning && !s.params.loop) return;
  8429. s.slidePrev();
  8430. };
  8431. s.onClickIndex = function (e) {
  8432. e.preventDefault();
  8433. var index = $(this).index() * s.params.slidesPerGroup;
  8434. if (s.params.loop) index = index + s.loopedSlides;
  8435. s.slideTo(index);
  8436. };
  8437. /*=========================
  8438. Handle Touches
  8439. ===========================*/
  8440. function findElementInEvent(e, selector) {
  8441. var el = $(e.target);
  8442. if (!el.is(selector)) {
  8443. if (typeof selector === 'string') {
  8444. el = el.parents(selector);
  8445. }
  8446. else if (selector.nodeType) {
  8447. var found;
  8448. el.parents().each(function (index, _el) {
  8449. if (_el === selector) found = selector;
  8450. });
  8451. if (!found) return undefined;
  8452. else return selector;
  8453. }
  8454. }
  8455. if (el.length === 0) {
  8456. return undefined;
  8457. }
  8458. return el[0];
  8459. }
  8460. s.updateClickedSlide = function (e) {
  8461. var slide = findElementInEvent(e, '.' + s.params.slideClass);
  8462. var slideFound = false;
  8463. if (slide) {
  8464. for (var i = 0; i < s.slides.length; i++) {
  8465. if (s.slides[i] === slide) slideFound = true;
  8466. }
  8467. }
  8468. if (slide && slideFound) {
  8469. s.clickedSlide = slide;
  8470. s.clickedIndex = $(slide).index();
  8471. }
  8472. else {
  8473. s.clickedSlide = undefined;
  8474. s.clickedIndex = undefined;
  8475. return;
  8476. }
  8477. if (s.params.slideToClickedSlide && s.clickedIndex !== undefined && s.clickedIndex !== s.activeIndex) {
  8478. var slideToIndex = s.clickedIndex,
  8479. realIndex,
  8480. duplicatedSlides;
  8481. if (s.params.loop) {
  8482. if (s.animating) return;
  8483. realIndex = $(s.clickedSlide).attr('data-swiper-slide-index');
  8484. if (s.params.centeredSlides) {
  8485. if ((slideToIndex < s.loopedSlides - s.params.slidesPerView/2) || (slideToIndex > s.slides.length - s.loopedSlides + s.params.slidesPerView/2)) {
  8486. s.fixLoop();
  8487. slideToIndex = s.wrapper.children('.' + s.params.slideClass + '[data-swiper-slide-index="' + realIndex + '"]:not(.swiper-slide-duplicate)').eq(0).index();
  8488. setTimeout(function () {
  8489. s.slideTo(slideToIndex);
  8490. }, 0);
  8491. }
  8492. else {
  8493. s.slideTo(slideToIndex);
  8494. }
  8495. }
  8496. else {
  8497. if (slideToIndex > s.slides.length - s.params.slidesPerView) {
  8498. s.fixLoop();
  8499. slideToIndex = s.wrapper.children('.' + s.params.slideClass + '[data-swiper-slide-index="' + realIndex + '"]:not(.swiper-slide-duplicate)').eq(0).index();
  8500. setTimeout(function () {
  8501. s.slideTo(slideToIndex);
  8502. }, 0);
  8503. }
  8504. else {
  8505. s.slideTo(slideToIndex);
  8506. }
  8507. }
  8508. }
  8509. else {
  8510. s.slideTo(slideToIndex);
  8511. }
  8512. }
  8513. };
  8514. var isTouched,
  8515. isMoved,
  8516. allowTouchCallbacks,
  8517. touchStartTime,
  8518. isScrolling,
  8519. currentTranslate,
  8520. startTranslate,
  8521. allowThresholdMove,
  8522. // Form elements to match
  8523. formElements = 'input, select, textarea, button',
  8524. // Last click time
  8525. lastClickTime = Date.now(), clickTimeout,
  8526. //Velocities
  8527. velocities = [],
  8528. allowMomentumBounce;
  8529. // Animating Flag
  8530. s.animating = false;
  8531. // Touches information
  8532. s.touches = {
  8533. startX: 0,
  8534. startY: 0,
  8535. currentX: 0,
  8536. currentY: 0,
  8537. diff: 0
  8538. };
  8539. // Touch handlers
  8540. var isTouchEvent, startMoving;
  8541. s.onTouchStart = function (e) {
  8542. if (e.originalEvent) e = e.originalEvent;
  8543. isTouchEvent = e.type === 'touchstart';
  8544. if (!isTouchEvent && 'which' in e && e.which === 3) return;
  8545. if (s.params.noSwiping && findElementInEvent(e, '.' + s.params.noSwipingClass)) {
  8546. s.allowClick = true;
  8547. return;
  8548. }
  8549. if (s.params.swipeHandler) {
  8550. if (!findElementInEvent(e, s.params.swipeHandler)) return;
  8551. }
  8552. var startX = s.touches.currentX = e.type === 'touchstart' ? e.targetTouches[0].pageX : e.pageX;
  8553. var startY = s.touches.currentY = e.type === 'touchstart' ? e.targetTouches[0].pageY : e.pageY;
  8554. // Do NOT start if iOS edge swipe is detected. Otherwise iOS app (UIWebView) cannot swipe-to-go-back anymore
  8555. if(s.device.ios && s.params.iOSEdgeSwipeDetection && startX <= s.params.iOSEdgeSwipeThreshold) {
  8556. return;
  8557. }
  8558. isTouched = true;
  8559. isMoved = false;
  8560. allowTouchCallbacks = true;
  8561. isScrolling = undefined;
  8562. startMoving = undefined;
  8563. s.touches.startX = startX;
  8564. s.touches.startY = startY;
  8565. touchStartTime = Date.now();
  8566. s.allowClick = true;
  8567. s.updateContainerSize();
  8568. s.swipeDirection = undefined;
  8569. if (s.params.threshold > 0) allowThresholdMove = false;
  8570. if (e.type !== 'touchstart') {
  8571. var preventDefault = true;
  8572. if ($(e.target).is(formElements)) preventDefault = false;
  8573. if (document.activeElement && $(document.activeElement).is(formElements)) {
  8574. document.activeElement.blur();
  8575. }
  8576. if (preventDefault) {
  8577. e.preventDefault();
  8578. }
  8579. }
  8580. s.emit('onTouchStart', s, e);
  8581. };
  8582. s.onTouchMove = function (e) {
  8583. if (e.originalEvent) e = e.originalEvent;
  8584. if (isTouchEvent && e.type === 'mousemove') return;
  8585. if (e.preventedByNestedSwiper) return;
  8586. if (s.params.onlyExternal) {
  8587. // isMoved = true;
  8588. s.allowClick = false;
  8589. if (isTouched) {
  8590. s.touches.startX = s.touches.currentX = e.type === 'touchmove' ? e.targetTouches[0].pageX : e.pageX;
  8591. s.touches.startY = s.touches.currentY = e.type === 'touchmove' ? e.targetTouches[0].pageY : e.pageY;
  8592. touchStartTime = Date.now();
  8593. }
  8594. return;
  8595. }
  8596. if (isTouchEvent && document.activeElement) {
  8597. if (e.target === document.activeElement && $(e.target).is(formElements)) {
  8598. isMoved = true;
  8599. s.allowClick = false;
  8600. return;
  8601. }
  8602. }
  8603. if (allowTouchCallbacks) {
  8604. s.emit('onTouchMove', s, e);
  8605. }
  8606. if (e.targetTouches && e.targetTouches.length > 1) return;
  8607. s.touches.currentX = e.type === 'touchmove' ? e.targetTouches[0].pageX : e.pageX;
  8608. s.touches.currentY = e.type === 'touchmove' ? e.targetTouches[0].pageY : e.pageY;
  8609. if (typeof isScrolling === 'undefined') {
  8610. var touchAngle = Math.atan2(Math.abs(s.touches.currentY - s.touches.startY), Math.abs(s.touches.currentX - s.touches.startX)) * 180 / Math.PI;
  8611. isScrolling = isH() ? touchAngle > s.params.touchAngle : (90 - touchAngle > s.params.touchAngle);
  8612. }
  8613. if (isScrolling) {
  8614. s.emit('onTouchMoveOpposite', s, e);
  8615. }
  8616. if (typeof startMoving === 'undefined' && s.browser.ieTouch) {
  8617. if (s.touches.currentX !== s.touches.startX || s.touches.currentY !== s.touches.startY) {
  8618. startMoving = true;
  8619. }
  8620. }
  8621. if (!isTouched) return;
  8622. if (isScrolling) {
  8623. isTouched = false;
  8624. return;
  8625. }
  8626. if (!startMoving && s.browser.ieTouch) {
  8627. return;
  8628. }
  8629. s.allowClick = false;
  8630. s.emit('onSliderMove', s, e);
  8631. e.preventDefault();
  8632. if (s.params.touchMoveStopPropagation && !s.params.nested) {
  8633. e.stopPropagation();
  8634. }
  8635. if (!isMoved) {
  8636. if (params.loop) {
  8637. s.fixLoop();
  8638. }
  8639. startTranslate = s.getWrapperTranslate();
  8640. s.setWrapperTransition(0);
  8641. if (s.animating) {
  8642. s.wrapper.trigger('webkitTransitionEnd transitionend oTransitionEnd MSTransitionEnd msTransitionEnd');
  8643. }
  8644. if (s.params.autoplay && s.autoplaying) {
  8645. if (s.params.autoplayDisableOnInteraction) {
  8646. s.stopAutoplay();
  8647. }
  8648. else {
  8649. s.pauseAutoplay();
  8650. }
  8651. }
  8652. allowMomentumBounce = false;
  8653. //Grab Cursor
  8654. if (s.params.grabCursor) {
  8655. s.container[0].style.cursor = 'move';
  8656. s.container[0].style.cursor = '-webkit-grabbing';
  8657. s.container[0].style.cursor = '-moz-grabbin';
  8658. s.container[0].style.cursor = 'grabbing';
  8659. }
  8660. }
  8661. isMoved = true;
  8662. var diff = s.touches.diff = isH() ? s.touches.currentX - s.touches.startX : s.touches.currentY - s.touches.startY;
  8663. diff = diff * s.params.touchRatio;
  8664. if (s.rtl) diff = -diff;
  8665. s.swipeDirection = diff > 0 ? 'prev' : 'next';
  8666. currentTranslate = diff + startTranslate;
  8667. var disableParentSwiper = true;
  8668. if ((diff > 0 && currentTranslate > s.minTranslate())) {
  8669. disableParentSwiper = false;
  8670. if (s.params.resistance) currentTranslate = s.minTranslate() - 1 + Math.pow(-s.minTranslate() + startTranslate + diff, s.params.resistanceRatio);
  8671. }
  8672. else if (diff < 0 && currentTranslate < s.maxTranslate()) {
  8673. disableParentSwiper = false;
  8674. if (s.params.resistance) currentTranslate = s.maxTranslate() + 1 - Math.pow(s.maxTranslate() - startTranslate - diff, s.params.resistanceRatio);
  8675. }
  8676. if (disableParentSwiper) {
  8677. e.preventedByNestedSwiper = true;
  8678. }
  8679. // Directions locks
  8680. if (!s.params.allowSwipeToNext && s.swipeDirection === 'next' && currentTranslate < startTranslate) {
  8681. currentTranslate = startTranslate;
  8682. }
  8683. if (!s.params.allowSwipeToPrev && s.swipeDirection === 'prev' && currentTranslate > startTranslate) {
  8684. currentTranslate = startTranslate;
  8685. }
  8686. if (!s.params.followFinger) return;
  8687. // Threshold
  8688. if (s.params.threshold > 0) {
  8689. if (Math.abs(diff) > s.params.threshold || allowThresholdMove) {
  8690. if (!allowThresholdMove) {
  8691. allowThresholdMove = true;
  8692. s.touches.startX = s.touches.currentX;
  8693. s.touches.startY = s.touches.currentY;
  8694. currentTranslate = startTranslate;
  8695. s.touches.diff = isH() ? s.touches.currentX - s.touches.startX : s.touches.currentY - s.touches.startY;
  8696. return;
  8697. }
  8698. }
  8699. else {
  8700. currentTranslate = startTranslate;
  8701. return;
  8702. }
  8703. }
  8704. // Update active index in free mode
  8705. if (s.params.freeMode || s.params.watchSlidesProgress) {
  8706. s.updateActiveIndex();
  8707. }
  8708. if (s.params.freeMode) {
  8709. //Velocity
  8710. if (velocities.length === 0) {
  8711. velocities.push({
  8712. position: s.touches[isH() ? 'startX' : 'startY'],
  8713. time: touchStartTime
  8714. });
  8715. }
  8716. velocities.push({
  8717. position: s.touches[isH() ? 'currentX' : 'currentY'],
  8718. time: (new window.Date()).getTime()
  8719. });
  8720. }
  8721. // Update progress
  8722. s.updateProgress(currentTranslate);
  8723. // Update translate
  8724. s.setWrapperTranslate(currentTranslate);
  8725. };
  8726. s.onTouchEnd = function (e) {
  8727. if (e.originalEvent) e = e.originalEvent;
  8728. if (allowTouchCallbacks) {
  8729. s.emit('onTouchEnd', s, e);
  8730. }
  8731. allowTouchCallbacks = false;
  8732. if (!isTouched) return;
  8733. //Return Grab Cursor
  8734. if (s.params.grabCursor && isMoved && isTouched) {
  8735. s.container[0].style.cursor = 'move';
  8736. s.container[0].style.cursor = '-webkit-grab';
  8737. s.container[0].style.cursor = '-moz-grab';
  8738. s.container[0].style.cursor = 'grab';
  8739. }
  8740. // Time diff
  8741. var touchEndTime = Date.now();
  8742. var timeDiff = touchEndTime - touchStartTime;
  8743. // Tap, doubleTap, Click
  8744. if (s.allowClick) {
  8745. s.updateClickedSlide(e);
  8746. s.emit('onTap', s, e);
  8747. if (timeDiff < 300 && (touchEndTime - lastClickTime) > 300) {
  8748. if (clickTimeout) clearTimeout(clickTimeout);
  8749. clickTimeout = setTimeout(function () {
  8750. if (!s) return;
  8751. if (s.params.paginationHide && s.paginationContainer.length > 0 && !$(e.target).hasClass(s.params.bulletClass)) {
  8752. s.paginationContainer.toggleClass(s.params.paginationHiddenClass);
  8753. }
  8754. s.emit('onClick', s, e);
  8755. }, 300);
  8756. }
  8757. if (timeDiff < 300 && (touchEndTime - lastClickTime) < 300) {
  8758. if (clickTimeout) clearTimeout(clickTimeout);
  8759. s.emit('onDoubleTap', s, e);
  8760. }
  8761. }
  8762. lastClickTime = Date.now();
  8763. setTimeout(function () {
  8764. if (s) s.allowClick = true;
  8765. }, 0);
  8766. if (!isTouched || !isMoved || !s.swipeDirection || s.touches.diff === 0 || currentTranslate === startTranslate) {
  8767. isTouched = isMoved = false;
  8768. return;
  8769. }
  8770. isTouched = isMoved = false;
  8771. var currentPos;
  8772. if (s.params.followFinger) {
  8773. currentPos = s.rtl ? s.translate : -s.translate;
  8774. }
  8775. else {
  8776. currentPos = -currentTranslate;
  8777. }
  8778. if (s.params.freeMode) {
  8779. if (currentPos < -s.minTranslate()) {
  8780. s.slideTo(s.activeIndex);
  8781. return;
  8782. }
  8783. else if (currentPos > -s.maxTranslate()) {
  8784. if (s.slides.length < s.snapGrid.length) {
  8785. s.slideTo(s.snapGrid.length - 1);
  8786. }
  8787. else {
  8788. s.slideTo(s.slides.length - 1);
  8789. }
  8790. return;
  8791. }
  8792. if (s.params.freeModeMomentum) {
  8793. if (velocities.length > 1) {
  8794. var lastMoveEvent = velocities.pop(), velocityEvent = velocities.pop();
  8795. var distance = lastMoveEvent.position - velocityEvent.position;
  8796. var time = lastMoveEvent.time - velocityEvent.time;
  8797. s.velocity = distance / time;
  8798. s.velocity = s.velocity / 2;
  8799. if (Math.abs(s.velocity) < s.params.freeModeMinimumVelocity) {
  8800. s.velocity = 0;
  8801. }
  8802. // this implies that the user stopped moving a finger then released.
  8803. // There would be no events with distance zero, so the last event is stale.
  8804. if (time > 150 || (new window.Date().getTime() - lastMoveEvent.time) > 300) {
  8805. s.velocity = 0;
  8806. }
  8807. } else {
  8808. s.velocity = 0;
  8809. }
  8810. velocities.length = 0;
  8811. var momentumDuration = 1000 * s.params.freeModeMomentumRatio;
  8812. var momentumDistance = s.velocity * momentumDuration;
  8813. var newPosition = s.translate + momentumDistance;
  8814. if (s.rtl) newPosition = - newPosition;
  8815. var doBounce = false;
  8816. var afterBouncePosition;
  8817. var bounceAmount = Math.abs(s.velocity) * 20 * s.params.freeModeMomentumBounceRatio;
  8818. if (newPosition < s.maxTranslate()) {
  8819. if (s.params.freeModeMomentumBounce) {
  8820. if (newPosition + s.maxTranslate() < -bounceAmount) {
  8821. newPosition = s.maxTranslate() - bounceAmount;
  8822. }
  8823. afterBouncePosition = s.maxTranslate();
  8824. doBounce = true;
  8825. allowMomentumBounce = true;
  8826. }
  8827. else {
  8828. newPosition = s.maxTranslate();
  8829. }
  8830. }
  8831. else if (newPosition > s.minTranslate()) {
  8832. if (s.params.freeModeMomentumBounce) {
  8833. if (newPosition - s.minTranslate() > bounceAmount) {
  8834. newPosition = s.minTranslate() + bounceAmount;
  8835. }
  8836. afterBouncePosition = s.minTranslate();
  8837. doBounce = true;
  8838. allowMomentumBounce = true;
  8839. }
  8840. else {
  8841. newPosition = s.minTranslate();
  8842. }
  8843. }
  8844. else if (s.params.freeModeSticky) {
  8845. var j = 0,
  8846. nextSlide;
  8847. for (j = 0; j < s.snapGrid.length; j += 1) {
  8848. if (s.snapGrid[j] > -newPosition) {
  8849. nextSlide = j;
  8850. break;
  8851. }
  8852. }
  8853. if (Math.abs(s.snapGrid[nextSlide] - newPosition) < Math.abs(s.snapGrid[nextSlide - 1] - newPosition) || s.swipeDirection === 'next') {
  8854. newPosition = s.snapGrid[nextSlide];
  8855. } else {
  8856. newPosition = s.snapGrid[nextSlide - 1];
  8857. }
  8858. if (!s.rtl) newPosition = - newPosition;
  8859. }
  8860. //Fix duration
  8861. if (s.velocity !== 0) {
  8862. if (s.rtl) {
  8863. momentumDuration = Math.abs((-newPosition - s.translate) / s.velocity);
  8864. }
  8865. else {
  8866. momentumDuration = Math.abs((newPosition - s.translate) / s.velocity);
  8867. }
  8868. }
  8869. else if (s.params.freeModeSticky) {
  8870. s.slideReset();
  8871. return;
  8872. }
  8873. if (s.params.freeModeMomentumBounce && doBounce) {
  8874. s.updateProgress(afterBouncePosition);
  8875. s.setWrapperTransition(momentumDuration);
  8876. s.setWrapperTranslate(newPosition);
  8877. s.onTransitionStart();
  8878. s.animating = true;
  8879. s.wrapper.transitionEnd(function () {
  8880. if (!s || !allowMomentumBounce) return;
  8881. s.emit('onMomentumBounce', s);
  8882. s.setWrapperTransition(s.params.speed);
  8883. s.setWrapperTranslate(afterBouncePosition);
  8884. s.wrapper.transitionEnd(function () {
  8885. if (!s) return;
  8886. s.onTransitionEnd();
  8887. });
  8888. });
  8889. } else if (s.velocity) {
  8890. s.updateProgress(newPosition);
  8891. s.setWrapperTransition(momentumDuration);
  8892. s.setWrapperTranslate(newPosition);
  8893. s.onTransitionStart();
  8894. if (!s.animating) {
  8895. s.animating = true;
  8896. s.wrapper.transitionEnd(function () {
  8897. if (!s) return;
  8898. s.onTransitionEnd();
  8899. });
  8900. }
  8901. } else {
  8902. s.updateProgress(newPosition);
  8903. }
  8904. s.updateActiveIndex();
  8905. }
  8906. if (!s.params.freeModeMomentum || timeDiff >= s.params.longSwipesMs) {
  8907. s.updateProgress();
  8908. s.updateActiveIndex();
  8909. }
  8910. return;
  8911. }
  8912. // Find current slide
  8913. var i, stopIndex = 0, groupSize = s.slidesSizesGrid[0];
  8914. for (i = 0; i < s.slidesGrid.length; i += s.params.slidesPerGroup) {
  8915. if (typeof s.slidesGrid[i + s.params.slidesPerGroup] !== 'undefined') {
  8916. if (currentPos >= s.slidesGrid[i] && currentPos < s.slidesGrid[i + s.params.slidesPerGroup]) {
  8917. stopIndex = i;
  8918. groupSize = s.slidesGrid[i + s.params.slidesPerGroup] - s.slidesGrid[i];
  8919. }
  8920. }
  8921. else {
  8922. if (currentPos >= s.slidesGrid[i]) {
  8923. stopIndex = i;
  8924. groupSize = s.slidesGrid[s.slidesGrid.length - 1] - s.slidesGrid[s.slidesGrid.length - 2];
  8925. }
  8926. }
  8927. }
  8928. // Find current slide size
  8929. var ratio = (currentPos - s.slidesGrid[stopIndex]) / groupSize;
  8930. if (timeDiff > s.params.longSwipesMs) {
  8931. // Long touches
  8932. if (!s.params.longSwipes) {
  8933. s.slideTo(s.activeIndex);
  8934. return;
  8935. }
  8936. if (s.swipeDirection === 'next') {
  8937. if (ratio >= s.params.longSwipesRatio) s.slideTo(stopIndex + s.params.slidesPerGroup);
  8938. else s.slideTo(stopIndex);
  8939. }
  8940. if (s.swipeDirection === 'prev') {
  8941. if (ratio > (1 - s.params.longSwipesRatio)) s.slideTo(stopIndex + s.params.slidesPerGroup);
  8942. else s.slideTo(stopIndex);
  8943. }
  8944. }
  8945. else {
  8946. // Short swipes
  8947. if (!s.params.shortSwipes) {
  8948. s.slideTo(s.activeIndex);
  8949. return;
  8950. }
  8951. if (s.swipeDirection === 'next') {
  8952. s.slideTo(stopIndex + s.params.slidesPerGroup);
  8953. }
  8954. if (s.swipeDirection === 'prev') {
  8955. s.slideTo(stopIndex);
  8956. }
  8957. }
  8958. };
  8959. /*=========================
  8960. Transitions
  8961. ===========================*/
  8962. s._slideTo = function (slideIndex, speed) {
  8963. return s.slideTo(slideIndex, speed, true, true);
  8964. };
  8965. s.slideTo = function (slideIndex, speed, runCallbacks, internal) {
  8966. if (typeof runCallbacks === 'undefined') runCallbacks = true;
  8967. if (typeof slideIndex === 'undefined') slideIndex = 0;
  8968. if (slideIndex < 0) slideIndex = 0;
  8969. s.snapIndex = Math.floor(slideIndex / s.params.slidesPerGroup);
  8970. if (s.snapIndex >= s.snapGrid.length) s.snapIndex = s.snapGrid.length - 1;
  8971. var translate = - s.snapGrid[s.snapIndex];
  8972. // Stop autoplay
  8973. if (s.params.autoplay && s.autoplaying) {
  8974. if (internal || !s.params.autoplayDisableOnInteraction) {
  8975. s.pauseAutoplay(speed);
  8976. }
  8977. else {
  8978. s.stopAutoplay();
  8979. }
  8980. }
  8981. // Update progress
  8982. s.updateProgress(translate);
  8983. // Normalize slideIndex
  8984. for (var i = 0; i < s.slidesGrid.length; i++) {
  8985. if (- Math.floor(translate * 100) >= Math.floor(s.slidesGrid[i] * 100)) {
  8986. slideIndex = i;
  8987. }
  8988. }
  8989. // Directions locks
  8990. if (!s.params.allowSwipeToNext && translate < s.translate && translate < s.minTranslate()) {
  8991. return false;
  8992. }
  8993. if (!s.params.allowSwipeToPrev && translate > s.translate && translate > s.maxTranslate()) {
  8994. if ((s.activeIndex || 0) !== slideIndex ) return false;
  8995. }
  8996. // Update Index
  8997. if (typeof speed === 'undefined') speed = s.params.speed;
  8998. s.previousIndex = s.activeIndex || 0;
  8999. s.activeIndex = slideIndex;
  9000. if ((s.rtl && -translate === s.translate) || (!s.rtl && translate === s.translate)) {
  9001. // Update Height
  9002. if (s.params.autoHeight) {
  9003. s.updateAutoHeight();
  9004. }
  9005. s.updateClasses();
  9006. if (s.params.effect !== 'slide') {
  9007. s.setWrapperTranslate(translate);
  9008. }
  9009. return false;
  9010. }
  9011. s.updateClasses();
  9012. s.onTransitionStart(runCallbacks);
  9013. if (speed === 0) {
  9014. s.setWrapperTranslate(translate);
  9015. s.setWrapperTransition(0);
  9016. s.onTransitionEnd(runCallbacks);
  9017. }
  9018. else {
  9019. s.setWrapperTranslate(translate);
  9020. s.setWrapperTransition(speed);
  9021. if (!s.animating) {
  9022. s.animating = true;
  9023. s.wrapper.transitionEnd(function () {
  9024. if (!s) return;
  9025. s.onTransitionEnd(runCallbacks);
  9026. });
  9027. }
  9028. }
  9029. return true;
  9030. };
  9031. s.onTransitionStart = function (runCallbacks) {
  9032. if (typeof runCallbacks === 'undefined') runCallbacks = true;
  9033. if (s.params.autoHeight) {
  9034. s.updateAutoHeight();
  9035. }
  9036. if (s.lazy) s.lazy.onTransitionStart();
  9037. if (runCallbacks) {
  9038. s.emit('onTransitionStart', s);
  9039. if (s.activeIndex !== s.previousIndex) {
  9040. s.emit('onSlideChangeStart', s);
  9041. _scope.$emit("$ionicSlides.slideChangeStart", {
  9042. slider: s,
  9043. activeIndex: s.getSlideDataIndex(s.activeIndex),
  9044. previousIndex: s.getSlideDataIndex(s.previousIndex)
  9045. });
  9046. if (s.activeIndex > s.previousIndex) {
  9047. s.emit('onSlideNextStart', s);
  9048. }
  9049. else {
  9050. s.emit('onSlidePrevStart', s);
  9051. }
  9052. }
  9053. }
  9054. };
  9055. s.onTransitionEnd = function (runCallbacks) {
  9056. s.animating = false;
  9057. s.setWrapperTransition(0);
  9058. if (typeof runCallbacks === 'undefined') runCallbacks = true;
  9059. if (s.lazy) s.lazy.onTransitionEnd();
  9060. if (runCallbacks) {
  9061. s.emit('onTransitionEnd', s);
  9062. if (s.activeIndex !== s.previousIndex) {
  9063. s.emit('onSlideChangeEnd', s);
  9064. _scope.$emit("$ionicSlides.slideChangeEnd", {
  9065. slider: s,
  9066. activeIndex: s.getSlideDataIndex(s.activeIndex),
  9067. previousIndex: s.getSlideDataIndex(s.previousIndex)
  9068. });
  9069. if (s.activeIndex > s.previousIndex) {
  9070. s.emit('onSlideNextEnd', s);
  9071. }
  9072. else {
  9073. s.emit('onSlidePrevEnd', s);
  9074. }
  9075. }
  9076. }
  9077. if (s.params.hashnav && s.hashnav) {
  9078. s.hashnav.setHash();
  9079. }
  9080. };
  9081. s.slideNext = function (runCallbacks, speed, internal) {
  9082. if (s.params.loop) {
  9083. if (s.animating) return false;
  9084. s.fixLoop();
  9085. var clientLeft = s.container[0].clientLeft;
  9086. return s.slideTo(s.activeIndex + s.params.slidesPerGroup, speed, runCallbacks, internal);
  9087. }
  9088. else return s.slideTo(s.activeIndex + s.params.slidesPerGroup, speed, runCallbacks, internal);
  9089. };
  9090. s._slideNext = function (speed) {
  9091. return s.slideNext(true, speed, true);
  9092. };
  9093. s.slidePrev = function (runCallbacks, speed, internal) {
  9094. if (s.params.loop) {
  9095. if (s.animating) return false;
  9096. s.fixLoop();
  9097. var clientLeft = s.container[0].clientLeft;
  9098. return s.slideTo(s.activeIndex - 1, speed, runCallbacks, internal);
  9099. }
  9100. else return s.slideTo(s.activeIndex - 1, speed, runCallbacks, internal);
  9101. };
  9102. s._slidePrev = function (speed) {
  9103. return s.slidePrev(true, speed, true);
  9104. };
  9105. s.slideReset = function (runCallbacks, speed, internal) {
  9106. return s.slideTo(s.activeIndex, speed, runCallbacks);
  9107. };
  9108. /*=========================
  9109. Translate/transition helpers
  9110. ===========================*/
  9111. s.setWrapperTransition = function (duration, byController) {
  9112. s.wrapper.transition(duration);
  9113. if (s.params.effect !== 'slide' && s.effects[s.params.effect]) {
  9114. s.effects[s.params.effect].setTransition(duration);
  9115. }
  9116. if (s.params.parallax && s.parallax) {
  9117. s.parallax.setTransition(duration);
  9118. }
  9119. if (s.params.scrollbar && s.scrollbar) {
  9120. s.scrollbar.setTransition(duration);
  9121. }
  9122. if (s.params.control && s.controller) {
  9123. s.controller.setTransition(duration, byController);
  9124. }
  9125. s.emit('onSetTransition', s, duration);
  9126. };
  9127. s.setWrapperTranslate = function (translate, updateActiveIndex, byController) {
  9128. var x = 0, y = 0, z = 0;
  9129. if (isH()) {
  9130. x = s.rtl ? -translate : translate;
  9131. }
  9132. else {
  9133. y = translate;
  9134. }
  9135. if (s.params.roundLengths) {
  9136. x = round(x);
  9137. y = round(y);
  9138. }
  9139. if (!s.params.virtualTranslate) {
  9140. if (s.support.transforms3d) s.wrapper.transform('translate3d(' + x + 'px, ' + y + 'px, ' + z + 'px)');
  9141. else s.wrapper.transform('translate(' + x + 'px, ' + y + 'px)');
  9142. }
  9143. s.translate = isH() ? x : y;
  9144. // Check if we need to update progress
  9145. var progress;
  9146. var translatesDiff = s.maxTranslate() - s.minTranslate();
  9147. if (translatesDiff === 0) {
  9148. progress = 0;
  9149. }
  9150. else {
  9151. progress = (translate - s.minTranslate()) / (translatesDiff);
  9152. }
  9153. if (progress !== s.progress) {
  9154. s.updateProgress(translate);
  9155. }
  9156. if (updateActiveIndex) s.updateActiveIndex();
  9157. if (s.params.effect !== 'slide' && s.effects[s.params.effect]) {
  9158. s.effects[s.params.effect].setTranslate(s.translate);
  9159. }
  9160. if (s.params.parallax && s.parallax) {
  9161. s.parallax.setTranslate(s.translate);
  9162. }
  9163. if (s.params.scrollbar && s.scrollbar) {
  9164. s.scrollbar.setTranslate(s.translate);
  9165. }
  9166. if (s.params.control && s.controller) {
  9167. s.controller.setTranslate(s.translate, byController);
  9168. }
  9169. s.emit('onSetTranslate', s, s.translate);
  9170. };
  9171. s.getTranslate = function (el, axis) {
  9172. var matrix, curTransform, curStyle, transformMatrix;
  9173. // automatic axis detection
  9174. if (typeof axis === 'undefined') {
  9175. axis = 'x';
  9176. }
  9177. if (s.params.virtualTranslate) {
  9178. return s.rtl ? -s.translate : s.translate;
  9179. }
  9180. curStyle = window.getComputedStyle(el, null);
  9181. if (window.WebKitCSSMatrix) {
  9182. curTransform = curStyle.transform || curStyle.webkitTransform;
  9183. if (curTransform.split(',').length > 6) {
  9184. curTransform = curTransform.split(', ').map(function(a){
  9185. return a.replace(',','.');
  9186. }).join(', ');
  9187. }
  9188. // Some old versions of Webkit choke when 'none' is passed; pass
  9189. // empty string instead in this case
  9190. transformMatrix = new window.WebKitCSSMatrix(curTransform === 'none' ? '' : curTransform);
  9191. }
  9192. else {
  9193. transformMatrix = curStyle.MozTransform || curStyle.OTransform || curStyle.MsTransform || curStyle.msTransform || curStyle.transform || curStyle.getPropertyValue('transform').replace('translate(', 'matrix(1, 0, 0, 1,');
  9194. matrix = transformMatrix.toString().split(',');
  9195. }
  9196. if (axis === 'x') {
  9197. //Latest Chrome and webkits Fix
  9198. if (window.WebKitCSSMatrix)
  9199. curTransform = transformMatrix.m41;
  9200. //Crazy IE10 Matrix
  9201. else if (matrix.length === 16)
  9202. curTransform = parseFloat(matrix[12]);
  9203. //Normal Browsers
  9204. else
  9205. curTransform = parseFloat(matrix[4]);
  9206. }
  9207. if (axis === 'y') {
  9208. //Latest Chrome and webkits Fix
  9209. if (window.WebKitCSSMatrix)
  9210. curTransform = transformMatrix.m42;
  9211. //Crazy IE10 Matrix
  9212. else if (matrix.length === 16)
  9213. curTransform = parseFloat(matrix[13]);
  9214. //Normal Browsers
  9215. else
  9216. curTransform = parseFloat(matrix[5]);
  9217. }
  9218. if (s.rtl && curTransform) curTransform = -curTransform;
  9219. return curTransform || 0;
  9220. };
  9221. s.getWrapperTranslate = function (axis) {
  9222. if (typeof axis === 'undefined') {
  9223. axis = isH() ? 'x' : 'y';
  9224. }
  9225. return s.getTranslate(s.wrapper[0], axis);
  9226. };
  9227. /*=========================
  9228. Observer
  9229. ===========================*/
  9230. s.observers = [];
  9231. function initObserver(target, options) {
  9232. options = options || {};
  9233. // create an observer instance
  9234. var ObserverFunc = window.MutationObserver || window.WebkitMutationObserver;
  9235. var observer = new ObserverFunc(function (mutations) {
  9236. mutations.forEach(function (mutation) {
  9237. s.onResize(true);
  9238. s.emit('onObserverUpdate', s, mutation);
  9239. });
  9240. });
  9241. observer.observe(target, {
  9242. attributes: typeof options.attributes === 'undefined' ? true : options.attributes,
  9243. childList: typeof options.childList === 'undefined' ? true : options.childList,
  9244. characterData: typeof options.characterData === 'undefined' ? true : options.characterData
  9245. });
  9246. s.observers.push(observer);
  9247. }
  9248. s.initObservers = function () {
  9249. if (s.params.observeParents) {
  9250. var containerParents = s.container.parents();
  9251. for (var i = 0; i < containerParents.length; i++) {
  9252. initObserver(containerParents[i]);
  9253. }
  9254. }
  9255. // Observe container
  9256. initObserver(s.container[0], {childList: false});
  9257. // Observe wrapper
  9258. initObserver(s.wrapper[0], {attributes: false});
  9259. };
  9260. s.disconnectObservers = function () {
  9261. for (var i = 0; i < s.observers.length; i++) {
  9262. s.observers[i].disconnect();
  9263. }
  9264. s.observers = [];
  9265. };
  9266. s.updateLoop = function(){
  9267. var currentSlide = s.slides.eq(s.activeIndex);
  9268. if ( angular.element(currentSlide).hasClass(s.params.slideDuplicateClass) ){
  9269. // we're on a duplicate, so slide to the non-duplicate
  9270. var swiperSlideIndex = angular.element(currentSlide).attr("data-swiper-slide-index");
  9271. var slides = s.wrapper.children('.' + s.params.slideClass);
  9272. for ( var i = 0; i < slides.length; i++ ){
  9273. if ( !angular.element(slides[i]).hasClass(s.params.slideDuplicateClass) && angular.element(slides[i]).attr("data-swiper-slide-index") === swiperSlideIndex ){
  9274. s.slideTo(i, 0, false, true);
  9275. break;
  9276. }
  9277. }
  9278. // if we needed to switch slides, we did that. So, now call the createLoop function internally
  9279. setTimeout(function(){
  9280. s.createLoop();
  9281. }, 50);
  9282. }
  9283. }
  9284. s.getSlideDataIndex = function(slideIndex){
  9285. // this is an Ionic custom function
  9286. // Swiper loops utilize duplicate DOM elements for slides when in a loop
  9287. // which means that we cannot rely on the actual slide index for our events
  9288. // because index 0 does not necessarily point to index 0
  9289. // and index n+1 does not necessarily point to the expected piece of data
  9290. // therefore, rather than using the actual slide index we should
  9291. // use the data index that swiper includes as an attribute on the dom elements
  9292. // because this is what will be meaningful to the consumer of our events
  9293. var slide = s.slides.eq(slideIndex);
  9294. var attributeIndex = angular.element(slide).attr("data-swiper-slide-index");
  9295. return parseInt(attributeIndex);
  9296. }
  9297. /*=========================
  9298. Loop
  9299. ===========================*/
  9300. // Create looped slides
  9301. s.createLoop = function () {
  9302. //console.log("Slider create loop method");
  9303. //var toRemove = s.wrapper.children('.' + s.params.slideClass + '.' + s.params.slideDuplicateClass);
  9304. //angular.element(toRemove).remove();
  9305. s.wrapper.children('.' + s.params.slideClass + '.' + s.params.slideDuplicateClass).remove();
  9306. var slides = s.wrapper.children('.' + s.params.slideClass);
  9307. if(s.params.slidesPerView === 'auto' && !s.params.loopedSlides) s.params.loopedSlides = slides.length;
  9308. s.loopedSlides = parseInt(s.params.loopedSlides || s.params.slidesPerView, 10);
  9309. s.loopedSlides = s.loopedSlides + s.params.loopAdditionalSlides;
  9310. if (s.loopedSlides > slides.length) {
  9311. s.loopedSlides = slides.length;
  9312. }
  9313. var prependSlides = [], appendSlides = [], i, scope, newNode;
  9314. slides.each(function (index, el) {
  9315. var slide = $(this);
  9316. if (index < s.loopedSlides) appendSlides.push(el);
  9317. if (index < slides.length && index >= slides.length - s.loopedSlides) prependSlides.push(el);
  9318. slide.attr('data-swiper-slide-index', index);
  9319. });
  9320. for (i = 0; i < appendSlides.length; i++) {
  9321. newNode = angular.element(appendSlides[i]).clone().addClass(s.params.slideDuplicateClass);
  9322. newNode.removeAttr('ng-transclude');
  9323. newNode.removeAttr('ng-repeat');
  9324. scope = angular.element(appendSlides[i]).scope();
  9325. newNode = $compile(newNode)(scope);
  9326. angular.element(s.wrapper).append(newNode);
  9327. //s.wrapper.append($(appendSlides[i].cloneNode(true)).addClass(s.params.slideDuplicateClass));
  9328. }
  9329. for (i = prependSlides.length - 1; i >= 0; i--) {
  9330. //s.wrapper.prepend($(prependSlides[i].cloneNode(true)).addClass(s.params.slideDuplicateClass));
  9331. newNode = angular.element(prependSlides[i]).clone().addClass(s.params.slideDuplicateClass);
  9332. newNode.removeAttr('ng-transclude');
  9333. newNode.removeAttr('ng-repeat');
  9334. scope = angular.element(prependSlides[i]).scope();
  9335. newNode = $compile(newNode)(scope);
  9336. angular.element(s.wrapper).prepend(newNode);
  9337. }
  9338. };
  9339. s.destroyLoop = function () {
  9340. s.wrapper.children('.' + s.params.slideClass + '.' + s.params.slideDuplicateClass).remove();
  9341. s.slides.removeAttr('data-swiper-slide-index');
  9342. };
  9343. s.fixLoop = function () {
  9344. var newIndex;
  9345. //Fix For Negative Oversliding
  9346. if (s.activeIndex < s.loopedSlides) {
  9347. newIndex = s.slides.length - s.loopedSlides * 3 + s.activeIndex;
  9348. newIndex = newIndex + s.loopedSlides;
  9349. s.slideTo(newIndex, 0, false, true);
  9350. }
  9351. //Fix For Positive Oversliding
  9352. else if ((s.params.slidesPerView === 'auto' && s.activeIndex >= s.loopedSlides * 2) || (s.activeIndex > s.slides.length - s.params.slidesPerView * 2)) {
  9353. newIndex = -s.slides.length + s.activeIndex + s.loopedSlides;
  9354. newIndex = newIndex + s.loopedSlides;
  9355. s.slideTo(newIndex, 0, false, true);
  9356. }
  9357. };
  9358. /*=========================
  9359. Append/Prepend/Remove Slides
  9360. ===========================*/
  9361. s.appendSlide = function (slides) {
  9362. if (s.params.loop) {
  9363. s.destroyLoop();
  9364. }
  9365. if (typeof slides === 'object' && slides.length) {
  9366. for (var i = 0; i < slides.length; i++) {
  9367. if (slides[i]) s.wrapper.append(slides[i]);
  9368. }
  9369. }
  9370. else {
  9371. s.wrapper.append(slides);
  9372. }
  9373. if (s.params.loop) {
  9374. s.createLoop();
  9375. }
  9376. if (!(s.params.observer && s.support.observer)) {
  9377. s.update(true);
  9378. }
  9379. };
  9380. s.prependSlide = function (slides) {
  9381. if (s.params.loop) {
  9382. s.destroyLoop();
  9383. }
  9384. var newActiveIndex = s.activeIndex + 1;
  9385. if (typeof slides === 'object' && slides.length) {
  9386. for (var i = 0; i < slides.length; i++) {
  9387. if (slides[i]) s.wrapper.prepend(slides[i]);
  9388. }
  9389. newActiveIndex = s.activeIndex + slides.length;
  9390. }
  9391. else {
  9392. s.wrapper.prepend(slides);
  9393. }
  9394. if (s.params.loop) {
  9395. s.createLoop();
  9396. }
  9397. if (!(s.params.observer && s.support.observer)) {
  9398. s.update(true);
  9399. }
  9400. s.slideTo(newActiveIndex, 0, false);
  9401. };
  9402. s.removeSlide = function (slidesIndexes) {
  9403. if (s.params.loop) {
  9404. s.destroyLoop();
  9405. s.slides = s.wrapper.children('.' + s.params.slideClass);
  9406. }
  9407. var newActiveIndex = s.activeIndex,
  9408. indexToRemove;
  9409. if (typeof slidesIndexes === 'object' && slidesIndexes.length) {
  9410. for (var i = 0; i < slidesIndexes.length; i++) {
  9411. indexToRemove = slidesIndexes[i];
  9412. if (s.slides[indexToRemove]) s.slides.eq(indexToRemove).remove();
  9413. if (indexToRemove < newActiveIndex) newActiveIndex--;
  9414. }
  9415. newActiveIndex = Math.max(newActiveIndex, 0);
  9416. }
  9417. else {
  9418. indexToRemove = slidesIndexes;
  9419. if (s.slides[indexToRemove]) s.slides.eq(indexToRemove).remove();
  9420. if (indexToRemove < newActiveIndex) newActiveIndex--;
  9421. newActiveIndex = Math.max(newActiveIndex, 0);
  9422. }
  9423. if (s.params.loop) {
  9424. s.createLoop();
  9425. }
  9426. if (!(s.params.observer && s.support.observer)) {
  9427. s.update(true);
  9428. }
  9429. if (s.params.loop) {
  9430. s.slideTo(newActiveIndex + s.loopedSlides, 0, false);
  9431. }
  9432. else {
  9433. s.slideTo(newActiveIndex, 0, false);
  9434. }
  9435. };
  9436. s.removeAllSlides = function () {
  9437. var slidesIndexes = [];
  9438. for (var i = 0; i < s.slides.length; i++) {
  9439. slidesIndexes.push(i);
  9440. }
  9441. s.removeSlide(slidesIndexes);
  9442. };
  9443. /*=========================
  9444. Effects
  9445. ===========================*/
  9446. s.effects = {
  9447. fade: {
  9448. setTranslate: function () {
  9449. for (var i = 0; i < s.slides.length; i++) {
  9450. var slide = s.slides.eq(i);
  9451. var offset = slide[0].swiperSlideOffset;
  9452. var tx = -offset;
  9453. if (!s.params.virtualTranslate) tx = tx - s.translate;
  9454. var ty = 0;
  9455. if (!isH()) {
  9456. ty = tx;
  9457. tx = 0;
  9458. }
  9459. var slideOpacity = s.params.fade.crossFade ?
  9460. Math.max(1 - Math.abs(slide[0].progress), 0) :
  9461. 1 + Math.min(Math.max(slide[0].progress, -1), 0);
  9462. slide
  9463. .css({
  9464. opacity: slideOpacity
  9465. })
  9466. .transform('translate3d(' + tx + 'px, ' + ty + 'px, 0px)');
  9467. }
  9468. },
  9469. setTransition: function (duration) {
  9470. s.slides.transition(duration);
  9471. if (s.params.virtualTranslate && duration !== 0) {
  9472. var eventTriggered = false;
  9473. s.slides.transitionEnd(function () {
  9474. if (eventTriggered) return;
  9475. if (!s) return;
  9476. eventTriggered = true;
  9477. s.animating = false;
  9478. var triggerEvents = ['webkitTransitionEnd', 'transitionend', 'oTransitionEnd', 'MSTransitionEnd', 'msTransitionEnd'];
  9479. for (var i = 0; i < triggerEvents.length; i++) {
  9480. s.wrapper.trigger(triggerEvents[i]);
  9481. }
  9482. });
  9483. }
  9484. }
  9485. },
  9486. cube: {
  9487. setTranslate: function () {
  9488. var wrapperRotate = 0, cubeShadow;
  9489. if (s.params.cube.shadow) {
  9490. if (isH()) {
  9491. cubeShadow = s.wrapper.find('.swiper-cube-shadow');
  9492. if (cubeShadow.length === 0) {
  9493. cubeShadow = $('<div class="swiper-cube-shadow"></div>');
  9494. s.wrapper.append(cubeShadow);
  9495. }
  9496. cubeShadow.css({height: s.width + 'px'});
  9497. }
  9498. else {
  9499. cubeShadow = s.container.find('.swiper-cube-shadow');
  9500. if (cubeShadow.length === 0) {
  9501. cubeShadow = $('<div class="swiper-cube-shadow"></div>');
  9502. s.container.append(cubeShadow);
  9503. }
  9504. }
  9505. }
  9506. for (var i = 0; i < s.slides.length; i++) {
  9507. var slide = s.slides.eq(i);
  9508. var slideAngle = i * 90;
  9509. var round = Math.floor(slideAngle / 360);
  9510. if (s.rtl) {
  9511. slideAngle = -slideAngle;
  9512. round = Math.floor(-slideAngle / 360);
  9513. }
  9514. var progress = Math.max(Math.min(slide[0].progress, 1), -1);
  9515. var tx = 0, ty = 0, tz = 0;
  9516. if (i % 4 === 0) {
  9517. tx = - round * 4 * s.size;
  9518. tz = 0;
  9519. }
  9520. else if ((i - 1) % 4 === 0) {
  9521. tx = 0;
  9522. tz = - round * 4 * s.size;
  9523. }
  9524. else if ((i - 2) % 4 === 0) {
  9525. tx = s.size + round * 4 * s.size;
  9526. tz = s.size;
  9527. }
  9528. else if ((i - 3) % 4 === 0) {
  9529. tx = - s.size;
  9530. tz = 3 * s.size + s.size * 4 * round;
  9531. }
  9532. if (s.rtl) {
  9533. tx = -tx;
  9534. }
  9535. if (!isH()) {
  9536. ty = tx;
  9537. tx = 0;
  9538. }
  9539. var transform = 'rotateX(' + (isH() ? 0 : -slideAngle) + 'deg) rotateY(' + (isH() ? slideAngle : 0) + 'deg) translate3d(' + tx + 'px, ' + ty + 'px, ' + tz + 'px)';
  9540. if (progress <= 1 && progress > -1) {
  9541. wrapperRotate = i * 90 + progress * 90;
  9542. if (s.rtl) wrapperRotate = -i * 90 - progress * 90;
  9543. }
  9544. slide.transform(transform);
  9545. if (s.params.cube.slideShadows) {
  9546. //Set shadows
  9547. var shadowBefore = isH() ? slide.find('.swiper-slide-shadow-left') : slide.find('.swiper-slide-shadow-top');
  9548. var shadowAfter = isH() ? slide.find('.swiper-slide-shadow-right') : slide.find('.swiper-slide-shadow-bottom');
  9549. if (shadowBefore.length === 0) {
  9550. shadowBefore = $('<div class="swiper-slide-shadow-' + (isH() ? 'left' : 'top') + '"></div>');
  9551. slide.append(shadowBefore);
  9552. }
  9553. if (shadowAfter.length === 0) {
  9554. shadowAfter = $('<div class="swiper-slide-shadow-' + (isH() ? 'right' : 'bottom') + '"></div>');
  9555. slide.append(shadowAfter);
  9556. }
  9557. var shadowOpacity = slide[0].progress;
  9558. if (shadowBefore.length) shadowBefore[0].style.opacity = -slide[0].progress;
  9559. if (shadowAfter.length) shadowAfter[0].style.opacity = slide[0].progress;
  9560. }
  9561. }
  9562. s.wrapper.css({
  9563. '-webkit-transform-origin': '50% 50% -' + (s.size / 2) + 'px',
  9564. '-moz-transform-origin': '50% 50% -' + (s.size / 2) + 'px',
  9565. '-ms-transform-origin': '50% 50% -' + (s.size / 2) + 'px',
  9566. 'transform-origin': '50% 50% -' + (s.size / 2) + 'px'
  9567. });
  9568. if (s.params.cube.shadow) {
  9569. if (isH()) {
  9570. cubeShadow.transform('translate3d(0px, ' + (s.width / 2 + s.params.cube.shadowOffset) + 'px, ' + (-s.width / 2) + 'px) rotateX(90deg) rotateZ(0deg) scale(' + (s.params.cube.shadowScale) + ')');
  9571. }
  9572. else {
  9573. var shadowAngle = Math.abs(wrapperRotate) - Math.floor(Math.abs(wrapperRotate) / 90) * 90;
  9574. var multiplier = 1.5 - (Math.sin(shadowAngle * 2 * Math.PI / 360) / 2 + Math.cos(shadowAngle * 2 * Math.PI / 360) / 2);
  9575. var scale1 = s.params.cube.shadowScale,
  9576. scale2 = s.params.cube.shadowScale / multiplier,
  9577. offset = s.params.cube.shadowOffset;
  9578. cubeShadow.transform('scale3d(' + scale1 + ', 1, ' + scale2 + ') translate3d(0px, ' + (s.height / 2 + offset) + 'px, ' + (-s.height / 2 / scale2) + 'px) rotateX(-90deg)');
  9579. }
  9580. }
  9581. var zFactor = (s.isSafari || s.isUiWebView) ? (-s.size / 2) : 0;
  9582. s.wrapper.transform('translate3d(0px,0,' + zFactor + 'px) rotateX(' + (isH() ? 0 : wrapperRotate) + 'deg) rotateY(' + (isH() ? -wrapperRotate : 0) + 'deg)');
  9583. },
  9584. setTransition: function (duration) {
  9585. s.slides.transition(duration).find('.swiper-slide-shadow-top, .swiper-slide-shadow-right, .swiper-slide-shadow-bottom, .swiper-slide-shadow-left').transition(duration);
  9586. if (s.params.cube.shadow && !isH()) {
  9587. s.container.find('.swiper-cube-shadow').transition(duration);
  9588. }
  9589. }
  9590. },
  9591. coverflow: {
  9592. setTranslate: function () {
  9593. var transform = s.translate;
  9594. var center = isH() ? -transform + s.width / 2 : -transform + s.height / 2;
  9595. var rotate = isH() ? s.params.coverflow.rotate: -s.params.coverflow.rotate;
  9596. var translate = s.params.coverflow.depth;
  9597. //Each slide offset from center
  9598. for (var i = 0, length = s.slides.length; i < length; i++) {
  9599. var slide = s.slides.eq(i);
  9600. var slideSize = s.slidesSizesGrid[i];
  9601. var slideOffset = slide[0].swiperSlideOffset;
  9602. var offsetMultiplier = (center - slideOffset - slideSize / 2) / slideSize * s.params.coverflow.modifier;
  9603. var rotateY = isH() ? rotate * offsetMultiplier : 0;
  9604. var rotateX = isH() ? 0 : rotate * offsetMultiplier;
  9605. // var rotateZ = 0
  9606. var translateZ = -translate * Math.abs(offsetMultiplier);
  9607. var translateY = isH() ? 0 : s.params.coverflow.stretch * (offsetMultiplier);
  9608. var translateX = isH() ? s.params.coverflow.stretch * (offsetMultiplier) : 0;
  9609. //Fix for ultra small values
  9610. if (Math.abs(translateX) < 0.001) translateX = 0;
  9611. if (Math.abs(translateY) < 0.001) translateY = 0;
  9612. if (Math.abs(translateZ) < 0.001) translateZ = 0;
  9613. if (Math.abs(rotateY) < 0.001) rotateY = 0;
  9614. if (Math.abs(rotateX) < 0.001) rotateX = 0;
  9615. var slideTransform = 'translate3d(' + translateX + 'px,' + translateY + 'px,' + translateZ + 'px) rotateX(' + rotateX + 'deg) rotateY(' + rotateY + 'deg)';
  9616. slide.transform(slideTransform);
  9617. slide[0].style.zIndex = -Math.abs(Math.round(offsetMultiplier)) + 1;
  9618. if (s.params.coverflow.slideShadows) {
  9619. //Set shadows
  9620. var shadowBefore = isH() ? slide.find('.swiper-slide-shadow-left') : slide.find('.swiper-slide-shadow-top');
  9621. var shadowAfter = isH() ? slide.find('.swiper-slide-shadow-right') : slide.find('.swiper-slide-shadow-bottom');
  9622. if (shadowBefore.length === 0) {
  9623. shadowBefore = $('<div class="swiper-slide-shadow-' + (isH() ? 'left' : 'top') + '"></div>');
  9624. slide.append(shadowBefore);
  9625. }
  9626. if (shadowAfter.length === 0) {
  9627. shadowAfter = $('<div class="swiper-slide-shadow-' + (isH() ? 'right' : 'bottom') + '"></div>');
  9628. slide.append(shadowAfter);
  9629. }
  9630. if (shadowBefore.length) shadowBefore[0].style.opacity = offsetMultiplier > 0 ? offsetMultiplier : 0;
  9631. if (shadowAfter.length) shadowAfter[0].style.opacity = (-offsetMultiplier) > 0 ? -offsetMultiplier : 0;
  9632. }
  9633. }
  9634. //Set correct perspective for IE10
  9635. if (s.browser.ie) {
  9636. var ws = s.wrapper[0].style;
  9637. ws.perspectiveOrigin = center + 'px 50%';
  9638. }
  9639. },
  9640. setTransition: function (duration) {
  9641. s.slides.transition(duration).find('.swiper-slide-shadow-top, .swiper-slide-shadow-right, .swiper-slide-shadow-bottom, .swiper-slide-shadow-left').transition(duration);
  9642. }
  9643. }
  9644. };
  9645. /*=========================
  9646. Images Lazy Loading
  9647. ===========================*/
  9648. s.lazy = {
  9649. initialImageLoaded: false,
  9650. loadImageInSlide: function (index, loadInDuplicate) {
  9651. if (typeof index === 'undefined') return;
  9652. if (typeof loadInDuplicate === 'undefined') loadInDuplicate = true;
  9653. if (s.slides.length === 0) return;
  9654. var slide = s.slides.eq(index);
  9655. var img = slide.find('.swiper-lazy:not(.swiper-lazy-loaded):not(.swiper-lazy-loading)');
  9656. if (slide.hasClass('swiper-lazy') && !slide.hasClass('swiper-lazy-loaded') && !slide.hasClass('swiper-lazy-loading')) {
  9657. img = img.add(slide[0]);
  9658. }
  9659. if (img.length === 0) return;
  9660. img.each(function () {
  9661. var _img = $(this);
  9662. _img.addClass('swiper-lazy-loading');
  9663. var background = _img.attr('data-background');
  9664. var src = _img.attr('data-src'),
  9665. srcset = _img.attr('data-srcset');
  9666. s.loadImage(_img[0], (src || background), srcset, false, function () {
  9667. if (background) {
  9668. _img.css('background-image', 'url(' + background + ')');
  9669. _img.removeAttr('data-background');
  9670. }
  9671. else {
  9672. if (srcset) {
  9673. _img.attr('srcset', srcset);
  9674. _img.removeAttr('data-srcset');
  9675. }
  9676. if (src) {
  9677. _img.attr('src', src);
  9678. _img.removeAttr('data-src');
  9679. }
  9680. }
  9681. _img.addClass('swiper-lazy-loaded').removeClass('swiper-lazy-loading');
  9682. slide.find('.swiper-lazy-preloader, .preloader').remove();
  9683. if (s.params.loop && loadInDuplicate) {
  9684. var slideOriginalIndex = slide.attr('data-swiper-slide-index');
  9685. if (slide.hasClass(s.params.slideDuplicateClass)) {
  9686. var originalSlide = s.wrapper.children('[data-swiper-slide-index="' + slideOriginalIndex + '"]:not(.' + s.params.slideDuplicateClass + ')');
  9687. s.lazy.loadImageInSlide(originalSlide.index(), false);
  9688. }
  9689. else {
  9690. var duplicatedSlide = s.wrapper.children('.' + s.params.slideDuplicateClass + '[data-swiper-slide-index="' + slideOriginalIndex + '"]');
  9691. s.lazy.loadImageInSlide(duplicatedSlide.index(), false);
  9692. }
  9693. }
  9694. s.emit('onLazyImageReady', s, slide[0], _img[0]);
  9695. });
  9696. s.emit('onLazyImageLoad', s, slide[0], _img[0]);
  9697. });
  9698. },
  9699. load: function () {
  9700. var i;
  9701. if (s.params.watchSlidesVisibility) {
  9702. s.wrapper.children('.' + s.params.slideVisibleClass).each(function () {
  9703. s.lazy.loadImageInSlide($(this).index());
  9704. });
  9705. }
  9706. else {
  9707. if (s.params.slidesPerView > 1) {
  9708. for (i = s.activeIndex; i < s.activeIndex + s.params.slidesPerView ; i++) {
  9709. if (s.slides[i]) s.lazy.loadImageInSlide(i);
  9710. }
  9711. }
  9712. else {
  9713. s.lazy.loadImageInSlide(s.activeIndex);
  9714. }
  9715. }
  9716. if (s.params.lazyLoadingInPrevNext) {
  9717. if (s.params.slidesPerView > 1) {
  9718. // Next Slides
  9719. for (i = s.activeIndex + s.params.slidesPerView; i < s.activeIndex + s.params.slidesPerView + s.params.slidesPerView; i++) {
  9720. if (s.slides[i]) s.lazy.loadImageInSlide(i);
  9721. }
  9722. // Prev Slides
  9723. for (i = s.activeIndex - s.params.slidesPerView; i < s.activeIndex ; i++) {
  9724. if (s.slides[i]) s.lazy.loadImageInSlide(i);
  9725. }
  9726. }
  9727. else {
  9728. var nextSlide = s.wrapper.children('.' + s.params.slideNextClass);
  9729. if (nextSlide.length > 0) s.lazy.loadImageInSlide(nextSlide.index());
  9730. var prevSlide = s.wrapper.children('.' + s.params.slidePrevClass);
  9731. if (prevSlide.length > 0) s.lazy.loadImageInSlide(prevSlide.index());
  9732. }
  9733. }
  9734. },
  9735. onTransitionStart: function () {
  9736. if (s.params.lazyLoading) {
  9737. if (s.params.lazyLoadingOnTransitionStart || (!s.params.lazyLoadingOnTransitionStart && !s.lazy.initialImageLoaded)) {
  9738. s.lazy.load();
  9739. }
  9740. }
  9741. },
  9742. onTransitionEnd: function () {
  9743. if (s.params.lazyLoading && !s.params.lazyLoadingOnTransitionStart) {
  9744. s.lazy.load();
  9745. }
  9746. }
  9747. };
  9748. /*=========================
  9749. Scrollbar
  9750. ===========================*/
  9751. s.scrollbar = {
  9752. isTouched: false,
  9753. setDragPosition: function (e) {
  9754. var sb = s.scrollbar;
  9755. var x = 0, y = 0;
  9756. var translate;
  9757. var pointerPosition = isH() ?
  9758. ((e.type === 'touchstart' || e.type === 'touchmove') ? e.targetTouches[0].pageX : e.pageX || e.clientX) :
  9759. ((e.type === 'touchstart' || e.type === 'touchmove') ? e.targetTouches[0].pageY : e.pageY || e.clientY) ;
  9760. var position = (pointerPosition) - sb.track.offset()[isH() ? 'left' : 'top'] - sb.dragSize / 2;
  9761. var positionMin = -s.minTranslate() * sb.moveDivider;
  9762. var positionMax = -s.maxTranslate() * sb.moveDivider;
  9763. if (position < positionMin) {
  9764. position = positionMin;
  9765. }
  9766. else if (position > positionMax) {
  9767. position = positionMax;
  9768. }
  9769. position = -position / sb.moveDivider;
  9770. s.updateProgress(position);
  9771. s.setWrapperTranslate(position, true);
  9772. },
  9773. dragStart: function (e) {
  9774. var sb = s.scrollbar;
  9775. sb.isTouched = true;
  9776. e.preventDefault();
  9777. e.stopPropagation();
  9778. sb.setDragPosition(e);
  9779. clearTimeout(sb.dragTimeout);
  9780. sb.track.transition(0);
  9781. if (s.params.scrollbarHide) {
  9782. sb.track.css('opacity', 1);
  9783. }
  9784. s.wrapper.transition(100);
  9785. sb.drag.transition(100);
  9786. s.emit('onScrollbarDragStart', s);
  9787. },
  9788. dragMove: function (e) {
  9789. var sb = s.scrollbar;
  9790. if (!sb.isTouched) return;
  9791. if (e.preventDefault) e.preventDefault();
  9792. else e.returnValue = false;
  9793. sb.setDragPosition(e);
  9794. s.wrapper.transition(0);
  9795. sb.track.transition(0);
  9796. sb.drag.transition(0);
  9797. s.emit('onScrollbarDragMove', s);
  9798. },
  9799. dragEnd: function (e) {
  9800. var sb = s.scrollbar;
  9801. if (!sb.isTouched) return;
  9802. sb.isTouched = false;
  9803. if (s.params.scrollbarHide) {
  9804. clearTimeout(sb.dragTimeout);
  9805. sb.dragTimeout = setTimeout(function () {
  9806. sb.track.css('opacity', 0);
  9807. sb.track.transition(400);
  9808. }, 1000);
  9809. }
  9810. s.emit('onScrollbarDragEnd', s);
  9811. if (s.params.scrollbarSnapOnRelease) {
  9812. s.slideReset();
  9813. }
  9814. },
  9815. enableDraggable: function () {
  9816. var sb = s.scrollbar;
  9817. var target = s.support.touch ? sb.track : document;
  9818. $(sb.track).on(s.touchEvents.start, sb.dragStart);
  9819. $(target).on(s.touchEvents.move, sb.dragMove);
  9820. $(target).on(s.touchEvents.end, sb.dragEnd);
  9821. },
  9822. disableDraggable: function () {
  9823. var sb = s.scrollbar;
  9824. var target = s.support.touch ? sb.track : document;
  9825. $(sb.track).off(s.touchEvents.start, sb.dragStart);
  9826. $(target).off(s.touchEvents.move, sb.dragMove);
  9827. $(target).off(s.touchEvents.end, sb.dragEnd);
  9828. },
  9829. set: function () {
  9830. if (!s.params.scrollbar) return;
  9831. var sb = s.scrollbar;
  9832. sb.track = $(s.params.scrollbar);
  9833. sb.drag = sb.track.find('.swiper-scrollbar-drag');
  9834. if (sb.drag.length === 0) {
  9835. sb.drag = $('<div class="swiper-scrollbar-drag"></div>');
  9836. sb.track.append(sb.drag);
  9837. }
  9838. sb.drag[0].style.width = '';
  9839. sb.drag[0].style.height = '';
  9840. sb.trackSize = isH() ? sb.track[0].offsetWidth : sb.track[0].offsetHeight;
  9841. sb.divider = s.size / s.virtualSize;
  9842. sb.moveDivider = sb.divider * (sb.trackSize / s.size);
  9843. sb.dragSize = sb.trackSize * sb.divider;
  9844. if (isH()) {
  9845. sb.drag[0].style.width = sb.dragSize + 'px';
  9846. }
  9847. else {
  9848. sb.drag[0].style.height = sb.dragSize + 'px';
  9849. }
  9850. if (sb.divider >= 1) {
  9851. sb.track[0].style.display = 'none';
  9852. }
  9853. else {
  9854. sb.track[0].style.display = '';
  9855. }
  9856. if (s.params.scrollbarHide) {
  9857. sb.track[0].style.opacity = 0;
  9858. }
  9859. },
  9860. setTranslate: function () {
  9861. if (!s.params.scrollbar) return;
  9862. var diff;
  9863. var sb = s.scrollbar;
  9864. var translate = s.translate || 0;
  9865. var newPos;
  9866. var newSize = sb.dragSize;
  9867. newPos = (sb.trackSize - sb.dragSize) * s.progress;
  9868. if (s.rtl && isH()) {
  9869. newPos = -newPos;
  9870. if (newPos > 0) {
  9871. newSize = sb.dragSize - newPos;
  9872. newPos = 0;
  9873. }
  9874. else if (-newPos + sb.dragSize > sb.trackSize) {
  9875. newSize = sb.trackSize + newPos;
  9876. }
  9877. }
  9878. else {
  9879. if (newPos < 0) {
  9880. newSize = sb.dragSize + newPos;
  9881. newPos = 0;
  9882. }
  9883. else if (newPos + sb.dragSize > sb.trackSize) {
  9884. newSize = sb.trackSize - newPos;
  9885. }
  9886. }
  9887. if (isH()) {
  9888. if (s.support.transforms3d) {
  9889. sb.drag.transform('translate3d(' + (newPos) + 'px, 0, 0)');
  9890. }
  9891. else {
  9892. sb.drag.transform('translateX(' + (newPos) + 'px)');
  9893. }
  9894. sb.drag[0].style.width = newSize + 'px';
  9895. }
  9896. else {
  9897. if (s.support.transforms3d) {
  9898. sb.drag.transform('translate3d(0px, ' + (newPos) + 'px, 0)');
  9899. }
  9900. else {
  9901. sb.drag.transform('translateY(' + (newPos) + 'px)');
  9902. }
  9903. sb.drag[0].style.height = newSize + 'px';
  9904. }
  9905. if (s.params.scrollbarHide) {
  9906. clearTimeout(sb.timeout);
  9907. sb.track[0].style.opacity = 1;
  9908. sb.timeout = setTimeout(function () {
  9909. sb.track[0].style.opacity = 0;
  9910. sb.track.transition(400);
  9911. }, 1000);
  9912. }
  9913. },
  9914. setTransition: function (duration) {
  9915. if (!s.params.scrollbar) return;
  9916. s.scrollbar.drag.transition(duration);
  9917. }
  9918. };
  9919. /*=========================
  9920. Controller
  9921. ===========================*/
  9922. s.controller = {
  9923. LinearSpline: function (x, y) {
  9924. this.x = x;
  9925. this.y = y;
  9926. this.lastIndex = x.length - 1;
  9927. // Given an x value (x2), return the expected y2 value:
  9928. // (x1,y1) is the known point before given value,
  9929. // (x3,y3) is the known point after given value.
  9930. var i1, i3;
  9931. var l = this.x.length;
  9932. this.interpolate = function (x2) {
  9933. if (!x2) return 0;
  9934. // Get the indexes of x1 and x3 (the array indexes before and after given x2):
  9935. i3 = binarySearch(this.x, x2);
  9936. i1 = i3 - 1;
  9937. // We have our indexes i1 & i3, so we can calculate already:
  9938. // y2 := ((x2−x1) × (y3−y1)) ÷ (x3−x1) + y1
  9939. return ((x2 - this.x[i1]) * (this.y[i3] - this.y[i1])) / (this.x[i3] - this.x[i1]) + this.y[i1];
  9940. };
  9941. var binarySearch = (function() {
  9942. var maxIndex, minIndex, guess;
  9943. return function(array, val) {
  9944. minIndex = -1;
  9945. maxIndex = array.length;
  9946. while (maxIndex - minIndex > 1)
  9947. if (array[guess = maxIndex + minIndex >> 1] <= val) {
  9948. minIndex = guess;
  9949. } else {
  9950. maxIndex = guess;
  9951. }
  9952. return maxIndex;
  9953. };
  9954. })();
  9955. },
  9956. //xxx: for now i will just save one spline function to to
  9957. getInterpolateFunction: function(c){
  9958. if(!s.controller.spline) s.controller.spline = s.params.loop ?
  9959. new s.controller.LinearSpline(s.slidesGrid, c.slidesGrid) :
  9960. new s.controller.LinearSpline(s.snapGrid, c.snapGrid);
  9961. },
  9962. setTranslate: function (translate, byController) {
  9963. var controlled = s.params.control;
  9964. var multiplier, controlledTranslate;
  9965. function setControlledTranslate(c) {
  9966. // this will create an Interpolate function based on the snapGrids
  9967. // x is the Grid of the scrolled scroller and y will be the controlled scroller
  9968. // it makes sense to create this only once and recall it for the interpolation
  9969. // the function does a lot of value caching for performance
  9970. translate = c.rtl && c.params.direction === 'horizontal' ? -s.translate : s.translate;
  9971. if (s.params.controlBy === 'slide') {
  9972. s.controller.getInterpolateFunction(c);
  9973. // i am not sure why the values have to be multiplicated this way, tried to invert the snapGrid
  9974. // but it did not work out
  9975. controlledTranslate = -s.controller.spline.interpolate(-translate);
  9976. }
  9977. if(!controlledTranslate || s.params.controlBy === 'container'){
  9978. multiplier = (c.maxTranslate() - c.minTranslate()) / (s.maxTranslate() - s.minTranslate());
  9979. controlledTranslate = (translate - s.minTranslate()) * multiplier + c.minTranslate();
  9980. }
  9981. if (s.params.controlInverse) {
  9982. controlledTranslate = c.maxTranslate() - controlledTranslate;
  9983. }
  9984. c.updateProgress(controlledTranslate);
  9985. c.setWrapperTranslate(controlledTranslate, false, s);
  9986. c.updateActiveIndex();
  9987. }
  9988. if (s.isArray(controlled)) {
  9989. for (var i = 0; i < controlled.length; i++) {
  9990. if (controlled[i] !== byController && controlled[i] instanceof Swiper) {
  9991. setControlledTranslate(controlled[i]);
  9992. }
  9993. }
  9994. }
  9995. else if (controlled instanceof Swiper && byController !== controlled) {
  9996. setControlledTranslate(controlled);
  9997. }
  9998. },
  9999. setTransition: function (duration, byController) {
  10000. var controlled = s.params.control;
  10001. var i;
  10002. function setControlledTransition(c) {
  10003. c.setWrapperTransition(duration, s);
  10004. if (duration !== 0) {
  10005. c.onTransitionStart();
  10006. c.wrapper.transitionEnd(function(){
  10007. if (!controlled) return;
  10008. if (c.params.loop && s.params.controlBy === 'slide') {
  10009. c.fixLoop();
  10010. }
  10011. c.onTransitionEnd();
  10012. });
  10013. }
  10014. }
  10015. if (s.isArray(controlled)) {
  10016. for (i = 0; i < controlled.length; i++) {
  10017. if (controlled[i] !== byController && controlled[i] instanceof Swiper) {
  10018. setControlledTransition(controlled[i]);
  10019. }
  10020. }
  10021. }
  10022. else if (controlled instanceof Swiper && byController !== controlled) {
  10023. setControlledTransition(controlled);
  10024. }
  10025. }
  10026. };
  10027. /*=========================
  10028. Hash Navigation
  10029. ===========================*/
  10030. s.hashnav = {
  10031. init: function () {
  10032. if (!s.params.hashnav) return;
  10033. s.hashnav.initialized = true;
  10034. var hash = document.location.hash.replace('#', '');
  10035. if (!hash) return;
  10036. var speed = 0;
  10037. for (var i = 0, length = s.slides.length; i < length; i++) {
  10038. var slide = s.slides.eq(i);
  10039. var slideHash = slide.attr('data-hash');
  10040. if (slideHash === hash && !slide.hasClass(s.params.slideDuplicateClass)) {
  10041. var index = slide.index();
  10042. s.slideTo(index, speed, s.params.runCallbacksOnInit, true);
  10043. }
  10044. }
  10045. },
  10046. setHash: function () {
  10047. if (!s.hashnav.initialized || !s.params.hashnav) return;
  10048. document.location.hash = s.slides.eq(s.activeIndex).attr('data-hash') || '';
  10049. }
  10050. };
  10051. /*=========================
  10052. Keyboard Control
  10053. ===========================*/
  10054. function handleKeyboard(e) {
  10055. if (e.originalEvent) e = e.originalEvent; //jquery fix
  10056. var kc = e.keyCode || e.charCode;
  10057. // Directions locks
  10058. if (!s.params.allowSwipeToNext && (isH() && kc === 39 || !isH() && kc === 40)) {
  10059. return false;
  10060. }
  10061. if (!s.params.allowSwipeToPrev && (isH() && kc === 37 || !isH() && kc === 38)) {
  10062. return false;
  10063. }
  10064. if (e.shiftKey || e.altKey || e.ctrlKey || e.metaKey) {
  10065. return;
  10066. }
  10067. if (document.activeElement && document.activeElement.nodeName && (document.activeElement.nodeName.toLowerCase() === 'input' || document.activeElement.nodeName.toLowerCase() === 'textarea')) {
  10068. return;
  10069. }
  10070. if (kc === 37 || kc === 39 || kc === 38 || kc === 40) {
  10071. var inView = false;
  10072. //Check that swiper should be inside of visible area of window
  10073. if (s.container.parents('.swiper-slide').length > 0 && s.container.parents('.swiper-slide-active').length === 0) {
  10074. return;
  10075. }
  10076. var windowScroll = {
  10077. left: window.pageXOffset,
  10078. top: window.pageYOffset
  10079. };
  10080. var windowWidth = window.innerWidth;
  10081. var windowHeight = window.innerHeight;
  10082. var swiperOffset = s.container.offset();
  10083. if (s.rtl) swiperOffset.left = swiperOffset.left - s.container[0].scrollLeft;
  10084. var swiperCoord = [
  10085. [swiperOffset.left, swiperOffset.top],
  10086. [swiperOffset.left + s.width, swiperOffset.top],
  10087. [swiperOffset.left, swiperOffset.top + s.height],
  10088. [swiperOffset.left + s.width, swiperOffset.top + s.height]
  10089. ];
  10090. for (var i = 0; i < swiperCoord.length; i++) {
  10091. var point = swiperCoord[i];
  10092. if (
  10093. point[0] >= windowScroll.left && point[0] <= windowScroll.left + windowWidth &&
  10094. point[1] >= windowScroll.top && point[1] <= windowScroll.top + windowHeight
  10095. ) {
  10096. inView = true;
  10097. }
  10098. }
  10099. if (!inView) return;
  10100. }
  10101. if (isH()) {
  10102. if (kc === 37 || kc === 39) {
  10103. if (e.preventDefault) e.preventDefault();
  10104. else e.returnValue = false;
  10105. }
  10106. if ((kc === 39 && !s.rtl) || (kc === 37 && s.rtl)) s.slideNext();
  10107. if ((kc === 37 && !s.rtl) || (kc === 39 && s.rtl)) s.slidePrev();
  10108. }
  10109. else {
  10110. if (kc === 38 || kc === 40) {
  10111. if (e.preventDefault) e.preventDefault();
  10112. else e.returnValue = false;
  10113. }
  10114. if (kc === 40) s.slideNext();
  10115. if (kc === 38) s.slidePrev();
  10116. }
  10117. }
  10118. s.disableKeyboardControl = function () {
  10119. s.params.keyboardControl = false;
  10120. $(document).off('keydown', handleKeyboard);
  10121. };
  10122. s.enableKeyboardControl = function () {
  10123. s.params.keyboardControl = true;
  10124. $(document).on('keydown', handleKeyboard);
  10125. };
  10126. /*=========================
  10127. Mousewheel Control
  10128. ===========================*/
  10129. s.mousewheel = {
  10130. event: false,
  10131. lastScrollTime: (new window.Date()).getTime()
  10132. };
  10133. if (s.params.mousewheelControl) {
  10134. try {
  10135. new window.WheelEvent('wheel');
  10136. s.mousewheel.event = 'wheel';
  10137. } catch (e) {}
  10138. if (!s.mousewheel.event && document.onmousewheel !== undefined) {
  10139. s.mousewheel.event = 'mousewheel';
  10140. }
  10141. if (!s.mousewheel.event) {
  10142. s.mousewheel.event = 'DOMMouseScroll';
  10143. }
  10144. }
  10145. function handleMousewheel(e) {
  10146. if (e.originalEvent) e = e.originalEvent; //jquery fix
  10147. var we = s.mousewheel.event;
  10148. var delta = 0;
  10149. var rtlFactor = s.rtl ? -1 : 1;
  10150. //Opera & IE
  10151. if (e.detail) delta = -e.detail;
  10152. //WebKits
  10153. else if (we === 'mousewheel') {
  10154. if (s.params.mousewheelForceToAxis) {
  10155. if (isH()) {
  10156. if (Math.abs(e.wheelDeltaX) > Math.abs(e.wheelDeltaY)) delta = e.wheelDeltaX * rtlFactor;
  10157. else return;
  10158. }
  10159. else {
  10160. if (Math.abs(e.wheelDeltaY) > Math.abs(e.wheelDeltaX)) delta = e.wheelDeltaY;
  10161. else return;
  10162. }
  10163. }
  10164. else {
  10165. delta = Math.abs(e.wheelDeltaX) > Math.abs(e.wheelDeltaY) ? - e.wheelDeltaX * rtlFactor : - e.wheelDeltaY;
  10166. }
  10167. }
  10168. //Old FireFox
  10169. else if (we === 'DOMMouseScroll') delta = -e.detail;
  10170. //New FireFox
  10171. else if (we === 'wheel') {
  10172. if (s.params.mousewheelForceToAxis) {
  10173. if (isH()) {
  10174. if (Math.abs(e.deltaX) > Math.abs(e.deltaY)) delta = -e.deltaX * rtlFactor;
  10175. else return;
  10176. }
  10177. else {
  10178. if (Math.abs(e.deltaY) > Math.abs(e.deltaX)) delta = -e.deltaY;
  10179. else return;
  10180. }
  10181. }
  10182. else {
  10183. delta = Math.abs(e.deltaX) > Math.abs(e.deltaY) ? - e.deltaX * rtlFactor : - e.deltaY;
  10184. }
  10185. }
  10186. if (delta === 0) return;
  10187. if (s.params.mousewheelInvert) delta = -delta;
  10188. if (!s.params.freeMode) {
  10189. if ((new window.Date()).getTime() - s.mousewheel.lastScrollTime > 60) {
  10190. if (delta < 0) {
  10191. if ((!s.isEnd || s.params.loop) && !s.animating) s.slideNext();
  10192. else if (s.params.mousewheelReleaseOnEdges) return true;
  10193. }
  10194. else {
  10195. if ((!s.isBeginning || s.params.loop) && !s.animating) s.slidePrev();
  10196. else if (s.params.mousewheelReleaseOnEdges) return true;
  10197. }
  10198. }
  10199. s.mousewheel.lastScrollTime = (new window.Date()).getTime();
  10200. }
  10201. else {
  10202. //Freemode or scrollContainer:
  10203. var position = s.getWrapperTranslate() + delta * s.params.mousewheelSensitivity;
  10204. var wasBeginning = s.isBeginning,
  10205. wasEnd = s.isEnd;
  10206. if (position >= s.minTranslate()) position = s.minTranslate();
  10207. if (position <= s.maxTranslate()) position = s.maxTranslate();
  10208. s.setWrapperTransition(0);
  10209. s.setWrapperTranslate(position);
  10210. s.updateProgress();
  10211. s.updateActiveIndex();
  10212. if (!wasBeginning && s.isBeginning || !wasEnd && s.isEnd) {
  10213. s.updateClasses();
  10214. }
  10215. if (s.params.freeModeSticky) {
  10216. clearTimeout(s.mousewheel.timeout);
  10217. s.mousewheel.timeout = setTimeout(function () {
  10218. s.slideReset();
  10219. }, 300);
  10220. }
  10221. // Return page scroll on edge positions
  10222. if (position === 0 || position === s.maxTranslate()) return;
  10223. }
  10224. if (s.params.autoplay) s.stopAutoplay();
  10225. if (e.preventDefault) e.preventDefault();
  10226. else e.returnValue = false;
  10227. return false;
  10228. }
  10229. s.disableMousewheelControl = function () {
  10230. if (!s.mousewheel.event) return false;
  10231. s.container.off(s.mousewheel.event, handleMousewheel);
  10232. return true;
  10233. };
  10234. s.enableMousewheelControl = function () {
  10235. if (!s.mousewheel.event) return false;
  10236. s.container.on(s.mousewheel.event, handleMousewheel);
  10237. return true;
  10238. };
  10239. /*=========================
  10240. Parallax
  10241. ===========================*/
  10242. function setParallaxTransform(el, progress) {
  10243. el = $(el);
  10244. var p, pX, pY;
  10245. var rtlFactor = s.rtl ? -1 : 1;
  10246. p = el.attr('data-swiper-parallax') || '0';
  10247. pX = el.attr('data-swiper-parallax-x');
  10248. pY = el.attr('data-swiper-parallax-y');
  10249. if (pX || pY) {
  10250. pX = pX || '0';
  10251. pY = pY || '0';
  10252. }
  10253. else {
  10254. if (isH()) {
  10255. pX = p;
  10256. pY = '0';
  10257. }
  10258. else {
  10259. pY = p;
  10260. pX = '0';
  10261. }
  10262. }
  10263. if ((pX).indexOf('%') >= 0) {
  10264. pX = parseInt(pX, 10) * progress * rtlFactor + '%';
  10265. }
  10266. else {
  10267. pX = pX * progress * rtlFactor + 'px' ;
  10268. }
  10269. if ((pY).indexOf('%') >= 0) {
  10270. pY = parseInt(pY, 10) * progress + '%';
  10271. }
  10272. else {
  10273. pY = pY * progress + 'px' ;
  10274. }
  10275. el.transform('translate3d(' + pX + ', ' + pY + ',0px)');
  10276. }
  10277. s.parallax = {
  10278. setTranslate: function () {
  10279. s.container.children('[data-swiper-parallax], [data-swiper-parallax-x], [data-swiper-parallax-y]').each(function(){
  10280. setParallaxTransform(this, s.progress);
  10281. });
  10282. s.slides.each(function () {
  10283. var slide = $(this);
  10284. slide.find('[data-swiper-parallax], [data-swiper-parallax-x], [data-swiper-parallax-y]').each(function () {
  10285. var progress = Math.min(Math.max(slide[0].progress, -1), 1);
  10286. setParallaxTransform(this, progress);
  10287. });
  10288. });
  10289. },
  10290. setTransition: function (duration) {
  10291. if (typeof duration === 'undefined') duration = s.params.speed;
  10292. s.container.find('[data-swiper-parallax], [data-swiper-parallax-x], [data-swiper-parallax-y]').each(function(){
  10293. var el = $(this);
  10294. var parallaxDuration = parseInt(el.attr('data-swiper-parallax-duration'), 10) || duration;
  10295. if (duration === 0) parallaxDuration = 0;
  10296. el.transition(parallaxDuration);
  10297. });
  10298. }
  10299. };
  10300. /*=========================
  10301. Plugins API. Collect all and init all plugins
  10302. ===========================*/
  10303. s._plugins = [];
  10304. for (var plugin in s.plugins) {
  10305. var p = s.plugins[plugin](s, s.params[plugin]);
  10306. if (p) s._plugins.push(p);
  10307. }
  10308. // Method to call all plugins event/method
  10309. s.callPlugins = function (eventName) {
  10310. for (var i = 0; i < s._plugins.length; i++) {
  10311. if (eventName in s._plugins[i]) {
  10312. s._plugins[i][eventName](arguments[1], arguments[2], arguments[3], arguments[4], arguments[5]);
  10313. }
  10314. }
  10315. };
  10316. /*=========================
  10317. Events/Callbacks/Plugins Emitter
  10318. ===========================*/
  10319. function normalizeEventName (eventName) {
  10320. if (eventName.indexOf('on') !== 0) {
  10321. if (eventName[0] !== eventName[0].toUpperCase()) {
  10322. eventName = 'on' + eventName[0].toUpperCase() + eventName.substring(1);
  10323. }
  10324. else {
  10325. eventName = 'on' + eventName;
  10326. }
  10327. }
  10328. return eventName;
  10329. }
  10330. s.emitterEventListeners = {
  10331. };
  10332. s.emit = function (eventName) {
  10333. // Trigger callbacks
  10334. if (s.params[eventName]) {
  10335. s.params[eventName](arguments[1], arguments[2], arguments[3], arguments[4], arguments[5]);
  10336. }
  10337. var i;
  10338. // Trigger events
  10339. if (s.emitterEventListeners[eventName]) {
  10340. for (i = 0; i < s.emitterEventListeners[eventName].length; i++) {
  10341. s.emitterEventListeners[eventName][i](arguments[1], arguments[2], arguments[3], arguments[4], arguments[5]);
  10342. }
  10343. }
  10344. // Trigger plugins
  10345. if (s.callPlugins) s.callPlugins(eventName, arguments[1], arguments[2], arguments[3], arguments[4], arguments[5]);
  10346. };
  10347. s.on = function (eventName, handler) {
  10348. eventName = normalizeEventName(eventName);
  10349. if (!s.emitterEventListeners[eventName]) s.emitterEventListeners[eventName] = [];
  10350. s.emitterEventListeners[eventName].push(handler);
  10351. return s;
  10352. };
  10353. s.off = function (eventName, handler) {
  10354. var i;
  10355. eventName = normalizeEventName(eventName);
  10356. if (typeof handler === 'undefined') {
  10357. // Remove all handlers for such event
  10358. s.emitterEventListeners[eventName] = [];
  10359. return s;
  10360. }
  10361. if (!s.emitterEventListeners[eventName] || s.emitterEventListeners[eventName].length === 0) return;
  10362. for (i = 0; i < s.emitterEventListeners[eventName].length; i++) {
  10363. if(s.emitterEventListeners[eventName][i] === handler) s.emitterEventListeners[eventName].splice(i, 1);
  10364. }
  10365. return s;
  10366. };
  10367. s.once = function (eventName, handler) {
  10368. eventName = normalizeEventName(eventName);
  10369. var _handler = function () {
  10370. handler(arguments[0], arguments[1], arguments[2], arguments[3], arguments[4]);
  10371. s.off(eventName, _handler);
  10372. };
  10373. s.on(eventName, _handler);
  10374. return s;
  10375. };
  10376. // Accessibility tools
  10377. s.a11y = {
  10378. makeFocusable: function ($el) {
  10379. $el.attr('tabIndex', '0');
  10380. return $el;
  10381. },
  10382. addRole: function ($el, role) {
  10383. $el.attr('role', role);
  10384. return $el;
  10385. },
  10386. addLabel: function ($el, label) {
  10387. $el.attr('aria-label', label);
  10388. return $el;
  10389. },
  10390. disable: function ($el) {
  10391. $el.attr('aria-disabled', true);
  10392. return $el;
  10393. },
  10394. enable: function ($el) {
  10395. $el.attr('aria-disabled', false);
  10396. return $el;
  10397. },
  10398. onEnterKey: function (event) {
  10399. if (event.keyCode !== 13) return;
  10400. if ($(event.target).is(s.params.nextButton)) {
  10401. s.onClickNext(event);
  10402. if (s.isEnd) {
  10403. s.a11y.notify(s.params.lastSlideMessage);
  10404. }
  10405. else {
  10406. s.a11y.notify(s.params.nextSlideMessage);
  10407. }
  10408. }
  10409. else if ($(event.target).is(s.params.prevButton)) {
  10410. s.onClickPrev(event);
  10411. if (s.isBeginning) {
  10412. s.a11y.notify(s.params.firstSlideMessage);
  10413. }
  10414. else {
  10415. s.a11y.notify(s.params.prevSlideMessage);
  10416. }
  10417. }
  10418. if ($(event.target).is('.' + s.params.bulletClass)) {
  10419. $(event.target)[0].click();
  10420. }
  10421. },
  10422. liveRegion: $('<span class="swiper-notification" aria-live="assertive" aria-atomic="true"></span>'),
  10423. notify: function (message) {
  10424. var notification = s.a11y.liveRegion;
  10425. if (notification.length === 0) return;
  10426. notification.html('');
  10427. notification.html(message);
  10428. },
  10429. init: function () {
  10430. // Setup accessibility
  10431. if (s.params.nextButton) {
  10432. var nextButton = $(s.params.nextButton);
  10433. s.a11y.makeFocusable(nextButton);
  10434. s.a11y.addRole(nextButton, 'button');
  10435. s.a11y.addLabel(nextButton, s.params.nextSlideMessage);
  10436. }
  10437. if (s.params.prevButton) {
  10438. var prevButton = $(s.params.prevButton);
  10439. s.a11y.makeFocusable(prevButton);
  10440. s.a11y.addRole(prevButton, 'button');
  10441. s.a11y.addLabel(prevButton, s.params.prevSlideMessage);
  10442. }
  10443. $(s.container).append(s.a11y.liveRegion);
  10444. },
  10445. initPagination: function () {
  10446. if (s.params.pagination && s.params.paginationClickable && s.bullets && s.bullets.length) {
  10447. s.bullets.each(function () {
  10448. var bullet = $(this);
  10449. s.a11y.makeFocusable(bullet);
  10450. s.a11y.addRole(bullet, 'button');
  10451. s.a11y.addLabel(bullet, s.params.paginationBulletMessage.replace(/{{index}}/, bullet.index() + 1));
  10452. });
  10453. }
  10454. },
  10455. destroy: function () {
  10456. if (s.a11y.liveRegion && s.a11y.liveRegion.length > 0) s.a11y.liveRegion.remove();
  10457. }
  10458. };
  10459. /*=========================
  10460. Init/Destroy
  10461. ===========================*/
  10462. s.init = function () {
  10463. if (s.params.loop) s.createLoop();
  10464. s.updateContainerSize();
  10465. s.updateSlidesSize();
  10466. s.updatePagination();
  10467. if (s.params.scrollbar && s.scrollbar) {
  10468. s.scrollbar.set();
  10469. if (s.params.scrollbarDraggable) {
  10470. s.scrollbar.enableDraggable();
  10471. }
  10472. }
  10473. if (s.params.effect !== 'slide' && s.effects[s.params.effect]) {
  10474. if (!s.params.loop) s.updateProgress();
  10475. s.effects[s.params.effect].setTranslate();
  10476. }
  10477. if (s.params.loop) {
  10478. s.slideTo(s.params.initialSlide + s.loopedSlides, 0, s.params.runCallbacksOnInit);
  10479. }
  10480. else {
  10481. s.slideTo(s.params.initialSlide, 0, s.params.runCallbacksOnInit);
  10482. if (s.params.initialSlide === 0) {
  10483. if (s.parallax && s.params.parallax) s.parallax.setTranslate();
  10484. if (s.lazy && s.params.lazyLoading) {
  10485. s.lazy.load();
  10486. s.lazy.initialImageLoaded = true;
  10487. }
  10488. }
  10489. }
  10490. s.attachEvents();
  10491. if (s.params.observer && s.support.observer) {
  10492. s.initObservers();
  10493. }
  10494. if (s.params.preloadImages && !s.params.lazyLoading) {
  10495. s.preloadImages();
  10496. }
  10497. if (s.params.autoplay) {
  10498. s.startAutoplay();
  10499. }
  10500. if (s.params.keyboardControl) {
  10501. if (s.enableKeyboardControl) s.enableKeyboardControl();
  10502. }
  10503. if (s.params.mousewheelControl) {
  10504. if (s.enableMousewheelControl) s.enableMousewheelControl();
  10505. }
  10506. if (s.params.hashnav) {
  10507. if (s.hashnav) s.hashnav.init();
  10508. }
  10509. if (s.params.a11y && s.a11y) s.a11y.init();
  10510. s.emit('onInit', s);
  10511. };
  10512. // Cleanup dynamic styles
  10513. s.cleanupStyles = function () {
  10514. // Container
  10515. s.container.removeClass(s.classNames.join(' ')).removeAttr('style');
  10516. // Wrapper
  10517. s.wrapper.removeAttr('style');
  10518. // Slides
  10519. if (s.slides && s.slides.length) {
  10520. s.slides
  10521. .removeClass([
  10522. s.params.slideVisibleClass,
  10523. s.params.slideActiveClass,
  10524. s.params.slideNextClass,
  10525. s.params.slidePrevClass
  10526. ].join(' '))
  10527. .removeAttr('style')
  10528. .removeAttr('data-swiper-column')
  10529. .removeAttr('data-swiper-row');
  10530. }
  10531. // Pagination/Bullets
  10532. if (s.paginationContainer && s.paginationContainer.length) {
  10533. s.paginationContainer.removeClass(s.params.paginationHiddenClass);
  10534. }
  10535. if (s.bullets && s.bullets.length) {
  10536. s.bullets.removeClass(s.params.bulletActiveClass);
  10537. }
  10538. // Buttons
  10539. if (s.params.prevButton) $(s.params.prevButton).removeClass(s.params.buttonDisabledClass);
  10540. if (s.params.nextButton) $(s.params.nextButton).removeClass(s.params.buttonDisabledClass);
  10541. // Scrollbar
  10542. if (s.params.scrollbar && s.scrollbar) {
  10543. if (s.scrollbar.track && s.scrollbar.track.length) s.scrollbar.track.removeAttr('style');
  10544. if (s.scrollbar.drag && s.scrollbar.drag.length) s.scrollbar.drag.removeAttr('style');
  10545. }
  10546. };
  10547. // Destroy
  10548. s.destroy = function (deleteInstance, cleanupStyles) {
  10549. // Detach evebts
  10550. s.detachEvents();
  10551. // Stop autoplay
  10552. s.stopAutoplay();
  10553. // Disable draggable
  10554. if (s.params.scrollbar && s.scrollbar) {
  10555. if (s.params.scrollbarDraggable) {
  10556. s.scrollbar.disableDraggable();
  10557. }
  10558. }
  10559. // Destroy loop
  10560. if (s.params.loop) {
  10561. s.destroyLoop();
  10562. }
  10563. // Cleanup styles
  10564. if (cleanupStyles) {
  10565. s.cleanupStyles();
  10566. }
  10567. // Disconnect observer
  10568. s.disconnectObservers();
  10569. // Disable keyboard/mousewheel
  10570. if (s.params.keyboardControl) {
  10571. if (s.disableKeyboardControl) s.disableKeyboardControl();
  10572. }
  10573. if (s.params.mousewheelControl) {
  10574. if (s.disableMousewheelControl) s.disableMousewheelControl();
  10575. }
  10576. // Disable a11y
  10577. if (s.params.a11y && s.a11y) s.a11y.destroy();
  10578. // Destroy callback
  10579. s.emit('onDestroy');
  10580. // Delete instance
  10581. if (deleteInstance !== false) s = null;
  10582. };
  10583. s.init();
  10584. // Return swiper instance
  10585. return s;
  10586. };
  10587. /*==================================================
  10588. Prototype
  10589. ====================================================*/
  10590. Swiper.prototype = {
  10591. isSafari: (function () {
  10592. var ua = navigator.userAgent.toLowerCase();
  10593. return (ua.indexOf('safari') >= 0 && ua.indexOf('chrome') < 0 && ua.indexOf('android') < 0);
  10594. })(),
  10595. isUiWebView: /(iPhone|iPod|iPad).*AppleWebKit(?!.*Safari)/i.test(navigator.userAgent),
  10596. isArray: function (arr) {
  10597. return Object.prototype.toString.apply(arr) === '[object Array]';
  10598. },
  10599. /*==================================================
  10600. Browser
  10601. ====================================================*/
  10602. browser: {
  10603. ie: window.navigator.pointerEnabled || window.navigator.msPointerEnabled,
  10604. ieTouch: (window.navigator.msPointerEnabled && window.navigator.msMaxTouchPoints > 1) || (window.navigator.pointerEnabled && window.navigator.maxTouchPoints > 1)
  10605. },
  10606. /*==================================================
  10607. Devices
  10608. ====================================================*/
  10609. device: (function () {
  10610. var ua = navigator.userAgent;
  10611. var android = ua.match(/(Android);?[\s\/]+([\d.]+)?/);
  10612. var ipad = ua.match(/(iPad).*OS\s([\d_]+)/);
  10613. var ipod = ua.match(/(iPod)(.*OS\s([\d_]+))?/);
  10614. var iphone = !ipad && ua.match(/(iPhone\sOS)\s([\d_]+)/);
  10615. return {
  10616. ios: ipad || iphone || ipod,
  10617. android: android
  10618. };
  10619. })(),
  10620. /*==================================================
  10621. Feature Detection
  10622. ====================================================*/
  10623. support: {
  10624. touch : (window.Modernizr && Modernizr.touch === true) || (function () {
  10625. return !!(('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch);
  10626. })(),
  10627. transforms3d : (window.Modernizr && Modernizr.csstransforms3d === true) || (function () {
  10628. var div = document.createElement('div').style;
  10629. return ('webkitPerspective' in div || 'MozPerspective' in div || 'OPerspective' in div || 'MsPerspective' in div || 'perspective' in div);
  10630. })(),
  10631. flexbox: (function () {
  10632. var div = document.createElement('div').style;
  10633. var styles = ('alignItems webkitAlignItems webkitBoxAlign msFlexAlign mozBoxAlign webkitFlexDirection msFlexDirection mozBoxDirection mozBoxOrient webkitBoxDirection webkitBoxOrient').split(' ');
  10634. for (var i = 0; i < styles.length; i++) {
  10635. if (styles[i] in div) return true;
  10636. }
  10637. })(),
  10638. observer: (function () {
  10639. return ('MutationObserver' in window || 'WebkitMutationObserver' in window);
  10640. })()
  10641. },
  10642. /*==================================================
  10643. Plugins
  10644. ====================================================*/
  10645. plugins: {}
  10646. };
  10647. /*===========================
  10648. Dom7 Library
  10649. ===========================*/
  10650. var Dom7 = (function () {
  10651. var Dom7 = function (arr) {
  10652. var _this = this, i = 0;
  10653. // Create array-like object
  10654. for (i = 0; i < arr.length; i++) {
  10655. _this[i] = arr[i];
  10656. }
  10657. _this.length = arr.length;
  10658. // Return collection with methods
  10659. return this;
  10660. };
  10661. var $ = function (selector, context) {
  10662. var arr = [], i = 0;
  10663. if (selector && !context) {
  10664. if (selector instanceof Dom7) {
  10665. return selector;
  10666. }
  10667. }
  10668. if (selector) {
  10669. // String
  10670. if (typeof selector === 'string') {
  10671. var els, tempParent, html = selector.trim();
  10672. if (html.indexOf('<') >= 0 && html.indexOf('>') >= 0) {
  10673. var toCreate = 'div';
  10674. if (html.indexOf('<li') === 0) toCreate = 'ul';
  10675. if (html.indexOf('<tr') === 0) toCreate = 'tbody';
  10676. if (html.indexOf('<td') === 0 || html.indexOf('<th') === 0) toCreate = 'tr';
  10677. if (html.indexOf('<tbody') === 0) toCreate = 'table';
  10678. if (html.indexOf('<option') === 0) toCreate = 'select';
  10679. tempParent = document.createElement(toCreate);
  10680. tempParent.innerHTML = selector;
  10681. for (i = 0; i < tempParent.childNodes.length; i++) {
  10682. arr.push(tempParent.childNodes[i]);
  10683. }
  10684. }
  10685. else {
  10686. if (!context && selector[0] === '#' && !selector.match(/[ .<>:~]/)) {
  10687. // Pure ID selector
  10688. els = [document.getElementById(selector.split('#')[1])];
  10689. }
  10690. else {
  10691. // Other selectors
  10692. els = (context || document).querySelectorAll(selector);
  10693. }
  10694. for (i = 0; i < els.length; i++) {
  10695. if (els[i]) arr.push(els[i]);
  10696. }
  10697. }
  10698. }
  10699. // Node/element
  10700. else if (selector.nodeType || selector === window || selector === document) {
  10701. arr.push(selector);
  10702. }
  10703. //Array of elements or instance of Dom
  10704. else if (selector.length > 0 && selector[0].nodeType) {
  10705. for (i = 0; i < selector.length; i++) {
  10706. arr.push(selector[i]);
  10707. }
  10708. }
  10709. }
  10710. return new Dom7(arr);
  10711. };
  10712. Dom7.prototype = {
  10713. // Classes and attriutes
  10714. addClass: function (className) {
  10715. if (typeof className === 'undefined') {
  10716. return this;
  10717. }
  10718. var classes = className.split(' ');
  10719. for (var i = 0; i < classes.length; i++) {
  10720. for (var j = 0; j < this.length; j++) {
  10721. this[j].classList.add(classes[i]);
  10722. }
  10723. }
  10724. return this;
  10725. },
  10726. removeClass: function (className) {
  10727. var classes = className.split(' ');
  10728. for (var i = 0; i < classes.length; i++) {
  10729. for (var j = 0; j < this.length; j++) {
  10730. this[j].classList.remove(classes[i]);
  10731. }
  10732. }
  10733. return this;
  10734. },
  10735. hasClass: function (className) {
  10736. if (!this[0]) return false;
  10737. else return this[0].classList.contains(className);
  10738. },
  10739. toggleClass: function (className) {
  10740. var classes = className.split(' ');
  10741. for (var i = 0; i < classes.length; i++) {
  10742. for (var j = 0; j < this.length; j++) {
  10743. this[j].classList.toggle(classes[i]);
  10744. }
  10745. }
  10746. return this;
  10747. },
  10748. attr: function (attrs, value) {
  10749. if (arguments.length === 1 && typeof attrs === 'string') {
  10750. // Get attr
  10751. if (this[0]) return this[0].getAttribute(attrs);
  10752. else return undefined;
  10753. }
  10754. else {
  10755. // Set attrs
  10756. for (var i = 0; i < this.length; i++) {
  10757. if (arguments.length === 2) {
  10758. // String
  10759. this[i].setAttribute(attrs, value);
  10760. }
  10761. else {
  10762. // Object
  10763. for (var attrName in attrs) {
  10764. this[i][attrName] = attrs[attrName];
  10765. this[i].setAttribute(attrName, attrs[attrName]);
  10766. }
  10767. }
  10768. }
  10769. return this;
  10770. }
  10771. },
  10772. removeAttr: function (attr) {
  10773. for (var i = 0; i < this.length; i++) {
  10774. this[i].removeAttribute(attr);
  10775. }
  10776. return this;
  10777. },
  10778. data: function (key, value) {
  10779. if (typeof value === 'undefined') {
  10780. // Get value
  10781. if (this[0]) {
  10782. var dataKey = this[0].getAttribute('data-' + key);
  10783. if (dataKey) return dataKey;
  10784. else if (this[0].dom7ElementDataStorage && (key in this[0].dom7ElementDataStorage)) return this[0].dom7ElementDataStorage[key];
  10785. else return undefined;
  10786. }
  10787. else return undefined;
  10788. }
  10789. else {
  10790. // Set value
  10791. for (var i = 0; i < this.length; i++) {
  10792. var el = this[i];
  10793. if (!el.dom7ElementDataStorage) el.dom7ElementDataStorage = {};
  10794. el.dom7ElementDataStorage[key] = value;
  10795. }
  10796. return this;
  10797. }
  10798. },
  10799. // Transforms
  10800. transform : function (transform) {
  10801. for (var i = 0; i < this.length; i++) {
  10802. var elStyle = this[i].style;
  10803. elStyle.webkitTransform = elStyle.MsTransform = elStyle.msTransform = elStyle.MozTransform = elStyle.OTransform = elStyle.transform = transform;
  10804. }
  10805. return this;
  10806. },
  10807. transition: function (duration) {
  10808. if (typeof duration !== 'string') {
  10809. duration = duration + 'ms';
  10810. }
  10811. for (var i = 0; i < this.length; i++) {
  10812. var elStyle = this[i].style;
  10813. elStyle.webkitTransitionDuration = elStyle.MsTransitionDuration = elStyle.msTransitionDuration = elStyle.MozTransitionDuration = elStyle.OTransitionDuration = elStyle.transitionDuration = duration;
  10814. }
  10815. return this;
  10816. },
  10817. //Events
  10818. on: function (eventName, targetSelector, listener, capture) {
  10819. function handleLiveEvent(e) {
  10820. var target = e.target;
  10821. if ($(target).is(targetSelector)) listener.call(target, e);
  10822. else {
  10823. var parents = $(target).parents();
  10824. for (var k = 0; k < parents.length; k++) {
  10825. if ($(parents[k]).is(targetSelector)) listener.call(parents[k], e);
  10826. }
  10827. }
  10828. }
  10829. var events = eventName.split(' ');
  10830. var i, j;
  10831. for (i = 0; i < this.length; i++) {
  10832. if (typeof targetSelector === 'function' || targetSelector === false) {
  10833. // Usual events
  10834. if (typeof targetSelector === 'function') {
  10835. listener = arguments[1];
  10836. capture = arguments[2] || false;
  10837. }
  10838. for (j = 0; j < events.length; j++) {
  10839. this[i].addEventListener(events[j], listener, capture);
  10840. }
  10841. }
  10842. else {
  10843. //Live events
  10844. for (j = 0; j < events.length; j++) {
  10845. if (!this[i].dom7LiveListeners) this[i].dom7LiveListeners = [];
  10846. this[i].dom7LiveListeners.push({listener: listener, liveListener: handleLiveEvent});
  10847. this[i].addEventListener(events[j], handleLiveEvent, capture);
  10848. }
  10849. }
  10850. }
  10851. return this;
  10852. },
  10853. off: function (eventName, targetSelector, listener, capture) {
  10854. var events = eventName.split(' ');
  10855. for (var i = 0; i < events.length; i++) {
  10856. for (var j = 0; j < this.length; j++) {
  10857. if (typeof targetSelector === 'function' || targetSelector === false) {
  10858. // Usual events
  10859. if (typeof targetSelector === 'function') {
  10860. listener = arguments[1];
  10861. capture = arguments[2] || false;
  10862. }
  10863. this[j].removeEventListener(events[i], listener, capture);
  10864. }
  10865. else {
  10866. // Live event
  10867. if (this[j].dom7LiveListeners) {
  10868. for (var k = 0; k < this[j].dom7LiveListeners.length; k++) {
  10869. if (this[j].dom7LiveListeners[k].listener === listener) {
  10870. this[j].removeEventListener(events[i], this[j].dom7LiveListeners[k].liveListener, capture);
  10871. }
  10872. }
  10873. }
  10874. }
  10875. }
  10876. }
  10877. return this;
  10878. },
  10879. once: function (eventName, targetSelector, listener, capture) {
  10880. var dom = this;
  10881. if (typeof targetSelector === 'function') {
  10882. targetSelector = false;
  10883. listener = arguments[1];
  10884. capture = arguments[2];
  10885. }
  10886. function proxy(e) {
  10887. listener(e);
  10888. dom.off(eventName, targetSelector, proxy, capture);
  10889. }
  10890. dom.on(eventName, targetSelector, proxy, capture);
  10891. },
  10892. trigger: function (eventName, eventData) {
  10893. for (var i = 0; i < this.length; i++) {
  10894. var evt;
  10895. try {
  10896. evt = new window.CustomEvent(eventName, {detail: eventData, bubbles: true, cancelable: true});
  10897. }
  10898. catch (e) {
  10899. evt = document.createEvent('Event');
  10900. evt.initEvent(eventName, true, true);
  10901. evt.detail = eventData;
  10902. }
  10903. this[i].dispatchEvent(evt);
  10904. }
  10905. return this;
  10906. },
  10907. transitionEnd: function (callback) {
  10908. var events = ['webkitTransitionEnd', 'transitionend', 'oTransitionEnd', 'MSTransitionEnd', 'msTransitionEnd'],
  10909. i, j, dom = this;
  10910. function fireCallBack(e) {
  10911. /*jshint validthis:true */
  10912. if (e.target !== this) return;
  10913. callback.call(this, e);
  10914. for (i = 0; i < events.length; i++) {
  10915. dom.off(events[i], fireCallBack);
  10916. }
  10917. }
  10918. if (callback) {
  10919. for (i = 0; i < events.length; i++) {
  10920. dom.on(events[i], fireCallBack);
  10921. }
  10922. }
  10923. return this;
  10924. },
  10925. // Sizing/Styles
  10926. width: function () {
  10927. if (this[0] === window) {
  10928. return window.innerWidth;
  10929. }
  10930. else {
  10931. if (this.length > 0) {
  10932. return parseFloat(this.css('width'));
  10933. }
  10934. else {
  10935. return null;
  10936. }
  10937. }
  10938. },
  10939. outerWidth: function (includeMargins) {
  10940. if (this.length > 0) {
  10941. if (includeMargins)
  10942. return this[0].offsetWidth + parseFloat(this.css('margin-right')) + parseFloat(this.css('margin-left'));
  10943. else
  10944. return this[0].offsetWidth;
  10945. }
  10946. else return null;
  10947. },
  10948. height: function () {
  10949. if (this[0] === window) {
  10950. return window.innerHeight;
  10951. }
  10952. else {
  10953. if (this.length > 0) {
  10954. return parseFloat(this.css('height'));
  10955. }
  10956. else {
  10957. return null;
  10958. }
  10959. }
  10960. },
  10961. outerHeight: function (includeMargins) {
  10962. if (this.length > 0) {
  10963. if (includeMargins)
  10964. return this[0].offsetHeight + parseFloat(this.css('margin-top')) + parseFloat(this.css('margin-bottom'));
  10965. else
  10966. return this[0].offsetHeight;
  10967. }
  10968. else return null;
  10969. },
  10970. offset: function () {
  10971. if (this.length > 0) {
  10972. var el = this[0];
  10973. var box = el.getBoundingClientRect();
  10974. var body = document.body;
  10975. var clientTop = el.clientTop || body.clientTop || 0;
  10976. var clientLeft = el.clientLeft || body.clientLeft || 0;
  10977. var scrollTop = window.pageYOffset || el.scrollTop;
  10978. var scrollLeft = window.pageXOffset || el.scrollLeft;
  10979. return {
  10980. top: box.top + scrollTop - clientTop,
  10981. left: box.left + scrollLeft - clientLeft
  10982. };
  10983. }
  10984. else {
  10985. return null;
  10986. }
  10987. },
  10988. css: function (props, value) {
  10989. var i;
  10990. if (arguments.length === 1) {
  10991. if (typeof props === 'string') {
  10992. if (this[0]) return window.getComputedStyle(this[0], null).getPropertyValue(props);
  10993. }
  10994. else {
  10995. for (i = 0; i < this.length; i++) {
  10996. for (var prop in props) {
  10997. this[i].style[prop] = props[prop];
  10998. }
  10999. }
  11000. return this;
  11001. }
  11002. }
  11003. if (arguments.length === 2 && typeof props === 'string') {
  11004. for (i = 0; i < this.length; i++) {
  11005. this[i].style[props] = value;
  11006. }
  11007. return this;
  11008. }
  11009. return this;
  11010. },
  11011. //Dom manipulation
  11012. each: function (callback) {
  11013. for (var i = 0; i < this.length; i++) {
  11014. callback.call(this[i], i, this[i]);
  11015. }
  11016. return this;
  11017. },
  11018. html: function (html) {
  11019. if (typeof html === 'undefined') {
  11020. return this[0] ? this[0].innerHTML : undefined;
  11021. }
  11022. else {
  11023. for (var i = 0; i < this.length; i++) {
  11024. this[i].innerHTML = html;
  11025. }
  11026. return this;
  11027. }
  11028. },
  11029. is: function (selector) {
  11030. if (!this[0]) return false;
  11031. var compareWith, i;
  11032. if (typeof selector === 'string') {
  11033. var el = this[0];
  11034. if (el === document) return selector === document;
  11035. if (el === window) return selector === window;
  11036. if (el.matches) return el.matches(selector);
  11037. else if (el.webkitMatchesSelector) return el.webkitMatchesSelector(selector);
  11038. else if (el.mozMatchesSelector) return el.mozMatchesSelector(selector);
  11039. else if (el.msMatchesSelector) return el.msMatchesSelector(selector);
  11040. else {
  11041. compareWith = $(selector);
  11042. for (i = 0; i < compareWith.length; i++) {
  11043. if (compareWith[i] === this[0]) return true;
  11044. }
  11045. return false;
  11046. }
  11047. }
  11048. else if (selector === document) return this[0] === document;
  11049. else if (selector === window) return this[0] === window;
  11050. else {
  11051. if (selector.nodeType || selector instanceof Dom7) {
  11052. compareWith = selector.nodeType ? [selector] : selector;
  11053. for (i = 0; i < compareWith.length; i++) {
  11054. if (compareWith[i] === this[0]) return true;
  11055. }
  11056. return false;
  11057. }
  11058. return false;
  11059. }
  11060. },
  11061. index: function () {
  11062. if (this[0]) {
  11063. var child = this[0];
  11064. var i = 0;
  11065. while ((child = child.previousSibling) !== null) {
  11066. if (child.nodeType === 1) i++;
  11067. }
  11068. return i;
  11069. }
  11070. else return undefined;
  11071. },
  11072. eq: function (index) {
  11073. if (typeof index === 'undefined') return this;
  11074. var length = this.length;
  11075. var returnIndex;
  11076. if (index > length - 1) {
  11077. return new Dom7([]);
  11078. }
  11079. if (index < 0) {
  11080. returnIndex = length + index;
  11081. if (returnIndex < 0) return new Dom7([]);
  11082. else return new Dom7([this[returnIndex]]);
  11083. }
  11084. return new Dom7([this[index]]);
  11085. },
  11086. append: function (newChild) {
  11087. var i, j;
  11088. for (i = 0; i < this.length; i++) {
  11089. if (typeof newChild === 'string') {
  11090. var tempDiv = document.createElement('div');
  11091. tempDiv.innerHTML = newChild;
  11092. while (tempDiv.firstChild) {
  11093. this[i].appendChild(tempDiv.firstChild);
  11094. }
  11095. }
  11096. else if (newChild instanceof Dom7) {
  11097. for (j = 0; j < newChild.length; j++) {
  11098. this[i].appendChild(newChild[j]);
  11099. }
  11100. }
  11101. else {
  11102. this[i].appendChild(newChild);
  11103. }
  11104. }
  11105. return this;
  11106. },
  11107. prepend: function (newChild) {
  11108. var i, j;
  11109. for (i = 0; i < this.length; i++) {
  11110. if (typeof newChild === 'string') {
  11111. var tempDiv = document.createElement('div');
  11112. tempDiv.innerHTML = newChild;
  11113. for (j = tempDiv.childNodes.length - 1; j >= 0; j--) {
  11114. this[i].insertBefore(tempDiv.childNodes[j], this[i].childNodes[0]);
  11115. }
  11116. // this[i].insertAdjacentHTML('afterbegin', newChild);
  11117. }
  11118. else if (newChild instanceof Dom7) {
  11119. for (j = 0; j < newChild.length; j++) {
  11120. this[i].insertBefore(newChild[j], this[i].childNodes[0]);
  11121. }
  11122. }
  11123. else {
  11124. this[i].insertBefore(newChild, this[i].childNodes[0]);
  11125. }
  11126. }
  11127. return this;
  11128. },
  11129. insertBefore: function (selector) {
  11130. var before = $(selector);
  11131. for (var i = 0; i < this.length; i++) {
  11132. if (before.length === 1) {
  11133. before[0].parentNode.insertBefore(this[i], before[0]);
  11134. }
  11135. else if (before.length > 1) {
  11136. for (var j = 0; j < before.length; j++) {
  11137. before[j].parentNode.insertBefore(this[i].cloneNode(true), before[j]);
  11138. }
  11139. }
  11140. }
  11141. },
  11142. insertAfter: function (selector) {
  11143. var after = $(selector);
  11144. for (var i = 0; i < this.length; i++) {
  11145. if (after.length === 1) {
  11146. after[0].parentNode.insertBefore(this[i], after[0].nextSibling);
  11147. }
  11148. else if (after.length > 1) {
  11149. for (var j = 0; j < after.length; j++) {
  11150. after[j].parentNode.insertBefore(this[i].cloneNode(true), after[j].nextSibling);
  11151. }
  11152. }
  11153. }
  11154. },
  11155. next: function (selector) {
  11156. if (this.length > 0) {
  11157. if (selector) {
  11158. if (this[0].nextElementSibling && $(this[0].nextElementSibling).is(selector)) return new Dom7([this[0].nextElementSibling]);
  11159. else return new Dom7([]);
  11160. }
  11161. else {
  11162. if (this[0].nextElementSibling) return new Dom7([this[0].nextElementSibling]);
  11163. else return new Dom7([]);
  11164. }
  11165. }
  11166. else return new Dom7([]);
  11167. },
  11168. nextAll: function (selector) {
  11169. var nextEls = [];
  11170. var el = this[0];
  11171. if (!el) return new Dom7([]);
  11172. while (el.nextElementSibling) {
  11173. var next = el.nextElementSibling;
  11174. if (selector) {
  11175. if($(next).is(selector)) nextEls.push(next);
  11176. }
  11177. else nextEls.push(next);
  11178. el = next;
  11179. }
  11180. return new Dom7(nextEls);
  11181. },
  11182. prev: function (selector) {
  11183. if (this.length > 0) {
  11184. if (selector) {
  11185. if (this[0].previousElementSibling && $(this[0].previousElementSibling).is(selector)) return new Dom7([this[0].previousElementSibling]);
  11186. else return new Dom7([]);
  11187. }
  11188. else {
  11189. if (this[0].previousElementSibling) return new Dom7([this[0].previousElementSibling]);
  11190. else return new Dom7([]);
  11191. }
  11192. }
  11193. else return new Dom7([]);
  11194. },
  11195. prevAll: function (selector) {
  11196. var prevEls = [];
  11197. var el = this[0];
  11198. if (!el) return new Dom7([]);
  11199. while (el.previousElementSibling) {
  11200. var prev = el.previousElementSibling;
  11201. if (selector) {
  11202. if($(prev).is(selector)) prevEls.push(prev);
  11203. }
  11204. else prevEls.push(prev);
  11205. el = prev;
  11206. }
  11207. return new Dom7(prevEls);
  11208. },
  11209. parent: function (selector) {
  11210. var parents = [];
  11211. for (var i = 0; i < this.length; i++) {
  11212. if (selector) {
  11213. if ($(this[i].parentNode).is(selector)) parents.push(this[i].parentNode);
  11214. }
  11215. else {
  11216. parents.push(this[i].parentNode);
  11217. }
  11218. }
  11219. return $($.unique(parents));
  11220. },
  11221. parents: function (selector) {
  11222. var parents = [];
  11223. for (var i = 0; i < this.length; i++) {
  11224. var parent = this[i].parentNode;
  11225. while (parent) {
  11226. if (selector) {
  11227. if ($(parent).is(selector)) parents.push(parent);
  11228. }
  11229. else {
  11230. parents.push(parent);
  11231. }
  11232. parent = parent.parentNode;
  11233. }
  11234. }
  11235. return $($.unique(parents));
  11236. },
  11237. find : function (selector) {
  11238. var foundElements = [];
  11239. for (var i = 0; i < this.length; i++) {
  11240. var found = this[i].querySelectorAll(selector);
  11241. for (var j = 0; j < found.length; j++) {
  11242. foundElements.push(found[j]);
  11243. }
  11244. }
  11245. return new Dom7(foundElements);
  11246. },
  11247. children: function (selector) {
  11248. var children = [];
  11249. for (var i = 0; i < this.length; i++) {
  11250. var childNodes = this[i].childNodes;
  11251. for (var j = 0; j < childNodes.length; j++) {
  11252. if (!selector) {
  11253. if (childNodes[j].nodeType === 1) children.push(childNodes[j]);
  11254. }
  11255. else {
  11256. if (childNodes[j].nodeType === 1 && $(childNodes[j]).is(selector)) children.push(childNodes[j]);
  11257. }
  11258. }
  11259. }
  11260. return new Dom7($.unique(children));
  11261. },
  11262. remove: function () {
  11263. for (var i = 0; i < this.length; i++) {
  11264. if (this[i].parentNode) this[i].parentNode.removeChild(this[i]);
  11265. }
  11266. return this;
  11267. },
  11268. add: function () {
  11269. var dom = this;
  11270. var i, j;
  11271. for (i = 0; i < arguments.length; i++) {
  11272. var toAdd = $(arguments[i]);
  11273. for (j = 0; j < toAdd.length; j++) {
  11274. dom[dom.length] = toAdd[j];
  11275. dom.length++;
  11276. }
  11277. }
  11278. return dom;
  11279. }
  11280. };
  11281. $.fn = Dom7.prototype;
  11282. $.unique = function (arr) {
  11283. var unique = [];
  11284. for (var i = 0; i < arr.length; i++) {
  11285. if (unique.indexOf(arr[i]) === -1) unique.push(arr[i]);
  11286. }
  11287. return unique;
  11288. };
  11289. return $;
  11290. })();
  11291. /*===========================
  11292. Get Dom libraries
  11293. ===========================*/
  11294. var swiperDomPlugins = ['jQuery', 'Zepto', 'Dom7'];
  11295. for (var i = 0; i < swiperDomPlugins.length; i++) {
  11296. if (window[swiperDomPlugins[i]]) {
  11297. addLibraryPlugin(window[swiperDomPlugins[i]]);
  11298. }
  11299. }
  11300. // Required DOM Plugins
  11301. var domLib;
  11302. if (typeof Dom7 === 'undefined') {
  11303. domLib = window.Dom7 || window.Zepto || window.jQuery;
  11304. }
  11305. else {
  11306. domLib = Dom7;
  11307. }
  11308. /*===========================
  11309. Add .swiper plugin from Dom libraries
  11310. ===========================*/
  11311. function addLibraryPlugin(lib) {
  11312. lib.fn.swiper = function (params) {
  11313. var firstInstance;
  11314. lib(this).each(function () {
  11315. var s = new Swiper(this, params);
  11316. if (!firstInstance) firstInstance = s;
  11317. });
  11318. return firstInstance;
  11319. };
  11320. }
  11321. if (domLib) {
  11322. if (!('transitionEnd' in domLib.fn)) {
  11323. domLib.fn.transitionEnd = function (callback) {
  11324. var events = ['webkitTransitionEnd', 'transitionend', 'oTransitionEnd', 'MSTransitionEnd', 'msTransitionEnd'],
  11325. i, j, dom = this;
  11326. function fireCallBack(e) {
  11327. /*jshint validthis:true */
  11328. if (e.target !== this) return;
  11329. callback.call(this, e);
  11330. for (i = 0; i < events.length; i++) {
  11331. dom.off(events[i], fireCallBack);
  11332. }
  11333. }
  11334. if (callback) {
  11335. for (i = 0; i < events.length; i++) {
  11336. dom.on(events[i], fireCallBack);
  11337. }
  11338. }
  11339. return this;
  11340. };
  11341. }
  11342. if (!('transform' in domLib.fn)) {
  11343. domLib.fn.transform = function (transform) {
  11344. for (var i = 0; i < this.length; i++) {
  11345. var elStyle = this[i].style;
  11346. elStyle.webkitTransform = elStyle.MsTransform = elStyle.msTransform = elStyle.MozTransform = elStyle.OTransform = elStyle.transform = transform;
  11347. }
  11348. return this;
  11349. };
  11350. }
  11351. if (!('transition' in domLib.fn)) {
  11352. domLib.fn.transition = function (duration) {
  11353. if (typeof duration !== 'string') {
  11354. duration = duration + 'ms';
  11355. }
  11356. for (var i = 0; i < this.length; i++) {
  11357. var elStyle = this[i].style;
  11358. elStyle.webkitTransitionDuration = elStyle.MsTransitionDuration = elStyle.msTransitionDuration = elStyle.MozTransitionDuration = elStyle.OTransitionDuration = elStyle.transitionDuration = duration;
  11359. }
  11360. return this;
  11361. };
  11362. }
  11363. }
  11364. ionic.views.Swiper = Swiper;
  11365. })();
  11366. (function(ionic) {
  11367. 'use strict';
  11368. ionic.views.Toggle = ionic.views.View.inherit({
  11369. initialize: function(opts) {
  11370. var self = this;
  11371. this.el = opts.el;
  11372. this.checkbox = opts.checkbox;
  11373. this.track = opts.track;
  11374. this.handle = opts.handle;
  11375. this.openPercent = -1;
  11376. this.onChange = opts.onChange || function() {};
  11377. this.triggerThreshold = opts.triggerThreshold || 20;
  11378. this.dragStartHandler = function(e) {
  11379. self.dragStart(e);
  11380. };
  11381. this.dragHandler = function(e) {
  11382. self.drag(e);
  11383. };
  11384. this.holdHandler = function(e) {
  11385. self.hold(e);
  11386. };
  11387. this.releaseHandler = function(e) {
  11388. self.release(e);
  11389. };
  11390. this.dragStartGesture = ionic.onGesture('dragstart', this.dragStartHandler, this.el);
  11391. this.dragGesture = ionic.onGesture('drag', this.dragHandler, this.el);
  11392. this.dragHoldGesture = ionic.onGesture('hold', this.holdHandler, this.el);
  11393. this.dragReleaseGesture = ionic.onGesture('release', this.releaseHandler, this.el);
  11394. },
  11395. destroy: function() {
  11396. ionic.offGesture(this.dragStartGesture, 'dragstart', this.dragStartGesture);
  11397. ionic.offGesture(this.dragGesture, 'drag', this.dragGesture);
  11398. ionic.offGesture(this.dragHoldGesture, 'hold', this.holdHandler);
  11399. ionic.offGesture(this.dragReleaseGesture, 'release', this.releaseHandler);
  11400. },
  11401. tap: function() {
  11402. if(this.el.getAttribute('disabled') !== 'disabled') {
  11403. this.val( !this.checkbox.checked );
  11404. }
  11405. },
  11406. dragStart: function(e) {
  11407. if(this.checkbox.disabled) return;
  11408. this._dragInfo = {
  11409. width: this.el.offsetWidth,
  11410. left: this.el.offsetLeft,
  11411. right: this.el.offsetLeft + this.el.offsetWidth,
  11412. triggerX: this.el.offsetWidth / 2,
  11413. initialState: this.checkbox.checked
  11414. };
  11415. // Stop any parent dragging
  11416. e.gesture.srcEvent.preventDefault();
  11417. // Trigger hold styles
  11418. this.hold(e);
  11419. },
  11420. drag: function(e) {
  11421. var self = this;
  11422. if(!this._dragInfo) { return; }
  11423. // Stop any parent dragging
  11424. e.gesture.srcEvent.preventDefault();
  11425. ionic.requestAnimationFrame(function () {
  11426. if (!self._dragInfo) { return; }
  11427. var px = e.gesture.touches[0].pageX - self._dragInfo.left;
  11428. var mx = self._dragInfo.width - self.triggerThreshold;
  11429. // The initial state was on, so "tend towards" on
  11430. if(self._dragInfo.initialState) {
  11431. if(px < self.triggerThreshold) {
  11432. self.setOpenPercent(0);
  11433. } else if(px > self._dragInfo.triggerX) {
  11434. self.setOpenPercent(100);
  11435. }
  11436. } else {
  11437. // The initial state was off, so "tend towards" off
  11438. if(px < self._dragInfo.triggerX) {
  11439. self.setOpenPercent(0);
  11440. } else if(px > mx) {
  11441. self.setOpenPercent(100);
  11442. }
  11443. }
  11444. });
  11445. },
  11446. endDrag: function() {
  11447. this._dragInfo = null;
  11448. },
  11449. hold: function() {
  11450. this.el.classList.add('dragging');
  11451. },
  11452. release: function(e) {
  11453. this.el.classList.remove('dragging');
  11454. this.endDrag(e);
  11455. },
  11456. setOpenPercent: function(openPercent) {
  11457. // only make a change if the new open percent has changed
  11458. if(this.openPercent < 0 || (openPercent < (this.openPercent - 3) || openPercent > (this.openPercent + 3) ) ) {
  11459. this.openPercent = openPercent;
  11460. if(openPercent === 0) {
  11461. this.val(false);
  11462. } else if(openPercent === 100) {
  11463. this.val(true);
  11464. } else {
  11465. var openPixel = Math.round( (openPercent / 100) * this.track.offsetWidth - (this.handle.offsetWidth) );
  11466. openPixel = (openPixel < 1 ? 0 : openPixel);
  11467. this.handle.style[ionic.CSS.TRANSFORM] = 'translate3d(' + openPixel + 'px,0,0)';
  11468. }
  11469. }
  11470. },
  11471. val: function(value) {
  11472. if(value === true || value === false) {
  11473. if(this.handle.style[ionic.CSS.TRANSFORM] !== "") {
  11474. this.handle.style[ionic.CSS.TRANSFORM] = "";
  11475. }
  11476. this.checkbox.checked = value;
  11477. this.openPercent = (value ? 100 : 0);
  11478. this.onChange && this.onChange();
  11479. }
  11480. return this.checkbox.checked;
  11481. }
  11482. });
  11483. })(ionic);
  11484. })();