Brak opisu

ui-bootstrap.js 228KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783678467856786678767886789679067916792679367946795679667976798679968006801680268036804680568066807680868096810681168126813681468156816681768186819682068216822682368246825682668276828682968306831683268336834683568366837683868396840684168426843684468456846684768486849685068516852685368546855685668576858685968606861686268636864686568666867686868696870687168726873687468756876687768786879688068816882688368846885688668876888688968906891689268936894689568966897689868996900690169026903690469056906690769086909691069116912691369146915691669176918691969206921692269236924
  1. /*
  2. * angular-ui-bootstrap
  3. * http://angular-ui.github.io/bootstrap/
  4. * Version: 1.3.3 - 2016-05-22
  5. * License: MIT
  6. */angular.module("ui.bootstrap", ["ui.bootstrap.collapse","ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.buttons","ui.bootstrap.carousel","ui.bootstrap.dateparser","ui.bootstrap.isClass","ui.bootstrap.datepicker","ui.bootstrap.position","ui.bootstrap.datepickerPopup","ui.bootstrap.debounce","ui.bootstrap.dropdown","ui.bootstrap.stackedMap","ui.bootstrap.modal","ui.bootstrap.paging","ui.bootstrap.pager","ui.bootstrap.pagination","ui.bootstrap.tooltip","ui.bootstrap.popover","ui.bootstrap.progressbar","ui.bootstrap.rating","ui.bootstrap.tabs","ui.bootstrap.timepicker","ui.bootstrap.typeahead"]);
  7. angular.module('ui.bootstrap.collapse', [])
  8. .directive('uibCollapse', ['$animate', '$q', '$parse', '$injector', function($animate, $q, $parse, $injector) {
  9. var $animateCss = $injector.has('$animateCss') ? $injector.get('$animateCss') : null;
  10. return {
  11. link: function(scope, element, attrs) {
  12. var expandingExpr = $parse(attrs.expanding),
  13. expandedExpr = $parse(attrs.expanded),
  14. collapsingExpr = $parse(attrs.collapsing),
  15. collapsedExpr = $parse(attrs.collapsed);
  16. if (!scope.$eval(attrs.uibCollapse)) {
  17. element.addClass('in')
  18. .addClass('collapse')
  19. .attr('aria-expanded', true)
  20. .attr('aria-hidden', false)
  21. .css({height: 'auto'});
  22. }
  23. function expand() {
  24. if (element.hasClass('collapse') && element.hasClass('in')) {
  25. return;
  26. }
  27. $q.resolve(expandingExpr(scope))
  28. .then(function() {
  29. element.removeClass('collapse')
  30. .addClass('collapsing')
  31. .attr('aria-expanded', true)
  32. .attr('aria-hidden', false);
  33. if ($animateCss) {
  34. $animateCss(element, {
  35. addClass: 'in',
  36. easing: 'ease',
  37. to: { height: element[0].scrollHeight + 'px' }
  38. }).start()['finally'](expandDone);
  39. } else {
  40. $animate.addClass(element, 'in', {
  41. to: { height: element[0].scrollHeight + 'px' }
  42. }).then(expandDone);
  43. }
  44. });
  45. }
  46. function expandDone() {
  47. element.removeClass('collapsing')
  48. .addClass('collapse')
  49. .css({height: 'auto'});
  50. expandedExpr(scope);
  51. }
  52. function collapse() {
  53. if (!element.hasClass('collapse') && !element.hasClass('in')) {
  54. return collapseDone();
  55. }
  56. $q.resolve(collapsingExpr(scope))
  57. .then(function() {
  58. element
  59. // IMPORTANT: The height must be set before adding "collapsing" class.
  60. // Otherwise, the browser attempts to animate from height 0 (in
  61. // collapsing class) to the given height here.
  62. .css({height: element[0].scrollHeight + 'px'})
  63. // initially all panel collapse have the collapse class, this removal
  64. // prevents the animation from jumping to collapsed state
  65. .removeClass('collapse')
  66. .addClass('collapsing')
  67. .attr('aria-expanded', false)
  68. .attr('aria-hidden', true);
  69. if ($animateCss) {
  70. $animateCss(element, {
  71. removeClass: 'in',
  72. to: {height: '0'}
  73. }).start()['finally'](collapseDone);
  74. } else {
  75. $animate.removeClass(element, 'in', {
  76. to: {height: '0'}
  77. }).then(collapseDone);
  78. }
  79. });
  80. }
  81. function collapseDone() {
  82. element.css({height: '0'}); // Required so that collapse works when animation is disabled
  83. element.removeClass('collapsing')
  84. .addClass('collapse');
  85. collapsedExpr(scope);
  86. }
  87. scope.$watch(attrs.uibCollapse, function(shouldCollapse) {
  88. if (shouldCollapse) {
  89. collapse();
  90. } else {
  91. expand();
  92. }
  93. });
  94. }
  95. };
  96. }]);
  97. angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse'])
  98. .constant('uibAccordionConfig', {
  99. closeOthers: true
  100. })
  101. .controller('UibAccordionController', ['$scope', '$attrs', 'uibAccordionConfig', function($scope, $attrs, accordionConfig) {
  102. // This array keeps track of the accordion groups
  103. this.groups = [];
  104. // Ensure that all the groups in this accordion are closed, unless close-others explicitly says not to
  105. this.closeOthers = function(openGroup) {
  106. var closeOthers = angular.isDefined($attrs.closeOthers) ?
  107. $scope.$eval($attrs.closeOthers) : accordionConfig.closeOthers;
  108. if (closeOthers) {
  109. angular.forEach(this.groups, function(group) {
  110. if (group !== openGroup) {
  111. group.isOpen = false;
  112. }
  113. });
  114. }
  115. };
  116. // This is called from the accordion-group directive to add itself to the accordion
  117. this.addGroup = function(groupScope) {
  118. var that = this;
  119. this.groups.push(groupScope);
  120. groupScope.$on('$destroy', function(event) {
  121. that.removeGroup(groupScope);
  122. });
  123. };
  124. // This is called from the accordion-group directive when to remove itself
  125. this.removeGroup = function(group) {
  126. var index = this.groups.indexOf(group);
  127. if (index !== -1) {
  128. this.groups.splice(index, 1);
  129. }
  130. };
  131. }])
  132. // The accordion directive simply sets up the directive controller
  133. // and adds an accordion CSS class to itself element.
  134. .directive('uibAccordion', function() {
  135. return {
  136. controller: 'UibAccordionController',
  137. controllerAs: 'accordion',
  138. transclude: true,
  139. templateUrl: function(element, attrs) {
  140. return attrs.templateUrl || 'uib/template/accordion/accordion.html';
  141. }
  142. };
  143. })
  144. // The accordion-group directive indicates a block of html that will expand and collapse in an accordion
  145. .directive('uibAccordionGroup', function() {
  146. return {
  147. require: '^uibAccordion', // We need this directive to be inside an accordion
  148. transclude: true, // It transcludes the contents of the directive into the template
  149. replace: true, // The element containing the directive will be replaced with the template
  150. templateUrl: function(element, attrs) {
  151. return attrs.templateUrl || 'uib/template/accordion/accordion-group.html';
  152. },
  153. scope: {
  154. heading: '@', // Interpolate the heading attribute onto this scope
  155. panelClass: '@?', // Ditto with panelClass
  156. isOpen: '=?',
  157. isDisabled: '=?'
  158. },
  159. controller: function() {
  160. this.setHeading = function(element) {
  161. this.heading = element;
  162. };
  163. },
  164. link: function(scope, element, attrs, accordionCtrl) {
  165. accordionCtrl.addGroup(scope);
  166. scope.openClass = attrs.openClass || 'panel-open';
  167. scope.panelClass = attrs.panelClass || 'panel-default';
  168. scope.$watch('isOpen', function(value) {
  169. element.toggleClass(scope.openClass, !!value);
  170. if (value) {
  171. accordionCtrl.closeOthers(scope);
  172. }
  173. });
  174. scope.toggleOpen = function($event) {
  175. if (!scope.isDisabled) {
  176. if (!$event || $event.which === 32) {
  177. scope.isOpen = !scope.isOpen;
  178. }
  179. }
  180. };
  181. var id = 'accordiongroup-' + scope.$id + '-' + Math.floor(Math.random() * 10000);
  182. scope.headingId = id + '-tab';
  183. scope.panelId = id + '-panel';
  184. }
  185. };
  186. })
  187. // Use accordion-heading below an accordion-group to provide a heading containing HTML
  188. .directive('uibAccordionHeading', function() {
  189. return {
  190. transclude: true, // Grab the contents to be used as the heading
  191. template: '', // In effect remove this element!
  192. replace: true,
  193. require: '^uibAccordionGroup',
  194. link: function(scope, element, attrs, accordionGroupCtrl, transclude) {
  195. // Pass the heading to the accordion-group controller
  196. // so that it can be transcluded into the right place in the template
  197. // [The second parameter to transclude causes the elements to be cloned so that they work in ng-repeat]
  198. accordionGroupCtrl.setHeading(transclude(scope, angular.noop));
  199. }
  200. };
  201. })
  202. // Use in the accordion-group template to indicate where you want the heading to be transcluded
  203. // You must provide the property on the accordion-group controller that will hold the transcluded element
  204. .directive('uibAccordionTransclude', function() {
  205. return {
  206. require: '^uibAccordionGroup',
  207. link: function(scope, element, attrs, controller) {
  208. scope.$watch(function() { return controller[attrs.uibAccordionTransclude]; }, function(heading) {
  209. if (heading) {
  210. var elem = angular.element(element[0].querySelector(getHeaderSelectors()));
  211. elem.html('');
  212. elem.append(heading);
  213. }
  214. });
  215. }
  216. };
  217. function getHeaderSelectors() {
  218. return 'uib-accordion-header,' +
  219. 'data-uib-accordion-header,' +
  220. 'x-uib-accordion-header,' +
  221. 'uib\\:accordion-header,' +
  222. '[uib-accordion-header],' +
  223. '[data-uib-accordion-header],' +
  224. '[x-uib-accordion-header]';
  225. }
  226. });
  227. angular.module('ui.bootstrap.alert', [])
  228. .controller('UibAlertController', ['$scope', '$attrs', '$interpolate', '$timeout', function($scope, $attrs, $interpolate, $timeout) {
  229. $scope.closeable = !!$attrs.close;
  230. var dismissOnTimeout = angular.isDefined($attrs.dismissOnTimeout) ?
  231. $interpolate($attrs.dismissOnTimeout)($scope.$parent) : null;
  232. if (dismissOnTimeout) {
  233. $timeout(function() {
  234. $scope.close();
  235. }, parseInt(dismissOnTimeout, 10));
  236. }
  237. }])
  238. .directive('uibAlert', function() {
  239. return {
  240. controller: 'UibAlertController',
  241. controllerAs: 'alert',
  242. templateUrl: function(element, attrs) {
  243. return attrs.templateUrl || 'uib/template/alert/alert.html';
  244. },
  245. transclude: true,
  246. replace: true,
  247. scope: {
  248. type: '@',
  249. close: '&'
  250. }
  251. };
  252. });
  253. angular.module('ui.bootstrap.buttons', [])
  254. .constant('uibButtonConfig', {
  255. activeClass: 'active',
  256. toggleEvent: 'click'
  257. })
  258. .controller('UibButtonsController', ['uibButtonConfig', function(buttonConfig) {
  259. this.activeClass = buttonConfig.activeClass || 'active';
  260. this.toggleEvent = buttonConfig.toggleEvent || 'click';
  261. }])
  262. .directive('uibBtnRadio', ['$parse', function($parse) {
  263. return {
  264. require: ['uibBtnRadio', 'ngModel'],
  265. controller: 'UibButtonsController',
  266. controllerAs: 'buttons',
  267. link: function(scope, element, attrs, ctrls) {
  268. var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1];
  269. var uncheckableExpr = $parse(attrs.uibUncheckable);
  270. element.find('input').css({display: 'none'});
  271. //model -> UI
  272. ngModelCtrl.$render = function() {
  273. element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, scope.$eval(attrs.uibBtnRadio)));
  274. };
  275. //ui->model
  276. element.on(buttonsCtrl.toggleEvent, function() {
  277. if (attrs.disabled) {
  278. return;
  279. }
  280. var isActive = element.hasClass(buttonsCtrl.activeClass);
  281. if (!isActive || angular.isDefined(attrs.uncheckable)) {
  282. scope.$apply(function() {
  283. ngModelCtrl.$setViewValue(isActive ? null : scope.$eval(attrs.uibBtnRadio));
  284. ngModelCtrl.$render();
  285. });
  286. }
  287. });
  288. if (attrs.uibUncheckable) {
  289. scope.$watch(uncheckableExpr, function(uncheckable) {
  290. attrs.$set('uncheckable', uncheckable ? '' : undefined);
  291. });
  292. }
  293. }
  294. };
  295. }])
  296. .directive('uibBtnCheckbox', function() {
  297. return {
  298. require: ['uibBtnCheckbox', 'ngModel'],
  299. controller: 'UibButtonsController',
  300. controllerAs: 'button',
  301. link: function(scope, element, attrs, ctrls) {
  302. var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1];
  303. element.find('input').css({display: 'none'});
  304. function getTrueValue() {
  305. return getCheckboxValue(attrs.btnCheckboxTrue, true);
  306. }
  307. function getFalseValue() {
  308. return getCheckboxValue(attrs.btnCheckboxFalse, false);
  309. }
  310. function getCheckboxValue(attribute, defaultValue) {
  311. return angular.isDefined(attribute) ? scope.$eval(attribute) : defaultValue;
  312. }
  313. //model -> UI
  314. ngModelCtrl.$render = function() {
  315. element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, getTrueValue()));
  316. };
  317. //ui->model
  318. element.on(buttonsCtrl.toggleEvent, function() {
  319. if (attrs.disabled) {
  320. return;
  321. }
  322. scope.$apply(function() {
  323. ngModelCtrl.$setViewValue(element.hasClass(buttonsCtrl.activeClass) ? getFalseValue() : getTrueValue());
  324. ngModelCtrl.$render();
  325. });
  326. });
  327. }
  328. };
  329. });
  330. angular.module('ui.bootstrap.carousel', [])
  331. .controller('UibCarouselController', ['$scope', '$element', '$interval', '$timeout', '$animate', function($scope, $element, $interval, $timeout, $animate) {
  332. var self = this,
  333. slides = self.slides = $scope.slides = [],
  334. SLIDE_DIRECTION = 'uib-slideDirection',
  335. currentIndex = $scope.active,
  336. currentInterval, isPlaying, bufferedTransitions = [];
  337. var destroyed = false;
  338. self.addSlide = function(slide, element) {
  339. slides.push({
  340. slide: slide,
  341. element: element
  342. });
  343. slides.sort(function(a, b) {
  344. return +a.slide.index - +b.slide.index;
  345. });
  346. //if this is the first slide or the slide is set to active, select it
  347. if (slide.index === $scope.active || slides.length === 1 && !angular.isNumber($scope.active)) {
  348. if ($scope.$currentTransition) {
  349. $scope.$currentTransition = null;
  350. }
  351. currentIndex = slide.index;
  352. $scope.active = slide.index;
  353. setActive(currentIndex);
  354. self.select(slides[findSlideIndex(slide)]);
  355. if (slides.length === 1) {
  356. $scope.play();
  357. }
  358. }
  359. };
  360. self.getCurrentIndex = function() {
  361. for (var i = 0; i < slides.length; i++) {
  362. if (slides[i].slide.index === currentIndex) {
  363. return i;
  364. }
  365. }
  366. };
  367. self.next = $scope.next = function() {
  368. var newIndex = (self.getCurrentIndex() + 1) % slides.length;
  369. if (newIndex === 0 && $scope.noWrap()) {
  370. $scope.pause();
  371. return;
  372. }
  373. return self.select(slides[newIndex], 'next');
  374. };
  375. self.prev = $scope.prev = function() {
  376. var newIndex = self.getCurrentIndex() - 1 < 0 ? slides.length - 1 : self.getCurrentIndex() - 1;
  377. if ($scope.noWrap() && newIndex === slides.length - 1) {
  378. $scope.pause();
  379. return;
  380. }
  381. return self.select(slides[newIndex], 'prev');
  382. };
  383. self.removeSlide = function(slide) {
  384. var index = findSlideIndex(slide);
  385. var bufferedIndex = bufferedTransitions.indexOf(slides[index]);
  386. if (bufferedIndex !== -1) {
  387. bufferedTransitions.splice(bufferedIndex, 1);
  388. }
  389. //get the index of the slide inside the carousel
  390. slides.splice(index, 1);
  391. if (slides.length > 0 && currentIndex === index) {
  392. if (index >= slides.length) {
  393. currentIndex = slides.length - 1;
  394. $scope.active = currentIndex;
  395. setActive(currentIndex);
  396. self.select(slides[slides.length - 1]);
  397. } else {
  398. currentIndex = index;
  399. $scope.active = currentIndex;
  400. setActive(currentIndex);
  401. self.select(slides[index]);
  402. }
  403. } else if (currentIndex > index) {
  404. currentIndex--;
  405. $scope.active = currentIndex;
  406. }
  407. //clean the active value when no more slide
  408. if (slides.length === 0) {
  409. currentIndex = null;
  410. $scope.active = null;
  411. clearBufferedTransitions();
  412. }
  413. };
  414. /* direction: "prev" or "next" */
  415. self.select = $scope.select = function(nextSlide, direction) {
  416. var nextIndex = findSlideIndex(nextSlide.slide);
  417. //Decide direction if it's not given
  418. if (direction === undefined) {
  419. direction = nextIndex > self.getCurrentIndex() ? 'next' : 'prev';
  420. }
  421. //Prevent this user-triggered transition from occurring if there is already one in progress
  422. if (nextSlide.slide.index !== currentIndex &&
  423. !$scope.$currentTransition) {
  424. goNext(nextSlide.slide, nextIndex, direction);
  425. } else if (nextSlide && nextSlide.slide.index !== currentIndex && $scope.$currentTransition) {
  426. bufferedTransitions.push(slides[nextIndex]);
  427. }
  428. };
  429. /* Allow outside people to call indexOf on slides array */
  430. $scope.indexOfSlide = function(slide) {
  431. return +slide.slide.index;
  432. };
  433. $scope.isActive = function(slide) {
  434. return $scope.active === slide.slide.index;
  435. };
  436. $scope.isPrevDisabled = function() {
  437. return $scope.active === 0 && $scope.noWrap();
  438. };
  439. $scope.isNextDisabled = function() {
  440. return $scope.active === slides.length - 1 && $scope.noWrap();
  441. };
  442. $scope.pause = function() {
  443. if (!$scope.noPause) {
  444. isPlaying = false;
  445. resetTimer();
  446. }
  447. };
  448. $scope.play = function() {
  449. if (!isPlaying) {
  450. isPlaying = true;
  451. restartTimer();
  452. }
  453. };
  454. $scope.$on('$destroy', function() {
  455. destroyed = true;
  456. resetTimer();
  457. });
  458. $scope.$watch('noTransition', function(noTransition) {
  459. $animate.enabled($element, !noTransition);
  460. });
  461. $scope.$watch('interval', restartTimer);
  462. $scope.$watchCollection('slides', resetTransition);
  463. $scope.$watch('active', function(index) {
  464. if (angular.isNumber(index) && currentIndex !== index) {
  465. for (var i = 0; i < slides.length; i++) {
  466. if (slides[i].slide.index === index) {
  467. index = i;
  468. break;
  469. }
  470. }
  471. var slide = slides[index];
  472. if (slide) {
  473. setActive(index);
  474. self.select(slides[index]);
  475. currentIndex = index;
  476. }
  477. }
  478. });
  479. function clearBufferedTransitions() {
  480. while (bufferedTransitions.length) {
  481. bufferedTransitions.shift();
  482. }
  483. }
  484. function getSlideByIndex(index) {
  485. for (var i = 0, l = slides.length; i < l; ++i) {
  486. if (slides[i].index === index) {
  487. return slides[i];
  488. }
  489. }
  490. }
  491. function setActive(index) {
  492. for (var i = 0; i < slides.length; i++) {
  493. slides[i].slide.active = i === index;
  494. }
  495. }
  496. function goNext(slide, index, direction) {
  497. if (destroyed) {
  498. return;
  499. }
  500. angular.extend(slide, {direction: direction});
  501. angular.extend(slides[currentIndex].slide || {}, {direction: direction});
  502. if ($animate.enabled($element) && !$scope.$currentTransition &&
  503. slides[index].element && self.slides.length > 1) {
  504. slides[index].element.data(SLIDE_DIRECTION, slide.direction);
  505. var currentIdx = self.getCurrentIndex();
  506. if (angular.isNumber(currentIdx) && slides[currentIdx].element) {
  507. slides[currentIdx].element.data(SLIDE_DIRECTION, slide.direction);
  508. }
  509. $scope.$currentTransition = true;
  510. $animate.on('addClass', slides[index].element, function(element, phase) {
  511. if (phase === 'close') {
  512. $scope.$currentTransition = null;
  513. $animate.off('addClass', element);
  514. if (bufferedTransitions.length) {
  515. var nextSlide = bufferedTransitions.pop().slide;
  516. var nextIndex = nextSlide.index;
  517. var nextDirection = nextIndex > self.getCurrentIndex() ? 'next' : 'prev';
  518. clearBufferedTransitions();
  519. goNext(nextSlide, nextIndex, nextDirection);
  520. }
  521. }
  522. });
  523. }
  524. $scope.active = slide.index;
  525. currentIndex = slide.index;
  526. setActive(index);
  527. //every time you change slides, reset the timer
  528. restartTimer();
  529. }
  530. function findSlideIndex(slide) {
  531. for (var i = 0; i < slides.length; i++) {
  532. if (slides[i].slide === slide) {
  533. return i;
  534. }
  535. }
  536. }
  537. function resetTimer() {
  538. if (currentInterval) {
  539. $interval.cancel(currentInterval);
  540. currentInterval = null;
  541. }
  542. }
  543. function resetTransition(slides) {
  544. if (!slides.length) {
  545. $scope.$currentTransition = null;
  546. clearBufferedTransitions();
  547. }
  548. }
  549. function restartTimer() {
  550. resetTimer();
  551. var interval = +$scope.interval;
  552. if (!isNaN(interval) && interval > 0) {
  553. currentInterval = $interval(timerFn, interval);
  554. }
  555. }
  556. function timerFn() {
  557. var interval = +$scope.interval;
  558. if (isPlaying && !isNaN(interval) && interval > 0 && slides.length) {
  559. $scope.next();
  560. } else {
  561. $scope.pause();
  562. }
  563. }
  564. }])
  565. .directive('uibCarousel', function() {
  566. return {
  567. transclude: true,
  568. replace: true,
  569. controller: 'UibCarouselController',
  570. controllerAs: 'carousel',
  571. templateUrl: function(element, attrs) {
  572. return attrs.templateUrl || 'uib/template/carousel/carousel.html';
  573. },
  574. scope: {
  575. active: '=',
  576. interval: '=',
  577. noTransition: '=',
  578. noPause: '=',
  579. noWrap: '&'
  580. }
  581. };
  582. })
  583. .directive('uibSlide', function() {
  584. return {
  585. require: '^uibCarousel',
  586. transclude: true,
  587. replace: true,
  588. templateUrl: function(element, attrs) {
  589. return attrs.templateUrl || 'uib/template/carousel/slide.html';
  590. },
  591. scope: {
  592. actual: '=?',
  593. index: '=?'
  594. },
  595. link: function (scope, element, attrs, carouselCtrl) {
  596. carouselCtrl.addSlide(scope, element);
  597. //when the scope is destroyed then remove the slide from the current slides array
  598. scope.$on('$destroy', function() {
  599. carouselCtrl.removeSlide(scope);
  600. });
  601. }
  602. };
  603. })
  604. .animation('.item', ['$animateCss',
  605. function($animateCss) {
  606. var SLIDE_DIRECTION = 'uib-slideDirection';
  607. function removeClass(element, className, callback) {
  608. element.removeClass(className);
  609. if (callback) {
  610. callback();
  611. }
  612. }
  613. return {
  614. beforeAddClass: function(element, className, done) {
  615. if (className === 'active') {
  616. var stopped = false;
  617. var direction = element.data(SLIDE_DIRECTION);
  618. var directionClass = direction === 'next' ? 'left' : 'right';
  619. var removeClassFn = removeClass.bind(this, element,
  620. directionClass + ' ' + direction, done);
  621. element.addClass(direction);
  622. $animateCss(element, {addClass: directionClass})
  623. .start()
  624. .done(removeClassFn);
  625. return function() {
  626. stopped = true;
  627. };
  628. }
  629. done();
  630. },
  631. beforeRemoveClass: function (element, className, done) {
  632. if (className === 'active') {
  633. var stopped = false;
  634. var direction = element.data(SLIDE_DIRECTION);
  635. var directionClass = direction === 'next' ? 'left' : 'right';
  636. var removeClassFn = removeClass.bind(this, element, directionClass, done);
  637. $animateCss(element, {addClass: directionClass})
  638. .start()
  639. .done(removeClassFn);
  640. return function() {
  641. stopped = true;
  642. };
  643. }
  644. done();
  645. }
  646. };
  647. }]);
  648. angular.module('ui.bootstrap.dateparser', [])
  649. .service('uibDateParser', ['$log', '$locale', 'dateFilter', 'orderByFilter', function($log, $locale, dateFilter, orderByFilter) {
  650. // Pulled from https://github.com/mbostock/d3/blob/master/src/format/requote.js
  651. var SPECIAL_CHARACTERS_REGEXP = /[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g;
  652. var localeId;
  653. var formatCodeToRegex;
  654. this.init = function() {
  655. localeId = $locale.id;
  656. this.parsers = {};
  657. this.formatters = {};
  658. formatCodeToRegex = [
  659. {
  660. key: 'yyyy',
  661. regex: '\\d{4}',
  662. apply: function(value) { this.year = +value; },
  663. formatter: function(date) {
  664. var _date = new Date();
  665. _date.setFullYear(Math.abs(date.getFullYear()));
  666. return dateFilter(_date, 'yyyy');
  667. }
  668. },
  669. {
  670. key: 'yy',
  671. regex: '\\d{2}',
  672. apply: function(value) { value = +value; this.year = value < 69 ? value + 2000 : value + 1900; },
  673. formatter: function(date) {
  674. var _date = new Date();
  675. _date.setFullYear(Math.abs(date.getFullYear()));
  676. return dateFilter(_date, 'yy');
  677. }
  678. },
  679. {
  680. key: 'y',
  681. regex: '\\d{1,4}',
  682. apply: function(value) { this.year = +value; },
  683. formatter: function(date) {
  684. var _date = new Date();
  685. _date.setFullYear(Math.abs(date.getFullYear()));
  686. return dateFilter(_date, 'y');
  687. }
  688. },
  689. {
  690. key: 'M!',
  691. regex: '0?[1-9]|1[0-2]',
  692. apply: function(value) { this.month = value - 1; },
  693. formatter: function(date) {
  694. var value = date.getMonth();
  695. if (/^[0-9]$/.test(value)) {
  696. return dateFilter(date, 'MM');
  697. }
  698. return dateFilter(date, 'M');
  699. }
  700. },
  701. {
  702. key: 'MMMM',
  703. regex: $locale.DATETIME_FORMATS.MONTH.join('|'),
  704. apply: function(value) { this.month = $locale.DATETIME_FORMATS.MONTH.indexOf(value); },
  705. formatter: function(date) { return dateFilter(date, 'MMMM'); }
  706. },
  707. {
  708. key: 'MMM',
  709. regex: $locale.DATETIME_FORMATS.SHORTMONTH.join('|'),
  710. apply: function(value) { this.month = $locale.DATETIME_FORMATS.SHORTMONTH.indexOf(value); },
  711. formatter: function(date) { return dateFilter(date, 'MMM'); }
  712. },
  713. {
  714. key: 'MM',
  715. regex: '0[1-9]|1[0-2]',
  716. apply: function(value) { this.month = value - 1; },
  717. formatter: function(date) { return dateFilter(date, 'MM'); }
  718. },
  719. {
  720. key: 'M',
  721. regex: '[1-9]|1[0-2]',
  722. apply: function(value) { this.month = value - 1; },
  723. formatter: function(date) { return dateFilter(date, 'M'); }
  724. },
  725. {
  726. key: 'd!',
  727. regex: '[0-2]?[0-9]{1}|3[0-1]{1}',
  728. apply: function(value) { this.date = +value; },
  729. formatter: function(date) {
  730. var value = date.getDate();
  731. if (/^[1-9]$/.test(value)) {
  732. return dateFilter(date, 'dd');
  733. }
  734. return dateFilter(date, 'd');
  735. }
  736. },
  737. {
  738. key: 'dd',
  739. regex: '[0-2][0-9]{1}|3[0-1]{1}',
  740. apply: function(value) { this.date = +value; },
  741. formatter: function(date) { return dateFilter(date, 'dd'); }
  742. },
  743. {
  744. key: 'd',
  745. regex: '[1-2]?[0-9]{1}|3[0-1]{1}',
  746. apply: function(value) { this.date = +value; },
  747. formatter: function(date) { return dateFilter(date, 'd'); }
  748. },
  749. {
  750. key: 'EEEE',
  751. regex: $locale.DATETIME_FORMATS.DAY.join('|'),
  752. formatter: function(date) { return dateFilter(date, 'EEEE'); }
  753. },
  754. {
  755. key: 'EEE',
  756. regex: $locale.DATETIME_FORMATS.SHORTDAY.join('|'),
  757. formatter: function(date) { return dateFilter(date, 'EEE'); }
  758. },
  759. {
  760. key: 'HH',
  761. regex: '(?:0|1)[0-9]|2[0-3]',
  762. apply: function(value) { this.hours = +value; },
  763. formatter: function(date) { return dateFilter(date, 'HH'); }
  764. },
  765. {
  766. key: 'hh',
  767. regex: '0[0-9]|1[0-2]',
  768. apply: function(value) { this.hours = +value; },
  769. formatter: function(date) { return dateFilter(date, 'hh'); }
  770. },
  771. {
  772. key: 'H',
  773. regex: '1?[0-9]|2[0-3]',
  774. apply: function(value) { this.hours = +value; },
  775. formatter: function(date) { return dateFilter(date, 'H'); }
  776. },
  777. {
  778. key: 'h',
  779. regex: '[0-9]|1[0-2]',
  780. apply: function(value) { this.hours = +value; },
  781. formatter: function(date) { return dateFilter(date, 'h'); }
  782. },
  783. {
  784. key: 'mm',
  785. regex: '[0-5][0-9]',
  786. apply: function(value) { this.minutes = +value; },
  787. formatter: function(date) { return dateFilter(date, 'mm'); }
  788. },
  789. {
  790. key: 'm',
  791. regex: '[0-9]|[1-5][0-9]',
  792. apply: function(value) { this.minutes = +value; },
  793. formatter: function(date) { return dateFilter(date, 'm'); }
  794. },
  795. {
  796. key: 'sss',
  797. regex: '[0-9][0-9][0-9]',
  798. apply: function(value) { this.milliseconds = +value; },
  799. formatter: function(date) { return dateFilter(date, 'sss'); }
  800. },
  801. {
  802. key: 'ss',
  803. regex: '[0-5][0-9]',
  804. apply: function(value) { this.seconds = +value; },
  805. formatter: function(date) { return dateFilter(date, 'ss'); }
  806. },
  807. {
  808. key: 's',
  809. regex: '[0-9]|[1-5][0-9]',
  810. apply: function(value) { this.seconds = +value; },
  811. formatter: function(date) { return dateFilter(date, 's'); }
  812. },
  813. {
  814. key: 'a',
  815. regex: $locale.DATETIME_FORMATS.AMPMS.join('|'),
  816. apply: function(value) {
  817. if (this.hours === 12) {
  818. this.hours = 0;
  819. }
  820. if (value === 'PM') {
  821. this.hours += 12;
  822. }
  823. },
  824. formatter: function(date) { return dateFilter(date, 'a'); }
  825. },
  826. {
  827. key: 'Z',
  828. regex: '[+-]\\d{4}',
  829. apply: function(value) {
  830. var matches = value.match(/([+-])(\d{2})(\d{2})/),
  831. sign = matches[1],
  832. hours = matches[2],
  833. minutes = matches[3];
  834. this.hours += toInt(sign + hours);
  835. this.minutes += toInt(sign + minutes);
  836. },
  837. formatter: function(date) {
  838. return dateFilter(date, 'Z');
  839. }
  840. },
  841. {
  842. key: 'ww',
  843. regex: '[0-4][0-9]|5[0-3]',
  844. formatter: function(date) { return dateFilter(date, 'ww'); }
  845. },
  846. {
  847. key: 'w',
  848. regex: '[0-9]|[1-4][0-9]|5[0-3]',
  849. formatter: function(date) { return dateFilter(date, 'w'); }
  850. },
  851. {
  852. key: 'GGGG',
  853. regex: $locale.DATETIME_FORMATS.ERANAMES.join('|').replace(/\s/g, '\\s'),
  854. formatter: function(date) { return dateFilter(date, 'GGGG'); }
  855. },
  856. {
  857. key: 'GGG',
  858. regex: $locale.DATETIME_FORMATS.ERAS.join('|'),
  859. formatter: function(date) { return dateFilter(date, 'GGG'); }
  860. },
  861. {
  862. key: 'GG',
  863. regex: $locale.DATETIME_FORMATS.ERAS.join('|'),
  864. formatter: function(date) { return dateFilter(date, 'GG'); }
  865. },
  866. {
  867. key: 'G',
  868. regex: $locale.DATETIME_FORMATS.ERAS.join('|'),
  869. formatter: function(date) { return dateFilter(date, 'G'); }
  870. }
  871. ];
  872. };
  873. this.init();
  874. function createParser(format, func) {
  875. var map = [], regex = format.split('');
  876. // check for literal values
  877. var quoteIndex = format.indexOf('\'');
  878. if (quoteIndex > -1) {
  879. var inLiteral = false;
  880. format = format.split('');
  881. for (var i = quoteIndex; i < format.length; i++) {
  882. if (inLiteral) {
  883. if (format[i] === '\'') {
  884. if (i + 1 < format.length && format[i+1] === '\'') { // escaped single quote
  885. format[i+1] = '$';
  886. regex[i+1] = '';
  887. } else { // end of literal
  888. regex[i] = '';
  889. inLiteral = false;
  890. }
  891. }
  892. format[i] = '$';
  893. } else {
  894. if (format[i] === '\'') { // start of literal
  895. format[i] = '$';
  896. regex[i] = '';
  897. inLiteral = true;
  898. }
  899. }
  900. }
  901. format = format.join('');
  902. }
  903. angular.forEach(formatCodeToRegex, function(data) {
  904. var index = format.indexOf(data.key);
  905. if (index > -1) {
  906. format = format.split('');
  907. regex[index] = '(' + data.regex + ')';
  908. format[index] = '$'; // Custom symbol to define consumed part of format
  909. for (var i = index + 1, n = index + data.key.length; i < n; i++) {
  910. regex[i] = '';
  911. format[i] = '$';
  912. }
  913. format = format.join('');
  914. map.push({
  915. index: index,
  916. key: data.key,
  917. apply: data[func],
  918. matcher: data.regex
  919. });
  920. }
  921. });
  922. return {
  923. regex: new RegExp('^' + regex.join('') + '$'),
  924. map: orderByFilter(map, 'index')
  925. };
  926. }
  927. this.filter = function(date, format) {
  928. if (!angular.isDate(date) || isNaN(date) || !format) {
  929. return '';
  930. }
  931. format = $locale.DATETIME_FORMATS[format] || format;
  932. if ($locale.id !== localeId) {
  933. this.init();
  934. }
  935. if (!this.formatters[format]) {
  936. this.formatters[format] = createParser(format, 'formatter');
  937. }
  938. var parser = this.formatters[format],
  939. map = parser.map;
  940. var _format = format;
  941. return map.reduce(function(str, mapper, i) {
  942. var match = _format.match(new RegExp('(.*)' + mapper.key));
  943. if (match && angular.isString(match[1])) {
  944. str += match[1];
  945. _format = _format.replace(match[1] + mapper.key, '');
  946. }
  947. var endStr = i === map.length - 1 ? _format : '';
  948. if (mapper.apply) {
  949. return str + mapper.apply.call(null, date) + endStr;
  950. }
  951. return str + endStr;
  952. }, '');
  953. };
  954. this.parse = function(input, format, baseDate) {
  955. if (!angular.isString(input) || !format) {
  956. return input;
  957. }
  958. format = $locale.DATETIME_FORMATS[format] || format;
  959. format = format.replace(SPECIAL_CHARACTERS_REGEXP, '\\$&');
  960. if ($locale.id !== localeId) {
  961. this.init();
  962. }
  963. if (!this.parsers[format]) {
  964. this.parsers[format] = createParser(format, 'apply');
  965. }
  966. var parser = this.parsers[format],
  967. regex = parser.regex,
  968. map = parser.map,
  969. results = input.match(regex),
  970. tzOffset = false;
  971. if (results && results.length) {
  972. var fields, dt;
  973. if (angular.isDate(baseDate) && !isNaN(baseDate.getTime())) {
  974. fields = {
  975. year: baseDate.getFullYear(),
  976. month: baseDate.getMonth(),
  977. date: baseDate.getDate(),
  978. hours: baseDate.getHours(),
  979. minutes: baseDate.getMinutes(),
  980. seconds: baseDate.getSeconds(),
  981. milliseconds: baseDate.getMilliseconds()
  982. };
  983. } else {
  984. if (baseDate) {
  985. $log.warn('dateparser:', 'baseDate is not a valid date');
  986. }
  987. fields = { year: 1900, month: 0, date: 1, hours: 0, minutes: 0, seconds: 0, milliseconds: 0 };
  988. }
  989. for (var i = 1, n = results.length; i < n; i++) {
  990. var mapper = map[i - 1];
  991. if (mapper.matcher === 'Z') {
  992. tzOffset = true;
  993. }
  994. if (mapper.apply) {
  995. mapper.apply.call(fields, results[i]);
  996. }
  997. }
  998. var datesetter = tzOffset ? Date.prototype.setUTCFullYear :
  999. Date.prototype.setFullYear;
  1000. var timesetter = tzOffset ? Date.prototype.setUTCHours :
  1001. Date.prototype.setHours;
  1002. if (isValid(fields.year, fields.month, fields.date)) {
  1003. if (angular.isDate(baseDate) && !isNaN(baseDate.getTime()) && !tzOffset) {
  1004. dt = new Date(baseDate);
  1005. datesetter.call(dt, fields.year, fields.month, fields.date);
  1006. timesetter.call(dt, fields.hours, fields.minutes,
  1007. fields.seconds, fields.milliseconds);
  1008. } else {
  1009. dt = new Date(0);
  1010. datesetter.call(dt, fields.year, fields.month, fields.date);
  1011. timesetter.call(dt, fields.hours || 0, fields.minutes || 0,
  1012. fields.seconds || 0, fields.milliseconds || 0);
  1013. }
  1014. }
  1015. return dt;
  1016. }
  1017. };
  1018. // Check if date is valid for specific month (and year for February).
  1019. // Month: 0 = Jan, 1 = Feb, etc
  1020. function isValid(year, month, date) {
  1021. if (date < 1) {
  1022. return false;
  1023. }
  1024. if (month === 1 && date > 28) {
  1025. return date === 29 && (year % 4 === 0 && year % 100 !== 0 || year % 400 === 0);
  1026. }
  1027. if (month === 3 || month === 5 || month === 8 || month === 10) {
  1028. return date < 31;
  1029. }
  1030. return true;
  1031. }
  1032. function toInt(str) {
  1033. return parseInt(str, 10);
  1034. }
  1035. this.toTimezone = toTimezone;
  1036. this.fromTimezone = fromTimezone;
  1037. this.timezoneToOffset = timezoneToOffset;
  1038. this.addDateMinutes = addDateMinutes;
  1039. this.convertTimezoneToLocal = convertTimezoneToLocal;
  1040. function toTimezone(date, timezone) {
  1041. return date && timezone ? convertTimezoneToLocal(date, timezone) : date;
  1042. }
  1043. function fromTimezone(date, timezone) {
  1044. return date && timezone ? convertTimezoneToLocal(date, timezone, true) : date;
  1045. }
  1046. //https://github.com/angular/angular.js/blob/622c42169699ec07fc6daaa19fe6d224e5d2f70e/src/Angular.js#L1207
  1047. function timezoneToOffset(timezone, fallback) {
  1048. timezone = timezone.replace(/:/g, '');
  1049. var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000;
  1050. return isNaN(requestedTimezoneOffset) ? fallback : requestedTimezoneOffset;
  1051. }
  1052. function addDateMinutes(date, minutes) {
  1053. date = new Date(date.getTime());
  1054. date.setMinutes(date.getMinutes() + minutes);
  1055. return date;
  1056. }
  1057. function convertTimezoneToLocal(date, timezone, reverse) {
  1058. reverse = reverse ? -1 : 1;
  1059. var dateTimezoneOffset = date.getTimezoneOffset();
  1060. var timezoneOffset = timezoneToOffset(timezone, dateTimezoneOffset);
  1061. return addDateMinutes(date, reverse * (timezoneOffset - dateTimezoneOffset));
  1062. }
  1063. }]);
  1064. // Avoiding use of ng-class as it creates a lot of watchers when a class is to be applied to
  1065. // at most one element.
  1066. angular.module('ui.bootstrap.isClass', [])
  1067. .directive('uibIsClass', [
  1068. '$animate',
  1069. function ($animate) {
  1070. // 11111111 22222222
  1071. var ON_REGEXP = /^\s*([\s\S]+?)\s+on\s+([\s\S]+?)\s*$/;
  1072. // 11111111 22222222
  1073. var IS_REGEXP = /^\s*([\s\S]+?)\s+for\s+([\s\S]+?)\s*$/;
  1074. var dataPerTracked = {};
  1075. return {
  1076. restrict: 'A',
  1077. compile: function(tElement, tAttrs) {
  1078. var linkedScopes = [];
  1079. var instances = [];
  1080. var expToData = {};
  1081. var lastActivated = null;
  1082. var onExpMatches = tAttrs.uibIsClass.match(ON_REGEXP);
  1083. var onExp = onExpMatches[2];
  1084. var expsStr = onExpMatches[1];
  1085. var exps = expsStr.split(',');
  1086. return linkFn;
  1087. function linkFn(scope, element, attrs) {
  1088. linkedScopes.push(scope);
  1089. instances.push({
  1090. scope: scope,
  1091. element: element
  1092. });
  1093. exps.forEach(function(exp, k) {
  1094. addForExp(exp, scope);
  1095. });
  1096. scope.$on('$destroy', removeScope);
  1097. }
  1098. function addForExp(exp, scope) {
  1099. var matches = exp.match(IS_REGEXP);
  1100. var clazz = scope.$eval(matches[1]);
  1101. var compareWithExp = matches[2];
  1102. var data = expToData[exp];
  1103. if (!data) {
  1104. var watchFn = function(compareWithVal) {
  1105. var newActivated = null;
  1106. instances.some(function(instance) {
  1107. var thisVal = instance.scope.$eval(onExp);
  1108. if (thisVal === compareWithVal) {
  1109. newActivated = instance;
  1110. return true;
  1111. }
  1112. });
  1113. if (data.lastActivated !== newActivated) {
  1114. if (data.lastActivated) {
  1115. $animate.removeClass(data.lastActivated.element, clazz);
  1116. }
  1117. if (newActivated) {
  1118. $animate.addClass(newActivated.element, clazz);
  1119. }
  1120. data.lastActivated = newActivated;
  1121. }
  1122. };
  1123. expToData[exp] = data = {
  1124. lastActivated: null,
  1125. scope: scope,
  1126. watchFn: watchFn,
  1127. compareWithExp: compareWithExp,
  1128. watcher: scope.$watch(compareWithExp, watchFn)
  1129. };
  1130. }
  1131. data.watchFn(scope.$eval(compareWithExp));
  1132. }
  1133. function removeScope(e) {
  1134. var removedScope = e.targetScope;
  1135. var index = linkedScopes.indexOf(removedScope);
  1136. linkedScopes.splice(index, 1);
  1137. instances.splice(index, 1);
  1138. if (linkedScopes.length) {
  1139. var newWatchScope = linkedScopes[0];
  1140. angular.forEach(expToData, function(data) {
  1141. if (data.scope === removedScope) {
  1142. data.watcher = newWatchScope.$watch(data.compareWithExp, data.watchFn);
  1143. data.scope = newWatchScope;
  1144. }
  1145. });
  1146. } else {
  1147. expToData = {};
  1148. }
  1149. }
  1150. }
  1151. };
  1152. }]);
  1153. angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootstrap.isClass'])
  1154. .value('$datepickerSuppressError', false)
  1155. .value('$datepickerLiteralWarning', true)
  1156. .constant('uibDatepickerConfig', {
  1157. datepickerMode: 'day',
  1158. formatDay: 'dd',
  1159. formatMonth: 'MMMM',
  1160. formatYear: 'yyyy',
  1161. formatDayHeader: 'EEE',
  1162. formatDayTitle: 'MMMM yyyy',
  1163. formatMonthTitle: 'yyyy',
  1164. maxDate: null,
  1165. maxMode: 'year',
  1166. minDate: null,
  1167. minMode: 'day',
  1168. ngModelOptions: {},
  1169. shortcutPropagation: false,
  1170. showWeeks: true,
  1171. yearColumns: 5,
  1172. yearRows: 4
  1173. })
  1174. .controller('UibDatepickerController', ['$scope', '$attrs', '$parse', '$interpolate', '$locale', '$log', 'dateFilter', 'uibDatepickerConfig', '$datepickerLiteralWarning', '$datepickerSuppressError', 'uibDateParser',
  1175. function($scope, $attrs, $parse, $interpolate, $locale, $log, dateFilter, datepickerConfig, $datepickerLiteralWarning, $datepickerSuppressError, dateParser) {
  1176. var self = this,
  1177. ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl;
  1178. ngModelOptions = {},
  1179. watchListeners = [],
  1180. optionsUsed = !!$attrs.datepickerOptions;
  1181. if (!$scope.datepickerOptions) {
  1182. $scope.datepickerOptions = {};
  1183. }
  1184. // Modes chain
  1185. this.modes = ['day', 'month', 'year'];
  1186. [
  1187. 'customClass',
  1188. 'dateDisabled',
  1189. 'datepickerMode',
  1190. 'formatDay',
  1191. 'formatDayHeader',
  1192. 'formatDayTitle',
  1193. 'formatMonth',
  1194. 'formatMonthTitle',
  1195. 'formatYear',
  1196. 'maxDate',
  1197. 'maxMode',
  1198. 'minDate',
  1199. 'minMode',
  1200. 'showWeeks',
  1201. 'shortcutPropagation',
  1202. 'startingDay',
  1203. 'yearColumns',
  1204. 'yearRows'
  1205. ].forEach(function(key) {
  1206. switch (key) {
  1207. case 'customClass':
  1208. case 'dateDisabled':
  1209. $scope[key] = $scope.datepickerOptions[key] || angular.noop;
  1210. break;
  1211. case 'datepickerMode':
  1212. $scope.datepickerMode = angular.isDefined($scope.datepickerOptions.datepickerMode) ?
  1213. $scope.datepickerOptions.datepickerMode : datepickerConfig.datepickerMode;
  1214. break;
  1215. case 'formatDay':
  1216. case 'formatDayHeader':
  1217. case 'formatDayTitle':
  1218. case 'formatMonth':
  1219. case 'formatMonthTitle':
  1220. case 'formatYear':
  1221. self[key] = angular.isDefined($scope.datepickerOptions[key]) ?
  1222. $interpolate($scope.datepickerOptions[key])($scope.$parent) :
  1223. datepickerConfig[key];
  1224. break;
  1225. case 'showWeeks':
  1226. case 'shortcutPropagation':
  1227. case 'yearColumns':
  1228. case 'yearRows':
  1229. self[key] = angular.isDefined($scope.datepickerOptions[key]) ?
  1230. $scope.datepickerOptions[key] : datepickerConfig[key];
  1231. break;
  1232. case 'startingDay':
  1233. if (angular.isDefined($scope.datepickerOptions.startingDay)) {
  1234. self.startingDay = $scope.datepickerOptions.startingDay;
  1235. } else if (angular.isNumber(datepickerConfig.startingDay)) {
  1236. self.startingDay = datepickerConfig.startingDay;
  1237. } else {
  1238. self.startingDay = ($locale.DATETIME_FORMATS.FIRSTDAYOFWEEK + 8) % 7;
  1239. }
  1240. break;
  1241. case 'maxDate':
  1242. case 'minDate':
  1243. $scope.$watch('datepickerOptions.' + key, function(value) {
  1244. if (value) {
  1245. if (angular.isDate(value)) {
  1246. self[key] = dateParser.fromTimezone(new Date(value), ngModelOptions.timezone);
  1247. } else {
  1248. if ($datepickerLiteralWarning) {
  1249. $log.warn('Literal date support has been deprecated, please switch to date object usage');
  1250. }
  1251. self[key] = new Date(dateFilter(value, 'medium'));
  1252. }
  1253. } else {
  1254. self[key] = datepickerConfig[key] ?
  1255. dateParser.fromTimezone(new Date(datepickerConfig[key]), ngModelOptions.timezone) :
  1256. null;
  1257. }
  1258. self.refreshView();
  1259. });
  1260. break;
  1261. case 'maxMode':
  1262. case 'minMode':
  1263. if ($scope.datepickerOptions[key]) {
  1264. $scope.$watch(function() { return $scope.datepickerOptions[key]; }, function(value) {
  1265. self[key] = $scope[key] = angular.isDefined(value) ? value : datepickerOptions[key];
  1266. if (key === 'minMode' && self.modes.indexOf($scope.datepickerOptions.datepickerMode) < self.modes.indexOf(self[key]) ||
  1267. key === 'maxMode' && self.modes.indexOf($scope.datepickerOptions.datepickerMode) > self.modes.indexOf(self[key])) {
  1268. $scope.datepickerMode = self[key];
  1269. $scope.datepickerOptions.datepickerMode = self[key];
  1270. }
  1271. });
  1272. } else {
  1273. self[key] = $scope[key] = datepickerConfig[key] || null;
  1274. }
  1275. break;
  1276. }
  1277. });
  1278. $scope.uniqueId = 'datepicker-' + $scope.$id + '-' + Math.floor(Math.random() * 10000);
  1279. $scope.disabled = angular.isDefined($attrs.disabled) || false;
  1280. if (angular.isDefined($attrs.ngDisabled)) {
  1281. watchListeners.push($scope.$parent.$watch($attrs.ngDisabled, function(disabled) {
  1282. $scope.disabled = disabled;
  1283. self.refreshView();
  1284. }));
  1285. }
  1286. $scope.isActive = function(dateObject) {
  1287. if (self.compare(dateObject.date, self.activeDate) === 0) {
  1288. $scope.activeDateId = dateObject.uid;
  1289. return true;
  1290. }
  1291. return false;
  1292. };
  1293. this.init = function(ngModelCtrl_) {
  1294. ngModelCtrl = ngModelCtrl_;
  1295. ngModelOptions = ngModelCtrl_.$options || datepickerConfig.ngModelOptions;
  1296. if ($scope.datepickerOptions.initDate) {
  1297. self.activeDate = dateParser.fromTimezone($scope.datepickerOptions.initDate, ngModelOptions.timezone) || new Date();
  1298. $scope.$watch('datepickerOptions.initDate', function(initDate) {
  1299. if (initDate && (ngModelCtrl.$isEmpty(ngModelCtrl.$modelValue) || ngModelCtrl.$invalid)) {
  1300. self.activeDate = dateParser.fromTimezone(initDate, ngModelOptions.timezone);
  1301. self.refreshView();
  1302. }
  1303. });
  1304. } else {
  1305. self.activeDate = new Date();
  1306. }
  1307. var date = ngModelCtrl.$modelValue ? new Date(ngModelCtrl.$modelValue) : new Date();
  1308. this.activeDate = !isNaN(date) ?
  1309. dateParser.fromTimezone(date, ngModelOptions.timezone) :
  1310. dateParser.fromTimezone(new Date(), ngModelOptions.timezone);
  1311. ngModelCtrl.$render = function() {
  1312. self.render();
  1313. };
  1314. };
  1315. this.render = function() {
  1316. if (ngModelCtrl.$viewValue) {
  1317. var date = new Date(ngModelCtrl.$viewValue),
  1318. isValid = !isNaN(date);
  1319. if (isValid) {
  1320. this.activeDate = dateParser.fromTimezone(date, ngModelOptions.timezone);
  1321. } else if (!$datepickerSuppressError) {
  1322. $log.error('Datepicker directive: "ng-model" value must be a Date object');
  1323. }
  1324. }
  1325. this.refreshView();
  1326. };
  1327. this.refreshView = function() {
  1328. if (this.element) {
  1329. $scope.selectedDt = null;
  1330. this._refreshView();
  1331. if ($scope.activeDt) {
  1332. $scope.activeDateId = $scope.activeDt.uid;
  1333. }
  1334. var date = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null;
  1335. date = dateParser.fromTimezone(date, ngModelOptions.timezone);
  1336. ngModelCtrl.$setValidity('dateDisabled', !date ||
  1337. this.element && !this.isDisabled(date));
  1338. }
  1339. };
  1340. this.createDateObject = function(date, format) {
  1341. var model = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null;
  1342. model = dateParser.fromTimezone(model, ngModelOptions.timezone);
  1343. var today = new Date();
  1344. today = dateParser.fromTimezone(today, ngModelOptions.timezone);
  1345. var time = this.compare(date, today);
  1346. var dt = {
  1347. date: date,
  1348. label: dateParser.filter(date, format),
  1349. selected: model && this.compare(date, model) === 0,
  1350. disabled: this.isDisabled(date),
  1351. past: time < 0,
  1352. current: time === 0,
  1353. future: time > 0,
  1354. customClass: this.customClass(date) || null
  1355. };
  1356. if (model && this.compare(date, model) === 0) {
  1357. $scope.selectedDt = dt;
  1358. }
  1359. if (self.activeDate && this.compare(dt.date, self.activeDate) === 0) {
  1360. $scope.activeDt = dt;
  1361. }
  1362. return dt;
  1363. };
  1364. this.isDisabled = function(date) {
  1365. return $scope.disabled ||
  1366. this.minDate && this.compare(date, this.minDate) < 0 ||
  1367. this.maxDate && this.compare(date, this.maxDate) > 0 ||
  1368. $scope.dateDisabled && $scope.dateDisabled({date: date, mode: $scope.datepickerMode});
  1369. };
  1370. this.customClass = function(date) {
  1371. return $scope.customClass({date: date, mode: $scope.datepickerMode});
  1372. };
  1373. // Split array into smaller arrays
  1374. this.split = function(arr, size) {
  1375. var arrays = [];
  1376. while (arr.length > 0) {
  1377. arrays.push(arr.splice(0, size));
  1378. }
  1379. return arrays;
  1380. };
  1381. $scope.select = function(date) {
  1382. if ($scope.datepickerMode === self.minMode) {
  1383. var dt = ngModelCtrl.$viewValue ? dateParser.fromTimezone(new Date(ngModelCtrl.$viewValue), ngModelOptions.timezone) : new Date(0, 0, 0, 0, 0, 0, 0);
  1384. dt.setFullYear(date.getFullYear(), date.getMonth(), date.getDate());
  1385. dt = dateParser.toTimezone(dt, ngModelOptions.timezone);
  1386. ngModelCtrl.$setViewValue(dt);
  1387. ngModelCtrl.$render();
  1388. } else {
  1389. self.activeDate = date;
  1390. setMode(self.modes[self.modes.indexOf($scope.datepickerMode) - 1]);
  1391. $scope.$emit('uib:datepicker.mode');
  1392. }
  1393. $scope.$broadcast('uib:datepicker.focus');
  1394. };
  1395. $scope.move = function(direction) {
  1396. var year = self.activeDate.getFullYear() + direction * (self.step.years || 0),
  1397. month = self.activeDate.getMonth() + direction * (self.step.months || 0);
  1398. self.activeDate.setFullYear(year, month, 1);
  1399. self.refreshView();
  1400. };
  1401. $scope.toggleMode = function(direction) {
  1402. direction = direction || 1;
  1403. if ($scope.datepickerMode === self.maxMode && direction === 1 ||
  1404. $scope.datepickerMode === self.minMode && direction === -1) {
  1405. return;
  1406. }
  1407. setMode(self.modes[self.modes.indexOf($scope.datepickerMode) + direction]);
  1408. $scope.$emit('uib:datepicker.mode');
  1409. };
  1410. // Key event mapper
  1411. $scope.keys = { 13: 'enter', 32: 'space', 33: 'pageup', 34: 'pagedown', 35: 'end', 36: 'home', 37: 'left', 38: 'up', 39: 'right', 40: 'down' };
  1412. var focusElement = function() {
  1413. self.element[0].focus();
  1414. };
  1415. // Listen for focus requests from popup directive
  1416. $scope.$on('uib:datepicker.focus', focusElement);
  1417. $scope.keydown = function(evt) {
  1418. var key = $scope.keys[evt.which];
  1419. if (!key || evt.shiftKey || evt.altKey || $scope.disabled) {
  1420. return;
  1421. }
  1422. evt.preventDefault();
  1423. if (!self.shortcutPropagation) {
  1424. evt.stopPropagation();
  1425. }
  1426. if (key === 'enter' || key === 'space') {
  1427. if (self.isDisabled(self.activeDate)) {
  1428. return; // do nothing
  1429. }
  1430. $scope.select(self.activeDate);
  1431. } else if (evt.ctrlKey && (key === 'up' || key === 'down')) {
  1432. $scope.toggleMode(key === 'up' ? 1 : -1);
  1433. } else {
  1434. self.handleKeyDown(key, evt);
  1435. self.refreshView();
  1436. }
  1437. };
  1438. $scope.$on('$destroy', function() {
  1439. //Clear all watch listeners on destroy
  1440. while (watchListeners.length) {
  1441. watchListeners.shift()();
  1442. }
  1443. });
  1444. function setMode(mode) {
  1445. $scope.datepickerMode = mode;
  1446. $scope.datepickerOptions.datepickerMode = mode;
  1447. }
  1448. }])
  1449. .controller('UibDaypickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) {
  1450. var DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
  1451. this.step = { months: 1 };
  1452. this.element = $element;
  1453. function getDaysInMonth(year, month) {
  1454. return month === 1 && year % 4 === 0 &&
  1455. (year % 100 !== 0 || year % 400 === 0) ? 29 : DAYS_IN_MONTH[month];
  1456. }
  1457. this.init = function(ctrl) {
  1458. angular.extend(ctrl, this);
  1459. scope.showWeeks = ctrl.showWeeks;
  1460. ctrl.refreshView();
  1461. };
  1462. this.getDates = function(startDate, n) {
  1463. var dates = new Array(n), current = new Date(startDate), i = 0, date;
  1464. while (i < n) {
  1465. date = new Date(current);
  1466. dates[i++] = date;
  1467. current.setDate(current.getDate() + 1);
  1468. }
  1469. return dates;
  1470. };
  1471. this._refreshView = function() {
  1472. var year = this.activeDate.getFullYear(),
  1473. month = this.activeDate.getMonth(),
  1474. firstDayOfMonth = new Date(this.activeDate);
  1475. firstDayOfMonth.setFullYear(year, month, 1);
  1476. var difference = this.startingDay - firstDayOfMonth.getDay(),
  1477. numDisplayedFromPreviousMonth = difference > 0 ?
  1478. 7 - difference : - difference,
  1479. firstDate = new Date(firstDayOfMonth);
  1480. if (numDisplayedFromPreviousMonth > 0) {
  1481. firstDate.setDate(-numDisplayedFromPreviousMonth + 1);
  1482. }
  1483. // 42 is the number of days on a six-week calendar
  1484. var days = this.getDates(firstDate, 42);
  1485. for (var i = 0; i < 42; i ++) {
  1486. days[i] = angular.extend(this.createDateObject(days[i], this.formatDay), {
  1487. secondary: days[i].getMonth() !== month,
  1488. uid: scope.uniqueId + '-' + i
  1489. });
  1490. }
  1491. scope.labels = new Array(7);
  1492. for (var j = 0; j < 7; j++) {
  1493. scope.labels[j] = {
  1494. abbr: dateFilter(days[j].date, this.formatDayHeader),
  1495. full: dateFilter(days[j].date, 'EEEE')
  1496. };
  1497. }
  1498. scope.title = dateFilter(this.activeDate, this.formatDayTitle);
  1499. scope.rows = this.split(days, 7);
  1500. if (scope.showWeeks) {
  1501. scope.weekNumbers = [];
  1502. var thursdayIndex = (4 + 7 - this.startingDay) % 7,
  1503. numWeeks = scope.rows.length;
  1504. for (var curWeek = 0; curWeek < numWeeks; curWeek++) {
  1505. scope.weekNumbers.push(
  1506. getISO8601WeekNumber(scope.rows[curWeek][thursdayIndex].date));
  1507. }
  1508. }
  1509. };
  1510. this.compare = function(date1, date2) {
  1511. var _date1 = new Date(date1.getFullYear(), date1.getMonth(), date1.getDate());
  1512. var _date2 = new Date(date2.getFullYear(), date2.getMonth(), date2.getDate());
  1513. _date1.setFullYear(date1.getFullYear());
  1514. _date2.setFullYear(date2.getFullYear());
  1515. return _date1 - _date2;
  1516. };
  1517. function getISO8601WeekNumber(date) {
  1518. var checkDate = new Date(date);
  1519. checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7)); // Thursday
  1520. var time = checkDate.getTime();
  1521. checkDate.setMonth(0); // Compare with Jan 1
  1522. checkDate.setDate(1);
  1523. return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1;
  1524. }
  1525. this.handleKeyDown = function(key, evt) {
  1526. var date = this.activeDate.getDate();
  1527. if (key === 'left') {
  1528. date = date - 1;
  1529. } else if (key === 'up') {
  1530. date = date - 7;
  1531. } else if (key === 'right') {
  1532. date = date + 1;
  1533. } else if (key === 'down') {
  1534. date = date + 7;
  1535. } else if (key === 'pageup' || key === 'pagedown') {
  1536. var month = this.activeDate.getMonth() + (key === 'pageup' ? - 1 : 1);
  1537. this.activeDate.setMonth(month, 1);
  1538. date = Math.min(getDaysInMonth(this.activeDate.getFullYear(), this.activeDate.getMonth()), date);
  1539. } else if (key === 'home') {
  1540. date = 1;
  1541. } else if (key === 'end') {
  1542. date = getDaysInMonth(this.activeDate.getFullYear(), this.activeDate.getMonth());
  1543. }
  1544. this.activeDate.setDate(date);
  1545. };
  1546. }])
  1547. .controller('UibMonthpickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) {
  1548. this.step = { years: 1 };
  1549. this.element = $element;
  1550. this.init = function(ctrl) {
  1551. angular.extend(ctrl, this);
  1552. ctrl.refreshView();
  1553. };
  1554. this._refreshView = function() {
  1555. var months = new Array(12),
  1556. year = this.activeDate.getFullYear(),
  1557. date;
  1558. for (var i = 0; i < 12; i++) {
  1559. date = new Date(this.activeDate);
  1560. date.setFullYear(year, i, 1);
  1561. months[i] = angular.extend(this.createDateObject(date, this.formatMonth), {
  1562. uid: scope.uniqueId + '-' + i
  1563. });
  1564. }
  1565. scope.title = dateFilter(this.activeDate, this.formatMonthTitle);
  1566. scope.rows = this.split(months, 3);
  1567. };
  1568. this.compare = function(date1, date2) {
  1569. var _date1 = new Date(date1.getFullYear(), date1.getMonth());
  1570. var _date2 = new Date(date2.getFullYear(), date2.getMonth());
  1571. _date1.setFullYear(date1.getFullYear());
  1572. _date2.setFullYear(date2.getFullYear());
  1573. return _date1 - _date2;
  1574. };
  1575. this.handleKeyDown = function(key, evt) {
  1576. var date = this.activeDate.getMonth();
  1577. if (key === 'left') {
  1578. date = date - 1;
  1579. } else if (key === 'up') {
  1580. date = date - 3;
  1581. } else if (key === 'right') {
  1582. date = date + 1;
  1583. } else if (key === 'down') {
  1584. date = date + 3;
  1585. } else if (key === 'pageup' || key === 'pagedown') {
  1586. var year = this.activeDate.getFullYear() + (key === 'pageup' ? - 1 : 1);
  1587. this.activeDate.setFullYear(year);
  1588. } else if (key === 'home') {
  1589. date = 0;
  1590. } else if (key === 'end') {
  1591. date = 11;
  1592. }
  1593. this.activeDate.setMonth(date);
  1594. };
  1595. }])
  1596. .controller('UibYearpickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) {
  1597. var columns, range;
  1598. this.element = $element;
  1599. function getStartingYear(year) {
  1600. return parseInt((year - 1) / range, 10) * range + 1;
  1601. }
  1602. this.yearpickerInit = function() {
  1603. columns = this.yearColumns;
  1604. range = this.yearRows * columns;
  1605. this.step = { years: range };
  1606. };
  1607. this._refreshView = function() {
  1608. var years = new Array(range), date;
  1609. for (var i = 0, start = getStartingYear(this.activeDate.getFullYear()); i < range; i++) {
  1610. date = new Date(this.activeDate);
  1611. date.setFullYear(start + i, 0, 1);
  1612. years[i] = angular.extend(this.createDateObject(date, this.formatYear), {
  1613. uid: scope.uniqueId + '-' + i
  1614. });
  1615. }
  1616. scope.title = [years[0].label, years[range - 1].label].join(' - ');
  1617. scope.rows = this.split(years, columns);
  1618. scope.columns = columns;
  1619. };
  1620. this.compare = function(date1, date2) {
  1621. return date1.getFullYear() - date2.getFullYear();
  1622. };
  1623. this.handleKeyDown = function(key, evt) {
  1624. var date = this.activeDate.getFullYear();
  1625. if (key === 'left') {
  1626. date = date - 1;
  1627. } else if (key === 'up') {
  1628. date = date - columns;
  1629. } else if (key === 'right') {
  1630. date = date + 1;
  1631. } else if (key === 'down') {
  1632. date = date + columns;
  1633. } else if (key === 'pageup' || key === 'pagedown') {
  1634. date += (key === 'pageup' ? - 1 : 1) * range;
  1635. } else if (key === 'home') {
  1636. date = getStartingYear(this.activeDate.getFullYear());
  1637. } else if (key === 'end') {
  1638. date = getStartingYear(this.activeDate.getFullYear()) + range - 1;
  1639. }
  1640. this.activeDate.setFullYear(date);
  1641. };
  1642. }])
  1643. .directive('uibDatepicker', function() {
  1644. return {
  1645. replace: true,
  1646. templateUrl: function(element, attrs) {
  1647. return attrs.templateUrl || 'uib/template/datepicker/datepicker.html';
  1648. },
  1649. scope: {
  1650. datepickerOptions: '=?'
  1651. },
  1652. require: ['uibDatepicker', '^ngModel'],
  1653. controller: 'UibDatepickerController',
  1654. controllerAs: 'datepicker',
  1655. link: function(scope, element, attrs, ctrls) {
  1656. var datepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1];
  1657. datepickerCtrl.init(ngModelCtrl);
  1658. }
  1659. };
  1660. })
  1661. .directive('uibDaypicker', function() {
  1662. return {
  1663. replace: true,
  1664. templateUrl: function(element, attrs) {
  1665. return attrs.templateUrl || 'uib/template/datepicker/day.html';
  1666. },
  1667. require: ['^uibDatepicker', 'uibDaypicker'],
  1668. controller: 'UibDaypickerController',
  1669. link: function(scope, element, attrs, ctrls) {
  1670. var datepickerCtrl = ctrls[0],
  1671. daypickerCtrl = ctrls[1];
  1672. daypickerCtrl.init(datepickerCtrl);
  1673. }
  1674. };
  1675. })
  1676. .directive('uibMonthpicker', function() {
  1677. return {
  1678. replace: true,
  1679. templateUrl: function(element, attrs) {
  1680. return attrs.templateUrl || 'uib/template/datepicker/month.html';
  1681. },
  1682. require: ['^uibDatepicker', 'uibMonthpicker'],
  1683. controller: 'UibMonthpickerController',
  1684. link: function(scope, element, attrs, ctrls) {
  1685. var datepickerCtrl = ctrls[0],
  1686. monthpickerCtrl = ctrls[1];
  1687. monthpickerCtrl.init(datepickerCtrl);
  1688. }
  1689. };
  1690. })
  1691. .directive('uibYearpicker', function() {
  1692. return {
  1693. replace: true,
  1694. templateUrl: function(element, attrs) {
  1695. return attrs.templateUrl || 'uib/template/datepicker/year.html';
  1696. },
  1697. require: ['^uibDatepicker', 'uibYearpicker'],
  1698. controller: 'UibYearpickerController',
  1699. link: function(scope, element, attrs, ctrls) {
  1700. var ctrl = ctrls[0];
  1701. angular.extend(ctrl, ctrls[1]);
  1702. ctrl.yearpickerInit();
  1703. ctrl.refreshView();
  1704. }
  1705. };
  1706. });
  1707. angular.module('ui.bootstrap.position', [])
  1708. /**
  1709. * A set of utility methods for working with the DOM.
  1710. * It is meant to be used where we need to absolute-position elements in
  1711. * relation to another element (this is the case for tooltips, popovers,
  1712. * typeahead suggestions etc.).
  1713. */
  1714. .factory('$uibPosition', ['$document', '$window', function($document, $window) {
  1715. /**
  1716. * Used by scrollbarWidth() function to cache scrollbar's width.
  1717. * Do not access this variable directly, use scrollbarWidth() instead.
  1718. */
  1719. var SCROLLBAR_WIDTH;
  1720. /**
  1721. * scrollbar on body and html element in IE and Edge overlay
  1722. * content and should be considered 0 width.
  1723. */
  1724. var BODY_SCROLLBAR_WIDTH;
  1725. var OVERFLOW_REGEX = {
  1726. normal: /(auto|scroll)/,
  1727. hidden: /(auto|scroll|hidden)/
  1728. };
  1729. var PLACEMENT_REGEX = {
  1730. auto: /\s?auto?\s?/i,
  1731. primary: /^(top|bottom|left|right)$/,
  1732. secondary: /^(top|bottom|left|right|center)$/,
  1733. vertical: /^(top|bottom)$/
  1734. };
  1735. var BODY_REGEX = /(HTML|BODY)/;
  1736. return {
  1737. /**
  1738. * Provides a raw DOM element from a jQuery/jQLite element.
  1739. *
  1740. * @param {element} elem - The element to convert.
  1741. *
  1742. * @returns {element} A HTML element.
  1743. */
  1744. getRawNode: function(elem) {
  1745. return elem.nodeName ? elem : elem[0] || elem;
  1746. },
  1747. /**
  1748. * Provides a parsed number for a style property. Strips
  1749. * units and casts invalid numbers to 0.
  1750. *
  1751. * @param {string} value - The style value to parse.
  1752. *
  1753. * @returns {number} A valid number.
  1754. */
  1755. parseStyle: function(value) {
  1756. value = parseFloat(value);
  1757. return isFinite(value) ? value : 0;
  1758. },
  1759. /**
  1760. * Provides the closest positioned ancestor.
  1761. *
  1762. * @param {element} element - The element to get the offest parent for.
  1763. *
  1764. * @returns {element} The closest positioned ancestor.
  1765. */
  1766. offsetParent: function(elem) {
  1767. elem = this.getRawNode(elem);
  1768. var offsetParent = elem.offsetParent || $document[0].documentElement;
  1769. function isStaticPositioned(el) {
  1770. return ($window.getComputedStyle(el).position || 'static') === 'static';
  1771. }
  1772. while (offsetParent && offsetParent !== $document[0].documentElement && isStaticPositioned(offsetParent)) {
  1773. offsetParent = offsetParent.offsetParent;
  1774. }
  1775. return offsetParent || $document[0].documentElement;
  1776. },
  1777. /**
  1778. * Provides the scrollbar width, concept from TWBS measureScrollbar()
  1779. * function in https://github.com/twbs/bootstrap/blob/master/js/modal.js
  1780. * In IE and Edge, scollbar on body and html element overlay and should
  1781. * return a width of 0.
  1782. *
  1783. * @returns {number} The width of the browser scollbar.
  1784. */
  1785. scrollbarWidth: function(isBody) {
  1786. if (isBody) {
  1787. if (angular.isUndefined(BODY_SCROLLBAR_WIDTH)) {
  1788. var bodyElem = $document.find('body');
  1789. bodyElem.addClass('uib-position-body-scrollbar-measure');
  1790. BODY_SCROLLBAR_WIDTH = $window.innerWidth - bodyElem[0].clientWidth;
  1791. BODY_SCROLLBAR_WIDTH = isFinite(BODY_SCROLLBAR_WIDTH) ? BODY_SCROLLBAR_WIDTH : 0;
  1792. bodyElem.removeClass('uib-position-body-scrollbar-measure');
  1793. }
  1794. return BODY_SCROLLBAR_WIDTH;
  1795. }
  1796. if (angular.isUndefined(SCROLLBAR_WIDTH)) {
  1797. var scrollElem = angular.element('<div class="uib-position-scrollbar-measure"></div>');
  1798. $document.find('body').append(scrollElem);
  1799. SCROLLBAR_WIDTH = scrollElem[0].offsetWidth - scrollElem[0].clientWidth;
  1800. SCROLLBAR_WIDTH = isFinite(SCROLLBAR_WIDTH) ? SCROLLBAR_WIDTH : 0;
  1801. scrollElem.remove();
  1802. }
  1803. return SCROLLBAR_WIDTH;
  1804. },
  1805. /**
  1806. * Provides the padding required on an element to replace the scrollbar.
  1807. *
  1808. * @returns {object} An object with the following properties:
  1809. * <ul>
  1810. * <li>**scrollbarWidth**: the width of the scrollbar</li>
  1811. * <li>**widthOverflow**: whether the the width is overflowing</li>
  1812. * <li>**right**: the amount of right padding on the element needed to replace the scrollbar</li>
  1813. * <li>**rightOriginal**: the amount of right padding currently on the element</li>
  1814. * <li>**heightOverflow**: whether the the height is overflowing</li>
  1815. * <li>**bottom**: the amount of bottom padding on the element needed to replace the scrollbar</li>
  1816. * <li>**bottomOriginal**: the amount of bottom padding currently on the element</li>
  1817. * </ul>
  1818. */
  1819. scrollbarPadding: function(elem) {
  1820. elem = this.getRawNode(elem);
  1821. var elemStyle = $window.getComputedStyle(elem);
  1822. var paddingRight = this.parseStyle(elemStyle.paddingRight);
  1823. var paddingBottom = this.parseStyle(elemStyle.paddingBottom);
  1824. var scrollParent = this.scrollParent(elem, false, true);
  1825. var scrollbarWidth = this.scrollbarWidth(scrollParent, BODY_REGEX.test(scrollParent.tagName));
  1826. return {
  1827. scrollbarWidth: scrollbarWidth,
  1828. widthOverflow: scrollParent.scrollWidth > scrollParent.clientWidth,
  1829. right: paddingRight + scrollbarWidth,
  1830. originalRight: paddingRight,
  1831. heightOverflow: scrollParent.scrollHeight > scrollParent.clientHeight,
  1832. bottom: paddingBottom + scrollbarWidth,
  1833. originalBottom: paddingBottom
  1834. };
  1835. },
  1836. /**
  1837. * Checks to see if the element is scrollable.
  1838. *
  1839. * @param {element} elem - The element to check.
  1840. * @param {boolean=} [includeHidden=false] - Should scroll style of 'hidden' be considered,
  1841. * default is false.
  1842. *
  1843. * @returns {boolean} Whether the element is scrollable.
  1844. */
  1845. isScrollable: function(elem, includeHidden) {
  1846. elem = this.getRawNode(elem);
  1847. var overflowRegex = includeHidden ? OVERFLOW_REGEX.hidden : OVERFLOW_REGEX.normal;
  1848. var elemStyle = $window.getComputedStyle(elem);
  1849. return overflowRegex.test(elemStyle.overflow + elemStyle.overflowY + elemStyle.overflowX);
  1850. },
  1851. /**
  1852. * Provides the closest scrollable ancestor.
  1853. * A port of the jQuery UI scrollParent method:
  1854. * https://github.com/jquery/jquery-ui/blob/master/ui/scroll-parent.js
  1855. *
  1856. * @param {element} elem - The element to find the scroll parent of.
  1857. * @param {boolean=} [includeHidden=false] - Should scroll style of 'hidden' be considered,
  1858. * default is false.
  1859. * @param {boolean=} [includeSelf=false] - Should the element being passed be
  1860. * included in the scrollable llokup.
  1861. *
  1862. * @returns {element} A HTML element.
  1863. */
  1864. scrollParent: function(elem, includeHidden, includeSelf) {
  1865. elem = this.getRawNode(elem);
  1866. var overflowRegex = includeHidden ? OVERFLOW_REGEX.hidden : OVERFLOW_REGEX.normal;
  1867. var documentEl = $document[0].documentElement;
  1868. var elemStyle = $window.getComputedStyle(elem);
  1869. if (includeSelf && overflowRegex.test(elemStyle.overflow + elemStyle.overflowY + elemStyle.overflowX)) {
  1870. return elem;
  1871. }
  1872. var excludeStatic = elemStyle.position === 'absolute';
  1873. var scrollParent = elem.parentElement || documentEl;
  1874. if (scrollParent === documentEl || elemStyle.position === 'fixed') {
  1875. return documentEl;
  1876. }
  1877. while (scrollParent.parentElement && scrollParent !== documentEl) {
  1878. var spStyle = $window.getComputedStyle(scrollParent);
  1879. if (excludeStatic && spStyle.position !== 'static') {
  1880. excludeStatic = false;
  1881. }
  1882. if (!excludeStatic && overflowRegex.test(spStyle.overflow + spStyle.overflowY + spStyle.overflowX)) {
  1883. break;
  1884. }
  1885. scrollParent = scrollParent.parentElement;
  1886. }
  1887. return scrollParent;
  1888. },
  1889. /**
  1890. * Provides read-only equivalent of jQuery's position function:
  1891. * http://api.jquery.com/position/ - distance to closest positioned
  1892. * ancestor. Does not account for margins by default like jQuery position.
  1893. *
  1894. * @param {element} elem - The element to caclulate the position on.
  1895. * @param {boolean=} [includeMargins=false] - Should margins be accounted
  1896. * for, default is false.
  1897. *
  1898. * @returns {object} An object with the following properties:
  1899. * <ul>
  1900. * <li>**width**: the width of the element</li>
  1901. * <li>**height**: the height of the element</li>
  1902. * <li>**top**: distance to top edge of offset parent</li>
  1903. * <li>**left**: distance to left edge of offset parent</li>
  1904. * </ul>
  1905. */
  1906. position: function(elem, includeMagins) {
  1907. elem = this.getRawNode(elem);
  1908. var elemOffset = this.offset(elem);
  1909. if (includeMagins) {
  1910. var elemStyle = $window.getComputedStyle(elem);
  1911. elemOffset.top -= this.parseStyle(elemStyle.marginTop);
  1912. elemOffset.left -= this.parseStyle(elemStyle.marginLeft);
  1913. }
  1914. var parent = this.offsetParent(elem);
  1915. var parentOffset = {top: 0, left: 0};
  1916. if (parent !== $document[0].documentElement) {
  1917. parentOffset = this.offset(parent);
  1918. parentOffset.top += parent.clientTop - parent.scrollTop;
  1919. parentOffset.left += parent.clientLeft - parent.scrollLeft;
  1920. }
  1921. return {
  1922. width: Math.round(angular.isNumber(elemOffset.width) ? elemOffset.width : elem.offsetWidth),
  1923. height: Math.round(angular.isNumber(elemOffset.height) ? elemOffset.height : elem.offsetHeight),
  1924. top: Math.round(elemOffset.top - parentOffset.top),
  1925. left: Math.round(elemOffset.left - parentOffset.left)
  1926. };
  1927. },
  1928. /**
  1929. * Provides read-only equivalent of jQuery's offset function:
  1930. * http://api.jquery.com/offset/ - distance to viewport. Does
  1931. * not account for borders, margins, or padding on the body
  1932. * element.
  1933. *
  1934. * @param {element} elem - The element to calculate the offset on.
  1935. *
  1936. * @returns {object} An object with the following properties:
  1937. * <ul>
  1938. * <li>**width**: the width of the element</li>
  1939. * <li>**height**: the height of the element</li>
  1940. * <li>**top**: distance to top edge of viewport</li>
  1941. * <li>**right**: distance to bottom edge of viewport</li>
  1942. * </ul>
  1943. */
  1944. offset: function(elem) {
  1945. elem = this.getRawNode(elem);
  1946. var elemBCR = elem.getBoundingClientRect();
  1947. return {
  1948. width: Math.round(angular.isNumber(elemBCR.width) ? elemBCR.width : elem.offsetWidth),
  1949. height: Math.round(angular.isNumber(elemBCR.height) ? elemBCR.height : elem.offsetHeight),
  1950. top: Math.round(elemBCR.top + ($window.pageYOffset || $document[0].documentElement.scrollTop)),
  1951. left: Math.round(elemBCR.left + ($window.pageXOffset || $document[0].documentElement.scrollLeft))
  1952. };
  1953. },
  1954. /**
  1955. * Provides offset distance to the closest scrollable ancestor
  1956. * or viewport. Accounts for border and scrollbar width.
  1957. *
  1958. * Right and bottom dimensions represent the distance to the
  1959. * respective edge of the viewport element. If the element
  1960. * edge extends beyond the viewport, a negative value will be
  1961. * reported.
  1962. *
  1963. * @param {element} elem - The element to get the viewport offset for.
  1964. * @param {boolean=} [useDocument=false] - Should the viewport be the document element instead
  1965. * of the first scrollable element, default is false.
  1966. * @param {boolean=} [includePadding=true] - Should the padding on the offset parent element
  1967. * be accounted for, default is true.
  1968. *
  1969. * @returns {object} An object with the following properties:
  1970. * <ul>
  1971. * <li>**top**: distance to the top content edge of viewport element</li>
  1972. * <li>**bottom**: distance to the bottom content edge of viewport element</li>
  1973. * <li>**left**: distance to the left content edge of viewport element</li>
  1974. * <li>**right**: distance to the right content edge of viewport element</li>
  1975. * </ul>
  1976. */
  1977. viewportOffset: function(elem, useDocument, includePadding) {
  1978. elem = this.getRawNode(elem);
  1979. includePadding = includePadding !== false ? true : false;
  1980. var elemBCR = elem.getBoundingClientRect();
  1981. var offsetBCR = {top: 0, left: 0, bottom: 0, right: 0};
  1982. var offsetParent = useDocument ? $document[0].documentElement : this.scrollParent(elem);
  1983. var offsetParentBCR = offsetParent.getBoundingClientRect();
  1984. offsetBCR.top = offsetParentBCR.top + offsetParent.clientTop;
  1985. offsetBCR.left = offsetParentBCR.left + offsetParent.clientLeft;
  1986. if (offsetParent === $document[0].documentElement) {
  1987. offsetBCR.top += $window.pageYOffset;
  1988. offsetBCR.left += $window.pageXOffset;
  1989. }
  1990. offsetBCR.bottom = offsetBCR.top + offsetParent.clientHeight;
  1991. offsetBCR.right = offsetBCR.left + offsetParent.clientWidth;
  1992. if (includePadding) {
  1993. var offsetParentStyle = $window.getComputedStyle(offsetParent);
  1994. offsetBCR.top += this.parseStyle(offsetParentStyle.paddingTop);
  1995. offsetBCR.bottom -= this.parseStyle(offsetParentStyle.paddingBottom);
  1996. offsetBCR.left += this.parseStyle(offsetParentStyle.paddingLeft);
  1997. offsetBCR.right -= this.parseStyle(offsetParentStyle.paddingRight);
  1998. }
  1999. return {
  2000. top: Math.round(elemBCR.top - offsetBCR.top),
  2001. bottom: Math.round(offsetBCR.bottom - elemBCR.bottom),
  2002. left: Math.round(elemBCR.left - offsetBCR.left),
  2003. right: Math.round(offsetBCR.right - elemBCR.right)
  2004. };
  2005. },
  2006. /**
  2007. * Provides an array of placement values parsed from a placement string.
  2008. * Along with the 'auto' indicator, supported placement strings are:
  2009. * <ul>
  2010. * <li>top: element on top, horizontally centered on host element.</li>
  2011. * <li>top-left: element on top, left edge aligned with host element left edge.</li>
  2012. * <li>top-right: element on top, lerightft edge aligned with host element right edge.</li>
  2013. * <li>bottom: element on bottom, horizontally centered on host element.</li>
  2014. * <li>bottom-left: element on bottom, left edge aligned with host element left edge.</li>
  2015. * <li>bottom-right: element on bottom, right edge aligned with host element right edge.</li>
  2016. * <li>left: element on left, vertically centered on host element.</li>
  2017. * <li>left-top: element on left, top edge aligned with host element top edge.</li>
  2018. * <li>left-bottom: element on left, bottom edge aligned with host element bottom edge.</li>
  2019. * <li>right: element on right, vertically centered on host element.</li>
  2020. * <li>right-top: element on right, top edge aligned with host element top edge.</li>
  2021. * <li>right-bottom: element on right, bottom edge aligned with host element bottom edge.</li>
  2022. * </ul>
  2023. * A placement string with an 'auto' indicator is expected to be
  2024. * space separated from the placement, i.e: 'auto bottom-left' If
  2025. * the primary and secondary placement values do not match 'top,
  2026. * bottom, left, right' then 'top' will be the primary placement and
  2027. * 'center' will be the secondary placement. If 'auto' is passed, true
  2028. * will be returned as the 3rd value of the array.
  2029. *
  2030. * @param {string} placement - The placement string to parse.
  2031. *
  2032. * @returns {array} An array with the following values
  2033. * <ul>
  2034. * <li>**[0]**: The primary placement.</li>
  2035. * <li>**[1]**: The secondary placement.</li>
  2036. * <li>**[2]**: If auto is passed: true, else undefined.</li>
  2037. * </ul>
  2038. */
  2039. parsePlacement: function(placement) {
  2040. var autoPlace = PLACEMENT_REGEX.auto.test(placement);
  2041. if (autoPlace) {
  2042. placement = placement.replace(PLACEMENT_REGEX.auto, '');
  2043. }
  2044. placement = placement.split('-');
  2045. placement[0] = placement[0] || 'top';
  2046. if (!PLACEMENT_REGEX.primary.test(placement[0])) {
  2047. placement[0] = 'top';
  2048. }
  2049. placement[1] = placement[1] || 'center';
  2050. if (!PLACEMENT_REGEX.secondary.test(placement[1])) {
  2051. placement[1] = 'center';
  2052. }
  2053. if (autoPlace) {
  2054. placement[2] = true;
  2055. } else {
  2056. placement[2] = false;
  2057. }
  2058. return placement;
  2059. },
  2060. /**
  2061. * Provides coordinates for an element to be positioned relative to
  2062. * another element. Passing 'auto' as part of the placement parameter
  2063. * will enable smart placement - where the element fits. i.e:
  2064. * 'auto left-top' will check to see if there is enough space to the left
  2065. * of the hostElem to fit the targetElem, if not place right (same for secondary
  2066. * top placement). Available space is calculated using the viewportOffset
  2067. * function.
  2068. *
  2069. * @param {element} hostElem - The element to position against.
  2070. * @param {element} targetElem - The element to position.
  2071. * @param {string=} [placement=top] - The placement for the targetElem,
  2072. * default is 'top'. 'center' is assumed as secondary placement for
  2073. * 'top', 'left', 'right', and 'bottom' placements. Available placements are:
  2074. * <ul>
  2075. * <li>top</li>
  2076. * <li>top-right</li>
  2077. * <li>top-left</li>
  2078. * <li>bottom</li>
  2079. * <li>bottom-left</li>
  2080. * <li>bottom-right</li>
  2081. * <li>left</li>
  2082. * <li>left-top</li>
  2083. * <li>left-bottom</li>
  2084. * <li>right</li>
  2085. * <li>right-top</li>
  2086. * <li>right-bottom</li>
  2087. * </ul>
  2088. * @param {boolean=} [appendToBody=false] - Should the top and left values returned
  2089. * be calculated from the body element, default is false.
  2090. *
  2091. * @returns {object} An object with the following properties:
  2092. * <ul>
  2093. * <li>**top**: Value for targetElem top.</li>
  2094. * <li>**left**: Value for targetElem left.</li>
  2095. * <li>**placement**: The resolved placement.</li>
  2096. * </ul>
  2097. */
  2098. positionElements: function(hostElem, targetElem, placement, appendToBody) {
  2099. hostElem = this.getRawNode(hostElem);
  2100. targetElem = this.getRawNode(targetElem);
  2101. // need to read from prop to support tests.
  2102. var targetWidth = angular.isDefined(targetElem.offsetWidth) ? targetElem.offsetWidth : targetElem.prop('offsetWidth');
  2103. var targetHeight = angular.isDefined(targetElem.offsetHeight) ? targetElem.offsetHeight : targetElem.prop('offsetHeight');
  2104. placement = this.parsePlacement(placement);
  2105. var hostElemPos = appendToBody ? this.offset(hostElem) : this.position(hostElem);
  2106. var targetElemPos = {top: 0, left: 0, placement: ''};
  2107. if (placement[2]) {
  2108. var viewportOffset = this.viewportOffset(hostElem, appendToBody);
  2109. var targetElemStyle = $window.getComputedStyle(targetElem);
  2110. var adjustedSize = {
  2111. width: targetWidth + Math.round(Math.abs(this.parseStyle(targetElemStyle.marginLeft) + this.parseStyle(targetElemStyle.marginRight))),
  2112. height: targetHeight + Math.round(Math.abs(this.parseStyle(targetElemStyle.marginTop) + this.parseStyle(targetElemStyle.marginBottom)))
  2113. };
  2114. placement[0] = placement[0] === 'top' && adjustedSize.height > viewportOffset.top && adjustedSize.height <= viewportOffset.bottom ? 'bottom' :
  2115. placement[0] === 'bottom' && adjustedSize.height > viewportOffset.bottom && adjustedSize.height <= viewportOffset.top ? 'top' :
  2116. placement[0] === 'left' && adjustedSize.width > viewportOffset.left && adjustedSize.width <= viewportOffset.right ? 'right' :
  2117. placement[0] === 'right' && adjustedSize.width > viewportOffset.right && adjustedSize.width <= viewportOffset.left ? 'left' :
  2118. placement[0];
  2119. placement[1] = placement[1] === 'top' && adjustedSize.height - hostElemPos.height > viewportOffset.bottom && adjustedSize.height - hostElemPos.height <= viewportOffset.top ? 'bottom' :
  2120. placement[1] === 'bottom' && adjustedSize.height - hostElemPos.height > viewportOffset.top && adjustedSize.height - hostElemPos.height <= viewportOffset.bottom ? 'top' :
  2121. placement[1] === 'left' && adjustedSize.width - hostElemPos.width > viewportOffset.right && adjustedSize.width - hostElemPos.width <= viewportOffset.left ? 'right' :
  2122. placement[1] === 'right' && adjustedSize.width - hostElemPos.width > viewportOffset.left && adjustedSize.width - hostElemPos.width <= viewportOffset.right ? 'left' :
  2123. placement[1];
  2124. if (placement[1] === 'center') {
  2125. if (PLACEMENT_REGEX.vertical.test(placement[0])) {
  2126. var xOverflow = hostElemPos.width / 2 - targetWidth / 2;
  2127. if (viewportOffset.left + xOverflow < 0 && adjustedSize.width - hostElemPos.width <= viewportOffset.right) {
  2128. placement[1] = 'left';
  2129. } else if (viewportOffset.right + xOverflow < 0 && adjustedSize.width - hostElemPos.width <= viewportOffset.left) {
  2130. placement[1] = 'right';
  2131. }
  2132. } else {
  2133. var yOverflow = hostElemPos.height / 2 - adjustedSize.height / 2;
  2134. if (viewportOffset.top + yOverflow < 0 && adjustedSize.height - hostElemPos.height <= viewportOffset.bottom) {
  2135. placement[1] = 'top';
  2136. } else if (viewportOffset.bottom + yOverflow < 0 && adjustedSize.height - hostElemPos.height <= viewportOffset.top) {
  2137. placement[1] = 'bottom';
  2138. }
  2139. }
  2140. }
  2141. }
  2142. switch (placement[0]) {
  2143. case 'top':
  2144. targetElemPos.top = hostElemPos.top - targetHeight;
  2145. break;
  2146. case 'bottom':
  2147. targetElemPos.top = hostElemPos.top + hostElemPos.height;
  2148. break;
  2149. case 'left':
  2150. targetElemPos.left = hostElemPos.left - targetWidth;
  2151. break;
  2152. case 'right':
  2153. targetElemPos.left = hostElemPos.left + hostElemPos.width;
  2154. break;
  2155. }
  2156. switch (placement[1]) {
  2157. case 'top':
  2158. targetElemPos.top = hostElemPos.top;
  2159. break;
  2160. case 'bottom':
  2161. targetElemPos.top = hostElemPos.top + hostElemPos.height - targetHeight;
  2162. break;
  2163. case 'left':
  2164. targetElemPos.left = hostElemPos.left;
  2165. break;
  2166. case 'right':
  2167. targetElemPos.left = hostElemPos.left + hostElemPos.width - targetWidth;
  2168. break;
  2169. case 'center':
  2170. if (PLACEMENT_REGEX.vertical.test(placement[0])) {
  2171. targetElemPos.left = hostElemPos.left + hostElemPos.width / 2 - targetWidth / 2;
  2172. } else {
  2173. targetElemPos.top = hostElemPos.top + hostElemPos.height / 2 - targetHeight / 2;
  2174. }
  2175. break;
  2176. }
  2177. targetElemPos.top = Math.round(targetElemPos.top);
  2178. targetElemPos.left = Math.round(targetElemPos.left);
  2179. targetElemPos.placement = placement[1] === 'center' ? placement[0] : placement[0] + '-' + placement[1];
  2180. return targetElemPos;
  2181. },
  2182. /**
  2183. * Provides a way for positioning tooltip & dropdown
  2184. * arrows when using placement options beyond the standard
  2185. * left, right, top, or bottom.
  2186. *
  2187. * @param {element} elem - The tooltip/dropdown element.
  2188. * @param {string} placement - The placement for the elem.
  2189. */
  2190. positionArrow: function(elem, placement) {
  2191. elem = this.getRawNode(elem);
  2192. var innerElem = elem.querySelector('.tooltip-inner, .popover-inner');
  2193. if (!innerElem) {
  2194. return;
  2195. }
  2196. var isTooltip = angular.element(innerElem).hasClass('tooltip-inner');
  2197. var arrowElem = isTooltip ? elem.querySelector('.tooltip-arrow') : elem.querySelector('.arrow');
  2198. if (!arrowElem) {
  2199. return;
  2200. }
  2201. var arrowCss = {
  2202. top: '',
  2203. bottom: '',
  2204. left: '',
  2205. right: ''
  2206. };
  2207. placement = this.parsePlacement(placement);
  2208. if (placement[1] === 'center') {
  2209. // no adjustment necessary - just reset styles
  2210. angular.element(arrowElem).css(arrowCss);
  2211. return;
  2212. }
  2213. var borderProp = 'border-' + placement[0] + '-width';
  2214. var borderWidth = $window.getComputedStyle(arrowElem)[borderProp];
  2215. var borderRadiusProp = 'border-';
  2216. if (PLACEMENT_REGEX.vertical.test(placement[0])) {
  2217. borderRadiusProp += placement[0] + '-' + placement[1];
  2218. } else {
  2219. borderRadiusProp += placement[1] + '-' + placement[0];
  2220. }
  2221. borderRadiusProp += '-radius';
  2222. var borderRadius = $window.getComputedStyle(isTooltip ? innerElem : elem)[borderRadiusProp];
  2223. switch (placement[0]) {
  2224. case 'top':
  2225. arrowCss.bottom = isTooltip ? '0' : '-' + borderWidth;
  2226. break;
  2227. case 'bottom':
  2228. arrowCss.top = isTooltip ? '0' : '-' + borderWidth;
  2229. break;
  2230. case 'left':
  2231. arrowCss.right = isTooltip ? '0' : '-' + borderWidth;
  2232. break;
  2233. case 'right':
  2234. arrowCss.left = isTooltip ? '0' : '-' + borderWidth;
  2235. break;
  2236. }
  2237. arrowCss[placement[1]] = borderRadius;
  2238. angular.element(arrowElem).css(arrowCss);
  2239. }
  2240. };
  2241. }]);
  2242. angular.module('ui.bootstrap.datepickerPopup', ['ui.bootstrap.datepicker', 'ui.bootstrap.position'])
  2243. .value('$datepickerPopupLiteralWarning', true)
  2244. .constant('uibDatepickerPopupConfig', {
  2245. altInputFormats: [],
  2246. appendToBody: false,
  2247. clearText: 'Clear',
  2248. closeOnDateSelection: true,
  2249. closeText: 'Done',
  2250. currentText: 'Today',
  2251. datepickerPopup: 'yyyy-MM-dd',
  2252. datepickerPopupTemplateUrl: 'uib/template/datepickerPopup/popup.html',
  2253. datepickerTemplateUrl: 'uib/template/datepicker/datepicker.html',
  2254. html5Types: {
  2255. date: 'yyyy-MM-dd',
  2256. 'datetime-local': 'yyyy-MM-ddTHH:mm:ss.sss',
  2257. 'month': 'yyyy-MM'
  2258. },
  2259. onOpenFocus: true,
  2260. showButtonBar: true,
  2261. placement: 'auto bottom-left'
  2262. })
  2263. .controller('UibDatepickerPopupController', ['$scope', '$element', '$attrs', '$compile', '$log', '$parse', '$window', '$document', '$rootScope', '$uibPosition', 'dateFilter', 'uibDateParser', 'uibDatepickerPopupConfig', '$timeout', 'uibDatepickerConfig', '$datepickerPopupLiteralWarning',
  2264. function($scope, $element, $attrs, $compile, $log, $parse, $window, $document, $rootScope, $position, dateFilter, dateParser, datepickerPopupConfig, $timeout, datepickerConfig, $datepickerPopupLiteralWarning) {
  2265. var cache = {},
  2266. isHtml5DateInput = false;
  2267. var dateFormat, closeOnDateSelection, appendToBody, onOpenFocus,
  2268. datepickerPopupTemplateUrl, datepickerTemplateUrl, popupEl, datepickerEl, scrollParentEl,
  2269. ngModel, ngModelOptions, $popup, altInputFormats, watchListeners = [],
  2270. timezone;
  2271. this.init = function(_ngModel_) {
  2272. ngModel = _ngModel_;
  2273. ngModelOptions = _ngModel_.$options;
  2274. closeOnDateSelection = angular.isDefined($attrs.closeOnDateSelection) ?
  2275. $scope.$parent.$eval($attrs.closeOnDateSelection) :
  2276. datepickerPopupConfig.closeOnDateSelection;
  2277. appendToBody = angular.isDefined($attrs.datepickerAppendToBody) ?
  2278. $scope.$parent.$eval($attrs.datepickerAppendToBody) :
  2279. datepickerPopupConfig.appendToBody;
  2280. onOpenFocus = angular.isDefined($attrs.onOpenFocus) ?
  2281. $scope.$parent.$eval($attrs.onOpenFocus) : datepickerPopupConfig.onOpenFocus;
  2282. datepickerPopupTemplateUrl = angular.isDefined($attrs.datepickerPopupTemplateUrl) ?
  2283. $attrs.datepickerPopupTemplateUrl :
  2284. datepickerPopupConfig.datepickerPopupTemplateUrl;
  2285. datepickerTemplateUrl = angular.isDefined($attrs.datepickerTemplateUrl) ?
  2286. $attrs.datepickerTemplateUrl : datepickerPopupConfig.datepickerTemplateUrl;
  2287. altInputFormats = angular.isDefined($attrs.altInputFormats) ?
  2288. $scope.$parent.$eval($attrs.altInputFormats) :
  2289. datepickerPopupConfig.altInputFormats;
  2290. $scope.showButtonBar = angular.isDefined($attrs.showButtonBar) ?
  2291. $scope.$parent.$eval($attrs.showButtonBar) :
  2292. datepickerPopupConfig.showButtonBar;
  2293. if (datepickerPopupConfig.html5Types[$attrs.type]) {
  2294. dateFormat = datepickerPopupConfig.html5Types[$attrs.type];
  2295. isHtml5DateInput = true;
  2296. } else {
  2297. dateFormat = $attrs.uibDatepickerPopup || datepickerPopupConfig.datepickerPopup;
  2298. $attrs.$observe('uibDatepickerPopup', function(value, oldValue) {
  2299. var newDateFormat = value || datepickerPopupConfig.datepickerPopup;
  2300. // Invalidate the $modelValue to ensure that formatters re-run
  2301. // FIXME: Refactor when PR is merged: https://github.com/angular/angular.js/pull/10764
  2302. if (newDateFormat !== dateFormat) {
  2303. dateFormat = newDateFormat;
  2304. ngModel.$modelValue = null;
  2305. if (!dateFormat) {
  2306. throw new Error('uibDatepickerPopup must have a date format specified.');
  2307. }
  2308. }
  2309. });
  2310. }
  2311. if (!dateFormat) {
  2312. throw new Error('uibDatepickerPopup must have a date format specified.');
  2313. }
  2314. if (isHtml5DateInput && $attrs.uibDatepickerPopup) {
  2315. throw new Error('HTML5 date input types do not support custom formats.');
  2316. }
  2317. // popup element used to display calendar
  2318. popupEl = angular.element('<div uib-datepicker-popup-wrap><div uib-datepicker></div></div>');
  2319. if (ngModelOptions) {
  2320. timezone = ngModelOptions.timezone;
  2321. $scope.ngModelOptions = angular.copy(ngModelOptions);
  2322. $scope.ngModelOptions.timezone = null;
  2323. if ($scope.ngModelOptions.updateOnDefault === true) {
  2324. $scope.ngModelOptions.updateOn = $scope.ngModelOptions.updateOn ?
  2325. $scope.ngModelOptions.updateOn + ' default' : 'default';
  2326. }
  2327. popupEl.attr('ng-model-options', 'ngModelOptions');
  2328. } else {
  2329. timezone = null;
  2330. }
  2331. popupEl.attr({
  2332. 'ng-model': 'date',
  2333. 'ng-change': 'dateSelection(date)',
  2334. 'template-url': datepickerPopupTemplateUrl
  2335. });
  2336. // datepicker element
  2337. datepickerEl = angular.element(popupEl.children()[0]);
  2338. datepickerEl.attr('template-url', datepickerTemplateUrl);
  2339. if (!$scope.datepickerOptions) {
  2340. $scope.datepickerOptions = {};
  2341. }
  2342. if (isHtml5DateInput) {
  2343. if ($attrs.type === 'month') {
  2344. $scope.datepickerOptions.datepickerMode = 'month';
  2345. $scope.datepickerOptions.minMode = 'month';
  2346. }
  2347. }
  2348. datepickerEl.attr('datepicker-options', 'datepickerOptions');
  2349. if (!isHtml5DateInput) {
  2350. // Internal API to maintain the correct ng-invalid-[key] class
  2351. ngModel.$$parserName = 'date';
  2352. ngModel.$validators.date = validator;
  2353. ngModel.$parsers.unshift(parseDate);
  2354. ngModel.$formatters.push(function(value) {
  2355. if (ngModel.$isEmpty(value)) {
  2356. $scope.date = value;
  2357. return value;
  2358. }
  2359. if (angular.isNumber(value)) {
  2360. value = new Date(value);
  2361. }
  2362. $scope.date = dateParser.fromTimezone(value, timezone);
  2363. return dateParser.filter($scope.date, dateFormat);
  2364. });
  2365. } else {
  2366. ngModel.$formatters.push(function(value) {
  2367. $scope.date = dateParser.fromTimezone(value, timezone);
  2368. return value;
  2369. });
  2370. }
  2371. // Detect changes in the view from the text box
  2372. ngModel.$viewChangeListeners.push(function() {
  2373. $scope.date = parseDateString(ngModel.$viewValue);
  2374. });
  2375. $element.on('keydown', inputKeydownBind);
  2376. $popup = $compile(popupEl)($scope);
  2377. // Prevent jQuery cache memory leak (template is now redundant after linking)
  2378. popupEl.remove();
  2379. if (appendToBody) {
  2380. $document.find('body').append($popup);
  2381. } else {
  2382. $element.after($popup);
  2383. }
  2384. $scope.$on('$destroy', function() {
  2385. if ($scope.isOpen === true) {
  2386. if (!$rootScope.$$phase) {
  2387. $scope.$apply(function() {
  2388. $scope.isOpen = false;
  2389. });
  2390. }
  2391. }
  2392. $popup.remove();
  2393. $element.off('keydown', inputKeydownBind);
  2394. $document.off('click', documentClickBind);
  2395. if (scrollParentEl) {
  2396. scrollParentEl.off('scroll', positionPopup);
  2397. }
  2398. angular.element($window).off('resize', positionPopup);
  2399. //Clear all watch listeners on destroy
  2400. while (watchListeners.length) {
  2401. watchListeners.shift()();
  2402. }
  2403. });
  2404. };
  2405. $scope.getText = function(key) {
  2406. return $scope[key + 'Text'] || datepickerPopupConfig[key + 'Text'];
  2407. };
  2408. $scope.isDisabled = function(date) {
  2409. if (date === 'today') {
  2410. date = dateParser.fromTimezone(new Date(), timezone);
  2411. }
  2412. var dates = {};
  2413. angular.forEach(['minDate', 'maxDate'], function(key) {
  2414. if (!$scope.datepickerOptions[key]) {
  2415. dates[key] = null;
  2416. } else if (angular.isDate($scope.datepickerOptions[key])) {
  2417. dates[key] = dateParser.fromTimezone(new Date($scope.datepickerOptions[key]), timezone);
  2418. } else {
  2419. if ($datepickerPopupLiteralWarning) {
  2420. $log.warn('Literal date support has been deprecated, please switch to date object usage');
  2421. }
  2422. dates[key] = new Date(dateFilter($scope.datepickerOptions[key], 'medium'));
  2423. }
  2424. });
  2425. return $scope.datepickerOptions &&
  2426. dates.minDate && $scope.compare(date, dates.minDate) < 0 ||
  2427. dates.maxDate && $scope.compare(date, dates.maxDate) > 0;
  2428. };
  2429. $scope.compare = function(date1, date2) {
  2430. return new Date(date1.getFullYear(), date1.getMonth(), date1.getDate()) - new Date(date2.getFullYear(), date2.getMonth(), date2.getDate());
  2431. };
  2432. // Inner change
  2433. $scope.dateSelection = function(dt) {
  2434. if (angular.isDefined(dt)) {
  2435. $scope.date = dt;
  2436. }
  2437. var date = $scope.date ? dateParser.filter($scope.date, dateFormat) : null; // Setting to NULL is necessary for form validators to function
  2438. $element.val(date);
  2439. ngModel.$setViewValue(date);
  2440. if (closeOnDateSelection) {
  2441. $scope.isOpen = false;
  2442. $element[0].focus();
  2443. }
  2444. };
  2445. $scope.keydown = function(evt) {
  2446. if (evt.which === 27) {
  2447. evt.stopPropagation();
  2448. $scope.isOpen = false;
  2449. $element[0].focus();
  2450. }
  2451. };
  2452. $scope.select = function(date, evt) {
  2453. evt.stopPropagation();
  2454. if (date === 'today') {
  2455. var today = new Date();
  2456. if (angular.isDate($scope.date)) {
  2457. date = new Date($scope.date);
  2458. date.setFullYear(today.getFullYear(), today.getMonth(), today.getDate());
  2459. } else {
  2460. date = new Date(today.setHours(0, 0, 0, 0));
  2461. }
  2462. }
  2463. $scope.dateSelection(date);
  2464. };
  2465. $scope.close = function(evt) {
  2466. evt.stopPropagation();
  2467. $scope.isOpen = false;
  2468. $element[0].focus();
  2469. };
  2470. $scope.disabled = angular.isDefined($attrs.disabled) || false;
  2471. if ($attrs.ngDisabled) {
  2472. watchListeners.push($scope.$parent.$watch($parse($attrs.ngDisabled), function(disabled) {
  2473. $scope.disabled = disabled;
  2474. }));
  2475. }
  2476. $scope.$watch('isOpen', function(value) {
  2477. if (value) {
  2478. if (!$scope.disabled) {
  2479. $timeout(function() {
  2480. positionPopup();
  2481. if (onOpenFocus) {
  2482. $scope.$broadcast('uib:datepicker.focus');
  2483. }
  2484. $document.on('click', documentClickBind);
  2485. var placement = $attrs.popupPlacement ? $attrs.popupPlacement : datepickerPopupConfig.placement;
  2486. if (appendToBody || $position.parsePlacement(placement)[2]) {
  2487. scrollParentEl = scrollParentEl || angular.element($position.scrollParent($element));
  2488. if (scrollParentEl) {
  2489. scrollParentEl.on('scroll', positionPopup);
  2490. }
  2491. } else {
  2492. scrollParentEl = null;
  2493. }
  2494. angular.element($window).on('resize', positionPopup);
  2495. }, 0, false);
  2496. } else {
  2497. $scope.isOpen = false;
  2498. }
  2499. } else {
  2500. $document.off('click', documentClickBind);
  2501. if (scrollParentEl) {
  2502. scrollParentEl.off('scroll', positionPopup);
  2503. }
  2504. angular.element($window).off('resize', positionPopup);
  2505. }
  2506. });
  2507. function cameltoDash(string) {
  2508. return string.replace(/([A-Z])/g, function($1) { return '-' + $1.toLowerCase(); });
  2509. }
  2510. function parseDateString(viewValue) {
  2511. var date = dateParser.parse(viewValue, dateFormat, $scope.date);
  2512. if (isNaN(date)) {
  2513. for (var i = 0; i < altInputFormats.length; i++) {
  2514. date = dateParser.parse(viewValue, altInputFormats[i], $scope.date);
  2515. if (!isNaN(date)) {
  2516. return date;
  2517. }
  2518. }
  2519. }
  2520. return date;
  2521. }
  2522. function parseDate(viewValue) {
  2523. if (angular.isNumber(viewValue)) {
  2524. // presumably timestamp to date object
  2525. viewValue = new Date(viewValue);
  2526. }
  2527. if (!viewValue) {
  2528. return null;
  2529. }
  2530. if (angular.isDate(viewValue) && !isNaN(viewValue)) {
  2531. return viewValue;
  2532. }
  2533. if (angular.isString(viewValue)) {
  2534. var date = parseDateString(viewValue);
  2535. if (!isNaN(date)) {
  2536. return dateParser.toTimezone(date, timezone);
  2537. }
  2538. }
  2539. return ngModel.$options && ngModel.$options.allowInvalid ? viewValue : undefined;
  2540. }
  2541. function validator(modelValue, viewValue) {
  2542. var value = modelValue || viewValue;
  2543. if (!$attrs.ngRequired && !value) {
  2544. return true;
  2545. }
  2546. if (angular.isNumber(value)) {
  2547. value = new Date(value);
  2548. }
  2549. if (!value) {
  2550. return true;
  2551. }
  2552. if (angular.isDate(value) && !isNaN(value)) {
  2553. return true;
  2554. }
  2555. if (angular.isString(value)) {
  2556. return !isNaN(parseDateString(viewValue));
  2557. }
  2558. return false;
  2559. }
  2560. function documentClickBind(event) {
  2561. if (!$scope.isOpen && $scope.disabled) {
  2562. return;
  2563. }
  2564. var popup = $popup[0];
  2565. var dpContainsTarget = $element[0].contains(event.target);
  2566. // The popup node may not be an element node
  2567. // In some browsers (IE) only element nodes have the 'contains' function
  2568. var popupContainsTarget = popup.contains !== undefined && popup.contains(event.target);
  2569. if ($scope.isOpen && !(dpContainsTarget || popupContainsTarget)) {
  2570. $scope.$apply(function() {
  2571. $scope.isOpen = false;
  2572. });
  2573. }
  2574. }
  2575. function inputKeydownBind(evt) {
  2576. if (evt.which === 27 && $scope.isOpen) {
  2577. evt.preventDefault();
  2578. evt.stopPropagation();
  2579. $scope.$apply(function() {
  2580. $scope.isOpen = false;
  2581. });
  2582. $element[0].focus();
  2583. } else if (evt.which === 40 && !$scope.isOpen) {
  2584. evt.preventDefault();
  2585. evt.stopPropagation();
  2586. $scope.$apply(function() {
  2587. $scope.isOpen = true;
  2588. });
  2589. }
  2590. }
  2591. function positionPopup() {
  2592. if ($scope.isOpen) {
  2593. var dpElement = angular.element($popup[0].querySelector('.uib-datepicker-popup'));
  2594. var placement = $attrs.popupPlacement ? $attrs.popupPlacement : datepickerPopupConfig.placement;
  2595. var position = $position.positionElements($element, dpElement, placement, appendToBody);
  2596. dpElement.css({top: position.top + 'px', left: position.left + 'px'});
  2597. if (dpElement.hasClass('uib-position-measure')) {
  2598. dpElement.removeClass('uib-position-measure');
  2599. }
  2600. }
  2601. }
  2602. $scope.$on('uib:datepicker.mode', function() {
  2603. $timeout(positionPopup, 0, false);
  2604. });
  2605. }])
  2606. .directive('uibDatepickerPopup', function() {
  2607. return {
  2608. require: ['ngModel', 'uibDatepickerPopup'],
  2609. controller: 'UibDatepickerPopupController',
  2610. scope: {
  2611. datepickerOptions: '=?',
  2612. isOpen: '=?',
  2613. currentText: '@',
  2614. clearText: '@',
  2615. closeText: '@'
  2616. },
  2617. link: function(scope, element, attrs, ctrls) {
  2618. var ngModel = ctrls[0],
  2619. ctrl = ctrls[1];
  2620. ctrl.init(ngModel);
  2621. }
  2622. };
  2623. })
  2624. .directive('uibDatepickerPopupWrap', function() {
  2625. return {
  2626. replace: true,
  2627. transclude: true,
  2628. templateUrl: function(element, attrs) {
  2629. return attrs.templateUrl || 'uib/template/datepickerPopup/popup.html';
  2630. }
  2631. };
  2632. });
  2633. angular.module('ui.bootstrap.debounce', [])
  2634. /**
  2635. * A helper, internal service that debounces a function
  2636. */
  2637. .factory('$$debounce', ['$timeout', function($timeout) {
  2638. return function(callback, debounceTime) {
  2639. var timeoutPromise;
  2640. return function() {
  2641. var self = this;
  2642. var args = Array.prototype.slice.call(arguments);
  2643. if (timeoutPromise) {
  2644. $timeout.cancel(timeoutPromise);
  2645. }
  2646. timeoutPromise = $timeout(function() {
  2647. callback.apply(self, args);
  2648. }, debounceTime);
  2649. };
  2650. };
  2651. }]);
  2652. angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
  2653. .constant('uibDropdownConfig', {
  2654. appendToOpenClass: 'uib-dropdown-open',
  2655. openClass: 'open'
  2656. })
  2657. .service('uibDropdownService', ['$document', '$rootScope', function($document, $rootScope) {
  2658. var openScope = null;
  2659. this.open = function(dropdownScope, element) {
  2660. if (!openScope) {
  2661. $document.on('click', closeDropdown);
  2662. element.on('keydown', keybindFilter);
  2663. }
  2664. if (openScope && openScope !== dropdownScope) {
  2665. openScope.isOpen = false;
  2666. }
  2667. openScope = dropdownScope;
  2668. };
  2669. this.close = function(dropdownScope, element) {
  2670. if (openScope === dropdownScope) {
  2671. openScope = null;
  2672. $document.off('click', closeDropdown);
  2673. element.off('keydown', keybindFilter);
  2674. }
  2675. };
  2676. var closeDropdown = function(evt) {
  2677. // This method may still be called during the same mouse event that
  2678. // unbound this event handler. So check openScope before proceeding.
  2679. if (!openScope) { return; }
  2680. if (evt && openScope.getAutoClose() === 'disabled') { return; }
  2681. if (evt && evt.which === 3) { return; }
  2682. var toggleElement = openScope.getToggleElement();
  2683. if (evt && toggleElement && toggleElement[0].contains(evt.target)) {
  2684. return;
  2685. }
  2686. var dropdownElement = openScope.getDropdownElement();
  2687. if (evt && openScope.getAutoClose() === 'outsideClick' &&
  2688. dropdownElement && dropdownElement[0].contains(evt.target)) {
  2689. return;
  2690. }
  2691. openScope.isOpen = false;
  2692. if (!$rootScope.$$phase) {
  2693. openScope.$apply();
  2694. }
  2695. };
  2696. var keybindFilter = function(evt) {
  2697. if (evt.which === 27) {
  2698. evt.stopPropagation();
  2699. openScope.focusToggleElement();
  2700. closeDropdown();
  2701. } else if (openScope.isKeynavEnabled() && [38, 40].indexOf(evt.which) !== -1 && openScope.isOpen) {
  2702. evt.preventDefault();
  2703. evt.stopPropagation();
  2704. openScope.focusDropdownEntry(evt.which);
  2705. }
  2706. };
  2707. }])
  2708. .controller('UibDropdownController', ['$scope', '$element', '$attrs', '$parse', 'uibDropdownConfig', 'uibDropdownService', '$animate', '$uibPosition', '$document', '$compile', '$templateRequest', function($scope, $element, $attrs, $parse, dropdownConfig, uibDropdownService, $animate, $position, $document, $compile, $templateRequest) {
  2709. var self = this,
  2710. scope = $scope.$new(), // create a child scope so we are not polluting original one
  2711. templateScope,
  2712. appendToOpenClass = dropdownConfig.appendToOpenClass,
  2713. openClass = dropdownConfig.openClass,
  2714. getIsOpen,
  2715. setIsOpen = angular.noop,
  2716. toggleInvoker = $attrs.onToggle ? $parse($attrs.onToggle) : angular.noop,
  2717. appendToBody = false,
  2718. appendTo = null,
  2719. keynavEnabled = false,
  2720. selectedOption = null,
  2721. body = $document.find('body');
  2722. $element.addClass('dropdown');
  2723. this.init = function() {
  2724. if ($attrs.isOpen) {
  2725. getIsOpen = $parse($attrs.isOpen);
  2726. setIsOpen = getIsOpen.assign;
  2727. $scope.$watch(getIsOpen, function(value) {
  2728. scope.isOpen = !!value;
  2729. });
  2730. }
  2731. if (angular.isDefined($attrs.dropdownAppendTo)) {
  2732. var appendToEl = $parse($attrs.dropdownAppendTo)(scope);
  2733. if (appendToEl) {
  2734. appendTo = angular.element(appendToEl);
  2735. }
  2736. }
  2737. appendToBody = angular.isDefined($attrs.dropdownAppendToBody);
  2738. keynavEnabled = angular.isDefined($attrs.keyboardNav);
  2739. if (appendToBody && !appendTo) {
  2740. appendTo = body;
  2741. }
  2742. if (appendTo && self.dropdownMenu) {
  2743. appendTo.append(self.dropdownMenu);
  2744. $element.on('$destroy', function handleDestroyEvent() {
  2745. self.dropdownMenu.remove();
  2746. });
  2747. }
  2748. };
  2749. this.toggle = function(open) {
  2750. scope.isOpen = arguments.length ? !!open : !scope.isOpen;
  2751. if (angular.isFunction(setIsOpen)) {
  2752. setIsOpen(scope, scope.isOpen);
  2753. }
  2754. return scope.isOpen;
  2755. };
  2756. // Allow other directives to watch status
  2757. this.isOpen = function() {
  2758. return scope.isOpen;
  2759. };
  2760. scope.getToggleElement = function() {
  2761. return self.toggleElement;
  2762. };
  2763. scope.getAutoClose = function() {
  2764. return $attrs.autoClose || 'always'; //or 'outsideClick' or 'disabled'
  2765. };
  2766. scope.getElement = function() {
  2767. return $element;
  2768. };
  2769. scope.isKeynavEnabled = function() {
  2770. return keynavEnabled;
  2771. };
  2772. scope.focusDropdownEntry = function(keyCode) {
  2773. var elems = self.dropdownMenu ? //If append to body is used.
  2774. angular.element(self.dropdownMenu).find('a') :
  2775. $element.find('ul').eq(0).find('a');
  2776. switch (keyCode) {
  2777. case 40: {
  2778. if (!angular.isNumber(self.selectedOption)) {
  2779. self.selectedOption = 0;
  2780. } else {
  2781. self.selectedOption = self.selectedOption === elems.length - 1 ?
  2782. self.selectedOption :
  2783. self.selectedOption + 1;
  2784. }
  2785. break;
  2786. }
  2787. case 38: {
  2788. if (!angular.isNumber(self.selectedOption)) {
  2789. self.selectedOption = elems.length - 1;
  2790. } else {
  2791. self.selectedOption = self.selectedOption === 0 ?
  2792. 0 : self.selectedOption - 1;
  2793. }
  2794. break;
  2795. }
  2796. }
  2797. elems[self.selectedOption].focus();
  2798. };
  2799. scope.getDropdownElement = function() {
  2800. return self.dropdownMenu;
  2801. };
  2802. scope.focusToggleElement = function() {
  2803. if (self.toggleElement) {
  2804. self.toggleElement[0].focus();
  2805. }
  2806. };
  2807. scope.$watch('isOpen', function(isOpen, wasOpen) {
  2808. if (appendTo && self.dropdownMenu) {
  2809. var pos = $position.positionElements($element, self.dropdownMenu, 'bottom-left', true),
  2810. css,
  2811. rightalign,
  2812. scrollbarWidth;
  2813. css = {
  2814. top: pos.top + 'px',
  2815. display: isOpen ? 'block' : 'none'
  2816. };
  2817. rightalign = self.dropdownMenu.hasClass('dropdown-menu-right');
  2818. if (!rightalign) {
  2819. css.left = pos.left + 'px';
  2820. css.right = 'auto';
  2821. } else {
  2822. css.left = 'auto';
  2823. scrollbarWidth = $position.scrollbarWidth(true);
  2824. css.right = window.innerWidth - scrollbarWidth -
  2825. (pos.left + $element.prop('offsetWidth')) + 'px';
  2826. }
  2827. // Need to adjust our positioning to be relative to the appendTo container
  2828. // if it's not the body element
  2829. if (!appendToBody) {
  2830. var appendOffset = $position.offset(appendTo);
  2831. css.top = pos.top - appendOffset.top + 'px';
  2832. if (!rightalign) {
  2833. css.left = pos.left - appendOffset.left + 'px';
  2834. } else {
  2835. css.right = window.innerWidth -
  2836. (pos.left - appendOffset.left + $element.prop('offsetWidth')) + 'px';
  2837. }
  2838. }
  2839. self.dropdownMenu.css(css);
  2840. }
  2841. var openContainer = appendTo ? appendTo : $element;
  2842. var hasOpenClass = openContainer.hasClass(appendTo ? appendToOpenClass : openClass);
  2843. if (hasOpenClass === !isOpen) {
  2844. $animate[isOpen ? 'addClass' : 'removeClass'](openContainer, appendTo ? appendToOpenClass : openClass).then(function() {
  2845. if (angular.isDefined(isOpen) && isOpen !== wasOpen) {
  2846. toggleInvoker($scope, { open: !!isOpen });
  2847. }
  2848. });
  2849. }
  2850. if (isOpen) {
  2851. if (self.dropdownMenuTemplateUrl) {
  2852. $templateRequest(self.dropdownMenuTemplateUrl).then(function(tplContent) {
  2853. templateScope = scope.$new();
  2854. $compile(tplContent.trim())(templateScope, function(dropdownElement) {
  2855. var newEl = dropdownElement;
  2856. self.dropdownMenu.replaceWith(newEl);
  2857. self.dropdownMenu = newEl;
  2858. });
  2859. });
  2860. }
  2861. scope.focusToggleElement();
  2862. uibDropdownService.open(scope, $element);
  2863. } else {
  2864. if (self.dropdownMenuTemplateUrl) {
  2865. if (templateScope) {
  2866. templateScope.$destroy();
  2867. }
  2868. var newEl = angular.element('<ul class="dropdown-menu"></ul>');
  2869. self.dropdownMenu.replaceWith(newEl);
  2870. self.dropdownMenu = newEl;
  2871. }
  2872. uibDropdownService.close(scope, $element);
  2873. self.selectedOption = null;
  2874. }
  2875. if (angular.isFunction(setIsOpen)) {
  2876. setIsOpen($scope, isOpen);
  2877. }
  2878. });
  2879. }])
  2880. .directive('uibDropdown', function() {
  2881. return {
  2882. controller: 'UibDropdownController',
  2883. link: function(scope, element, attrs, dropdownCtrl) {
  2884. dropdownCtrl.init();
  2885. }
  2886. };
  2887. })
  2888. .directive('uibDropdownMenu', function() {
  2889. return {
  2890. restrict: 'A',
  2891. require: '?^uibDropdown',
  2892. link: function(scope, element, attrs, dropdownCtrl) {
  2893. if (!dropdownCtrl || angular.isDefined(attrs.dropdownNested)) {
  2894. return;
  2895. }
  2896. element.addClass('dropdown-menu');
  2897. var tplUrl = attrs.templateUrl;
  2898. if (tplUrl) {
  2899. dropdownCtrl.dropdownMenuTemplateUrl = tplUrl;
  2900. }
  2901. if (!dropdownCtrl.dropdownMenu) {
  2902. dropdownCtrl.dropdownMenu = element;
  2903. }
  2904. }
  2905. };
  2906. })
  2907. .directive('uibDropdownToggle', function() {
  2908. return {
  2909. require: '?^uibDropdown',
  2910. link: function(scope, element, attrs, dropdownCtrl) {
  2911. if (!dropdownCtrl) {
  2912. return;
  2913. }
  2914. element.addClass('dropdown-toggle');
  2915. dropdownCtrl.toggleElement = element;
  2916. var toggleDropdown = function(event) {
  2917. event.preventDefault();
  2918. if (!element.hasClass('disabled') && !attrs.disabled) {
  2919. scope.$apply(function() {
  2920. dropdownCtrl.toggle();
  2921. });
  2922. }
  2923. };
  2924. element.bind('click', toggleDropdown);
  2925. // WAI-ARIA
  2926. element.attr({ 'aria-haspopup': true, 'aria-expanded': false });
  2927. scope.$watch(dropdownCtrl.isOpen, function(isOpen) {
  2928. element.attr('aria-expanded', !!isOpen);
  2929. });
  2930. scope.$on('$destroy', function() {
  2931. element.unbind('click', toggleDropdown);
  2932. });
  2933. }
  2934. };
  2935. });
  2936. angular.module('ui.bootstrap.stackedMap', [])
  2937. /**
  2938. * A helper, internal data structure that acts as a map but also allows getting / removing
  2939. * elements in the LIFO order
  2940. */
  2941. .factory('$$stackedMap', function() {
  2942. return {
  2943. createNew: function() {
  2944. var stack = [];
  2945. return {
  2946. add: function(key, value) {
  2947. stack.push({
  2948. key: key,
  2949. value: value
  2950. });
  2951. },
  2952. get: function(key) {
  2953. for (var i = 0; i < stack.length; i++) {
  2954. if (key === stack[i].key) {
  2955. return stack[i];
  2956. }
  2957. }
  2958. },
  2959. keys: function() {
  2960. var keys = [];
  2961. for (var i = 0; i < stack.length; i++) {
  2962. keys.push(stack[i].key);
  2963. }
  2964. return keys;
  2965. },
  2966. top: function() {
  2967. return stack[stack.length - 1];
  2968. },
  2969. remove: function(key) {
  2970. var idx = -1;
  2971. for (var i = 0; i < stack.length; i++) {
  2972. if (key === stack[i].key) {
  2973. idx = i;
  2974. break;
  2975. }
  2976. }
  2977. return stack.splice(idx, 1)[0];
  2978. },
  2979. removeTop: function() {
  2980. return stack.splice(stack.length - 1, 1)[0];
  2981. },
  2982. length: function() {
  2983. return stack.length;
  2984. }
  2985. };
  2986. }
  2987. };
  2988. });
  2989. angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap', 'ui.bootstrap.position'])
  2990. /**
  2991. * A helper, internal data structure that stores all references attached to key
  2992. */
  2993. .factory('$$multiMap', function() {
  2994. return {
  2995. createNew: function() {
  2996. var map = {};
  2997. return {
  2998. entries: function() {
  2999. return Object.keys(map).map(function(key) {
  3000. return {
  3001. key: key,
  3002. value: map[key]
  3003. };
  3004. });
  3005. },
  3006. get: function(key) {
  3007. return map[key];
  3008. },
  3009. hasKey: function(key) {
  3010. return !!map[key];
  3011. },
  3012. keys: function() {
  3013. return Object.keys(map);
  3014. },
  3015. put: function(key, value) {
  3016. if (!map[key]) {
  3017. map[key] = [];
  3018. }
  3019. map[key].push(value);
  3020. },
  3021. remove: function(key, value) {
  3022. var values = map[key];
  3023. if (!values) {
  3024. return;
  3025. }
  3026. var idx = values.indexOf(value);
  3027. if (idx !== -1) {
  3028. values.splice(idx, 1);
  3029. }
  3030. if (!values.length) {
  3031. delete map[key];
  3032. }
  3033. }
  3034. };
  3035. }
  3036. };
  3037. })
  3038. /**
  3039. * Pluggable resolve mechanism for the modal resolve resolution
  3040. * Supports UI Router's $resolve service
  3041. */
  3042. .provider('$uibResolve', function() {
  3043. var resolve = this;
  3044. this.resolver = null;
  3045. this.setResolver = function(resolver) {
  3046. this.resolver = resolver;
  3047. };
  3048. this.$get = ['$injector', '$q', function($injector, $q) {
  3049. var resolver = resolve.resolver ? $injector.get(resolve.resolver) : null;
  3050. return {
  3051. resolve: function(invocables, locals, parent, self) {
  3052. if (resolver) {
  3053. return resolver.resolve(invocables, locals, parent, self);
  3054. }
  3055. var promises = [];
  3056. angular.forEach(invocables, function(value) {
  3057. if (angular.isFunction(value) || angular.isArray(value)) {
  3058. promises.push($q.resolve($injector.invoke(value)));
  3059. } else if (angular.isString(value)) {
  3060. promises.push($q.resolve($injector.get(value)));
  3061. } else {
  3062. promises.push($q.resolve(value));
  3063. }
  3064. });
  3065. return $q.all(promises).then(function(resolves) {
  3066. var resolveObj = {};
  3067. var resolveIter = 0;
  3068. angular.forEach(invocables, function(value, key) {
  3069. resolveObj[key] = resolves[resolveIter++];
  3070. });
  3071. return resolveObj;
  3072. });
  3073. }
  3074. };
  3075. }];
  3076. })
  3077. /**
  3078. * A helper directive for the $modal service. It creates a backdrop element.
  3079. */
  3080. .directive('uibModalBackdrop', ['$animate', '$injector', '$uibModalStack',
  3081. function($animate, $injector, $modalStack) {
  3082. return {
  3083. replace: true,
  3084. templateUrl: 'uib/template/modal/backdrop.html',
  3085. compile: function(tElement, tAttrs) {
  3086. tElement.addClass(tAttrs.backdropClass);
  3087. return linkFn;
  3088. }
  3089. };
  3090. function linkFn(scope, element, attrs) {
  3091. if (attrs.modalInClass) {
  3092. $animate.addClass(element, attrs.modalInClass);
  3093. scope.$on($modalStack.NOW_CLOSING_EVENT, function(e, setIsAsync) {
  3094. var done = setIsAsync();
  3095. if (scope.modalOptions.animation) {
  3096. $animate.removeClass(element, attrs.modalInClass).then(done);
  3097. } else {
  3098. done();
  3099. }
  3100. });
  3101. }
  3102. }
  3103. }])
  3104. .directive('uibModalWindow', ['$uibModalStack', '$q', '$animateCss', '$document',
  3105. function($modalStack, $q, $animateCss, $document) {
  3106. return {
  3107. scope: {
  3108. index: '@'
  3109. },
  3110. replace: true,
  3111. transclude: true,
  3112. templateUrl: function(tElement, tAttrs) {
  3113. return tAttrs.templateUrl || 'uib/template/modal/window.html';
  3114. },
  3115. link: function(scope, element, attrs) {
  3116. element.addClass(attrs.windowClass || '');
  3117. element.addClass(attrs.windowTopClass || '');
  3118. scope.size = attrs.size;
  3119. scope.close = function(evt) {
  3120. var modal = $modalStack.getTop();
  3121. if (modal && modal.value.backdrop &&
  3122. modal.value.backdrop !== 'static' &&
  3123. evt.target === evt.currentTarget) {
  3124. evt.preventDefault();
  3125. evt.stopPropagation();
  3126. $modalStack.dismiss(modal.key, 'backdrop click');
  3127. }
  3128. };
  3129. // moved from template to fix issue #2280
  3130. element.on('click', scope.close);
  3131. // This property is only added to the scope for the purpose of detecting when this directive is rendered.
  3132. // We can detect that by using this property in the template associated with this directive and then use
  3133. // {@link Attribute#$observe} on it. For more details please see {@link TableColumnResize}.
  3134. scope.$isRendered = true;
  3135. // Deferred object that will be resolved when this modal is render.
  3136. var modalRenderDeferObj = $q.defer();
  3137. // Observe function will be called on next digest cycle after compilation, ensuring that the DOM is ready.
  3138. // In order to use this way of finding whether DOM is ready, we need to observe a scope property used in modal's template.
  3139. attrs.$observe('modalRender', function(value) {
  3140. if (value === 'true') {
  3141. modalRenderDeferObj.resolve();
  3142. }
  3143. });
  3144. modalRenderDeferObj.promise.then(function() {
  3145. var animationPromise = null;
  3146. if (attrs.modalInClass) {
  3147. animationPromise = $animateCss(element, {
  3148. addClass: attrs.modalInClass
  3149. }).start();
  3150. scope.$on($modalStack.NOW_CLOSING_EVENT, function(e, setIsAsync) {
  3151. var done = setIsAsync();
  3152. $animateCss(element, {
  3153. removeClass: attrs.modalInClass
  3154. }).start().then(done);
  3155. });
  3156. }
  3157. $q.when(animationPromise).then(function() {
  3158. // Notify {@link $modalStack} that modal is rendered.
  3159. var modal = $modalStack.getTop();
  3160. if (modal) {
  3161. $modalStack.modalRendered(modal.key);
  3162. }
  3163. /**
  3164. * If something within the freshly-opened modal already has focus (perhaps via a
  3165. * directive that causes focus). then no need to try and focus anything.
  3166. */
  3167. if (!($document[0].activeElement && element[0].contains($document[0].activeElement))) {
  3168. var inputWithAutofocus = element[0].querySelector('[autofocus]');
  3169. /**
  3170. * Auto-focusing of a freshly-opened modal element causes any child elements
  3171. * with the autofocus attribute to lose focus. This is an issue on touch
  3172. * based devices which will show and then hide the onscreen keyboard.
  3173. * Attempts to refocus the autofocus element via JavaScript will not reopen
  3174. * the onscreen keyboard. Fixed by updated the focusing logic to only autofocus
  3175. * the modal element if the modal does not contain an autofocus element.
  3176. */
  3177. if (inputWithAutofocus) {
  3178. inputWithAutofocus.focus();
  3179. } else {
  3180. element[0].focus();
  3181. }
  3182. }
  3183. });
  3184. });
  3185. }
  3186. };
  3187. }])
  3188. .directive('uibModalAnimationClass', function() {
  3189. return {
  3190. compile: function(tElement, tAttrs) {
  3191. if (tAttrs.modalAnimation) {
  3192. tElement.addClass(tAttrs.uibModalAnimationClass);
  3193. }
  3194. }
  3195. };
  3196. })
  3197. .directive('uibModalTransclude', function() {
  3198. return {
  3199. link: function(scope, element, attrs, controller, transclude) {
  3200. transclude(scope.$parent, function(clone) {
  3201. element.empty();
  3202. element.append(clone);
  3203. });
  3204. }
  3205. };
  3206. })
  3207. .factory('$uibModalStack', ['$animate', '$animateCss', '$document',
  3208. '$compile', '$rootScope', '$q', '$$multiMap', '$$stackedMap', '$uibPosition',
  3209. function($animate, $animateCss, $document, $compile, $rootScope, $q, $$multiMap, $$stackedMap, $uibPosition) {
  3210. var OPENED_MODAL_CLASS = 'modal-open';
  3211. var backdropDomEl, backdropScope;
  3212. var openedWindows = $$stackedMap.createNew();
  3213. var openedClasses = $$multiMap.createNew();
  3214. var $modalStack = {
  3215. NOW_CLOSING_EVENT: 'modal.stack.now-closing'
  3216. };
  3217. var topModalIndex = 0;
  3218. var previousTopOpenedModal = null;
  3219. //Modal focus behavior
  3220. var tabableSelector = 'a[href], area[href], input:not([disabled]), ' +
  3221. 'button:not([disabled]),select:not([disabled]), textarea:not([disabled]), ' +
  3222. 'iframe, object, embed, *[tabindex], *[contenteditable=true]';
  3223. var scrollbarPadding;
  3224. function isVisible(element) {
  3225. return !!(element.offsetWidth ||
  3226. element.offsetHeight ||
  3227. element.getClientRects().length);
  3228. }
  3229. function backdropIndex() {
  3230. var topBackdropIndex = -1;
  3231. var opened = openedWindows.keys();
  3232. for (var i = 0; i < opened.length; i++) {
  3233. if (openedWindows.get(opened[i]).value.backdrop) {
  3234. topBackdropIndex = i;
  3235. }
  3236. }
  3237. // If any backdrop exist, ensure that it's index is always
  3238. // right below the top modal
  3239. if (topBackdropIndex > -1 && topBackdropIndex < topModalIndex) {
  3240. topBackdropIndex = topModalIndex;
  3241. }
  3242. return topBackdropIndex;
  3243. }
  3244. $rootScope.$watch(backdropIndex, function(newBackdropIndex) {
  3245. if (backdropScope) {
  3246. backdropScope.index = newBackdropIndex;
  3247. }
  3248. });
  3249. function removeModalWindow(modalInstance, elementToReceiveFocus) {
  3250. var modalWindow = openedWindows.get(modalInstance).value;
  3251. var appendToElement = modalWindow.appendTo;
  3252. //clean up the stack
  3253. openedWindows.remove(modalInstance);
  3254. previousTopOpenedModal = openedWindows.top();
  3255. if (previousTopOpenedModal) {
  3256. topModalIndex = parseInt(previousTopOpenedModal.value.modalDomEl.attr('index'), 10);
  3257. }
  3258. removeAfterAnimate(modalWindow.modalDomEl, modalWindow.modalScope, function() {
  3259. var modalBodyClass = modalWindow.openedClass || OPENED_MODAL_CLASS;
  3260. openedClasses.remove(modalBodyClass, modalInstance);
  3261. var areAnyOpen = openedClasses.hasKey(modalBodyClass);
  3262. appendToElement.toggleClass(modalBodyClass, areAnyOpen);
  3263. if (!areAnyOpen && scrollbarPadding && scrollbarPadding.heightOverflow && scrollbarPadding.scrollbarWidth) {
  3264. if (scrollbarPadding.originalRight) {
  3265. appendToElement.css({paddingRight: scrollbarPadding.originalRight + 'px'});
  3266. } else {
  3267. appendToElement.css({paddingRight: ''});
  3268. }
  3269. scrollbarPadding = null;
  3270. }
  3271. toggleTopWindowClass(true);
  3272. }, modalWindow.closedDeferred);
  3273. checkRemoveBackdrop();
  3274. //move focus to specified element if available, or else to body
  3275. if (elementToReceiveFocus && elementToReceiveFocus.focus) {
  3276. elementToReceiveFocus.focus();
  3277. } else if (appendToElement.focus) {
  3278. appendToElement.focus();
  3279. }
  3280. }
  3281. // Add or remove "windowTopClass" from the top window in the stack
  3282. function toggleTopWindowClass(toggleSwitch) {
  3283. var modalWindow;
  3284. if (openedWindows.length() > 0) {
  3285. modalWindow = openedWindows.top().value;
  3286. modalWindow.modalDomEl.toggleClass(modalWindow.windowTopClass || '', toggleSwitch);
  3287. }
  3288. }
  3289. function checkRemoveBackdrop() {
  3290. //remove backdrop if no longer needed
  3291. if (backdropDomEl && backdropIndex() === -1) {
  3292. var backdropScopeRef = backdropScope;
  3293. removeAfterAnimate(backdropDomEl, backdropScope, function() {
  3294. backdropScopeRef = null;
  3295. });
  3296. backdropDomEl = undefined;
  3297. backdropScope = undefined;
  3298. }
  3299. }
  3300. function removeAfterAnimate(domEl, scope, done, closedDeferred) {
  3301. var asyncDeferred;
  3302. var asyncPromise = null;
  3303. var setIsAsync = function() {
  3304. if (!asyncDeferred) {
  3305. asyncDeferred = $q.defer();
  3306. asyncPromise = asyncDeferred.promise;
  3307. }
  3308. return function asyncDone() {
  3309. asyncDeferred.resolve();
  3310. };
  3311. };
  3312. scope.$broadcast($modalStack.NOW_CLOSING_EVENT, setIsAsync);
  3313. // Note that it's intentional that asyncPromise might be null.
  3314. // That's when setIsAsync has not been called during the
  3315. // NOW_CLOSING_EVENT broadcast.
  3316. return $q.when(asyncPromise).then(afterAnimating);
  3317. function afterAnimating() {
  3318. if (afterAnimating.done) {
  3319. return;
  3320. }
  3321. afterAnimating.done = true;
  3322. $animate.leave(domEl).then(function() {
  3323. domEl.remove();
  3324. if (closedDeferred) {
  3325. closedDeferred.resolve();
  3326. }
  3327. });
  3328. scope.$destroy();
  3329. if (done) {
  3330. done();
  3331. }
  3332. }
  3333. }
  3334. $document.on('keydown', keydownListener);
  3335. $rootScope.$on('$destroy', function() {
  3336. $document.off('keydown', keydownListener);
  3337. });
  3338. function keydownListener(evt) {
  3339. if (evt.isDefaultPrevented()) {
  3340. return evt;
  3341. }
  3342. var modal = openedWindows.top();
  3343. if (modal) {
  3344. switch (evt.which) {
  3345. case 27: {
  3346. if (modal.value.keyboard) {
  3347. evt.preventDefault();
  3348. $rootScope.$apply(function() {
  3349. $modalStack.dismiss(modal.key, 'escape key press');
  3350. });
  3351. }
  3352. break;
  3353. }
  3354. case 9: {
  3355. var list = $modalStack.loadFocusElementList(modal);
  3356. var focusChanged = false;
  3357. if (evt.shiftKey) {
  3358. if ($modalStack.isFocusInFirstItem(evt, list) || $modalStack.isModalFocused(evt, modal)) {
  3359. focusChanged = $modalStack.focusLastFocusableElement(list);
  3360. }
  3361. } else {
  3362. if ($modalStack.isFocusInLastItem(evt, list)) {
  3363. focusChanged = $modalStack.focusFirstFocusableElement(list);
  3364. }
  3365. }
  3366. if (focusChanged) {
  3367. evt.preventDefault();
  3368. evt.stopPropagation();
  3369. }
  3370. break;
  3371. }
  3372. }
  3373. }
  3374. }
  3375. $modalStack.open = function(modalInstance, modal) {
  3376. var modalOpener = $document[0].activeElement,
  3377. modalBodyClass = modal.openedClass || OPENED_MODAL_CLASS;
  3378. toggleTopWindowClass(false);
  3379. // Store the current top first, to determine what index we ought to use
  3380. // for the current top modal
  3381. previousTopOpenedModal = openedWindows.top();
  3382. openedWindows.add(modalInstance, {
  3383. deferred: modal.deferred,
  3384. renderDeferred: modal.renderDeferred,
  3385. closedDeferred: modal.closedDeferred,
  3386. modalScope: modal.scope,
  3387. backdrop: modal.backdrop,
  3388. keyboard: modal.keyboard,
  3389. openedClass: modal.openedClass,
  3390. windowTopClass: modal.windowTopClass,
  3391. animation: modal.animation,
  3392. appendTo: modal.appendTo
  3393. });
  3394. openedClasses.put(modalBodyClass, modalInstance);
  3395. var appendToElement = modal.appendTo,
  3396. currBackdropIndex = backdropIndex();
  3397. if (!appendToElement.length) {
  3398. throw new Error('appendTo element not found. Make sure that the element passed is in DOM.');
  3399. }
  3400. if (currBackdropIndex >= 0 && !backdropDomEl) {
  3401. backdropScope = $rootScope.$new(true);
  3402. backdropScope.modalOptions = modal;
  3403. backdropScope.index = currBackdropIndex;
  3404. backdropDomEl = angular.element('<div uib-modal-backdrop="modal-backdrop"></div>');
  3405. backdropDomEl.attr('backdrop-class', modal.backdropClass);
  3406. if (modal.animation) {
  3407. backdropDomEl.attr('modal-animation', 'true');
  3408. }
  3409. $compile(backdropDomEl)(backdropScope);
  3410. $animate.enter(backdropDomEl, appendToElement);
  3411. scrollbarPadding = $uibPosition.scrollbarPadding(appendToElement);
  3412. if (scrollbarPadding.heightOverflow && scrollbarPadding.scrollbarWidth) {
  3413. appendToElement.css({paddingRight: scrollbarPadding.right + 'px'});
  3414. }
  3415. }
  3416. // Set the top modal index based on the index of the previous top modal
  3417. topModalIndex = previousTopOpenedModal ? parseInt(previousTopOpenedModal.value.modalDomEl.attr('index'), 10) + 1 : 0;
  3418. var angularDomEl = angular.element('<div uib-modal-window="modal-window"></div>');
  3419. angularDomEl.attr({
  3420. 'template-url': modal.windowTemplateUrl,
  3421. 'window-class': modal.windowClass,
  3422. 'window-top-class': modal.windowTopClass,
  3423. 'size': modal.size,
  3424. 'index': topModalIndex,
  3425. 'animate': 'animate'
  3426. }).html(modal.content);
  3427. if (modal.animation) {
  3428. angularDomEl.attr('modal-animation', 'true');
  3429. }
  3430. appendToElement.addClass(modalBodyClass);
  3431. $animate.enter($compile(angularDomEl)(modal.scope), appendToElement);
  3432. openedWindows.top().value.modalDomEl = angularDomEl;
  3433. openedWindows.top().value.modalOpener = modalOpener;
  3434. };
  3435. function broadcastClosing(modalWindow, resultOrReason, closing) {
  3436. return !modalWindow.value.modalScope.$broadcast('modal.closing', resultOrReason, closing).defaultPrevented;
  3437. }
  3438. $modalStack.close = function(modalInstance, result) {
  3439. var modalWindow = openedWindows.get(modalInstance);
  3440. if (modalWindow && broadcastClosing(modalWindow, result, true)) {
  3441. modalWindow.value.modalScope.$$uibDestructionScheduled = true;
  3442. modalWindow.value.deferred.resolve(result);
  3443. removeModalWindow(modalInstance, modalWindow.value.modalOpener);
  3444. return true;
  3445. }
  3446. return !modalWindow;
  3447. };
  3448. $modalStack.dismiss = function(modalInstance, reason) {
  3449. var modalWindow = openedWindows.get(modalInstance);
  3450. if (modalWindow && broadcastClosing(modalWindow, reason, false)) {
  3451. modalWindow.value.modalScope.$$uibDestructionScheduled = true;
  3452. modalWindow.value.deferred.reject(reason);
  3453. removeModalWindow(modalInstance, modalWindow.value.modalOpener);
  3454. return true;
  3455. }
  3456. return !modalWindow;
  3457. };
  3458. $modalStack.dismissAll = function(reason) {
  3459. var topModal = this.getTop();
  3460. while (topModal && this.dismiss(topModal.key, reason)) {
  3461. topModal = this.getTop();
  3462. }
  3463. };
  3464. $modalStack.getTop = function() {
  3465. return openedWindows.top();
  3466. };
  3467. $modalStack.modalRendered = function(modalInstance) {
  3468. var modalWindow = openedWindows.get(modalInstance);
  3469. if (modalWindow) {
  3470. modalWindow.value.renderDeferred.resolve();
  3471. }
  3472. };
  3473. $modalStack.focusFirstFocusableElement = function(list) {
  3474. if (list.length > 0) {
  3475. list[0].focus();
  3476. return true;
  3477. }
  3478. return false;
  3479. };
  3480. $modalStack.focusLastFocusableElement = function(list) {
  3481. if (list.length > 0) {
  3482. list[list.length - 1].focus();
  3483. return true;
  3484. }
  3485. return false;
  3486. };
  3487. $modalStack.isModalFocused = function(evt, modalWindow) {
  3488. if (evt && modalWindow) {
  3489. var modalDomEl = modalWindow.value.modalDomEl;
  3490. if (modalDomEl && modalDomEl.length) {
  3491. return (evt.target || evt.srcElement) === modalDomEl[0];
  3492. }
  3493. }
  3494. return false;
  3495. };
  3496. $modalStack.isFocusInFirstItem = function(evt, list) {
  3497. if (list.length > 0) {
  3498. return (evt.target || evt.srcElement) === list[0];
  3499. }
  3500. return false;
  3501. };
  3502. $modalStack.isFocusInLastItem = function(evt, list) {
  3503. if (list.length > 0) {
  3504. return (evt.target || evt.srcElement) === list[list.length - 1];
  3505. }
  3506. return false;
  3507. };
  3508. $modalStack.loadFocusElementList = function(modalWindow) {
  3509. if (modalWindow) {
  3510. var modalDomE1 = modalWindow.value.modalDomEl;
  3511. if (modalDomE1 && modalDomE1.length) {
  3512. var elements = modalDomE1[0].querySelectorAll(tabableSelector);
  3513. return elements ?
  3514. Array.prototype.filter.call(elements, function(element) {
  3515. return isVisible(element);
  3516. }) : elements;
  3517. }
  3518. }
  3519. };
  3520. return $modalStack;
  3521. }])
  3522. .provider('$uibModal', function() {
  3523. var $modalProvider = {
  3524. options: {
  3525. animation: true,
  3526. backdrop: true, //can also be false or 'static'
  3527. keyboard: true
  3528. },
  3529. $get: ['$rootScope', '$q', '$document', '$templateRequest', '$controller', '$uibResolve', '$uibModalStack',
  3530. function ($rootScope, $q, $document, $templateRequest, $controller, $uibResolve, $modalStack) {
  3531. var $modal = {};
  3532. function getTemplatePromise(options) {
  3533. return options.template ? $q.when(options.template) :
  3534. $templateRequest(angular.isFunction(options.templateUrl) ?
  3535. options.templateUrl() : options.templateUrl);
  3536. }
  3537. var promiseChain = null;
  3538. $modal.getPromiseChain = function() {
  3539. return promiseChain;
  3540. };
  3541. $modal.open = function(modalOptions) {
  3542. var modalResultDeferred = $q.defer();
  3543. var modalOpenedDeferred = $q.defer();
  3544. var modalClosedDeferred = $q.defer();
  3545. var modalRenderDeferred = $q.defer();
  3546. //prepare an instance of a modal to be injected into controllers and returned to a caller
  3547. var modalInstance = {
  3548. result: modalResultDeferred.promise,
  3549. opened: modalOpenedDeferred.promise,
  3550. closed: modalClosedDeferred.promise,
  3551. rendered: modalRenderDeferred.promise,
  3552. close: function (result) {
  3553. return $modalStack.close(modalInstance, result);
  3554. },
  3555. dismiss: function (reason) {
  3556. return $modalStack.dismiss(modalInstance, reason);
  3557. }
  3558. };
  3559. //merge and clean up options
  3560. modalOptions = angular.extend({}, $modalProvider.options, modalOptions);
  3561. modalOptions.resolve = modalOptions.resolve || {};
  3562. modalOptions.appendTo = modalOptions.appendTo || $document.find('body').eq(0);
  3563. //verify options
  3564. if (!modalOptions.template && !modalOptions.templateUrl) {
  3565. throw new Error('One of template or templateUrl options is required.');
  3566. }
  3567. var templateAndResolvePromise =
  3568. $q.all([getTemplatePromise(modalOptions), $uibResolve.resolve(modalOptions.resolve, {}, null, null)]);
  3569. function resolveWithTemplate() {
  3570. return templateAndResolvePromise;
  3571. }
  3572. // Wait for the resolution of the existing promise chain.
  3573. // Then switch to our own combined promise dependency (regardless of how the previous modal fared).
  3574. // Then add to $modalStack and resolve opened.
  3575. // Finally clean up the chain variable if no subsequent modal has overwritten it.
  3576. var samePromise;
  3577. samePromise = promiseChain = $q.all([promiseChain])
  3578. .then(resolveWithTemplate, resolveWithTemplate)
  3579. .then(function resolveSuccess(tplAndVars) {
  3580. var providedScope = modalOptions.scope || $rootScope;
  3581. var modalScope = providedScope.$new();
  3582. modalScope.$close = modalInstance.close;
  3583. modalScope.$dismiss = modalInstance.dismiss;
  3584. modalScope.$on('$destroy', function() {
  3585. if (!modalScope.$$uibDestructionScheduled) {
  3586. modalScope.$dismiss('$uibUnscheduledDestruction');
  3587. }
  3588. });
  3589. var ctrlInstance, ctrlInstantiate, ctrlLocals = {};
  3590. //controllers
  3591. if (modalOptions.controller) {
  3592. ctrlLocals.$scope = modalScope;
  3593. ctrlLocals.$scope.$resolve = {};
  3594. ctrlLocals.$uibModalInstance = modalInstance;
  3595. angular.forEach(tplAndVars[1], function(value, key) {
  3596. ctrlLocals[key] = value;
  3597. ctrlLocals.$scope.$resolve[key] = value;
  3598. });
  3599. // the third param will make the controller instantiate later,private api
  3600. // @see https://github.com/angular/angular.js/blob/master/src/ng/controller.js#L126
  3601. ctrlInstantiate = $controller(modalOptions.controller, ctrlLocals, true, modalOptions.controllerAs);
  3602. if (modalOptions.controllerAs && modalOptions.bindToController) {
  3603. ctrlInstance = ctrlInstantiate.instance;
  3604. ctrlInstance.$close = modalScope.$close;
  3605. ctrlInstance.$dismiss = modalScope.$dismiss;
  3606. angular.extend(ctrlInstance, {
  3607. $resolve: ctrlLocals.$scope.$resolve
  3608. }, providedScope);
  3609. }
  3610. ctrlInstance = ctrlInstantiate();
  3611. if (angular.isFunction(ctrlInstance.$onInit)) {
  3612. ctrlInstance.$onInit();
  3613. }
  3614. }
  3615. $modalStack.open(modalInstance, {
  3616. scope: modalScope,
  3617. deferred: modalResultDeferred,
  3618. renderDeferred: modalRenderDeferred,
  3619. closedDeferred: modalClosedDeferred,
  3620. content: tplAndVars[0],
  3621. animation: modalOptions.animation,
  3622. backdrop: modalOptions.backdrop,
  3623. keyboard: modalOptions.keyboard,
  3624. backdropClass: modalOptions.backdropClass,
  3625. windowTopClass: modalOptions.windowTopClass,
  3626. windowClass: modalOptions.windowClass,
  3627. windowTemplateUrl: modalOptions.windowTemplateUrl,
  3628. size: modalOptions.size,
  3629. openedClass: modalOptions.openedClass,
  3630. appendTo: modalOptions.appendTo
  3631. });
  3632. modalOpenedDeferred.resolve(true);
  3633. }, function resolveError(reason) {
  3634. modalOpenedDeferred.reject(reason);
  3635. modalResultDeferred.reject(reason);
  3636. })['finally'](function() {
  3637. if (promiseChain === samePromise) {
  3638. promiseChain = null;
  3639. }
  3640. });
  3641. return modalInstance;
  3642. };
  3643. return $modal;
  3644. }
  3645. ]
  3646. };
  3647. return $modalProvider;
  3648. });
  3649. angular.module('ui.bootstrap.paging', [])
  3650. /**
  3651. * Helper internal service for generating common controller code between the
  3652. * pager and pagination components
  3653. */
  3654. .factory('uibPaging', ['$parse', function($parse) {
  3655. return {
  3656. create: function(ctrl, $scope, $attrs) {
  3657. ctrl.setNumPages = $attrs.numPages ? $parse($attrs.numPages).assign : angular.noop;
  3658. ctrl.ngModelCtrl = { $setViewValue: angular.noop }; // nullModelCtrl
  3659. ctrl._watchers = [];
  3660. ctrl.init = function(ngModelCtrl, config) {
  3661. ctrl.ngModelCtrl = ngModelCtrl;
  3662. ctrl.config = config;
  3663. ngModelCtrl.$render = function() {
  3664. ctrl.render();
  3665. };
  3666. if ($attrs.itemsPerPage) {
  3667. ctrl._watchers.push($scope.$parent.$watch($attrs.itemsPerPage, function(value) {
  3668. ctrl.itemsPerPage = parseInt(value, 10);
  3669. $scope.totalPages = ctrl.calculateTotalPages();
  3670. ctrl.updatePage();
  3671. }));
  3672. } else {
  3673. ctrl.itemsPerPage = config.itemsPerPage;
  3674. }
  3675. $scope.$watch('totalItems', function(newTotal, oldTotal) {
  3676. if (angular.isDefined(newTotal) || newTotal !== oldTotal) {
  3677. $scope.totalPages = ctrl.calculateTotalPages();
  3678. ctrl.updatePage();
  3679. }
  3680. });
  3681. };
  3682. ctrl.calculateTotalPages = function() {
  3683. var totalPages = ctrl.itemsPerPage < 1 ? 1 : Math.ceil($scope.totalItems / ctrl.itemsPerPage);
  3684. return Math.max(totalPages || 0, 1);
  3685. };
  3686. ctrl.render = function() {
  3687. $scope.page = parseInt(ctrl.ngModelCtrl.$viewValue, 10) || 1;
  3688. };
  3689. $scope.selectPage = function(page, evt) {
  3690. if (evt) {
  3691. evt.preventDefault();
  3692. }
  3693. var clickAllowed = !$scope.ngDisabled || !evt;
  3694. if (clickAllowed && $scope.page !== page && page > 0 && page <= $scope.totalPages) {
  3695. if (evt && evt.target) {
  3696. evt.target.blur();
  3697. }
  3698. ctrl.ngModelCtrl.$setViewValue(page);
  3699. ctrl.ngModelCtrl.$render();
  3700. }
  3701. };
  3702. $scope.getText = function(key) {
  3703. return $scope[key + 'Text'] || ctrl.config[key + 'Text'];
  3704. };
  3705. $scope.noPrevious = function() {
  3706. return $scope.page === 1;
  3707. };
  3708. $scope.noNext = function() {
  3709. return $scope.page === $scope.totalPages;
  3710. };
  3711. ctrl.updatePage = function() {
  3712. ctrl.setNumPages($scope.$parent, $scope.totalPages); // Readonly variable
  3713. if ($scope.page > $scope.totalPages) {
  3714. $scope.selectPage($scope.totalPages);
  3715. } else {
  3716. ctrl.ngModelCtrl.$render();
  3717. }
  3718. };
  3719. $scope.$on('$destroy', function() {
  3720. while (ctrl._watchers.length) {
  3721. ctrl._watchers.shift()();
  3722. }
  3723. });
  3724. }
  3725. };
  3726. }]);
  3727. angular.module('ui.bootstrap.pager', ['ui.bootstrap.paging'])
  3728. .controller('UibPagerController', ['$scope', '$attrs', 'uibPaging', 'uibPagerConfig', function($scope, $attrs, uibPaging, uibPagerConfig) {
  3729. $scope.align = angular.isDefined($attrs.align) ? $scope.$parent.$eval($attrs.align) : uibPagerConfig.align;
  3730. uibPaging.create(this, $scope, $attrs);
  3731. }])
  3732. .constant('uibPagerConfig', {
  3733. itemsPerPage: 10,
  3734. previousText: '« Previous',
  3735. nextText: 'Next »',
  3736. align: true
  3737. })
  3738. .directive('uibPager', ['uibPagerConfig', function(uibPagerConfig) {
  3739. return {
  3740. scope: {
  3741. totalItems: '=',
  3742. previousText: '@',
  3743. nextText: '@',
  3744. ngDisabled: '='
  3745. },
  3746. require: ['uibPager', '?ngModel'],
  3747. controller: 'UibPagerController',
  3748. controllerAs: 'pager',
  3749. templateUrl: function(element, attrs) {
  3750. return attrs.templateUrl || 'uib/template/pager/pager.html';
  3751. },
  3752. replace: true,
  3753. link: function(scope, element, attrs, ctrls) {
  3754. var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1];
  3755. if (!ngModelCtrl) {
  3756. return; // do nothing if no ng-model
  3757. }
  3758. paginationCtrl.init(ngModelCtrl, uibPagerConfig);
  3759. }
  3760. };
  3761. }]);
  3762. angular.module('ui.bootstrap.pagination', ['ui.bootstrap.paging'])
  3763. .controller('UibPaginationController', ['$scope', '$attrs', '$parse', 'uibPaging', 'uibPaginationConfig', function($scope, $attrs, $parse, uibPaging, uibPaginationConfig) {
  3764. var ctrl = this;
  3765. // Setup configuration parameters
  3766. var maxSize = angular.isDefined($attrs.maxSize) ? $scope.$parent.$eval($attrs.maxSize) : uibPaginationConfig.maxSize,
  3767. rotate = angular.isDefined($attrs.rotate) ? $scope.$parent.$eval($attrs.rotate) : uibPaginationConfig.rotate,
  3768. forceEllipses = angular.isDefined($attrs.forceEllipses) ? $scope.$parent.$eval($attrs.forceEllipses) : uibPaginationConfig.forceEllipses,
  3769. boundaryLinkNumbers = angular.isDefined($attrs.boundaryLinkNumbers) ? $scope.$parent.$eval($attrs.boundaryLinkNumbers) : uibPaginationConfig.boundaryLinkNumbers,
  3770. pageLabel = angular.isDefined($attrs.pageLabel) ? function(idx) { return $scope.$parent.$eval($attrs.pageLabel, {$page: idx}); } : angular.identity;
  3771. $scope.boundaryLinks = angular.isDefined($attrs.boundaryLinks) ? $scope.$parent.$eval($attrs.boundaryLinks) : uibPaginationConfig.boundaryLinks;
  3772. $scope.directionLinks = angular.isDefined($attrs.directionLinks) ? $scope.$parent.$eval($attrs.directionLinks) : uibPaginationConfig.directionLinks;
  3773. uibPaging.create(this, $scope, $attrs);
  3774. if ($attrs.maxSize) {
  3775. ctrl._watchers.push($scope.$parent.$watch($parse($attrs.maxSize), function(value) {
  3776. maxSize = parseInt(value, 10);
  3777. ctrl.render();
  3778. }));
  3779. }
  3780. // Create page object used in template
  3781. function makePage(number, text, isActive) {
  3782. return {
  3783. number: number,
  3784. text: text,
  3785. active: isActive
  3786. };
  3787. }
  3788. function getPages(currentPage, totalPages) {
  3789. var pages = [];
  3790. // Default page limits
  3791. var startPage = 1, endPage = totalPages;
  3792. var isMaxSized = angular.isDefined(maxSize) && maxSize < totalPages;
  3793. // recompute if maxSize
  3794. if (isMaxSized) {
  3795. if (rotate) {
  3796. // Current page is displayed in the middle of the visible ones
  3797. startPage = Math.max(currentPage - Math.floor(maxSize / 2), 1);
  3798. endPage = startPage + maxSize - 1;
  3799. // Adjust if limit is exceeded
  3800. if (endPage > totalPages) {
  3801. endPage = totalPages;
  3802. startPage = endPage - maxSize + 1;
  3803. }
  3804. } else {
  3805. // Visible pages are paginated with maxSize
  3806. startPage = (Math.ceil(currentPage / maxSize) - 1) * maxSize + 1;
  3807. // Adjust last page if limit is exceeded
  3808. endPage = Math.min(startPage + maxSize - 1, totalPages);
  3809. }
  3810. }
  3811. // Add page number links
  3812. for (var number = startPage; number <= endPage; number++) {
  3813. var page = makePage(number, pageLabel(number), number === currentPage);
  3814. pages.push(page);
  3815. }
  3816. // Add links to move between page sets
  3817. if (isMaxSized && maxSize > 0 && (!rotate || forceEllipses || boundaryLinkNumbers)) {
  3818. if (startPage > 1) {
  3819. if (!boundaryLinkNumbers || startPage > 3) { //need ellipsis for all options unless range is too close to beginning
  3820. var previousPageSet = makePage(startPage - 1, '...', false);
  3821. pages.unshift(previousPageSet);
  3822. }
  3823. if (boundaryLinkNumbers) {
  3824. if (startPage === 3) { //need to replace ellipsis when the buttons would be sequential
  3825. var secondPageLink = makePage(2, '2', false);
  3826. pages.unshift(secondPageLink);
  3827. }
  3828. //add the first page
  3829. var firstPageLink = makePage(1, '1', false);
  3830. pages.unshift(firstPageLink);
  3831. }
  3832. }
  3833. if (endPage < totalPages) {
  3834. if (!boundaryLinkNumbers || endPage < totalPages - 2) { //need ellipsis for all options unless range is too close to end
  3835. var nextPageSet = makePage(endPage + 1, '...', false);
  3836. pages.push(nextPageSet);
  3837. }
  3838. if (boundaryLinkNumbers) {
  3839. if (endPage === totalPages - 2) { //need to replace ellipsis when the buttons would be sequential
  3840. var secondToLastPageLink = makePage(totalPages - 1, totalPages - 1, false);
  3841. pages.push(secondToLastPageLink);
  3842. }
  3843. //add the last page
  3844. var lastPageLink = makePage(totalPages, totalPages, false);
  3845. pages.push(lastPageLink);
  3846. }
  3847. }
  3848. }
  3849. return pages;
  3850. }
  3851. var originalRender = this.render;
  3852. this.render = function() {
  3853. originalRender();
  3854. if ($scope.page > 0 && $scope.page <= $scope.totalPages) {
  3855. $scope.pages = getPages($scope.page, $scope.totalPages);
  3856. }
  3857. };
  3858. }])
  3859. .constant('uibPaginationConfig', {
  3860. itemsPerPage: 10,
  3861. boundaryLinks: false,
  3862. boundaryLinkNumbers: false,
  3863. directionLinks: true,
  3864. firstText: 'First',
  3865. previousText: 'Previous',
  3866. nextText: 'Next',
  3867. lastText: 'Last',
  3868. rotate: true,
  3869. forceEllipses: false
  3870. })
  3871. .directive('uibPagination', ['$parse', 'uibPaginationConfig', function($parse, uibPaginationConfig) {
  3872. return {
  3873. scope: {
  3874. totalItems: '=',
  3875. firstText: '@',
  3876. previousText: '@',
  3877. nextText: '@',
  3878. lastText: '@',
  3879. ngDisabled:'='
  3880. },
  3881. require: ['uibPagination', '?ngModel'],
  3882. controller: 'UibPaginationController',
  3883. controllerAs: 'pagination',
  3884. templateUrl: function(element, attrs) {
  3885. return attrs.templateUrl || 'uib/template/pagination/pagination.html';
  3886. },
  3887. replace: true,
  3888. link: function(scope, element, attrs, ctrls) {
  3889. var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1];
  3890. if (!ngModelCtrl) {
  3891. return; // do nothing if no ng-model
  3892. }
  3893. paginationCtrl.init(ngModelCtrl, uibPaginationConfig);
  3894. }
  3895. };
  3896. }]);
  3897. /**
  3898. * The following features are still outstanding: animation as a
  3899. * function, placement as a function, inside, support for more triggers than
  3900. * just mouse enter/leave, html tooltips, and selector delegation.
  3901. */
  3902. angular.module('ui.bootstrap.tooltip', ['ui.bootstrap.position', 'ui.bootstrap.stackedMap'])
  3903. /**
  3904. * The $tooltip service creates tooltip- and popover-like directives as well as
  3905. * houses global options for them.
  3906. */
  3907. .provider('$uibTooltip', function() {
  3908. // The default options tooltip and popover.
  3909. var defaultOptions = {
  3910. placement: 'top',
  3911. placementClassPrefix: '',
  3912. animation: true,
  3913. popupDelay: 0,
  3914. popupCloseDelay: 0,
  3915. useContentExp: false
  3916. };
  3917. // Default hide triggers for each show trigger
  3918. var triggerMap = {
  3919. 'mouseenter': 'mouseleave',
  3920. 'click': 'click',
  3921. 'outsideClick': 'outsideClick',
  3922. 'focus': 'blur',
  3923. 'none': ''
  3924. };
  3925. // The options specified to the provider globally.
  3926. var globalOptions = {};
  3927. /**
  3928. * `options({})` allows global configuration of all tooltips in the
  3929. * application.
  3930. *
  3931. * var app = angular.module( 'App', ['ui.bootstrap.tooltip'], function( $tooltipProvider ) {
  3932. * // place tooltips left instead of top by default
  3933. * $tooltipProvider.options( { placement: 'left' } );
  3934. * });
  3935. */
  3936. this.options = function(value) {
  3937. angular.extend(globalOptions, value);
  3938. };
  3939. /**
  3940. * This allows you to extend the set of trigger mappings available. E.g.:
  3941. *
  3942. * $tooltipProvider.setTriggers( { 'openTrigger': 'closeTrigger' } );
  3943. */
  3944. this.setTriggers = function setTriggers(triggers) {
  3945. angular.extend(triggerMap, triggers);
  3946. };
  3947. /**
  3948. * This is a helper function for translating camel-case to snake_case.
  3949. */
  3950. function snake_case(name) {
  3951. var regexp = /[A-Z]/g;
  3952. var separator = '-';
  3953. return name.replace(regexp, function(letter, pos) {
  3954. return (pos ? separator : '') + letter.toLowerCase();
  3955. });
  3956. }
  3957. /**
  3958. * Returns the actual instance of the $tooltip service.
  3959. * TODO support multiple triggers
  3960. */
  3961. this.$get = ['$window', '$compile', '$timeout', '$document', '$uibPosition', '$interpolate', '$rootScope', '$parse', '$$stackedMap', function($window, $compile, $timeout, $document, $position, $interpolate, $rootScope, $parse, $$stackedMap) {
  3962. var openedTooltips = $$stackedMap.createNew();
  3963. $document.on('keypress', keypressListener);
  3964. $rootScope.$on('$destroy', function() {
  3965. $document.off('keypress', keypressListener);
  3966. });
  3967. function keypressListener(e) {
  3968. if (e.which === 27) {
  3969. var last = openedTooltips.top();
  3970. if (last) {
  3971. last.value.close();
  3972. openedTooltips.removeTop();
  3973. last = null;
  3974. }
  3975. }
  3976. }
  3977. return function $tooltip(ttType, prefix, defaultTriggerShow, options) {
  3978. options = angular.extend({}, defaultOptions, globalOptions, options);
  3979. /**
  3980. * Returns an object of show and hide triggers.
  3981. *
  3982. * If a trigger is supplied,
  3983. * it is used to show the tooltip; otherwise, it will use the `trigger`
  3984. * option passed to the `$tooltipProvider.options` method; else it will
  3985. * default to the trigger supplied to this directive factory.
  3986. *
  3987. * The hide trigger is based on the show trigger. If the `trigger` option
  3988. * was passed to the `$tooltipProvider.options` method, it will use the
  3989. * mapped trigger from `triggerMap` or the passed trigger if the map is
  3990. * undefined; otherwise, it uses the `triggerMap` value of the show
  3991. * trigger; else it will just use the show trigger.
  3992. */
  3993. function getTriggers(trigger) {
  3994. var show = (trigger || options.trigger || defaultTriggerShow).split(' ');
  3995. var hide = show.map(function(trigger) {
  3996. return triggerMap[trigger] || trigger;
  3997. });
  3998. return {
  3999. show: show,
  4000. hide: hide
  4001. };
  4002. }
  4003. var directiveName = snake_case(ttType);
  4004. var startSym = $interpolate.startSymbol();
  4005. var endSym = $interpolate.endSymbol();
  4006. var template =
  4007. '<div '+ directiveName + '-popup ' +
  4008. 'uib-title="' + startSym + 'title' + endSym + '" ' +
  4009. (options.useContentExp ?
  4010. 'content-exp="contentExp()" ' :
  4011. 'content="' + startSym + 'content' + endSym + '" ') +
  4012. 'placement="' + startSym + 'placement' + endSym + '" ' +
  4013. 'popup-class="' + startSym + 'popupClass' + endSym + '" ' +
  4014. 'animation="animation" ' +
  4015. 'is-open="isOpen" ' +
  4016. 'origin-scope="origScope" ' +
  4017. 'class="uib-position-measure"' +
  4018. '>' +
  4019. '</div>';
  4020. return {
  4021. compile: function(tElem, tAttrs) {
  4022. var tooltipLinker = $compile(template);
  4023. return function link(scope, element, attrs, tooltipCtrl) {
  4024. var tooltip;
  4025. var tooltipLinkedScope;
  4026. var transitionTimeout;
  4027. var showTimeout;
  4028. var hideTimeout;
  4029. var positionTimeout;
  4030. var appendToBody = angular.isDefined(options.appendToBody) ? options.appendToBody : false;
  4031. var triggers = getTriggers(undefined);
  4032. var hasEnableExp = angular.isDefined(attrs[prefix + 'Enable']);
  4033. var ttScope = scope.$new(true);
  4034. var repositionScheduled = false;
  4035. var isOpenParse = angular.isDefined(attrs[prefix + 'IsOpen']) ? $parse(attrs[prefix + 'IsOpen']) : false;
  4036. var contentParse = options.useContentExp ? $parse(attrs[ttType]) : false;
  4037. var observers = [];
  4038. var lastPlacement;
  4039. var positionTooltip = function() {
  4040. // check if tooltip exists and is not empty
  4041. if (!tooltip || !tooltip.html()) { return; }
  4042. if (!positionTimeout) {
  4043. positionTimeout = $timeout(function() {
  4044. var ttPosition = $position.positionElements(element, tooltip, ttScope.placement, appendToBody);
  4045. tooltip.css({ top: ttPosition.top + 'px', left: ttPosition.left + 'px' });
  4046. if (!tooltip.hasClass(ttPosition.placement.split('-')[0])) {
  4047. tooltip.removeClass(lastPlacement.split('-')[0]);
  4048. tooltip.addClass(ttPosition.placement.split('-')[0]);
  4049. }
  4050. if (!tooltip.hasClass(options.placementClassPrefix + ttPosition.placement)) {
  4051. tooltip.removeClass(options.placementClassPrefix + lastPlacement);
  4052. tooltip.addClass(options.placementClassPrefix + ttPosition.placement);
  4053. }
  4054. // first time through tt element will have the
  4055. // uib-position-measure class or if the placement
  4056. // has changed we need to position the arrow.
  4057. if (tooltip.hasClass('uib-position-measure')) {
  4058. $position.positionArrow(tooltip, ttPosition.placement);
  4059. tooltip.removeClass('uib-position-measure');
  4060. } else if (lastPlacement !== ttPosition.placement) {
  4061. $position.positionArrow(tooltip, ttPosition.placement);
  4062. }
  4063. lastPlacement = ttPosition.placement;
  4064. positionTimeout = null;
  4065. }, 0, false);
  4066. }
  4067. };
  4068. // Set up the correct scope to allow transclusion later
  4069. ttScope.origScope = scope;
  4070. // By default, the tooltip is not open.
  4071. // TODO add ability to start tooltip opened
  4072. ttScope.isOpen = false;
  4073. openedTooltips.add(ttScope, {
  4074. close: hide
  4075. });
  4076. function toggleTooltipBind() {
  4077. if (!ttScope.isOpen) {
  4078. showTooltipBind();
  4079. } else {
  4080. hideTooltipBind();
  4081. }
  4082. }
  4083. // Show the tooltip with delay if specified, otherwise show it immediately
  4084. function showTooltipBind() {
  4085. if (hasEnableExp && !scope.$eval(attrs[prefix + 'Enable'])) {
  4086. return;
  4087. }
  4088. cancelHide();
  4089. prepareTooltip();
  4090. if (ttScope.popupDelay) {
  4091. // Do nothing if the tooltip was already scheduled to pop-up.
  4092. // This happens if show is triggered multiple times before any hide is triggered.
  4093. if (!showTimeout) {
  4094. showTimeout = $timeout(show, ttScope.popupDelay, false);
  4095. }
  4096. } else {
  4097. show();
  4098. }
  4099. }
  4100. function hideTooltipBind() {
  4101. cancelShow();
  4102. if (ttScope.popupCloseDelay) {
  4103. if (!hideTimeout) {
  4104. hideTimeout = $timeout(hide, ttScope.popupCloseDelay, false);
  4105. }
  4106. } else {
  4107. hide();
  4108. }
  4109. }
  4110. // Show the tooltip popup element.
  4111. function show() {
  4112. cancelShow();
  4113. cancelHide();
  4114. // Don't show empty tooltips.
  4115. if (!ttScope.content) {
  4116. return angular.noop;
  4117. }
  4118. createTooltip();
  4119. // And show the tooltip.
  4120. ttScope.$evalAsync(function() {
  4121. ttScope.isOpen = true;
  4122. assignIsOpen(true);
  4123. positionTooltip();
  4124. });
  4125. }
  4126. function cancelShow() {
  4127. if (showTimeout) {
  4128. $timeout.cancel(showTimeout);
  4129. showTimeout = null;
  4130. }
  4131. if (positionTimeout) {
  4132. $timeout.cancel(positionTimeout);
  4133. positionTimeout = null;
  4134. }
  4135. }
  4136. // Hide the tooltip popup element.
  4137. function hide() {
  4138. if (!ttScope) {
  4139. return;
  4140. }
  4141. // First things first: we don't show it anymore.
  4142. ttScope.$evalAsync(function() {
  4143. if (ttScope) {
  4144. ttScope.isOpen = false;
  4145. assignIsOpen(false);
  4146. // And now we remove it from the DOM. However, if we have animation, we
  4147. // need to wait for it to expire beforehand.
  4148. // FIXME: this is a placeholder for a port of the transitions library.
  4149. // The fade transition in TWBS is 150ms.
  4150. if (ttScope.animation) {
  4151. if (!transitionTimeout) {
  4152. transitionTimeout = $timeout(removeTooltip, 150, false);
  4153. }
  4154. } else {
  4155. removeTooltip();
  4156. }
  4157. }
  4158. });
  4159. }
  4160. function cancelHide() {
  4161. if (hideTimeout) {
  4162. $timeout.cancel(hideTimeout);
  4163. hideTimeout = null;
  4164. }
  4165. if (transitionTimeout) {
  4166. $timeout.cancel(transitionTimeout);
  4167. transitionTimeout = null;
  4168. }
  4169. }
  4170. function createTooltip() {
  4171. // There can only be one tooltip element per directive shown at once.
  4172. if (tooltip) {
  4173. return;
  4174. }
  4175. tooltipLinkedScope = ttScope.$new();
  4176. tooltip = tooltipLinker(tooltipLinkedScope, function(tooltip) {
  4177. if (appendToBody) {
  4178. $document.find('body').append(tooltip);
  4179. } else {
  4180. element.after(tooltip);
  4181. }
  4182. });
  4183. prepObservers();
  4184. }
  4185. function removeTooltip() {
  4186. cancelShow();
  4187. cancelHide();
  4188. unregisterObservers();
  4189. if (tooltip) {
  4190. tooltip.remove();
  4191. tooltip = null;
  4192. }
  4193. if (tooltipLinkedScope) {
  4194. tooltipLinkedScope.$destroy();
  4195. tooltipLinkedScope = null;
  4196. }
  4197. }
  4198. /**
  4199. * Set the initial scope values. Once
  4200. * the tooltip is created, the observers
  4201. * will be added to keep things in sync.
  4202. */
  4203. function prepareTooltip() {
  4204. ttScope.title = attrs[prefix + 'Title'];
  4205. if (contentParse) {
  4206. ttScope.content = contentParse(scope);
  4207. } else {
  4208. ttScope.content = attrs[ttType];
  4209. }
  4210. ttScope.popupClass = attrs[prefix + 'Class'];
  4211. ttScope.placement = angular.isDefined(attrs[prefix + 'Placement']) ? attrs[prefix + 'Placement'] : options.placement;
  4212. var placement = $position.parsePlacement(ttScope.placement);
  4213. lastPlacement = placement[1] ? placement[0] + '-' + placement[1] : placement[0];
  4214. var delay = parseInt(attrs[prefix + 'PopupDelay'], 10);
  4215. var closeDelay = parseInt(attrs[prefix + 'PopupCloseDelay'], 10);
  4216. ttScope.popupDelay = !isNaN(delay) ? delay : options.popupDelay;
  4217. ttScope.popupCloseDelay = !isNaN(closeDelay) ? closeDelay : options.popupCloseDelay;
  4218. }
  4219. function assignIsOpen(isOpen) {
  4220. if (isOpenParse && angular.isFunction(isOpenParse.assign)) {
  4221. isOpenParse.assign(scope, isOpen);
  4222. }
  4223. }
  4224. ttScope.contentExp = function() {
  4225. return ttScope.content;
  4226. };
  4227. /**
  4228. * Observe the relevant attributes.
  4229. */
  4230. attrs.$observe('disabled', function(val) {
  4231. if (val) {
  4232. cancelShow();
  4233. }
  4234. if (val && ttScope.isOpen) {
  4235. hide();
  4236. }
  4237. });
  4238. if (isOpenParse) {
  4239. scope.$watch(isOpenParse, function(val) {
  4240. if (ttScope && !val === ttScope.isOpen) {
  4241. toggleTooltipBind();
  4242. }
  4243. });
  4244. }
  4245. function prepObservers() {
  4246. observers.length = 0;
  4247. if (contentParse) {
  4248. observers.push(
  4249. scope.$watch(contentParse, function(val) {
  4250. ttScope.content = val;
  4251. if (!val && ttScope.isOpen) {
  4252. hide();
  4253. }
  4254. })
  4255. );
  4256. observers.push(
  4257. tooltipLinkedScope.$watch(function() {
  4258. if (!repositionScheduled) {
  4259. repositionScheduled = true;
  4260. tooltipLinkedScope.$$postDigest(function() {
  4261. repositionScheduled = false;
  4262. if (ttScope && ttScope.isOpen) {
  4263. positionTooltip();
  4264. }
  4265. });
  4266. }
  4267. })
  4268. );
  4269. } else {
  4270. observers.push(
  4271. attrs.$observe(ttType, function(val) {
  4272. ttScope.content = val;
  4273. if (!val && ttScope.isOpen) {
  4274. hide();
  4275. } else {
  4276. positionTooltip();
  4277. }
  4278. })
  4279. );
  4280. }
  4281. observers.push(
  4282. attrs.$observe(prefix + 'Title', function(val) {
  4283. ttScope.title = val;
  4284. if (ttScope.isOpen) {
  4285. positionTooltip();
  4286. }
  4287. })
  4288. );
  4289. observers.push(
  4290. attrs.$observe(prefix + 'Placement', function(val) {
  4291. ttScope.placement = val ? val : options.placement;
  4292. if (ttScope.isOpen) {
  4293. positionTooltip();
  4294. }
  4295. })
  4296. );
  4297. }
  4298. function unregisterObservers() {
  4299. if (observers.length) {
  4300. angular.forEach(observers, function(observer) {
  4301. observer();
  4302. });
  4303. observers.length = 0;
  4304. }
  4305. }
  4306. // hide tooltips/popovers for outsideClick trigger
  4307. function bodyHideTooltipBind(e) {
  4308. if (!ttScope || !ttScope.isOpen || !tooltip) {
  4309. return;
  4310. }
  4311. // make sure the tooltip/popover link or tool tooltip/popover itself were not clicked
  4312. if (!element[0].contains(e.target) && !tooltip[0].contains(e.target)) {
  4313. hideTooltipBind();
  4314. }
  4315. }
  4316. var unregisterTriggers = function() {
  4317. triggers.show.forEach(function(trigger) {
  4318. if (trigger === 'outsideClick') {
  4319. element.off('click', toggleTooltipBind);
  4320. } else {
  4321. element.off(trigger, showTooltipBind);
  4322. element.off(trigger, toggleTooltipBind);
  4323. }
  4324. });
  4325. triggers.hide.forEach(function(trigger) {
  4326. if (trigger === 'outsideClick') {
  4327. $document.off('click', bodyHideTooltipBind);
  4328. } else {
  4329. element.off(trigger, hideTooltipBind);
  4330. }
  4331. });
  4332. };
  4333. function prepTriggers() {
  4334. var val = attrs[prefix + 'Trigger'];
  4335. unregisterTriggers();
  4336. triggers = getTriggers(val);
  4337. if (triggers.show !== 'none') {
  4338. triggers.show.forEach(function(trigger, idx) {
  4339. if (trigger === 'outsideClick') {
  4340. element.on('click', toggleTooltipBind);
  4341. $document.on('click', bodyHideTooltipBind);
  4342. } else if (trigger === triggers.hide[idx]) {
  4343. element.on(trigger, toggleTooltipBind);
  4344. } else if (trigger) {
  4345. element.on(trigger, showTooltipBind);
  4346. element.on(triggers.hide[idx], hideTooltipBind);
  4347. }
  4348. element.on('keypress', function(e) {
  4349. if (e.which === 27) {
  4350. hideTooltipBind();
  4351. }
  4352. });
  4353. });
  4354. }
  4355. }
  4356. prepTriggers();
  4357. var animation = scope.$eval(attrs[prefix + 'Animation']);
  4358. ttScope.animation = angular.isDefined(animation) ? !!animation : options.animation;
  4359. var appendToBodyVal;
  4360. var appendKey = prefix + 'AppendToBody';
  4361. if (appendKey in attrs && attrs[appendKey] === undefined) {
  4362. appendToBodyVal = true;
  4363. } else {
  4364. appendToBodyVal = scope.$eval(attrs[appendKey]);
  4365. }
  4366. appendToBody = angular.isDefined(appendToBodyVal) ? appendToBodyVal : appendToBody;
  4367. // Make sure tooltip is destroyed and removed.
  4368. scope.$on('$destroy', function onDestroyTooltip() {
  4369. unregisterTriggers();
  4370. removeTooltip();
  4371. openedTooltips.remove(ttScope);
  4372. ttScope = null;
  4373. });
  4374. };
  4375. }
  4376. };
  4377. };
  4378. }];
  4379. })
  4380. // This is mostly ngInclude code but with a custom scope
  4381. .directive('uibTooltipTemplateTransclude', [
  4382. '$animate', '$sce', '$compile', '$templateRequest',
  4383. function ($animate, $sce, $compile, $templateRequest) {
  4384. return {
  4385. link: function(scope, elem, attrs) {
  4386. var origScope = scope.$eval(attrs.tooltipTemplateTranscludeScope);
  4387. var changeCounter = 0,
  4388. currentScope,
  4389. previousElement,
  4390. currentElement;
  4391. var cleanupLastIncludeContent = function() {
  4392. if (previousElement) {
  4393. previousElement.remove();
  4394. previousElement = null;
  4395. }
  4396. if (currentScope) {
  4397. currentScope.$destroy();
  4398. currentScope = null;
  4399. }
  4400. if (currentElement) {
  4401. $animate.leave(currentElement).then(function() {
  4402. previousElement = null;
  4403. });
  4404. previousElement = currentElement;
  4405. currentElement = null;
  4406. }
  4407. };
  4408. scope.$watch($sce.parseAsResourceUrl(attrs.uibTooltipTemplateTransclude), function(src) {
  4409. var thisChangeId = ++changeCounter;
  4410. if (src) {
  4411. //set the 2nd param to true to ignore the template request error so that the inner
  4412. //contents and scope can be cleaned up.
  4413. $templateRequest(src, true).then(function(response) {
  4414. if (thisChangeId !== changeCounter) { return; }
  4415. var newScope = origScope.$new();
  4416. var template = response;
  4417. var clone = $compile(template)(newScope, function(clone) {
  4418. cleanupLastIncludeContent();
  4419. $animate.enter(clone, elem);
  4420. });
  4421. currentScope = newScope;
  4422. currentElement = clone;
  4423. currentScope.$emit('$includeContentLoaded', src);
  4424. }, function() {
  4425. if (thisChangeId === changeCounter) {
  4426. cleanupLastIncludeContent();
  4427. scope.$emit('$includeContentError', src);
  4428. }
  4429. });
  4430. scope.$emit('$includeContentRequested', src);
  4431. } else {
  4432. cleanupLastIncludeContent();
  4433. }
  4434. });
  4435. scope.$on('$destroy', cleanupLastIncludeContent);
  4436. }
  4437. };
  4438. }])
  4439. /**
  4440. * Note that it's intentional that these classes are *not* applied through $animate.
  4441. * They must not be animated as they're expected to be present on the tooltip on
  4442. * initialization.
  4443. */
  4444. .directive('uibTooltipClasses', ['$uibPosition', function($uibPosition) {
  4445. return {
  4446. restrict: 'A',
  4447. link: function(scope, element, attrs) {
  4448. // need to set the primary position so the
  4449. // arrow has space during position measure.
  4450. // tooltip.positionTooltip()
  4451. if (scope.placement) {
  4452. // // There are no top-left etc... classes
  4453. // // in TWBS, so we need the primary position.
  4454. var position = $uibPosition.parsePlacement(scope.placement);
  4455. element.addClass(position[0]);
  4456. }
  4457. if (scope.popupClass) {
  4458. element.addClass(scope.popupClass);
  4459. }
  4460. if (scope.animation()) {
  4461. element.addClass(attrs.tooltipAnimationClass);
  4462. }
  4463. }
  4464. };
  4465. }])
  4466. .directive('uibTooltipPopup', function() {
  4467. return {
  4468. replace: true,
  4469. scope: { content: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' },
  4470. templateUrl: 'uib/template/tooltip/tooltip-popup.html'
  4471. };
  4472. })
  4473. .directive('uibTooltip', [ '$uibTooltip', function($uibTooltip) {
  4474. return $uibTooltip('uibTooltip', 'tooltip', 'mouseenter');
  4475. }])
  4476. .directive('uibTooltipTemplatePopup', function() {
  4477. return {
  4478. replace: true,
  4479. scope: { contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&',
  4480. originScope: '&' },
  4481. templateUrl: 'uib/template/tooltip/tooltip-template-popup.html'
  4482. };
  4483. })
  4484. .directive('uibTooltipTemplate', ['$uibTooltip', function($uibTooltip) {
  4485. return $uibTooltip('uibTooltipTemplate', 'tooltip', 'mouseenter', {
  4486. useContentExp: true
  4487. });
  4488. }])
  4489. .directive('uibTooltipHtmlPopup', function() {
  4490. return {
  4491. replace: true,
  4492. scope: { contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&' },
  4493. templateUrl: 'uib/template/tooltip/tooltip-html-popup.html'
  4494. };
  4495. })
  4496. .directive('uibTooltipHtml', ['$uibTooltip', function($uibTooltip) {
  4497. return $uibTooltip('uibTooltipHtml', 'tooltip', 'mouseenter', {
  4498. useContentExp: true
  4499. });
  4500. }]);
  4501. /**
  4502. * The following features are still outstanding: popup delay, animation as a
  4503. * function, placement as a function, inside, support for more triggers than
  4504. * just mouse enter/leave, and selector delegatation.
  4505. */
  4506. angular.module('ui.bootstrap.popover', ['ui.bootstrap.tooltip'])
  4507. .directive('uibPopoverTemplatePopup', function() {
  4508. return {
  4509. replace: true,
  4510. scope: { uibTitle: '@', contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&',
  4511. originScope: '&' },
  4512. templateUrl: 'uib/template/popover/popover-template.html'
  4513. };
  4514. })
  4515. .directive('uibPopoverTemplate', ['$uibTooltip', function($uibTooltip) {
  4516. return $uibTooltip('uibPopoverTemplate', 'popover', 'click', {
  4517. useContentExp: true
  4518. });
  4519. }])
  4520. .directive('uibPopoverHtmlPopup', function() {
  4521. return {
  4522. replace: true,
  4523. scope: { contentExp: '&', uibTitle: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' },
  4524. templateUrl: 'uib/template/popover/popover-html.html'
  4525. };
  4526. })
  4527. .directive('uibPopoverHtml', ['$uibTooltip', function($uibTooltip) {
  4528. return $uibTooltip('uibPopoverHtml', 'popover', 'click', {
  4529. useContentExp: true
  4530. });
  4531. }])
  4532. .directive('uibPopoverPopup', function() {
  4533. return {
  4534. replace: true,
  4535. scope: { uibTitle: '@', content: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' },
  4536. templateUrl: 'uib/template/popover/popover.html'
  4537. };
  4538. })
  4539. .directive('uibPopover', ['$uibTooltip', function($uibTooltip) {
  4540. return $uibTooltip('uibPopover', 'popover', 'click');
  4541. }]);
  4542. angular.module('ui.bootstrap.progressbar', [])
  4543. .constant('uibProgressConfig', {
  4544. animate: true,
  4545. max: 100
  4546. })
  4547. .controller('UibProgressController', ['$scope', '$attrs', 'uibProgressConfig', function($scope, $attrs, progressConfig) {
  4548. var self = this,
  4549. animate = angular.isDefined($attrs.animate) ? $scope.$parent.$eval($attrs.animate) : progressConfig.animate;
  4550. this.bars = [];
  4551. $scope.max = getMaxOrDefault();
  4552. this.addBar = function(bar, element, attrs) {
  4553. if (!animate) {
  4554. element.css({'transition': 'none'});
  4555. }
  4556. this.bars.push(bar);
  4557. bar.max = getMaxOrDefault();
  4558. bar.title = attrs && angular.isDefined(attrs.title) ? attrs.title : 'progressbar';
  4559. bar.$watch('value', function(value) {
  4560. bar.recalculatePercentage();
  4561. });
  4562. bar.recalculatePercentage = function() {
  4563. var totalPercentage = self.bars.reduce(function(total, bar) {
  4564. bar.percent = +(100 * bar.value / bar.max).toFixed(2);
  4565. return total + bar.percent;
  4566. }, 0);
  4567. if (totalPercentage > 100) {
  4568. bar.percent -= totalPercentage - 100;
  4569. }
  4570. };
  4571. bar.$on('$destroy', function() {
  4572. element = null;
  4573. self.removeBar(bar);
  4574. });
  4575. };
  4576. this.removeBar = function(bar) {
  4577. this.bars.splice(this.bars.indexOf(bar), 1);
  4578. this.bars.forEach(function (bar) {
  4579. bar.recalculatePercentage();
  4580. });
  4581. };
  4582. //$attrs.$observe('maxParam', function(maxParam) {
  4583. $scope.$watch('maxParam', function(maxParam) {
  4584. self.bars.forEach(function(bar) {
  4585. bar.max = getMaxOrDefault();
  4586. bar.recalculatePercentage();
  4587. });
  4588. });
  4589. function getMaxOrDefault () {
  4590. return angular.isDefined($scope.maxParam) ? $scope.maxParam : progressConfig.max;
  4591. }
  4592. }])
  4593. .directive('uibProgress', function() {
  4594. return {
  4595. replace: true,
  4596. transclude: true,
  4597. controller: 'UibProgressController',
  4598. require: 'uibProgress',
  4599. scope: {
  4600. maxParam: '=?max'
  4601. },
  4602. templateUrl: 'uib/template/progressbar/progress.html'
  4603. };
  4604. })
  4605. .directive('uibBar', function() {
  4606. return {
  4607. replace: true,
  4608. transclude: true,
  4609. require: '^uibProgress',
  4610. scope: {
  4611. value: '=',
  4612. type: '@'
  4613. },
  4614. templateUrl: 'uib/template/progressbar/bar.html',
  4615. link: function(scope, element, attrs, progressCtrl) {
  4616. progressCtrl.addBar(scope, element, attrs);
  4617. }
  4618. };
  4619. })
  4620. .directive('uibProgressbar', function() {
  4621. return {
  4622. replace: true,
  4623. transclude: true,
  4624. controller: 'UibProgressController',
  4625. scope: {
  4626. value: '=',
  4627. maxParam: '=?max',
  4628. type: '@'
  4629. },
  4630. templateUrl: 'uib/template/progressbar/progressbar.html',
  4631. link: function(scope, element, attrs, progressCtrl) {
  4632. progressCtrl.addBar(scope, angular.element(element.children()[0]), {title: attrs.title});
  4633. }
  4634. };
  4635. });
  4636. angular.module('ui.bootstrap.rating', [])
  4637. .constant('uibRatingConfig', {
  4638. max: 5,
  4639. stateOn: null,
  4640. stateOff: null,
  4641. enableReset: true,
  4642. titles : ['one', 'two', 'three', 'four', 'five']
  4643. })
  4644. .controller('UibRatingController', ['$scope', '$attrs', 'uibRatingConfig', function($scope, $attrs, ratingConfig) {
  4645. var ngModelCtrl = { $setViewValue: angular.noop },
  4646. self = this;
  4647. this.init = function(ngModelCtrl_) {
  4648. ngModelCtrl = ngModelCtrl_;
  4649. ngModelCtrl.$render = this.render;
  4650. ngModelCtrl.$formatters.push(function(value) {
  4651. if (angular.isNumber(value) && value << 0 !== value) {
  4652. value = Math.round(value);
  4653. }
  4654. return value;
  4655. });
  4656. this.stateOn = angular.isDefined($attrs.stateOn) ? $scope.$parent.$eval($attrs.stateOn) : ratingConfig.stateOn;
  4657. this.stateOff = angular.isDefined($attrs.stateOff) ? $scope.$parent.$eval($attrs.stateOff) : ratingConfig.stateOff;
  4658. this.enableReset = angular.isDefined($attrs.enableReset) ?
  4659. $scope.$parent.$eval($attrs.enableReset) : ratingConfig.enableReset;
  4660. var tmpTitles = angular.isDefined($attrs.titles) ? $scope.$parent.$eval($attrs.titles) : ratingConfig.titles;
  4661. this.titles = angular.isArray(tmpTitles) && tmpTitles.length > 0 ?
  4662. tmpTitles : ratingConfig.titles;
  4663. var ratingStates = angular.isDefined($attrs.ratingStates) ?
  4664. $scope.$parent.$eval($attrs.ratingStates) :
  4665. new Array(angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : ratingConfig.max);
  4666. $scope.range = this.buildTemplateObjects(ratingStates);
  4667. };
  4668. this.buildTemplateObjects = function(states) {
  4669. for (var i = 0, n = states.length; i < n; i++) {
  4670. states[i] = angular.extend({ index: i }, { stateOn: this.stateOn, stateOff: this.stateOff, title: this.getTitle(i) }, states[i]);
  4671. }
  4672. return states;
  4673. };
  4674. this.getTitle = function(index) {
  4675. if (index >= this.titles.length) {
  4676. return index + 1;
  4677. }
  4678. return this.titles[index];
  4679. };
  4680. $scope.rate = function(value) {
  4681. if (!$scope.readonly && value >= 0 && value <= $scope.range.length) {
  4682. var newViewValue = self.enableReset && ngModelCtrl.$viewValue === value ? 0 : value;
  4683. ngModelCtrl.$setViewValue(newViewValue);
  4684. ngModelCtrl.$render();
  4685. }
  4686. };
  4687. $scope.enter = function(value) {
  4688. if (!$scope.readonly) {
  4689. $scope.value = value;
  4690. }
  4691. $scope.onHover({value: value});
  4692. };
  4693. $scope.reset = function() {
  4694. $scope.value = ngModelCtrl.$viewValue;
  4695. $scope.onLeave();
  4696. };
  4697. $scope.onKeydown = function(evt) {
  4698. if (/(37|38|39|40)/.test(evt.which)) {
  4699. evt.preventDefault();
  4700. evt.stopPropagation();
  4701. $scope.rate($scope.value + (evt.which === 38 || evt.which === 39 ? 1 : -1));
  4702. }
  4703. };
  4704. this.render = function() {
  4705. $scope.value = ngModelCtrl.$viewValue;
  4706. $scope.title = self.getTitle($scope.value - 1);
  4707. };
  4708. }])
  4709. .directive('uibRating', function() {
  4710. return {
  4711. require: ['uibRating', 'ngModel'],
  4712. scope: {
  4713. readonly: '=?readOnly',
  4714. onHover: '&',
  4715. onLeave: '&'
  4716. },
  4717. controller: 'UibRatingController',
  4718. templateUrl: 'uib/template/rating/rating.html',
  4719. replace: true,
  4720. link: function(scope, element, attrs, ctrls) {
  4721. var ratingCtrl = ctrls[0], ngModelCtrl = ctrls[1];
  4722. ratingCtrl.init(ngModelCtrl);
  4723. }
  4724. };
  4725. });
  4726. angular.module('ui.bootstrap.tabs', [])
  4727. .controller('UibTabsetController', ['$scope', function ($scope) {
  4728. var ctrl = this,
  4729. oldIndex;
  4730. ctrl.tabs = [];
  4731. ctrl.select = function(index, evt) {
  4732. if (!destroyed) {
  4733. var previousIndex = findTabIndex(oldIndex);
  4734. var previousSelected = ctrl.tabs[previousIndex];
  4735. if (previousSelected) {
  4736. previousSelected.tab.onDeselect({
  4737. $event: evt,
  4738. $selectedIndex: index
  4739. });
  4740. if (evt && evt.isDefaultPrevented()) {
  4741. return;
  4742. }
  4743. previousSelected.tab.active = false;
  4744. }
  4745. var selected = ctrl.tabs[index];
  4746. if (selected) {
  4747. selected.tab.onSelect({
  4748. $event: evt
  4749. });
  4750. selected.tab.active = true;
  4751. ctrl.active = selected.index;
  4752. oldIndex = selected.index;
  4753. } else if (!selected && angular.isDefined(oldIndex)) {
  4754. ctrl.active = null;
  4755. oldIndex = null;
  4756. }
  4757. }
  4758. };
  4759. ctrl.addTab = function addTab(tab) {
  4760. ctrl.tabs.push({
  4761. tab: tab,
  4762. index: tab.index
  4763. });
  4764. ctrl.tabs.sort(function(t1, t2) {
  4765. if (t1.index > t2.index) {
  4766. return 1;
  4767. }
  4768. if (t1.index < t2.index) {
  4769. return -1;
  4770. }
  4771. return 0;
  4772. });
  4773. if (tab.index === ctrl.active || !angular.isDefined(ctrl.active) && ctrl.tabs.length === 1) {
  4774. var newActiveIndex = findTabIndex(tab.index);
  4775. ctrl.select(newActiveIndex);
  4776. }
  4777. };
  4778. ctrl.removeTab = function removeTab(tab) {
  4779. var index;
  4780. for (var i = 0; i < ctrl.tabs.length; i++) {
  4781. if (ctrl.tabs[i].tab === tab) {
  4782. index = i;
  4783. break;
  4784. }
  4785. }
  4786. if (ctrl.tabs[index].index === ctrl.active) {
  4787. var newActiveTabIndex = index === ctrl.tabs.length - 1 ?
  4788. index - 1 : index + 1 % ctrl.tabs.length;
  4789. ctrl.select(newActiveTabIndex);
  4790. }
  4791. ctrl.tabs.splice(index, 1);
  4792. };
  4793. $scope.$watch('tabset.active', function(val) {
  4794. if (angular.isDefined(val) && val !== oldIndex) {
  4795. ctrl.select(findTabIndex(val));
  4796. }
  4797. });
  4798. var destroyed;
  4799. $scope.$on('$destroy', function() {
  4800. destroyed = true;
  4801. });
  4802. function findTabIndex(index) {
  4803. for (var i = 0; i < ctrl.tabs.length; i++) {
  4804. if (ctrl.tabs[i].index === index) {
  4805. return i;
  4806. }
  4807. }
  4808. }
  4809. }])
  4810. .directive('uibTabset', function() {
  4811. return {
  4812. transclude: true,
  4813. replace: true,
  4814. scope: {},
  4815. bindToController: {
  4816. active: '=?',
  4817. type: '@'
  4818. },
  4819. controller: 'UibTabsetController',
  4820. controllerAs: 'tabset',
  4821. templateUrl: function(element, attrs) {
  4822. return attrs.templateUrl || 'uib/template/tabs/tabset.html';
  4823. },
  4824. link: function(scope, element, attrs) {
  4825. scope.vertical = angular.isDefined(attrs.vertical) ?
  4826. scope.$parent.$eval(attrs.vertical) : false;
  4827. scope.justified = angular.isDefined(attrs.justified) ?
  4828. scope.$parent.$eval(attrs.justified) : false;
  4829. }
  4830. };
  4831. })
  4832. .directive('uibTab', ['$parse', function($parse) {
  4833. return {
  4834. require: '^uibTabset',
  4835. replace: true,
  4836. templateUrl: function(element, attrs) {
  4837. return attrs.templateUrl || 'uib/template/tabs/tab.html';
  4838. },
  4839. transclude: true,
  4840. scope: {
  4841. heading: '@',
  4842. index: '=?',
  4843. classes: '@?',
  4844. onSelect: '&select', //This callback is called in contentHeadingTransclude
  4845. //once it inserts the tab's content into the dom
  4846. onDeselect: '&deselect'
  4847. },
  4848. controller: function() {
  4849. //Empty controller so other directives can require being 'under' a tab
  4850. },
  4851. controllerAs: 'tab',
  4852. link: function(scope, elm, attrs, tabsetCtrl, transclude) {
  4853. scope.disabled = false;
  4854. if (attrs.disable) {
  4855. scope.$parent.$watch($parse(attrs.disable), function(value) {
  4856. scope.disabled = !! value;
  4857. });
  4858. }
  4859. if (angular.isUndefined(attrs.index)) {
  4860. if (tabsetCtrl.tabs && tabsetCtrl.tabs.length) {
  4861. scope.index = Math.max.apply(null, tabsetCtrl.tabs.map(function(t) { return t.index; })) + 1;
  4862. } else {
  4863. scope.index = 0;
  4864. }
  4865. }
  4866. if (angular.isUndefined(attrs.classes)) {
  4867. scope.classes = '';
  4868. }
  4869. scope.select = function(evt) {
  4870. if (!scope.disabled) {
  4871. var index;
  4872. for (var i = 0; i < tabsetCtrl.tabs.length; i++) {
  4873. if (tabsetCtrl.tabs[i].tab === scope) {
  4874. index = i;
  4875. break;
  4876. }
  4877. }
  4878. tabsetCtrl.select(index, evt);
  4879. }
  4880. };
  4881. tabsetCtrl.addTab(scope);
  4882. scope.$on('$destroy', function() {
  4883. tabsetCtrl.removeTab(scope);
  4884. });
  4885. //We need to transclude later, once the content container is ready.
  4886. //when this link happens, we're inside a tab heading.
  4887. scope.$transcludeFn = transclude;
  4888. }
  4889. };
  4890. }])
  4891. .directive('uibTabHeadingTransclude', function() {
  4892. return {
  4893. restrict: 'A',
  4894. require: '^uibTab',
  4895. link: function(scope, elm) {
  4896. scope.$watch('headingElement', function updateHeadingElement(heading) {
  4897. if (heading) {
  4898. elm.html('');
  4899. elm.append(heading);
  4900. }
  4901. });
  4902. }
  4903. };
  4904. })
  4905. .directive('uibTabContentTransclude', function() {
  4906. return {
  4907. restrict: 'A',
  4908. require: '^uibTabset',
  4909. link: function(scope, elm, attrs) {
  4910. var tab = scope.$eval(attrs.uibTabContentTransclude).tab;
  4911. //Now our tab is ready to be transcluded: both the tab heading area
  4912. //and the tab content area are loaded. Transclude 'em both.
  4913. tab.$transcludeFn(tab.$parent, function(contents) {
  4914. angular.forEach(contents, function(node) {
  4915. if (isTabHeading(node)) {
  4916. //Let tabHeadingTransclude know.
  4917. tab.headingElement = node;
  4918. } else {
  4919. elm.append(node);
  4920. }
  4921. });
  4922. });
  4923. }
  4924. };
  4925. function isTabHeading(node) {
  4926. return node.tagName && (
  4927. node.hasAttribute('uib-tab-heading') ||
  4928. node.hasAttribute('data-uib-tab-heading') ||
  4929. node.hasAttribute('x-uib-tab-heading') ||
  4930. node.tagName.toLowerCase() === 'uib-tab-heading' ||
  4931. node.tagName.toLowerCase() === 'data-uib-tab-heading' ||
  4932. node.tagName.toLowerCase() === 'x-uib-tab-heading' ||
  4933. node.tagName.toLowerCase() === 'uib:tab-heading'
  4934. );
  4935. }
  4936. });
  4937. angular.module('ui.bootstrap.timepicker', [])
  4938. .constant('uibTimepickerConfig', {
  4939. hourStep: 1,
  4940. minuteStep: 1,
  4941. secondStep: 1,
  4942. showMeridian: true,
  4943. showSeconds: false,
  4944. meridians: null,
  4945. readonlyInput: false,
  4946. mousewheel: true,
  4947. arrowkeys: true,
  4948. showSpinners: true,
  4949. templateUrl: 'uib/template/timepicker/timepicker.html'
  4950. })
  4951. .controller('UibTimepickerController', ['$scope', '$element', '$attrs', '$parse', '$log', '$locale', 'uibTimepickerConfig', function($scope, $element, $attrs, $parse, $log, $locale, timepickerConfig) {
  4952. var selected = new Date(),
  4953. watchers = [],
  4954. ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl
  4955. meridians = angular.isDefined($attrs.meridians) ? $scope.$parent.$eval($attrs.meridians) : timepickerConfig.meridians || $locale.DATETIME_FORMATS.AMPMS,
  4956. padHours = angular.isDefined($attrs.padHours) ? $scope.$parent.$eval($attrs.padHours) : true;
  4957. $scope.tabindex = angular.isDefined($attrs.tabindex) ? $attrs.tabindex : 0;
  4958. $element.removeAttr('tabindex');
  4959. this.init = function(ngModelCtrl_, inputs) {
  4960. ngModelCtrl = ngModelCtrl_;
  4961. ngModelCtrl.$render = this.render;
  4962. ngModelCtrl.$formatters.unshift(function(modelValue) {
  4963. return modelValue ? new Date(modelValue) : null;
  4964. });
  4965. var hoursInputEl = inputs.eq(0),
  4966. minutesInputEl = inputs.eq(1),
  4967. secondsInputEl = inputs.eq(2);
  4968. var mousewheel = angular.isDefined($attrs.mousewheel) ? $scope.$parent.$eval($attrs.mousewheel) : timepickerConfig.mousewheel;
  4969. if (mousewheel) {
  4970. this.setupMousewheelEvents(hoursInputEl, minutesInputEl, secondsInputEl);
  4971. }
  4972. var arrowkeys = angular.isDefined($attrs.arrowkeys) ? $scope.$parent.$eval($attrs.arrowkeys) : timepickerConfig.arrowkeys;
  4973. if (arrowkeys) {
  4974. this.setupArrowkeyEvents(hoursInputEl, minutesInputEl, secondsInputEl);
  4975. }
  4976. $scope.readonlyInput = angular.isDefined($attrs.readonlyInput) ? $scope.$parent.$eval($attrs.readonlyInput) : timepickerConfig.readonlyInput;
  4977. this.setupInputEvents(hoursInputEl, minutesInputEl, secondsInputEl);
  4978. };
  4979. var hourStep = timepickerConfig.hourStep;
  4980. if ($attrs.hourStep) {
  4981. watchers.push($scope.$parent.$watch($parse($attrs.hourStep), function(value) {
  4982. hourStep = +value;
  4983. }));
  4984. }
  4985. var minuteStep = timepickerConfig.minuteStep;
  4986. if ($attrs.minuteStep) {
  4987. watchers.push($scope.$parent.$watch($parse($attrs.minuteStep), function(value) {
  4988. minuteStep = +value;
  4989. }));
  4990. }
  4991. var min;
  4992. watchers.push($scope.$parent.$watch($parse($attrs.min), function(value) {
  4993. var dt = new Date(value);
  4994. min = isNaN(dt) ? undefined : dt;
  4995. }));
  4996. var max;
  4997. watchers.push($scope.$parent.$watch($parse($attrs.max), function(value) {
  4998. var dt = new Date(value);
  4999. max = isNaN(dt) ? undefined : dt;
  5000. }));
  5001. var disabled = false;
  5002. if ($attrs.ngDisabled) {
  5003. watchers.push($scope.$parent.$watch($parse($attrs.ngDisabled), function(value) {
  5004. disabled = value;
  5005. }));
  5006. }
  5007. $scope.noIncrementHours = function() {
  5008. var incrementedSelected = addMinutes(selected, hourStep * 60);
  5009. return disabled || incrementedSelected > max ||
  5010. incrementedSelected < selected && incrementedSelected < min;
  5011. };
  5012. $scope.noDecrementHours = function() {
  5013. var decrementedSelected = addMinutes(selected, -hourStep * 60);
  5014. return disabled || decrementedSelected < min ||
  5015. decrementedSelected > selected && decrementedSelected > max;
  5016. };
  5017. $scope.noIncrementMinutes = function() {
  5018. var incrementedSelected = addMinutes(selected, minuteStep);
  5019. return disabled || incrementedSelected > max ||
  5020. incrementedSelected < selected && incrementedSelected < min;
  5021. };
  5022. $scope.noDecrementMinutes = function() {
  5023. var decrementedSelected = addMinutes(selected, -minuteStep);
  5024. return disabled || decrementedSelected < min ||
  5025. decrementedSelected > selected && decrementedSelected > max;
  5026. };
  5027. $scope.noIncrementSeconds = function() {
  5028. var incrementedSelected = addSeconds(selected, secondStep);
  5029. return disabled || incrementedSelected > max ||
  5030. incrementedSelected < selected && incrementedSelected < min;
  5031. };
  5032. $scope.noDecrementSeconds = function() {
  5033. var decrementedSelected = addSeconds(selected, -secondStep);
  5034. return disabled || decrementedSelected < min ||
  5035. decrementedSelected > selected && decrementedSelected > max;
  5036. };
  5037. $scope.noToggleMeridian = function() {
  5038. if (selected.getHours() < 12) {
  5039. return disabled || addMinutes(selected, 12 * 60) > max;
  5040. }
  5041. return disabled || addMinutes(selected, -12 * 60) < min;
  5042. };
  5043. var secondStep = timepickerConfig.secondStep;
  5044. if ($attrs.secondStep) {
  5045. watchers.push($scope.$parent.$watch($parse($attrs.secondStep), function(value) {
  5046. secondStep = +value;
  5047. }));
  5048. }
  5049. $scope.showSeconds = timepickerConfig.showSeconds;
  5050. if ($attrs.showSeconds) {
  5051. watchers.push($scope.$parent.$watch($parse($attrs.showSeconds), function(value) {
  5052. $scope.showSeconds = !!value;
  5053. }));
  5054. }
  5055. // 12H / 24H mode
  5056. $scope.showMeridian = timepickerConfig.showMeridian;
  5057. if ($attrs.showMeridian) {
  5058. watchers.push($scope.$parent.$watch($parse($attrs.showMeridian), function(value) {
  5059. $scope.showMeridian = !!value;
  5060. if (ngModelCtrl.$error.time) {
  5061. // Evaluate from template
  5062. var hours = getHoursFromTemplate(), minutes = getMinutesFromTemplate();
  5063. if (angular.isDefined(hours) && angular.isDefined(minutes)) {
  5064. selected.setHours(hours);
  5065. refresh();
  5066. }
  5067. } else {
  5068. updateTemplate();
  5069. }
  5070. }));
  5071. }
  5072. // Get $scope.hours in 24H mode if valid
  5073. function getHoursFromTemplate() {
  5074. var hours = +$scope.hours;
  5075. var valid = $scope.showMeridian ? hours > 0 && hours < 13 :
  5076. hours >= 0 && hours < 24;
  5077. if (!valid || $scope.hours === '') {
  5078. return undefined;
  5079. }
  5080. if ($scope.showMeridian) {
  5081. if (hours === 12) {
  5082. hours = 0;
  5083. }
  5084. if ($scope.meridian === meridians[1]) {
  5085. hours = hours + 12;
  5086. }
  5087. }
  5088. return hours;
  5089. }
  5090. function getMinutesFromTemplate() {
  5091. var minutes = +$scope.minutes;
  5092. var valid = minutes >= 0 && minutes < 60;
  5093. if (!valid || $scope.minutes === '') {
  5094. return undefined;
  5095. }
  5096. return minutes;
  5097. }
  5098. function getSecondsFromTemplate() {
  5099. var seconds = +$scope.seconds;
  5100. return seconds >= 0 && seconds < 60 ? seconds : undefined;
  5101. }
  5102. function pad(value, noPad) {
  5103. if (value === null) {
  5104. return '';
  5105. }
  5106. return angular.isDefined(value) && value.toString().length < 2 && !noPad ?
  5107. '0' + value : value.toString();
  5108. }
  5109. // Respond on mousewheel spin
  5110. this.setupMousewheelEvents = function(hoursInputEl, minutesInputEl, secondsInputEl) {
  5111. var isScrollingUp = function(e) {
  5112. if (e.originalEvent) {
  5113. e = e.originalEvent;
  5114. }
  5115. //pick correct delta variable depending on event
  5116. var delta = e.wheelDelta ? e.wheelDelta : -e.deltaY;
  5117. return e.detail || delta > 0;
  5118. };
  5119. hoursInputEl.bind('mousewheel wheel', function(e) {
  5120. if (!disabled) {
  5121. $scope.$apply(isScrollingUp(e) ? $scope.incrementHours() : $scope.decrementHours());
  5122. }
  5123. e.preventDefault();
  5124. });
  5125. minutesInputEl.bind('mousewheel wheel', function(e) {
  5126. if (!disabled) {
  5127. $scope.$apply(isScrollingUp(e) ? $scope.incrementMinutes() : $scope.decrementMinutes());
  5128. }
  5129. e.preventDefault();
  5130. });
  5131. secondsInputEl.bind('mousewheel wheel', function(e) {
  5132. if (!disabled) {
  5133. $scope.$apply(isScrollingUp(e) ? $scope.incrementSeconds() : $scope.decrementSeconds());
  5134. }
  5135. e.preventDefault();
  5136. });
  5137. };
  5138. // Respond on up/down arrowkeys
  5139. this.setupArrowkeyEvents = function(hoursInputEl, minutesInputEl, secondsInputEl) {
  5140. hoursInputEl.bind('keydown', function(e) {
  5141. if (!disabled) {
  5142. if (e.which === 38) { // up
  5143. e.preventDefault();
  5144. $scope.incrementHours();
  5145. $scope.$apply();
  5146. } else if (e.which === 40) { // down
  5147. e.preventDefault();
  5148. $scope.decrementHours();
  5149. $scope.$apply();
  5150. }
  5151. }
  5152. });
  5153. minutesInputEl.bind('keydown', function(e) {
  5154. if (!disabled) {
  5155. if (e.which === 38) { // up
  5156. e.preventDefault();
  5157. $scope.incrementMinutes();
  5158. $scope.$apply();
  5159. } else if (e.which === 40) { // down
  5160. e.preventDefault();
  5161. $scope.decrementMinutes();
  5162. $scope.$apply();
  5163. }
  5164. }
  5165. });
  5166. secondsInputEl.bind('keydown', function(e) {
  5167. if (!disabled) {
  5168. if (e.which === 38) { // up
  5169. e.preventDefault();
  5170. $scope.incrementSeconds();
  5171. $scope.$apply();
  5172. } else if (e.which === 40) { // down
  5173. e.preventDefault();
  5174. $scope.decrementSeconds();
  5175. $scope.$apply();
  5176. }
  5177. }
  5178. });
  5179. };
  5180. this.setupInputEvents = function(hoursInputEl, minutesInputEl, secondsInputEl) {
  5181. if ($scope.readonlyInput) {
  5182. $scope.updateHours = angular.noop;
  5183. $scope.updateMinutes = angular.noop;
  5184. $scope.updateSeconds = angular.noop;
  5185. return;
  5186. }
  5187. var invalidate = function(invalidHours, invalidMinutes, invalidSeconds) {
  5188. ngModelCtrl.$setViewValue(null);
  5189. ngModelCtrl.$setValidity('time', false);
  5190. if (angular.isDefined(invalidHours)) {
  5191. $scope.invalidHours = invalidHours;
  5192. }
  5193. if (angular.isDefined(invalidMinutes)) {
  5194. $scope.invalidMinutes = invalidMinutes;
  5195. }
  5196. if (angular.isDefined(invalidSeconds)) {
  5197. $scope.invalidSeconds = invalidSeconds;
  5198. }
  5199. };
  5200. $scope.updateHours = function() {
  5201. var hours = getHoursFromTemplate(),
  5202. minutes = getMinutesFromTemplate();
  5203. ngModelCtrl.$setDirty();
  5204. if (angular.isDefined(hours) && angular.isDefined(minutes)) {
  5205. selected.setHours(hours);
  5206. selected.setMinutes(minutes);
  5207. if (selected < min || selected > max) {
  5208. invalidate(true);
  5209. } else {
  5210. refresh('h');
  5211. }
  5212. } else {
  5213. invalidate(true);
  5214. }
  5215. };
  5216. hoursInputEl.bind('blur', function(e) {
  5217. ngModelCtrl.$setTouched();
  5218. if (modelIsEmpty()) {
  5219. makeValid();
  5220. } else if ($scope.hours === null || $scope.hours === '') {
  5221. invalidate(true);
  5222. } else if (!$scope.invalidHours && $scope.hours < 10) {
  5223. $scope.$apply(function() {
  5224. $scope.hours = pad($scope.hours, !padHours);
  5225. });
  5226. }
  5227. });
  5228. $scope.updateMinutes = function() {
  5229. var minutes = getMinutesFromTemplate(),
  5230. hours = getHoursFromTemplate();
  5231. ngModelCtrl.$setDirty();
  5232. if (angular.isDefined(minutes) && angular.isDefined(hours)) {
  5233. selected.setHours(hours);
  5234. selected.setMinutes(minutes);
  5235. if (selected < min || selected > max) {
  5236. invalidate(undefined, true);
  5237. } else {
  5238. refresh('m');
  5239. }
  5240. } else {
  5241. invalidate(undefined, true);
  5242. }
  5243. };
  5244. minutesInputEl.bind('blur', function(e) {
  5245. ngModelCtrl.$setTouched();
  5246. if (modelIsEmpty()) {
  5247. makeValid();
  5248. } else if ($scope.minutes === null) {
  5249. invalidate(undefined, true);
  5250. } else if (!$scope.invalidMinutes && $scope.minutes < 10) {
  5251. $scope.$apply(function() {
  5252. $scope.minutes = pad($scope.minutes);
  5253. });
  5254. }
  5255. });
  5256. $scope.updateSeconds = function() {
  5257. var seconds = getSecondsFromTemplate();
  5258. ngModelCtrl.$setDirty();
  5259. if (angular.isDefined(seconds)) {
  5260. selected.setSeconds(seconds);
  5261. refresh('s');
  5262. } else {
  5263. invalidate(undefined, undefined, true);
  5264. }
  5265. };
  5266. secondsInputEl.bind('blur', function(e) {
  5267. if (modelIsEmpty()) {
  5268. makeValid();
  5269. } else if (!$scope.invalidSeconds && $scope.seconds < 10) {
  5270. $scope.$apply( function() {
  5271. $scope.seconds = pad($scope.seconds);
  5272. });
  5273. }
  5274. });
  5275. };
  5276. this.render = function() {
  5277. var date = ngModelCtrl.$viewValue;
  5278. if (isNaN(date)) {
  5279. ngModelCtrl.$setValidity('time', false);
  5280. $log.error('Timepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.');
  5281. } else {
  5282. if (date) {
  5283. selected = date;
  5284. }
  5285. if (selected < min || selected > max) {
  5286. ngModelCtrl.$setValidity('time', false);
  5287. $scope.invalidHours = true;
  5288. $scope.invalidMinutes = true;
  5289. } else {
  5290. makeValid();
  5291. }
  5292. updateTemplate();
  5293. }
  5294. };
  5295. // Call internally when we know that model is valid.
  5296. function refresh(keyboardChange) {
  5297. makeValid();
  5298. ngModelCtrl.$setViewValue(new Date(selected));
  5299. updateTemplate(keyboardChange);
  5300. }
  5301. function makeValid() {
  5302. ngModelCtrl.$setValidity('time', true);
  5303. $scope.invalidHours = false;
  5304. $scope.invalidMinutes = false;
  5305. $scope.invalidSeconds = false;
  5306. }
  5307. function updateTemplate(keyboardChange) {
  5308. if (!ngModelCtrl.$modelValue) {
  5309. $scope.hours = null;
  5310. $scope.minutes = null;
  5311. $scope.seconds = null;
  5312. $scope.meridian = meridians[0];
  5313. } else {
  5314. var hours = selected.getHours(),
  5315. minutes = selected.getMinutes(),
  5316. seconds = selected.getSeconds();
  5317. if ($scope.showMeridian) {
  5318. hours = hours === 0 || hours === 12 ? 12 : hours % 12; // Convert 24 to 12 hour system
  5319. }
  5320. $scope.hours = keyboardChange === 'h' ? hours : pad(hours, !padHours);
  5321. if (keyboardChange !== 'm') {
  5322. $scope.minutes = pad(minutes);
  5323. }
  5324. $scope.meridian = selected.getHours() < 12 ? meridians[0] : meridians[1];
  5325. if (keyboardChange !== 's') {
  5326. $scope.seconds = pad(seconds);
  5327. }
  5328. $scope.meridian = selected.getHours() < 12 ? meridians[0] : meridians[1];
  5329. }
  5330. }
  5331. function addSecondsToSelected(seconds) {
  5332. selected = addSeconds(selected, seconds);
  5333. refresh();
  5334. }
  5335. function addMinutes(selected, minutes) {
  5336. return addSeconds(selected, minutes*60);
  5337. }
  5338. function addSeconds(date, seconds) {
  5339. var dt = new Date(date.getTime() + seconds * 1000);
  5340. var newDate = new Date(date);
  5341. newDate.setHours(dt.getHours(), dt.getMinutes(), dt.getSeconds());
  5342. return newDate;
  5343. }
  5344. function modelIsEmpty() {
  5345. return ($scope.hours === null || $scope.hours === '') &&
  5346. ($scope.minutes === null || $scope.minutes === '') &&
  5347. (!$scope.showSeconds || $scope.showSeconds && ($scope.seconds === null || $scope.seconds === ''));
  5348. }
  5349. $scope.showSpinners = angular.isDefined($attrs.showSpinners) ?
  5350. $scope.$parent.$eval($attrs.showSpinners) : timepickerConfig.showSpinners;
  5351. $scope.incrementHours = function() {
  5352. if (!$scope.noIncrementHours()) {
  5353. addSecondsToSelected(hourStep * 60 * 60);
  5354. }
  5355. };
  5356. $scope.decrementHours = function() {
  5357. if (!$scope.noDecrementHours()) {
  5358. addSecondsToSelected(-hourStep * 60 * 60);
  5359. }
  5360. };
  5361. $scope.incrementMinutes = function() {
  5362. if (!$scope.noIncrementMinutes()) {
  5363. addSecondsToSelected(minuteStep * 60);
  5364. }
  5365. };
  5366. $scope.decrementMinutes = function() {
  5367. if (!$scope.noDecrementMinutes()) {
  5368. addSecondsToSelected(-minuteStep * 60);
  5369. }
  5370. };
  5371. $scope.incrementSeconds = function() {
  5372. if (!$scope.noIncrementSeconds()) {
  5373. addSecondsToSelected(secondStep);
  5374. }
  5375. };
  5376. $scope.decrementSeconds = function() {
  5377. if (!$scope.noDecrementSeconds()) {
  5378. addSecondsToSelected(-secondStep);
  5379. }
  5380. };
  5381. $scope.toggleMeridian = function() {
  5382. var minutes = getMinutesFromTemplate(),
  5383. hours = getHoursFromTemplate();
  5384. if (!$scope.noToggleMeridian()) {
  5385. if (angular.isDefined(minutes) && angular.isDefined(hours)) {
  5386. addSecondsToSelected(12 * 60 * (selected.getHours() < 12 ? 60 : -60));
  5387. } else {
  5388. $scope.meridian = $scope.meridian === meridians[0] ? meridians[1] : meridians[0];
  5389. }
  5390. }
  5391. };
  5392. $scope.blur = function() {
  5393. ngModelCtrl.$setTouched();
  5394. };
  5395. $scope.$on('$destroy', function() {
  5396. while (watchers.length) {
  5397. watchers.shift()();
  5398. }
  5399. });
  5400. }])
  5401. .directive('uibTimepicker', ['uibTimepickerConfig', function(uibTimepickerConfig) {
  5402. return {
  5403. require: ['uibTimepicker', '?^ngModel'],
  5404. controller: 'UibTimepickerController',
  5405. controllerAs: 'timepicker',
  5406. replace: true,
  5407. scope: {},
  5408. templateUrl: function(element, attrs) {
  5409. return attrs.templateUrl || uibTimepickerConfig.templateUrl;
  5410. },
  5411. link: function(scope, element, attrs, ctrls) {
  5412. var timepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1];
  5413. if (ngModelCtrl) {
  5414. timepickerCtrl.init(ngModelCtrl, element.find('input'));
  5415. }
  5416. }
  5417. };
  5418. }]);
  5419. angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.debounce', 'ui.bootstrap.position'])
  5420. /**
  5421. * A helper service that can parse typeahead's syntax (string provided by users)
  5422. * Extracted to a separate service for ease of unit testing
  5423. */
  5424. .factory('uibTypeaheadParser', ['$parse', function($parse) {
  5425. // 00000111000000000000022200000000000000003333333333333330000000000044000
  5426. var TYPEAHEAD_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+([\s\S]+?)$/;
  5427. return {
  5428. parse: function(input) {
  5429. var match = input.match(TYPEAHEAD_REGEXP);
  5430. if (!match) {
  5431. throw new Error(
  5432. 'Expected typeahead specification in form of "_modelValue_ (as _label_)? for _item_ in _collection_"' +
  5433. ' but got "' + input + '".');
  5434. }
  5435. return {
  5436. itemName: match[3],
  5437. source: $parse(match[4]),
  5438. viewMapper: $parse(match[2] || match[1]),
  5439. modelMapper: $parse(match[1])
  5440. };
  5441. }
  5442. };
  5443. }])
  5444. .controller('UibTypeaheadController', ['$scope', '$element', '$attrs', '$compile', '$parse', '$q', '$timeout', '$document', '$window', '$rootScope', '$$debounce', '$uibPosition', 'uibTypeaheadParser',
  5445. function(originalScope, element, attrs, $compile, $parse, $q, $timeout, $document, $window, $rootScope, $$debounce, $position, typeaheadParser) {
  5446. var HOT_KEYS = [9, 13, 27, 38, 40];
  5447. var eventDebounceTime = 200;
  5448. var modelCtrl, ngModelOptions;
  5449. //SUPPORTED ATTRIBUTES (OPTIONS)
  5450. //minimal no of characters that needs to be entered before typeahead kicks-in
  5451. var minLength = originalScope.$eval(attrs.typeaheadMinLength);
  5452. if (!minLength && minLength !== 0) {
  5453. minLength = 1;
  5454. }
  5455. originalScope.$watch(attrs.typeaheadMinLength, function (newVal) {
  5456. minLength = !newVal && newVal !== 0 ? 1 : newVal;
  5457. });
  5458. //minimal wait time after last character typed before typeahead kicks-in
  5459. var waitTime = originalScope.$eval(attrs.typeaheadWaitMs) || 0;
  5460. //should it restrict model values to the ones selected from the popup only?
  5461. var isEditable = originalScope.$eval(attrs.typeaheadEditable) !== false;
  5462. originalScope.$watch(attrs.typeaheadEditable, function (newVal) {
  5463. isEditable = newVal !== false;
  5464. });
  5465. //binding to a variable that indicates if matches are being retrieved asynchronously
  5466. var isLoadingSetter = $parse(attrs.typeaheadLoading).assign || angular.noop;
  5467. //a function to determine if an event should cause selection
  5468. var isSelectEvent = attrs.typeaheadShouldSelect ? $parse(attrs.typeaheadShouldSelect) : function(scope, vals) {
  5469. var evt = vals.$event;
  5470. return evt.which === 13 || evt.which === 9;
  5471. };
  5472. //a callback executed when a match is selected
  5473. var onSelectCallback = $parse(attrs.typeaheadOnSelect);
  5474. //should it select highlighted popup value when losing focus?
  5475. var isSelectOnBlur = angular.isDefined(attrs.typeaheadSelectOnBlur) ? originalScope.$eval(attrs.typeaheadSelectOnBlur) : false;
  5476. //binding to a variable that indicates if there were no results after the query is completed
  5477. var isNoResultsSetter = $parse(attrs.typeaheadNoResults).assign || angular.noop;
  5478. var inputFormatter = attrs.typeaheadInputFormatter ? $parse(attrs.typeaheadInputFormatter) : undefined;
  5479. var appendToBody = attrs.typeaheadAppendToBody ? originalScope.$eval(attrs.typeaheadAppendToBody) : false;
  5480. var appendTo = attrs.typeaheadAppendTo ?
  5481. originalScope.$eval(attrs.typeaheadAppendTo) : null;
  5482. var focusFirst = originalScope.$eval(attrs.typeaheadFocusFirst) !== false;
  5483. //If input matches an item of the list exactly, select it automatically
  5484. var selectOnExact = attrs.typeaheadSelectOnExact ? originalScope.$eval(attrs.typeaheadSelectOnExact) : false;
  5485. //binding to a variable that indicates if dropdown is open
  5486. var isOpenSetter = $parse(attrs.typeaheadIsOpen).assign || angular.noop;
  5487. var showHint = originalScope.$eval(attrs.typeaheadShowHint) || false;
  5488. //INTERNAL VARIABLES
  5489. //model setter executed upon match selection
  5490. var parsedModel = $parse(attrs.ngModel);
  5491. var invokeModelSetter = $parse(attrs.ngModel + '($$$p)');
  5492. var $setModelValue = function(scope, newValue) {
  5493. if (angular.isFunction(parsedModel(originalScope)) &&
  5494. ngModelOptions && ngModelOptions.$options && ngModelOptions.$options.getterSetter) {
  5495. return invokeModelSetter(scope, {$$$p: newValue});
  5496. }
  5497. return parsedModel.assign(scope, newValue);
  5498. };
  5499. //expressions used by typeahead
  5500. var parserResult = typeaheadParser.parse(attrs.uibTypeahead);
  5501. var hasFocus;
  5502. //Used to avoid bug in iOS webview where iOS keyboard does not fire
  5503. //mousedown & mouseup events
  5504. //Issue #3699
  5505. var selected;
  5506. //create a child scope for the typeahead directive so we are not polluting original scope
  5507. //with typeahead-specific data (matches, query etc.)
  5508. var scope = originalScope.$new();
  5509. var offDestroy = originalScope.$on('$destroy', function() {
  5510. scope.$destroy();
  5511. });
  5512. scope.$on('$destroy', offDestroy);
  5513. // WAI-ARIA
  5514. var popupId = 'typeahead-' + scope.$id + '-' + Math.floor(Math.random() * 10000);
  5515. element.attr({
  5516. 'aria-autocomplete': 'list',
  5517. 'aria-expanded': false,
  5518. 'aria-owns': popupId
  5519. });
  5520. var inputsContainer, hintInputElem;
  5521. //add read-only input to show hint
  5522. if (showHint) {
  5523. inputsContainer = angular.element('<div></div>');
  5524. inputsContainer.css('position', 'relative');
  5525. element.after(inputsContainer);
  5526. hintInputElem = element.clone();
  5527. hintInputElem.attr('placeholder', '');
  5528. hintInputElem.attr('tabindex', '-1');
  5529. hintInputElem.val('');
  5530. hintInputElem.css({
  5531. 'position': 'absolute',
  5532. 'top': '0px',
  5533. 'left': '0px',
  5534. 'border-color': 'transparent',
  5535. 'box-shadow': 'none',
  5536. 'opacity': 1,
  5537. 'background': 'none 0% 0% / auto repeat scroll padding-box border-box rgb(255, 255, 255)',
  5538. 'color': '#999'
  5539. });
  5540. element.css({
  5541. 'position': 'relative',
  5542. 'vertical-align': 'top',
  5543. 'background-color': 'transparent'
  5544. });
  5545. inputsContainer.append(hintInputElem);
  5546. hintInputElem.after(element);
  5547. }
  5548. //pop-up element used to display matches
  5549. var popUpEl = angular.element('<div uib-typeahead-popup></div>');
  5550. popUpEl.attr({
  5551. id: popupId,
  5552. matches: 'matches',
  5553. active: 'activeIdx',
  5554. select: 'select(activeIdx, evt)',
  5555. 'move-in-progress': 'moveInProgress',
  5556. query: 'query',
  5557. position: 'position',
  5558. 'assign-is-open': 'assignIsOpen(isOpen)',
  5559. debounce: 'debounceUpdate'
  5560. });
  5561. //custom item template
  5562. if (angular.isDefined(attrs.typeaheadTemplateUrl)) {
  5563. popUpEl.attr('template-url', attrs.typeaheadTemplateUrl);
  5564. }
  5565. if (angular.isDefined(attrs.typeaheadPopupTemplateUrl)) {
  5566. popUpEl.attr('popup-template-url', attrs.typeaheadPopupTemplateUrl);
  5567. }
  5568. var resetHint = function() {
  5569. if (showHint) {
  5570. hintInputElem.val('');
  5571. }
  5572. };
  5573. var resetMatches = function() {
  5574. scope.matches = [];
  5575. scope.activeIdx = -1;
  5576. element.attr('aria-expanded', false);
  5577. resetHint();
  5578. };
  5579. var getMatchId = function(index) {
  5580. return popupId + '-option-' + index;
  5581. };
  5582. // Indicate that the specified match is the active (pre-selected) item in the list owned by this typeahead.
  5583. // This attribute is added or removed automatically when the `activeIdx` changes.
  5584. scope.$watch('activeIdx', function(index) {
  5585. if (index < 0) {
  5586. element.removeAttr('aria-activedescendant');
  5587. } else {
  5588. element.attr('aria-activedescendant', getMatchId(index));
  5589. }
  5590. });
  5591. var inputIsExactMatch = function(inputValue, index) {
  5592. if (scope.matches.length > index && inputValue) {
  5593. return inputValue.toUpperCase() === scope.matches[index].label.toUpperCase();
  5594. }
  5595. return false;
  5596. };
  5597. var getMatchesAsync = function(inputValue, evt) {
  5598. var locals = {$viewValue: inputValue};
  5599. isLoadingSetter(originalScope, true);
  5600. isNoResultsSetter(originalScope, false);
  5601. $q.when(parserResult.source(originalScope, locals)).then(function(matches) {
  5602. //it might happen that several async queries were in progress if a user were typing fast
  5603. //but we are interested only in responses that correspond to the current view value
  5604. var onCurrentRequest = inputValue === modelCtrl.$viewValue;
  5605. if (onCurrentRequest && hasFocus) {
  5606. if (matches && matches.length > 0) {
  5607. scope.activeIdx = focusFirst ? 0 : -1;
  5608. isNoResultsSetter(originalScope, false);
  5609. scope.matches.length = 0;
  5610. //transform labels
  5611. for (var i = 0; i < matches.length; i++) {
  5612. locals[parserResult.itemName] = matches[i];
  5613. scope.matches.push({
  5614. id: getMatchId(i),
  5615. label: parserResult.viewMapper(scope, locals),
  5616. model: matches[i]
  5617. });
  5618. }
  5619. scope.query = inputValue;
  5620. //position pop-up with matches - we need to re-calculate its position each time we are opening a window
  5621. //with matches as a pop-up might be absolute-positioned and position of an input might have changed on a page
  5622. //due to other elements being rendered
  5623. recalculatePosition();
  5624. element.attr('aria-expanded', true);
  5625. //Select the single remaining option if user input matches
  5626. if (selectOnExact && scope.matches.length === 1 && inputIsExactMatch(inputValue, 0)) {
  5627. if (angular.isNumber(scope.debounceUpdate) || angular.isObject(scope.debounceUpdate)) {
  5628. $$debounce(function() {
  5629. scope.select(0, evt);
  5630. }, angular.isNumber(scope.debounceUpdate) ? scope.debounceUpdate : scope.debounceUpdate['default']);
  5631. } else {
  5632. scope.select(0, evt);
  5633. }
  5634. }
  5635. if (showHint) {
  5636. var firstLabel = scope.matches[0].label;
  5637. if (angular.isString(inputValue) &&
  5638. inputValue.length > 0 &&
  5639. firstLabel.slice(0, inputValue.length).toUpperCase() === inputValue.toUpperCase()) {
  5640. hintInputElem.val(inputValue + firstLabel.slice(inputValue.length));
  5641. } else {
  5642. hintInputElem.val('');
  5643. }
  5644. }
  5645. } else {
  5646. resetMatches();
  5647. isNoResultsSetter(originalScope, true);
  5648. }
  5649. }
  5650. if (onCurrentRequest) {
  5651. isLoadingSetter(originalScope, false);
  5652. }
  5653. }, function() {
  5654. resetMatches();
  5655. isLoadingSetter(originalScope, false);
  5656. isNoResultsSetter(originalScope, true);
  5657. });
  5658. };
  5659. // bind events only if appendToBody params exist - performance feature
  5660. if (appendToBody) {
  5661. angular.element($window).on('resize', fireRecalculating);
  5662. $document.find('body').on('scroll', fireRecalculating);
  5663. }
  5664. // Declare the debounced function outside recalculating for
  5665. // proper debouncing
  5666. var debouncedRecalculate = $$debounce(function() {
  5667. // if popup is visible
  5668. if (scope.matches.length) {
  5669. recalculatePosition();
  5670. }
  5671. scope.moveInProgress = false;
  5672. }, eventDebounceTime);
  5673. // Default progress type
  5674. scope.moveInProgress = false;
  5675. function fireRecalculating() {
  5676. if (!scope.moveInProgress) {
  5677. scope.moveInProgress = true;
  5678. scope.$digest();
  5679. }
  5680. debouncedRecalculate();
  5681. }
  5682. // recalculate actual position and set new values to scope
  5683. // after digest loop is popup in right position
  5684. function recalculatePosition() {
  5685. scope.position = appendToBody ? $position.offset(element) : $position.position(element);
  5686. scope.position.top += element.prop('offsetHeight');
  5687. }
  5688. //we need to propagate user's query so we can higlight matches
  5689. scope.query = undefined;
  5690. //Declare the timeout promise var outside the function scope so that stacked calls can be cancelled later
  5691. var timeoutPromise;
  5692. var scheduleSearchWithTimeout = function(inputValue) {
  5693. timeoutPromise = $timeout(function() {
  5694. getMatchesAsync(inputValue);
  5695. }, waitTime);
  5696. };
  5697. var cancelPreviousTimeout = function() {
  5698. if (timeoutPromise) {
  5699. $timeout.cancel(timeoutPromise);
  5700. }
  5701. };
  5702. resetMatches();
  5703. scope.assignIsOpen = function (isOpen) {
  5704. isOpenSetter(originalScope, isOpen);
  5705. };
  5706. scope.select = function(activeIdx, evt) {
  5707. //called from within the $digest() cycle
  5708. var locals = {};
  5709. var model, item;
  5710. selected = true;
  5711. locals[parserResult.itemName] = item = scope.matches[activeIdx].model;
  5712. model = parserResult.modelMapper(originalScope, locals);
  5713. $setModelValue(originalScope, model);
  5714. modelCtrl.$setValidity('editable', true);
  5715. modelCtrl.$setValidity('parse', true);
  5716. onSelectCallback(originalScope, {
  5717. $item: item,
  5718. $model: model,
  5719. $label: parserResult.viewMapper(originalScope, locals),
  5720. $event: evt
  5721. });
  5722. resetMatches();
  5723. //return focus to the input element if a match was selected via a mouse click event
  5724. // use timeout to avoid $rootScope:inprog error
  5725. if (scope.$eval(attrs.typeaheadFocusOnSelect) !== false) {
  5726. $timeout(function() { element[0].focus(); }, 0, false);
  5727. }
  5728. };
  5729. //bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(27)
  5730. element.on('keydown', function(evt) {
  5731. //typeahead is open and an "interesting" key was pressed
  5732. if (scope.matches.length === 0 || HOT_KEYS.indexOf(evt.which) === -1) {
  5733. return;
  5734. }
  5735. var shouldSelect = isSelectEvent(originalScope, {$event: evt});
  5736. /**
  5737. * if there's nothing selected (i.e. focusFirst) and enter or tab is hit
  5738. * or
  5739. * shift + tab is pressed to bring focus to the previous element
  5740. * then clear the results
  5741. */
  5742. if (scope.activeIdx === -1 && shouldSelect || evt.which === 9 && !!evt.shiftKey) {
  5743. resetMatches();
  5744. scope.$digest();
  5745. return;
  5746. }
  5747. evt.preventDefault();
  5748. var target;
  5749. switch (evt.which) {
  5750. case 27: // escape
  5751. evt.stopPropagation();
  5752. resetMatches();
  5753. originalScope.$digest();
  5754. break;
  5755. case 38: // up arrow
  5756. scope.activeIdx = (scope.activeIdx > 0 ? scope.activeIdx : scope.matches.length) - 1;
  5757. scope.$digest();
  5758. target = popUpEl.find('li')[scope.activeIdx];
  5759. target.parentNode.scrollTop = target.offsetTop;
  5760. break;
  5761. case 40: // down arrow
  5762. scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length;
  5763. scope.$digest();
  5764. target = popUpEl.find('li')[scope.activeIdx];
  5765. target.parentNode.scrollTop = target.offsetTop;
  5766. break;
  5767. default:
  5768. if (shouldSelect) {
  5769. scope.$apply(function() {
  5770. if (angular.isNumber(scope.debounceUpdate) || angular.isObject(scope.debounceUpdate)) {
  5771. $$debounce(function() {
  5772. scope.select(scope.activeIdx, evt);
  5773. }, angular.isNumber(scope.debounceUpdate) ? scope.debounceUpdate : scope.debounceUpdate['default']);
  5774. } else {
  5775. scope.select(scope.activeIdx, evt);
  5776. }
  5777. });
  5778. }
  5779. }
  5780. });
  5781. element.bind('focus', function (evt) {
  5782. hasFocus = true;
  5783. if (minLength === 0 && !modelCtrl.$viewValue) {
  5784. $timeout(function() {
  5785. getMatchesAsync(modelCtrl.$viewValue, evt);
  5786. }, 0);
  5787. }
  5788. });
  5789. element.bind('blur', function(evt) {
  5790. if (isSelectOnBlur && scope.matches.length && scope.activeIdx !== -1 && !selected) {
  5791. selected = true;
  5792. scope.$apply(function() {
  5793. if (angular.isObject(scope.debounceUpdate) && angular.isNumber(scope.debounceUpdate.blur)) {
  5794. $$debounce(function() {
  5795. scope.select(scope.activeIdx, evt);
  5796. }, scope.debounceUpdate.blur);
  5797. } else {
  5798. scope.select(scope.activeIdx, evt);
  5799. }
  5800. });
  5801. }
  5802. if (!isEditable && modelCtrl.$error.editable) {
  5803. modelCtrl.$setViewValue();
  5804. // Reset validity as we are clearing
  5805. modelCtrl.$setValidity('editable', true);
  5806. modelCtrl.$setValidity('parse', true);
  5807. element.val('');
  5808. }
  5809. hasFocus = false;
  5810. selected = false;
  5811. });
  5812. // Keep reference to click handler to unbind it.
  5813. var dismissClickHandler = function(evt) {
  5814. // Issue #3973
  5815. // Firefox treats right click as a click on document
  5816. if (element[0] !== evt.target && evt.which !== 3 && scope.matches.length !== 0) {
  5817. resetMatches();
  5818. if (!$rootScope.$$phase) {
  5819. originalScope.$digest();
  5820. }
  5821. }
  5822. };
  5823. $document.on('click', dismissClickHandler);
  5824. originalScope.$on('$destroy', function() {
  5825. $document.off('click', dismissClickHandler);
  5826. if (appendToBody || appendTo) {
  5827. $popup.remove();
  5828. }
  5829. if (appendToBody) {
  5830. angular.element($window).off('resize', fireRecalculating);
  5831. $document.find('body').off('scroll', fireRecalculating);
  5832. }
  5833. // Prevent jQuery cache memory leak
  5834. popUpEl.remove();
  5835. if (showHint) {
  5836. inputsContainer.remove();
  5837. }
  5838. });
  5839. var $popup = $compile(popUpEl)(scope);
  5840. if (appendToBody) {
  5841. $document.find('body').append($popup);
  5842. } else if (appendTo) {
  5843. angular.element(appendTo).eq(0).append($popup);
  5844. } else {
  5845. element.after($popup);
  5846. }
  5847. this.init = function(_modelCtrl, _ngModelOptions) {
  5848. modelCtrl = _modelCtrl;
  5849. ngModelOptions = _ngModelOptions;
  5850. scope.debounceUpdate = modelCtrl.$options && $parse(modelCtrl.$options.debounce)(originalScope);
  5851. //plug into $parsers pipeline to open a typeahead on view changes initiated from DOM
  5852. //$parsers kick-in on all the changes coming from the view as well as manually triggered by $setViewValue
  5853. modelCtrl.$parsers.unshift(function(inputValue) {
  5854. hasFocus = true;
  5855. if (minLength === 0 || inputValue && inputValue.length >= minLength) {
  5856. if (waitTime > 0) {
  5857. cancelPreviousTimeout();
  5858. scheduleSearchWithTimeout(inputValue);
  5859. } else {
  5860. getMatchesAsync(inputValue);
  5861. }
  5862. } else {
  5863. isLoadingSetter(originalScope, false);
  5864. cancelPreviousTimeout();
  5865. resetMatches();
  5866. }
  5867. if (isEditable) {
  5868. return inputValue;
  5869. }
  5870. if (!inputValue) {
  5871. // Reset in case user had typed something previously.
  5872. modelCtrl.$setValidity('editable', true);
  5873. return null;
  5874. }
  5875. modelCtrl.$setValidity('editable', false);
  5876. return undefined;
  5877. });
  5878. modelCtrl.$formatters.push(function(modelValue) {
  5879. var candidateViewValue, emptyViewValue;
  5880. var locals = {};
  5881. // The validity may be set to false via $parsers (see above) if
  5882. // the model is restricted to selected values. If the model
  5883. // is set manually it is considered to be valid.
  5884. if (!isEditable) {
  5885. modelCtrl.$setValidity('editable', true);
  5886. }
  5887. if (inputFormatter) {
  5888. locals.$model = modelValue;
  5889. return inputFormatter(originalScope, locals);
  5890. }
  5891. //it might happen that we don't have enough info to properly render input value
  5892. //we need to check for this situation and simply return model value if we can't apply custom formatting
  5893. locals[parserResult.itemName] = modelValue;
  5894. candidateViewValue = parserResult.viewMapper(originalScope, locals);
  5895. locals[parserResult.itemName] = undefined;
  5896. emptyViewValue = parserResult.viewMapper(originalScope, locals);
  5897. return candidateViewValue !== emptyViewValue ? candidateViewValue : modelValue;
  5898. });
  5899. };
  5900. }])
  5901. .directive('uibTypeahead', function() {
  5902. return {
  5903. controller: 'UibTypeaheadController',
  5904. require: ['ngModel', '^?ngModelOptions', 'uibTypeahead'],
  5905. link: function(originalScope, element, attrs, ctrls) {
  5906. ctrls[2].init(ctrls[0], ctrls[1]);
  5907. }
  5908. };
  5909. })
  5910. .directive('uibTypeaheadPopup', ['$$debounce', function($$debounce) {
  5911. return {
  5912. scope: {
  5913. matches: '=',
  5914. query: '=',
  5915. active: '=',
  5916. position: '&',
  5917. moveInProgress: '=',
  5918. select: '&',
  5919. assignIsOpen: '&',
  5920. debounce: '&'
  5921. },
  5922. replace: true,
  5923. templateUrl: function(element, attrs) {
  5924. return attrs.popupTemplateUrl || 'uib/template/typeahead/typeahead-popup.html';
  5925. },
  5926. link: function(scope, element, attrs) {
  5927. scope.templateUrl = attrs.templateUrl;
  5928. scope.isOpen = function() {
  5929. var isDropdownOpen = scope.matches.length > 0;
  5930. scope.assignIsOpen({ isOpen: isDropdownOpen });
  5931. return isDropdownOpen;
  5932. };
  5933. scope.isActive = function(matchIdx) {
  5934. return scope.active === matchIdx;
  5935. };
  5936. scope.selectActive = function(matchIdx) {
  5937. scope.active = matchIdx;
  5938. };
  5939. scope.selectMatch = function(activeIdx, evt) {
  5940. var debounce = scope.debounce();
  5941. if (angular.isNumber(debounce) || angular.isObject(debounce)) {
  5942. $$debounce(function() {
  5943. scope.select({activeIdx: activeIdx, evt: evt});
  5944. }, angular.isNumber(debounce) ? debounce : debounce['default']);
  5945. } else {
  5946. scope.select({activeIdx: activeIdx, evt: evt});
  5947. }
  5948. };
  5949. }
  5950. };
  5951. }])
  5952. .directive('uibTypeaheadMatch', ['$templateRequest', '$compile', '$parse', function($templateRequest, $compile, $parse) {
  5953. return {
  5954. scope: {
  5955. index: '=',
  5956. match: '=',
  5957. query: '='
  5958. },
  5959. link: function(scope, element, attrs) {
  5960. var tplUrl = $parse(attrs.templateUrl)(scope.$parent) || 'uib/template/typeahead/typeahead-match.html';
  5961. $templateRequest(tplUrl).then(function(tplContent) {
  5962. var tplEl = angular.element(tplContent.trim());
  5963. element.replaceWith(tplEl);
  5964. $compile(tplEl)(scope);
  5965. });
  5966. }
  5967. };
  5968. }])
  5969. .filter('uibTypeaheadHighlight', ['$sce', '$injector', '$log', function($sce, $injector, $log) {
  5970. var isSanitizePresent;
  5971. isSanitizePresent = $injector.has('$sanitize');
  5972. function escapeRegexp(queryToEscape) {
  5973. // Regex: capture the whole query string and replace it with the string that will be used to match
  5974. // the results, for example if the capture is "a" the result will be \a
  5975. return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1');
  5976. }
  5977. function containsHtml(matchItem) {
  5978. return /<.*>/g.test(matchItem);
  5979. }
  5980. return function(matchItem, query) {
  5981. if (!isSanitizePresent && containsHtml(matchItem)) {
  5982. $log.warn('Unsafe use of typeahead please use ngSanitize'); // Warn the user about the danger
  5983. }
  5984. matchItem = query ? ('' + matchItem).replace(new RegExp(escapeRegexp(query), 'gi'), '<strong>$&</strong>') : matchItem; // Replaces the capture string with a the same string inside of a "strong" tag
  5985. if (!isSanitizePresent) {
  5986. matchItem = $sce.trustAsHtml(matchItem); // If $sanitize is not present we pack the string in a $sce object for the ng-bind-html directive
  5987. }
  5988. return matchItem;
  5989. };
  5990. }]);
  5991. angular.module('ui.bootstrap.carousel').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibCarouselCss && angular.element(document).find('head').prepend('<style type="text/css">.ng-animate.item:not(.left):not(.right){-webkit-transition:0s ease-in-out left;transition:0s ease-in-out left}</style>'); angular.$$uibCarouselCss = true; });
  5992. angular.module('ui.bootstrap.datepicker').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibDatepickerCss && angular.element(document).find('head').prepend('<style type="text/css">.uib-datepicker .uib-title{width:100%;}.uib-day button,.uib-month button,.uib-year button{min-width:100%;}.uib-left,.uib-right{width:100%}</style>'); angular.$$uibDatepickerCss = true; });
  5993. angular.module('ui.bootstrap.position').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibPositionCss && angular.element(document).find('head').prepend('<style type="text/css">.uib-position-measure{display:block !important;visibility:hidden !important;position:absolute !important;top:-9999px !important;left:-9999px !important;}.uib-position-scrollbar-measure{position:absolute !important;top:-9999px !important;width:50px !important;height:50px !important;overflow:scroll !important;}.uib-position-body-scrollbar-measure{overflow:scroll !important;}</style>'); angular.$$uibPositionCss = true; });
  5994. angular.module('ui.bootstrap.datepickerPopup').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibDatepickerpopupCss && angular.element(document).find('head').prepend('<style type="text/css">.uib-datepicker-popup.dropdown-menu{display:block;float:none;margin:0;}.uib-button-bar{padding:10px 9px 2px;}</style>'); angular.$$uibDatepickerpopupCss = true; });
  5995. angular.module('ui.bootstrap.tooltip').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibTooltipCss && angular.element(document).find('head').prepend('<style type="text/css">[uib-tooltip-popup].tooltip.top-left > .tooltip-arrow,[uib-tooltip-popup].tooltip.top-right > .tooltip-arrow,[uib-tooltip-popup].tooltip.bottom-left > .tooltip-arrow,[uib-tooltip-popup].tooltip.bottom-right > .tooltip-arrow,[uib-tooltip-popup].tooltip.left-top > .tooltip-arrow,[uib-tooltip-popup].tooltip.left-bottom > .tooltip-arrow,[uib-tooltip-popup].tooltip.right-top > .tooltip-arrow,[uib-tooltip-popup].tooltip.right-bottom > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.top-left > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.top-right > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.bottom-left > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.bottom-right > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.left-top > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.left-bottom > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.right-top > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.right-bottom > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.top-left > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.top-right > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.bottom-left > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.bottom-right > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.left-top > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.left-bottom > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.right-top > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.right-bottom > .tooltip-arrow,[uib-popover-popup].popover.top-left > .arrow,[uib-popover-popup].popover.top-right > .arrow,[uib-popover-popup].popover.bottom-left > .arrow,[uib-popover-popup].popover.bottom-right > .arrow,[uib-popover-popup].popover.left-top > .arrow,[uib-popover-popup].popover.left-bottom > .arrow,[uib-popover-popup].popover.right-top > .arrow,[uib-popover-popup].popover.right-bottom > .arrow,[uib-popover-html-popup].popover.top-left > .arrow,[uib-popover-html-popup].popover.top-right > .arrow,[uib-popover-html-popup].popover.bottom-left > .arrow,[uib-popover-html-popup].popover.bottom-right > .arrow,[uib-popover-html-popup].popover.left-top > .arrow,[uib-popover-html-popup].popover.left-bottom > .arrow,[uib-popover-html-popup].popover.right-top > .arrow,[uib-popover-html-popup].popover.right-bottom > .arrow,[uib-popover-template-popup].popover.top-left > .arrow,[uib-popover-template-popup].popover.top-right > .arrow,[uib-popover-template-popup].popover.bottom-left > .arrow,[uib-popover-template-popup].popover.bottom-right > .arrow,[uib-popover-template-popup].popover.left-top > .arrow,[uib-popover-template-popup].popover.left-bottom > .arrow,[uib-popover-template-popup].popover.right-top > .arrow,[uib-popover-template-popup].popover.right-bottom > .arrow{top:auto;bottom:auto;left:auto;right:auto;margin:0;}[uib-popover-popup].popover,[uib-popover-html-popup].popover,[uib-popover-template-popup].popover{display:block !important;}</style>'); angular.$$uibTooltipCss = true; });
  5996. angular.module('ui.bootstrap.timepicker').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibTimepickerCss && angular.element(document).find('head').prepend('<style type="text/css">.uib-time input{width:50px;}</style>'); angular.$$uibTimepickerCss = true; });
  5997. angular.module('ui.bootstrap.typeahead').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibTypeaheadCss && angular.element(document).find('head').prepend('<style type="text/css">[uib-typeahead-popup].dropdown-menu{display:block;}</style>'); angular.$$uibTypeaheadCss = true; });