unpoly.js 528 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783678467856786678767886789679067916792679367946795679667976798679968006801680268036804680568066807680868096810681168126813681468156816681768186819682068216822682368246825682668276828682968306831683268336834683568366837683868396840684168426843684468456846684768486849685068516852685368546855685668576858685968606861686268636864686568666867686868696870687168726873687468756876687768786879688068816882688368846885688668876888688968906891689268936894689568966897689868996900690169026903690469056906690769086909691069116912691369146915691669176918691969206921692269236924692569266927692869296930693169326933693469356936693769386939694069416942694369446945694669476948694969506951695269536954695569566957695869596960696169626963696469656966696769686969697069716972697369746975697669776978697969806981698269836984698569866987698869896990699169926993699469956996699769986999700070017002700370047005700670077008700970107011701270137014701570167017701870197020702170227023702470257026702770287029703070317032703370347035703670377038703970407041704270437044704570467047704870497050705170527053705470557056705770587059706070617062706370647065706670677068706970707071707270737074707570767077707870797080708170827083708470857086708770887089709070917092709370947095709670977098709971007101710271037104710571067107710871097110711171127113711471157116711771187119712071217122712371247125712671277128712971307131713271337134713571367137713871397140714171427143714471457146714771487149715071517152715371547155715671577158715971607161716271637164716571667167716871697170717171727173717471757176717771787179718071817182718371847185718671877188718971907191719271937194719571967197719871997200720172027203720472057206720772087209721072117212721372147215721672177218721972207221722272237224722572267227722872297230723172327233723472357236723772387239724072417242724372447245724672477248724972507251725272537254725572567257725872597260726172627263726472657266726772687269727072717272727372747275727672777278727972807281728272837284728572867287728872897290729172927293729472957296729772987299730073017302730373047305730673077308730973107311731273137314731573167317731873197320732173227323732473257326732773287329733073317332733373347335733673377338733973407341734273437344734573467347734873497350735173527353735473557356735773587359736073617362736373647365736673677368736973707371737273737374737573767377737873797380738173827383738473857386738773887389739073917392739373947395739673977398739974007401740274037404740574067407740874097410741174127413741474157416741774187419742074217422742374247425742674277428742974307431743274337434743574367437743874397440744174427443744474457446744774487449745074517452745374547455745674577458745974607461746274637464746574667467746874697470747174727473747474757476747774787479748074817482748374847485748674877488748974907491749274937494749574967497749874997500750175027503750475057506750775087509751075117512751375147515751675177518751975207521752275237524752575267527752875297530753175327533753475357536753775387539754075417542754375447545754675477548754975507551755275537554755575567557755875597560756175627563756475657566756775687569757075717572757375747575757675777578757975807581758275837584758575867587758875897590759175927593759475957596759775987599760076017602760376047605760676077608760976107611761276137614761576167617761876197620762176227623762476257626762776287629763076317632763376347635763676377638763976407641764276437644764576467647764876497650765176527653765476557656765776587659766076617662766376647665766676677668766976707671767276737674767576767677767876797680768176827683768476857686768776887689769076917692769376947695769676977698769977007701770277037704770577067707770877097710771177127713771477157716771777187719772077217722772377247725772677277728772977307731773277337734773577367737773877397740774177427743774477457746774777487749775077517752775377547755775677577758775977607761776277637764776577667767776877697770777177727773777477757776777777787779778077817782778377847785778677877788778977907791779277937794779577967797779877997800780178027803780478057806780778087809781078117812781378147815781678177818781978207821782278237824782578267827782878297830783178327833783478357836783778387839784078417842784378447845784678477848784978507851785278537854785578567857785878597860786178627863786478657866786778687869787078717872787378747875787678777878787978807881788278837884788578867887788878897890789178927893789478957896789778987899790079017902790379047905790679077908790979107911791279137914791579167917791879197920792179227923792479257926792779287929793079317932793379347935793679377938793979407941794279437944794579467947794879497950795179527953795479557956795779587959796079617962796379647965796679677968796979707971797279737974797579767977797879797980798179827983798479857986798779887989799079917992799379947995799679977998799980008001800280038004800580068007800880098010801180128013801480158016801780188019802080218022802380248025802680278028802980308031803280338034803580368037803880398040804180428043804480458046804780488049805080518052805380548055805680578058805980608061806280638064806580668067806880698070807180728073807480758076807780788079808080818082808380848085808680878088808980908091809280938094809580968097809880998100810181028103810481058106810781088109811081118112811381148115811681178118811981208121812281238124812581268127812881298130813181328133813481358136813781388139814081418142814381448145814681478148814981508151815281538154815581568157815881598160816181628163816481658166816781688169817081718172817381748175817681778178817981808181818281838184818581868187818881898190819181928193819481958196819781988199820082018202820382048205820682078208820982108211821282138214821582168217821882198220822182228223822482258226822782288229823082318232823382348235823682378238823982408241824282438244824582468247824882498250825182528253825482558256825782588259826082618262826382648265826682678268826982708271827282738274827582768277827882798280828182828283828482858286828782888289829082918292829382948295829682978298829983008301830283038304830583068307830883098310831183128313831483158316831783188319832083218322832383248325832683278328832983308331833283338334833583368337833883398340834183428343834483458346834783488349835083518352835383548355835683578358835983608361836283638364836583668367836883698370837183728373837483758376837783788379838083818382838383848385838683878388838983908391839283938394839583968397839883998400840184028403840484058406840784088409841084118412841384148415841684178418841984208421842284238424842584268427842884298430843184328433843484358436843784388439844084418442844384448445844684478448844984508451845284538454845584568457845884598460846184628463846484658466846784688469847084718472847384748475847684778478847984808481848284838484848584868487848884898490849184928493849484958496849784988499850085018502850385048505850685078508850985108511851285138514851585168517851885198520852185228523852485258526852785288529853085318532853385348535853685378538853985408541854285438544854585468547854885498550855185528553855485558556855785588559856085618562856385648565856685678568856985708571857285738574857585768577857885798580858185828583858485858586858785888589859085918592859385948595859685978598859986008601860286038604860586068607860886098610861186128613861486158616861786188619862086218622862386248625862686278628862986308631863286338634863586368637863886398640864186428643864486458646864786488649865086518652865386548655865686578658865986608661866286638664866586668667866886698670867186728673867486758676867786788679868086818682868386848685868686878688868986908691869286938694869586968697869886998700870187028703870487058706870787088709871087118712871387148715871687178718871987208721872287238724872587268727872887298730873187328733873487358736873787388739874087418742874387448745874687478748874987508751875287538754875587568757875887598760876187628763876487658766876787688769877087718772877387748775877687778778877987808781878287838784878587868787878887898790879187928793879487958796879787988799880088018802880388048805880688078808880988108811881288138814881588168817881888198820882188228823882488258826882788288829883088318832883388348835883688378838883988408841884288438844884588468847884888498850885188528853885488558856885788588859886088618862886388648865886688678868886988708871887288738874887588768877887888798880888188828883888488858886888788888889889088918892889388948895889688978898889989008901890289038904890589068907890889098910891189128913891489158916891789188919892089218922892389248925892689278928892989308931893289338934893589368937893889398940894189428943894489458946894789488949895089518952895389548955895689578958895989608961896289638964896589668967896889698970897189728973897489758976897789788979898089818982898389848985898689878988898989908991899289938994899589968997899889999000900190029003900490059006900790089009901090119012901390149015901690179018901990209021902290239024902590269027902890299030903190329033903490359036903790389039904090419042904390449045904690479048904990509051905290539054905590569057905890599060906190629063906490659066906790689069907090719072907390749075907690779078907990809081908290839084908590869087908890899090909190929093909490959096909790989099910091019102910391049105910691079108910991109111911291139114911591169117911891199120912191229123912491259126912791289129913091319132913391349135913691379138913991409141914291439144914591469147914891499150915191529153915491559156915791589159916091619162916391649165916691679168916991709171917291739174917591769177917891799180918191829183918491859186918791889189919091919192919391949195919691979198919992009201920292039204920592069207920892099210921192129213921492159216921792189219922092219222922392249225922692279228922992309231923292339234923592369237923892399240924192429243924492459246924792489249925092519252925392549255925692579258925992609261926292639264926592669267926892699270927192729273927492759276927792789279928092819282928392849285928692879288928992909291929292939294929592969297929892999300930193029303930493059306930793089309931093119312931393149315931693179318931993209321932293239324932593269327932893299330933193329333933493359336933793389339934093419342934393449345934693479348934993509351935293539354935593569357935893599360936193629363936493659366936793689369937093719372937393749375937693779378937993809381938293839384938593869387938893899390939193929393939493959396939793989399940094019402940394049405940694079408940994109411941294139414941594169417941894199420942194229423942494259426942794289429943094319432943394349435943694379438943994409441944294439444944594469447944894499450945194529453945494559456945794589459946094619462946394649465946694679468946994709471947294739474947594769477947894799480948194829483948494859486948794889489949094919492949394949495949694979498949995009501950295039504950595069507950895099510951195129513951495159516951795189519952095219522952395249525952695279528952995309531953295339534953595369537953895399540954195429543954495459546954795489549955095519552955395549555955695579558955995609561956295639564956595669567956895699570957195729573957495759576957795789579958095819582958395849585958695879588958995909591959295939594959595969597959895999600960196029603960496059606960796089609961096119612961396149615961696179618961996209621962296239624962596269627962896299630963196329633963496359636963796389639964096419642964396449645964696479648964996509651965296539654965596569657965896599660966196629663966496659666966796689669967096719672967396749675967696779678967996809681968296839684968596869687968896899690969196929693969496959696969796989699970097019702970397049705970697079708970997109711971297139714971597169717971897199720972197229723972497259726972797289729973097319732973397349735973697379738973997409741974297439744974597469747974897499750975197529753975497559756975797589759976097619762976397649765976697679768976997709771977297739774977597769777977897799780978197829783978497859786978797889789979097919792979397949795979697979798979998009801980298039804980598069807980898099810981198129813981498159816981798189819982098219822982398249825982698279828982998309831983298339834983598369837983898399840984198429843984498459846984798489849985098519852985398549855985698579858985998609861986298639864986598669867986898699870987198729873987498759876987798789879988098819882988398849885988698879888988998909891989298939894989598969897989898999900990199029903990499059906990799089909991099119912991399149915991699179918991999209921992299239924992599269927992899299930993199329933993499359936993799389939994099419942994399449945994699479948994999509951995299539954995599569957995899599960996199629963996499659966996799689969997099719972997399749975997699779978997999809981998299839984998599869987998899899990999199929993999499959996999799989999100001000110002100031000410005100061000710008100091001010011100121001310014100151001610017100181001910020100211002210023100241002510026100271002810029100301003110032100331003410035100361003710038100391004010041100421004310044100451004610047100481004910050100511005210053100541005510056100571005810059100601006110062100631006410065100661006710068100691007010071100721007310074100751007610077100781007910080100811008210083100841008510086100871008810089100901009110092100931009410095100961009710098100991010010101101021010310104101051010610107101081010910110101111011210113101141011510116101171011810119101201012110122101231012410125101261012710128101291013010131101321013310134101351013610137101381013910140101411014210143101441014510146101471014810149101501015110152101531015410155101561015710158101591016010161101621016310164101651016610167101681016910170101711017210173101741017510176101771017810179101801018110182101831018410185101861018710188101891019010191101921019310194101951019610197101981019910200102011020210203102041020510206102071020810209102101021110212102131021410215102161021710218102191022010221102221022310224102251022610227102281022910230102311023210233102341023510236102371023810239102401024110242102431024410245102461024710248102491025010251102521025310254102551025610257102581025910260102611026210263102641026510266102671026810269102701027110272102731027410275102761027710278102791028010281102821028310284102851028610287102881028910290102911029210293102941029510296102971029810299103001030110302103031030410305103061030710308103091031010311103121031310314103151031610317103181031910320103211032210323103241032510326103271032810329103301033110332103331033410335103361033710338103391034010341103421034310344103451034610347103481034910350103511035210353103541035510356103571035810359103601036110362103631036410365103661036710368103691037010371103721037310374103751037610377103781037910380103811038210383103841038510386103871038810389103901039110392103931039410395103961039710398103991040010401104021040310404104051040610407104081040910410104111041210413104141041510416104171041810419104201042110422104231042410425104261042710428104291043010431104321043310434104351043610437104381043910440104411044210443104441044510446104471044810449104501045110452104531045410455104561045710458104591046010461104621046310464104651046610467104681046910470104711047210473104741047510476104771047810479104801048110482104831048410485104861048710488104891049010491104921049310494104951049610497104981049910500105011050210503105041050510506105071050810509105101051110512105131051410515105161051710518105191052010521105221052310524105251052610527105281052910530105311053210533105341053510536105371053810539105401054110542105431054410545105461054710548105491055010551105521055310554105551055610557105581055910560105611056210563105641056510566105671056810569105701057110572105731057410575105761057710578105791058010581105821058310584105851058610587105881058910590105911059210593105941059510596105971059810599106001060110602106031060410605106061060710608106091061010611106121061310614106151061610617106181061910620106211062210623106241062510626106271062810629106301063110632106331063410635106361063710638106391064010641106421064310644106451064610647106481064910650106511065210653106541065510656106571065810659106601066110662106631066410665106661066710668106691067010671106721067310674106751067610677106781067910680106811068210683106841068510686106871068810689106901069110692106931069410695106961069710698106991070010701107021070310704107051070610707107081070910710107111071210713107141071510716107171071810719107201072110722107231072410725107261072710728107291073010731107321073310734107351073610737107381073910740107411074210743107441074510746107471074810749107501075110752107531075410755107561075710758107591076010761107621076310764107651076610767107681076910770107711077210773107741077510776107771077810779107801078110782107831078410785107861078710788107891079010791107921079310794107951079610797107981079910800108011080210803108041080510806108071080810809108101081110812108131081410815108161081710818108191082010821108221082310824108251082610827108281082910830108311083210833108341083510836108371083810839108401084110842108431084410845108461084710848108491085010851108521085310854108551085610857108581085910860108611086210863108641086510866108671086810869108701087110872108731087410875108761087710878108791088010881108821088310884108851088610887108881088910890108911089210893108941089510896108971089810899109001090110902109031090410905109061090710908109091091010911109121091310914109151091610917109181091910920109211092210923109241092510926109271092810929109301093110932109331093410935109361093710938109391094010941109421094310944109451094610947109481094910950109511095210953109541095510956109571095810959109601096110962109631096410965109661096710968109691097010971109721097310974109751097610977109781097910980109811098210983109841098510986109871098810989109901099110992109931099410995109961099710998109991100011001110021100311004110051100611007110081100911010110111101211013110141101511016110171101811019110201102111022110231102411025110261102711028110291103011031110321103311034110351103611037110381103911040110411104211043110441104511046110471104811049110501105111052110531105411055110561105711058110591106011061110621106311064110651106611067110681106911070110711107211073110741107511076110771107811079110801108111082110831108411085110861108711088110891109011091110921109311094110951109611097110981109911100111011110211103111041110511106111071110811109111101111111112111131111411115111161111711118111191112011121111221112311124111251112611127111281112911130111311113211133111341113511136111371113811139111401114111142111431114411145111461114711148111491115011151111521115311154111551115611157111581115911160111611116211163111641116511166111671116811169111701117111172111731117411175111761117711178111791118011181111821118311184111851118611187111881118911190111911119211193111941119511196111971119811199112001120111202112031120411205112061120711208112091121011211112121121311214112151121611217112181121911220112211122211223112241122511226112271122811229112301123111232112331123411235112361123711238112391124011241112421124311244112451124611247112481124911250112511125211253112541125511256112571125811259112601126111262112631126411265112661126711268112691127011271112721127311274112751127611277112781127911280112811128211283112841128511286112871128811289112901129111292112931129411295112961129711298112991130011301113021130311304113051130611307113081130911310113111131211313113141131511316113171131811319113201132111322113231132411325113261132711328113291133011331113321133311334113351133611337113381133911340113411134211343113441134511346113471134811349113501135111352113531135411355113561135711358113591136011361113621136311364113651136611367113681136911370113711137211373113741137511376113771137811379113801138111382113831138411385113861138711388113891139011391113921139311394113951139611397113981139911400114011140211403114041140511406114071140811409114101141111412114131141411415114161141711418114191142011421114221142311424114251142611427114281142911430114311143211433114341143511436114371143811439114401144111442114431144411445114461144711448114491145011451114521145311454114551145611457114581145911460114611146211463114641146511466114671146811469114701147111472114731147411475114761147711478114791148011481114821148311484114851148611487114881148911490114911149211493114941149511496114971149811499115001150111502115031150411505115061150711508115091151011511115121151311514115151151611517115181151911520115211152211523115241152511526115271152811529115301153111532115331153411535115361153711538115391154011541115421154311544115451154611547115481154911550115511155211553115541155511556115571155811559115601156111562115631156411565115661156711568115691157011571115721157311574115751157611577115781157911580115811158211583115841158511586115871158811589115901159111592115931159411595115961159711598115991160011601116021160311604116051160611607116081160911610116111161211613116141161511616116171161811619116201162111622116231162411625116261162711628116291163011631116321163311634116351163611637116381163911640116411164211643116441164511646116471164811649116501165111652116531165411655116561165711658116591166011661116621166311664116651166611667116681166911670116711167211673116741167511676116771167811679116801168111682116831168411685116861168711688116891169011691116921169311694116951169611697116981169911700117011170211703117041170511706117071170811709117101171111712117131171411715117161171711718117191172011721117221172311724117251172611727117281172911730117311173211733117341173511736117371173811739117401174111742117431174411745117461174711748117491175011751117521175311754117551175611757117581175911760117611176211763117641176511766117671176811769117701177111772117731177411775117761177711778117791178011781117821178311784117851178611787117881178911790117911179211793117941179511796117971179811799118001180111802118031180411805118061180711808118091181011811118121181311814118151181611817118181181911820118211182211823118241182511826118271182811829118301183111832118331183411835118361183711838118391184011841118421184311844118451184611847118481184911850118511185211853118541185511856118571185811859118601186111862118631186411865118661186711868118691187011871118721187311874118751187611877118781187911880118811188211883118841188511886118871188811889118901189111892118931189411895118961189711898118991190011901119021190311904119051190611907119081190911910119111191211913119141191511916119171191811919119201192111922119231192411925119261192711928119291193011931119321193311934119351193611937119381193911940119411194211943119441194511946119471194811949119501195111952119531195411955119561195711958119591196011961119621196311964119651196611967119681196911970119711197211973119741197511976119771197811979119801198111982119831198411985119861198711988119891199011991119921199311994119951199611997119981199912000120011200212003120041200512006120071200812009120101201112012120131201412015120161201712018120191202012021120221202312024120251202612027120281202912030120311203212033120341203512036120371203812039120401204112042120431204412045120461204712048120491205012051120521205312054120551205612057120581205912060120611206212063120641206512066120671206812069120701207112072120731207412075120761207712078120791208012081120821208312084120851208612087120881208912090120911209212093120941209512096120971209812099121001210112102121031210412105121061210712108121091211012111121121211312114121151211612117121181211912120121211212212123121241212512126121271212812129121301213112132121331213412135121361213712138121391214012141121421214312144121451214612147121481214912150121511215212153121541215512156121571215812159121601216112162121631216412165121661216712168121691217012171121721217312174121751217612177121781217912180121811218212183121841218512186121871218812189121901219112192121931219412195121961219712198121991220012201122021220312204122051220612207122081220912210122111221212213122141221512216122171221812219122201222112222122231222412225122261222712228122291223012231122321223312234122351223612237122381223912240122411224212243122441224512246122471224812249122501225112252122531225412255122561225712258122591226012261122621226312264122651226612267122681226912270122711227212273122741227512276122771227812279122801228112282122831228412285122861228712288122891229012291122921229312294122951229612297122981229912300123011230212303123041230512306123071230812309123101231112312123131231412315123161231712318123191232012321123221232312324123251232612327123281232912330123311233212333123341233512336123371233812339123401234112342123431234412345123461234712348123491235012351123521235312354123551235612357123581235912360123611236212363123641236512366123671236812369123701237112372123731237412375123761237712378123791238012381123821238312384123851238612387123881238912390123911239212393123941239512396123971239812399124001240112402124031240412405124061240712408124091241012411124121241312414124151241612417124181241912420124211242212423124241242512426124271242812429124301243112432124331243412435124361243712438124391244012441124421244312444124451244612447124481244912450124511245212453124541245512456124571245812459124601246112462124631246412465124661246712468124691247012471124721247312474124751247612477124781247912480124811248212483124841248512486124871248812489124901249112492124931249412495124961249712498124991250012501125021250312504125051250612507125081250912510125111251212513125141251512516125171251812519125201252112522125231252412525125261252712528125291253012531125321253312534125351253612537125381253912540125411254212543125441254512546125471254812549125501255112552125531255412555125561255712558125591256012561125621256312564125651256612567125681256912570125711257212573125741257512576125771257812579125801258112582125831258412585125861258712588125891259012591125921259312594125951259612597125981259912600126011260212603126041260512606126071260812609126101261112612126131261412615126161261712618126191262012621126221262312624126251262612627126281262912630126311263212633126341263512636126371263812639126401264112642126431264412645126461264712648126491265012651126521265312654126551265612657126581265912660126611266212663126641266512666126671266812669126701267112672126731267412675126761267712678126791268012681126821268312684126851268612687126881268912690126911269212693126941269512696126971269812699127001270112702127031270412705127061270712708127091271012711127121271312714127151271612717127181271912720127211272212723127241272512726127271272812729127301273112732127331273412735127361273712738127391274012741127421274312744127451274612747127481274912750127511275212753127541275512756127571275812759127601276112762127631276412765127661276712768127691277012771127721277312774127751277612777127781277912780127811278212783127841278512786127871278812789127901279112792127931279412795127961279712798127991280012801128021280312804128051280612807128081280912810128111281212813128141281512816128171281812819128201282112822128231282412825128261282712828128291283012831128321283312834128351283612837128381283912840128411284212843128441284512846128471284812849128501285112852128531285412855128561285712858128591286012861128621286312864128651286612867128681286912870128711287212873128741287512876128771287812879128801288112882128831288412885128861288712888128891289012891128921289312894128951289612897128981289912900129011290212903129041290512906129071290812909129101291112912129131291412915129161291712918129191292012921129221292312924129251292612927129281292912930129311293212933129341293512936129371293812939129401294112942129431294412945129461294712948129491295012951129521295312954129551295612957129581295912960129611296212963129641296512966129671296812969129701297112972129731297412975129761297712978129791298012981129821298312984129851298612987129881298912990129911299212993129941299512996129971299812999130001300113002130031300413005130061300713008130091301013011130121301313014130151301613017130181301913020130211302213023130241302513026130271302813029130301303113032130331303413035130361303713038130391304013041130421304313044130451304613047130481304913050130511305213053130541305513056130571305813059130601306113062130631306413065130661306713068130691307013071130721307313074130751307613077130781307913080130811308213083130841308513086130871308813089130901309113092130931309413095130961309713098130991310013101131021310313104131051310613107131081310913110131111311213113131141311513116131171311813119131201312113122131231312413125131261312713128131291313013131131321313313134131351313613137131381313913140131411314213143131441314513146131471314813149131501315113152131531315413155131561315713158131591316013161131621316313164131651316613167131681316913170131711317213173131741317513176131771317813179131801318113182131831318413185131861318713188131891319013191131921319313194131951319613197131981319913200132011320213203132041320513206132071320813209132101321113212132131321413215132161321713218132191322013221132221322313224132251322613227132281322913230132311323213233132341323513236132371323813239132401324113242132431324413245132461324713248132491325013251132521325313254132551325613257132581325913260132611326213263132641326513266132671326813269132701327113272132731327413275132761327713278132791328013281132821328313284132851328613287132881328913290132911329213293132941329513296132971329813299133001330113302133031330413305133061330713308133091331013311133121331313314133151331613317133181331913320133211332213323133241332513326133271332813329133301333113332133331333413335133361333713338133391334013341133421334313344133451334613347133481334913350133511335213353133541335513356133571335813359133601336113362133631336413365133661336713368133691337013371133721337313374133751337613377133781337913380133811338213383133841338513386133871338813389133901339113392133931339413395133961339713398133991340013401134021340313404134051340613407134081340913410134111341213413134141341513416134171341813419134201342113422134231342413425134261342713428134291343013431134321343313434134351343613437134381343913440134411344213443134441344513446134471344813449134501345113452134531345413455134561345713458134591346013461134621346313464134651346613467134681346913470134711347213473134741347513476134771347813479134801348113482134831348413485134861348713488134891349013491134921349313494134951349613497134981349913500135011350213503135041350513506135071350813509135101351113512135131351413515135161351713518135191352013521135221352313524135251352613527135281352913530135311353213533135341353513536135371353813539135401354113542135431354413545135461354713548135491355013551135521355313554135551355613557135581355913560135611356213563135641356513566135671356813569135701357113572135731357413575135761357713578135791358013581135821358313584135851358613587135881358913590135911359213593135941359513596135971359813599136001360113602136031360413605136061360713608136091361013611136121361313614136151361613617136181361913620136211362213623136241362513626136271362813629136301363113632136331363413635136361363713638136391364013641136421364313644136451364613647136481364913650136511365213653136541365513656136571365813659136601366113662136631366413665136661366713668136691367013671136721367313674136751367613677136781367913680136811368213683136841368513686136871368813689136901369113692136931369413695136961369713698136991370013701137021370313704137051370613707137081370913710137111371213713137141371513716137171371813719137201372113722137231372413725137261372713728137291373013731137321373313734137351373613737137381373913740137411374213743137441374513746137471374813749137501375113752137531375413755137561375713758137591376013761137621376313764137651376613767137681376913770137711377213773137741377513776137771377813779137801378113782137831378413785137861378713788137891379013791137921379313794137951379613797137981379913800138011380213803138041380513806138071380813809138101381113812138131381413815138161381713818138191382013821138221382313824138251382613827138281382913830138311383213833138341383513836138371383813839138401384113842138431384413845138461384713848138491385013851138521385313854138551385613857138581385913860138611386213863138641386513866138671386813869138701387113872138731387413875138761387713878138791388013881138821388313884138851388613887138881388913890138911389213893138941389513896138971389813899139001390113902139031390413905139061390713908139091391013911139121391313914139151391613917139181391913920139211392213923139241392513926139271392813929139301393113932139331393413935139361393713938139391394013941139421394313944139451394613947139481394913950139511395213953139541395513956139571395813959139601396113962139631396413965139661396713968139691397013971139721397313974139751397613977139781397913980139811398213983139841398513986139871398813989139901399113992139931399413995139961399713998139991400014001140021400314004140051400614007140081400914010140111401214013140141401514016140171401814019140201402114022140231402414025140261402714028140291403014031140321403314034140351403614037140381403914040140411404214043140441404514046140471404814049140501405114052140531405414055140561405714058140591406014061140621406314064140651406614067140681406914070140711407214073140741407514076140771407814079140801408114082140831408414085140861408714088140891409014091140921409314094140951409614097140981409914100141011410214103141041410514106141071410814109141101411114112141131411414115141161411714118141191412014121141221412314124141251412614127141281412914130141311413214133141341413514136141371413814139141401414114142141431414414145141461414714148141491415014151141521415314154141551415614157141581415914160141611416214163141641416514166141671416814169141701417114172141731417414175141761417714178141791418014181141821418314184141851418614187141881418914190141911419214193141941419514196141971419814199142001420114202142031420414205142061420714208142091421014211142121421314214142151421614217142181421914220142211422214223142241422514226142271422814229142301423114232142331423414235142361423714238142391424014241142421424314244142451424614247142481424914250142511425214253142541425514256142571425814259142601426114262142631426414265142661426714268142691427014271142721427314274142751427614277142781427914280142811428214283142841428514286142871428814289142901429114292142931429414295142961429714298142991430014301143021430314304143051430614307143081430914310143111431214313143141431514316143171431814319143201432114322143231432414325143261432714328143291433014331143321433314334143351433614337143381433914340143411434214343143441434514346143471434814349143501435114352143531435414355143561435714358143591436014361143621436314364143651436614367143681436914370143711437214373143741437514376143771437814379143801438114382143831438414385143861438714388143891439014391143921439314394143951439614397143981439914400144011440214403144041440514406144071440814409144101441114412144131441414415144161441714418144191442014421144221442314424144251442614427144281442914430144311443214433144341443514436144371443814439144401444114442144431444414445144461444714448144491445014451144521445314454144551445614457144581445914460144611446214463144641446514466144671446814469144701447114472144731447414475144761447714478144791448014481144821448314484144851448614487144881448914490144911449214493144941449514496144971449814499145001450114502145031450414505145061450714508145091451014511145121451314514145151451614517145181451914520145211452214523145241452514526145271452814529145301453114532145331453414535145361453714538145391454014541145421454314544145451454614547145481454914550145511455214553145541455514556145571455814559145601456114562145631456414565145661456714568145691457014571145721457314574145751457614577145781457914580145811458214583145841458514586145871458814589145901459114592145931459414595145961459714598145991460014601146021460314604146051460614607146081460914610146111461214613146141461514616146171461814619146201462114622146231462414625146261462714628146291463014631146321463314634146351463614637146381463914640146411464214643146441464514646146471464814649146501465114652146531465414655146561465714658146591466014661146621466314664146651466614667146681466914670146711467214673146741467514676146771467814679146801468114682146831468414685146861468714688146891469014691146921469314694146951469614697146981469914700147011470214703147041470514706147071470814709147101471114712147131471414715147161471714718147191472014721147221472314724147251472614727147281472914730147311473214733147341473514736147371473814739147401474114742147431474414745147461474714748147491475014751147521475314754147551475614757147581475914760147611476214763147641476514766147671476814769147701477114772147731477414775147761477714778147791478014781147821478314784147851478614787147881478914790147911479214793147941479514796147971479814799148001480114802148031480414805148061480714808148091481014811148121481314814148151481614817148181481914820148211482214823148241482514826148271482814829148301483114832148331483414835148361483714838148391484014841148421484314844148451484614847148481484914850148511485214853148541485514856148571485814859148601486114862148631486414865148661486714868148691487014871148721487314874148751487614877148781487914880148811488214883148841488514886148871488814889148901489114892148931489414895148961489714898148991490014901149021490314904149051490614907149081490914910149111491214913149141491514916149171491814919149201492114922149231492414925149261492714928149291493014931149321493314934149351493614937149381493914940149411494214943149441494514946149471494814949149501495114952149531495414955149561495714958149591496014961149621496314964149651496614967149681496914970149711497214973149741497514976149771497814979149801498114982149831498414985149861498714988149891499014991149921499314994149951499614997149981499915000150011500215003150041500515006150071500815009150101501115012150131501415015150161501715018150191502015021150221502315024150251502615027150281502915030150311503215033150341503515036150371503815039150401504115042150431504415045150461504715048150491505015051150521505315054150551505615057150581505915060150611506215063150641506515066150671506815069150701507115072150731507415075150761507715078150791508015081150821508315084150851508615087150881508915090150911509215093150941509515096150971509815099151001510115102151031510415105151061510715108151091511015111151121511315114151151511615117151181511915120151211512215123151241512515126151271512815129151301513115132151331513415135151361513715138151391514015141151421514315144151451514615147151481514915150151511515215153151541515515156151571515815159151601516115162151631516415165151661516715168151691517015171151721517315174151751517615177151781517915180151811518215183151841518515186151871518815189151901519115192151931519415195151961519715198151991520015201152021520315204152051520615207152081520915210152111521215213152141521515216152171521815219152201522115222152231522415225152261522715228152291523015231152321523315234152351523615237152381523915240152411524215243152441524515246152471524815249152501525115252152531525415255152561525715258152591526015261152621526315264152651526615267152681526915270152711527215273152741527515276152771527815279152801528115282152831528415285152861528715288152891529015291152921529315294152951529615297152981529915300153011530215303153041530515306153071530815309153101531115312153131531415315153161531715318153191532015321153221532315324153251532615327153281532915330153311533215333153341533515336153371533815339153401534115342153431534415345153461534715348153491535015351153521535315354153551535615357153581535915360153611536215363153641536515366153671536815369153701537115372153731537415375153761537715378153791538015381153821538315384153851538615387153881538915390153911539215393153941539515396153971539815399154001540115402154031540415405154061540715408154091541015411154121541315414154151541615417154181541915420154211542215423154241542515426154271542815429154301543115432154331543415435154361543715438154391544015441154421544315444154451544615447154481544915450154511545215453154541545515456154571545815459154601546115462154631546415465154661546715468154691547015471154721547315474154751547615477154781547915480154811548215483154841548515486154871548815489154901549115492154931549415495154961549715498154991550015501155021550315504155051550615507155081550915510155111551215513155141551515516155171551815519155201552115522155231552415525155261552715528155291553015531155321553315534155351553615537155381553915540155411554215543155441554515546155471554815549155501555115552155531555415555155561555715558155591556015561155621556315564155651556615567155681556915570155711557215573155741557515576155771557815579155801558115582155831558415585155861558715588155891559015591155921559315594155951559615597155981559915600156011560215603156041560515606156071560815609156101561115612156131561415615156161561715618156191562015621156221562315624156251562615627156281562915630156311563215633156341563515636156371563815639156401564115642156431564415645156461564715648156491565015651156521565315654156551565615657156581565915660156611566215663156641566515666156671566815669156701567115672156731567415675156761567715678156791568015681156821568315684156851568615687156881568915690156911569215693156941569515696156971569815699157001570115702157031570415705157061570715708157091571015711157121571315714157151571615717157181571915720157211572215723157241572515726157271572815729157301573115732157331573415735157361573715738157391574015741157421574315744157451574615747157481574915750157511575215753157541575515756157571575815759157601576115762157631576415765157661576715768157691577015771157721577315774157751577615777157781577915780157811578215783157841578515786157871578815789157901579115792157931579415795157961579715798157991580015801158021580315804158051580615807158081580915810158111581215813158141581515816158171581815819158201582115822158231582415825158261582715828158291583015831158321583315834158351583615837158381583915840158411584215843158441584515846158471584815849158501585115852158531585415855158561585715858158591586015861158621586315864158651586615867158681586915870158711587215873158741587515876158771587815879158801588115882158831588415885158861588715888158891589015891158921589315894158951589615897158981589915900159011590215903159041590515906159071590815909159101591115912159131591415915159161591715918159191592015921159221592315924159251592615927159281592915930159311593215933159341593515936159371593815939159401594115942159431594415945159461594715948159491595015951159521595315954159551595615957159581595915960159611596215963159641596515966159671596815969159701597115972159731597415975159761597715978159791598015981159821598315984159851598615987159881598915990159911599215993159941599515996159971599815999160001600116002160031600416005160061600716008160091601016011160121601316014160151601616017160181601916020160211602216023160241602516026160271602816029160301603116032160331603416035160361603716038160391604016041160421604316044160451604616047160481604916050160511605216053160541605516056160571605816059160601606116062160631606416065160661606716068160691607016071160721607316074160751607616077160781607916080160811608216083160841608516086160871608816089160901609116092160931609416095160961609716098160991610016101161021610316104161051610616107161081610916110161111611216113161141611516116161171611816119161201612116122161231612416125161261612716128161291613016131161321613316134161351613616137161381613916140161411614216143161441614516146161471614816149161501615116152161531615416155161561615716158161591616016161161621616316164161651616616167161681616916170161711617216173161741617516176161771617816179161801618116182161831618416185161861618716188161891619016191161921619316194161951619616197161981619916200162011620216203162041620516206162071620816209162101621116212162131621416215162161621716218162191622016221162221622316224162251622616227162281622916230162311623216233162341623516236162371623816239162401624116242162431624416245162461624716248162491625016251162521625316254162551625616257162581625916260
  1. /***
  2. @module up
  3. */
  4. (function() {
  5. window.up = {
  6. version: "0.62.0"
  7. };
  8. }).call(this);
  9. /***
  10. Utility functions
  11. =================
  12. The `up.util` module contains functions to facilitate the work with basic JavaScript
  13. values like lists, strings or functions.
  14. You will recognize many functions form other utility libraries like [Lodash](https://lodash.com/).
  15. While feature parity with Lodash is not a goal of `up.util`, you might find it sufficient
  16. to not include another library in your asset bundle.
  17. @module up.util
  18. */
  19. (function() {
  20. var slice = [].slice,
  21. hasProp = {}.hasOwnProperty;
  22. up.util = (function() {
  23. /***
  24. A function that does nothing.
  25. @function up.util.noop
  26. @experimental
  27. */
  28. var ESCAPE_HTML_ENTITY_MAP, always, arrayToSet, assign, assignPolyfill, asyncNoop, compact, contains, copy, deepCopy, each, eachIterator, endsWith, escapeHtml, escapePressed, escapeRegexp, evalOption, every, except, extractCallback, extractLastArg, extractOptions, fail, filterList, findInList, findResult, flatMap, flatten, horizontalScreenHalf, identity, intersect, isArguments, isArray, isBasicObjectProperty, isBlank, isBoolean, isCrossDomain, isDefined, isElement, isEqual, isEqualList, isFormData, isFunction, isGiven, isHTMLCollection, isJQuery, isList, isMissing, isNodeList, isNull, isNumber, isObject, isOptions, isPresent, isPromise, isStandardPort, isString, isTruthy, isUndefined, isUnmodifiedKeyEvent, isUnmodifiedMouseEvent, iteratee, last, map, mapObject, memoize, merge, methodAllowsPayload, muteRejection, newDeferred, newOptions, nextUid, noop, normalizeMethod, normalizeUrl, objectValues, only, parseUrl, partial, pluckKey, presence, previewable, queueMicrotask, queueTask, reject, rejectOnError, remove, renameKey, scheduleTimer, sequence, setToArray, simpleEase, some, splitValues, sum, times, toArray, uid, uniq, uniqBy, unresolvablePromise, valuesPolyfill, wrapList, wrapValue;
  29. noop = (function() {});
  30. /***
  31. A function that returns a resolved promise.
  32. @function up.util.asyncNoop
  33. @internal
  34. */
  35. asyncNoop = function() {
  36. return Promise.resolve();
  37. };
  38. /***
  39. Ensures that the given function can only be called a single time.
  40. Subsequent calls will return the return value of the first call.
  41. Note that this is a simple implementation that
  42. doesn't distinguish between argument lists.
  43. @function up.util.memoize
  44. @internal
  45. */
  46. memoize = function(func) {
  47. var cached, cachedValue;
  48. cachedValue = void 0;
  49. cached = false;
  50. return function() {
  51. var args;
  52. args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
  53. if (cached) {
  54. return cachedValue;
  55. } else {
  56. cached = true;
  57. return cachedValue = func.apply(null, args);
  58. }
  59. };
  60. };
  61. /***
  62. Returns if the given port is the default port for the given protocol.
  63. @function up.util.isStandardPort
  64. @internal
  65. */
  66. isStandardPort = function(protocol, port) {
  67. port = port.toString();
  68. return ((port === "" || port === "80") && protocol === 'http:') || (port === "443" && protocol === 'https:');
  69. };
  70. /***
  71. Normalizes relative paths and absolute paths to a full URL
  72. that can be checked for equality with other normalized URLs.
  73. By default hashes are ignored, search queries are included.
  74. @function up.util.normalizeUrl
  75. @param {boolean} [options.hash=false]
  76. Whether to include an `#hash` anchor in the normalized URL
  77. @param {boolean} [options.search=true]
  78. Whether to include a `?query` string in the normalized URL
  79. @param {boolean} [options.stripTrailingSlash=false]
  80. Whether to strip a trailing slash from the pathname
  81. @internal
  82. */
  83. normalizeUrl = function(urlOrAnchor, options) {
  84. var normalized, parts, pathname;
  85. parts = parseUrl(urlOrAnchor);
  86. normalized = parts.protocol + "//" + parts.hostname;
  87. if (!isStandardPort(parts.protocol, parts.port)) {
  88. normalized += ":" + parts.port;
  89. }
  90. pathname = parts.pathname;
  91. if ((options != null ? options.stripTrailingSlash : void 0) === true) {
  92. pathname = pathname.replace(/\/$/, '');
  93. }
  94. normalized += pathname;
  95. if ((options != null ? options.search : void 0) !== false) {
  96. normalized += parts.search;
  97. }
  98. if ((options != null ? options.hash : void 0) === true) {
  99. normalized += parts.hash;
  100. }
  101. return normalized;
  102. };
  103. isCrossDomain = function(targetUrl) {
  104. var currentUrl;
  105. currentUrl = parseUrl(location.href);
  106. targetUrl = parseUrl(targetUrl);
  107. return currentUrl.protocol !== targetUrl.protocol || currentUrl.hostname !== targetUrl.hostname;
  108. };
  109. /***
  110. Parses the given URL into components such as hostname and path.
  111. If the given URL is not fully qualified, it is assumed to be relative
  112. to the current page.
  113. @function up.util.parseUrl
  114. @return {Object}
  115. The parsed URL as an object with
  116. `protocol`, `hostname`, `port`, `pathname`, `search` and `hash`
  117. properties.
  118. @stable
  119. */
  120. parseUrl = function(urlOrLink) {
  121. var link;
  122. if (isJQuery(urlOrLink)) {
  123. link = up.element.get(urlOrLink);
  124. } else if (urlOrLink.pathname) {
  125. link = urlOrLink;
  126. } else {
  127. link = document.createElement('a');
  128. link.href = urlOrLink;
  129. }
  130. if (!link.hostname) {
  131. link.href = link.href;
  132. }
  133. if (link.pathname[0] !== '/') {
  134. link = only(link, 'protocol', 'hostname', 'port', 'pathname', 'search', 'hash');
  135. link.pathname = '/' + link.pathname;
  136. }
  137. return link;
  138. };
  139. /***
  140. @function up.util.normalizeMethod
  141. @internal
  142. */
  143. normalizeMethod = function(method) {
  144. if (method) {
  145. return method.toUpperCase();
  146. } else {
  147. return 'GET';
  148. }
  149. };
  150. /***
  151. @function up.util.methodAllowsPayload
  152. @internal
  153. */
  154. methodAllowsPayload = function(method) {
  155. return method !== 'GET' && method !== 'HEAD';
  156. };
  157. assignPolyfill = function() {
  158. var i, key, len, source, sources, target, value;
  159. target = arguments[0], sources = 2 <= arguments.length ? slice.call(arguments, 1) : [];
  160. for (i = 0, len = sources.length; i < len; i++) {
  161. source = sources[i];
  162. for (key in source) {
  163. if (!hasProp.call(source, key)) continue;
  164. value = source[key];
  165. target[key] = value;
  166. }
  167. }
  168. return target;
  169. };
  170. /***
  171. Merge the own properties of one or more `sources` into the `target` object.
  172. @function up.util.assign
  173. @param {Object} target
  174. @param {Array<Object>} sources...
  175. @stable
  176. */
  177. assign = Object.assign || assignPolyfill;
  178. valuesPolyfill = function(object) {
  179. var key, results, value;
  180. results = [];
  181. for (key in object) {
  182. value = object[key];
  183. results.push(value);
  184. }
  185. return results;
  186. };
  187. /***
  188. Returns an array of values of the given object.
  189. @function up.util.values
  190. @param {Object} object
  191. @return {Array<string>}
  192. @stable
  193. */
  194. objectValues = Object.values || valuesPolyfill;
  195. iteratee = function(block) {
  196. if (isString(block)) {
  197. return function(item) {
  198. return item[block];
  199. };
  200. } else {
  201. return block;
  202. }
  203. };
  204. /***
  205. Translate all items in an array to new array of items.
  206. @function up.util.map
  207. @param {Array} array
  208. @param {Function(element, index): any|String} block
  209. A function that will be called with each element and (optional) iteration index.
  210. You can also pass a property name as a String,
  211. which will be collected from each item in the array.
  212. @return {Array}
  213. A new array containing the result of each function call.
  214. @stable
  215. */
  216. map = function(array, block) {
  217. var i, index, item, len, results;
  218. if (array.length === 0) {
  219. return [];
  220. }
  221. block = iteratee(block);
  222. results = [];
  223. for (index = i = 0, len = array.length; i < len; index = ++i) {
  224. item = array[index];
  225. results.push(block(item, index));
  226. }
  227. return results;
  228. };
  229. /***
  230. @function up.util.mapObject
  231. @internal
  232. */
  233. mapObject = function(array, pairer) {
  234. var merger;
  235. merger = function(object, pair) {
  236. object[pair[0]] = pair[1];
  237. return object;
  238. };
  239. return map(array, pairer).reduce(merger, {});
  240. };
  241. /***
  242. Calls the given function for each element (and, optional, index)
  243. of the given array.
  244. @function up.util.each
  245. @param {Array} array
  246. @param {Function(element, index)} block
  247. A function that will be called with each element and (optional) iteration index.
  248. @stable
  249. */
  250. each = map;
  251. eachIterator = function(iterator, callback) {
  252. var entry, results;
  253. results = [];
  254. while ((entry = iterator.next()) && !entry.done) {
  255. results.push(callback(entry.value));
  256. }
  257. return results;
  258. };
  259. /***
  260. Calls the given function for the given number of times.
  261. @function up.util.times
  262. @param {number} count
  263. @param {Function()} block
  264. @stable
  265. */
  266. times = function(count, block) {
  267. var i, iteration, ref, results;
  268. results = [];
  269. for (iteration = i = 0, ref = count - 1; 0 <= ref ? i <= ref : i >= ref; iteration = 0 <= ref ? ++i : --i) {
  270. results.push(block(iteration));
  271. }
  272. return results;
  273. };
  274. /***
  275. Returns whether the given argument is `null`.
  276. @function up.util.isNull
  277. @param object
  278. @return {boolean}
  279. @stable
  280. */
  281. isNull = function(object) {
  282. return object === null;
  283. };
  284. /***
  285. Returns whether the given argument is `undefined`.
  286. @function up.util.isUndefined
  287. @param object
  288. @return {boolean}
  289. @stable
  290. */
  291. isUndefined = function(object) {
  292. return object === void 0;
  293. };
  294. /***
  295. Returns whether the given argument is not `undefined`.
  296. @function up.util.isDefined
  297. @param object
  298. @return {boolean}
  299. @stable
  300. */
  301. isDefined = function(object) {
  302. return !isUndefined(object);
  303. };
  304. /***
  305. Returns whether the given argument is either `undefined` or `null`.
  306. Note that empty strings or zero are *not* considered to be "missing".
  307. For the opposite of `up.util.isMissing()` see [`up.util.isGiven()`](/up.util.isGiven).
  308. @function up.util.isMissing
  309. @param object
  310. @return {boolean}
  311. @stable
  312. */
  313. isMissing = function(object) {
  314. return isUndefined(object) || isNull(object);
  315. };
  316. /***
  317. Returns whether the given argument is neither `undefined` nor `null`.
  318. Note that empty strings or zero *are* considered to be "given".
  319. For the opposite of `up.util.isGiven()` see [`up.util.isMissing()`](/up.util.isMissing).
  320. @function up.util.isGiven
  321. @param object
  322. @return {boolean}
  323. @stable
  324. */
  325. isGiven = function(object) {
  326. return !isMissing(object);
  327. };
  328. /***
  329. Return whether the given argument is considered to be blank.
  330. By default, this function returns `true` for:
  331. - `undefined`
  332. - `null`
  333. - Empty strings
  334. - Empty arrays
  335. - A plain object without own enumerable properties
  336. All other arguments return `false`.
  337. To check implement blank-ness checks for user-defined classes,
  338. see `up.util.isBlank.key`.
  339. @function up.util.isBlank
  340. @param value
  341. The value is to check.
  342. @return {boolean}
  343. Whether the value is blank.
  344. @stable
  345. */
  346. isBlank = function(value) {
  347. if (isMissing(value)) {
  348. return true;
  349. }
  350. if (isObject(value) && value[isBlank.key]) {
  351. return value[isBlank.key]();
  352. }
  353. if (isString(value) || isList(value)) {
  354. return value.length === 0;
  355. }
  356. if (isOptions(value)) {
  357. return Object.keys(value).length === 0;
  358. }
  359. return false;
  360. };
  361. /***
  362. This property contains the name of a method that user-defined classes
  363. may implement to hook into the `up.util.isBlank()` protocol.
  364. \#\#\# Example
  365. We have a user-defined `Account` class that we want to use with `up.util.isBlank()`:
  366. ```
  367. class Account {
  368. constructor(email) {
  369. this.email = email
  370. }
  371. [up.util.isBlank.key]() {
  372. return up.util.isBlank(this.email)
  373. }
  374. }
  375. ```
  376. Note that the protocol method is not actually named `'up.util.isBlank.key'`.
  377. Instead it is named after the *value* of the `up.util.isBlank.key` property.
  378. To do so, the code sample above is using a
  379. [computed property name](https://medium.com/front-end-weekly/javascript-object-creation-356e504173a8)
  380. in square brackets.
  381. We may now use `Account` instances with `up.util.isBlank()`:
  382. ```
  383. foo = new Account('foo@foo.com')
  384. bar = new Account('')
  385. console.log(up.util.isBlank(foo)) // prints false
  386. console.log(up.util.isBlank(bar)) // prints true
  387. ```
  388. @property up.util.isBlank.key
  389. @experimental
  390. */
  391. isBlank.key = 'up.util.isBlank';
  392. /***
  393. Returns the given argument if the argument is [present](/up.util.isPresent),
  394. otherwise returns `undefined`.
  395. @function up.util.presence
  396. @param value
  397. @param {Function(value): boolean} [tester=up.util.isPresent]
  398. The function that will be used to test whether the argument is present.
  399. @return {any|undefined}
  400. @stable
  401. */
  402. presence = function(value, tester) {
  403. if (tester == null) {
  404. tester = isPresent;
  405. }
  406. if (tester(value)) {
  407. return value;
  408. } else {
  409. return void 0;
  410. }
  411. };
  412. /***
  413. Returns whether the given argument is not [blank](/up.util.isBlank).
  414. @function up.util.isPresent
  415. @param object
  416. @return {boolean}
  417. @stable
  418. */
  419. isPresent = function(object) {
  420. return !isBlank(object);
  421. };
  422. /***
  423. Returns whether the given argument is a function.
  424. @function up.util.isFunction
  425. @param object
  426. @return {boolean}
  427. @stable
  428. */
  429. isFunction = function(object) {
  430. return typeof object === 'function';
  431. };
  432. /***
  433. Returns whether the given argument is a string.
  434. @function up.util.isString
  435. @param object
  436. @return {boolean}
  437. @stable
  438. */
  439. isString = function(object) {
  440. return typeof object === 'string' || object instanceof String;
  441. };
  442. /***
  443. Returns whether the given argument is a boolean value.
  444. @function up.util.isBoolean
  445. @param object
  446. @return {boolean}
  447. @stable
  448. */
  449. isBoolean = function(object) {
  450. return typeof object === 'boolean' || object instanceof Boolean;
  451. };
  452. /***
  453. Returns whether the given argument is a number.
  454. Note that this will check the argument's *type*.
  455. It will return `false` for a string like `"123"`.
  456. @function up.util.isNumber
  457. @param object
  458. @return {boolean}
  459. @stable
  460. */
  461. isNumber = function(object) {
  462. return typeof object === 'number' || object instanceof Number;
  463. };
  464. /***
  465. Returns whether the given argument is an options hash,
  466. Differently from [`up.util.isObject()`], this returns false for
  467. functions, jQuery collections, promises, `FormData` instances and arrays.
  468. @function up.util.isOptions
  469. @param object
  470. @return {boolean}
  471. @internal
  472. */
  473. isOptions = function(object) {
  474. return typeof object === 'object' && !isNull(object) && (isUndefined(object.constructor) || object.constructor === Object);
  475. };
  476. /***
  477. Returns whether the given argument is an object.
  478. This also returns `true` for functions, which may behave like objects in JavaScript.
  479. @function up.util.isObject
  480. @param object
  481. @return {boolean}
  482. @stable
  483. */
  484. isObject = function(object) {
  485. var typeOfResult;
  486. typeOfResult = typeof object;
  487. return (typeOfResult === 'object' && !isNull(object)) || typeOfResult === 'function';
  488. };
  489. /***
  490. Returns whether the given argument is a [DOM element](https://developer.mozilla.org/de/docs/Web/API/Element).
  491. @function up.util.isElement
  492. @param object
  493. @return {boolean}
  494. @stable
  495. */
  496. isElement = function(object) {
  497. return object instanceof Element;
  498. };
  499. /***
  500. Returns whether the given argument is a [jQuery collection](https://learn.jquery.com/using-jquery-core/jquery-object/).
  501. @function up.util.isJQuery
  502. @param object
  503. @return {boolean}
  504. @stable
  505. */
  506. isJQuery = function(object) {
  507. return !!(object != null ? object.jquery : void 0);
  508. };
  509. /***
  510. Returns whether the given argument is an object with a `then` method.
  511. @function up.util.isPromise
  512. @param object
  513. @return {boolean}
  514. @stable
  515. */
  516. isPromise = function(object) {
  517. return isObject(object) && isFunction(object.then);
  518. };
  519. /***
  520. Returns whether the given argument is an array.
  521. @function up.util.isArray
  522. @param object
  523. @return {boolean}
  524. @stable
  525. */
  526. isArray = Array.isArray;
  527. /***
  528. Returns whether the given argument is a `FormData` instance.
  529. Always returns `false` in browsers that don't support `FormData`.
  530. @function up.util.isFormData
  531. @param object
  532. @return {boolean}
  533. @internal
  534. */
  535. isFormData = function(object) {
  536. return object instanceof FormData;
  537. };
  538. /***
  539. Converts the given [array-like value](/up.util.isList) into an array.
  540. If the given value is already an array, it is returned unchanged.
  541. @function up.util.toArray
  542. @param object
  543. @return {Array}
  544. @stable
  545. */
  546. toArray = function(value) {
  547. if (isArray(value)) {
  548. return value;
  549. } else {
  550. return Array.prototype.slice.call(value);
  551. }
  552. };
  553. /****
  554. Returns whether the given argument is an array-like value.
  555. Return true for `Array`, a
  556. [`NodeList`](https://developer.mozilla.org/en-US/docs/Web/API/NodeList),
  557. the [arguments object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments)
  558. or a jQuery collection.
  559. Use [`up.util.isArray()`](/up.util.isArray) to test whether a value is an actual `Array`.
  560. @function up.util.isList
  561. @param value
  562. @return {Boolean}
  563. @experimental
  564. */
  565. isList = function(value) {
  566. return isArray(value) || isNodeList(value) || isArguments(value) || isJQuery(value) || isHTMLCollection(value);
  567. };
  568. /***
  569. Returns whether the given value is a [`NodeList`](https://developer.mozilla.org/en-US/docs/Web/API/NodeList).
  570. `NodeLists` are array-like objects returned by [`document.querySelectorAll()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/querySelectorAll).
  571. @function up.util.isNodeList
  572. @param value
  573. @return {Boolean}
  574. @internal
  575. */
  576. isNodeList = function(value) {
  577. return value instanceof NodeList;
  578. };
  579. isHTMLCollection = function(value) {
  580. return value instanceof HTMLCollection;
  581. };
  582. /***
  583. Returns whether the given value is an [arguments object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments).
  584. @function up.util.isArguments
  585. @param value
  586. @return {Boolean}
  587. @internal
  588. */
  589. isArguments = function(value) {
  590. return Object.prototype.toString.call(value) === '[object Arguments]';
  591. };
  592. /***
  593. @function up.util.wrapList
  594. @return {Array|NodeList|jQuery}
  595. @internal
  596. */
  597. wrapList = function(value) {
  598. if (isList(value)) {
  599. return value;
  600. } else if (isMissing(value)) {
  601. return [];
  602. } else {
  603. return [value];
  604. }
  605. };
  606. /***
  607. Returns a shallow copy of the given value.
  608. \#\#\# Copying protocol
  609. - By default `up.util.copy()` can copy [array-like values](/up.util.isList),
  610. plain objects and `Date` instances.
  611. - Array-like objects are copied into new arrays.
  612. - Unsupported types of values are returned unchanged.
  613. - To make the copying protocol work with user-defined class,
  614. see `up.util.copy.key`.
  615. - Immutable objects, like strings or numbers, do not need to be copied.
  616. @function up.util.copy
  617. @param {any} object
  618. @return {any}
  619. @stable
  620. */
  621. copy = function(value, deep) {
  622. var copied, k, v;
  623. if (isObject(value) && value[copy.key]) {
  624. value = value[copy.key]();
  625. } else if (isList(value)) {
  626. value = Array.prototype.slice.call(value);
  627. copied = true;
  628. } else if (isOptions(value)) {
  629. value = assign({}, value);
  630. copied = true;
  631. }
  632. if (copied && deep) {
  633. for (k in value) {
  634. v = value[k];
  635. value[k] = copy(v, true);
  636. }
  637. }
  638. return value;
  639. };
  640. /***
  641. This property contains the name of a method that user-defined classes
  642. may implement to hook into the `up.util.copy()` protocol.
  643. \#\#\# Example
  644. We have a user-defined `Account` class that we want to use with `up.util.copy()`:
  645. ```
  646. class Account {
  647. constructor(email) {
  648. this.email = email
  649. }
  650. [up.util.copy.key]() {
  651. return new Account(this.email)
  652. }
  653. }
  654. ```
  655. Note that the protocol method is not actually named `'up.util.copy.key'`.
  656. Instead it is named after the *value* of the `up.util.copy.key` property.
  657. To do so, the code sample above is using a
  658. [computed property name](https://medium.com/front-end-weekly/javascript-object-creation-356e504173a8)
  659. in square brackets.
  660. We may now use `Account` instances with `up.util.copy()`:
  661. ```
  662. original = new User('foo@foo.com')
  663. copy = up.util.copy(original)
  664. console.log(copy.email) // prints 'foo@foo.com'
  665. original.email = 'bar@bar.com' // change the original
  666. console.log(copy.email) // still prints 'foo@foo.com'
  667. ```
  668. @property up.util.copy.key
  669. @param {string} key
  670. @experimental
  671. */
  672. copy.key = 'up.util.copy';
  673. Date.prototype[copy.key] = function() {
  674. return new Date(+this);
  675. };
  676. /***
  677. Returns a deep copy of the given array or object.
  678. @function up.util.deepCopy
  679. @param {Object|Array} object
  680. @return {Object|Array}
  681. @internal
  682. */
  683. deepCopy = function(object) {
  684. return copy(object, true);
  685. };
  686. /***
  687. Creates a new object by merging together the properties from the given objects.
  688. @function up.util.merge
  689. @param {Array<Object>} sources...
  690. @return Object
  691. @stable
  692. */
  693. merge = function() {
  694. var sources;
  695. sources = 1 <= arguments.length ? slice.call(arguments, 0) : [];
  696. return assign.apply(null, [{}].concat(slice.call(sources)));
  697. };
  698. /***
  699. Creates an options hash from the given argument and some defaults.
  700. The semantics of this function are confusing.
  701. We want to get rid of this in the future.
  702. @function up.util.options
  703. @param {Object} object
  704. @param {Object} [defaults]
  705. @return {Object}
  706. @internal
  707. */
  708. newOptions = function(object, defaults) {
  709. if (defaults) {
  710. return merge(defaults, object);
  711. } else if (object) {
  712. return copy(object);
  713. } else {
  714. return {};
  715. }
  716. };
  717. /***
  718. Passes each element in the given [array-like value](/up.util.isList) to the given function.
  719. Returns the first element for which the function returns a truthy value.
  720. If no object matches, returns `undefined`.
  721. @function up.util.find
  722. @param {List<T>} list
  723. @param {Function(value): boolean} tester
  724. @return {T|undefined}
  725. @stable
  726. */
  727. findInList = function(list, tester) {
  728. var element, i, len, match;
  729. match = void 0;
  730. for (i = 0, len = list.length; i < len; i++) {
  731. element = list[i];
  732. if (tester(element)) {
  733. match = element;
  734. break;
  735. }
  736. }
  737. return match;
  738. };
  739. /***
  740. Returns whether the given function returns a truthy value
  741. for any element in the given [array-like value](/up.util.isList).
  742. @function up.util.some
  743. @param {List} list
  744. @param {Function(value, index): boolean} tester
  745. A function that will be called with each element and (optional) iteration index.
  746. @return {boolean}
  747. @stable
  748. */
  749. some = function(list, tester) {
  750. return !!findResult(list, tester);
  751. };
  752. /***
  753. Consecutively calls the given function which each element
  754. in the given array. Returns the first truthy return value.
  755. Returned `undefined` iff the function does not return a truthy
  756. value for any element in the array.
  757. @function up.util.findResult
  758. @param {Array} array
  759. @param {Function(element): any} tester
  760. A function that will be called with each element and (optional) iteration index.
  761. @return {any|undefined}
  762. @experimental
  763. */
  764. findResult = function(array, tester) {
  765. var element, i, index, len, result;
  766. tester = iteratee(tester);
  767. for (index = i = 0, len = array.length; i < len; index = ++i) {
  768. element = array[index];
  769. if (result = tester(element, index)) {
  770. return result;
  771. }
  772. }
  773. return void 0;
  774. };
  775. /***
  776. Returns whether the given function returns a truthy value
  777. for all elements in the given [array-like value](/up.util.isList).
  778. @function up.util.every
  779. @param {List} list
  780. @param {Function(element, index): boolean} tester
  781. A function that will be called with each element and (optional) iteration index.
  782. @return {boolean}
  783. @experimental
  784. */
  785. every = function(list, tester) {
  786. var element, i, index, len, match;
  787. tester = iteratee(tester);
  788. match = true;
  789. for (index = i = 0, len = list.length; i < len; index = ++i) {
  790. element = list[index];
  791. if (!tester(element, index)) {
  792. match = false;
  793. break;
  794. }
  795. }
  796. return match;
  797. };
  798. /***
  799. Returns all elements from the given array that are
  800. neither `null` or `undefined`.
  801. @function up.util.compact
  802. @param {Array<T>} array
  803. @return {Array<T>}
  804. @stable
  805. */
  806. compact = function(array) {
  807. return filterList(array, isGiven);
  808. };
  809. /***
  810. Returns the given array without duplicates.
  811. @function up.util.uniq
  812. @param {Array<T>} array
  813. @return {Array<T>}
  814. @stable
  815. */
  816. uniq = function(array) {
  817. if (array.length < 2) {
  818. return array;
  819. }
  820. return setToArray(arrayToSet(array));
  821. };
  822. /***
  823. This function is like [`uniq`](/up.util.uniq), accept that
  824. the given function is invoked for each element to generate the value
  825. for which uniquness is computed.
  826. @function up.util.uniqBy
  827. @param {Array} array
  828. @param {Function(value): any} array
  829. @return {Array}
  830. @experimental
  831. */
  832. uniqBy = function(array, mapper) {
  833. var set;
  834. if (array.length < 2) {
  835. return array;
  836. }
  837. mapper = iteratee(mapper);
  838. set = new Set();
  839. return filterList(array, function(elem, index) {
  840. var mapped;
  841. mapped = mapper(elem, index);
  842. if (set.has(mapped)) {
  843. return false;
  844. } else {
  845. set.add(mapped);
  846. return true;
  847. }
  848. });
  849. };
  850. /***
  851. @function up.util.setToArray
  852. @internal
  853. */
  854. setToArray = function(set) {
  855. var array;
  856. array = [];
  857. set.forEach(function(elem) {
  858. return array.push(elem);
  859. });
  860. return array;
  861. };
  862. /***
  863. @function up.util.arrayToSet
  864. @internal
  865. */
  866. arrayToSet = function(array) {
  867. var set;
  868. set = new Set();
  869. array.forEach(function(elem) {
  870. return set.add(elem);
  871. });
  872. return set;
  873. };
  874. /***
  875. Returns all elements from the given [array-like value](/up.util.isList) that return
  876. a truthy value when passed to the given function.
  877. @function up.util.filter
  878. @param {List} list
  879. @param {Function(value, index): boolean} tester
  880. @return {Array}
  881. @stable
  882. */
  883. filterList = function(list, tester) {
  884. var matches;
  885. tester = iteratee(tester);
  886. matches = [];
  887. each(list, function(element, index) {
  888. if (tester(element, index)) {
  889. return matches.push(element);
  890. }
  891. });
  892. return matches;
  893. };
  894. /***
  895. Returns all elements from the given [array-like value](/up.util.isList) that do not return
  896. a truthy value when passed to the given function.
  897. @function up.util.reject
  898. @param {List} list
  899. @param {Function(element, index): boolean} tester
  900. @return {Array}
  901. @stable
  902. */
  903. reject = function(list, tester) {
  904. tester = iteratee(tester);
  905. return filterList(list, function(element, index) {
  906. return !tester(element, index);
  907. });
  908. };
  909. /***
  910. Returns the intersection of the given two arrays.
  911. Implementation is not optimized. Don't use it for large arrays.
  912. @function up.util.intersect
  913. @internal
  914. */
  915. intersect = function(array1, array2) {
  916. return filterList(array1, function(element) {
  917. return contains(array2, element);
  918. });
  919. };
  920. /***
  921. Waits for the given number of milliseconds, the runs the given callback.
  922. Instead of `up.util.timer(0, fn)` you can also use [`up.util.task(fn)`](/up.util.task).
  923. @function up.util.timer
  924. @param {number} millis
  925. @param {Function()} callback
  926. @stable
  927. */
  928. scheduleTimer = function(millis, callback) {
  929. return setTimeout(callback, millis);
  930. };
  931. /***
  932. Pushes the given function to the [JavaScript task queue](https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/) (also "macrotask queue").
  933. Equivalent to calling `setTimeout(fn, 0)`.
  934. Also see `up.util.microtask()`.
  935. @function up.util.task
  936. @param {Function()} block
  937. @stable
  938. */
  939. queueTask = function(block) {
  940. return setTimeout(block, 0);
  941. };
  942. /***
  943. Pushes the given function to the [JavaScript microtask queue](https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/).
  944. @function up.util.microtask
  945. @param {Function()} task
  946. @return {Promise}
  947. @experimental
  948. */
  949. queueMicrotask = function(task) {
  950. return Promise.resolve().then(task);
  951. };
  952. /***
  953. Returns the last element of the given array.
  954. @function up.util.last
  955. @param {Array<T>} array
  956. @return {T}
  957. */
  958. last = function(array) {
  959. return array[array.length - 1];
  960. };
  961. /***
  962. Returns whether the given keyboard event involved the ESC key.
  963. @function up.util.escapePressed
  964. @internal
  965. */
  966. escapePressed = function(event) {
  967. var key;
  968. key = event.key;
  969. return key === 'Escape' || key === 'Esc';
  970. };
  971. /***
  972. Returns whether the given array or string contains the given element or substring.
  973. @function up.util.contains
  974. @param {Array|string} arrayOrString
  975. @param elementOrSubstring
  976. @stable
  977. */
  978. contains = function(arrayOrString, elementOrSubstring) {
  979. return arrayOrString.indexOf(elementOrSubstring) >= 0;
  980. };
  981. /***
  982. Returns a copy of the given object that only contains
  983. the given properties.
  984. @function up.util.only
  985. @param {Object} object
  986. @param {Array} keys...
  987. @stable
  988. */
  989. only = function() {
  990. var filtered, i, len, object, properties, property;
  991. object = arguments[0], properties = 2 <= arguments.length ? slice.call(arguments, 1) : [];
  992. filtered = {};
  993. for (i = 0, len = properties.length; i < len; i++) {
  994. property = properties[i];
  995. if (property in object) {
  996. filtered[property] = object[property];
  997. }
  998. }
  999. return filtered;
  1000. };
  1001. /***
  1002. Returns a copy of the given object that contains all except
  1003. the given properties.
  1004. @function up.util.except
  1005. @param {Object} object
  1006. @param {Array} keys...
  1007. @stable
  1008. */
  1009. except = function() {
  1010. var filtered, i, len, object, properties, property;
  1011. object = arguments[0], properties = 2 <= arguments.length ? slice.call(arguments, 1) : [];
  1012. filtered = copy(object);
  1013. for (i = 0, len = properties.length; i < len; i++) {
  1014. property = properties[i];
  1015. delete filtered[property];
  1016. }
  1017. return filtered;
  1018. };
  1019. /***
  1020. @function up.util.isUnmodifiedKeyEvent
  1021. @internal
  1022. */
  1023. isUnmodifiedKeyEvent = function(event) {
  1024. return !(event.metaKey || event.shiftKey || event.ctrlKey);
  1025. };
  1026. /***
  1027. @function up.util.isUnmodifiedMouseEvent
  1028. @internal
  1029. */
  1030. isUnmodifiedMouseEvent = function(event) {
  1031. var isLeftButton;
  1032. isLeftButton = isUndefined(event.button) || event.button === 0;
  1033. return isLeftButton && isUnmodifiedKeyEvent(event);
  1034. };
  1035. /***
  1036. Returns a promise that will never be resolved.
  1037. @function up.util.unresolvablePromise
  1038. @internal
  1039. */
  1040. unresolvablePromise = function() {
  1041. return new Promise(noop);
  1042. };
  1043. /***
  1044. Removes the given element from the given array.
  1045. This changes the given array.
  1046. @function up.util.remove
  1047. @param {Array<T>} array
  1048. @param {T} element
  1049. @stable
  1050. */
  1051. remove = function(array, element) {
  1052. var index;
  1053. index = array.indexOf(element);
  1054. if (index >= 0) {
  1055. array.splice(index, 1);
  1056. return element;
  1057. }
  1058. };
  1059. /***
  1060. If the given `value` is a function, calls the function with the given `args`.
  1061. Otherwise it just returns `value`.
  1062. @function up.util.evalOption
  1063. @internal
  1064. */
  1065. evalOption = function() {
  1066. var args, value;
  1067. value = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];
  1068. if (isFunction(value)) {
  1069. return value.apply(null, args);
  1070. } else {
  1071. return value;
  1072. }
  1073. };
  1074. /***
  1075. Throws a [JavaScript error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)
  1076. with the given message.
  1077. The message will also be printed to the [error log](/up.log.error). Also a notification will be shown at the bottom of the screen.
  1078. The message may contain [substitution marks](https://developer.mozilla.org/en-US/docs/Web/API/console#Using_string_substitutions).
  1079. \#\#\# Examples
  1080. up.fail('Division by zero')
  1081. up.fail('Unexpected result %o', result)
  1082. @function up.fail
  1083. @param {string} message
  1084. A message with details about the error.
  1085. The message can contain [substitution marks](https://developer.mozilla.org/en-US/docs/Web/API/console#Using_string_substitutions)
  1086. like `%s` or `%o`.
  1087. @param {Array<string>} vars...
  1088. A list of variables to replace any substitution marks in the error message.
  1089. @experimental
  1090. */
  1091. fail = function() {
  1092. var args, asString, messageArgs, ref, ref1, toastOptions;
  1093. args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
  1094. if (isArray(args[0])) {
  1095. messageArgs = args[0];
  1096. toastOptions = args[1] || {};
  1097. } else {
  1098. messageArgs = args;
  1099. toastOptions = {};
  1100. }
  1101. (ref = up.log).error.apply(ref, messageArgs);
  1102. up.event.onReady(function() {
  1103. return up.toast.open(messageArgs, toastOptions);
  1104. });
  1105. asString = (ref1 = up.log).sprintf.apply(ref1, messageArgs);
  1106. throw new Error(asString);
  1107. };
  1108. ESCAPE_HTML_ENTITY_MAP = {
  1109. "&": "&amp;",
  1110. "<": "&lt;",
  1111. ">": "&gt;",
  1112. '"': '&quot;'
  1113. };
  1114. /***
  1115. Escapes the given string of HTML by replacing control chars with their HTML entities.
  1116. @function up.util.escapeHtml
  1117. @param {string} string
  1118. The text that should be escaped
  1119. @stable
  1120. */
  1121. escapeHtml = function(string) {
  1122. return string.replace(/[&<>"]/g, function(char) {
  1123. return ESCAPE_HTML_ENTITY_MAP[char];
  1124. });
  1125. };
  1126. /***
  1127. @function up.util.escapeRegexp
  1128. @internal
  1129. */
  1130. escapeRegexp = function(string) {
  1131. return string.replace(/[\\^$*+?.()|[\]{}]/g, '\\$&');
  1132. };
  1133. pluckKey = function(object, key) {
  1134. var value;
  1135. value = object[key];
  1136. delete object[key];
  1137. return value;
  1138. };
  1139. renameKey = function(object, oldKey, newKey) {
  1140. return object[newKey] = pluckKey(object, oldKey);
  1141. };
  1142. extractLastArg = function(args, tester) {
  1143. var lastArg;
  1144. lastArg = last(args);
  1145. if (tester(lastArg)) {
  1146. return args.pop();
  1147. }
  1148. };
  1149. extractCallback = function(args) {
  1150. return extractLastArg(args, isFunction);
  1151. };
  1152. extractOptions = function(args) {
  1153. return extractLastArg(args, isOptions) || {};
  1154. };
  1155. partial = function() {
  1156. var fixedArgs, fn;
  1157. fn = arguments[0], fixedArgs = 2 <= arguments.length ? slice.call(arguments, 1) : [];
  1158. return function() {
  1159. var callArgs;
  1160. callArgs = 1 <= arguments.length ? slice.call(arguments, 0) : [];
  1161. return fn.apply(this, fixedArgs.concat(callArgs));
  1162. };
  1163. };
  1164. identity = function(arg) {
  1165. return arg;
  1166. };
  1167. /***
  1168. Given a function that will return a promise, returns a proxy function
  1169. with an additional `.promise` attribute.
  1170. When the proxy is called, the inner function is called.
  1171. The proxy's `.promise` attribute is available even before the function is called
  1172. and will resolve when the inner function's returned promise resolves.
  1173. If the inner function does not return a promise, the proxy's `.promise` attribute
  1174. will resolve as soon as the inner function returns.
  1175. @function up.util.previewable
  1176. @internal
  1177. */
  1178. previewable = function(fun) {
  1179. var deferred, preview;
  1180. deferred = newDeferred();
  1181. preview = function() {
  1182. var args, funValue;
  1183. args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
  1184. funValue = fun.apply(null, args);
  1185. deferred.resolve(funValue);
  1186. return funValue;
  1187. };
  1188. preview.promise = deferred.promise();
  1189. return preview;
  1190. };
  1191. /***
  1192. @function up.util.sequence
  1193. @param {Array<Function()>} functions
  1194. @return {Function()}
  1195. A function that will call all `functions` if called.
  1196. @internal
  1197. */
  1198. sequence = function(functions) {
  1199. if (functions.length === 1) {
  1200. return functions[0];
  1201. } else {
  1202. return function() {
  1203. return map(functions, function(f) {
  1204. return f();
  1205. });
  1206. };
  1207. }
  1208. };
  1209. /***
  1210. Returns `'left'` if the center of the given element is in the left 50% of the screen.
  1211. Otherwise returns `'right'`.
  1212. @function up.util.horizontalScreenHalf
  1213. @internal
  1214. */
  1215. horizontalScreenHalf = function(element) {
  1216. var elementDims, elementMid, screenMid;
  1217. elementDims = element.getBoundingClientRect();
  1218. elementMid = elementDims.left + 0.5 * elementDims.width;
  1219. screenMid = 0.5 * up.viewport.rootWidth();
  1220. if (elementMid < screenMid) {
  1221. return 'left';
  1222. } else {
  1223. return 'right';
  1224. }
  1225. };
  1226. /***
  1227. Flattens the given `array` a single level deep.
  1228. @function up.util.flatten
  1229. @param {Array} array
  1230. An array which might contain other arrays
  1231. @return {Array}
  1232. The flattened array
  1233. @experimental
  1234. */
  1235. flatten = function(array) {
  1236. var flattened, i, len, object;
  1237. flattened = [];
  1238. for (i = 0, len = array.length; i < len; i++) {
  1239. object = array[i];
  1240. if (isList(object)) {
  1241. flattened.push.apply(flattened, object);
  1242. } else {
  1243. flattened.push(object);
  1244. }
  1245. }
  1246. return flattened;
  1247. };
  1248. /***
  1249. Maps each element using a mapping function,
  1250. then flattens the result into a new array.
  1251. @function up.util.flatMap
  1252. @param {Array} array
  1253. @param {Function(element)} mapping
  1254. @return {Array}
  1255. @experimental
  1256. */
  1257. flatMap = function(array, block) {
  1258. return flatten(map(array, block));
  1259. };
  1260. /***
  1261. Returns whether the given value is truthy.
  1262. @function up.util.isTruthy
  1263. @internal
  1264. */
  1265. isTruthy = function(object) {
  1266. return !!object;
  1267. };
  1268. /***
  1269. Sets the given callback as both fulfillment and rejection handler for the given promise.
  1270. @function up.util.always
  1271. @internal
  1272. */
  1273. always = function(promise, callback) {
  1274. return promise.then(callback, callback);
  1275. };
  1276. /***
  1277. * Registers an empty rejection handler with the given promise.
  1278. * This prevents browsers from printing "Uncaught (in promise)" to the error
  1279. * console when the promise is rejection.
  1280. *
  1281. * This is helpful for event handlers where it is clear that no rejection
  1282. * handler will be registered:
  1283. *
  1284. * up.on('submit', 'form[up-target]', (event, $form) => {
  1285. * promise = up.submit($form)
  1286. * up.util.muteRejection(promise)
  1287. * })
  1288. *
  1289. * Does nothing if passed a missing value.
  1290. *
  1291. * @function up.util.muteRejection
  1292. * @param {Promise|undefined|null} promise
  1293. * @return {Promise}
  1294. */
  1295. muteRejection = function(promise) {
  1296. return promise != null ? promise["catch"](noop) : void 0;
  1297. };
  1298. /***
  1299. @function up.util.newDeferred
  1300. @internal
  1301. */
  1302. /***
  1303. @function up.util.newDeferred
  1304. @internal
  1305. */
  1306. newDeferred = function() {
  1307. var nativePromise, rejectFn, resolveFn;
  1308. resolveFn = void 0;
  1309. rejectFn = void 0;
  1310. nativePromise = new Promise(function(givenResolve, givenReject) {
  1311. resolveFn = givenResolve;
  1312. return rejectFn = givenReject;
  1313. });
  1314. nativePromise.resolve = resolveFn;
  1315. nativePromise.reject = rejectFn;
  1316. nativePromise.promise = function() {
  1317. return nativePromise;
  1318. };
  1319. return nativePromise;
  1320. };
  1321. /***
  1322. Calls the given block. If the block throws an exception,
  1323. a rejected promise is returned instead.
  1324. @function up.util.rejectOnError
  1325. @internal
  1326. */
  1327. rejectOnError = function(block) {
  1328. var error;
  1329. try {
  1330. return block();
  1331. } catch (error1) {
  1332. error = error1;
  1333. return Promise.reject(error);
  1334. }
  1335. };
  1336. sum = function(list, block) {
  1337. var entry, entryValue, i, len, totalValue;
  1338. block = iteratee(block);
  1339. totalValue = 0;
  1340. for (i = 0, len = list.length; i < len; i++) {
  1341. entry = list[i];
  1342. entryValue = block(entry);
  1343. if (isGiven(entryValue)) {
  1344. totalValue += entryValue;
  1345. }
  1346. }
  1347. return totalValue;
  1348. };
  1349. isBasicObjectProperty = function(k) {
  1350. return Object.prototype.hasOwnProperty(k);
  1351. };
  1352. /***
  1353. Returns whether the two arguments are equal by value.
  1354. \#\#\# Comparison protocol
  1355. - By default `up.util.isEqual()` can compare strings, numbers,
  1356. [array-like values](/up.util.isList), plain objects and `Date` objects.
  1357. - To make the copying protocol work with user-defined classes,
  1358. see `up.util.isEqual.key`.
  1359. - Objects without a defined comparison protocol are
  1360. defined by reference (`===`).
  1361. @function up.util.isEqual
  1362. @param {any} a
  1363. @param {any} b
  1364. @return {boolean}
  1365. Whether the arguments are equal by value.
  1366. @experimental
  1367. */
  1368. isEqual = function(a, b) {
  1369. var aKeys, bKeys;
  1370. if (a != null ? a.valueOf : void 0) {
  1371. a = a.valueOf();
  1372. }
  1373. if (b != null ? b.valueOf : void 0) {
  1374. b = b.valueOf();
  1375. }
  1376. if (typeof a !== typeof b) {
  1377. return false;
  1378. } else if (isList(a) && isList(b)) {
  1379. return isEqualList(a, b);
  1380. } else if (isObject(a) && a[isEqual.key]) {
  1381. return a[isEqual.key](b);
  1382. } else if (isOptions(a) && isOptions(b)) {
  1383. aKeys = Object.keys(a);
  1384. bKeys = Object.keys(b);
  1385. if (isEqualList(aKeys, bKeys)) {
  1386. return every(aKeys, function(aKey) {
  1387. return isEqual(a[aKey], b[aKey]);
  1388. });
  1389. } else {
  1390. return false;
  1391. }
  1392. } else {
  1393. return a === b;
  1394. }
  1395. };
  1396. /***
  1397. This property contains the name of a method that user-defined classes
  1398. may implement to hook into the `up.util.isEqual()` protocol.
  1399. \#\#\# Example
  1400. We have a user-defined `Account` class that we want to use with `up.util.isEqual()`:
  1401. ```
  1402. class Account {
  1403. constructor(email) {
  1404. this.email = email
  1405. }
  1406. [up.util.isEqual.key](other) {
  1407. return this.email === other.email;
  1408. }
  1409. }
  1410. ```
  1411. Note that the protocol method is not actually named `'up.util.isEqual.key'`.
  1412. Instead it is named after the *value* of the `up.util.isEqual.key` property.
  1413. To do so, the code sample above is using a
  1414. [computed property name](https://medium.com/front-end-weekly/javascript-object-creation-356e504173a8)
  1415. in square brackets.
  1416. We may now use `Account` instances with `up.util.isEqual()`:
  1417. ```
  1418. one = new User('foo@foo.com')
  1419. two = new User('foo@foo.com')
  1420. three = new User('bar@bar.com')
  1421. isEqual = up.util.isEqual(one, two)
  1422. // isEqual is now true
  1423. isEqual = up.util.isEqual(one, three)
  1424. // isEqual is now false
  1425. ```
  1426. @property up.util.isEqual.key
  1427. @param {string} key
  1428. @experimental
  1429. */
  1430. isEqual.key = 'up.util.isEqual';
  1431. isEqualList = function(a, b) {
  1432. return a.length === b.length && every(a, function(elem, index) {
  1433. return isEqual(elem, b[index]);
  1434. });
  1435. };
  1436. splitValues = function(value, separator) {
  1437. var values;
  1438. if (separator == null) {
  1439. separator = ' ';
  1440. }
  1441. values = value.split(separator);
  1442. values = map(values, function(v) {
  1443. return v.trim();
  1444. });
  1445. values = filterList(values, isPresent);
  1446. return values;
  1447. };
  1448. endsWith = function(string, search) {
  1449. if (search.length > string.length) {
  1450. return false;
  1451. } else {
  1452. return string.substring(string.length - search.length) === search;
  1453. }
  1454. };
  1455. simpleEase = function(x) {
  1456. if (x < 0.5) {
  1457. return 2 * x * x;
  1458. } else {
  1459. return x * (4 - x * 2) - 1;
  1460. }
  1461. };
  1462. wrapValue = function(object, constructor) {
  1463. if (object instanceof constructor) {
  1464. return object;
  1465. } else {
  1466. return new constructor(object);
  1467. }
  1468. };
  1469. nextUid = 0;
  1470. uid = function() {
  1471. return nextUid++;
  1472. };
  1473. return {
  1474. parseUrl: parseUrl,
  1475. normalizeUrl: normalizeUrl,
  1476. normalizeMethod: normalizeMethod,
  1477. methodAllowsPayload: methodAllowsPayload,
  1478. assign: assign,
  1479. assignPolyfill: assignPolyfill,
  1480. copy: copy,
  1481. deepCopy: deepCopy,
  1482. merge: merge,
  1483. options: newOptions,
  1484. fail: fail,
  1485. each: each,
  1486. eachIterator: eachIterator,
  1487. map: map,
  1488. flatMap: flatMap,
  1489. mapObject: mapObject,
  1490. times: times,
  1491. findResult: findResult,
  1492. some: some,
  1493. any: function() {
  1494. up.legacy.warn('up.util.any() has been renamed to up.util.some()');
  1495. return some.apply(null, arguments);
  1496. },
  1497. every: every,
  1498. all: function() {
  1499. up.legacy.warn('up.util.all() has been renamed to up.util.every()');
  1500. return every.apply(null, arguments);
  1501. },
  1502. detect: function() {
  1503. up.legacy.warn('up.util.find() has been renamed to up.util.find()');
  1504. return findInList.apply(null, arguments);
  1505. },
  1506. find: findInList,
  1507. select: function() {
  1508. up.legacy.warn('up.util.select() has been renamed to up.util.filter()');
  1509. return filterList.apply(null, arguments);
  1510. },
  1511. filter: filterList,
  1512. reject: reject,
  1513. intersect: intersect,
  1514. compact: compact,
  1515. uniq: uniq,
  1516. uniqBy: uniqBy,
  1517. last: last,
  1518. isNull: isNull,
  1519. isDefined: isDefined,
  1520. isUndefined: isUndefined,
  1521. isGiven: isGiven,
  1522. isMissing: isMissing,
  1523. isPresent: isPresent,
  1524. isBlank: isBlank,
  1525. presence: presence,
  1526. isObject: isObject,
  1527. isFunction: isFunction,
  1528. isString: isString,
  1529. isBoolean: isBoolean,
  1530. isNumber: isNumber,
  1531. isElement: isElement,
  1532. isJQuery: isJQuery,
  1533. isPromise: isPromise,
  1534. isOptions: isOptions,
  1535. isArray: isArray,
  1536. isFormData: isFormData,
  1537. isNodeList: isNodeList,
  1538. isArguments: isArguments,
  1539. isList: isList,
  1540. isUnmodifiedKeyEvent: isUnmodifiedKeyEvent,
  1541. isUnmodifiedMouseEvent: isUnmodifiedMouseEvent,
  1542. timer: scheduleTimer,
  1543. setTimer: function() {
  1544. up.legacy.warn('up.util.setTimer() has been renamed to up.util.timer()');
  1545. return scheduleTimer.apply(null, arguments);
  1546. },
  1547. escapePressed: escapePressed,
  1548. contains: contains,
  1549. toArray: toArray,
  1550. only: only,
  1551. except: except,
  1552. unresolvablePromise: unresolvablePromise,
  1553. remove: remove,
  1554. memoize: memoize,
  1555. error: fail,
  1556. pluckKey: pluckKey,
  1557. renameKey: renameKey,
  1558. extractOptions: extractOptions,
  1559. extractCallback: extractCallback,
  1560. noop: noop,
  1561. asyncNoop: asyncNoop,
  1562. identity: identity,
  1563. escapeHtml: escapeHtml,
  1564. escapeRegexp: escapeRegexp,
  1565. sequence: sequence,
  1566. previewable: previewable,
  1567. evalOption: evalOption,
  1568. horizontalScreenHalf: horizontalScreenHalf,
  1569. flatten: flatten,
  1570. isTruthy: isTruthy,
  1571. newDeferred: newDeferred,
  1572. always: always,
  1573. muteRejection: muteRejection,
  1574. rejectOnError: rejectOnError,
  1575. isBasicObjectProperty: isBasicObjectProperty,
  1576. isCrossDomain: isCrossDomain,
  1577. selectorForElement: function() {
  1578. up.legacy.warn('up.util.selectorForElement() has been renamed to up.element.toSelector()');
  1579. return up.element.toSelector.apply(null, arguments);
  1580. },
  1581. nextFrame: function() {
  1582. up.legacy.warn('up.util.nextFrame() has been renamed to up.util.task()');
  1583. return queueTask.apply(null, arguments);
  1584. },
  1585. task: queueTask,
  1586. microtask: queueMicrotask,
  1587. isEqual: isEqual,
  1588. splitValues: splitValues,
  1589. endsWith: endsWith,
  1590. sum: sum,
  1591. wrapList: wrapList,
  1592. wrapValue: wrapValue,
  1593. simpleEase: simpleEase,
  1594. values: objectValues,
  1595. partial: partial,
  1596. arrayToSet: arrayToSet,
  1597. setToArray: setToArray,
  1598. uid: uid
  1599. };
  1600. })();
  1601. up.fail = up.util.fail;
  1602. }).call(this);
  1603. (function() {
  1604. var u,
  1605. slice = [].slice;
  1606. u = up.util;
  1607. up.legacy = (function() {
  1608. var fixKey, renamedModule, warn, warnedMessages;
  1609. fixKey = function(object, oldKey, newKey) {
  1610. if (oldKey in object) {
  1611. warn('Property { %s } has been renamed to { %s } (found in %o)', oldKey, newKey, object);
  1612. return u.renameKey(object, oldKey, newKey);
  1613. }
  1614. };
  1615. renamedModule = function(oldName, newName) {
  1616. return Object.defineProperty(up, oldName, {
  1617. get: function() {
  1618. warn("up." + oldName + " has been renamed to up." + newName);
  1619. return up[newName];
  1620. }
  1621. });
  1622. };
  1623. warnedMessages = {};
  1624. warn = function() {
  1625. var args, message, ref;
  1626. message = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];
  1627. message = "[DEPRECATION] " + message;
  1628. message = (ref = up.log).sprintf.apply(ref, [message].concat(slice.call(args)));
  1629. if (!warnedMessages[message]) {
  1630. warnedMessages[message] = true;
  1631. return up.warn(message);
  1632. }
  1633. };
  1634. return {
  1635. renamedModule: renamedModule,
  1636. fixKey: fixKey,
  1637. warn: warn
  1638. };
  1639. })();
  1640. }).call(this);
  1641. /***
  1642. Browser support
  1643. ===============
  1644. Unpoly supports all modern browsers.
  1645. Chrome, Firefox, Edge, Safari
  1646. : Full support
  1647. Internet Explorer 11
  1648. : Full support with a `Promise` polyfill like [es6-promise](https://github.com/stefanpenner/es6-promise) (2.4 KB).
  1649. Internet Explorer 10 or lower
  1650. : Unpoly prevents itself from booting itself, leaving you with a classic server-side application.
  1651. @module up.browser
  1652. */
  1653. (function() {
  1654. var slice = [].slice;
  1655. up.browser = (function() {
  1656. var callJQuery, canAnimationFrame, canConsole, canControlScrollRestoration, canCssTransition, canCustomElements, canDOMParser, canFormData, canInputEvent, canInspectFormData, canJQuery, canPromise, canPushState, isIE10OrWorse, isIE11, isSupported, navigate, popCookie, submitForm, u, url, whenConfirmed;
  1657. u = up.util;
  1658. /***
  1659. @method up.browser.navigate
  1660. @param {string} url
  1661. @param {string} [options.method='get']
  1662. @param {object|Array|FormData|string} [options.params]
  1663. @internal
  1664. */
  1665. navigate = function(url, options) {
  1666. var request, requestOpts;
  1667. requestOpts = u.merge(options, {
  1668. url: url
  1669. });
  1670. request = new up.Request(requestOpts);
  1671. return request.navigate();
  1672. };
  1673. /***
  1674. For mocking in specs.
  1675. @method submitForm
  1676. */
  1677. submitForm = function(form) {
  1678. return form.submit();
  1679. };
  1680. url = function() {
  1681. return location.href;
  1682. };
  1683. isIE10OrWorse = u.memoize(function() {
  1684. return !window.atob;
  1685. });
  1686. isIE11 = u.memoize(function() {
  1687. return 'ActiveXObject' in window;
  1688. });
  1689. /***
  1690. Returns whether this browser supports manipulation of the current URL
  1691. via [`history.pushState`](https://developer.mozilla.org/en-US/docs/Web/API/History/pushState).
  1692. When `pushState` (e.g. through [`up.follow()`](/up.follow)), it will gracefully
  1693. fall back to a full page load.
  1694. Note that Unpoly will not use `pushState` if the initial page was loaded with
  1695. a request method other than GET.
  1696. @function up.browser.canPushState
  1697. @return {boolean}
  1698. @experimental
  1699. */
  1700. canPushState = function() {
  1701. return u.isDefined(history.pushState) && up.protocol.initialRequestMethod() === 'get';
  1702. };
  1703. /***
  1704. Returns whether this browser supports animation using
  1705. [CSS transitions](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Transitions).
  1706. When Unpoly is asked to animate history on a browser that doesn't support
  1707. CSS transitions (e.g. through [`up.animate()`](/up.animate)), it will skip the
  1708. animation by instantly jumping to the last frame.
  1709. @function up.browser.canCssTransition
  1710. @return {boolean}
  1711. @internal
  1712. */
  1713. canCssTransition = u.memoize(function() {
  1714. return 'transition' in document.documentElement.style;
  1715. });
  1716. /***
  1717. Returns whether this browser supports the DOM event [`input`](https://developer.mozilla.org/de/docs/Web/Events/input).
  1718. @function up.browser.canInputEvent
  1719. @return {boolean}
  1720. @internal
  1721. */
  1722. canInputEvent = u.memoize(function() {
  1723. return 'oninput' in document.createElement('input');
  1724. });
  1725. /***
  1726. Returns whether this browser supports promises.
  1727. @function up.browser.canPromise
  1728. @return {boolean}
  1729. @internal
  1730. */
  1731. canPromise = u.memoize(function() {
  1732. return !!window.Promise;
  1733. });
  1734. /***
  1735. Returns whether this browser supports the [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData)
  1736. interface.
  1737. @function up.browser.canFormData
  1738. @return {boolean}
  1739. @experimental
  1740. */
  1741. canFormData = u.memoize(function() {
  1742. return !!window.FormData;
  1743. });
  1744. /***
  1745. @function up.browser.canInspectFormData
  1746. @return {boolean}
  1747. @internal
  1748. */
  1749. canInspectFormData = u.memoize(function() {
  1750. return canFormData() && !!FormData.prototype.entries;
  1751. });
  1752. /***
  1753. Returns whether this browser supports the [`DOMParser`](https://developer.mozilla.org/en-US/docs/Web/API/DOMParser)
  1754. interface.
  1755. @function up.browser.canDOMParser
  1756. @return {boolean}
  1757. @internal
  1758. */
  1759. canDOMParser = u.memoize(function() {
  1760. return !!window.DOMParser;
  1761. });
  1762. /***
  1763. Returns whether this browser supports the [`debugging console`](https://developer.mozilla.org/en-US/docs/Web/API/Console).
  1764. @function up.browser.canConsole
  1765. @return {boolean}
  1766. @internal
  1767. */
  1768. canConsole = u.memoize(function() {
  1769. return window.console && console.debug && console.info && console.warn && console.error && console.group && console.groupCollapsed && console.groupEnd;
  1770. });
  1771. canCustomElements = u.memoize(function() {
  1772. return !!window.customElements;
  1773. });
  1774. canAnimationFrame = u.memoize(function() {
  1775. return 'requestAnimationFrame' in window;
  1776. });
  1777. canControlScrollRestoration = u.memoize(function() {
  1778. return 'scrollRestoration' in history;
  1779. });
  1780. canJQuery = function() {
  1781. return !!window.jQuery;
  1782. };
  1783. popCookie = function(name) {
  1784. var ref, value;
  1785. value = (ref = document.cookie.match(new RegExp(name + "=(\\w+)"))) != null ? ref[1] : void 0;
  1786. if (u.isPresent(value)) {
  1787. document.cookie = name + '=; expires=Thu, 01-Jan-70 00:00:01 GMT; path=/';
  1788. }
  1789. return value;
  1790. };
  1791. /***
  1792. @function up,browser.whenConfirmed
  1793. @return {Promise}
  1794. @param {string} options.confirm
  1795. @param {boolean} options.preload
  1796. @internal
  1797. */
  1798. whenConfirmed = function(options) {
  1799. if (options.preload || u.isBlank(options.confirm) || window.confirm(options.confirm)) {
  1800. return Promise.resolve();
  1801. } else {
  1802. return Promise.reject(new Error('User canceled action'));
  1803. }
  1804. };
  1805. /***
  1806. Returns whether Unpoly supports the current browser.
  1807. If this returns `false` Unpoly will prevent itself from [booting](/up.boot)
  1808. and ignores all registered [event handlers](/up.on) and [compilers](/up.compiler).
  1809. This leaves you with a classic server-side application.
  1810. This is usually a better fallback than loading incompatible Javascript and causing
  1811. many errors on load.
  1812. @function up.browser.isSupported
  1813. @stable
  1814. */
  1815. isSupported = function() {
  1816. return !isIE10OrWorse() && canConsole() && canDOMParser() && canFormData() && canCssTransition() && canInputEvent() && canPromise() && canAnimationFrame();
  1817. };
  1818. callJQuery = function() {
  1819. var args;
  1820. args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
  1821. canJQuery() || up.fail("jQuery must be published as window.jQuery");
  1822. return jQuery.apply(null, args);
  1823. };
  1824. return {
  1825. url: url,
  1826. navigate: navigate,
  1827. submitForm: submitForm,
  1828. canPushState: canPushState,
  1829. canFormData: canFormData,
  1830. canInspectFormData: canInspectFormData,
  1831. canCustomElements: canCustomElements,
  1832. canControlScrollRestoration: canControlScrollRestoration,
  1833. canJQuery: canJQuery,
  1834. whenConfirmed: whenConfirmed,
  1835. isSupported: isSupported,
  1836. popCookie: popCookie,
  1837. jQuery: callJQuery,
  1838. isIE11: isIE11
  1839. };
  1840. })();
  1841. }).call(this);
  1842. (function() {
  1843. var u;
  1844. u = up.util;
  1845. up.Selector = (function() {
  1846. var CSS_HAS_SUFFIX_PATTERN, MATCH_FN_NAME;
  1847. CSS_HAS_SUFFIX_PATTERN = new RegExp("\\:has\\(([^\\)]+)\\)$");
  1848. MATCH_FN_NAME = up.browser.isIE11() ? 'msMatchesSelector' : 'matches';
  1849. function Selector(selector1, filterFn) {
  1850. this.selector = selector1;
  1851. this.filterFn = filterFn;
  1852. }
  1853. Selector.prototype.matches = function(element) {
  1854. var doesMatch;
  1855. doesMatch = element[MATCH_FN_NAME](this.selector);
  1856. if (this.filterFn) {
  1857. doesMatch && (doesMatch = this.filterFn(element));
  1858. }
  1859. return doesMatch;
  1860. };
  1861. Selector.prototype.descendants = function(root) {
  1862. var matches;
  1863. matches = root.querySelectorAll(this.selector);
  1864. if (this.filterFn) {
  1865. matches = u.filter(matches, this.filterFn);
  1866. }
  1867. return matches;
  1868. };
  1869. Selector.prototype.descendant = function(root) {
  1870. var candidates;
  1871. if (!this.filterFn) {
  1872. return root.querySelector(this.selector);
  1873. } else {
  1874. candidates = root.querySelectorAll(this.selector);
  1875. return u.find(candidates, this.filterFn);
  1876. }
  1877. };
  1878. Selector.prototype.subtree = function(root) {
  1879. var matches;
  1880. matches = [];
  1881. if (this.matches(root)) {
  1882. matches.push(root);
  1883. }
  1884. matches.push.apply(matches, this.descendants(root));
  1885. return matches;
  1886. };
  1887. Selector.prototype.closest = function(root) {
  1888. if (root.closest && !this.filterFn) {
  1889. return root.closest(this.selector);
  1890. } else {
  1891. return this.closestPolyfill(root);
  1892. }
  1893. };
  1894. Selector.prototype.closestPolyfill = function(root) {
  1895. if (this.matches(root, this.selector)) {
  1896. return root;
  1897. } else {
  1898. return this.ancestor(root);
  1899. }
  1900. };
  1901. Selector.prototype.ancestor = function(element) {
  1902. var parentElement;
  1903. if (parentElement = element.parentElement) {
  1904. if (this.matches(parentElement)) {
  1905. return parentElement;
  1906. } else {
  1907. return this.ancestor(parentElement);
  1908. }
  1909. }
  1910. };
  1911. Selector.parse = function(selector) {
  1912. var filter;
  1913. filter = null;
  1914. selector = selector.replace(CSS_HAS_SUFFIX_PATTERN, function(match, descendantSelector) {
  1915. filter = function(element) {
  1916. return element.querySelector(descendantSelector);
  1917. };
  1918. return '';
  1919. });
  1920. return new this(selector, filter);
  1921. };
  1922. return Selector;
  1923. })();
  1924. }).call(this);
  1925. /***
  1926. DOM helpers
  1927. ===========
  1928. The `up.element` module offers functions for DOM manipulation and traversal.
  1929. It complements [native `Element` methods](https://www.w3schools.com/jsref/dom_obj_all.asp) and works across all [supported browsers](/up.browser).
  1930. @module up.element
  1931. */
  1932. (function() {
  1933. var slice = [].slice;
  1934. up.element = (function() {
  1935. var CSS_LENGTH_PROPS, NONE, affix, all, ancestor, attributeSelector, booleanAttr, booleanOrStringAttr, closest, computedStyle, computedStyleNumber, concludeCssTransition, createDocumentFromHtml, createFromHtml, createFromSelector, cssLength, elementTagName, extractFromStyleObject, first, fixedToAbsolute, getList, getOne, getRoot, hasCssTransition, hide, inlineStyle, insertBefore, isSingleton, isVisible, jsonAttr, matches, metaContent, nonUpClasses, normalizeStyleValueForWrite, numberAttr, paint, parseSelector, remove, replace, resolveSelector, setAttrs, setInlineStyle, setMissingAttrs, setTemporaryStyle, show, subtree, toSelector, toggle, toggleClass, u, unwrap, valueToList;
  1936. u = up.util;
  1937. /***
  1938. Returns a null-object that mostly behaves like an `Element`.
  1939. @function up.element.none()
  1940. @internal
  1941. */
  1942. NONE = {
  1943. getAttribute: function() {
  1944. return void 0;
  1945. }
  1946. };
  1947. /***
  1948. Matches all elements that have a descendant matching the given selector.
  1949. \#\#\# Example
  1950. `up.element.all('div:has(span)')` matches all `<div>` elements with at least one `<span>` among its descendants:
  1951. ```html
  1952. <div>
  1953. <span>Will be matched</span>
  1954. </div>
  1955. <div>
  1956. Will NOT be matched
  1957. </div>
  1958. <div>
  1959. <span>Will be matched</span>
  1960. </div>
  1961. ```
  1962. \#\#\# Compatibility
  1963. `:has()` is supported by all Unpoly functions (like `up.element.all()`) and
  1964. selectors (like `a[up-target]`).
  1965. As a [level 4 CSS selector](https://drafts.csswg.org/selectors-4/#relational),
  1966. `:has()` [has yet to be implemented](https://caniuse.com/#feat=css-has)
  1967. in native browser functions like [`document.querySelectorAll()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/querySelectorAll).
  1968. You can also use [`:has()` in jQuery](https://api.jquery.com/has-selector/).
  1969. @selector :has()
  1970. @experimental
  1971. */
  1972. parseSelector = function(selector) {
  1973. return up.Selector.parse(selector);
  1974. };
  1975. /***
  1976. Returns the first descendant element matching the given selector.
  1977. It is similar to [`element.querySelector()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector),
  1978. but also supports the [`:has()`](/has) selector.
  1979. @function up.element.first
  1980. @param {Element} [parent=document]
  1981. The parent element whose descendants to search.
  1982. If omitted, all elements in the `document` will be searched.
  1983. @param {string} selector
  1984. The CSS selector to match.
  1985. @return {Element|undefined|null}
  1986. The first element matching the selector.
  1987. Returns `null` or `undefined` if no element macthes.
  1988. @experimental
  1989. */
  1990. first = function() {
  1991. var args, parent, ref, selector;
  1992. args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
  1993. selector = args.pop();
  1994. parent = (ref = args[0]) != null ? ref : document;
  1995. return parseSelector(selector).descendant(parent);
  1996. };
  1997. /***
  1998. Returns all descendant elements matching the given selector.
  1999. @function up.element.all
  2000. @param {Element} [parent=document]
  2001. The parent element whose descendants to search.
  2002. If omitted, all elements in the `document` will be searched.
  2003. @param {string} selector
  2004. The CSS selector to match.
  2005. @return {NodeList<Element>|Array<Element>}
  2006. A list of all elements matching the selector.
  2007. Returns an empty list if there are no matches.
  2008. @experimental
  2009. */
  2010. all = function() {
  2011. var args, parent, ref, selector;
  2012. args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
  2013. selector = args.pop();
  2014. parent = (ref = args[0]) != null ? ref : document;
  2015. return parseSelector(selector).descendants(parent);
  2016. };
  2017. /***
  2018. Returns a list of the given parent's descendants matching the given selector.
  2019. The list will also include the parent element if it matches the selector itself.
  2020. @function up.element.subtree
  2021. @param {Element} parent
  2022. The parent element for the search.
  2023. @param {string} selector
  2024. The CSS selector to match.
  2025. @return {NodeList<Element>|Array<Element>}
  2026. A list of all matching elements.
  2027. @experimental
  2028. */
  2029. subtree = function(root, selector) {
  2030. return parseSelector(selector).subtree(root);
  2031. };
  2032. /***
  2033. Returns the first element that matches the selector by testing the element itself
  2034. and traversing up through its ancestors in the DOM tree.
  2035. @function up.element.closest
  2036. @param {Element} element
  2037. The element on which to start the search.
  2038. @param {string} selector
  2039. The CSS selector to match.
  2040. @return {Element|null|undefined} element
  2041. The matching element.
  2042. Returns `null` or `undefined` if no element matches.
  2043. @experimental
  2044. */
  2045. closest = function(element, selector) {
  2046. return parseSelector(selector).closest(element);
  2047. };
  2048. /***
  2049. Returns whether the given element matches the given CSS selector.
  2050. @function up.element.matches
  2051. @param {Element} element
  2052. The element to check.
  2053. @param {string} selector
  2054. The CSS selector to match.
  2055. @return {boolean}
  2056. Whether `element` matches `selector`.
  2057. @experimental
  2058. */
  2059. matches = function(element, selector) {
  2060. return parseSelector(selector).matches(element);
  2061. };
  2062. /***
  2063. @function up.element.ancestor
  2064. @internal
  2065. */
  2066. ancestor = function(element, selector) {
  2067. return parseSelector(selector).ancestor(element);
  2068. };
  2069. /***
  2070. Casts the given value to a native [Element](https://developer.mozilla.org/en-US/docs/Web/API/Element).
  2071. This is useful when working with jQuery values, or to allow callers to pass CSS selectors
  2072. instead of elements.
  2073. \#\#\# Casting rules
  2074. - If given an element, returns that element.
  2075. - If given a CSS selector string, returns the [first element matching](/up.element.first) that selector.
  2076. - If given a jQuery collection , returns the first element in the collection.
  2077. Throws an error if the collection contains more than one element.
  2078. - If given any other argument (`undefined`, `null`, `document`, `window`…), returns the argument unchanged.
  2079. @function up.element.get
  2080. @param {Element|jQuery|string} value
  2081. The value to cast.
  2082. @return {Element}
  2083. The obtained `Element`.
  2084. @experimental
  2085. */
  2086. getOne = function(value) {
  2087. if (u.isElement(value)) {
  2088. return value;
  2089. } else if (u.isString(value)) {
  2090. return first(value);
  2091. } else if (u.isJQuery(value)) {
  2092. if (value.length > 1) {
  2093. up.fail('up.element.get(): Cannot cast multiple elements (%o) to a single element', value);
  2094. }
  2095. return value[0];
  2096. } else {
  2097. return value;
  2098. }
  2099. };
  2100. /***
  2101. Composes a list of elements from the given arguments.
  2102. \#\#\# Casting rules
  2103. - If given a string, returns the all elements matching that string.
  2104. - If given any other argument, returns the argument [wrapped as a list](/up.util.wrapList).
  2105. \#\#\# Example
  2106. ```javascript
  2107. $jquery = $('.jquery') // returns jQuery (2) [div.jquery, div.jquery]
  2108. nodeList = document.querySelectorAll('.node') // returns NodeList (2) [div.node, div.node]
  2109. element = document.querySelector('.element') // returns Element div.element
  2110. selector = '.selector' // returns String '.selector'
  2111. elements = up.element.list($jquery, nodeList, undefined, element, selector)
  2112. // returns [div.jquery, div.jquery, div.node, div.node, div.element, div.selector]
  2113. ```
  2114. @function up.element.list
  2115. @param {Array<jQuery|Element|Array<Element>|String|undefined|null>} ...args
  2116. @return {Array<Element>}
  2117. @internal
  2118. */
  2119. getList = function() {
  2120. var args;
  2121. args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
  2122. return u.flatMap(args, valueToList);
  2123. };
  2124. valueToList = function(value) {
  2125. if (u.isString(value)) {
  2126. return all(value);
  2127. } else {
  2128. return u.wrapList(value);
  2129. }
  2130. };
  2131. /***
  2132. Removes the given element from the DOM tree.
  2133. If you don't need IE11 support you may also use the built-in
  2134. [`Element#remove()`](https://developer.mozilla.org/en-US/docs/Web/API/ChildNode/remove) to the same effect.
  2135. @function up.element.remove
  2136. @param {Element} element
  2137. The element to remove.
  2138. @experimental
  2139. */
  2140. remove = function(element) {
  2141. var parent;
  2142. if (element.remove) {
  2143. return element.remove();
  2144. } else if (parent = element.parentNode) {
  2145. return parent.removeChild(element);
  2146. }
  2147. };
  2148. /***
  2149. Hides the given element.
  2150. The element is hidden by setting an [inline style](https://www.codecademy.com/articles/html-inline-styles)
  2151. of `{ display: none }`.
  2152. Also see `up.element.show()`.
  2153. @function up.element.hide
  2154. @param {Element} element
  2155. @experimental
  2156. */
  2157. hide = function(element) {
  2158. return element.style.display = 'none';
  2159. };
  2160. /***
  2161. Shows the given element.
  2162. Also see `up.element.hide()`.
  2163. \#\#\# Limitations
  2164. The element is shown by setting an [inline style](https://www.codecademy.com/articles/html-inline-styles)
  2165. of `{ display: '' }`.
  2166. You might have CSS rules causing the element to remain hidden after calling `up.element.show(element)`.
  2167. Unpoly will not handle such cases in order to keep this function performant. As a workaround, you may
  2168. manually set the `element.style.display` property. Also see discussion
  2169. in jQuery issues [#88](https://github.com/jquery/jquery.com/issues/88),
  2170. [#2057](https://github.com/jquery/jquery/issues/2057) and
  2171. [this WHATWG mailing list post](http://lists.w3.org/Archives/Public/public-whatwg-archive/2014Apr/0094.html).
  2172. @function up.element.show
  2173. @experimental
  2174. */
  2175. show = function(element) {
  2176. return element.style.display = '';
  2177. };
  2178. /***
  2179. Display or hide the given element, depending on its current visibility.
  2180. @function up.element.toggle
  2181. @param {Element} element
  2182. @param {Boolean} [newVisible]
  2183. Pass `true` to show the element or `false` to hide it.
  2184. If omitted, the element will be hidden if shown and shown if hidden.
  2185. @experimental
  2186. */
  2187. toggle = function(element, newVisible) {
  2188. if (newVisible == null) {
  2189. newVisible = !isVisible(element);
  2190. }
  2191. if (newVisible) {
  2192. return show(element);
  2193. } else {
  2194. return hide(element);
  2195. }
  2196. };
  2197. /***
  2198. Adds or removes the given class from the given element.
  2199. If you don't need IE11 support you may also use the built-in
  2200. [`Element#classList.toggle(className)`](https://developer.mozilla.org/en-US/docs/Web/API/Element/classList) to the same effect.
  2201. @function up.element.toggleClass
  2202. @param {Element} element
  2203. The element for which to add or remove the class.
  2204. @param {String} className
  2205. A boolean value to determine whether the class should be added or removed.
  2206. @param {String} state
  2207. If omitted, the class will be added if missing and removed if present.
  2208. @experimental
  2209. */
  2210. toggleClass = function(element, klass, newPresent) {
  2211. var list;
  2212. list = element.classList;
  2213. if (newPresent == null) {
  2214. newPresent = !list.contains(klass);
  2215. }
  2216. if (newPresent) {
  2217. return list.add(klass);
  2218. } else {
  2219. return list.remove(klass);
  2220. }
  2221. };
  2222. /***
  2223. Sets all key/values from the given object as attributes on the given element.
  2224. \#\#\# Example
  2225. up.element.setAttrs(element, { title: 'Tooltip', tabindex: 1 })
  2226. @function up.element.setAttrs
  2227. @param {Element} element
  2228. The element on which to set attributes.
  2229. @param {object} attributes
  2230. An object of attributes to set.
  2231. @experimental
  2232. */
  2233. setAttrs = function(element, attributes) {
  2234. var key, results, value;
  2235. results = [];
  2236. for (key in attributes) {
  2237. value = attributes[key];
  2238. results.push(element.setAttribute(key, value));
  2239. }
  2240. return results;
  2241. };
  2242. /***
  2243. @function up.element.metaContent
  2244. @internal
  2245. */
  2246. metaContent = function(name) {
  2247. var ref, selector;
  2248. selector = "meta" + attributeSelector('name', name);
  2249. return (ref = first(selector)) != null ? ref.getAttribute('content') : void 0;
  2250. };
  2251. /***
  2252. @function up.element.insertBefore
  2253. @internal
  2254. */
  2255. insertBefore = function(existingElement, newElement) {
  2256. return existingElement.insertAdjacentElement('beforebegin', newElement);
  2257. };
  2258. /***
  2259. Replaces the given old element with the given new element.
  2260. The old element will be removed from the DOM tree.
  2261. If you don't need IE11 support you may also use the built-in
  2262. [`Element#replaceWith()`](https://developer.mozilla.org/en-US/docs/Web/API/ChildNode/replaceWith) to the same effect.
  2263. @function up.element.replace
  2264. @param {Element} oldElement
  2265. @param {Element} newElement
  2266. @experimental
  2267. */
  2268. replace = function(oldElement, newElement) {
  2269. return oldElement.parentElement.replaceChild(newElement, oldElement);
  2270. };
  2271. /***
  2272. Creates an element matching the given CSS selector.
  2273. The created element will not yet be attached to the DOM tree.
  2274. Attach it with [`Element#appendChild()`](https://developer.mozilla.org/en-US/docs/Web/API/Node/appendChild)
  2275. or use `up.element.affix()` to create an attached element.
  2276. \#\#\# Examples
  2277. To create an element with a given tag name:
  2278. element = up.element.createFromSelector('span')
  2279. // element is <span></span>
  2280. To create an element with a given class:
  2281. element = up.element.createFromSelector('.klass')
  2282. // element is <div class="klass"></div>
  2283. To create an element with a given ID:
  2284. element = up.element.createFromSelector('#foo')
  2285. // element is <div id="foo"></div>
  2286. To create an element with a given boolean attribute:
  2287. element = up.element.createFromSelector('[attr]')
  2288. // element is <div attr></div>
  2289. To create an element with a given attribute value:
  2290. element = up.element.createFromSelector('[attr="value"]')
  2291. // element is <div attr="value"></div>
  2292. You may also pass an object of attribute names/values as a second argument:
  2293. element = up.element.createFromSelector('div', { attr: 'value' })
  2294. // element is <div attr="value"></div>
  2295. You may set the element's inner text by passing a `{ text }` option:
  2296. element = up.element.createFromSelector('div', { text: 'inner text' })
  2297. // element is <div>inner text</div>
  2298. You may set inline styles by passing an object of CSS properties as a second argument:
  2299. element = up.element.createFromSelector('div', { style: { color: 'red' }})
  2300. // element is <div style="color: red"></div>
  2301. @function up.element.createFromSelector
  2302. @param {string} selector
  2303. The CSS selector from which to create an element.
  2304. @param {Object} [attrs]
  2305. An object of attributes to set on the created element.
  2306. @param {Object} [attrs.text]
  2307. The [text content](https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent) of the created element.
  2308. @param {Object} [attrs.style]
  2309. An object of CSS properties that will be set as the inline style
  2310. of the created element.
  2311. @return {Element}
  2312. The created element.
  2313. @experimental
  2314. */
  2315. createFromSelector = function(selector, attrs) {
  2316. var attrValues, classValue, depthElement, depthSelector, depths, i, j, klass, len, len1, previousElement, ref, rootElement, selectorWithoutAttrValues, styleValue, tagName, textValue;
  2317. attrValues = [];
  2318. selectorWithoutAttrValues = selector.replace(/\[([\w-]+)(?:=(["'])?([^"'\]]*?)\2)?\]/g, function(_match, attrName, _quote, attrValue) {
  2319. attrValues.push(attrValue || '');
  2320. return "[" + attrName + "]";
  2321. });
  2322. depths = selectorWithoutAttrValues.split(/[ >]+/);
  2323. rootElement = void 0;
  2324. depthElement = void 0;
  2325. previousElement = void 0;
  2326. for (i = 0, len = depths.length; i < len; i++) {
  2327. depthSelector = depths[i];
  2328. tagName = void 0;
  2329. depthSelector = depthSelector.replace(/^[\w-]+/, function(match) {
  2330. tagName = match;
  2331. return '';
  2332. });
  2333. depthElement = document.createElement(tagName || 'div');
  2334. rootElement || (rootElement = depthElement);
  2335. depthSelector = depthSelector.replace(/\#([\w-]+)/, function(_match, id) {
  2336. depthElement.id = id;
  2337. return '';
  2338. });
  2339. depthSelector = depthSelector.replace(/\.([\w-]+)/g, function(_match, className) {
  2340. depthElement.classList.add(className);
  2341. return '';
  2342. });
  2343. if (attrValues.length) {
  2344. depthSelector = depthSelector.replace(/\[([\w-]+)\]/g, function(_match, attrName) {
  2345. depthElement.setAttribute(attrName, attrValues.shift());
  2346. return '';
  2347. });
  2348. }
  2349. if (depthSelector !== '') {
  2350. throw new Error('Cannot parse selector: ' + selector);
  2351. }
  2352. if (previousElement != null) {
  2353. previousElement.appendChild(depthElement);
  2354. }
  2355. previousElement = depthElement;
  2356. }
  2357. if (attrs) {
  2358. if (classValue = u.pluckKey(attrs, 'class')) {
  2359. ref = u.wrapList(classValue);
  2360. for (j = 0, len1 = ref.length; j < len1; j++) {
  2361. klass = ref[j];
  2362. rootElement.classList.add(klass);
  2363. }
  2364. }
  2365. if (styleValue = u.pluckKey(attrs, 'style')) {
  2366. setInlineStyle(rootElement, styleValue);
  2367. }
  2368. if (textValue = u.pluckKey(attrs, 'text')) {
  2369. rootElement.innerText = textValue;
  2370. }
  2371. setAttrs(rootElement, attrs);
  2372. }
  2373. return rootElement;
  2374. };
  2375. /***
  2376. Creates an element matching the given CSS selector and attaches it to the given parent element.
  2377. To create a detached element from a selector,
  2378. see `up.element.createFromSelector()`.
  2379. \#\#\# Example
  2380. element = up.element.affix(document.body, '.klass')
  2381. element.parentElement // returns document.body
  2382. element.className // returns 'klass'
  2383. @function up.element.affix
  2384. @param {Element} parent
  2385. The parent to which to attach the created element.
  2386. @param {string} selector
  2387. The CSS selector from which to create an element.
  2388. @param {Object} attrs
  2389. An object of attributes to set on the created element.
  2390. @param {Object} attrs.text
  2391. The [text content](https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent) of the created element.
  2392. @param {Object} attrs.style
  2393. An object of CSS properties that will be set as the inline style
  2394. of the created element.
  2395. @return {Element}
  2396. The created element.
  2397. @experimental
  2398. */
  2399. affix = function(parent, selector, attributes) {
  2400. var element;
  2401. element = createFromSelector(selector, attributes);
  2402. parent.appendChild(element);
  2403. return element;
  2404. };
  2405. /***
  2406. Returns a CSS selector that matches the given element as good as possible.
  2407. To build the selector, the following element properties are used in decreasing
  2408. order of priority:
  2409. - The element's `[up-id]` attribute
  2410. - The element's `[id]` attribute
  2411. - The element's `[name]` attribute
  2412. - The element's `[class]` names
  2413. - The element's [`[aria-label]`](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_aria-label_attribute) attribute
  2414. - The element's tag name
  2415. \#\#\# Example
  2416. element = document.createElement('span')
  2417. element.className = 'klass'
  2418. selector = up.element.toSelector(element) // returns '.klass'
  2419. @function up.element.toSelector
  2420. @param {string|Element|jQuery}
  2421. The element for which to create a selector.
  2422. @experimental
  2423. */
  2424. toSelector = function(element) {
  2425. var ariaLabel, classes, i, id, klass, len, name, selector, upId;
  2426. if (u.isString(element)) {
  2427. return element;
  2428. }
  2429. element = getOne(element);
  2430. selector = void 0;
  2431. if (isSingleton(element)) {
  2432. selector = elementTagName(element);
  2433. } else if (upId = element.getAttribute("up-id")) {
  2434. selector = attributeSelector('up-id', upId);
  2435. } else if (id = element.getAttribute("id")) {
  2436. if (id.match(/^[a-z0-9\-_]+$/i)) {
  2437. selector = "#" + id;
  2438. } else {
  2439. selector = attributeSelector('id', id);
  2440. }
  2441. } else if (name = element.getAttribute("name")) {
  2442. selector = elementTagName(element) + attributeSelector('name', name);
  2443. } else if (classes = u.presence(nonUpClasses(element))) {
  2444. selector = '';
  2445. for (i = 0, len = classes.length; i < len; i++) {
  2446. klass = classes[i];
  2447. selector += "." + klass;
  2448. }
  2449. } else if (ariaLabel = element.getAttribute("aria-label")) {
  2450. selector = attributeSelector('aria-label', ariaLabel);
  2451. } else {
  2452. selector = elementTagName(element);
  2453. }
  2454. return selector;
  2455. };
  2456. /***
  2457. Sets an unique identifier for this element.
  2458. This identifier is used by `up.element.toSelector()`
  2459. to create a CSS selector that matches this element precisely.
  2460. If the element already has other attributes that make a good identifier,
  2461. like a `[id]`, `[class]` or `[aria-label]`, it is not necessary to
  2462. set `[up-id]`.
  2463. \#\#\# Example
  2464. Take this element:
  2465. <a href="/">Homepage</a>
  2466. Unpoly cannot generate a good CSS selector for this element:
  2467. up.element.toSelector(element)
  2468. // returns 'a'
  2469. We can improve this by assigning an `[up-id]`:
  2470. <a href="/" up-id="link-to-home">Open user 4</a>
  2471. The attribute value is used to create a better selector:
  2472. up.element.toSelector(element)
  2473. // returns '[up-id="link-to-home"]'
  2474. @selector [up-id]
  2475. @param {string} up-id
  2476. A string that uniquely identifies this element.
  2477. @stable
  2478. */
  2479. /***
  2480. @function up.element.isSingleton
  2481. @internal
  2482. */
  2483. isSingleton = function(element) {
  2484. return matches(element, 'html, body, head, title');
  2485. };
  2486. elementTagName = function(element) {
  2487. return element.tagName.toLowerCase();
  2488. };
  2489. /***
  2490. @function up.element.attributeSelector
  2491. @internal
  2492. */
  2493. attributeSelector = function(attribute, value) {
  2494. value = value.replace(/"/g, '\\"');
  2495. return "[" + attribute + "=\"" + value + "\"]";
  2496. };
  2497. nonUpClasses = function(element) {
  2498. var classString, classes;
  2499. classString = element.className;
  2500. classes = u.splitValues(classString);
  2501. return u.reject(classes, function(klass) {
  2502. return klass.match(/^up-/);
  2503. });
  2504. };
  2505. /***
  2506. @function up.element.createDocumentFromHtml
  2507. @internal
  2508. */
  2509. createDocumentFromHtml = function(html) {
  2510. var parser;
  2511. parser = new DOMParser();
  2512. return parser.parseFromString(html, 'text/html');
  2513. };
  2514. /***
  2515. Creates an element from the given HTML fragment.
  2516. \#\#\# Example
  2517. element = up.element.createFromHtml('<div class="foo"><span>text</span></div>')
  2518. element.className // returns 'foo'
  2519. element.children[0] // returns <span> element
  2520. element.children[0].textContent // returns 'text'
  2521. @function up.element.createFromHtml
  2522. @experimental
  2523. */
  2524. createFromHtml = function(html) {
  2525. var doc;
  2526. doc = createDocumentFromHtml(html);
  2527. return doc.body.children[0];
  2528. };
  2529. /***
  2530. @function up.element.root
  2531. @internal
  2532. */
  2533. getRoot = function() {
  2534. return document.documentElement;
  2535. };
  2536. /***
  2537. Forces the browser to paint the given element now.
  2538. @function up.element.paint
  2539. @internal
  2540. */
  2541. paint = function(element) {
  2542. return element.offsetHeight;
  2543. };
  2544. /***
  2545. @function up.element.concludeCssTransition
  2546. @internal
  2547. */
  2548. concludeCssTransition = function(element) {
  2549. var undo;
  2550. undo = setTemporaryStyle(element, {
  2551. transition: 'none'
  2552. });
  2553. paint(element);
  2554. return undo;
  2555. };
  2556. /***
  2557. Returns whether the given element has a CSS transition set.
  2558. @function up.element.hasCssTransition
  2559. @return {boolean}
  2560. @internal
  2561. */
  2562. hasCssTransition = function(elementOrStyleHash) {
  2563. var duration, noTransition, prop, styleHash;
  2564. if (u.isOptions(elementOrStyleHash)) {
  2565. styleHash = elementOrStyleHash;
  2566. } else {
  2567. styleHash = computedStyle(elementOrStyleHash);
  2568. }
  2569. prop = styleHash.transitionProperty;
  2570. duration = styleHash.transitionDuration;
  2571. noTransition = prop === 'none' || (prop === 'all' && duration === 0);
  2572. return !noTransition;
  2573. };
  2574. /***
  2575. @function up.element.fixedToAbsolute
  2576. @internal
  2577. */
  2578. fixedToAbsolute = function(element) {
  2579. var elementRectAsFixed, offsetParentRect;
  2580. elementRectAsFixed = element.getBoundingClientRect();
  2581. element.style.position = 'absolute';
  2582. offsetParentRect = element.offsetParent.getBoundingClientRect();
  2583. return setInlineStyle(element, {
  2584. left: elementRectAsFixed.left - computedStyleNumber(element, 'margin-left') - offsetParentRect.left,
  2585. top: elementRectAsFixed.top - computedStyleNumber(element, 'margin-top') - offsetParentRect.top,
  2586. right: '',
  2587. bottom: ''
  2588. });
  2589. };
  2590. /***
  2591. On the given element, set attributes that are still missing.
  2592. @function up.element.setMissingAttrs
  2593. @internal
  2594. */
  2595. setMissingAttrs = function(element, attrs) {
  2596. var key, results, value;
  2597. results = [];
  2598. for (key in attrs) {
  2599. value = attrs[key];
  2600. if (u.isMissing(element.getAttribute(key))) {
  2601. results.push(element.setAttribute(key, value));
  2602. } else {
  2603. results.push(void 0);
  2604. }
  2605. }
  2606. return results;
  2607. };
  2608. /***
  2609. @function up.element.unwrap
  2610. @internal
  2611. */
  2612. unwrap = function(wrapper) {
  2613. var parent, wrappedNodes;
  2614. parent = wrapper.parentNode;
  2615. wrappedNodes = u.toArray(wrapper.childNodes);
  2616. u.each(wrappedNodes, function(wrappedNode) {
  2617. return parent.insertBefore(wrappedNode, wrapper);
  2618. });
  2619. return parent.removeChild(wrapper);
  2620. };
  2621. /***
  2622. Returns the value of the given attribute on the given element, cast as a boolean value.
  2623. If the attribute value cannot be cast to `true` or `false`, `undefined` is returned.
  2624. \#\#\# Casting rules
  2625. This function deviates from the
  2626. [HTML Standard for boolean attributes](https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#boolean-attributes)
  2627. in order to allow `undefined` values. When an attribute is missing, Unpoly considers the value to be `undefined`
  2628. (where the standard would assume `false`).
  2629. Unpoly also allows `"true"` and `"false"` as attribute values.
  2630. The table below shows return values for `up.element.booleanAttr(element, 'foo')` given different elements:
  2631. | Element | Return value |
  2632. |---------------------|--------------|
  2633. | `<div foo>` | `true` |
  2634. | `<div foo="foo">` | `true` |
  2635. | `<div foo="true">` | `true` |
  2636. | `<div foo="">` | `true` |
  2637. | `<div foo="false">` | `false` |
  2638. | `<div>` | `undefined` |
  2639. | `<div foo="bar">` | `undefined` |
  2640. @function up.element.booleanAttr
  2641. @param {Element} element
  2642. The element from which to retrieve the attribute value.
  2643. @param {String} attribute
  2644. The attribute name.
  2645. @return {boolean|undefined}
  2646. The cast attribute value.
  2647. @experimental
  2648. */
  2649. booleanAttr = function(element, attribute, pass) {
  2650. var value;
  2651. value = element.getAttribute(attribute);
  2652. switch (value) {
  2653. case 'false':
  2654. return false;
  2655. case 'true':
  2656. case '':
  2657. case attribute:
  2658. return true;
  2659. default:
  2660. if (pass) {
  2661. return value;
  2662. }
  2663. }
  2664. };
  2665. /***
  2666. Returns the given attribute value cast as boolean.
  2667. If the attribute value cannot be cast, returns the attribute value unchanged.
  2668. @internal
  2669. */
  2670. booleanOrStringAttr = function(element, attribute) {
  2671. return booleanAttr(element, attribute, true);
  2672. };
  2673. /***
  2674. Returns the value of the given attribute on the given element, cast to a number.
  2675. If the attribute value cannot be cast to a number, `undefined` is returned.
  2676. @function up.element.numberAttr
  2677. @param {Element} element
  2678. The element from which to retrieve the attribute value.
  2679. @param {String} attribute
  2680. The attribute name.
  2681. @return {number|undefined}
  2682. The cast attribute value.
  2683. @experimental
  2684. */
  2685. numberAttr = function(element, attribute) {
  2686. var value;
  2687. value = element.getAttribute(attribute);
  2688. if (value != null ? value.match(/^[\d\.]+$/) : void 0) {
  2689. return parseFloat(value);
  2690. }
  2691. };
  2692. /***
  2693. Reads the given attribute from the element, parsed as [JSON](https://www.json.org/).
  2694. Returns `undefined` if the attribute value is [blank](/up.util.isBlank).
  2695. Throws a `SyntaxError` if the attribute value is an invalid JSON string.
  2696. @function up.element.jsonAttr
  2697. @param {Element} element
  2698. The element from which to retrieve the attribute value.
  2699. @param {String} attribute
  2700. The attribute name.
  2701. @return {Object|undefined}
  2702. The cast attribute value.
  2703. @experimental
  2704. */
  2705. jsonAttr = function(element, attribute) {
  2706. var json, ref;
  2707. if (json = typeof element.getAttribute === "function" ? (ref = element.getAttribute(attribute)) != null ? ref.trim() : void 0 : void 0) {
  2708. return JSON.parse(json);
  2709. }
  2710. };
  2711. /***
  2712. Temporarily sets the inline CSS styles on the given element.
  2713. Returns a function that restores the original inline styles when called.
  2714. \#\#\# Example
  2715. element = document.querySelector('div')
  2716. unhide = up.element.setTemporaryStyle(element, { 'visibility': 'hidden' })
  2717. // do things while element is invisible
  2718. unhide()
  2719. // element is visible again
  2720. @function up.element.setTemporaryStyle
  2721. @param {Element} element
  2722. The element to style.
  2723. @param {Object} styles
  2724. An object of CSS property names and values.
  2725. @return {Function()}
  2726. A function that restores the original inline styles when called.
  2727. @internal
  2728. */
  2729. setTemporaryStyle = function(element, newStyles, block) {
  2730. var oldStyles;
  2731. oldStyles = inlineStyle(element, Object.keys(newStyles));
  2732. setInlineStyle(element, newStyles);
  2733. return function() {
  2734. return setInlineStyle(element, oldStyles);
  2735. };
  2736. };
  2737. /***
  2738. Receives [computed CSS styles](https://developer.mozilla.org/en-US/docs/Web/API/Window/getComputedStyle)
  2739. for the given element.
  2740. \#\#\# Examples
  2741. When requesting a single CSS property, its value will be returned as a string:
  2742. value = up.element.style(element, 'font-size')
  2743. // value is '16px'
  2744. When requesting multiple CSS properties, the function returns an object of property names and values:
  2745. value = up.element.style(element, ['font-size', 'margin-top'])
  2746. // value is { 'font-size': '16px', 'margin-top': '10px' }
  2747. @function up.element.style
  2748. @param {Element} element
  2749. @param {String|Array} propOrProps
  2750. One or more CSS property names in kebab-case or camelCase.
  2751. @return {string|object}
  2752. @experimental
  2753. */
  2754. computedStyle = function(element, props) {
  2755. var style;
  2756. style = window.getComputedStyle(element);
  2757. return extractFromStyleObject(style, props);
  2758. };
  2759. /***
  2760. Receives a [computed CSS property value](https://developer.mozilla.org/en-US/docs/Web/API/Window/getComputedStyle)
  2761. for the given element, casted as a number.
  2762. The value is casted by removing the property's [unit](https://www.w3schools.com/cssref/css_units.asp) (which is usually `px` for computed properties).
  2763. The result is then parsed as a floating point number.
  2764. Returns `undefined` if the property value is missing, or if it cannot
  2765. be parsed as a number.
  2766. \#\#\# Examples
  2767. When requesting a single CSS property, its value will be returned as a string:
  2768. value = up.element.style(element, 'font-size')
  2769. // value is '16px'
  2770. value = up.element.styleNumber(element, 'font-size')
  2771. // value is 16
  2772. @function up.element.styleNumber
  2773. @param {Element} element
  2774. @param {String} prop
  2775. A single property name in kebab-case or camelCase.
  2776. @return {number|undefined}
  2777. @experimental
  2778. */
  2779. computedStyleNumber = function(element, prop) {
  2780. var rawValue;
  2781. rawValue = computedStyle(element, prop);
  2782. if (u.isGiven(rawValue)) {
  2783. return parseFloat(rawValue);
  2784. } else {
  2785. return void 0;
  2786. }
  2787. };
  2788. /***
  2789. Gets the given inline style(s) from the given element's `[style]` attribute.
  2790. @function up.element.inlineStyle
  2791. @param {Element} element
  2792. @param {String|Array} propOrProps
  2793. One or more CSS property names in kebab-case or camelCase.
  2794. @return {string|object}
  2795. @internal
  2796. */
  2797. inlineStyle = function(element, props) {
  2798. var style;
  2799. style = element.style;
  2800. return extractFromStyleObject(style, props);
  2801. };
  2802. extractFromStyleObject = function(style, keyOrKeys) {
  2803. if (u.isString(keyOrKeys)) {
  2804. return style[keyOrKeys];
  2805. } else {
  2806. return u.only.apply(u, [style].concat(slice.call(keyOrKeys)));
  2807. }
  2808. };
  2809. /***
  2810. Sets the given CSS properties as inline styles on the given element.
  2811. @function up.element.setStyle
  2812. @param {Element} element
  2813. @param {Object} props
  2814. One or more CSS properties with kebab-case keys or camelCase keys.
  2815. @return {string|object}
  2816. @experimental
  2817. */
  2818. setInlineStyle = function(element, props) {
  2819. var key, results, style, value;
  2820. style = element.style;
  2821. results = [];
  2822. for (key in props) {
  2823. value = props[key];
  2824. value = normalizeStyleValueForWrite(key, value);
  2825. results.push(style[key] = value);
  2826. }
  2827. return results;
  2828. };
  2829. normalizeStyleValueForWrite = function(key, value) {
  2830. if (u.isMissing(value)) {
  2831. value = '';
  2832. } else if (CSS_LENGTH_PROPS.has(key.toLowerCase().replace(/-/, ''))) {
  2833. value = cssLength(value);
  2834. }
  2835. return value;
  2836. };
  2837. CSS_LENGTH_PROPS = u.arrayToSet(['top', 'right', 'bottom', 'left', 'padding', 'paddingtop', 'paddingright', 'paddingbottom', 'paddingleft', 'margin', 'margintop', 'marginright', 'marginbottom', 'marginleft', 'borderwidth', 'bordertopwidth', 'borderrightwidth', 'borderbottomwidth', 'borderleftwidth', 'width', 'height', 'maxwidth', 'maxheight', 'minwidth', 'minheight']);
  2838. /***
  2839. Converts the given value to a CSS length value, adding a `px` unit if required.
  2840. @function cssLength
  2841. @internal
  2842. */
  2843. cssLength = function(obj) {
  2844. if (u.isNumber(obj) || (u.isString(obj) && /^\d+$/.test(obj))) {
  2845. return obj.toString() + "px";
  2846. } else {
  2847. return obj;
  2848. }
  2849. };
  2850. /***
  2851. Resolves the given CSS selector (which might contain `&` references)
  2852. to a full CSS selector without ampersands.
  2853. If passed an `Element` or `jQuery` element, returns a CSS selector string
  2854. for that element.
  2855. @function up.element.resolveSelector
  2856. @param {string|Element|jQuery} selectorOrElement
  2857. @param {string|Element|jQuery} origin
  2858. The element that this selector resolution is relative to.
  2859. That element's selector will be substituted for `&` ([like in Sass](https://sass-lang.com/documentation/file.SASS_REFERENCE.html#parent-selector)).
  2860. @return {string}
  2861. @internal
  2862. */
  2863. resolveSelector = function(selectorOrElement, origin) {
  2864. var originSelector, selector;
  2865. if (u.isString(selectorOrElement)) {
  2866. selector = selectorOrElement;
  2867. if (u.contains(selector, '&')) {
  2868. if (u.isPresent(origin)) {
  2869. originSelector = toSelector(origin);
  2870. selector = selector.replace(/\&/, originSelector);
  2871. } else {
  2872. up.fail("Found origin reference (%s) in selector %s, but no origin was given", '&', selector);
  2873. }
  2874. }
  2875. } else {
  2876. selector = toSelector(selectorOrElement);
  2877. }
  2878. return selector;
  2879. };
  2880. /***
  2881. Returns whether the given element is currently visible.
  2882. An element is considered visible if it consumes space in the document.
  2883. Elements with `{ visibility: hidden }` or `{ opacity: 0 }` are considered visible, since they still consume space in the layout.
  2884. Elements not attached to the DOM are considered hidden.
  2885. @function up.element.isVisible
  2886. @param {Element} element
  2887. The element to check.
  2888. @experimental
  2889. */
  2890. isVisible = function(element) {
  2891. return !!(element.offsetWidth || element.offsetHeight || element.getClientRects().length);
  2892. };
  2893. return {
  2894. first: first,
  2895. all: all,
  2896. subtree: subtree,
  2897. closest: closest,
  2898. matches: matches,
  2899. ancestor: ancestor,
  2900. get: getOne,
  2901. list: getList,
  2902. remove: remove,
  2903. toggle: toggle,
  2904. toggleClass: toggleClass,
  2905. hide: hide,
  2906. show: show,
  2907. metaContent: metaContent,
  2908. replace: replace,
  2909. insertBefore: insertBefore,
  2910. createFromSelector: createFromSelector,
  2911. setAttrs: setAttrs,
  2912. affix: affix,
  2913. toSelector: toSelector,
  2914. isSingleton: isSingleton,
  2915. attributeSelector: attributeSelector,
  2916. createDocumentFromHtml: createDocumentFromHtml,
  2917. createFromHtml: createFromHtml,
  2918. root: getRoot,
  2919. paint: paint,
  2920. concludeCssTransition: concludeCssTransition,
  2921. hasCssTransition: hasCssTransition,
  2922. fixedToAbsolute: fixedToAbsolute,
  2923. setMissingAttrs: setMissingAttrs,
  2924. unwrap: unwrap,
  2925. booleanAttr: booleanAttr,
  2926. numberAttr: numberAttr,
  2927. jsonAttr: jsonAttr,
  2928. booleanOrStringAttr: booleanOrStringAttr,
  2929. setTemporaryStyle: setTemporaryStyle,
  2930. style: computedStyle,
  2931. styleNumber: computedStyleNumber,
  2932. inlineStyle: inlineStyle,
  2933. setStyle: setInlineStyle,
  2934. resolveSelector: resolveSelector,
  2935. none: function() {
  2936. return NONE;
  2937. },
  2938. isVisible: isVisible
  2939. };
  2940. })();
  2941. }).call(this);
  2942. (function() {
  2943. var e;
  2944. e = up.element;
  2945. up.BodyShifter = (function() {
  2946. function BodyShifter() {
  2947. this.unshiftFns = [];
  2948. }
  2949. BodyShifter.prototype.shift = function() {
  2950. var anchor, body, bodyRightPadding, bodyRightShift, elementRight, elementRightShift, i, len, overflowElement, ref, results, scrollbarWidth;
  2951. if (!up.viewport.rootHasVerticalScrollbar()) {
  2952. return;
  2953. }
  2954. body = document.body;
  2955. overflowElement = up.viewport.rootOverflowElement();
  2956. scrollbarWidth = up.viewport.scrollbarWidth();
  2957. bodyRightPadding = e.styleNumber(body, 'paddingRight');
  2958. bodyRightShift = scrollbarWidth + bodyRightPadding;
  2959. this.unshiftFns.push(e.setTemporaryStyle(body, {
  2960. paddingRight: bodyRightShift
  2961. }));
  2962. this.unshiftFns.push(e.setTemporaryStyle(overflowElement, {
  2963. overflowY: 'hidden'
  2964. }));
  2965. ref = up.viewport.anchoredRight();
  2966. results = [];
  2967. for (i = 0, len = ref.length; i < len; i++) {
  2968. anchor = ref[i];
  2969. elementRight = e.styleNumber(anchor, 'right');
  2970. elementRightShift = scrollbarWidth + elementRight;
  2971. results.push(this.unshiftFns.push(e.setTemporaryStyle(anchor, {
  2972. right: elementRightShift
  2973. })));
  2974. }
  2975. return results;
  2976. };
  2977. BodyShifter.prototype.unshift = function() {
  2978. var results, unshiftFn;
  2979. results = [];
  2980. while (unshiftFn = this.unshiftFns.pop()) {
  2981. results.push(unshiftFn());
  2982. }
  2983. return results;
  2984. };
  2985. return BodyShifter;
  2986. })();
  2987. }).call(this);
  2988. (function() {
  2989. var u,
  2990. bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
  2991. slice = [].slice;
  2992. u = up.util;
  2993. /***
  2994. @class up.Cache
  2995. @internal
  2996. */
  2997. up.Cache = (function() {
  2998. /***
  2999. @constructor
  3000. @param {number|Function(): number} [config.size]
  3001. Maximum number of cache entries.
  3002. Set to `undefined` to not limit the cache size.
  3003. @param {number|Function(): number} [config.expiry]
  3004. The number of milliseconds after which a cache entry
  3005. will be discarded.
  3006. @param {string} [config.logPrefix]
  3007. A prefix for log entries printed by this cache object.
  3008. @param {Function(entry): string} [config.key]
  3009. A function that takes an argument and returns a string key
  3010. for storage. If omitted, `toString()` is called on the argument.
  3011. @param {Function(entry): boolean} [config.cachable]
  3012. A function that takes a potential cache entry and returns whether
  3013. this entry can be stored in the hash. If omitted, all entries are considered
  3014. cachable.
  3015. */
  3016. function Cache(config) {
  3017. this.config = config != null ? config : {};
  3018. this.get = bind(this.get, this);
  3019. this.isFresh = bind(this.isFresh, this);
  3020. this.remove = bind(this.remove, this);
  3021. this.set = bind(this.set, this);
  3022. this.timestamp = bind(this.timestamp, this);
  3023. this.alias = bind(this.alias, this);
  3024. this.makeRoomForAnotherKey = bind(this.makeRoomForAnotherKey, this);
  3025. this.keys = bind(this.keys, this);
  3026. this.log = bind(this.log, this);
  3027. this.clear = bind(this.clear, this);
  3028. this.isCachable = bind(this.isCachable, this);
  3029. this.isEnabled = bind(this.isEnabled, this);
  3030. this.normalizeStoreKey = bind(this.normalizeStoreKey, this);
  3031. this.expiryMillis = bind(this.expiryMillis, this);
  3032. this.maxKeys = bind(this.maxKeys, this);
  3033. this.store = this.config.store || new up.store.Memory();
  3034. }
  3035. Cache.prototype.maxKeys = function() {
  3036. return u.evalOption(this.config.size);
  3037. };
  3038. Cache.prototype.expiryMillis = function() {
  3039. return u.evalOption(this.config.expiry);
  3040. };
  3041. Cache.prototype.normalizeStoreKey = function(key) {
  3042. if (this.config.key) {
  3043. return this.config.key(key);
  3044. } else {
  3045. return key.toString();
  3046. }
  3047. };
  3048. Cache.prototype.isEnabled = function() {
  3049. return this.maxKeys() !== 0 && this.expiryMillis() !== 0;
  3050. };
  3051. Cache.prototype.isCachable = function(key) {
  3052. if (this.config.cachable) {
  3053. return this.config.cachable(key);
  3054. } else {
  3055. return true;
  3056. }
  3057. };
  3058. Cache.prototype.clear = function() {
  3059. return this.store.clear();
  3060. };
  3061. Cache.prototype.log = function() {
  3062. var args;
  3063. args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
  3064. if (this.config.logPrefix) {
  3065. args[0] = "[" + this.config.logPrefix + "] " + args[0];
  3066. return up.puts.apply(up, args);
  3067. }
  3068. };
  3069. Cache.prototype.keys = function() {
  3070. return this.store.keys();
  3071. };
  3072. Cache.prototype.makeRoomForAnotherKey = function() {
  3073. var max, oldestKey, oldestTimestamp, storeKeys;
  3074. storeKeys = u.copy(this.keys());
  3075. max = this.maxKeys();
  3076. if (max && storeKeys.length >= max) {
  3077. oldestKey = void 0;
  3078. oldestTimestamp = void 0;
  3079. u.each(storeKeys, (function(_this) {
  3080. return function(key) {
  3081. var entry, timestamp;
  3082. entry = _this.store.get(key);
  3083. timestamp = entry.timestamp;
  3084. if (!oldestTimestamp || oldestTimestamp > timestamp) {
  3085. oldestKey = key;
  3086. return oldestTimestamp = timestamp;
  3087. }
  3088. };
  3089. })(this));
  3090. if (oldestKey) {
  3091. return this.store.remove(oldestKey);
  3092. }
  3093. }
  3094. };
  3095. Cache.prototype.alias = function(oldKey, newKey) {
  3096. var value;
  3097. value = this.get(oldKey, {
  3098. silent: true
  3099. });
  3100. if (u.isDefined(value)) {
  3101. return this.set(newKey, value);
  3102. }
  3103. };
  3104. Cache.prototype.timestamp = function() {
  3105. return (new Date()).valueOf();
  3106. };
  3107. Cache.prototype.set = function(key, value) {
  3108. var storeKey, timestampedValue;
  3109. if (this.isEnabled() && this.isCachable(key)) {
  3110. this.makeRoomForAnotherKey();
  3111. storeKey = this.normalizeStoreKey(key);
  3112. this.log("Setting entry %o to %o", storeKey, value);
  3113. timestampedValue = {
  3114. timestamp: this.timestamp(),
  3115. value: value
  3116. };
  3117. return this.store.set(storeKey, timestampedValue);
  3118. }
  3119. };
  3120. Cache.prototype.remove = function(key) {
  3121. var storeKey;
  3122. if (this.isCachable(key)) {
  3123. storeKey = this.normalizeStoreKey(key);
  3124. return this.store.remove(storeKey);
  3125. }
  3126. };
  3127. Cache.prototype.isFresh = function(entry) {
  3128. var millis, timeSinceTouch;
  3129. millis = this.expiryMillis();
  3130. if (millis) {
  3131. timeSinceTouch = this.timestamp() - entry.timestamp;
  3132. return timeSinceTouch < millis;
  3133. } else {
  3134. return true;
  3135. }
  3136. };
  3137. Cache.prototype.get = function(key, options) {
  3138. var entry;
  3139. if (options == null) {
  3140. options = {};
  3141. }
  3142. if (this.isCachable(key) && (entry = this.store.get(this.normalizeStoreKey(key)))) {
  3143. if (this.isFresh(entry)) {
  3144. if (!options.silent) {
  3145. this.log("Cache hit for '%s'", key);
  3146. }
  3147. return entry.value;
  3148. } else {
  3149. if (!options.silent) {
  3150. this.log("Discarding stale cache entry for '%s'", key);
  3151. }
  3152. this.remove(key);
  3153. return void 0;
  3154. }
  3155. } else {
  3156. if (!options.silent) {
  3157. this.log("Cache miss for '%s'", key);
  3158. }
  3159. return void 0;
  3160. }
  3161. };
  3162. return Cache;
  3163. })();
  3164. }).call(this);
  3165. (function() {
  3166. var u,
  3167. slice = [].slice;
  3168. u = up.util;
  3169. up.Record = (function() {
  3170. Record.prototype.fields = function() {
  3171. throw 'Return an array of property names';
  3172. };
  3173. function Record(options) {
  3174. u.assign(this, this.attributes(options));
  3175. }
  3176. Record.prototype.attributes = function(source) {
  3177. if (source == null) {
  3178. source = this;
  3179. }
  3180. return u.only.apply(u, [source].concat(slice.call(this.fields())));
  3181. };
  3182. Record.prototype["" + u.copy.key] = function() {
  3183. return this.variant();
  3184. };
  3185. Record.prototype.variant = function(changes) {
  3186. var attributesWithChanges;
  3187. if (changes == null) {
  3188. changes = {};
  3189. }
  3190. attributesWithChanges = u.merge(this.attributes(), changes);
  3191. return new this.constructor(attributesWithChanges);
  3192. };
  3193. Record.prototype["" + u.isEqual.key] = function(other) {
  3194. return other && (this.constructor === other.constructor) && u.isEqual(this.attributes(), other.attributes());
  3195. };
  3196. return Record;
  3197. })();
  3198. }).call(this);
  3199. (function() {
  3200. var e, u,
  3201. bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
  3202. u = up.util;
  3203. e = up.element;
  3204. up.CompilePass = (function() {
  3205. function CompilePass(root, compilers, options) {
  3206. this.root = root;
  3207. this.compilers = compilers;
  3208. if (options == null) {
  3209. options = {};
  3210. }
  3211. this.isInSkippedSubtree = bind(this.isInSkippedSubtree, this);
  3212. this.skipSubtrees = options.skip;
  3213. if (!(this.skipSubtrees.length && this.root.querySelector('[up-keep]'))) {
  3214. this.skipSubtrees = void 0;
  3215. }
  3216. }
  3217. CompilePass.prototype.compile = function() {
  3218. return up.log.group("Compiling fragment %o", this.root, (function(_this) {
  3219. return function() {
  3220. var compiler, i, len, ref, results;
  3221. ref = _this.compilers;
  3222. results = [];
  3223. for (i = 0, len = ref.length; i < len; i++) {
  3224. compiler = ref[i];
  3225. results.push(_this.runCompiler(compiler));
  3226. }
  3227. return results;
  3228. };
  3229. })(this));
  3230. };
  3231. CompilePass.prototype.runCompiler = function(compiler) {
  3232. var matches;
  3233. matches = this.select(compiler.selector);
  3234. if (!matches.length) {
  3235. return;
  3236. }
  3237. return up.log.group((!compiler.isDefault ? "Compiling '%s' on %d element(s)" : void 0), compiler.selector, matches.length, (function(_this) {
  3238. return function() {
  3239. var i, j, keepValue, len, len1, match, results, value;
  3240. if (compiler.batch) {
  3241. _this.compileBatch(compiler, matches);
  3242. } else {
  3243. for (i = 0, len = matches.length; i < len; i++) {
  3244. match = matches[i];
  3245. _this.compileOneElement(compiler, match);
  3246. }
  3247. }
  3248. if (keepValue = compiler.keep) {
  3249. value = u.isString(keepValue) ? keepValue : '';
  3250. results = [];
  3251. for (j = 0, len1 = matches.length; j < len1; j++) {
  3252. match = matches[j];
  3253. results.push(match.setAttribute('up-keep', value));
  3254. }
  3255. return results;
  3256. }
  3257. };
  3258. })(this));
  3259. };
  3260. CompilePass.prototype.compileOneElement = function(compiler, element) {
  3261. var compileArgs, data, destructorOrDestructors, elementArg, result;
  3262. elementArg = compiler.jQuery ? up.browser.jQuery(element) : element;
  3263. compileArgs = [elementArg];
  3264. if (compiler.length !== 1) {
  3265. data = up.syntax.data(element);
  3266. compileArgs.push(data);
  3267. }
  3268. result = compiler.apply(element, compileArgs);
  3269. if (destructorOrDestructors = this.destructorPresence(result)) {
  3270. return up.destructor(element, destructorOrDestructors);
  3271. }
  3272. };
  3273. CompilePass.prototype.compileBatch = function(compiler, elements) {
  3274. var compileArgs, dataList, elementsArgs, result;
  3275. elementsArgs = compiler.jQuery ? up.browser.jQuery(elements) : elements;
  3276. compileArgs = [elementsArgs];
  3277. if (compiler.length !== 1) {
  3278. dataList = u.map(elements, up.syntax.data);
  3279. compileArgs.push(dataList);
  3280. }
  3281. result = compiler.apply(elements, compileArgs);
  3282. if (this.destructorPresence(result)) {
  3283. return up.fail('Compilers with { batch: true } cannot return destructors');
  3284. }
  3285. };
  3286. CompilePass.prototype.destructorPresence = function(result) {
  3287. if (u.isFunction(result) || u.isArray(result) && (u.every(result, u.isFunction))) {
  3288. return result;
  3289. }
  3290. };
  3291. CompilePass.prototype.select = function(selector) {
  3292. var matches;
  3293. if (u.isFunction(selector)) {
  3294. selector = selector();
  3295. }
  3296. matches = e.subtree(this.root, selector);
  3297. if (this.skipSubtrees) {
  3298. matches = u.reject(matches, this.isInSkippedSubtree);
  3299. }
  3300. return matches;
  3301. };
  3302. CompilePass.prototype.isInSkippedSubtree = function(element) {
  3303. var parent;
  3304. if (u.contains(this.skipSubtrees, element)) {
  3305. return true;
  3306. } else if (parent = element.parentElement) {
  3307. return this.isInSkippedSubtree(parent);
  3308. } else {
  3309. return false;
  3310. }
  3311. };
  3312. return CompilePass;
  3313. })();
  3314. }).call(this);
  3315. (function() {
  3316. var u;
  3317. u = up.util;
  3318. up.Config = (function() {
  3319. function Config(blueprint) {
  3320. this.blueprint = blueprint;
  3321. this.reset();
  3322. }
  3323. Config.prototype.reset = function() {
  3324. return u.assign(this, u.deepCopy(this.blueprint));
  3325. };
  3326. return Config;
  3327. })();
  3328. }).call(this);
  3329. (function() {
  3330. var e, u,
  3331. bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
  3332. u = up.util;
  3333. e = up.element;
  3334. up.CssTransition = (function() {
  3335. function CssTransition(element, lastFrameKebab, options) {
  3336. this.element = element;
  3337. this.lastFrameKebab = lastFrameKebab;
  3338. this.startMotion = bind(this.startMotion, this);
  3339. this.resumeOldTransition = bind(this.resumeOldTransition, this);
  3340. this.pauseOldTransition = bind(this.pauseOldTransition, this);
  3341. this.finish = bind(this.finish, this);
  3342. this.onTransitionEnd = bind(this.onTransitionEnd, this);
  3343. this.listenToTransitionEnd = bind(this.listenToTransitionEnd, this);
  3344. this.stopFallbackTimer = bind(this.stopFallbackTimer, this);
  3345. this.startFallbackTimer = bind(this.startFallbackTimer, this);
  3346. this.onFinishEvent = bind(this.onFinishEvent, this);
  3347. this.listenToFinishEvent = bind(this.listenToFinishEvent, this);
  3348. this.start = bind(this.start, this);
  3349. this.lastFrameKeysKebab = Object.keys(this.lastFrameKebab);
  3350. if (u.some(this.lastFrameKeysKebab, function(key) {
  3351. return key.match(/A-Z/);
  3352. })) {
  3353. up.fail('Animation keys must be kebab-case');
  3354. }
  3355. this.finishEvent = options.finishEvent;
  3356. this.duration = options.duration;
  3357. this.delay = options.delay;
  3358. this.totalDuration = this.delay + this.duration;
  3359. this.easing = options.easing;
  3360. this.finished = false;
  3361. }
  3362. CssTransition.prototype.start = function() {
  3363. if (this.lastFrameKeysKebab.length === 0) {
  3364. this.finished = true;
  3365. return Promise.resolve();
  3366. }
  3367. this.deferred = u.newDeferred();
  3368. this.pauseOldTransition();
  3369. this.startTime = new Date();
  3370. this.startFallbackTimer();
  3371. this.listenToFinishEvent();
  3372. this.listenToTransitionEnd();
  3373. this.startMotion();
  3374. return this.deferred.promise();
  3375. };
  3376. CssTransition.prototype.listenToFinishEvent = function() {
  3377. if (this.finishEvent) {
  3378. return this.stopListenToFinishEvent = this.element.addEventListener(this.finishEvent, this.onFinishEvent);
  3379. }
  3380. };
  3381. CssTransition.prototype.onFinishEvent = function(event) {
  3382. event.stopPropagation();
  3383. return this.finish();
  3384. };
  3385. CssTransition.prototype.startFallbackTimer = function() {
  3386. var timingTolerance;
  3387. timingTolerance = 100;
  3388. return this.fallbackTimer = u.timer(this.totalDuration + timingTolerance, (function(_this) {
  3389. return function() {
  3390. return _this.finish();
  3391. };
  3392. })(this));
  3393. };
  3394. CssTransition.prototype.stopFallbackTimer = function() {
  3395. return clearTimeout(this.fallbackTimer);
  3396. };
  3397. CssTransition.prototype.listenToTransitionEnd = function() {
  3398. return this.stopListenToTransitionEnd = up.on(this.element, 'transitionend', this.onTransitionEnd);
  3399. };
  3400. CssTransition.prototype.onTransitionEnd = function(event) {
  3401. var completedPropertyKebab, elapsed;
  3402. if (event.target !== this.element) {
  3403. return;
  3404. }
  3405. elapsed = new Date() - this.startTime;
  3406. if (!(elapsed > 0.25 * this.totalDuration)) {
  3407. return;
  3408. }
  3409. completedPropertyKebab = event.propertyName;
  3410. if (!u.contains(this.lastFrameKeysKebab, completedPropertyKebab)) {
  3411. return;
  3412. }
  3413. return this.finish();
  3414. };
  3415. CssTransition.prototype.finish = function() {
  3416. if (this.finished) {
  3417. return;
  3418. }
  3419. this.finished = true;
  3420. this.stopFallbackTimer();
  3421. if (typeof this.stopListenToFinishEvent === "function") {
  3422. this.stopListenToFinishEvent();
  3423. }
  3424. if (typeof this.stopListenToTransitionEnd === "function") {
  3425. this.stopListenToTransitionEnd();
  3426. }
  3427. e.concludeCssTransition(this.element);
  3428. this.resumeOldTransition();
  3429. return this.deferred.resolve();
  3430. };
  3431. CssTransition.prototype.pauseOldTransition = function() {
  3432. var oldTransition, oldTransitionFrameKebab, oldTransitionProperties;
  3433. oldTransition = e.style(this.element, ['transitionProperty', 'transitionDuration', 'transitionDelay', 'transitionTimingFunction']);
  3434. if (e.hasCssTransition(oldTransition)) {
  3435. if (oldTransition.transitionProperty !== 'all') {
  3436. oldTransitionProperties = oldTransition.transitionProperty.split(/\s*,\s*/);
  3437. oldTransitionFrameKebab = e.style(this.element, oldTransitionProperties);
  3438. this.setOldTransitionTargetFrame = e.setTemporaryStyle(this.element, oldTransitionFrameKebab);
  3439. }
  3440. return this.setOldTransition = e.concludeCssTransition(this.element);
  3441. }
  3442. };
  3443. CssTransition.prototype.resumeOldTransition = function() {
  3444. if (typeof this.setOldTransitionTargetFrame === "function") {
  3445. this.setOldTransitionTargetFrame();
  3446. }
  3447. return typeof this.setOldTransition === "function" ? this.setOldTransition() : void 0;
  3448. };
  3449. CssTransition.prototype.startMotion = function() {
  3450. e.setStyle(this.element, {
  3451. transitionProperty: Object.keys(this.lastFrameKebab).join(', '),
  3452. transitionDuration: this.duration + "ms",
  3453. transitionDelay: this.delay + "ms",
  3454. transitionTimingFunction: this.easing
  3455. });
  3456. return e.setStyle(this.element, this.lastFrameKebab);
  3457. };
  3458. return CssTransition;
  3459. })();
  3460. }).call(this);
  3461. (function() {
  3462. var u,
  3463. bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
  3464. slice = [].slice;
  3465. u = up.util;
  3466. /***
  3467. A linear task queue whose (2..n)th tasks can be changed at any time.
  3468. @function up.DivertibleChain
  3469. @internal
  3470. */
  3471. up.DivertibleChain = (function() {
  3472. function DivertibleChain() {
  3473. this.asap = bind(this.asap, this);
  3474. this.poke = bind(this.poke, this);
  3475. this.allTasks = bind(this.allTasks, this);
  3476. this.promise = bind(this.promise, this);
  3477. this.reset = bind(this.reset, this);
  3478. this.reset();
  3479. }
  3480. DivertibleChain.prototype.reset = function() {
  3481. this.queue = [];
  3482. return this.currentTask = void 0;
  3483. };
  3484. DivertibleChain.prototype.promise = function() {
  3485. var lastTask;
  3486. lastTask = u.last(this.allTasks());
  3487. return (lastTask != null ? lastTask.promise : void 0) || Promise.resolve();
  3488. };
  3489. DivertibleChain.prototype.allTasks = function() {
  3490. var tasks;
  3491. tasks = [];
  3492. if (this.currentTask) {
  3493. tasks.push(this.currentTask);
  3494. }
  3495. tasks = tasks.concat(this.queue);
  3496. return tasks;
  3497. };
  3498. DivertibleChain.prototype.poke = function() {
  3499. var promise;
  3500. if (!this.currentTask) {
  3501. if (this.currentTask = this.queue.shift()) {
  3502. promise = this.currentTask();
  3503. return u.always(promise, (function(_this) {
  3504. return function() {
  3505. _this.currentTask = void 0;
  3506. return _this.poke();
  3507. };
  3508. })(this));
  3509. }
  3510. }
  3511. };
  3512. DivertibleChain.prototype.asap = function() {
  3513. var newTasks;
  3514. newTasks = 1 <= arguments.length ? slice.call(arguments, 0) : [];
  3515. this.queue = u.map(newTasks, u.previewable);
  3516. this.poke();
  3517. return this.promise();
  3518. };
  3519. return DivertibleChain;
  3520. })();
  3521. }).call(this);
  3522. (function() {
  3523. var e, u,
  3524. bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
  3525. u = up.util;
  3526. e = up.element;
  3527. up.EventListener = (function() {
  3528. function EventListener(element1, eventName1, selector1, callback1, options) {
  3529. this.element = element1;
  3530. this.eventName = eventName1;
  3531. this.selector = selector1;
  3532. this.callback = callback1;
  3533. if (options == null) {
  3534. options = {};
  3535. }
  3536. this.nativeCallback = bind(this.nativeCallback, this);
  3537. this.unbind = bind(this.unbind, this);
  3538. this.jQuery = options.jQuery;
  3539. this.key = this.constructor.key(this.eventName, this.selector, this.callback);
  3540. this.isDefault = up.framework.isBooting();
  3541. }
  3542. EventListener.prototype.bind = function() {
  3543. var base, map;
  3544. map = ((base = this.element).upEventListeners || (base.upEventListeners = {}));
  3545. if (map[this.key]) {
  3546. up.fail('up.on(): The %o callback %o cannot be registered more than once', this.eventName, this.callback);
  3547. }
  3548. map[this.key] = this;
  3549. return this.element.addEventListener(this.eventName, this.nativeCallback);
  3550. };
  3551. EventListener.prototype.unbind = function() {
  3552. var map;
  3553. if (map = this.element.upEventListeners) {
  3554. delete map[this.key];
  3555. }
  3556. return this.element.removeEventListener(this.eventName, this.nativeCallback);
  3557. };
  3558. EventListener.prototype.nativeCallback = function(event) {
  3559. var args, data, element, elementArg, expectedArgCount;
  3560. element = event.target;
  3561. if (this.selector) {
  3562. element = e.closest(element, this.selector);
  3563. }
  3564. if (element) {
  3565. elementArg = this.jQuery ? up.browser.jQuery(element) : element;
  3566. args = [event, elementArg];
  3567. expectedArgCount = this.callback.length;
  3568. if (!(expectedArgCount === 1 || expectedArgCount === 2)) {
  3569. data = up.syntax.data(element);
  3570. args.push(data);
  3571. }
  3572. return this.callback.apply(element, args);
  3573. }
  3574. };
  3575. /*
  3576. Parses the following arg variants into an object:
  3577. - [elements, eventNames, selector, callback]
  3578. - [elements, eventNames, callback]
  3579. - [ eventNames, selector, callback]
  3580. - [ eventNames, callback]
  3581. @function up.EventListener#parseArgs
  3582. @internal
  3583. */
  3584. EventListener.parseArgs = function(args) {
  3585. var callback, elements, eventNames, selector;
  3586. args = u.copy(args);
  3587. callback = args.pop();
  3588. callback.upUid || (callback.upUid = u.uid());
  3589. if (args[0].addEventListener) {
  3590. elements = [args.shift()];
  3591. } else if (u.isJQuery(args[0]) || (u.isList(args[0]) && args[0][0].addEventListener)) {
  3592. elements = args.shift();
  3593. } else {
  3594. elements = [document];
  3595. }
  3596. eventNames = u.splitValues(args.shift());
  3597. selector = args[0];
  3598. return {
  3599. elements: elements,
  3600. eventNames: eventNames,
  3601. selector: selector,
  3602. callback: callback
  3603. };
  3604. };
  3605. EventListener.bind = function(args, options) {
  3606. var element, eventName, i, j, len, len1, listener, parsed, ref, ref1, unbindFns;
  3607. parsed = this.parseArgs(args);
  3608. unbindFns = [];
  3609. ref = parsed.elements;
  3610. for (i = 0, len = ref.length; i < len; i++) {
  3611. element = ref[i];
  3612. ref1 = parsed.eventNames;
  3613. for (j = 0, len1 = ref1.length; j < len1; j++) {
  3614. eventName = ref1[j];
  3615. listener = new this(element, eventName, parsed.selector, parsed.callback, options);
  3616. listener.bind();
  3617. unbindFns.push(listener.unbind);
  3618. }
  3619. }
  3620. return u.sequence(unbindFns);
  3621. };
  3622. EventListener.key = function(eventName, selector, callback) {
  3623. return [eventName, selector, callback.upUid].join('|');
  3624. };
  3625. EventListener.unbind = function(args) {
  3626. var element, eventName, i, key, len, listener, map, parsed, ref, results;
  3627. parsed = this.parseArgs(args);
  3628. ref = parsed.elements;
  3629. results = [];
  3630. for (i = 0, len = ref.length; i < len; i++) {
  3631. element = ref[i];
  3632. map = element.upEventListeners;
  3633. results.push((function() {
  3634. var j, len1, ref1, results1;
  3635. ref1 = parsed.eventNames;
  3636. results1 = [];
  3637. for (j = 0, len1 = ref1.length; j < len1; j++) {
  3638. eventName = ref1[j];
  3639. key = this.key(eventName, parsed.selector, parsed.callback);
  3640. if (map && (listener = map[key])) {
  3641. results1.push(listener.unbind());
  3642. } else {
  3643. results1.push(void 0);
  3644. }
  3645. }
  3646. return results1;
  3647. }).call(this));
  3648. }
  3649. return results;
  3650. };
  3651. EventListener.unbindNonDefault = function(element) {
  3652. var i, len, listener, listeners, map, results;
  3653. if (map = element.upEventListeners) {
  3654. listeners = u.values(map);
  3655. results = [];
  3656. for (i = 0, len = listeners.length; i < len; i++) {
  3657. listener = listeners[i];
  3658. if (!listener.isDefault) {
  3659. results.push(listener.unbind());
  3660. } else {
  3661. results.push(void 0);
  3662. }
  3663. }
  3664. return results;
  3665. }
  3666. };
  3667. return EventListener;
  3668. })();
  3669. }).call(this);
  3670. (function() {
  3671. var u,
  3672. bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
  3673. u = up.util;
  3674. up.ExtractCascade = (function() {
  3675. function ExtractCascade(selectorOrElement, options) {
  3676. this.oldPlanNotFound = bind(this.oldPlanNotFound, this);
  3677. this.matchingPlanNotFound = bind(this.matchingPlanNotFound, this);
  3678. this.bestMatchingSteps = bind(this.bestMatchingSteps, this);
  3679. this.bestPreflightSelector = bind(this.bestPreflightSelector, this);
  3680. this.detectPlan = bind(this.detectPlan, this);
  3681. this.matchingPlan = bind(this.matchingPlan, this);
  3682. this.newPlan = bind(this.newPlan, this);
  3683. this.oldPlan = bind(this.oldPlan, this);
  3684. var base, base1;
  3685. this.options = u.options(options, {
  3686. humanizedTarget: 'selector',
  3687. layer: 'auto'
  3688. });
  3689. if ((base = this.options).transition == null) {
  3690. base.transition = this.options.animation;
  3691. }
  3692. if ((base1 = this.options).hungry == null) {
  3693. base1.hungry = true;
  3694. }
  3695. this.candidates = this.buildCandidates(selectorOrElement);
  3696. this.plans = u.map(this.candidates, (function(_this) {
  3697. return function(candidate, i) {
  3698. var planOptions, ref;
  3699. planOptions = u.copy(_this.options);
  3700. if (i > 0) {
  3701. planOptions.transition = (ref = up.fragment.config.fallbackTransition) != null ? ref : _this.options.transition;
  3702. }
  3703. return new up.ExtractPlan(candidate, planOptions);
  3704. };
  3705. })(this));
  3706. }
  3707. ExtractCascade.prototype.buildCandidates = function(selector) {
  3708. var candidates;
  3709. candidates = [selector, this.options.fallback, up.fragment.config.fallbacks];
  3710. candidates = u.flatten(candidates);
  3711. candidates = u.filter(candidates, u.isTruthy);
  3712. candidates = u.uniq(candidates);
  3713. if (this.options.fallback === false || this.options.provideTarget) {
  3714. candidates = [candidates[0]];
  3715. }
  3716. return candidates;
  3717. };
  3718. ExtractCascade.prototype.oldPlan = function() {
  3719. return this.detectPlan('oldExists');
  3720. };
  3721. ExtractCascade.prototype.newPlan = function() {
  3722. return this.detectPlan('newExists');
  3723. };
  3724. ExtractCascade.prototype.matchingPlan = function() {
  3725. return this.detectPlan('matchExists');
  3726. };
  3727. ExtractCascade.prototype.detectPlan = function(checker) {
  3728. return u.find(this.plans, function(plan) {
  3729. return plan[checker]();
  3730. });
  3731. };
  3732. ExtractCascade.prototype.bestPreflightSelector = function() {
  3733. var plan;
  3734. if (this.options.provideTarget) {
  3735. plan = this.plans[0];
  3736. } else {
  3737. plan = this.oldPlan();
  3738. }
  3739. if (plan) {
  3740. plan.resolveNesting();
  3741. return plan.selector();
  3742. } else {
  3743. return this.oldPlanNotFound();
  3744. }
  3745. };
  3746. ExtractCascade.prototype.bestMatchingSteps = function() {
  3747. var plan;
  3748. if (plan = this.matchingPlan()) {
  3749. plan.addHungrySteps();
  3750. plan.resolveNesting();
  3751. return plan.steps;
  3752. } else {
  3753. return this.matchingPlanNotFound();
  3754. }
  3755. };
  3756. ExtractCascade.prototype.matchingPlanNotFound = function() {
  3757. var inspectAction, message;
  3758. if (this.newPlan()) {
  3759. return this.oldPlanNotFound();
  3760. } else {
  3761. if (this.oldPlan()) {
  3762. message = "Could not find " + this.options.humanizedTarget + " in response";
  3763. } else {
  3764. message = "Could not match " + this.options.humanizedTarget + " in current page and response";
  3765. }
  3766. if (this.options.inspectResponse) {
  3767. inspectAction = {
  3768. label: 'Open response',
  3769. callback: this.options.inspectResponse
  3770. };
  3771. }
  3772. return up.fail([message + " (tried %o)", this.candidates], {
  3773. action: inspectAction
  3774. });
  3775. }
  3776. };
  3777. ExtractCascade.prototype.oldPlanNotFound = function() {
  3778. var layerProse;
  3779. layerProse = this.options.layer;
  3780. if (layerProse === 'auto') {
  3781. layerProse = 'page, modal or popup';
  3782. }
  3783. return up.fail("Could not find " + this.options.humanizedTarget + " in current " + layerProse + " (tried %o)", this.candidates);
  3784. };
  3785. return ExtractCascade;
  3786. })();
  3787. }).call(this);
  3788. (function() {
  3789. var e, u,
  3790. bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
  3791. u = up.util;
  3792. e = up.element;
  3793. up.ExtractPlan = (function() {
  3794. function ExtractPlan(selector, options) {
  3795. this.addHungrySteps = bind(this.addHungrySteps, this);
  3796. this.parseSteps = bind(this.parseSteps, this);
  3797. this.selector = bind(this.selector, this);
  3798. this.resolveNesting = bind(this.resolveNesting, this);
  3799. this.addSteps = bind(this.addSteps, this);
  3800. this.matchExists = bind(this.matchExists, this);
  3801. this.newExists = bind(this.newExists, this);
  3802. this.oldExists = bind(this.oldExists, this);
  3803. this.findNew = bind(this.findNew, this);
  3804. this.findOld = bind(this.findOld, this);
  3805. var originalSelector;
  3806. this.reveal = options.reveal;
  3807. this.origin = options.origin;
  3808. this.hungry = options.hungry;
  3809. this.transition = options.transition;
  3810. this.response = options.response;
  3811. this.oldLayer = options.layer;
  3812. originalSelector = e.resolveSelector(selector, this.origin);
  3813. this.parseSteps(originalSelector);
  3814. }
  3815. ExtractPlan.prototype.findOld = function() {
  3816. return u.each(this.steps, (function(_this) {
  3817. return function(step) {
  3818. return step.oldElement = up.fragment.first(step.selector, {
  3819. layer: _this.oldLayer
  3820. });
  3821. };
  3822. })(this));
  3823. };
  3824. ExtractPlan.prototype.findNew = function() {
  3825. return u.each(this.steps, (function(_this) {
  3826. return function(step) {
  3827. return step.newElement = _this.response.first(step.selector);
  3828. };
  3829. })(this));
  3830. };
  3831. ExtractPlan.prototype.oldExists = function() {
  3832. this.findOld();
  3833. return u.every(this.steps, function(step) {
  3834. return step.oldElement;
  3835. });
  3836. };
  3837. ExtractPlan.prototype.newExists = function() {
  3838. this.findNew();
  3839. return u.every(this.steps, function(step) {
  3840. return step.newElement;
  3841. });
  3842. };
  3843. ExtractPlan.prototype.matchExists = function() {
  3844. return this.oldExists() && this.newExists();
  3845. };
  3846. ExtractPlan.prototype.addSteps = function(steps) {
  3847. return this.steps = this.steps.concat(steps);
  3848. };
  3849. ExtractPlan.prototype.resolveNesting = function() {
  3850. var compressed;
  3851. if (this.steps.length < 2) {
  3852. return;
  3853. }
  3854. compressed = u.copy(this.steps);
  3855. compressed = u.uniqBy(compressed, function(step) {
  3856. return step.oldElement;
  3857. });
  3858. compressed = u.filter(compressed, (function(_this) {
  3859. return function(candidateStep, candidateIndex) {
  3860. return u.every(compressed, function(rivalStep, rivalIndex) {
  3861. var candidateElement, rivalElement;
  3862. if (rivalIndex === candidateIndex) {
  3863. return true;
  3864. } else {
  3865. candidateElement = candidateStep.oldElement;
  3866. rivalElement = rivalStep.oldElement;
  3867. return rivalStep.pseudoClass || !rivalElement.contains(candidateElement);
  3868. }
  3869. });
  3870. };
  3871. })(this));
  3872. compressed[0].reveal = this.steps[0].reveal;
  3873. return this.steps = compressed;
  3874. };
  3875. ExtractPlan.prototype.selector = function() {
  3876. return u.map(this.steps, 'expression').join(', ');
  3877. };
  3878. ExtractPlan.prototype.parseSteps = function(originalSelector) {
  3879. var comma, disjunction;
  3880. comma = /\ *,\ */;
  3881. this.steps = [];
  3882. disjunction = originalSelector.split(comma);
  3883. return u.each(disjunction, (function(_this) {
  3884. return function(expression, i) {
  3885. var doReveal, expressionParts, pseudoClass, selector;
  3886. expressionParts = expression.match(/^(.+?)(?:\:(before|after))?$/);
  3887. expressionParts || up.fail('Could not parse selector literal "%s"', expression);
  3888. selector = expressionParts[1];
  3889. if (selector === 'html') {
  3890. selector = 'body';
  3891. }
  3892. pseudoClass = expressionParts[2];
  3893. doReveal = i === 0 ? _this.reveal : false;
  3894. return _this.steps.push({
  3895. expression: expression,
  3896. selector: selector,
  3897. pseudoClass: pseudoClass,
  3898. transition: _this.transition,
  3899. origin: _this.origin,
  3900. reveal: doReveal
  3901. });
  3902. };
  3903. })(this));
  3904. };
  3905. ExtractPlan.prototype.addHungrySteps = function() {
  3906. var hungries, hungry, hungrySteps, j, len, newHungry, ref, selector, transition;
  3907. hungrySteps = [];
  3908. if (this.hungry) {
  3909. hungries = e.all(up.radio.hungrySelector());
  3910. transition = (ref = up.radio.config.hungryTransition) != null ? ref : this.transition;
  3911. for (j = 0, len = hungries.length; j < len; j++) {
  3912. hungry = hungries[j];
  3913. selector = e.toSelector(hungry);
  3914. if (newHungry = this.response.first(selector)) {
  3915. hungrySteps.push({
  3916. selector: selector,
  3917. oldElement: hungry,
  3918. newElement: newHungry,
  3919. transition: transition,
  3920. reveal: false,
  3921. origin: null
  3922. });
  3923. }
  3924. }
  3925. }
  3926. return this.addSteps(hungrySteps);
  3927. };
  3928. return ExtractPlan;
  3929. })();
  3930. }).call(this);
  3931. (function() {
  3932. var e, u,
  3933. bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
  3934. u = up.util;
  3935. e = up.element;
  3936. up.FieldObserver = (function() {
  3937. function FieldObserver(fieldOrFields, options, callback) {
  3938. this.callback = callback;
  3939. this.check = bind(this.check, this);
  3940. this.readFieldValues = bind(this.readFieldValues, this);
  3941. this.requestCallback = bind(this.requestCallback, this);
  3942. this.isNewValues = bind(this.isNewValues, this);
  3943. this.scheduleValues = bind(this.scheduleValues, this);
  3944. this.scheduleTimer = bind(this.scheduleTimer, this);
  3945. this.cancelTimer = bind(this.cancelTimer, this);
  3946. this.stop = bind(this.stop, this);
  3947. this.start = bind(this.start, this);
  3948. this.fields = e.list(fieldOrFields);
  3949. this.delay = options.delay;
  3950. this.batch = options.batch;
  3951. }
  3952. FieldObserver.prototype.start = function() {
  3953. this.scheduledValues = null;
  3954. this.processedValues = this.readFieldValues();
  3955. this.currentTimer = void 0;
  3956. this.callbackRunning = false;
  3957. return this.unbind = up.on(this.fields, 'input change', this.check);
  3958. };
  3959. FieldObserver.prototype.stop = function() {
  3960. this.unbind();
  3961. return this.cancelTimer();
  3962. };
  3963. FieldObserver.prototype.cancelTimer = function() {
  3964. clearTimeout(this.currentTimer);
  3965. return this.currentTimer = void 0;
  3966. };
  3967. FieldObserver.prototype.scheduleTimer = function() {
  3968. this.cancelTimer();
  3969. return this.currentTimer = u.timer(this.delay, (function(_this) {
  3970. return function() {
  3971. _this.currentTimer = void 0;
  3972. return _this.requestCallback();
  3973. };
  3974. })(this));
  3975. };
  3976. FieldObserver.prototype.scheduleValues = function(values) {
  3977. this.scheduledValues = values;
  3978. return this.scheduleTimer();
  3979. };
  3980. FieldObserver.prototype.isNewValues = function(values) {
  3981. return !u.isEqual(values, this.processedValues) && !u.isEqual(this.scheduledValues, values);
  3982. };
  3983. FieldObserver.prototype.requestCallback = function() {
  3984. var callbackReturnValues, callbacksDone, diff, name, value;
  3985. if (this.scheduledValues !== null && !this.currentTimer && !this.callbackRunning) {
  3986. diff = this.changedValues(this.processedValues, this.scheduledValues);
  3987. this.processedValues = this.scheduledValues;
  3988. this.scheduledValues = null;
  3989. this.callbackRunning = true;
  3990. callbackReturnValues = [];
  3991. if (this.batch) {
  3992. callbackReturnValues.push(this.callback(diff));
  3993. } else {
  3994. for (name in diff) {
  3995. value = diff[name];
  3996. callbackReturnValues.push(this.callback(value, name));
  3997. }
  3998. }
  3999. callbacksDone = Promise.all(callbackReturnValues);
  4000. return u.always(callbacksDone, (function(_this) {
  4001. return function() {
  4002. _this.callbackRunning = false;
  4003. return _this.requestCallback();
  4004. };
  4005. })(this));
  4006. }
  4007. };
  4008. FieldObserver.prototype.changedValues = function(previous, next) {
  4009. var changes, i, key, keys, len, nextValue, previousValue;
  4010. changes = {};
  4011. keys = Object.keys(previous);
  4012. keys = keys.concat(Object.keys(next));
  4013. keys = u.uniq(keys);
  4014. for (i = 0, len = keys.length; i < len; i++) {
  4015. key = keys[i];
  4016. previousValue = previous[key];
  4017. nextValue = next[key];
  4018. if (!u.isEqual(previousValue, nextValue)) {
  4019. changes[key] = nextValue;
  4020. }
  4021. }
  4022. return changes;
  4023. };
  4024. FieldObserver.prototype.readFieldValues = function() {
  4025. return up.Params.fromFields(this.fields).toObject();
  4026. };
  4027. FieldObserver.prototype.check = function() {
  4028. var values;
  4029. values = this.readFieldValues();
  4030. if (this.isNewValues(values)) {
  4031. return this.scheduleValues(values);
  4032. }
  4033. };
  4034. return FieldObserver;
  4035. })();
  4036. }).call(this);
  4037. (function() {
  4038. }).call(this);
  4039. (function() {
  4040. var e, u,
  4041. bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
  4042. slice = [].slice;
  4043. u = up.util;
  4044. e = up.element;
  4045. up.FollowVariant = (function() {
  4046. function FollowVariant(selector, options) {
  4047. this.matchesLink = bind(this.matchesLink, this);
  4048. this.followLink = bind(this.followLink, this);
  4049. this.fullSelector = bind(this.fullSelector, this);
  4050. this.onMousedown = bind(this.onMousedown, this);
  4051. this.onClick = bind(this.onClick, this);
  4052. this.followNow = options.follow;
  4053. this.preloadLink = options.preload;
  4054. this.selectors = u.splitValues(selector, ',');
  4055. }
  4056. FollowVariant.prototype.onClick = function(event, link) {
  4057. if (up.link.shouldProcessEvent(event, link)) {
  4058. if (e.matches(link, '[up-instant]') && link.upInstantSupported) {
  4059. up.event.halt(event);
  4060. link.upInstantSupported = false;
  4061. } else {
  4062. up.event.consumeAction(event);
  4063. return this.followLink(link);
  4064. }
  4065. } else {
  4066. return up.link.allowDefault(event);
  4067. }
  4068. };
  4069. FollowVariant.prototype.onMousedown = function(event, link) {
  4070. if (up.link.shouldProcessEvent(event, link)) {
  4071. link.upInstantSupported = true;
  4072. up.event.consumeAction(event);
  4073. return this.followLink(link);
  4074. }
  4075. };
  4076. FollowVariant.prototype.fullSelector = function(additionalClause) {
  4077. var parts;
  4078. if (additionalClause == null) {
  4079. additionalClause = '';
  4080. }
  4081. parts = [];
  4082. this.selectors.forEach(function(variantSelector) {
  4083. var i, len, ref, results, tagSelector;
  4084. ref = ['a', '[up-href]'];
  4085. results = [];
  4086. for (i = 0, len = ref.length; i < len; i++) {
  4087. tagSelector = ref[i];
  4088. results.push(parts.push("" + tagSelector + variantSelector + additionalClause));
  4089. }
  4090. return results;
  4091. });
  4092. return parts.join(', ');
  4093. };
  4094. FollowVariant.prototype.registerEvents = function() {
  4095. up.on('click', this.fullSelector(), (function(_this) {
  4096. return function() {
  4097. var args;
  4098. args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
  4099. return u.muteRejection(_this.onClick.apply(_this, args));
  4100. };
  4101. })(this));
  4102. return up.on('mousedown', this.fullSelector('[up-instant]'), (function(_this) {
  4103. return function() {
  4104. var args;
  4105. args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
  4106. return u.muteRejection(_this.onMousedown.apply(_this, args));
  4107. };
  4108. })(this));
  4109. };
  4110. FollowVariant.prototype.followLink = function(link, options) {
  4111. var promise;
  4112. if (options == null) {
  4113. options = {};
  4114. }
  4115. promise = up.event.whenEmitted('up:link:follow', {
  4116. log: 'Following link',
  4117. target: link
  4118. });
  4119. promise = promise.then((function(_this) {
  4120. return function() {
  4121. if (!options.preload) {
  4122. up.feedback.start(link);
  4123. }
  4124. return _this.followNow(link, options);
  4125. };
  4126. })(this));
  4127. if (!options.preload) {
  4128. u.always(promise, function() {
  4129. return up.feedback.stop(link);
  4130. });
  4131. }
  4132. return promise;
  4133. };
  4134. FollowVariant.prototype.matchesLink = function(link) {
  4135. return e.matches(link, this.fullSelector());
  4136. };
  4137. return FollowVariant;
  4138. })();
  4139. }).call(this);
  4140. (function() {
  4141. var e, u;
  4142. u = up.util;
  4143. e = up.element;
  4144. up.HtmlParser = (function() {
  4145. function HtmlParser(html) {
  4146. this.html = html;
  4147. this.wrapNoscriptInHtml();
  4148. this.parsedDoc = e.createDocumentFromHtml(this.html);
  4149. }
  4150. HtmlParser.prototype.title = function() {
  4151. var ref;
  4152. return (ref = this.parsedDoc.querySelector("head title")) != null ? ref.textContent : void 0;
  4153. };
  4154. HtmlParser.prototype.first = function(selector) {
  4155. return e.first(this.parsedDoc, selector);
  4156. };
  4157. HtmlParser.prototype.prepareForInsertion = function(element) {
  4158. return this.unwrapNoscriptInElement(element);
  4159. };
  4160. HtmlParser.prototype.wrapNoscriptInHtml = function() {
  4161. var noscriptPattern;
  4162. noscriptPattern = /<noscript[^>]*>((.|\s)*?)<\/noscript>/ig;
  4163. return this.html = this.html.replace(noscriptPattern, (function(_this) {
  4164. return function(match, content) {
  4165. _this.didWrapNoscript = true;
  4166. return '<div class="up-noscript" data-html="' + u.escapeHtml(content) + '"></div>';
  4167. };
  4168. })(this));
  4169. };
  4170. HtmlParser.prototype.unwrapNoscriptInElement = function(element) {
  4171. var i, len, noscript, results, wrappedContent, wrappedNoscript, wrappedNoscripts;
  4172. if (!this.didWrapNoscript) {
  4173. return;
  4174. }
  4175. wrappedNoscripts = element.querySelectorAll('.up-noscript');
  4176. results = [];
  4177. for (i = 0, len = wrappedNoscripts.length; i < len; i++) {
  4178. wrappedNoscript = wrappedNoscripts[i];
  4179. wrappedContent = wrappedNoscript.getAttribute('data-html');
  4180. noscript = document.createElement('noscript');
  4181. noscript.textContent = wrappedContent;
  4182. results.push(wrappedNoscript.parentNode.replaceChild(noscript, wrappedNoscript));
  4183. }
  4184. return results;
  4185. };
  4186. return HtmlParser;
  4187. })();
  4188. }).call(this);
  4189. (function() {
  4190. var e, u,
  4191. bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
  4192. u = up.util;
  4193. e = up.element;
  4194. up.MotionController = (function() {
  4195. function MotionController(name) {
  4196. this.reset = bind(this.reset, this);
  4197. this.whileForwardingFinishEvent = bind(this.whileForwardingFinishEvent, this);
  4198. this.unmarkCluster = bind(this.unmarkCluster, this);
  4199. this.markCluster = bind(this.markCluster, this);
  4200. this.whenElementFinished = bind(this.whenElementFinished, this);
  4201. this.emitFinishEvent = bind(this.emitFinishEvent, this);
  4202. this.finishOneElement = bind(this.finishOneElement, this);
  4203. this.isActive = bind(this.isActive, this);
  4204. this.expandFinishRequest = bind(this.expandFinishRequest, this);
  4205. this.finish = bind(this.finish, this);
  4206. this.startFunction = bind(this.startFunction, this);
  4207. this.activeClass = "up-" + name;
  4208. this.dataKey = "up-" + name + "-finished";
  4209. this.selector = "." + this.activeClass;
  4210. this.finishEvent = "up:" + name + ":finish";
  4211. this.finishCount = 0;
  4212. this.clusterCount = 0;
  4213. }
  4214. /***
  4215. Finishes all animations in the given elements' ancestors and
  4216. descendants, then calls the given function.
  4217. The function is expected to return a promise that is fulfilled when
  4218. the animation ends. The function is also expected to listen to
  4219. `this.finishEvent` and instantly skip to the last frame
  4220. when the event is observed.
  4221. The animation is tracked so it can be
  4222. [`finished`](/up.MotionController.finish) later.
  4223. @method startFunction
  4224. @param {Element|List<Element>} cluster
  4225. A list of elements that will be affected by the motion.
  4226. @param {Function(): Promise} startMotion
  4227. @param {Object} [memory.trackMotion=true]
  4228. @return {Promise}
  4229. A promise that is fulfilled when the animation ends.
  4230. */
  4231. MotionController.prototype.startFunction = function(cluster, startMotion, memory) {
  4232. var mutedAnimator, ref;
  4233. if (memory == null) {
  4234. memory = {};
  4235. }
  4236. cluster = e.list(cluster);
  4237. mutedAnimator = function() {
  4238. return u.muteRejection(startMotion());
  4239. };
  4240. memory.trackMotion = (ref = memory.trackMotion) != null ? ref : up.motion.isEnabled();
  4241. if (memory.trackMotion === false) {
  4242. return u.microtask(mutedAnimator);
  4243. } else {
  4244. memory.trackMotion = false;
  4245. return this.finish(cluster).then((function(_this) {
  4246. return function() {
  4247. var promise;
  4248. promise = _this.whileForwardingFinishEvent(cluster, mutedAnimator);
  4249. promise = promise.then(function() {
  4250. return _this.unmarkCluster(cluster);
  4251. });
  4252. _this.markCluster(cluster, promise);
  4253. return promise;
  4254. };
  4255. })(this));
  4256. }
  4257. };
  4258. /**
  4259. Finishes all animations in the given elements' ancestors and
  4260. descendants, then calls `motion.start()`.
  4261. Also listens to `this.finishEvent` on the given elements.
  4262. When this event is observed, calls `motion.finish()`.
  4263. @method startMotion
  4264. @param {Element|List<Element>} cluster
  4265. @param {up.Motion} motion
  4266. @param {Object} [memory.trackMotion=true]
  4267. */
  4268. MotionController.prototype.startMotion = function(cluster, motion, memory) {
  4269. var finish, promise, start, unbindFinish;
  4270. if (memory == null) {
  4271. memory = {};
  4272. }
  4273. start = function() {
  4274. return motion.start();
  4275. };
  4276. finish = function() {
  4277. return motion.finish();
  4278. };
  4279. unbindFinish = up.on(cluster, this.finishEvent, finish);
  4280. promise = this.startFunction(cluster, start, memory);
  4281. promise = promise.then(unbindFinish);
  4282. return promise;
  4283. };
  4284. /***
  4285. @method finish
  4286. @param {List<Element>} [elements]
  4287. If no element is given, finishes all animations in the documnet.
  4288. If an element is given, only finishes animations in its subtree and ancestors.
  4289. @return {Promise} A promise that is fulfilled when animations have finished.
  4290. */
  4291. MotionController.prototype.finish = function(elements) {
  4292. var allFinished;
  4293. this.finishCount++;
  4294. if (this.clusterCount === 0 || !up.motion.isEnabled()) {
  4295. return Promise.resolve();
  4296. }
  4297. elements = this.expandFinishRequest(elements);
  4298. allFinished = u.map(elements, this.finishOneElement);
  4299. return Promise.all(allFinished);
  4300. };
  4301. MotionController.prototype.expandFinishRequest = function(elements) {
  4302. if (elements) {
  4303. return u.flatMap(elements, (function(_this) {
  4304. return function(el) {
  4305. return e.list(e.closest(el, _this.selector), e.all(el, _this.selector));
  4306. };
  4307. })(this));
  4308. } else {
  4309. return e.all(this.selector);
  4310. }
  4311. };
  4312. MotionController.prototype.isActive = function(element) {
  4313. return element.classList.contains(this.activeClass);
  4314. };
  4315. MotionController.prototype.finishOneElement = function(element) {
  4316. this.emitFinishEvent(element);
  4317. return this.whenElementFinished(element);
  4318. };
  4319. MotionController.prototype.emitFinishEvent = function(element, eventAttrs) {
  4320. if (eventAttrs == null) {
  4321. eventAttrs = {};
  4322. }
  4323. eventAttrs = u.merge({
  4324. target: element,
  4325. log: false
  4326. }, eventAttrs);
  4327. return up.emit(this.finishEvent, eventAttrs);
  4328. };
  4329. MotionController.prototype.whenElementFinished = function(element) {
  4330. return element[this.dataKey] || Promise.resolve();
  4331. };
  4332. MotionController.prototype.markCluster = function(cluster, promise) {
  4333. var element, i, len, results;
  4334. this.clusterCount++;
  4335. results = [];
  4336. for (i = 0, len = cluster.length; i < len; i++) {
  4337. element = cluster[i];
  4338. element.classList.add(this.activeClass);
  4339. results.push(element[this.dataKey] = promise);
  4340. }
  4341. return results;
  4342. };
  4343. MotionController.prototype.unmarkCluster = function(cluster) {
  4344. var element, i, len, results;
  4345. this.clusterCount--;
  4346. results = [];
  4347. for (i = 0, len = cluster.length; i < len; i++) {
  4348. element = cluster[i];
  4349. element.classList.remove(this.activeClass);
  4350. results.push(delete element[this.dataKey]);
  4351. }
  4352. return results;
  4353. };
  4354. MotionController.prototype.whileForwardingFinishEvent = function(cluster, fn) {
  4355. var doForward, unbindFinish;
  4356. if (cluster.length < 2) {
  4357. return fn();
  4358. }
  4359. doForward = (function(_this) {
  4360. return function(event) {
  4361. if (!event.forwarded) {
  4362. return u.each(cluster, function(element) {
  4363. if (element !== event.target && _this.isActive(element)) {
  4364. return _this.emitFinishEvent(element, {
  4365. forwarded: true
  4366. });
  4367. }
  4368. });
  4369. }
  4370. };
  4371. })(this);
  4372. unbindFinish = up.on(cluster, this.finishEvent, doForward);
  4373. return fn().then(unbindFinish);
  4374. };
  4375. MotionController.prototype.reset = function() {
  4376. return this.finish().then((function(_this) {
  4377. return function() {
  4378. _this.finishCount = 0;
  4379. return _this.clusterCount = 0;
  4380. };
  4381. })(this));
  4382. };
  4383. return MotionController;
  4384. })();
  4385. }).call(this);
  4386. (function() {
  4387. var e, u,
  4388. bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
  4389. u = up.util;
  4390. e = up.element;
  4391. /***
  4392. The `up.Params` class offers a consistent API to read and manipulate request parameters
  4393. independent of their type.
  4394. Request parameters are used in [form submissions](/up.Params#fromForm) and
  4395. [URLs](/up.Params#fromURL). Methods like `up.submit()` or `up.replace()` accept
  4396. request parameters as a `{ params }` option.
  4397. \#\#\# Supported parameter types
  4398. The following types of parameter representation are supported:
  4399. 1. An object like `{ email: 'foo@bar.com' }`
  4400. 2. A query string like `'email=foo%40bar.com'`
  4401. 3. An array of `{ name, value }` objects like `[{ name: 'email', value: 'foo@bar.com' }]`
  4402. 4. A [FormData](https://developer.mozilla.org/en-US/docs/Web/API/FormData) object.
  4403. On IE 11 and Edge, `FormData` payloads require a [polyfill for `FormData#entries()`](https://github.com/jimmywarting/FormData).
  4404. @class up.Params
  4405. */
  4406. up.Params = (function() {
  4407. /***
  4408. Constructs a new `up.Params` instance.
  4409. @constructor up.Params
  4410. @param {Object|Array|string|up.Params} [params]
  4411. An existing list of params with which to initialize the new `up.Params` object.
  4412. The given params value may be of any [supported type](/up.Params).
  4413. @return {up.Params}
  4414. @experimental
  4415. */
  4416. function Params(raw) {
  4417. this.arrayEntryToQuery = bind(this.arrayEntryToQuery, this);
  4418. this.clear();
  4419. this.addAll(raw);
  4420. }
  4421. /***
  4422. Removes all params from this object.
  4423. @method up.Params#clear
  4424. @experimental
  4425. */
  4426. Params.prototype.clear = function() {
  4427. return this.entries = [];
  4428. };
  4429. Params.prototype["" + u.copy.key] = function() {
  4430. return new up.Params(this);
  4431. };
  4432. /***
  4433. Returns an object representation of this `up.Params` instance.
  4434. The returned value is a simple JavaScript object with properties
  4435. that correspond to the key/values in the given `params`.
  4436. \#\#\# Example
  4437. var params = new up.Params('foo=bar&baz=bam')
  4438. var object = params.toObject()
  4439. // object is now: {
  4440. // foo: 'bar',
  4441. // baz: 'bam'
  4442. // ]
  4443. @function up.Params#toObject
  4444. @return {Object}
  4445. @experimental
  4446. */
  4447. Params.prototype.toObject = function() {
  4448. var entry, i, len, name, obj, ref, value;
  4449. obj = {};
  4450. ref = this.entries;
  4451. for (i = 0, len = ref.length; i < len; i++) {
  4452. entry = ref[i];
  4453. name = entry.name, value = entry.value;
  4454. if (!u.isBasicObjectProperty(name)) {
  4455. if (this.isArrayKey(name)) {
  4456. obj[name] || (obj[name] = []);
  4457. obj[name].push(value);
  4458. } else {
  4459. obj[name] = value;
  4460. }
  4461. }
  4462. }
  4463. return obj;
  4464. };
  4465. /***
  4466. Returns an array representation of this `up.Params` instance.
  4467. The returned value is a JavaScript array with elements that are objects with
  4468. `{ key }` and `{ value }` properties.
  4469. \#\#\# Example
  4470. var params = new up.Params('foo=bar&baz=bam')
  4471. var array = params.toArray()
  4472. // array is now: [
  4473. // { name: 'foo', value: 'bar' },
  4474. // { name: 'baz', value: 'bam' }
  4475. // ]
  4476. @function up.Params#toArray
  4477. @return {Array}
  4478. @experimental
  4479. */
  4480. Params.prototype.toArray = function() {
  4481. return this.entries;
  4482. };
  4483. /***
  4484. Returns a [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData) representation
  4485. of this `up.Params` instance.
  4486. \#\#\# Example
  4487. var params = new up.Params('foo=bar&baz=bam')
  4488. var formData = params.toFormData()
  4489. formData.get('foo') // 'bar'
  4490. formData.get('baz') // 'bam'
  4491. @function up.Params#toFormData
  4492. @return {FormData}
  4493. @experimental
  4494. */
  4495. Params.prototype.toFormData = function() {
  4496. var entry, formData, i, len, ref;
  4497. formData = new FormData();
  4498. ref = this.entries;
  4499. for (i = 0, len = ref.length; i < len; i++) {
  4500. entry = ref[i];
  4501. formData.append(entry.name, entry.value);
  4502. }
  4503. return formData;
  4504. };
  4505. /***
  4506. Returns an [query string](https://en.wikipedia.org/wiki/Query_string) for this `up.Params` instance.
  4507. The keys and values in the returned query string will be [percent-encoded](https://developer.mozilla.org/en-US/docs/Glossary/percent-encoding).
  4508. Non-primitive values (like [`File`](https://developer.mozilla.org/en-US/docs/Web/API/File) will be omitted from
  4509. the retuned query string.
  4510. \#\#\# Example
  4511. var params = new up.Params({ foo: 'bar', baz: 'bam' })
  4512. var query = params.toQuery()
  4513. // query is now: 'foo=bar&baz=bam'
  4514. @function up.Params#toQuery
  4515. @param {Object|FormData|string|Array|undefined} params
  4516. the params to convert
  4517. @return {string}
  4518. a query string built from the given params
  4519. @experimental
  4520. */
  4521. Params.prototype.toQuery = function() {
  4522. var parts;
  4523. parts = u.map(this.entries, this.arrayEntryToQuery);
  4524. parts = u.compact(parts);
  4525. return parts.join('&');
  4526. };
  4527. Params.prototype.arrayEntryToQuery = function(entry) {
  4528. var query, value;
  4529. value = entry.value;
  4530. if (!this.isPrimitiveValue(value)) {
  4531. return void 0;
  4532. }
  4533. query = encodeURIComponent(entry.name);
  4534. if (u.isGiven(value)) {
  4535. query += "=";
  4536. query += encodeURIComponent(value);
  4537. }
  4538. return query;
  4539. };
  4540. /***
  4541. Returns whether the given value can be encoded into a query string.
  4542. We will have `File` values in our params when we serialize a form with a file input.
  4543. These entries will be filtered out when converting to a query string.
  4544. @function up.Params#isPrimitiveValue
  4545. @internal
  4546. */
  4547. Params.prototype.isPrimitiveValue = function(value) {
  4548. return u.isMissing(value) || u.isString(value) || u.isNumber(value) || u.isBoolean(value);
  4549. };
  4550. /***
  4551. Builds an URL string from the given base URL and
  4552. this `up.Params` instance as a [query string](/up.Params.toString).
  4553. The base URL may or may not already contain a query string. The
  4554. additional query string will be joined with an `&` or `?` character accordingly.
  4555. @function up.Params#toURL
  4556. @param {string} base
  4557. The base URL that will be prepended to this `up.Params` object as a [query string](/up.Params.toString).
  4558. @return {string}
  4559. The built URL.
  4560. @experimental
  4561. */
  4562. Params.prototype.toURL = function(base) {
  4563. var parts, separator;
  4564. parts = [base, this.toQuery()];
  4565. parts = u.filter(parts, u.isPresent);
  4566. separator = u.contains(base, '?') ? '&' : '?';
  4567. return parts.join(separator);
  4568. };
  4569. /***
  4570. Adds a new entry with the given `name` and `value`.
  4571. An `up.Params` instance can hold multiple entries with the same name.
  4572. To overwrite all existing entries with the given `name`, use `up.Params#set()` instead.
  4573. \#\#\# Example
  4574. var params = new up.Params()
  4575. params.add('foo', 'fooValue')
  4576. var foo = params.get('foo')
  4577. // foo is now 'fooValue'
  4578. @function up.Params#add
  4579. @param {string} name
  4580. The name of the new entry.
  4581. @param {any} value
  4582. The value of the new entry.
  4583. @experimental
  4584. */
  4585. Params.prototype.add = function(name, value) {
  4586. return this.entries.push({
  4587. name: name,
  4588. value: value
  4589. });
  4590. };
  4591. /***
  4592. Adds all entries from the given list of params.
  4593. The given params value may be of any [supported type](/up.Params).
  4594. @function up.Params#addAll
  4595. @param {Object|Array|string|up.Params|undefined} params
  4596. @experimental
  4597. */
  4598. Params.prototype.addAll = function(raw) {
  4599. var ref, ref1;
  4600. if (u.isMissing(raw)) {
  4601. } else if (raw instanceof this.constructor) {
  4602. return (ref = this.entries).push.apply(ref, raw.entries);
  4603. } else if (u.isArray(raw)) {
  4604. return (ref1 = this.entries).push.apply(ref1, raw);
  4605. } else if (u.isString(raw)) {
  4606. return this.addAllFromQuery(raw);
  4607. } else if (u.isFormData(raw)) {
  4608. return this.addAllFromFormData(raw);
  4609. } else if (u.isObject(raw)) {
  4610. return this.addAllFromObject(raw);
  4611. } else {
  4612. return up.fail("Unsupport params type: %o", raw);
  4613. }
  4614. };
  4615. Params.prototype.addAllFromObject = function(object) {
  4616. var key, results, value, valueElement, valueElements;
  4617. results = [];
  4618. for (key in object) {
  4619. value = object[key];
  4620. valueElements = u.isArray(value) ? value : [value];
  4621. results.push((function() {
  4622. var i, len, results1;
  4623. results1 = [];
  4624. for (i = 0, len = valueElements.length; i < len; i++) {
  4625. valueElement = valueElements[i];
  4626. results1.push(this.add(key, valueElement));
  4627. }
  4628. return results1;
  4629. }).call(this));
  4630. }
  4631. return results;
  4632. };
  4633. Params.prototype.addAllFromQuery = function(query) {
  4634. var i, len, name, part, ref, ref1, results, value;
  4635. ref = query.split('&');
  4636. results = [];
  4637. for (i = 0, len = ref.length; i < len; i++) {
  4638. part = ref[i];
  4639. if (part) {
  4640. ref1 = part.split('='), name = ref1[0], value = ref1[1];
  4641. name = decodeURIComponent(name);
  4642. if (u.isGiven(value)) {
  4643. value = decodeURIComponent(value);
  4644. } else {
  4645. value = null;
  4646. }
  4647. results.push(this.add(name, value));
  4648. } else {
  4649. results.push(void 0);
  4650. }
  4651. }
  4652. return results;
  4653. };
  4654. Params.prototype.addAllFromFormData = function(formData) {
  4655. return u.eachIterator(formData.entries(), (function(_this) {
  4656. return function(value) {
  4657. return _this.add.apply(_this, value);
  4658. };
  4659. })(this));
  4660. };
  4661. /***
  4662. Sets the `value` for the entry with given `name`.
  4663. An `up.Params` instance can hold multiple entries with the same name.
  4664. All existing entries with the given `name` are [deleted](/up.Params#delete) before the
  4665. new entry is set. To add a new entry even if the `name` is taken, use `up.Params#add()`.
  4666. @function up.Params#set
  4667. @param {string} name
  4668. The name of the entry to set.
  4669. @param {any} value
  4670. The new value of the entry.
  4671. @experimental
  4672. */
  4673. Params.prototype.set = function(name, value) {
  4674. this["delete"](name);
  4675. return this.add(name, value);
  4676. };
  4677. /***
  4678. Deletes all entries with the given `name`.
  4679. @function up.Params#delete
  4680. @param {string} name
  4681. @experimental
  4682. */
  4683. Params.prototype["delete"] = function(name) {
  4684. return this.entries = u.reject(this.entries, this.matchEntryFn(name));
  4685. };
  4686. Params.prototype.matchEntryFn = function(name) {
  4687. return function(entry) {
  4688. return entry.name === name;
  4689. };
  4690. };
  4691. /***
  4692. Returns the first param value with the given `name` from the given `params`.
  4693. Returns `undefined` if no param value with that name is set.
  4694. If the `name` denotes an array field (e.g. `foo[]`), *all* param values with the given `name`
  4695. are returned as an array. If no param value with that array name is set, an empty
  4696. array is returned.
  4697. To always return a single value use `up.Params#getFirst()` instead.
  4698. To always return an array of values use `up.Params#getAll()` instead.
  4699. \#\#\# Example
  4700. var params = new up.Params({ foo: 'fooValue', bar: 'barValue' })
  4701. var params = new up.Params([
  4702. { name: 'foo', value: 'fooValue' }
  4703. { name: 'bar[]', value: 'barValue1' }
  4704. { name: 'bar[]', value: 'barValue2' })
  4705. ]})
  4706. var foo = params.get('foo')
  4707. // foo is now 'fooValue'
  4708. var bar = params.get('bar')
  4709. // bar is now ['barValue1', 'barValue2']
  4710. @function up.Params#get
  4711. @param {string} name
  4712. @experimental
  4713. */
  4714. Params.prototype.get = function(name) {
  4715. if (this.isArrayKey(name)) {
  4716. return this.getAll(name);
  4717. } else {
  4718. return this.getFirst(name);
  4719. }
  4720. };
  4721. /***
  4722. Returns the first param value with the given `name`.
  4723. Returns `undefined` if no param value with that name is set.
  4724. @function up.Params#getFirst
  4725. @param {string} name
  4726. @return {any}
  4727. The value of the param with the given name.
  4728. */
  4729. Params.prototype.getFirst = function(name) {
  4730. var entry;
  4731. entry = u.find(this.entries, this.matchEntryFn(name));
  4732. return entry != null ? entry.value : void 0;
  4733. };
  4734. /***
  4735. Returns an array of all param values with the given `name`.
  4736. Returns an empty array if no param value with that name is set.
  4737. @function up.Params#getAll
  4738. @param {string} name
  4739. @return {Array}
  4740. An array of all values with the given name.
  4741. */
  4742. Params.prototype.getAll = function(name) {
  4743. var entries;
  4744. if (this.isArrayKey(name)) {
  4745. return this.getAll(name);
  4746. } else {
  4747. entries = u.map(this.entries, this.matchEntryFn(name));
  4748. return u.map(entries, 'value');
  4749. }
  4750. };
  4751. Params.prototype.isArrayKey = function(key) {
  4752. return u.endsWith(key, '[]');
  4753. };
  4754. Params.prototype["" + u.isBlank.key] = function() {
  4755. return this.entries.length === 0;
  4756. };
  4757. /***
  4758. Constructs a new `up.Params` instance from the given `<form>`.
  4759. The returned params may be passed as `{ params }` option to
  4760. [`up.request()`](/up.request) or [`up.replace()`](/up.replace).
  4761. The constructed `up.Params` will include exactly those form values that would be
  4762. included in a regular form submission. In particular:
  4763. - All `<input>` types are suppported
  4764. - Field values are usually strings, but an `<input type="file">` will produce
  4765. [`File`](https://developer.mozilla.org/en-US/docs/Web/API/File) values.
  4766. - An `<input type="radio">` or `<input type="checkbox">` will only be added if they are `[checked]`.
  4767. - An `<select>` will only be added if at least one value is `[checked]`.
  4768. - If passed a `<select multiple>` or `<input type="file" multiple>`, all selected values are added.
  4769. If passed a `<select multiple>`, all selected values are added.
  4770. - Fields that are `[disabled]` are ignored
  4771. - Fields without a `[name]` attribute are ignored.
  4772. - The serialized params will include the form's submit button, if that
  4773. button as a `name` attribute.
  4774. \#\#\# Example
  4775. Given this HTML form:
  4776. <form>
  4777. <input type="text" name="email" value="foo@bar.com">
  4778. <input type="password" name="pass" value="secret">
  4779. </form>
  4780. This would serialize the form into an array representation:
  4781. var params = up.Params.fromForm('input[name=email]')
  4782. var email = params.get('email') // email is now 'foo@bar.com'
  4783. var pass = params.get('pass') // pass is now 'secret'
  4784. @function up.Params.fromForm
  4785. @param {Element|jQuery|string} form
  4786. A `<form>` element or a selector that matches a `<form>` element.
  4787. @return {up.Params}
  4788. A new `up.Params` instance with values from the given form.
  4789. @experimental
  4790. */
  4791. Params.fromForm = function(form) {
  4792. var fields;
  4793. if (form = e.get(form)) {
  4794. fields = up.form.submissionFields(form);
  4795. return this.fromFields(fields);
  4796. }
  4797. };
  4798. /***
  4799. Constructs a new `up.Params` instance from one or more
  4800. [HTML form field](https://www.w3schools.com/html/html_form_elements.asp).
  4801. The constructed `up.Params` will include exactly those form values that would be
  4802. included for the given fields in a regular form submission. If a given field wouldn't
  4803. submit a value (like an unchecked `<input type="checkbox">`, nothing will be added.
  4804. See `up.Params.fromForm()` for more details and examples.
  4805. @function up.Params.fromFields
  4806. @param {Element|List<Element>|jQuery} fields
  4807. @return {up.Params}
  4808. @experimental
  4809. */
  4810. Params.fromFields = function(fields) {
  4811. var field, i, len, params, ref;
  4812. params = new this();
  4813. ref = u.wrapList(fields);
  4814. for (i = 0, len = ref.length; i < len; i++) {
  4815. field = ref[i];
  4816. params.addField(field);
  4817. }
  4818. return params;
  4819. };
  4820. /***
  4821. Adds params from the given [HTML form field](https://www.w3schools.com/html/html_form_elements.asp).
  4822. The added params will include exactly those form values that would be
  4823. included for the given field in a regular form submission. If the given field wouldn't
  4824. submit a value (like an unchecked `<input type="checkbox">`, nothing will be added.
  4825. See `up.Params.fromForm()` for more details and examples.
  4826. @function up.Params#addField
  4827. @param {Element|jQuery} field
  4828. @experimental
  4829. */
  4830. Params.prototype.addField = function(field) {
  4831. var file, i, j, len, len1, name, option, params, ref, ref1, results, results1, tagName, type;
  4832. params = new this.constructor();
  4833. if ((field = e.get(field)) && (name = field.name) && (!field.disabled)) {
  4834. tagName = field.tagName;
  4835. type = field.type;
  4836. if (tagName === 'SELECT') {
  4837. ref = field.querySelectorAll('option');
  4838. results = [];
  4839. for (i = 0, len = ref.length; i < len; i++) {
  4840. option = ref[i];
  4841. if (option.selected) {
  4842. results.push(this.add(name, option.value));
  4843. } else {
  4844. results.push(void 0);
  4845. }
  4846. }
  4847. return results;
  4848. } else if (type === 'checkbox' || type === 'radio') {
  4849. if (field.checked) {
  4850. return this.add(name, field.value);
  4851. }
  4852. } else if (type === 'file') {
  4853. ref1 = field.files;
  4854. results1 = [];
  4855. for (j = 0, len1 = ref1.length; j < len1; j++) {
  4856. file = ref1[j];
  4857. results1.push(this.add(name, file));
  4858. }
  4859. return results1;
  4860. } else {
  4861. return this.add(name, field.value);
  4862. }
  4863. }
  4864. };
  4865. Params.prototype["" + u.isEqual.key] = function(other) {
  4866. return other && (this.constructor === other.constructor) && u.isEqual(this.entries, other.entries);
  4867. };
  4868. /***
  4869. Constructs a new `up.Params` instance from the given URL's
  4870. [query string](https://en.wikipedia.org/wiki/Query_string).
  4871. Constructs an empty `up.Params` instance if the given URL has no query string.
  4872. \#\#\# Example
  4873. var params = up.Params.fromURL('http://foo.com?foo=fooValue&bar=barValue')
  4874. var foo = params.get('foo')
  4875. // foo is now: 'fooValue'
  4876. @function up.Params.fromURL
  4877. @param {string} url
  4878. The URL from which to extract the query string.
  4879. @return {string|undefined}
  4880. The given URL's query string, or `undefined` if the URL has no query component.
  4881. @experimental
  4882. */
  4883. Params.fromURL = function(url) {
  4884. var params, query, urlParts;
  4885. params = new this();
  4886. urlParts = u.parseUrl(url);
  4887. if (query = urlParts.search) {
  4888. query = query.replace(/^\?/, '');
  4889. params.addAll(query);
  4890. }
  4891. return params;
  4892. };
  4893. /***
  4894. Returns the given URL without its [query string](https://en.wikipedia.org/wiki/Query_string).
  4895. \#\#\# Example
  4896. var url = up.Params.stripURL('http://foo.com?key=value')
  4897. // url is now: 'http://foo.com'
  4898. @function up.Params.stripURL
  4899. @param {string} url
  4900. A URL (with or without a query string).
  4901. @return {string}
  4902. The given URL without its query string.
  4903. @experimental
  4904. */
  4905. Params.stripURL = function(url) {
  4906. return u.normalizeUrl(url, {
  4907. search: false
  4908. });
  4909. };
  4910. /***
  4911. If passed an `up.Params` instance, it is returned unchanged.
  4912. Otherwise constructs an `up.Params` instance from the given value.
  4913. The given params value may be of any [supported type](/up.Params)
  4914. The return value is always an `up.Params` instance.
  4915. @function up.Params.wrap
  4916. @param {Object|Array|string|up.Params|undefined} params
  4917. @return {up.Params}
  4918. */
  4919. Params.wrap = function(value) {
  4920. return u.wrapValue(value, this);
  4921. };
  4922. return Params;
  4923. })();
  4924. }).call(this);
  4925. (function() {
  4926. var u = up.util
  4927. up.Rect = function(props) {
  4928. u.assign(this, u.only(props, 'left', 'top', 'width', 'height'))
  4929. }
  4930. up.Rect.prototype = {
  4931. get bottom() {
  4932. return this.top + this.height
  4933. },
  4934. get right() {
  4935. return this.left + this.width
  4936. }
  4937. }
  4938. up.Rect.fromElement = function(element) {
  4939. return new up.Rect(element.getBoundingClientRect())
  4940. }
  4941. })()
  4942. ;
  4943. (function() {
  4944. var e, u,
  4945. bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
  4946. extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
  4947. hasProp = {}.hasOwnProperty;
  4948. u = up.util;
  4949. e = up.element;
  4950. /***
  4951. Instances of `up.Request` normalizes properties of an [`AJAX request`](/up.request)
  4952. such as the requested URL, form parameters and HTTP method.
  4953. @class up.Request
  4954. */
  4955. up.Request = (function(superClass) {
  4956. extend(Request, superClass);
  4957. /***
  4958. The HTTP method for the request.
  4959. @property up.Request#method
  4960. @param {string} method
  4961. @stable
  4962. */
  4963. /***
  4964. The URL for the request.
  4965. @property up.Request#url
  4966. @param {string} url
  4967. @stable
  4968. */
  4969. /***
  4970. [Parameters](/up.Params) that should be sent as the request's payload.
  4971. @property up.Request#params
  4972. @param {Object|FormData|string|Array} params
  4973. @stable
  4974. */
  4975. /***
  4976. The CSS selector that will be sent as an [`X-Up-Target` header](/up.protocol#optimizing-responses).
  4977. @property up.Request#target
  4978. @param {string} target
  4979. @stable
  4980. */
  4981. /***
  4982. The CSS selector that will be sent as an [`X-Up-Fail-Target` header](/up.protocol#optimizing-responses).
  4983. @property up.Request#failTarget
  4984. @param {string} failTarget
  4985. @stable
  4986. */
  4987. /***
  4988. An object of additional HTTP headers.
  4989. @property up.Request#headers
  4990. @param {Object} headers
  4991. @stable
  4992. */
  4993. /***
  4994. A timeout in milliseconds.
  4995. If [`up.proxy.config.maxRequests`](/up.proxy.config#config.maxRequests) is set,
  4996. the timeout will not include the time spent waiting in the queue.
  4997. @property up.Request#timeout
  4998. @param {Object|undefined} timeout
  4999. @stable
  5000. */
  5001. Request.prototype.fields = function() {
  5002. return ['method', 'url', 'params', 'target', 'failTarget', 'headers', 'timeout', 'preload', 'cache'];
  5003. };
  5004. /***
  5005. Creates a new `up.Request` object.
  5006. This will not actually send the request over the network. For that use `up.request()`.
  5007. @constructor up.Request
  5008. @param {string} attrs.url
  5009. @param {string} [attrs.method='get']
  5010. @param {up.Params|string|Object|Array} [attrs.params]
  5011. @param {string} [attrs.target]
  5012. @param {string} [attrs.failTarget]
  5013. @param {Object<string, string>} [attrs.headers]
  5014. @param {number} [attrs.timeout]
  5015. @internal
  5016. */
  5017. function Request(options) {
  5018. this.cacheKey = bind(this.cacheKey, this);
  5019. this.isCachable = bind(this.isCachable, this);
  5020. this.buildResponse = bind(this.buildResponse, this);
  5021. this.isCrossDomain = bind(this.isCrossDomain, this);
  5022. this.csrfToken = bind(this.csrfToken, this);
  5023. this.navigate = bind(this.navigate, this);
  5024. this.send = bind(this.send, this);
  5025. this.isSafe = bind(this.isSafe, this);
  5026. this.transferSearchToParams = bind(this.transferSearchToParams, this);
  5027. this.transferParamsToUrl = bind(this.transferParamsToUrl, this);
  5028. this.extractHashFromUrl = bind(this.extractHashFromUrl, this);
  5029. this.normalize = bind(this.normalize, this);
  5030. up.legacy.fixKey(options, 'data', 'params');
  5031. Request.__super__.constructor.call(this, options);
  5032. this.normalize();
  5033. }
  5034. Request.prototype.normalize = function() {
  5035. this.params = new up.Params(this.params);
  5036. this.method = u.normalizeMethod(this.method);
  5037. this.headers || (this.headers = {});
  5038. this.extractHashFromUrl();
  5039. if (!u.methodAllowsPayload(this.method)) {
  5040. return this.transferParamsToUrl();
  5041. }
  5042. };
  5043. Request.prototype.extractHashFromUrl = function() {
  5044. var urlParts;
  5045. urlParts = u.parseUrl(this.url);
  5046. this.hash = u.presence(urlParts.hash);
  5047. return this.url = u.normalizeUrl(urlParts, {
  5048. hash: false
  5049. });
  5050. };
  5051. Request.prototype.transferParamsToUrl = function() {
  5052. if (!u.isBlank(this.params)) {
  5053. this.url = this.params.toURL(this.url);
  5054. return this.params.clear();
  5055. }
  5056. };
  5057. Request.prototype.transferSearchToParams = function() {
  5058. var paramsFromQuery;
  5059. paramsFromQuery = up.Params.fromURL(this.url);
  5060. if (!u.isBlank(paramsFromQuery)) {
  5061. this.params.addAll(paramsFromQuery);
  5062. return this.url = u.normalizeUrl(this.url, {
  5063. search: false
  5064. });
  5065. }
  5066. };
  5067. Request.prototype.isSafe = function() {
  5068. return up.proxy.isSafeMethod(this.method);
  5069. };
  5070. Request.prototype.send = function() {
  5071. return new Promise((function(_this) {
  5072. return function(resolve, reject) {
  5073. var csrfToken, header, pc, resolveWithResponse, value, xhr, xhrHeaders, xhrMethod, xhrParams, xhrPayload, xhrUrl;
  5074. xhr = new XMLHttpRequest();
  5075. xhrHeaders = u.copy(_this.headers);
  5076. xhrUrl = _this.url;
  5077. xhrParams = u.copy(_this.params);
  5078. xhrMethod = up.proxy.wrapMethod(_this.method, xhrParams);
  5079. xhrPayload = null;
  5080. if (!u.isBlank(xhrParams)) {
  5081. delete xhrHeaders['Content-Type'];
  5082. xhrPayload = xhrParams.toFormData();
  5083. }
  5084. pc = up.protocol.config;
  5085. if (_this.target) {
  5086. xhrHeaders[pc.targetHeader] = _this.target;
  5087. }
  5088. if (_this.failTarget) {
  5089. xhrHeaders[pc.failTargetHeader] = _this.failTarget;
  5090. }
  5091. if (!_this.isCrossDomain()) {
  5092. xhrHeaders['X-Requested-With'] || (xhrHeaders['X-Requested-With'] = 'XMLHttpRequest');
  5093. }
  5094. if (csrfToken = _this.csrfToken()) {
  5095. xhrHeaders[pc.csrfHeader] = csrfToken;
  5096. }
  5097. xhr.open(xhrMethod, xhrUrl);
  5098. for (header in xhrHeaders) {
  5099. value = xhrHeaders[header];
  5100. xhr.setRequestHeader(header, value);
  5101. }
  5102. resolveWithResponse = function() {
  5103. var response;
  5104. response = _this.buildResponse(xhr);
  5105. if (response.isSuccess()) {
  5106. return resolve(response);
  5107. } else {
  5108. return reject(response);
  5109. }
  5110. };
  5111. xhr.onload = resolveWithResponse;
  5112. xhr.onerror = resolveWithResponse;
  5113. xhr.ontimeout = resolveWithResponse;
  5114. if (_this.timeout) {
  5115. xhr.timeout = _this.timeout;
  5116. }
  5117. return xhr.send(xhrPayload);
  5118. };
  5119. })(this));
  5120. };
  5121. Request.prototype.navigate = function() {
  5122. var addField, csrfParam, csrfToken, form, formMethod;
  5123. this.transferSearchToParams();
  5124. form = e.affix(document.body, 'form.up-page-loader');
  5125. addField = function(attrs) {
  5126. return e.affix(form, 'input[type=hidden]', attrs);
  5127. };
  5128. if (this.method === 'GET') {
  5129. formMethod = 'GET';
  5130. } else {
  5131. addField({
  5132. name: up.protocol.config.methodParam,
  5133. value: this.method
  5134. });
  5135. formMethod = 'POST';
  5136. }
  5137. e.setAttrs(form, {
  5138. method: formMethod,
  5139. action: this.url
  5140. });
  5141. if ((csrfParam = up.protocol.csrfParam()) && (csrfToken = this.csrfToken())) {
  5142. addField({
  5143. name: csrfParam,
  5144. value: csrfToken
  5145. });
  5146. }
  5147. u.each(this.params.toArray(), addField);
  5148. e.hide(form);
  5149. return up.browser.submitForm(form);
  5150. };
  5151. Request.prototype.csrfToken = function() {
  5152. if (!this.isSafe() && !this.isCrossDomain()) {
  5153. return up.protocol.csrfToken();
  5154. }
  5155. };
  5156. Request.prototype.isCrossDomain = function() {
  5157. return u.isCrossDomain(this.url);
  5158. };
  5159. Request.prototype.buildResponse = function(xhr) {
  5160. var ref, responseAttrs, urlFromServer;
  5161. responseAttrs = {
  5162. method: this.method,
  5163. url: this.url,
  5164. text: xhr.responseText,
  5165. status: xhr.status,
  5166. request: this,
  5167. xhr: xhr
  5168. };
  5169. if (urlFromServer = up.protocol.locationFromXhr(xhr)) {
  5170. responseAttrs.url = urlFromServer;
  5171. responseAttrs.method = (ref = up.protocol.methodFromXhr(xhr)) != null ? ref : 'GET';
  5172. }
  5173. responseAttrs.title = up.protocol.titleFromXhr(xhr);
  5174. return new up.Response(responseAttrs);
  5175. };
  5176. Request.prototype.isCachable = function() {
  5177. return this.isSafe() && !u.isFormData(this.params);
  5178. };
  5179. Request.prototype.cacheKey = function() {
  5180. return [this.url, this.method, this.params.toQuery(), this.target].join('|');
  5181. };
  5182. Request.wrap = function(value) {
  5183. return u.wrapValue(value, this);
  5184. };
  5185. return Request;
  5186. })(up.Record);
  5187. }).call(this);
  5188. (function() {
  5189. var u,
  5190. bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
  5191. extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
  5192. hasProp = {}.hasOwnProperty;
  5193. u = up.util;
  5194. /***
  5195. Instances of `up.Response` describe the server response to an [`AJAX request`](/up.request).
  5196. \#\#\# Example
  5197. up.request('/foo').then(function(response) {
  5198. console.log(response.status) // 200
  5199. console.log(response.text) // "<html><body>..."
  5200. })
  5201. @class up.Response
  5202. */
  5203. up.Response = (function(superClass) {
  5204. extend(Response, superClass);
  5205. /***
  5206. The HTTP method used for the response.
  5207. This is usually the HTTP method used by the request.
  5208. However, after a redirect the server should signal a `GET` method using
  5209. an [`X-Up-Method: GET` header](/up.protocol#redirect-detection).
  5210. @property up.Response#method
  5211. @param {string} method
  5212. @stable
  5213. */
  5214. /***
  5215. The URL used for the response.
  5216. This is usually the requested URL.
  5217. However, after a redirect the server should signal a the new URL
  5218. using an [`X-Up-Location: /new-url` header](/up.protocol#redirect-detection).
  5219. @property up.Response#url
  5220. @param {string} method
  5221. @stable
  5222. */
  5223. /***
  5224. The response body as a `string`.
  5225. @property up.Response#text
  5226. @param {string} text
  5227. @stable
  5228. */
  5229. /***
  5230. The response's
  5231. [HTTP status code](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes)
  5232. as a `number`.
  5233. A successful response will usually have a `200` or `201' status code.
  5234. @property up.Response#status
  5235. @param {number} status
  5236. @stable
  5237. */
  5238. /***
  5239. The [request](/up.Request) that triggered this response.
  5240. @property up.Response#request
  5241. @param {up.Request} request
  5242. @experimental
  5243. */
  5244. /***
  5245. The [`XMLHttpRequest`](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest)
  5246. object that was used to create this response.
  5247. @property up.Response#xhr
  5248. @param {XMLHttpRequest} xhr
  5249. @experimental
  5250. */
  5251. /***
  5252. A [document title pushed by the server](/up.protocol#pushing-a-document-title-to-the-client).
  5253. If the server pushed no title via HTTP header, this will be `undefined`.
  5254. @property up.Response#title
  5255. @param {string} [title]
  5256. @stable
  5257. */
  5258. Response.prototype.fields = function() {
  5259. return ['method', 'url', 'text', 'status', 'request', 'xhr', 'title'];
  5260. };
  5261. /***
  5262. @constructor up.Response
  5263. @internal
  5264. */
  5265. function Response(options) {
  5266. this.getHeader = bind(this.getHeader, this);
  5267. this.isFatalError = bind(this.isFatalError, this);
  5268. this.isError = bind(this.isError, this);
  5269. this.isSuccess = bind(this.isSuccess, this);
  5270. Response.__super__.constructor.call(this, options);
  5271. }
  5272. /***
  5273. Returns whether the server responded with a 2xx HTTP status.
  5274. @function up.Response#isSuccess
  5275. @return {boolean}
  5276. @experimental
  5277. */
  5278. Response.prototype.isSuccess = function() {
  5279. return this.status && (this.status >= 200 && this.status <= 299);
  5280. };
  5281. /***
  5282. Returns whether the response was not [successful](/up.Request.prototype.isSuccess).
  5283. This also returns `true` when the request encountered a [fatal error](/up.Request.prototype.isFatalError)
  5284. like a timeout or loss of network connectivity.
  5285. @function up.Response#isError
  5286. @return {boolean}
  5287. @experimental
  5288. */
  5289. Response.prototype.isError = function() {
  5290. return !this.isSuccess();
  5291. };
  5292. /***
  5293. Returns whether the request encountered a [fatal error](/up.Request.prototype.isFatalError)
  5294. like a timeout or loss of network connectivity.
  5295. When the server produces an error message with an HTTP status like `500`,
  5296. this is not considered a fatal error and `false` is returned.
  5297. @function up.Response#isFatalError
  5298. @return {boolean}
  5299. @experimental
  5300. */
  5301. Response.prototype.isFatalError = function() {
  5302. return this.isError() && u.isBlank(this.text);
  5303. };
  5304. /***
  5305. Returns the HTTP header value with the given name.
  5306. The search for the header name is case-insensitive.
  5307. Returns `undefined` if the given header name was not included in the response.
  5308. @function up.Response#getHeader
  5309. @param {string} name
  5310. @return {string|undefined} value
  5311. @experimental
  5312. */
  5313. Response.prototype.getHeader = function(name) {
  5314. return this.xhr.getResponseHeader(name);
  5315. };
  5316. return Response;
  5317. })(up.Record);
  5318. }).call(this);
  5319. (function() {
  5320. var e;
  5321. e = up.element;
  5322. up.RevealMotion = (function() {
  5323. function RevealMotion(element, options) {
  5324. var layoutConfig, ref, ref1, ref2, ref3, ref4, ref5, ref6, ref7, ref8, ref9, snapDefault;
  5325. this.element = element;
  5326. if (options == null) {
  5327. options = {};
  5328. }
  5329. layoutConfig = up.viewport.config;
  5330. this.viewport = (ref = options.viewport) != null ? ref : up.viewport.closest(this.element);
  5331. up.legacy.fixKey(layoutConfig, 'snap', 'revealSnap');
  5332. snapDefault = layoutConfig.revealSnap;
  5333. this.snap = (ref1 = (ref2 = options.snap) != null ? ref2 : options.revealSnap) != null ? ref1 : snapDefault;
  5334. if (this.snap === false) {
  5335. this.snap = 0;
  5336. } else if (this.snap === true) {
  5337. this.snap = snapDefault;
  5338. }
  5339. this.padding = (ref3 = (ref4 = options.padding) != null ? ref4 : options.revealPadding) != null ? ref3 : layoutConfig.revealPadding;
  5340. this.top = options.top;
  5341. this.fixedTop = (ref5 = options.fixedTop) != null ? ref5 : layoutConfig.fixedTop;
  5342. this.fixedBottom = (ref6 = options.fixedBottom) != null ? ref6 : layoutConfig.fixedBottom;
  5343. this.speed = (ref7 = (ref8 = options.speed) != null ? ref8 : options.scrollSpeed) != null ? ref7 : layoutConfig.scrollSpeed;
  5344. this.behavior = (ref9 = options.behavior) != null ? ref9 : options.scrollBehavior;
  5345. }
  5346. RevealMotion.prototype.start = function() {
  5347. var diff, elementRect, newScrollTop, originalScrollTop, viewportRect;
  5348. elementRect = up.Rect.fromElement(this.element);
  5349. viewportRect = this.getViewportRect(this.viewport);
  5350. this.addPadding(elementRect);
  5351. this.substractObstructions(viewportRect);
  5352. if (viewportRect.height <= 0) {
  5353. return Promise.reject(new Error('Viewport has no visible area'));
  5354. }
  5355. originalScrollTop = this.viewport.scrollTop;
  5356. newScrollTop = originalScrollTop;
  5357. if (this.top || elementRect.height > viewportRect.height) {
  5358. diff = elementRect.top - viewportRect.top;
  5359. newScrollTop += diff;
  5360. } else if (elementRect.top < viewportRect.top) {
  5361. newScrollTop -= viewportRect.top - elementRect.top;
  5362. } else if (elementRect.bottom > viewportRect.bottom) {
  5363. newScrollTop += elementRect.bottom - viewportRect.bottom;
  5364. } else {
  5365. }
  5366. if (newScrollTop < this.snap && elementRect.top < (0.5 * viewportRect.height)) {
  5367. newScrollTop = 0;
  5368. }
  5369. if (newScrollTop !== originalScrollTop) {
  5370. return this.scrollTo(newScrollTop);
  5371. } else {
  5372. return Promise.resolve();
  5373. }
  5374. };
  5375. RevealMotion.prototype.scrollTo = function(newScrollTop) {
  5376. var scrollOptions;
  5377. scrollOptions = {
  5378. speed: this.speed,
  5379. behavior: this.behavior
  5380. };
  5381. this.scrollMotion = new up.ScrollMotion(this.viewport, newScrollTop, scrollOptions);
  5382. return this.scrollMotion.start();
  5383. };
  5384. RevealMotion.prototype.getViewportRect = function() {
  5385. if (up.viewport.isRoot(this.viewport)) {
  5386. return new up.Rect({
  5387. left: 0,
  5388. top: 0,
  5389. width: up.viewport.rootWidth(),
  5390. height: up.viewport.rootHeight()
  5391. });
  5392. } else {
  5393. return up.Rect.fromElement(this.viewport);
  5394. }
  5395. };
  5396. RevealMotion.prototype.addPadding = function(elementRect) {
  5397. elementRect.top -= this.padding;
  5398. return elementRect.height += 2 * this.padding;
  5399. };
  5400. RevealMotion.prototype.substractObstructions = function(viewportRect) {
  5401. var diff, i, j, len, len1, obstruction, obstructionRect, ref, ref1, results;
  5402. ref = e.list.apply(e, this.fixedTop);
  5403. for (i = 0, len = ref.length; i < len; i++) {
  5404. obstruction = ref[i];
  5405. obstructionRect = up.Rect.fromElement(obstruction);
  5406. diff = obstructionRect.bottom - viewportRect.top;
  5407. if (diff > 0) {
  5408. viewportRect.top += diff;
  5409. viewportRect.height -= diff;
  5410. }
  5411. }
  5412. ref1 = e.list.apply(e, this.fixedBottom);
  5413. results = [];
  5414. for (j = 0, len1 = ref1.length; j < len1; j++) {
  5415. obstruction = ref1[j];
  5416. obstructionRect = up.Rect.fromElement(obstruction);
  5417. diff = viewportRect.bottom - obstructionRect.top;
  5418. if (diff > 0) {
  5419. results.push(viewportRect.height -= diff);
  5420. } else {
  5421. results.push(void 0);
  5422. }
  5423. }
  5424. return results;
  5425. };
  5426. RevealMotion.prototype.finish = function() {
  5427. var ref;
  5428. return (ref = this.scrollMotion) != null ? ref.finish() : void 0;
  5429. };
  5430. return RevealMotion;
  5431. })();
  5432. }).call(this);
  5433. (function() {
  5434. var u,
  5435. bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
  5436. u = up.util;
  5437. up.ScrollMotion = (function() {
  5438. var SPEED_CALIBRATION;
  5439. SPEED_CALIBRATION = 0.065;
  5440. function ScrollMotion(scrollable, targetTop, options) {
  5441. var ref, ref1, ref2, ref3;
  5442. this.scrollable = scrollable;
  5443. this.targetTop = targetTop;
  5444. if (options == null) {
  5445. options = {};
  5446. }
  5447. this.finish = bind(this.finish, this);
  5448. this.cancel = bind(this.cancel, this);
  5449. this.animationFrame = bind(this.animationFrame, this);
  5450. this.start = bind(this.start, this);
  5451. this.behavior = (ref = (ref1 = options.behavior) != null ? ref1 : options.scrollBehavior) != null ? ref : 'auto';
  5452. this.speed = ((ref2 = (ref3 = options.speed) != null ? ref3 : options.scrollSpeed) != null ? ref2 : up.viewport.config.scrollSpeed) * SPEED_CALIBRATION;
  5453. }
  5454. ScrollMotion.prototype.start = function() {
  5455. return new Promise((function(_this) {
  5456. return function(resolve, reject) {
  5457. _this.resolve = resolve;
  5458. _this.reject = reject;
  5459. if (_this.behavior === 'smooth' && up.motion.isEnabled()) {
  5460. return _this.startAnimation();
  5461. } else {
  5462. return _this.finish();
  5463. }
  5464. };
  5465. })(this));
  5466. };
  5467. ScrollMotion.prototype.startAnimation = function() {
  5468. this.startTime = Date.now();
  5469. this.startTop = this.scrollable.scrollTop;
  5470. this.topDiff = this.targetTop - this.startTop;
  5471. this.duration = Math.sqrt(Math.abs(this.topDiff)) / this.speed;
  5472. return requestAnimationFrame(this.animationFrame);
  5473. };
  5474. ScrollMotion.prototype.animationFrame = function() {
  5475. var currentTime, timeElapsed, timeFraction;
  5476. if (this.settled) {
  5477. return;
  5478. }
  5479. if (this.frameTop && Math.abs(this.frameTop - this.scrollable.scrollTop) > 1.5) {
  5480. this.cancel('Animation aborted due to user intervention');
  5481. }
  5482. currentTime = Date.now();
  5483. timeElapsed = currentTime - this.startTime;
  5484. timeFraction = Math.min(timeElapsed / this.duration, 1);
  5485. this.frameTop = this.startTop + (u.simpleEase(timeFraction) * this.topDiff);
  5486. if (Math.abs(this.targetTop - this.frameTop) < 0.3) {
  5487. return this.finish();
  5488. } else {
  5489. this.scrollable.scrollTop = this.frameTop;
  5490. return requestAnimationFrame(this.animationFrame);
  5491. }
  5492. };
  5493. ScrollMotion.prototype.cancel = function(reason) {
  5494. this.settled = true;
  5495. return this.reject(new Error(reason));
  5496. };
  5497. ScrollMotion.prototype.finish = function() {
  5498. this.settled = true;
  5499. this.scrollable.scrollTop = this.targetTop;
  5500. return this.resolve();
  5501. };
  5502. return ScrollMotion;
  5503. })();
  5504. }).call(this);
  5505. (function() {
  5506. var u,
  5507. bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
  5508. up.store || (up.store = {});
  5509. u = up.util;
  5510. up.store.Memory = (function() {
  5511. function Memory() {
  5512. this.values = bind(this.values, this);
  5513. this.keys = bind(this.keys, this);
  5514. this.remove = bind(this.remove, this);
  5515. this.set = bind(this.set, this);
  5516. this.get = bind(this.get, this);
  5517. this.clear = bind(this.clear, this);
  5518. this.clear();
  5519. }
  5520. Memory.prototype.clear = function() {
  5521. return this.data = {};
  5522. };
  5523. Memory.prototype.get = function(key) {
  5524. return this.data[key];
  5525. };
  5526. Memory.prototype.set = function(key, value) {
  5527. return this.data[key] = value;
  5528. };
  5529. Memory.prototype.remove = function(key) {
  5530. return delete this.data[key];
  5531. };
  5532. Memory.prototype.keys = function() {
  5533. return Object.keys(this.data);
  5534. };
  5535. Memory.prototype.values = function() {
  5536. return u.values(this.data);
  5537. };
  5538. return Memory;
  5539. })();
  5540. }).call(this);
  5541. (function() {
  5542. var u,
  5543. bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
  5544. extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
  5545. hasProp = {}.hasOwnProperty;
  5546. u = up.util;
  5547. up.store.Session = (function(superClass) {
  5548. extend(Session, superClass);
  5549. function Session(rootKey) {
  5550. this.saveToSessionStorage = bind(this.saveToSessionStorage, this);
  5551. this.loadFromSessionStorage = bind(this.loadFromSessionStorage, this);
  5552. this.remove = bind(this.remove, this);
  5553. this.set = bind(this.set, this);
  5554. this.clear = bind(this.clear, this);
  5555. this.rootKey = rootKey;
  5556. this.loadFromSessionStorage();
  5557. }
  5558. Session.prototype.clear = function() {
  5559. Session.__super__.clear.call(this);
  5560. return this.saveToSessionStorage();
  5561. };
  5562. Session.prototype.set = function(key, value) {
  5563. Session.__super__.set.call(this, key, value);
  5564. return this.saveToSessionStorage();
  5565. };
  5566. Session.prototype.remove = function(key) {
  5567. Session.__super__.remove.call(this, key);
  5568. return this.saveToSessionStorage();
  5569. };
  5570. Session.prototype.loadFromSessionStorage = function() {
  5571. var raw;
  5572. try {
  5573. if (raw = typeof sessionStorage !== "undefined" && sessionStorage !== null ? sessionStorage.getItem(this.rootKey) : void 0) {
  5574. this.data = JSON.parse(raw);
  5575. }
  5576. } catch (error) {
  5577. }
  5578. return this.data || (this.data = {});
  5579. };
  5580. Session.prototype.saveToSessionStorage = function() {
  5581. var json;
  5582. json = JSON.stringify(this.data);
  5583. try {
  5584. return typeof sessionStorage !== "undefined" && sessionStorage !== null ? sessionStorage.setItem(this.rootKey, json) : void 0;
  5585. } catch (error) {
  5586. }
  5587. };
  5588. return Session;
  5589. })(up.store.Memory);
  5590. }).call(this);
  5591. (function() {
  5592. var e, u,
  5593. bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
  5594. u = up.util;
  5595. e = up.element;
  5596. up.Tether = (function() {
  5597. function Tether(options) {
  5598. this.sync = bind(this.sync, this);
  5599. this.scheduleSync = bind(this.scheduleSync, this);
  5600. var ref;
  5601. this.anchor = options.anchor;
  5602. ref = options.position.split('-'), this.position = ref[0], this.align = ref[1];
  5603. if (this.align) {
  5604. up.legacy.warn('The position value %o is deprecated. Use %o instead.', options.position, this.describeConstraints());
  5605. } else {
  5606. this.align = options.align;
  5607. }
  5608. this.alignAxis = this.position === 'top' || this.position === 'bottom' ? 'horizontal' : 'vertical';
  5609. this.viewport = up.viewport.closest(this.anchor);
  5610. this.parent = this.viewport === e.root() ? document.body : this.viewport;
  5611. this.syncOnScroll = !this.viewport.contains(this.anchor.offsetParent);
  5612. this.root = e.affix(this.parent, '.up-bounds');
  5613. this.setBoundsOffset(0, 0);
  5614. this.changeEventSubscription('on');
  5615. }
  5616. Tether.prototype.destroy = function() {
  5617. e.remove(this.root);
  5618. return this.changeEventSubscription('off');
  5619. };
  5620. Tether.prototype.changeEventSubscription = function(fn) {
  5621. up[fn](window, 'resize', this.scheduleSync);
  5622. if (this.syncOnScroll) {
  5623. return up[fn](this.viewport, 'scroll', this.scheduleSync);
  5624. }
  5625. };
  5626. Tether.prototype.scheduleSync = function() {
  5627. clearTimeout(this.syncTimer);
  5628. return this.syncTimer = u.task(this.sync);
  5629. };
  5630. Tether.prototype.sync = function() {
  5631. var anchorBox, left, rootBox, top;
  5632. rootBox = this.root.getBoundingClientRect();
  5633. anchorBox = this.anchor.getBoundingClientRect();
  5634. left = void 0;
  5635. top = void 0;
  5636. switch (this.alignAxis) {
  5637. case 'horizontal':
  5638. top = (function() {
  5639. switch (this.position) {
  5640. case 'top':
  5641. return anchorBox.top - rootBox.height;
  5642. case 'bottom':
  5643. return anchorBox.top + anchorBox.height;
  5644. }
  5645. }).call(this);
  5646. left = (function() {
  5647. switch (this.align) {
  5648. case 'left':
  5649. return anchorBox.left;
  5650. case 'center':
  5651. return anchorBox.left + 0.5 * (anchorBox.width - rootBox.width);
  5652. case 'right':
  5653. return anchorBox.left + anchorBox.width - rootBox.width;
  5654. }
  5655. }).call(this);
  5656. break;
  5657. case 'vertical':
  5658. top = (function() {
  5659. switch (this.align) {
  5660. case 'top':
  5661. return anchorBox.top;
  5662. case 'center':
  5663. return anchorBox.top + 0.5 * (anchorBox.height - rootBox.height);
  5664. case 'bottom':
  5665. return anchorBox.top + anchorBox.height - rootBox.height;
  5666. }
  5667. }).call(this);
  5668. left = (function() {
  5669. switch (this.position) {
  5670. case 'left':
  5671. return anchorBox.left - rootBox.width;
  5672. case 'right':
  5673. return anchorBox.left + anchorBox.width;
  5674. }
  5675. }).call(this);
  5676. }
  5677. if (u.isDefined(left) || u.isDefined(top)) {
  5678. return this.moveTo(left, top);
  5679. } else {
  5680. return up.fail('Invalid tether constraints: %o', this.describeConstraints());
  5681. }
  5682. };
  5683. Tether.prototype.describeConstraints = function() {
  5684. return {
  5685. position: this.position,
  5686. align: this.align
  5687. };
  5688. };
  5689. Tether.prototype.moveTo = function(targetLeft, targetTop) {
  5690. var rootBox;
  5691. rootBox = this.root.getBoundingClientRect();
  5692. return this.setBoundsOffset(targetLeft - rootBox.left + this.offsetLeft, targetTop - rootBox.top + this.offsetTop);
  5693. };
  5694. Tether.prototype.setBoundsOffset = function(left, top) {
  5695. this.offsetLeft = left;
  5696. this.offsetTop = top;
  5697. return e.setStyle(this.root, {
  5698. left: left,
  5699. top: top
  5700. });
  5701. };
  5702. return Tether;
  5703. })();
  5704. }).call(this);
  5705. (function() {
  5706. var u,
  5707. bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
  5708. u = up.util;
  5709. up.UrlSet = (function() {
  5710. function UrlSet(urls, options) {
  5711. this.urls = urls;
  5712. if (options == null) {
  5713. options = {};
  5714. }
  5715. this["" + u.isEqual.key] = bind(this["" + u.isEqual.key], this);
  5716. this.matchesAny = bind(this.matchesAny, this);
  5717. this.doesMatchPattern = bind(this.doesMatchPattern, this);
  5718. this.doesMatchFully = bind(this.doesMatchFully, this);
  5719. this.matches = bind(this.matches, this);
  5720. this.normalizeUrl = options.normalizeUrl || u.normalizeUrl;
  5721. this.urls = u.map(this.urls, this.normalizeUrl);
  5722. this.urls = u.compact(this.urls);
  5723. }
  5724. UrlSet.prototype.matches = function(testUrl) {
  5725. if (testUrl.indexOf('*') >= 0) {
  5726. return this.doesMatchPattern(testUrl);
  5727. } else {
  5728. return this.doesMatchFully(testUrl);
  5729. }
  5730. };
  5731. UrlSet.prototype.doesMatchFully = function(testUrl) {
  5732. return u.contains(this.urls, testUrl);
  5733. };
  5734. UrlSet.prototype.doesMatchPattern = function(pattern) {
  5735. var placeholder;
  5736. placeholder = "__ASTERISK__";
  5737. pattern = pattern.replace(/\*/g, placeholder);
  5738. pattern = u.escapeRegexp(pattern);
  5739. pattern = pattern.replace(new RegExp(placeholder, 'g'), '.*?');
  5740. pattern = new RegExp('^' + pattern + '$');
  5741. return u.find(this.urls, function(url) {
  5742. return pattern.test(url);
  5743. });
  5744. };
  5745. UrlSet.prototype.matchesAny = function(testUrls) {
  5746. return u.find(testUrls, this.matches);
  5747. };
  5748. UrlSet.prototype["" + u.isEqual.key] = function(otherSet) {
  5749. return u.isEqual(this.urls, otherSet != null ? otherSet.urls : void 0);
  5750. };
  5751. return UrlSet;
  5752. })();
  5753. }).call(this);
  5754. /***
  5755. @module up.framework
  5756. */
  5757. (function() {
  5758. up.framework = (function() {
  5759. var boot, emitReset, isBooting, u;
  5760. u = up.util;
  5761. isBooting = true;
  5762. /***
  5763. Resets Unpoly to the state when it was booted.
  5764. All custom event handlers, animations, etc. that have been registered
  5765. will be discarded.
  5766. Emits event [`up:framework:reset`](/up:framework:reset).
  5767. @function up.framework.reset
  5768. @internal
  5769. */
  5770. emitReset = function() {
  5771. return up.emit('up:framework:reset', {
  5772. log: 'Resetting framework'
  5773. });
  5774. };
  5775. /***
  5776. This event is [emitted](/up.emit) when Unpoly is [reset](/up.framework.reset) during unit tests.
  5777. @event up:framework:reset
  5778. @internal
  5779. */
  5780. /***
  5781. Boots the Unpoly framework.
  5782. **This is called automatically** by including the Unpoly JavaScript files.
  5783. Unpoly will not boot if the current browser is [not supported](/up.browser.isSupported).
  5784. This leaves you with a classic server-side application on legacy browsers.
  5785. @function up.boot
  5786. @internal
  5787. */
  5788. boot = function() {
  5789. if (up.browser.isSupported()) {
  5790. up.emit('up:framework:booted', {
  5791. log: 'Framework booted'
  5792. });
  5793. isBooting = false;
  5794. return up.event.onReady(function() {
  5795. return u.task(function() {
  5796. up.emit('up:app:boot', {
  5797. log: 'Booting user application'
  5798. });
  5799. return up.emit('up:app:booted', {
  5800. log: 'User application booted'
  5801. });
  5802. });
  5803. });
  5804. } else {
  5805. return typeof console.log === "function" ? console.log("Unpoly doesn't support this browser. Framework was not booted.") : void 0;
  5806. }
  5807. };
  5808. return {
  5809. reset: emitReset,
  5810. boot: boot,
  5811. isBooting: function() {
  5812. return isBooting;
  5813. }
  5814. };
  5815. })();
  5816. }).call(this);
  5817. /***
  5818. Events
  5819. ======
  5820. Most Unpoly interactions emit DOM events that are prefixed with `up:`.
  5821. document.addEventListener('up:modal:opened', (event) => {
  5822. console.log('A new modal has just opened!')
  5823. })
  5824. Events often have both present and past forms. For example,
  5825. `up:modal:open` is emitted before a modal starts to open.
  5826. `up:modal:opened` is emitted when the modal has finished its
  5827. opening animation.
  5828. \#\#\# Preventing events
  5829. You can prevent most present form events by calling `preventDefault()`:
  5830. document.addEventListener('up:modal:open', (event) => {
  5831. if (event.url == '/evil') {
  5832. // Prevent the modal from opening
  5833. event.preventDefault()
  5834. }
  5835. })
  5836. \#\#\# A better way to bind event listeners
  5837. Instead of using [`Element#addEventListener()`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener),
  5838. you may find it convenient to use [`up.on()`](/up.on) instead:
  5839. up.on('click', 'button', function(event, button, data) {
  5840. // button is the clicked element
  5841. // data is the parsed [`up-data`](/up-data) attribute
  5842. })
  5843. There are some advantages to using `up.on()`:
  5844. - You may pass a selector for [event delegation](https://davidwalsh.name/event-delegate).
  5845. - The event target is automatically passed as a second argument.
  5846. - You may register a listener to multiple events by passing a space-separated list of event name (e.g. `"click mousedown"`).
  5847. - You may register a listener to multiple elements in a single `up.on()` call, by passing a [list](/up.util.isList) of elements.
  5848. - You may use an [`[up-data]`](/up-data) attribute to [attach structured data](/up.on#attaching-structured-data)
  5849. to observed elements. If an `[up-data]` attribute is set, its value will automatically be
  5850. parsed as JSON and passed as a third argument.
  5851. - Event listeners on [unsupported browsers](/up.browser.isSupported) are silently discarded,
  5852. leaving you with an application without JavaScript. This is typically preferable to
  5853. a soup of randomly broken JavaScript in ancient browsers.
  5854. @module up.event
  5855. */
  5856. (function() {
  5857. var slice = [].slice;
  5858. up.event = (function() {
  5859. var $bind, bind, bindNow, buildEvent, consumeAction, e, emit, halt, logEmission, nobodyPrevents, onEscape, onReady, reset, u, unbind, whenEmitted;
  5860. u = up.util;
  5861. e = up.element;
  5862. reset = function() {
  5863. var element, i, len, ref, results;
  5864. ref = [window, document, document.documentElement, document.body];
  5865. results = [];
  5866. for (i = 0, len = ref.length; i < len; i++) {
  5867. element = ref[i];
  5868. results.push(up.EventListener.unbindNonDefault(element));
  5869. }
  5870. return results;
  5871. };
  5872. /***
  5873. Listens to a [DOM event](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Events)
  5874. on `document` or a given element.
  5875. `up.on()` has some quality of life improvements over
  5876. [`Element#addEventListener()`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener):
  5877. - You may pass a selector for [event delegation](https://davidwalsh.name/event-delegate).
  5878. - The event target is automatically passed as a second argument.
  5879. - You may register a listener to multiple events by passing a space-separated list of event name (e.g. `"click mousedown"`)
  5880. - You may register a listener to multiple elements in a single `up.on()` call, by passing a [list](/up.util.isList) of elements.
  5881. - You use an [`[up-data]`](/up-data) attribute to [attach structured data](/up.on#attaching-structured-data)
  5882. to observed elements. If an `[up-data]` attribute is set, its value will automatically be
  5883. parsed as JSON and passed as a third argument.
  5884. - Event listeners on [unsupported browsers](/up.browser.isSupported) are silently discarded,
  5885. leaving you with an application without JavaScript. This is typically preferable to
  5886. a soup of randomly broken JavaScript in ancient browsers.
  5887. \#\#\# Examples
  5888. The code below will call the listener when a `<a>` is clicked
  5889. anywhere in the `document`:
  5890. up.on('click', 'a', function(event, element) {
  5891. console.log("Click on a link %o", element)
  5892. })
  5893. You may also bind the listener to a given element instead of `document`:
  5894. var form = document.querySelector('form')
  5895. up.on(form, 'click', function(event, form) {
  5896. console.log("Click within %o", form)
  5897. })
  5898. You may also pass both an element and a selector
  5899. for [event delegation](https://davidwalsh.name/event-delegate):
  5900. var form = document.querySelector('form')
  5901. up.on(form, 'click', 'a', function(event, link) {
  5902. console.log("Click on a link %o within %o", link, form)
  5903. })
  5904. \#\#\# Attaching structured data
  5905. In case you want to attach structured data to the event you're observing,
  5906. you can serialize the data to JSON and put it into an `[up-data]` attribute:
  5907. <span class='person' up-data='{ "age": 18, "name": "Bob" }'>Bob</span>
  5908. <span class='person' up-data='{ "age": 22, "name": "Jim" }'>Jim</span>
  5909. The JSON will be parsed and handed to your event handler as a third argument:
  5910. up.on('click', '.person', function(event, element, data) {
  5911. console.log("This is %o who is %o years old", data.name, data.age)
  5912. })
  5913. \#\#\# Unbinding an event listener
  5914. `up.on()` returns a function that unbinds the event listeners when called:
  5915. // Define the listener
  5916. var listener = function(event) { ... }
  5917. // Binding the listener returns an unbind function
  5918. var unbind = up.on('click', listener)
  5919. // Unbind the listener
  5920. unbind()
  5921. There is also a function [`up.off()`](/up.off) which you can use for the same purpose:
  5922. // Define the listener
  5923. var listener = function(event) { ... }
  5924. // Bind the listener
  5925. up.on('click', listener)
  5926. // Unbind the listener
  5927. up.off('click', listener)
  5928. @function up.on
  5929. @param {Element|jQuery} [element=document]
  5930. The element on which to register the event listener.
  5931. If no element is given, the listener is registered on the `document`.
  5932. @param {string} events
  5933. A space-separated list of event names to bind to.
  5934. @param {string} [selector]
  5935. The selector of an element on which the event must be triggered.
  5936. Omit the selector to listen to all events with that name, regardless
  5937. of the event target.
  5938. @param {Function(event, [element], [data])} listener
  5939. The listener function that should be called.
  5940. The function takes the affected element as the first argument).
  5941. If the element has an [`up-data`](/up-data) attribute, its value is parsed as JSON
  5942. and passed as a second argument.
  5943. @return {Function()}
  5944. A function that unbinds the event listeners when called.
  5945. @stable
  5946. */
  5947. bind = function() {
  5948. var args;
  5949. args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
  5950. return bindNow(args);
  5951. };
  5952. /***
  5953. Listens to an event on `document` or a given element.
  5954. The event handler is called with the event target as a
  5955. [jQuery collection](https://learn.jquery.com/using-jquery-core/jquery-object/).
  5956. If you're not using jQuery, use `up.on()` instead, which calls
  5957. event handlers with a native element.
  5958. \#\#\# Example
  5959. ```
  5960. up.$on('click', 'a', function(event, $link) {
  5961. console.log("Click on a link with destination %s", $element.attr('href'))
  5962. })
  5963. ```
  5964. @function up.$on
  5965. @param {Element|jQuery} [element=document]
  5966. The element on which to register the event listener.
  5967. If no element is given, the listener is registered on the `document`.
  5968. @param {string} events
  5969. A space-separated list of event names to bind to.
  5970. @param {string} [selector]
  5971. The selector of an element on which the event must be triggered.
  5972. Omit the selector to listen to all events with that name, regardless
  5973. of the event target.
  5974. @param {Function(event, [element], [data])} listener
  5975. The listener function that should be called.
  5976. The function takes the affected element as the first argument).
  5977. If the element has an [`up-data`](/up-data) attribute, its value is parsed as JSON
  5978. and passed as a second argument.
  5979. @return {Function()}
  5980. A function that unbinds the event listeners when called.
  5981. @stable
  5982. */
  5983. $bind = function() {
  5984. var args;
  5985. args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
  5986. return bindNow(args, {
  5987. jQuery: true
  5988. });
  5989. };
  5990. bindNow = function(args, options) {
  5991. if (!up.browser.isSupported()) {
  5992. return (function() {});
  5993. }
  5994. return up.EventListener.bind(args, options);
  5995. };
  5996. /***
  5997. Unbinds an event listener previously bound with [`up.on()`](/up.on).
  5998. \#\#\# Example
  5999. Let's say you are listing to clicks on `.button` elements:
  6000. var listener = function() { ... }
  6001. up.on('click', '.button', listener)
  6002. You can stop listening to these events like this:
  6003. up.off('click', '.button', listener)
  6004. Note that you need to pass `up.off()` a reference to the same listener function
  6005. that was passed to `up.on()` earlier.
  6006. @function up.off
  6007. @stable
  6008. */
  6009. unbind = function() {
  6010. var args;
  6011. args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
  6012. return up.EventListener.unbind(args);
  6013. };
  6014. /***
  6015. Emits a event with the given name and properties.
  6016. The event will be triggered as an event on `document` or on the given element.
  6017. Other code can subscribe to events with that name using
  6018. [`Element#addEventListener()`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener)
  6019. or [`up.on()`](/up.on).
  6020. \#\#\# Example
  6021. up.on('my:event', function(event) {
  6022. console.log(event.foo)
  6023. })
  6024. up.emit('my:event', { foo: 'bar' })
  6025. // Prints "bar" to the console
  6026. @function up.emit
  6027. @param {Element|jQuery} [target=document]
  6028. The element on which the event is triggered.
  6029. If omitted, the event will be emitted on the `document`.
  6030. @param {string} eventName
  6031. The name of the event.
  6032. @param {Object} [eventProps={}]
  6033. A list of properties to become part of the event object
  6034. that will be passed to listeners. Note that the event object
  6035. will by default include properties like `preventDefault()`
  6036. or `stopPropagation()`.
  6037. @param {string|Array} [eventProps.log=false]
  6038. A message to print to the console when the event is emitted.
  6039. Pass `true` to print a default message
  6040. @param {Element|jQuery} [eventProps.target=document]
  6041. The element on which the event is triggered.
  6042. @stable
  6043. */
  6044. emit = function() {
  6045. var args, event, eventName, eventProps, target, targetFromProps;
  6046. args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
  6047. if (args[0].addEventListener) {
  6048. target = args.shift();
  6049. } else if (u.isJQuery(args[0])) {
  6050. target = e.get(args.shift());
  6051. }
  6052. eventName = args[0];
  6053. eventProps = args[1] || {};
  6054. if (targetFromProps = u.pluckKey(eventProps, 'target')) {
  6055. target = targetFromProps;
  6056. }
  6057. if (target == null) {
  6058. target = document;
  6059. }
  6060. logEmission(eventName, eventProps);
  6061. event = buildEvent(eventName, eventProps);
  6062. target.dispatchEvent(event);
  6063. return event;
  6064. };
  6065. buildEvent = function(name, props) {
  6066. var event;
  6067. event = document.createEvent('Event');
  6068. event.initEvent(name, true, true);
  6069. u.assign(event, props);
  6070. if (up.browser.isIE11()) {
  6071. event.preventDefault = function() {
  6072. return Object.defineProperty(event, 'defaultPrevented', {
  6073. get: function() {
  6074. return true;
  6075. }
  6076. });
  6077. };
  6078. }
  6079. return event;
  6080. };
  6081. logEmission = function(eventName, eventProps) {
  6082. var message, messageArgs, ref;
  6083. if (!up.log.isEnabled()) {
  6084. return;
  6085. }
  6086. message = u.pluckKey(eventProps, 'log');
  6087. if (u.isArray(message)) {
  6088. ref = message, message = ref[0], messageArgs = 2 <= ref.length ? slice.call(ref, 1) : [];
  6089. } else {
  6090. messageArgs = [];
  6091. }
  6092. if (u.isString(message)) {
  6093. if (u.isPresent(eventProps)) {
  6094. return up.puts.apply(up, [message + " (%s (%o))"].concat(slice.call(messageArgs), [eventName], [eventProps]));
  6095. } else {
  6096. return up.puts.apply(up, [message + " (%s)"].concat(slice.call(messageArgs), [eventName]));
  6097. }
  6098. } else if (message === true) {
  6099. if (u.isPresent(eventProps)) {
  6100. return up.puts('Event %s (%o)', eventName, eventProps);
  6101. } else {
  6102. return up.puts('Event %s', eventName);
  6103. }
  6104. }
  6105. };
  6106. /***
  6107. [Emits an event](/up.emit) and returns whether no listener
  6108. has prevented the default action.
  6109. @function up.event.nobodyPrevents
  6110. @param {string} eventName
  6111. @param {Object} eventProps
  6112. @param {string|Array} [eventProps.log]
  6113. @return {boolean}
  6114. whether no listener has prevented the default action
  6115. @experimental
  6116. */
  6117. nobodyPrevents = function() {
  6118. var args, event;
  6119. args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
  6120. event = emit.apply(null, args);
  6121. return !event.defaultPrevented;
  6122. };
  6123. /***
  6124. [Emits](/up.emit) the given event and returns a promise
  6125. that will be fulfilled if no listener has prevented the default action.
  6126. If any listener prevented the default listener
  6127. the returned promise will never be resolved.
  6128. @function up.event.whenEmitted
  6129. @param {string} eventName
  6130. @param {Object} eventProps
  6131. @param {string|Array} [eventProps.message]
  6132. @return {Promise}
  6133. @internal
  6134. */
  6135. whenEmitted = function() {
  6136. var args;
  6137. args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
  6138. return new Promise(function(resolve, reject) {
  6139. if (nobodyPrevents.apply(null, args)) {
  6140. return resolve();
  6141. } else {
  6142. return reject(new Error("Event " + args[0] + " was prevented"));
  6143. }
  6144. });
  6145. };
  6146. /***
  6147. Registers an event listener to be called when the user
  6148. presses the `Escape` key.
  6149. @function up.event.onEscape
  6150. @param {Function(event)} listener
  6151. The listener function to register.
  6152. @return {Function()}
  6153. A function that unbinds the event listeners when called.
  6154. @experimental
  6155. */
  6156. onEscape = function(listener) {
  6157. return bind('keydown', 'body', function(event) {
  6158. if (u.escapePressed(event)) {
  6159. return listener(event);
  6160. }
  6161. });
  6162. };
  6163. /***
  6164. Prevents the event from bubbling up the DOM.
  6165. Also prevents other event handlers bound on the same element.
  6166. Also prevents the event's default action.
  6167. \#\#\# Example
  6168. up.on('click', 'link.disabled', function(event) {
  6169. up.event.halt(event)
  6170. })
  6171. @function up.event.halt
  6172. @param {Event} event
  6173. @experimental
  6174. */
  6175. halt = function(event) {
  6176. event.stopImmediatePropagation();
  6177. return event.preventDefault();
  6178. };
  6179. /***
  6180. @function up.event.consumeAction
  6181. @internal
  6182. */
  6183. consumeAction = function(event) {
  6184. halt(event);
  6185. if (event.type !== 'up:action:consumed') {
  6186. return emit(event.target, 'up:action:consumed', {
  6187. log: false
  6188. });
  6189. }
  6190. };
  6191. onReady = function(callback) {
  6192. if (document.readyState !== 'loading') {
  6193. return callback();
  6194. } else {
  6195. return document.addEventListener('DOMContentLoaded', callback);
  6196. }
  6197. };
  6198. bind('up:framework:reset', reset);
  6199. return {
  6200. on: bind,
  6201. $on: $bind,
  6202. off: unbind,
  6203. emit: emit,
  6204. nobodyPrevents: nobodyPrevents,
  6205. whenEmitted: whenEmitted,
  6206. onEscape: onEscape,
  6207. halt: halt,
  6208. consumeAction: consumeAction,
  6209. onReady: onReady
  6210. };
  6211. })();
  6212. up.on = up.event.on;
  6213. up.$on = up.event.$on;
  6214. up.off = up.event.off;
  6215. up.$off = up.event.off;
  6216. up.emit = up.event.emit;
  6217. up.legacy.renamedModule('bus', 'event');
  6218. }).call(this);
  6219. (function() {
  6220. }).call(this);
  6221. /***
  6222. Server protocol
  6223. ===============
  6224. You rarely need to change server-side code
  6225. in order to use Unpoly. There is no need to provide a JSON API, or add
  6226. extra routes for AJAX requests. The server simply renders a series
  6227. of full HTML pages, just like it would without Unpoly.
  6228. That said, there is an **optional** protocol your server can use to
  6229. exchange additional information when Unpoly is [updating fragments](/up.link).
  6230. While the protocol can help you optimize performance and handle some
  6231. edge cases, implementing it is **entirely optional**. For instance,
  6232. `unpoly.com` itself is a static site that uses Unpoly on the frontend
  6233. and doesn't even have a server component.
  6234. ## Existing implementations
  6235. You should be able to implement the protocol in a very short time.
  6236. There are existing implementations for various web frameworks:
  6237. - [Ruby on Rails](/install/rails)
  6238. - [Roda](https://github.com/adam12/roda-unpoly)
  6239. - [Rack](https://github.com/adam12/rack-unpoly) (Sinatra, Padrino, Hanami, Cuba, ...)
  6240. - [Phoenix](https://elixirforum.com/t/unpoly-a-framework-like-turbolinks/3614/15) (Elixir)
  6241. ## Protocol details
  6242. \#\#\# Redirect detection for IE11
  6243. On Internet Explorer 11, Unpoly cannot detect the final URL after a redirect.
  6244. You can fix this edge case by delivering an additional HTTP header
  6245. with the *last* response in a series of redirects:
  6246. ```http
  6247. X-Up-Location: /current-url
  6248. ```
  6249. The **simplest implementation** is to set these headers for every request.
  6250. \#\#\# Optimizing responses
  6251. When [updating a fragment](/up.link), Unpoly will send an
  6252. additional HTTP header containing the CSS selector that is being replaced:
  6253. ```http
  6254. X-Up-Target: .user-list
  6255. ```
  6256. Server-side code is free to **optimize its response** by only returning HTML
  6257. that matches the selector. For example, you might prefer to not render an
  6258. expensive sidebar if the sidebar is not targeted.
  6259. Unpoly will often update a different selector in case the request fails.
  6260. This selector is also included as a HTTP header:
  6261. ```
  6262. X-Up-Fail-Target: body
  6263. ```
  6264. \#\#\# Pushing a document title to the client
  6265. When [updating a fragment](/up.link), Unpoly will by default
  6266. extract the `<title>` from the server response and update the document title accordingly.
  6267. The server can also force Unpoly to set a document title by passing a HTTP header:
  6268. ```http
  6269. X-Up-Title: My server-pushed title
  6270. ```
  6271. This is useful when you [optimize your response](#optimizing-responses) and not render
  6272. the application layout unless it is targeted. Since your optimized response
  6273. no longer includes a `<title>`, you can instead use the HTTP header to pass the document title.
  6274. \#\#\# Signaling failed form submissions
  6275. When [submitting a form via AJAX](/form-up-target)
  6276. Unpoly needs to know whether the form submission has failed (to update the form with
  6277. validation errors) or succeeded (to update the `up-target` selector).
  6278. For Unpoly to be able to detect a failed form submission, the response must be
  6279. return a non-200 HTTP status code. We recommend to use either
  6280. 400 (bad request) or 422 (unprocessable entity).
  6281. To do so in [Ruby on Rails](http://rubyonrails.org/), pass a [`:status` option to `render`](http://guides.rubyonrails.org/layouts_and_rendering.html#the-status-option):
  6282. class UsersController < ApplicationController
  6283. def create
  6284. user_params = params[:user].permit(:email, :password)
  6285. @user = User.new(user_params)
  6286. if @user.save?
  6287. sign_in @user
  6288. else
  6289. render 'form', status: :bad_request
  6290. end
  6291. end
  6292. end
  6293. \#\#\# Detecting live form validations
  6294. When [validating a form](/input-up-validate), Unpoly will
  6295. send an additional HTTP header containing a CSS selector for the form that is
  6296. being updated:
  6297. ```http
  6298. X-Up-Validate: .user-form
  6299. ```
  6300. When detecting a validation request, the server is expected to **validate (but not save)**
  6301. the form submission and render a new copy of the form with validation errors.
  6302. Below you will an example for a writing route that is aware of Unpoly's live form
  6303. validations. The code is for [Ruby on Rails](http://rubyonrails.org/),
  6304. but you can adapt it for other languages:
  6305. class UsersController < ApplicationController
  6306. def create
  6307. user_params = params[:user].permit(:email, :password)
  6308. @user = User.new(user_params)
  6309. if request.headers['X-Up-Validate']
  6310. @user.valid? # run validations, but don't save to the database
  6311. render 'form' # render form with error messages
  6312. elsif @user.save?
  6313. sign_in @user
  6314. else
  6315. render 'form', status: :bad_request
  6316. end
  6317. end
  6318. end
  6319. \#\#\# Signaling the initial request method
  6320. If the initial page was loaded with a non-`GET` HTTP method, Unpoly prefers to make a full
  6321. page load when you try to update a fragment. Once the next page was loaded with a `GET` method,
  6322. Unpoly will restore its standard behavior.
  6323. This fixes two edge cases you might or might not care about:
  6324. 1. Unpoly replaces the initial page state so it can later restore it when the user
  6325. goes back to that initial URL. However, if the initial request was a POST,
  6326. Unpoly will wrongly assume that it can restore the state by reloading with GET.
  6327. 2. Some browsers have a bug where the initial request method is used for all
  6328. subsequently pushed states. That means if the user reloads the page on a later
  6329. GET state, the browser will wrongly attempt a POST request.
  6330. This issue affects Safari 9-12 (last tested in 2019-03).
  6331. Modern Firefoxes, Chromes and IE10+ don't have this behavior.
  6332. In order to allow Unpoly to detect the HTTP method of the initial page load,
  6333. the server must set a cookie:
  6334. ```http
  6335. Set-Cookie: _up_method=POST
  6336. ```
  6337. When Unpoly boots, it will look for this cookie and configure its behavior accordingly.
  6338. The cookie is then deleted in order to not affect following requests.
  6339. The **simplest implementation** is to set this cookie for every request that is neither
  6340. `GET` nor contains an [`X-Up-Target` header](/#optimizing-responses). For all other requests
  6341. an existing cookie should be deleted.
  6342. @module up.protocol
  6343. */
  6344. (function() {
  6345. up.protocol = (function() {
  6346. var config, csrfParam, csrfToken, e, initialRequestMethod, locationFromXhr, methodFromXhr, reset, titleFromXhr, u;
  6347. u = up.util;
  6348. e = up.element;
  6349. /***
  6350. @function up.protocol.locationFromXhr
  6351. @internal
  6352. */
  6353. locationFromXhr = function(xhr) {
  6354. return xhr.getResponseHeader(config.locationHeader) || xhr.responseURL;
  6355. };
  6356. /***
  6357. @function up.protocol.titleFromXhr
  6358. @internal
  6359. */
  6360. titleFromXhr = function(xhr) {
  6361. return xhr.getResponseHeader(config.titleHeader);
  6362. };
  6363. /***
  6364. @function up.protocol.methodFromXhr
  6365. @internal
  6366. */
  6367. methodFromXhr = function(xhr) {
  6368. var method;
  6369. if (method = xhr.getResponseHeader(config.methodHeader)) {
  6370. return u.normalizeMethod(method);
  6371. }
  6372. };
  6373. /***
  6374. Server-side companion libraries like unpoly-rails set this cookie so we
  6375. have a way to detect the request method of the initial page load.
  6376. There is no JavaScript API for this.
  6377. @function up.protocol.initialRequestMethod
  6378. @internal
  6379. */
  6380. initialRequestMethod = u.memoize(function() {
  6381. var methodFromServer;
  6382. methodFromServer = up.browser.popCookie(config.methodCookie);
  6383. return (methodFromServer || 'get').toLowerCase();
  6384. });
  6385. up.on('up:framework:booted', initialRequestMethod);
  6386. /***
  6387. Configures strings used in the optional [server protocol](/up.protocol).
  6388. @property up.protocol.config
  6389. @param {String} [config.targetHeader='X-Up-Target']
  6390. @param {String} [config.failTargetHeader='X-Up-Fail-Target']
  6391. @param {String} [config.locationHeader='X-Up-Location']
  6392. @param {String} [config.titleHeader='X-Up-Title']
  6393. @param {String} [config.validateHeader='X-Up-Validate']
  6394. @param {String} [config.methodHeader='X-Up-Method']
  6395. @param {String} [config.methodCookie='_up_method']
  6396. The name of the optional cookie the server can send to
  6397. [signal the initial request method](/up.protocol#signaling-the-initial-request-method).
  6398. @param {String} [config.methodParam='_method']
  6399. The name of the POST parameter when [wrapping HTTP methods](/up.proxy.config#config.wrapMethods)
  6400. in a `POST` request.
  6401. @param {String} [config.csrfHeader='X-CSRF-Token']
  6402. The name of the HTTP header that will include the
  6403. [CSRF token](https://en.wikipedia.org/wiki/Cross-site_request_forgery#Synchronizer_token_pattern)
  6404. for AJAX requests.
  6405. @param {string|Function(): string} [config.csrfParam]
  6406. The `name` of the hidden `<input>` used for sending a
  6407. [CSRF token](https://en.wikipedia.org/wiki/Cross-site_request_forgery#Synchronizer_token_pattern) when
  6408. submitting a default, non-AJAX form. For AJAX request the token is sent as an HTTP header instead.
  6409. The parameter name can be configured as a string or as function that returns the parameter name.
  6410. If no name is set, no token will be sent.
  6411. Defaults to the `content` attribute of a `<meta>` tag named `csrf-param`:
  6412. <meta name="csrf-param" content="authenticity_token" />
  6413. @param {string|Function(): string} [config.csrfToken]
  6414. The [CSRF token](https://en.wikipedia.org/wiki/Cross-site_request_forgery#Synchronizer_token_pattern)
  6415. to send for unsafe requests. The token will be sent as either a HTTP header (for AJAX requests)
  6416. or hidden form `<input>` (for default, non-AJAX form submissions).
  6417. The token can either be configured as a string or as function that returns the token.
  6418. If no token is set, no token will be sent.
  6419. Defaults to the `content` attribute of a `<meta>` tag named `csrf-token`:
  6420. <meta name='csrf-token' content='secret12345'>
  6421. @experimental
  6422. */
  6423. config = new up.Config({
  6424. targetHeader: 'X-Up-Target',
  6425. failTargetHeader: 'X-Up-Fail-Target',
  6426. locationHeader: 'X-Up-Location',
  6427. validateHeader: 'X-Up-Validate',
  6428. titleHeader: 'X-Up-Title',
  6429. methodHeader: 'X-Up-Method',
  6430. methodCookie: '_up_method',
  6431. methodParam: '_method',
  6432. csrfParam: function() {
  6433. return e.metaContent('csrf-param');
  6434. },
  6435. csrfToken: function() {
  6436. return e.metaContent('csrf-token');
  6437. },
  6438. csrfHeader: 'X-CSRF-Token'
  6439. });
  6440. csrfParam = function() {
  6441. return u.evalOption(config.csrfParam);
  6442. };
  6443. csrfToken = function() {
  6444. return u.evalOption(config.csrfToken);
  6445. };
  6446. reset = function() {
  6447. return config.reset();
  6448. };
  6449. up.on('up:framework:reset', reset);
  6450. return {
  6451. config: config,
  6452. reset: reset,
  6453. locationFromXhr: locationFromXhr,
  6454. titleFromXhr: titleFromXhr,
  6455. methodFromXhr: methodFromXhr,
  6456. csrfParam: csrfParam,
  6457. csrfToken: csrfToken,
  6458. initialRequestMethod: initialRequestMethod
  6459. };
  6460. })();
  6461. }).call(this);
  6462. /***
  6463. Logging
  6464. =======
  6465. Unpoly can print debugging information to the developer console, e.g.:
  6466. - Which [events](/up.event) are called
  6467. - When we're [making requests to the network](/up.proxy)
  6468. - Which [compilers](/up.syntax) are applied to which elements
  6469. You can activate logging by calling [`up.log.enable()`](/up.log.enable).
  6470. The output can be configured using the [`up.log.config`](/up.log.config) property.
  6471. @module up.log
  6472. */
  6473. (function() {
  6474. var slice = [].slice;
  6475. up.log = (function() {
  6476. var CONSOLE_PLACEHOLDERS, b, callConsole, config, debug, disable, enable, error, group, prefix, printBanner, puts, reset, sessionStore, setEnabled, sprintf, sprintfWithFormattedArgs, stringifyArg, u, warn;
  6477. u = up.util;
  6478. b = up.browser;
  6479. sessionStore = new up.store.Session('up.log');
  6480. /***
  6481. Configures the logging output on the developer console.
  6482. @property up.log.config
  6483. @param {boolean} [options.enabled=false]
  6484. Whether Unpoly will print debugging information to the developer console.
  6485. Debugging information includes which elements are being [compiled](/up.syntax)
  6486. and which [events](/up.event) are being emitted.
  6487. Note that errors will always be printed, regardless of this setting.
  6488. @param {boolean} [options.collapse=false]
  6489. Whether debugging information is printed as a collapsed tree.
  6490. Set this to `true` if you are overwhelmed by the debugging information Unpoly
  6491. prints to the developer console.
  6492. @param {string} [options.prefix='[UP] ']
  6493. A string to prepend to Unpoly's logging messages so you can distinguish it from your own messages.
  6494. @stable
  6495. */
  6496. config = new up.Config({
  6497. prefix: '[UP] ',
  6498. enabled: sessionStore.get('enabled'),
  6499. collapse: false
  6500. });
  6501. reset = function() {
  6502. return config.reset();
  6503. };
  6504. prefix = function(message) {
  6505. return "" + config.prefix + message;
  6506. };
  6507. /***
  6508. A cross-browser way to interact with `console.log`, `console.error`, etc.
  6509. This function falls back to `console.log` if the output stream is not implemented.
  6510. It also prints substitution strings (e.g. `console.log("From %o to %o", "a", "b")`)
  6511. as a single string if the browser console does not support substitution strings.
  6512. \#\#\# Example
  6513. up.browser.puts('log', 'Hi world')
  6514. up.browser.puts('error', 'There was an error in %o', obj)
  6515. @function up.browser.puts
  6516. @internal
  6517. */
  6518. callConsole = function() {
  6519. var args, stream;
  6520. stream = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];
  6521. return console[stream].apply(console, args);
  6522. };
  6523. CONSOLE_PLACEHOLDERS = /\%[odisf]/g;
  6524. stringifyArg = function(arg) {
  6525. var attr, closer, j, len, maxLength, ref, string, value;
  6526. maxLength = 200;
  6527. closer = '';
  6528. if (u.isString(arg)) {
  6529. string = arg.replace(/[\n\r\t ]+/g, ' ');
  6530. string = string.replace(/^[\n\r\t ]+/, '');
  6531. string = string.replace(/[\n\r\t ]$/, '');
  6532. string = "\"" + string + "\"";
  6533. closer = '"';
  6534. } else if (u.isUndefined(arg)) {
  6535. string = 'undefined';
  6536. } else if (u.isNumber(arg) || u.isFunction(arg)) {
  6537. string = arg.toString();
  6538. } else if (u.isArray(arg)) {
  6539. string = "[" + (u.map(arg, stringifyArg).join(', ')) + "]";
  6540. closer = ']';
  6541. } else if (u.isJQuery(arg)) {
  6542. string = "$(" + (u.map(arg, stringifyArg).join(', ')) + ")";
  6543. closer = ')';
  6544. } else if (u.isElement(arg)) {
  6545. string = "<" + (arg.tagName.toLowerCase());
  6546. ref = ['id', 'name', 'class'];
  6547. for (j = 0, len = ref.length; j < len; j++) {
  6548. attr = ref[j];
  6549. if (value = arg.getAttribute(attr)) {
  6550. string += " " + attr + "=\"" + value + "\"";
  6551. }
  6552. }
  6553. string += ">";
  6554. closer = '>';
  6555. } else {
  6556. string = JSON.stringify(arg);
  6557. }
  6558. if (string.length > maxLength) {
  6559. string = (string.substr(0, maxLength)) + " …";
  6560. string += closer;
  6561. }
  6562. return string;
  6563. };
  6564. /***
  6565. See https://developer.mozilla.org/en-US/docs/Web/API/Console#Using_string_substitutions
  6566. @function up.browser.sprintf
  6567. @internal
  6568. */
  6569. sprintf = function() {
  6570. var args, message;
  6571. message = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];
  6572. return sprintfWithFormattedArgs.apply(null, [u.identity, message].concat(slice.call(args)));
  6573. };
  6574. /***
  6575. @function up.browser.sprintfWithFormattedArgs
  6576. @internal
  6577. */
  6578. sprintfWithFormattedArgs = function() {
  6579. var args, formatter, i, message;
  6580. formatter = arguments[0], message = arguments[1], args = 3 <= arguments.length ? slice.call(arguments, 2) : [];
  6581. if (u.isBlank(message)) {
  6582. return '';
  6583. }
  6584. i = 0;
  6585. return message.replace(CONSOLE_PLACEHOLDERS, function() {
  6586. var arg;
  6587. arg = args[i];
  6588. arg = formatter(stringifyArg(arg));
  6589. i += 1;
  6590. return arg;
  6591. });
  6592. };
  6593. /***
  6594. Prints a debugging message to the browser console.
  6595. @function up.log.debug
  6596. @param {string} message
  6597. @param {Array} ...args
  6598. @internal
  6599. */
  6600. debug = function() {
  6601. var args, message;
  6602. message = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];
  6603. if (config.enabled && message) {
  6604. return console.debug.apply(console, [prefix(message)].concat(slice.call(args)));
  6605. }
  6606. };
  6607. /***
  6608. Prints a logging message to the browser console.
  6609. @function up.puts
  6610. @param {string} message
  6611. @param {Array} ...args
  6612. @internal
  6613. */
  6614. puts = function() {
  6615. var args, message;
  6616. message = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];
  6617. if (config.enabled && message) {
  6618. return console.log.apply(console, [prefix(message)].concat(slice.call(args)));
  6619. }
  6620. };
  6621. /***
  6622. @function up.warn
  6623. @internal
  6624. */
  6625. warn = function() {
  6626. var args, message;
  6627. message = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];
  6628. if (message) {
  6629. return console.warn.apply(console, [prefix(message)].concat(slice.call(args)));
  6630. }
  6631. };
  6632. /***
  6633. - Makes sure the group always closes
  6634. - Does not make a group if the message is nil
  6635. @function up.log.group
  6636. @internal
  6637. */
  6638. group = function() {
  6639. var args, block, fn, message;
  6640. message = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];
  6641. block = args.pop();
  6642. if (config.enabled && message) {
  6643. fn = config.collapse ? 'groupCollapsed' : 'group';
  6644. console[fn].apply(console, [prefix(message)].concat(slice.call(args)));
  6645. try {
  6646. return block();
  6647. } finally {
  6648. if (message) {
  6649. console.groupEnd();
  6650. }
  6651. }
  6652. } else {
  6653. return block();
  6654. }
  6655. };
  6656. /***
  6657. @function up.log.error
  6658. @internal
  6659. */
  6660. error = function() {
  6661. var args, message;
  6662. message = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];
  6663. if (message) {
  6664. return console.error.apply(console, [prefix(message)].concat(slice.call(args)));
  6665. }
  6666. };
  6667. printBanner = function() {
  6668. var banner;
  6669. banner = " __ _____ ___ ___ / /_ __\n" + ("/ // / _ \\/ _ \\/ _ \\/ / // / " + up.version + "\n") + "\\___/_//_/ .__/\\___/_/\\_. / \n" + " / / / /\n" + "\n";
  6670. if (config.enabled) {
  6671. banner += "Call `up.log.disable()` to disable logging for this session.";
  6672. } else {
  6673. banner += "Call `up.log.enable()` to enable logging for this session.";
  6674. }
  6675. return console.log(banner);
  6676. };
  6677. up.on('up:framework:booted', printBanner);
  6678. up.on('up:framework:reset', reset);
  6679. setEnabled = function(value) {
  6680. sessionStore.set('enabled', value);
  6681. return config.enabled = value;
  6682. };
  6683. /***
  6684. Makes future Unpoly events print vast amounts of debugging information to the developer console.
  6685. Debugging information includes which elements are being [compiled](/up.syntax)
  6686. and which [events](/up.event) are being emitted.
  6687. @function up.log.enable
  6688. @stable
  6689. */
  6690. enable = function() {
  6691. return setEnabled(true);
  6692. };
  6693. /***
  6694. Prevents future Unpoly events from printing vast amounts of debugging information to the developer console.
  6695. Errors will still be printed, even with logging disabled.
  6696. @function up.log.disable
  6697. @stable
  6698. */
  6699. disable = function() {
  6700. return setEnabled(false);
  6701. };
  6702. return {
  6703. puts: puts,
  6704. sprintf: sprintf,
  6705. sprintfWithFormattedArgs: sprintfWithFormattedArgs,
  6706. puts: puts,
  6707. debug: debug,
  6708. error: error,
  6709. warn: warn,
  6710. group: group,
  6711. config: config,
  6712. enable: enable,
  6713. disable: disable,
  6714. isEnabled: function() {
  6715. return config.enabled;
  6716. }
  6717. };
  6718. })();
  6719. up.puts = up.log.puts;
  6720. up.warn = up.log.warn;
  6721. }).call(this);
  6722. /***
  6723. Toast alerts
  6724. ============
  6725. @module up.toast
  6726. */
  6727. (function() {
  6728. var slice = [].slice;
  6729. up.toast = (function() {
  6730. var VARIABLE_FORMATTER, addAction, close, e, isOpen, messageToHtml, open, reset, state, u;
  6731. u = up.util;
  6732. e = up.element;
  6733. VARIABLE_FORMATTER = function(arg) {
  6734. return "<span class='up-toast-variable'>" + (u.escapeHtml(arg)) + "</span>";
  6735. };
  6736. state = new up.Config({
  6737. element: null
  6738. });
  6739. reset = function() {
  6740. close();
  6741. return state.reset();
  6742. };
  6743. messageToHtml = function(message) {
  6744. var ref;
  6745. if (u.isArray(message)) {
  6746. message[0] = u.escapeHtml(message[0]);
  6747. message = (ref = up.log).sprintfWithFormattedArgs.apply(ref, [VARIABLE_FORMATTER].concat(slice.call(message)));
  6748. } else {
  6749. message = u.escapeHtml(message);
  6750. }
  6751. return message;
  6752. };
  6753. isOpen = function() {
  6754. return !!state.element;
  6755. };
  6756. addAction = function(label, callback) {
  6757. var action, actions;
  6758. actions = state.element.querySelector('.up-toast-actions');
  6759. action = e.affix(actions, '.up-toast-action');
  6760. action.innerText = label;
  6761. return action.addEventListener('click', callback);
  6762. };
  6763. open = function(message, options) {
  6764. var action;
  6765. if (options == null) {
  6766. options = {};
  6767. }
  6768. close();
  6769. message = messageToHtml(message);
  6770. state.element = e.createFromHtml("<div class=\"up-toast\">\n <div class=\"up-toast-message\">" + message + "</div>\n <div class=\"up-toast-actions\"></div>\n</div>");
  6771. if (action = options.action || options.inspect) {
  6772. addAction(action.label, action.callback);
  6773. }
  6774. addAction('Close', close);
  6775. return document.body.appendChild(state.element);
  6776. };
  6777. close = function() {
  6778. if (isOpen()) {
  6779. e.remove(state.element);
  6780. return state.element = null;
  6781. }
  6782. };
  6783. up.on('up:framework:reset', reset);
  6784. return {
  6785. open: open,
  6786. close: close,
  6787. reset: reset,
  6788. isOpen: isOpen
  6789. };
  6790. })();
  6791. }).call(this);
  6792. /***
  6793. Custom JavaScript
  6794. =================
  6795. Every app needs a way to pair JavaScript snippets with certain HTML elements,
  6796. in order to integrate libraries or implement custom behavior.
  6797. Unpoly lets you organize your JavaScript snippets using [compilers](/up.compiler).
  6798. For instance, to activate the [Masonry](http://masonry.desandro.com/) library for every element
  6799. with a `grid` class, use this compiler:
  6800. up.compiler('.grid', function(element) {
  6801. new Masonry(element, { itemSelector: '.grid--item' })
  6802. })
  6803. The compiler function will be called on matching elements when the page loads
  6804. or when a matching fragment is [inserted via AJAX](/up.link) later.
  6805. @module up.syntax
  6806. */
  6807. (function() {
  6808. var slice = [].slice;
  6809. up.syntax = (function() {
  6810. var SYSTEM_MACRO_PRIORITIES, buildCompiler, clean, compile, compilers, detectSystemMacroPriority, e, insertCompiler, macros, parseCompilerArgs, readData, registerCompiler, registerDestructor, registerJQueryCompiler, registerJQueryMacro, registerMacro, reset, u;
  6811. u = up.util;
  6812. e = up.element;
  6813. SYSTEM_MACRO_PRIORITIES = {
  6814. '[up-back]': -100,
  6815. '[up-drawer]': -200,
  6816. '[up-dash]': -200,
  6817. '[up-expand]': -300,
  6818. '[data-method]': -400,
  6819. '[data-confirm]': -400
  6820. };
  6821. compilers = [];
  6822. macros = [];
  6823. /***
  6824. Registers a function to be called when an element with
  6825. the given selector is inserted into the DOM.
  6826. Use compilers to activate your custom Javascript behavior on matching
  6827. elements.
  6828. You should migrate your [`DOMContentLoaded`](https://api.jquery.com/ready/)
  6829. callbacks to compilers. This will make sure they run both at page load and
  6830. when [a new fragment is inserted later](/a-up-target).
  6831. It will also organize your JavaScript snippets by selector of affected elements.
  6832. \#\#\# Example
  6833. This jQuery compiler will insert the current time into a
  6834. `<div class='current-time'></div>`:
  6835. up.compiler('.current-time', function(element) {
  6836. var now = new Date()
  6837. element.textContent = now.toString()
  6838. })
  6839. The compiler function will be called once for each matching element when
  6840. the page loads, or when a matching fragment is [inserted](/up.replace) later.
  6841. \#\#\# Integrating JavaScript libraries
  6842. `up.compiler()` is a great way to integrate JavaScript libraries.
  6843. Let's say your JavaScript plugin wants you to call `lightboxify()`
  6844. on links that should open a lightbox. You decide to
  6845. do this for all links with an `lightbox` class:
  6846. <a href="river.png" class="lightbox">River</a>
  6847. <a href="ocean.png" class="lightbox">Ocean</a>
  6848. This JavaScript will do exactly that:
  6849. up.compiler('a.lightbox', function(element) {
  6850. lightboxify(element)
  6851. })
  6852. \#\#\# Cleaning up after yourself
  6853. If your compiler returns a function, Unpoly will use this as a *destructor* to
  6854. clean up if the element leaves the DOM. Note that in Unpoly the same DOM and JavaScript environment
  6855. will persist through many page loads, so it's important to not create
  6856. [memory leaks](https://makandracards.com/makandra/31325-how-to-create-memory-leaks-in-jquery).
  6857. You should clean up after yourself whenever your compilers have global
  6858. side effects, like a [`setInterval`](https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setInterval)
  6859. or [event handlers bound to the document root](/up.on).
  6860. Here is a version of `.current-time` that updates
  6861. the time every second, and cleans up once it's done. Note how it returns
  6862. a function that calls `clearInterval`:
  6863. up.compiler('.current-time', function(element) {
  6864. function update() {
  6865. var now = new Date()
  6866. element.textContent = now.toString()
  6867. }
  6868. setInterval(update, 1000)
  6869. return function() {
  6870. clearInterval(update)
  6871. };
  6872. })
  6873. If we didn't clean up after ourselves, we would have many ticking intervals
  6874. operating on detached DOM elements after we have created and removed a couple
  6875. of `<clock>` elements.
  6876. \#\#\# Attaching structured data
  6877. In case you want to attach structured data to the event you're observing,
  6878. you can serialize the data to JSON and put it into an `[up-data]` attribute.
  6879. For instance, a container for a [Google Map](https://developers.google.com/maps/documentation/javascript/tutorial)
  6880. might attach the location and names of its marker pins:
  6881. <div class='google-map' up-data='[
  6882. { "lat": 48.36, "lng": 10.99, "title": "Friedberg" },
  6883. { "lat": 48.75, "lng": 11.45, "title": "Ingolstadt" }
  6884. ]'></div>
  6885. The JSON will be parsed and handed to your compiler as a second argument:
  6886. up.compiler('.google-map', function(element, pins) {
  6887. var map = new google.maps.Map(element)
  6888. pins.forEach(function(pin) {
  6889. var position = new google.maps.LatLng(pin.lat, pin.lng)
  6890. new google.maps.Marker({
  6891. position: position,
  6892. map: map,
  6893. title: pin.title
  6894. })
  6895. })
  6896. })
  6897. @function up.compiler
  6898. @param {string} selector
  6899. The selector to match.
  6900. @param {number} [options.priority=0]
  6901. The priority of this compiler.
  6902. Compilers with a higher priority are run first.
  6903. Two compilers with the same priority are run in the order they were registered.
  6904. @param {boolean} [options.batch=false]
  6905. If set to `true` and a fragment insertion contains multiple
  6906. elements matching the selector, `compiler` is only called once
  6907. with a jQuery collection containing all matching elements.
  6908. @param {boolean} [options.keep=false]
  6909. If set to `true` compiled fragment will be [persisted](/up-keep) during
  6910. [page updates](/a-up-target).
  6911. This has the same effect as setting an `up-keep` attribute on the element.
  6912. @param {Function(element, data)} compiler
  6913. The function to call when a matching element is inserted.
  6914. The function takes the new element as the first argument.
  6915. If the element has an [`up-data`](/up-data) attribute, its value is parsed as JSON
  6916. and passed as a second argument.
  6917. The function may return a destructor function that cleans the compiled
  6918. object before it is removed from the DOM. The destructor is supposed to
  6919. [clear global state](/up.compiler#cleaning-up-after-yourself)
  6920. such as timeouts and event handlers bound to the document.
  6921. The destructor is *not* expected to remove the element from the DOM, which
  6922. is already handled by [`up.destroy()`](/up.destroy).
  6923. @stable
  6924. */
  6925. registerCompiler = function() {
  6926. var args, compiler;
  6927. args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
  6928. compiler = buildCompiler(args);
  6929. return insertCompiler(compilers, compiler);
  6930. };
  6931. /***
  6932. Registers a function to be called when an element with
  6933. the given selector is inserted into the DOM. The function is called
  6934. with each matching element as a
  6935. [jQuery object](https://learn.jquery.com/using-jquery-core/jquery-object/).
  6936. If you're not using jQuery, use `up.compiler()` instead, which calls
  6937. the compiler function with a native element.
  6938. \#\#\# Example
  6939. This jQuery compiler will insert the current time into a
  6940. `<div class='current-time'></div>`:
  6941. up.$compiler('.current-time', function($element) {
  6942. var now = new Date()
  6943. $element.text(now.toString())
  6944. })
  6945. @function up.$compiler
  6946. @param {string} selector
  6947. The selector to match.
  6948. @param {Object} [options]
  6949. See [`options` argument for `up.compiler()`](/up.compiler#parameters).
  6950. @param {Function($element, data)} compiler
  6951. The function to call when a matching element is inserted.
  6952. See [`compiler` argument for `up.compiler()`](/up.compiler#parameters).
  6953. @stable
  6954. */
  6955. registerJQueryCompiler = function() {
  6956. var args, compiler;
  6957. args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
  6958. compiler = registerCompiler.apply(null, args);
  6959. compiler.jQuery = true;
  6960. return compiler;
  6961. };
  6962. /***
  6963. Registers a [compiler](/up.compiler) that is run before all other compilers.
  6964. Use `up.macro()` to register a compiler that sets multiple Unpoly attributes.
  6965. \#\#\# Example
  6966. You will sometimes find yourself setting the same combination of UJS attributes again and again:
  6967. <a href="/page1" up-target=".content" up-transition="cross-fade" up-duration="300">Page 1</a>
  6968. <a href="/page2" up-target=".content" up-transition="cross-fade" up-duration="300">Page 2</a>
  6969. <a href="/page3" up-target=".content" up-transition="cross-fade" up-duration="300">Page 3</a>
  6970. We would much rather define a new `[content-link]` attribute that let's us
  6971. write the same links like this:
  6972. <a href="/page1" content-link>Page 1</a>
  6973. <a href="/page2" content-link>Page 2</a>
  6974. <a href="/page3" content-link>Page 3</a>
  6975. We can define the `[content-link]` attribute by registering a macro that
  6976. sets the `[up-target]`, `[up-transition]` and `[up-duration]` attributes for us:
  6977. up.macro('[content-link]', function(link) {
  6978. link.setAttribute('up-target', '.content')
  6979. link.setAttribute('up-transition', 'cross-fade')
  6980. link.setAttribute('up-duration', '300')
  6981. })
  6982. Examples for built-in macros are [`a[up-dash]`](/a-up-dash) and [`[up-expand]`](/up-expand).
  6983. @function up.macro
  6984. @param {string} selector
  6985. The selector to match.
  6986. @param {Object} options
  6987. See options for [`up.compiler()`](/up.compiler).
  6988. @param {Function(element, data)} macro
  6989. The function to call when a matching element is inserted.
  6990. See [`up.compiler()`](/up.compiler#parameters) for details.
  6991. @stable
  6992. */
  6993. registerMacro = function() {
  6994. var args, macro;
  6995. args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
  6996. macro = buildCompiler(args);
  6997. if (up.framework.isBooting()) {
  6998. macro.priority = detectSystemMacroPriority(macro.selector) || up.fail('Unregistered priority for system macro %o', macro.selector);
  6999. }
  7000. return insertCompiler(macros, macro);
  7001. };
  7002. /***
  7003. Registers a [compiler](/up.compiler) that is run before all other compilers.
  7004. The compiler function is called with each matching element as a
  7005. [jQuery object](https://learn.jquery.com/using-jquery-core/jquery-object/).
  7006. If you're not using jQuery, use `up.macro()` instead, which calls
  7007. the macro function with a native element.
  7008. \#\#\# Example
  7009. up.$macro('[content-link]', function($link) {
  7010. $link.attr(
  7011. 'up-target': '.content',
  7012. 'up-transition': 'cross-fade',
  7013. 'up-duration':'300'
  7014. )
  7015. })
  7016. @function up.$macro
  7017. @param {string} selector
  7018. The selector to match.
  7019. @param {Object} options
  7020. See [`options` argument for `up.compiler()`](/up.compiler#parameters).
  7021. @param {Function(element, data)} macro
  7022. The function to call when a matching element is inserted.
  7023. See [`compiler` argument for `up.compiler()`](/up.compiler#parameters).
  7024. @stable
  7025. */
  7026. registerJQueryMacro = function() {
  7027. var args, macro;
  7028. args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
  7029. macro = registerMacro.apply(null, args);
  7030. macro.jQuery = true;
  7031. return macro;
  7032. };
  7033. detectSystemMacroPriority = function(macroSelector) {
  7034. var priority, substr;
  7035. for (substr in SYSTEM_MACRO_PRIORITIES) {
  7036. priority = SYSTEM_MACRO_PRIORITIES[substr];
  7037. if (macroSelector.indexOf(substr) >= 0) {
  7038. return priority;
  7039. }
  7040. }
  7041. };
  7042. parseCompilerArgs = function(args) {
  7043. var callback, options, selector;
  7044. selector = args.shift();
  7045. callback = args.pop();
  7046. options = u.extractOptions(args);
  7047. return [selector, options, callback];
  7048. };
  7049. buildCompiler = function(args) {
  7050. var callback, options, ref, selector;
  7051. ref = parseCompilerArgs(args), selector = ref[0], options = ref[1], callback = ref[2];
  7052. options = u.options(options, {
  7053. selector: selector,
  7054. isDefault: up.framework.isBooting(),
  7055. priority: 0,
  7056. batch: false,
  7057. keep: false,
  7058. jQuery: false
  7059. });
  7060. return u.assign(callback, options);
  7061. };
  7062. insertCompiler = function(queue, newCompiler) {
  7063. var existingCompiler, index;
  7064. index = 0;
  7065. while ((existingCompiler = queue[index]) && (existingCompiler.priority >= newCompiler.priority)) {
  7066. index += 1;
  7067. }
  7068. queue.splice(index, 0, newCompiler);
  7069. return newCompiler;
  7070. };
  7071. /***
  7072. Applies all compilers on the given element and its descendants.
  7073. Unlike [`up.hello()`](/up.hello), this doesn't emit any events.
  7074. @function up.syntax.compile
  7075. @param {Array<Element>} [options.skip]
  7076. A list of elements whose subtrees should not be compiled.
  7077. @internal
  7078. */
  7079. compile = function(fragment, options) {
  7080. var compileRun, orderedCompilers;
  7081. orderedCompilers = macros.concat(compilers);
  7082. compileRun = new up.CompilePass(fragment, orderedCompilers, options);
  7083. return compileRun.compile();
  7084. };
  7085. /***
  7086. Registers a function to be called when the given element
  7087. is [destroyed](/up.destroy).
  7088. The preferred way to register a destructor function is to `return`
  7089. it from a [compiler function](/up.compiler).
  7090. @function up.destructor
  7091. @param {Element} element
  7092. @param {Function|Array<Function>} destructor
  7093. One or more destructor functions
  7094. @internal
  7095. */
  7096. registerDestructor = function(element, destructor) {
  7097. var destructors;
  7098. if (!(destructors = element.upDestructors)) {
  7099. destructors = [];
  7100. element.upDestructors = destructors;
  7101. element.classList.add('up-can-clean');
  7102. }
  7103. if (u.isArray(destructor)) {
  7104. return destructors.push.apply(destructors, destructor);
  7105. } else {
  7106. return destructors.push(destructor);
  7107. }
  7108. };
  7109. /***
  7110. Runs any destructor on the given fragment and its descendants.
  7111. Unlike [`up.destroy()`](/up.destroy), this doesn't emit any events
  7112. and does not remove the element from the DOM.
  7113. @function up.syntax.clean
  7114. @internal
  7115. */
  7116. clean = function(fragment) {
  7117. var cleanables;
  7118. cleanables = e.subtree(fragment, '.up-can-clean');
  7119. return u.each(cleanables, function(cleanable) {
  7120. var destructor, destructors, i, len, results;
  7121. if (destructors = cleanable.upDestructors) {
  7122. results = [];
  7123. for (i = 0, len = destructors.length; i < len; i++) {
  7124. destructor = destructors[i];
  7125. results.push(destructor());
  7126. }
  7127. return results;
  7128. }
  7129. });
  7130. };
  7131. /***
  7132. Checks if the given element has an [`up-data`](/up-data) attribute.
  7133. If yes, parses the attribute value as JSON and returns the parsed object.
  7134. Returns `undefined` if the element has no `up-data` attribute.
  7135. \#\#\# Example
  7136. You have an element with JSON data serialized into an `up-data` attribute:
  7137. <span class='person' up-data='{ "age": 18, "name": "Bob" }'>Bob</span>
  7138. Calling `up.syntax.data()` will deserialize the JSON string into a JavaScript object:
  7139. up.syntax.data('.person') // returns { age: 18, name: 'Bob' }
  7140. @function up.syntax.data
  7141. @param {string|Element|jQuery} elementOrSelector
  7142. @return
  7143. The JSON-decoded value of the `up-data` attribute.
  7144. Returns `undefined` if the element has no (or an empty) `up-data` attribute.
  7145. @experimental
  7146. */
  7147. /***
  7148. If an element with an `up-data` attribute enters the DOM,
  7149. Unpoly will parse the JSON and pass the resulting object to any matching
  7150. [`up.compiler()`](/up.compiler) handlers.
  7151. For instance, a container for a [Google Map](https://developers.google.com/maps/documentation/javascript/tutorial)
  7152. might attach the location and names of its marker pins:
  7153. <div class='google-map' up-data='[
  7154. { "lat": 48.36, "lng": 10.99, "title": "Friedberg" },
  7155. { "lat": 48.75, "lng": 11.45, "title": "Ingolstadt" }
  7156. ]'></div>
  7157. The JSON will be parsed and handed to your compiler as a second argument:
  7158. up.compiler('.google-map', function(element, pins) {
  7159. var map = new google.maps.Map(element)
  7160. pins.forEach(function(pin) {
  7161. var position = new google.maps.LatLng(pin.lat, pin.lng)
  7162. new google.maps.Marker({
  7163. position: position,
  7164. map: map,
  7165. title: pin.title
  7166. })
  7167. })
  7168. })
  7169. Similarly, when an event is triggered on an element annotated with
  7170. [`up-data`], the parsed object will be passed to any matching
  7171. [`up.on()`](/up.on) handlers.
  7172. up.on('click', '.google-map', function(event, element, pins) {
  7173. console.log("There are %d pins on the clicked map", pins.length)
  7174. })
  7175. @selector [up-data]
  7176. @param {JSON} up-data
  7177. A serialized JSON string
  7178. @stable
  7179. */
  7180. readData = function(elementOrSelector) {
  7181. var element;
  7182. element = e.get(elementOrSelector);
  7183. return e.jsonAttr(element, 'up-data') || {};
  7184. };
  7185. /***
  7186. Resets the list of registered compiler directives to the
  7187. moment when the framework was booted.
  7188. @internal
  7189. */
  7190. reset = function() {
  7191. compilers = u.filter(compilers, 'isDefault');
  7192. return macros = u.filter(macros, 'isDefault');
  7193. };
  7194. up.on('up:framework:reset', reset);
  7195. return {
  7196. compiler: registerCompiler,
  7197. macro: registerMacro,
  7198. $compiler: registerJQueryCompiler,
  7199. $macro: registerJQueryMacro,
  7200. destructor: registerDestructor,
  7201. compile: compile,
  7202. clean: clean,
  7203. data: readData
  7204. };
  7205. })();
  7206. up.compiler = up.syntax.compiler;
  7207. up.$compiler = up.syntax.$compiler;
  7208. up.destructor = up.syntax.destructor;
  7209. up.macro = up.syntax.macro;
  7210. up.$macro = up.syntax.$macro;
  7211. }).call(this);
  7212. /***
  7213. History
  7214. ========
  7215. In an Unpoly app, every page has an URL.
  7216. [Fragment updates](/up.link) automatically update the URL.
  7217. @module up.history
  7218. */
  7219. (function() {
  7220. up.history = (function() {
  7221. var buildState, config, currentUrl, e, isCurrentUrl, manipulate, nextPreviousUrl, normalizeUrl, observeNewUrl, pop, previousUrl, push, replace, reset, restoreStateOnPop, u;
  7222. u = up.util;
  7223. e = up.element;
  7224. /***
  7225. Configures behavior when the user goes back or forward in browser history.
  7226. @property up.history.config
  7227. @param {Array} [config.popTargets=['body']]
  7228. An array of CSS selectors to replace when the user goes
  7229. back in history.
  7230. @param {boolean} [config.restoreScroll=true]
  7231. Whether to restore the known scroll positions
  7232. when the user goes back or forward in history.
  7233. @stable
  7234. */
  7235. config = new up.Config({
  7236. enabled: true,
  7237. popTargets: ['body'],
  7238. restoreScroll: true
  7239. });
  7240. /***
  7241. Returns the previous URL in the browser history.
  7242. Note that this will only work reliably for history changes that
  7243. were applied by [`up.history.push()`](/up.history.replace) or
  7244. [`up.history.replace()`](/up.history.replace).
  7245. @function up.history.previousUrl
  7246. @internal
  7247. */
  7248. previousUrl = void 0;
  7249. nextPreviousUrl = void 0;
  7250. reset = function() {
  7251. config.reset();
  7252. previousUrl = void 0;
  7253. return nextPreviousUrl = void 0;
  7254. };
  7255. normalizeUrl = function(url, normalizeOptions) {
  7256. normalizeOptions || (normalizeOptions = {});
  7257. normalizeOptions.hash = true;
  7258. return u.normalizeUrl(url, normalizeOptions);
  7259. };
  7260. /***
  7261. Returns a normalized URL for the current history entry.
  7262. @function up.history.url
  7263. @experimental
  7264. */
  7265. currentUrl = function(normalizeOptions) {
  7266. return normalizeUrl(up.browser.url(), normalizeOptions);
  7267. };
  7268. isCurrentUrl = function(url) {
  7269. var normalizeOptions;
  7270. normalizeOptions = {
  7271. stripTrailingSlash: true
  7272. };
  7273. return normalizeUrl(url, normalizeOptions) === currentUrl(normalizeOptions);
  7274. };
  7275. /***
  7276. Remembers the given URL so we can offer `up.history.previousUrl()`.
  7277. @function observeNewUrl
  7278. @internal
  7279. */
  7280. observeNewUrl = function(url) {
  7281. if (nextPreviousUrl) {
  7282. previousUrl = nextPreviousUrl;
  7283. nextPreviousUrl = void 0;
  7284. }
  7285. return nextPreviousUrl = url;
  7286. };
  7287. /***
  7288. Replaces the current history entry and updates the
  7289. browser's location bar with the given URL.
  7290. When the user navigates to the replaced history entry at a later time,
  7291. Unpoly will [`replace`](/up.replace) the document body with
  7292. the body from that URL.
  7293. Note that functions like [`up.replace()`](/up.replace) or
  7294. [`up.submit()`](/up.submit) will automatically update the
  7295. browser's location bar for you.
  7296. @function up.history.replace
  7297. @param {string} url
  7298. @internal
  7299. */
  7300. replace = function(url) {
  7301. if (manipulate('replaceState', url)) {
  7302. return up.emit('up:history:replaced', {
  7303. url: url
  7304. });
  7305. }
  7306. };
  7307. /***
  7308. Adds a new history entry and updates the browser's
  7309. address bar with the given URL.
  7310. When the user navigates to the added history entry at a later time,
  7311. Unpoly will [`replace`](/up.replace) the document body with
  7312. the body from that URL.
  7313. Note that functions like [`up.replace()`](/up.replace) or
  7314. [`up.submit()`](/up.submit) will automatically update the
  7315. browser's location bar for you.
  7316. Emits events [`up:history:push`](/up:history:push) and [`up:history:pushed`](/up:history:pushed).
  7317. @function up.history.push
  7318. @param {string} url
  7319. The URL for the history entry to be added.
  7320. @experimental
  7321. */
  7322. push = function(url, options) {
  7323. options = u.options(options, {
  7324. force: false
  7325. });
  7326. url = normalizeUrl(url);
  7327. if ((options.force || !isCurrentUrl(url)) && up.event.nobodyPrevents('up:history:push', {
  7328. url: url,
  7329. log: "Adding history entry for " + url
  7330. })) {
  7331. if (manipulate('pushState', url)) {
  7332. return up.emit('up:history:pushed', {
  7333. url: url,
  7334. log: "Advanced to location " + url
  7335. });
  7336. } else {
  7337. return up.emit('up:history:muted', {
  7338. url: url,
  7339. log: "Did not advance to " + url + " (history is unavailable)"
  7340. });
  7341. }
  7342. }
  7343. };
  7344. /***
  7345. This event is [emitted](/up.emit) before a new history entry is added.
  7346. @event up:history:push
  7347. @param {string} event.url
  7348. The URL for the history entry that is going to be added.
  7349. @param event.preventDefault()
  7350. Event listeners may call this method to prevent the history entry from being added.
  7351. @experimental
  7352. */
  7353. /***
  7354. This event is [emitted](/up.emit) after a new history entry has been added.
  7355. @event up:history:pushed
  7356. @param {string} event.url
  7357. The URL for the history entry that has been added.
  7358. @experimental
  7359. */
  7360. manipulate = function(method, url) {
  7361. var state;
  7362. if (up.browser.canPushState() && config.enabled) {
  7363. state = buildState();
  7364. window.history[method](state, '', url);
  7365. observeNewUrl(currentUrl());
  7366. return true;
  7367. } else {
  7368. return false;
  7369. }
  7370. };
  7371. buildState = function() {
  7372. return {
  7373. fromUp: true
  7374. };
  7375. };
  7376. restoreStateOnPop = function(state) {
  7377. var popSelector, replaced, url;
  7378. if (state != null ? state.fromUp : void 0) {
  7379. url = currentUrl();
  7380. up.emit('up:history:restore', {
  7381. url: url,
  7382. log: "Restoring location " + url
  7383. });
  7384. popSelector = config.popTargets.join(', ');
  7385. replaced = up.replace(popSelector, url, {
  7386. history: false,
  7387. title: true,
  7388. reveal: false,
  7389. saveScroll: false,
  7390. restoreScroll: config.restoreScroll,
  7391. layer: 'page'
  7392. });
  7393. return replaced.then(function() {
  7394. url = currentUrl();
  7395. return up.emit('up:history:restored', {
  7396. url: url,
  7397. log: "Restored location " + url
  7398. });
  7399. });
  7400. } else {
  7401. return up.puts('Ignoring a state not pushed by Unpoly (%o)', state);
  7402. }
  7403. };
  7404. pop = function(event) {
  7405. var state;
  7406. observeNewUrl(currentUrl());
  7407. up.viewport.saveScroll({
  7408. url: previousUrl
  7409. });
  7410. state = event.state;
  7411. return restoreStateOnPop(state);
  7412. };
  7413. /***
  7414. This event is [emitted](/up.emit) before a history entry will be restored.
  7415. History entries are restored when the user uses the *Back* or *Forward* button.
  7416. @event up:history:restore
  7417. @param {string} event.url
  7418. The URL for the history entry that has been restored.
  7419. @internal
  7420. */
  7421. /***
  7422. This event is [emitted](/up.emit) after a history entry has been restored.
  7423. History entries are restored when the user uses the *Back* or *Forward* button.
  7424. @event up:history:restored
  7425. @param {string} event.url
  7426. The URL for the history entry that has been restored.
  7427. @experimental
  7428. */
  7429. up.on('up:app:boot', function() {
  7430. var register;
  7431. if (up.browser.canPushState()) {
  7432. register = function() {
  7433. if (up.browser.canControlScrollRestoration()) {
  7434. window.history.scrollRestoration = 'manual';
  7435. }
  7436. window.addEventListener('popstate', pop);
  7437. return replace(currentUrl(), {
  7438. force: true
  7439. });
  7440. };
  7441. if (typeof jasmine !== "undefined" && jasmine !== null) {
  7442. return register();
  7443. } else {
  7444. return setTimeout(register, 100);
  7445. }
  7446. }
  7447. });
  7448. /***
  7449. Changes the link's destination so it points to the previous URL.
  7450. Note that this will *not* call `location.back()`, but will set
  7451. the link's `up-href` attribute to the actual, previous URL.
  7452. If no previous URL is known, the link will not be changed.
  7453. \#\#\# Example
  7454. This link ...
  7455. <a href="/default" up-back>
  7456. Go back
  7457. </a>
  7458. ... will be transformed to:
  7459. <a href="/default" up-href="/previous-page" up-restore-scroll up-follow>
  7460. Go back
  7461. </a>
  7462. @selector a[up-back]
  7463. @stable
  7464. */
  7465. up.macro('a[up-back], [up-href][up-back]', function(link) {
  7466. if (u.isPresent(previousUrl)) {
  7467. e.setMissingAttrs(link, {
  7468. 'up-href': previousUrl,
  7469. 'up-restore-scroll': ''
  7470. });
  7471. link.removeAttribute('up-back');
  7472. return up.link.makeFollowable(link);
  7473. }
  7474. });
  7475. up.on('up:framework:reset', reset);
  7476. return {
  7477. config: config,
  7478. push: push,
  7479. replace: replace,
  7480. url: currentUrl,
  7481. isUrl: isCurrentUrl,
  7482. previousUrl: function() {
  7483. return previousUrl;
  7484. },
  7485. normalizeUrl: normalizeUrl
  7486. };
  7487. })();
  7488. }).call(this);
  7489. /***
  7490. Scrolling viewports
  7491. ===================
  7492. The `up.viewport` module controls the scroll position of scrollable containers ("viewports").
  7493. The default viewport for any web application is the main document. An application may
  7494. define additional viewports by giving the CSS property `{ overflow-y: scroll }` to any `<div>`.
  7495. \#\#\# Revealing new content
  7496. When following a [link to a fragment](/a-up-target) Unpoly will automatically
  7497. scroll the document's viewport to [reveal](/up.viewport) the updated content.
  7498. You should [make Unpoly aware](/up.viewport.config#config.fixedTop) of fixed elements in your
  7499. layout, such as navigation bars or headers. Unpoly will respect these sticky
  7500. elements when [revealing updated fragments](/up.reveal).
  7501. You should also [tell Unpoly](/up.viewport.config#config.viewports) when your application has more than one viewport,
  7502. so Unpoly can pick the right viewport to scroll for each fragment update.
  7503. \#\#\# Bootstrap integration
  7504. When using Bootstrap integration (`unpoly-bootstrap3.js` and `unpoly-bootstrap3.css`)
  7505. Unpoly will automatically be aware of sticky Bootstrap components such as
  7506. [fixed navbar](https://getbootstrap.com/examples/navbar-fixed-top/).
  7507. @module up.viewport
  7508. */
  7509. (function() {
  7510. var slice = [].slice;
  7511. up.viewport = (function() {
  7512. var absolutize, allSelector, anchoredRight, closest, config, e, finishScrolling, firstHashTarget, fixedElements, getAll, getAround, getRoot, getSubtree, isRoot, lastScrollTops, measureObstruction, pureHash, reset, restoreScroll, reveal, revealHash, rootHasVerticalScrollbar, rootHeight, rootOverflowElement, rootSelector, rootWidth, saveScroll, scroll, scrollAfterInsertFragment, scrollTopKey, scrollTops, scrollbarWidth, scrollingController, u, wasChosenAsOverflowingElement;
  7513. u = up.util;
  7514. e = up.element;
  7515. /***
  7516. Configures the application layout.
  7517. @property up.viewport.config
  7518. @param {Array} [config.viewports]
  7519. An array of CSS selectors that find viewports
  7520. (containers that scroll their contents).
  7521. @param {Array} [config.fixedTop]
  7522. An array of CSS selectors that find elements fixed to the
  7523. top edge of the screen (using `position: fixed`).
  7524. See [`[up-fixed="top"]`](/up-fixed-top) for details.
  7525. @param {Array} [config.fixedBottom]
  7526. An array of CSS selectors that find elements fixed to the
  7527. bottom edge of the screen (using `position: fixed`).
  7528. See [`[up-fixed="bottom"]`](/up-fixed-bottom) for details.
  7529. @param {Array} [config.anchoredRight]
  7530. An array of CSS selectors that find elements anchored to the
  7531. right edge of the screen (using `right:0` with `position: fixed` or `position: absolute`).
  7532. See [`[up-anchored="right"]`](/up-anchored-right) for details.
  7533. @param {number} [config.revealSnap=50]
  7534. When [revealing](/up.reveal) elements, Unpoly will scroll an viewport
  7535. to the top when the revealed element is closer to the top than `config.revealSnap`.
  7536. @param {number} [config.revealPadding=0]
  7537. The desired padding between a [revealed](/up.reveal) element and the
  7538. closest [viewport](/up.viewport) edge (in pixels).
  7539. @param {number} [config.scrollSpeed=1]
  7540. The speed of the scrolling motion when [scrolling](/up.scroll) with `{ behavior: 'smooth' }`.
  7541. The default value (`1`) roughly corresponds to the speed of Chrome's
  7542. [native smooth scrolling](https://developer.mozilla.org/en-US/docs/Web/API/ScrollToOptions/behavior).
  7543. @stable
  7544. */
  7545. config = new up.Config({
  7546. duration: 0,
  7547. viewports: ['.up-modal-viewport', '[up-viewport]', '[up-fixed]'],
  7548. fixedTop: ['[up-fixed~=top]'],
  7549. fixedBottom: ['[up-fixed~=bottom]'],
  7550. anchoredRight: ['[up-anchored~=right]', '[up-fixed~=top]', '[up-fixed~=bottom]', '[up-fixed~=right]'],
  7551. revealSnap: 50,
  7552. revealPadding: 0,
  7553. scrollSpeed: 1
  7554. });
  7555. lastScrollTops = new up.Cache({
  7556. size: 30,
  7557. key: up.history.normalizeUrl
  7558. });
  7559. scrollingController = new up.MotionController('scrolling');
  7560. reset = function() {
  7561. config.reset();
  7562. lastScrollTops.clear();
  7563. return scrollingController.reset();
  7564. };
  7565. /***
  7566. Scrolls the given viewport to the given Y-position.
  7567. A "viewport" is an element that has scrollbars, e.g. `<body>` or
  7568. a container with `overflow-x: scroll`.
  7569. \#\#\# Example
  7570. This will scroll a `<div class="main">...</div>` to a Y-position of 100 pixels:
  7571. up.scroll('.main', 100)
  7572. \#\#\# Animating the scrolling motion
  7573. The scrolling can (optionally) be animated.
  7574. up.scroll('.main', 100, { behavior: 'smooth' })
  7575. If the given viewport is already in a scroll animation when `up.scroll()`
  7576. is called a second time, the previous animation will instantly jump to the
  7577. last frame before the next animation is started.
  7578. @function up.scroll
  7579. @param {string|Element|jQuery} viewport
  7580. The container element to scroll.
  7581. @param {number} scrollPos
  7582. The absolute number of pixels to set the scroll position to.
  7583. @param {string}[options.behavior='auto']
  7584. When set to `'auto'`, this will immediately scroll to the new position.
  7585. When set to `'smooth'`, this will scroll smoothly to the new position.
  7586. @param {number}[options.speed]
  7587. The speed of the scrolling motion when scrolling with `{ behavior: 'smooth' }`.
  7588. Defaults to `up.viewport.config.scrollSpeed`.
  7589. @return {Promise}
  7590. A promise that will be fulfilled when the scrolling ends.
  7591. @experimental
  7592. */
  7593. scroll = function(viewport, scrollTop, options) {
  7594. var motion;
  7595. motion = new up.ScrollMotion(viewport, scrollTop, options);
  7596. return scrollingController.startMotion(viewport, motion, options);
  7597. };
  7598. /***
  7599. Finishes scrolling animations in the given element, its ancestors or its descendants.
  7600. @function up.viewport.finishScrolling
  7601. @param {string|Element|jQuery}
  7602. @return {Promise}
  7603. @internal
  7604. */
  7605. finishScrolling = function(element) {
  7606. var scrollable;
  7607. if (!up.motion.isEnabled()) {
  7608. return Promise.resolve();
  7609. }
  7610. scrollable = closest(element);
  7611. return scrollingController.finish(scrollable);
  7612. };
  7613. /***
  7614. @function up.viewport.anchoredRight
  7615. @internal
  7616. */
  7617. anchoredRight = function() {
  7618. var selector;
  7619. selector = config.anchoredRight.join(',');
  7620. return e.all(selector);
  7621. };
  7622. /***
  7623. @function measureObstruction
  7624. @return {Object}
  7625. @internal
  7626. */
  7627. measureObstruction = function(viewportHeight) {
  7628. var bottomObstructions, bottomObstructors, composeHeight, measureBottomObstructor, measureTopObstructor, topObstructions, topObstructors;
  7629. composeHeight = function(obstructor, distanceFromEdgeProps) {
  7630. var distanceFromEdge;
  7631. distanceFromEdge = u.sum(distanceFromEdgeProps, function(prop) {
  7632. return e.styleNumber(obstructor, prop);
  7633. }) || 0;
  7634. return distanceFromEdge + obstructor.offsetHeight;
  7635. };
  7636. measureTopObstructor = function(obstructor) {
  7637. return composeHeight(obstructor, ['top', 'margin-top']);
  7638. };
  7639. measureBottomObstructor = function(obstructor) {
  7640. return composeHeight(obstructor, ['bottom', 'margin-bottom']);
  7641. };
  7642. topObstructors = e.all(config.fixedTop.join(', '));
  7643. bottomObstructors = e.all(config.fixedBottom.join(', '));
  7644. topObstructions = u.map(topObstructors, measureTopObstructor);
  7645. bottomObstructions = u.map(bottomObstructors, measureBottomObstructor);
  7646. return {
  7647. top: Math.max.apply(Math, [0].concat(slice.call(topObstructions))),
  7648. bottom: Math.max.apply(Math, [0].concat(slice.call(bottomObstructions)))
  7649. };
  7650. };
  7651. /***
  7652. Scroll's the given element's viewport so the first rows of the
  7653. element are visible for the user.
  7654. By default Unpoly will always reveal an element before
  7655. updating it with JavaScript functions like [`up.replace()`](/up.replace)
  7656. or UJS behavior like [`[up-target]`](/a-up-target).
  7657. \#\#\# How Unpoly finds the viewport
  7658. The viewport (the container that is going to be scrolled)
  7659. is the closest parent of the element that is either:
  7660. - the currently open [modal](/up.modal)
  7661. - an element with the attribute `[up-viewport]`
  7662. - the `<body>` element
  7663. - an element matching the selector you have configured using `up.viewport.config.viewports.push('my-custom-selector')`
  7664. \#\#\# Fixed elements obstruction the viewport
  7665. Many applications have a navigation bar fixed to the top or bottom,
  7666. obstructing the view on an element.
  7667. You can make `up.reveal()` aware of these fixed elements
  7668. so it can scroll the viewport far enough so the revealed element is fully visible.
  7669. To make `up.reveal()` aware fixed elements you can either:
  7670. - give the element an attribute [`up-fixed="top"`](/up-fixed-top) or [`up-fixed="bottom"`](up-fixed-bottom)
  7671. - [configure default options](/up.viewport.config) for `fixedTop` or `fixedBottom`
  7672. @function up.reveal
  7673. @param {string|Element|jQuery} element
  7674. @param {number} [options.speed]
  7675. @param {string} [options.snap]
  7676. @param {string|Element|jQuery} [options.viewport]
  7677. @param {boolean} [options.top=false]
  7678. Whether to scroll the viewport so that the first element row aligns
  7679. with the top edge of the viewport.
  7680. @param {string}[options.behavior='auto']
  7681. When set to `'auto'`, this will immediately scroll to the new position.
  7682. When set to `'smooth'`, this will scroll smoothly to the new position.
  7683. @param {number}[options.speed]
  7684. The speed of the scrolling motion when scrolling with `{ behavior: 'smooth' }`.
  7685. Defaults to `up.viewport.config.scrollSpeed`.
  7686. @param {number} [config.padding=0]
  7687. The desired padding between the revealed element and the
  7688. closest [viewport](/up.viewport) edge (in pixels).
  7689. @param {number|boolean} [config.snap]
  7690. Whether to snap to the top of the viewport if the new scroll position
  7691. after revealing the element is close to the top edge.
  7692. You may pass a maximum number of pixels under which to snap to the top.
  7693. Passing `false` will disable snapping.
  7694. Passing `true` will use the snap pixel value from `up.viewport.config.revealSnap`.
  7695. @return {Promise}
  7696. A promise that fulfills when the element is revealed.
  7697. @stable
  7698. */
  7699. reveal = function(elementOrSelector, options) {
  7700. var element, motion;
  7701. element = e.get(elementOrSelector);
  7702. motion = new up.RevealMotion(element, options);
  7703. return scrollingController.startMotion(element, motion, options);
  7704. };
  7705. /***
  7706. @function up.viewport.scrollAfterInsertFragment
  7707. @param {boolean|object} [options.restoreScroll]
  7708. @param {boolean|string|jQuery|Element} [options.reveal]
  7709. @param {boolean|string} [options.reveal]
  7710. @return {Promise}
  7711. A promise that is fulfilled when the scrolling has finished.
  7712. @internal
  7713. */
  7714. scrollAfterInsertFragment = function(element, options) {
  7715. var givenTops, hashOpt, restoreScrollOpt, revealOpt, scrollOptions, selector;
  7716. if (options == null) {
  7717. options = {};
  7718. }
  7719. hashOpt = options.hash;
  7720. revealOpt = options.reveal;
  7721. restoreScrollOpt = options.restoreScroll;
  7722. scrollOptions = u.only(options, 'scrollBehavior', 'scrollSpeed');
  7723. if (restoreScrollOpt) {
  7724. givenTops = u.presence(restoreScrollOpt, u.isObject);
  7725. return restoreScroll({
  7726. around: element,
  7727. scrollTops: givenTops
  7728. });
  7729. } else if (hashOpt && revealOpt === true) {
  7730. return revealHash(hashOpt, scrollOptions);
  7731. } else if (revealOpt) {
  7732. if (u.isElement(revealOpt) || u.isJQuery(revealOpt)) {
  7733. element = e.get(revealOpt);
  7734. } else if (u.isString(revealOpt)) {
  7735. selector = e.resolveSelector(revealOpt, options.origin);
  7736. element = up.fragment.first(selector);
  7737. } else {
  7738. }
  7739. if (element) {
  7740. return reveal(element, scrollOptions);
  7741. }
  7742. } else {
  7743. return Promise.resolve();
  7744. }
  7745. };
  7746. /***
  7747. [Reveals](/up.reveal) an element matching the given `#hash` anchor.
  7748. Other than the default behavior found in browsers, `up.revealHash` works with
  7749. [multiple viewports](/up-viewport) and honors [fixed elements](/up-fixed-top) obstructing the user's
  7750. view of the viewport.
  7751. When the page loads initially, this function is automatically called with the hash from
  7752. the current URL.
  7753. If no element matches the given `#hash` anchor, a resolved promise is returned.
  7754. \#\#\# Example
  7755. up.revealHash('#chapter2')
  7756. @function up.viewport.revealHash
  7757. @param {string} hash
  7758. @return {Promise}
  7759. A promise that is fulfilled when scroll position has changed to match the location hash.
  7760. @experimental
  7761. */
  7762. revealHash = function(hash) {
  7763. var match;
  7764. if (hash && (match = firstHashTarget(hash))) {
  7765. return reveal(match, {
  7766. top: true
  7767. });
  7768. } else {
  7769. return Promise.resolve();
  7770. }
  7771. };
  7772. allSelector = function() {
  7773. return [rootSelector()].concat(slice.call(config.viewports)).join(',');
  7774. };
  7775. /***
  7776. Returns the scrolling container for the given element.
  7777. Returns the [document's scrolling element](/up.viewport.root)
  7778. if no closer viewport exists.
  7779. @function up.viewport.closest
  7780. @param {string|Element|jQuery} selectorOrElement
  7781. @return {Element}
  7782. @experimental
  7783. */
  7784. closest = function(selectorOrElement) {
  7785. var element;
  7786. element = e.get(selectorOrElement);
  7787. return e.closest(element, allSelector());
  7788. };
  7789. /***
  7790. Returns a jQuery collection of all the viewports contained within the
  7791. given selector or element.
  7792. @function up.viewport.subtree
  7793. @param {string|Element|jQuery} selectorOrElement
  7794. @return List<Element>
  7795. @internal
  7796. */
  7797. getSubtree = function(selectorOrElement) {
  7798. var element;
  7799. element = e.get(selectorOrElement);
  7800. return e.subtree(element, allSelector());
  7801. };
  7802. getAround = function(selectorOrElement) {
  7803. var element;
  7804. element = e.get(selectorOrElement);
  7805. return e.list(closest(element), getSubtree(element));
  7806. };
  7807. /***
  7808. Returns a list of all the viewports on the screen.
  7809. @function up.viewport.all
  7810. @internal
  7811. */
  7812. getAll = function() {
  7813. return e.all(allSelector());
  7814. };
  7815. rootSelector = function() {
  7816. var element;
  7817. if (element = document.scrollingElement) {
  7818. return element.tagName;
  7819. } else {
  7820. return 'html';
  7821. }
  7822. };
  7823. /***
  7824. Return the [scrolling element](https://developer.mozilla.org/en-US/docs/Web/API/document/scrollingElement)
  7825. for the browser's main content area.
  7826. @function up.viewport.root
  7827. @return {Element}
  7828. @experimental
  7829. */
  7830. getRoot = function() {
  7831. return document.querySelector(rootSelector());
  7832. };
  7833. rootWidth = function() {
  7834. return e.root().clientWidth;
  7835. };
  7836. rootHeight = function() {
  7837. return e.root().clientHeight;
  7838. };
  7839. isRoot = function(element) {
  7840. return e.matches(element, rootSelector());
  7841. };
  7842. /***
  7843. Returns whether the given element is currently showing a vertical scrollbar.
  7844. @function up.viewport.rootHasVerticalScrollbar
  7845. @internal
  7846. */
  7847. rootHasVerticalScrollbar = function() {
  7848. return window.innerWidth > document.documentElement.offsetWidth;
  7849. };
  7850. /***
  7851. Returns the element that controls the `overflow-y` behavior for the
  7852. [document viewport](/up.viewport.root()).
  7853. @function up.viewport.rootOverflowElement
  7854. @internal
  7855. */
  7856. rootOverflowElement = function() {
  7857. var body, element, html;
  7858. body = document.body;
  7859. html = document.documentElement;
  7860. element = u.find([html, body], wasChosenAsOverflowingElement);
  7861. return element || getRoot();
  7862. };
  7863. /***
  7864. Returns whether the given element was chosen as the overflowing
  7865. element by the developer.
  7866. We have no control whether developers set the property on <body> or
  7867. <html>. The developer also won't know what is going to be the
  7868. [scrolling element](/up.viewport.root()) on the user's brower.
  7869. @function wasChosenAsOverflowingElement
  7870. @internal
  7871. */
  7872. wasChosenAsOverflowingElement = function(element) {
  7873. var overflowY;
  7874. overflowY = e.style(element, 'overflow-y');
  7875. return overflowY === 'auto' || overflowY === 'scroll';
  7876. };
  7877. /***
  7878. Returns the width of a scrollbar.
  7879. This only runs once per page load.
  7880. @function up.viewport.scrollbarWidth
  7881. @internal
  7882. */
  7883. scrollbarWidth = u.memoize(function() {
  7884. var outer, outerStyle, width;
  7885. outerStyle = {
  7886. position: 'absolute',
  7887. top: '0',
  7888. left: '0',
  7889. width: '100px',
  7890. height: '100px',
  7891. overflowY: 'scroll'
  7892. };
  7893. outer = up.element.affix(document.body, '[up-viewport]', {
  7894. style: outerStyle
  7895. });
  7896. width = outer.offsetWidth - outer.clientWidth;
  7897. up.element.remove(outer);
  7898. return width;
  7899. });
  7900. scrollTopKey = function(viewport) {
  7901. return e.toSelector(viewport);
  7902. };
  7903. /***
  7904. Returns a hash with scroll positions.
  7905. Each key in the hash is a viewport selector. The corresponding
  7906. value is the viewport's top scroll position:
  7907. up.viewport.scrollTops()
  7908. => { '.main': 0, '.sidebar': 73 }
  7909. @function up.viewport.scrollTops
  7910. @return Object<string, number>
  7911. @internal
  7912. */
  7913. scrollTops = function() {
  7914. return u.mapObject(getAll(), function(viewport) {
  7915. return [scrollTopKey(viewport), viewport.scrollTop];
  7916. });
  7917. };
  7918. /***
  7919. @function up.viewport.fixedElements
  7920. @internal
  7921. */
  7922. fixedElements = function(root) {
  7923. var queryParts;
  7924. if (root == null) {
  7925. root = document;
  7926. }
  7927. queryParts = ['[up-fixed]'].concat(config.fixedTop).concat(config.fixedBottom);
  7928. return root.querySelectorAll(queryParts.join(','));
  7929. };
  7930. /***
  7931. Saves the top scroll positions of all the
  7932. viewports configured in [`up.viewport.config.viewports`](/up.viewport.config).
  7933. The scroll positions will be associated with the current URL.
  7934. They can later be restored by calling [`up.viewport.restoreScroll()`](/up.viewport.restoreScroll)
  7935. at the same URL, or by following a link with an [`[up-restore-scroll]`](/a-up-follow#up-restore-scroll)
  7936. attribute.
  7937. Unpoly automatically saves scroll positions before a [fragment update](/up.replace)
  7938. you will rarely need to call this function yourself.
  7939. \#\#\# Examples
  7940. Should you need to save the current scroll positions outside of a [fragment update](/up.replace),
  7941. you may call:
  7942. up.viewport.saveScroll()
  7943. Instead of saving the current scroll positions for the current URL, you may also pass another
  7944. url or vertical scroll positionsfor each viewport:
  7945. up.viewport.saveScroll({
  7946. url: '/inbox',
  7947. tops: {
  7948. 'body': 0,
  7949. '.sidebar', 100,
  7950. '.main', 320
  7951. }
  7952. })
  7953. @function up.viewport.saveScroll
  7954. @param {string} [options.url]
  7955. The URL for which to save scroll positions.
  7956. If omitted, the current browser location is used.
  7957. @param {Object<string, number>} [options.tops]
  7958. An object mapping viewport selectors to vertical scroll positions in pixels.
  7959. @experimental
  7960. */
  7961. saveScroll = function(options) {
  7962. var ref, ref1, tops, url;
  7963. if (options == null) {
  7964. options = {};
  7965. }
  7966. url = (ref = options.url) != null ? ref : up.history.url();
  7967. tops = (ref1 = options.tops) != null ? ref1 : scrollTops();
  7968. return lastScrollTops.set(url, tops);
  7969. };
  7970. /***
  7971. Restores [previously saved](/up.viewport.saveScroll) scroll positions of viewports
  7972. viewports configured in [`up.viewport.config.viewports`](/up.viewport.config).
  7973. Unpoly automatically restores scroll positions when the user presses the back button.
  7974. You can disable this behavior by setting [`up.history.config.restoreScroll = false`](/up.history.config).
  7975. @function up.viewport.restoreScroll
  7976. @param {Element} [options.around]
  7977. If set, only restores viewports that are either an ancestor
  7978. or descendant of the given element.
  7979. @return {Promise}
  7980. A promise that will be fulfilled once scroll positions have been restored.
  7981. @experimental
  7982. */
  7983. restoreScroll = function(options) {
  7984. var scrollTopsForUrl, url, viewports;
  7985. if (options == null) {
  7986. options = {};
  7987. }
  7988. url = up.history.url();
  7989. viewports = options.around ? getAround(options.around) : getAll();
  7990. scrollTopsForUrl = options.scrollTops || lastScrollTops.get(url) || {};
  7991. return up.log.group('Restoring scroll positions for URL %s to %o', url, scrollTopsForUrl, function() {
  7992. var allScrollPromises;
  7993. allScrollPromises = u.map(viewports, function(viewport) {
  7994. var key, scrollTop;
  7995. key = scrollTopKey(viewport);
  7996. scrollTop = scrollTopsForUrl[key] || 0;
  7997. return scroll(viewport, scrollTop, {
  7998. duration: 0
  7999. });
  8000. });
  8001. return Promise.all(allScrollPromises);
  8002. });
  8003. };
  8004. /***
  8005. @internal
  8006. */
  8007. absolutize = function(elementOrSelector, options) {
  8008. var bounds, boundsRect, element, moveBounds, newElementRect, originalRect, viewport, viewportRect;
  8009. if (options == null) {
  8010. options = {};
  8011. }
  8012. element = e.get(elementOrSelector);
  8013. viewport = up.viewport.closest(element);
  8014. viewportRect = viewport.getBoundingClientRect();
  8015. originalRect = element.getBoundingClientRect();
  8016. boundsRect = new up.Rect({
  8017. left: originalRect.left - viewportRect.left,
  8018. top: originalRect.top - viewportRect.top,
  8019. width: originalRect.width,
  8020. height: originalRect.height
  8021. });
  8022. if (typeof options.afterMeasure === "function") {
  8023. options.afterMeasure();
  8024. }
  8025. e.setStyle(element, {
  8026. position: element.style.position === 'static' ? 'static' : 'relative',
  8027. top: 'auto',
  8028. right: 'auto',
  8029. bottom: 'auto',
  8030. left: 'auto',
  8031. width: '100%',
  8032. height: '100%'
  8033. });
  8034. bounds = e.createFromSelector('.up-bounds');
  8035. e.insertBefore(element, bounds);
  8036. bounds.appendChild(element);
  8037. moveBounds = function(diffX, diffY) {
  8038. boundsRect.left += diffX;
  8039. boundsRect.top += diffY;
  8040. return e.setStyle(bounds, boundsRect);
  8041. };
  8042. moveBounds(0, 0);
  8043. newElementRect = element.getBoundingClientRect();
  8044. moveBounds(originalRect.left - newElementRect.left, originalRect.top - newElementRect.top);
  8045. u.each(fixedElements(element), e.fixedToAbsolute);
  8046. return {
  8047. bounds: bounds,
  8048. moveBounds: moveBounds
  8049. };
  8050. };
  8051. /***
  8052. Marks this element as a scrolling container ("viewport").
  8053. Apply this attribute if your app uses a custom panel layout with fixed positioning
  8054. instead of scrolling `<body>`. As an alternative you can also push a selector
  8055. matching your custom viewport to the [`up.viewport.config.viewports`](/up.viewport.config) array.
  8056. [`up.reveal()`](/up.reveal) will always try to scroll the viewport closest
  8057. to the element that is being revealed. By default this is the `<body>` element.
  8058. \#\#\# Example
  8059. Here is an example for a layout for an e-mail client, showing a list of e-mails
  8060. on the left side and the e-mail text on the right side:
  8061. .side {
  8062. position: fixed;
  8063. top: 0;
  8064. bottom: 0;
  8065. left: 0;
  8066. width: 100px;
  8067. overflow-y: scroll;
  8068. }
  8069. .main {
  8070. position: fixed;
  8071. top: 0;
  8072. bottom: 0;
  8073. left: 100px;
  8074. right: 0;
  8075. overflow-y: scroll;
  8076. }
  8077. This would be the HTML (notice the `up-viewport` attribute):
  8078. <div class=".side" up-viewport>
  8079. <a href="/emails/5001" up-target=".main">Re: Your invoice</a>
  8080. <a href="/emails/2023" up-target=".main">Quote for services</a>
  8081. <a href="/emails/9002" up-target=".main">Fwd: Room reservation</a>
  8082. </div>
  8083. <div class="main" up-viewport>
  8084. <h1>Re: Your Invoice</h1>
  8085. <p>
  8086. Lorem ipsum dolor sit amet, consetetur sadipscing elitr.
  8087. Stet clita kasd gubergren, no sea takimata sanctus est.
  8088. </p>
  8089. </div>
  8090. @selector [up-viewport]
  8091. @stable
  8092. */
  8093. /***
  8094. Marks this element as being fixed to the top edge of the screen
  8095. using `position: fixed`.
  8096. When [following a fragment link](/a-up-target), the viewport is scrolled
  8097. so the targeted element becomes visible. By using this attribute you can make
  8098. Unpoly aware of fixed elements that are obstructing the viewport contents.
  8099. Unpoly will then scroll the viewport far enough that the revealed element is fully visible.
  8100. Instead of using this attribute,
  8101. you can also configure a selector in [`up.viewport.config.fixedTop`](/up.viewport.config#config.fixedTop).
  8102. \#\#\# Example
  8103. <div class="top-nav" up-fixed="top">...</div>
  8104. @selector [up-fixed=top]
  8105. @stable
  8106. */
  8107. /***
  8108. Marks this element as being fixed to the bottom edge of the screen
  8109. using `position: fixed`.
  8110. When [following a fragment link](/a-up-target), the viewport is scrolled
  8111. so the targeted element becomes visible. By using this attribute you can make
  8112. Unpoly aware of fixed elements that are obstructing the viewport contents.
  8113. Unpoly will then scroll the viewport far enough that the revealed element is fully visible.
  8114. Instead of using this attribute,
  8115. you can also configure a selector in [`up.viewport.config.fixedBottom`](/up.viewport.config#config.fixedBottom).
  8116. \#\#\# Example
  8117. <div class="bottom-nav" up-fixed="bottom">...</div>
  8118. @selector [up-fixed=bottom]
  8119. @stable
  8120. */
  8121. /***
  8122. Marks this element as being anchored to the right edge of the screen,
  8123. typically fixed navigation bars.
  8124. Since [modal dialogs](/up.modal) hide the document scroll bar,
  8125. elements anchored to the right appear to jump when the dialog opens or
  8126. closes. Applying this attribute to anchored elements will make Unpoly
  8127. aware of the issue and adjust the `right` property accordingly.
  8128. You should give this attribute to layout elements
  8129. with a CSS of `right: 0` with `position: fixed` or `position:absolute`.
  8130. Instead of giving this attribute to any affected element,
  8131. you can also configure a selector in [`up.viewport.config.anchoredRight`](/up.viewport.config#config.anchoredRight).
  8132. \#\#\# Example
  8133. Here is the CSS for a navigation bar that is anchored to the top edge of the screen:
  8134. .top-nav {
  8135. position: fixed;
  8136. top: 0;
  8137. left: 0;
  8138. right: 0;
  8139. }
  8140. By adding an `up-anchored="right"` attribute to the element, we can prevent the
  8141. `right` edge from jumping when a [modal dialog](/up.modal) opens or closes:
  8142. <div class="top-nav" up-anchored="right">...</div>
  8143. @selector [up-anchored=right]
  8144. @stable
  8145. */
  8146. /***
  8147. @function up.viewport.firstHashTarget
  8148. @internal
  8149. */
  8150. firstHashTarget = function(hash) {
  8151. var selector;
  8152. if (hash = pureHash(hash)) {
  8153. selector = [e.attributeSelector('up-id', hash), e.attributeSelector('id', hash), 'a' + e.attributeSelector('name', hash)].join(',');
  8154. return up.fragment.first(selector);
  8155. }
  8156. };
  8157. /***
  8158. Returns `'foo'` if the hash is `'#foo'`.
  8159. Returns undefined if the hash is `'#'`, `''` or `undefined`.
  8160. @function pureHash
  8161. @internal
  8162. */
  8163. pureHash = function(value) {
  8164. if (value && value[0] === '#') {
  8165. value = value.substr(1);
  8166. }
  8167. return u.presence(value);
  8168. };
  8169. up.on('up:app:booted', function() {
  8170. return revealHash(location.hash);
  8171. });
  8172. up.on('up:framework:reset', reset);
  8173. return {
  8174. reveal: reveal,
  8175. revealHash: revealHash,
  8176. firstHashTarget: firstHashTarget,
  8177. scroll: scroll,
  8178. config: config,
  8179. closest: closest,
  8180. subtree: getSubtree,
  8181. around: getAround,
  8182. all: getAll,
  8183. rootSelector: rootSelector,
  8184. root: getRoot,
  8185. rootWidth: rootWidth,
  8186. rootHeight: rootHeight,
  8187. rootHasVerticalScrollbar: rootHasVerticalScrollbar,
  8188. rootOverflowElement: rootOverflowElement,
  8189. isRoot: isRoot,
  8190. scrollbarWidth: scrollbarWidth,
  8191. scrollTops: scrollTops,
  8192. saveScroll: saveScroll,
  8193. restoreScroll: restoreScroll,
  8194. scrollAfterInsertFragment: scrollAfterInsertFragment,
  8195. anchoredRight: anchoredRight,
  8196. fixedElements: fixedElements,
  8197. absolutize: absolutize
  8198. };
  8199. })();
  8200. up.scroll = up.viewport.scroll;
  8201. up.reveal = up.viewport.reveal;
  8202. up.revealHash = up.viewport.revealHash;
  8203. up.legacy.renamedModule('layout', 'viewport');
  8204. }).call(this);
  8205. /***
  8206. Fragment update API
  8207. ===================
  8208. The `up.fragment` module exposes a high-level Javascript API to [update](/up.replace) or
  8209. [destroy](/up.destroy) page fragments.
  8210. Fragments are [compiled](/up.compiler) elements that can be updated from a server URL.
  8211. They also exist on a layer (page, modal, popup).
  8212. Most of Unpoly's functionality (like [fragment links](/up.link) or [modals](/up.modal))
  8213. is built from `up.fragment` functions. You may use them to extend Unpoly from your
  8214. [custom Javascript](/up.syntax).
  8215. @module up.fragment
  8216. */
  8217. (function() {
  8218. var slice = [].slice;
  8219. up.fragment = (function() {
  8220. var bestMatchingSteps, bestPreflightSelector, config, createPlaceholder, destroy, e, emitFragmentDestroyed, emitFragmentInserted, emitFragmentKept, extract, findKeepPlan, first, firstInLayer, firstInPriority, hello, isRealElement, layerOf, markElementAsDestroying, matchesLayer, processResponse, reload, replace, reset, setSource, shouldExtractTitle, shouldLogDestruction, source, swapElements, transferKeepableElements, u, updateHistoryAndTitle;
  8221. u = up.util;
  8222. e = up.element;
  8223. /***
  8224. Configures defaults for fragment insertion.
  8225. @property up.fragment.config
  8226. @param {string} [options.fallbacks=['body']]
  8227. When a fragment updates cannot find the requested element, Unpoly will try this list of alternative selectors.
  8228. The first selector that matches an element in the current page (or response) will be used.
  8229. If the response contains none of the selectors, an error message will be shown.
  8230. It is recommend to always keep `'body'` as the last selector in the last in the case
  8231. your server or load balancer renders an error message that does not contain your
  8232. application layout.
  8233. @param {string} [options.fallbackTransition=null]
  8234. The transition to use when using a [fallback target](/#options.fallbacks).
  8235. By default this is not set and the original replacement's transition is used.
  8236. @stable
  8237. */
  8238. config = new up.Config({
  8239. fallbacks: ['body'],
  8240. fallbackTransition: null
  8241. });
  8242. reset = function() {
  8243. return config.reset();
  8244. };
  8245. setSource = function(element, sourceUrl) {
  8246. if (sourceUrl !== false) {
  8247. if (u.isPresent(sourceUrl)) {
  8248. sourceUrl = u.normalizeUrl(sourceUrl);
  8249. }
  8250. return element.setAttribute("up-source", sourceUrl);
  8251. }
  8252. };
  8253. /***
  8254. Returns the URL the given element was retrieved from.
  8255. @method up.fragment.source
  8256. @param {string|Element|jQuery} selectorOrElement
  8257. @experimental
  8258. */
  8259. source = function(selectorOrElement) {
  8260. var element;
  8261. element = e.get(selectorOrElement);
  8262. if (element = e.closest(element, '[up-source]')) {
  8263. return element.getAttribute("up-source");
  8264. } else {
  8265. return up.browser.url();
  8266. }
  8267. };
  8268. /***
  8269. Replaces elements on the current page with corresponding elements
  8270. from a new page fetched from the server.
  8271. The current and new elements must both match the given CSS selector.
  8272. The unobtrusive variant of this is the [`a[up-target]`](/a-up-target) selector.
  8273. \#\#\# Example
  8274. Let's say your current HTML looks like this:
  8275. <div class="one">old one</div>
  8276. <div class="two">old two</div>
  8277. We now replace the second `<div>`:
  8278. up.replace('.two', '/new')
  8279. The server renders a response for `/new`:
  8280. <div class="one">new one</div>
  8281. <div class="two">new two</div>
  8282. Unpoly looks for the selector `.two` in the response and [implants](/up.extract) it into
  8283. the current page. The current page now looks like this:
  8284. <div class="one">old one</div>
  8285. <div class="two">new two</div>
  8286. Note how only `.two` has changed. The update for `.one` was
  8287. discarded, since it didn't match the selector.
  8288. \#\#\# Appending or prepending instead of replacing
  8289. By default Unpoly will replace the given selector with the same
  8290. selector from a freshly fetched page. Instead of replacing you
  8291. can *append* the loaded content to the existing content by using the
  8292. `:after` pseudo selector. In the same fashion, you can use `:before`
  8293. to indicate that you would like the *prepend* the loaded content.
  8294. A practical example would be a paginated list of items:
  8295. <ul class="tasks">
  8296. <li>Wash car</li>
  8297. <li>Purchase supplies</li>
  8298. <li>Fix tent</li>
  8299. </ul>
  8300. In order to append more items from a URL, replace into
  8301. the `.tasks:after` selector:
  8302. up.replace('.tasks:after', '/page/2')
  8303. \#\#\# Setting the window title from the server
  8304. If the `replace` call changes history, the document title will be set
  8305. to the contents of a `<title>` tag in the response.
  8306. The server can also change the document title by setting
  8307. an `X-Up-Title` header in the response.
  8308. \#\#\# Optimizing response rendering
  8309. The server is free to optimize Unpoly requests by only rendering the HTML fragment
  8310. that is being updated. The request's `X-Up-Target` header will contain
  8311. the CSS selector for the updating fragment.
  8312. If you are using the `unpoly-rails` gem you can also access the selector via
  8313. `up.target` in all controllers, views and helpers.
  8314. \#\#\# Events
  8315. Unpoly will emit [`up:fragment:destroyed`](/up:fragment:destroyed) on the element
  8316. that was replaced and [`up:fragment:inserted`](/up:fragment:inserted) on the new
  8317. element that replaces it.
  8318. @function up.replace
  8319. @param {string|Element|jQuery} selectorOrElement
  8320. The CSS selector to update. You can also pass a DOM element or jQuery element
  8321. here, in which case a selector will be inferred from the element's class and ID.
  8322. @param {string} url
  8323. The URL to fetch from the server.
  8324. @param {string} [options.failTarget]
  8325. The CSS selector to update if the server sends a non-200 status code.
  8326. @param {string} [options.fallback]
  8327. The selector to update when the original target was not found in the page.
  8328. @param {string} [options.title]
  8329. The document title after the replacement.
  8330. If the call pushes an history entry and this option is missing, the title is extracted from the response's `<title>` tag.
  8331. You can also pass `false` to explicitly prevent the title from being updated.
  8332. @param {string} [options.method='get']
  8333. The HTTP method to use for the request.
  8334. @param {Object|FormData|string|Array} [options.params]
  8335. [Parameters](/up.Params) that should be sent as the request's payload.
  8336. @param {string} [options.transition='none']
  8337. @param {string|boolean} [options.history=true]
  8338. If a string is given, it is used as the URL the browser's location bar and history.
  8339. If omitted or true, the `url` argument will be used.
  8340. If set to `false`, the history will remain unchanged.
  8341. @param {boolean|string} [options.source=true]
  8342. @param {boolean|string} [options.reveal=false]
  8343. Whether to [reveal](/up.reveal) the new fragment.
  8344. You can also pass a CSS selector for the element to reveal.
  8345. @param {boolean|string} [options.failReveal=false]
  8346. Whether to [reveal](/up.reveal) the new fragment when the server responds with an error.
  8347. You can also pass a CSS selector for the element to reveal.
  8348. @param {number} [options.revealPadding]
  8349. @param {boolean} [options.restoreScroll=false]
  8350. If set to true, Unpoly will try to restore the scroll position
  8351. of all the viewports around or below the updated element. The position
  8352. will be reset to the last known top position before a previous
  8353. history change for the current URL.
  8354. @param {boolean} [options.cache]
  8355. Whether to use a [cached response](/up.proxy) if available.
  8356. @param {string} [options.historyMethod='push']
  8357. @param {Object} [options.headers={}]
  8358. An object of additional header key/value pairs to send along
  8359. with the request.
  8360. @param {Element|jQuery} [options.origin]
  8361. The element that triggered the replacement.
  8362. The element's selector will be substituted for the `&` shorthand in the target selector ([like in Sass](https://sass-lang.com/documentation/file.SASS_REFERENCE.html#parent-selector)).
  8363. @param {string} [options.layer='auto']
  8364. The name of the layer that ought to be updated. Valid values are
  8365. `'auto'`, `'page'`, `'modal'` and `'popup'`.
  8366. If set to `'auto'` (default), Unpoly will try to find a match in the
  8367. same layer as the element that triggered the replacement (see `options.origin`).
  8368. If that element is not known, or no match was found in that layer,
  8369. Unpoly will search in other layers, starting from the topmost layer.
  8370. @param {string} [options.failLayer='auto']
  8371. The name of the layer that ought to be updated if the server sends a non-200 status code.
  8372. @param {boolean} [options.keep=true]
  8373. Whether this replacement will preserve [`[up-keep]`](/up-keep) elements.
  8374. @param {boolean} [options.hungry=true]
  8375. Whether this replacement will update [`[up-hungry]`](/up-hungry) elements.
  8376. @return {Promise}
  8377. A promise that will be fulfilled when the page has been updated.
  8378. @stable
  8379. */
  8380. replace = function(selectorOrElement, url, options) {
  8381. var error, failureOptions, fullLoad, improvedFailTarget, improvedTarget, onFailure, onSuccess, promise, request, requestAttrs, successOptions;
  8382. options = u.options(options);
  8383. options.inspectResponse = fullLoad = function() {
  8384. return up.browser.navigate(url, u.only(options, 'method', 'params'));
  8385. };
  8386. if (!up.browser.canPushState() && options.history !== false) {
  8387. if (!options.preload) {
  8388. fullLoad();
  8389. }
  8390. return u.unresolvablePromise();
  8391. }
  8392. successOptions = u.merge(options, {
  8393. humanizedTarget: 'target'
  8394. });
  8395. failureOptions = u.merge(options, {
  8396. humanizedTarget: 'failure target',
  8397. provideTarget: void 0,
  8398. restoreScroll: false
  8399. });
  8400. u.renameKey(failureOptions, 'failTransition', 'transition');
  8401. u.renameKey(failureOptions, 'failLayer', 'layer');
  8402. u.renameKey(failureOptions, 'failReveal', 'reveal');
  8403. try {
  8404. improvedTarget = bestPreflightSelector(selectorOrElement, successOptions);
  8405. improvedFailTarget = bestPreflightSelector(options.failTarget, failureOptions);
  8406. } catch (error1) {
  8407. error = error1;
  8408. return Promise.reject(error);
  8409. }
  8410. requestAttrs = u.only(options, 'method', 'data', 'params', 'cache', 'preload', 'headers', 'timeout');
  8411. u.assign(requestAttrs, {
  8412. url: url,
  8413. target: improvedTarget,
  8414. failTarget: improvedFailTarget
  8415. });
  8416. request = new up.Request(requestAttrs);
  8417. onSuccess = function(response) {
  8418. return processResponse(true, improvedTarget, request, response, successOptions);
  8419. };
  8420. onFailure = function(response) {
  8421. var promise, rejection;
  8422. rejection = function() {
  8423. return Promise.reject(response);
  8424. };
  8425. if (response.isFatalError()) {
  8426. return rejection();
  8427. } else {
  8428. promise = processResponse(false, improvedFailTarget, request, response, failureOptions);
  8429. return u.always(promise, rejection);
  8430. }
  8431. };
  8432. promise = up.request(request);
  8433. if (!options.preload) {
  8434. promise = promise.then(onSuccess, onFailure);
  8435. }
  8436. return promise;
  8437. };
  8438. /***
  8439. @internal
  8440. */
  8441. processResponse = function(isSuccess, selector, request, response, options) {
  8442. var hash, historyUrl, isReloadable, sourceUrl;
  8443. sourceUrl = response.url;
  8444. historyUrl = sourceUrl;
  8445. if (hash = request.hash) {
  8446. options.hash = hash;
  8447. historyUrl += hash;
  8448. }
  8449. isReloadable = response.method === 'GET';
  8450. if (isSuccess) {
  8451. if (isReloadable) {
  8452. if (!(options.history === false || u.isString(options.history))) {
  8453. options.history = historyUrl;
  8454. }
  8455. if (!(options.source === false || u.isString(options.source))) {
  8456. options.source = sourceUrl;
  8457. }
  8458. } else {
  8459. if (!u.isString(options.history)) {
  8460. options.history = false;
  8461. }
  8462. if (!u.isString(options.source)) {
  8463. options.source = 'keep';
  8464. }
  8465. }
  8466. } else {
  8467. if (isReloadable) {
  8468. if (options.history !== false) {
  8469. options.history = historyUrl;
  8470. }
  8471. if (options.source !== false) {
  8472. options.source = sourceUrl;
  8473. }
  8474. } else {
  8475. options.history = false;
  8476. options.source = 'keep';
  8477. }
  8478. }
  8479. if (shouldExtractTitle(options) && response.title) {
  8480. options.title = response.title;
  8481. }
  8482. return extract(selector, response.text, options);
  8483. };
  8484. shouldExtractTitle = function(options) {
  8485. return !(options.title === false || u.isString(options.title) || (options.history === false && options.title !== true));
  8486. };
  8487. /***
  8488. Updates a selector on the current page with the
  8489. same selector from the given HTML string.
  8490. \#\#\# Example
  8491. Let's say your current HTML looks like this:
  8492. <div class="one">old one</div>
  8493. <div class="two">old two</div>
  8494. We now replace the second `<div>`, using an HTML string
  8495. as the source:
  8496. html = '<div class="one">new one</div>' +
  8497. '<div class="two">new two</div>';
  8498. up.extract('.two', html)
  8499. Unpoly looks for the selector `.two` in the strings and updates its
  8500. contents in the current page. The current page now looks like this:
  8501. <div class="one">old one</div>
  8502. <div class="two">new two</div>
  8503. Note how only `.two` has changed. The update for `.one` was
  8504. discarded, since it didn't match the selector.
  8505. @function up.extract
  8506. @param {string|Element|jQuery} selectorOrElement
  8507. @param {string} html
  8508. @param {Object} [options]
  8509. See options for [`up.replace()`](/up.replace).
  8510. @return {Promise}
  8511. A promise that will be fulfilled then the selector was updated
  8512. and all animation has finished.
  8513. @stable
  8514. */
  8515. extract = function(selectorOrElement, html, options) {
  8516. return up.log.group('Extracting %s from %d bytes of HTML', selectorOrElement, html != null ? html.length : void 0, function() {
  8517. options = u.options(options, {
  8518. historyMethod: 'push',
  8519. keep: true,
  8520. layer: 'auto'
  8521. });
  8522. if (options.saveScroll !== false) {
  8523. up.viewport.saveScroll();
  8524. }
  8525. return u.rejectOnError(function() {
  8526. var extractSteps, i, len, responseDoc, responseTitle, step, swapPromises;
  8527. if (typeof options.provideTarget === "function") {
  8528. options.provideTarget();
  8529. }
  8530. responseDoc = new up.HtmlParser(html);
  8531. extractSteps = bestMatchingSteps(selectorOrElement, responseDoc, options);
  8532. if (shouldExtractTitle(options) && (responseTitle = responseDoc.title())) {
  8533. options.title = responseTitle;
  8534. }
  8535. updateHistoryAndTitle(options);
  8536. swapPromises = [];
  8537. for (i = 0, len = extractSteps.length; i < len; i++) {
  8538. step = extractSteps[i];
  8539. up.log.group('Swapping fragment %s', step.selector, function() {
  8540. var swapOptions, swapPromise;
  8541. swapOptions = u.merge(options, u.only(step, 'origin', 'reveal'));
  8542. responseDoc.prepareForInsertion(step.newElement);
  8543. swapPromise = swapElements(step.oldElement, step.newElement, step.pseudoClass, step.transition, swapOptions);
  8544. return swapPromises.push(swapPromise);
  8545. });
  8546. }
  8547. return Promise.all(swapPromises);
  8548. });
  8549. });
  8550. };
  8551. bestPreflightSelector = function(selectorOrElement, options) {
  8552. var cascade;
  8553. cascade = new up.ExtractCascade(selectorOrElement, options);
  8554. return cascade.bestPreflightSelector();
  8555. };
  8556. bestMatchingSteps = function(selectorOrElement, response, options) {
  8557. var cascade;
  8558. options = u.merge(options, {
  8559. response: response
  8560. });
  8561. cascade = new up.ExtractCascade(selectorOrElement, options);
  8562. return cascade.bestMatchingSteps();
  8563. };
  8564. updateHistoryAndTitle = function(options) {
  8565. options = u.options(options, {
  8566. historyMethod: 'push'
  8567. });
  8568. if (options.history) {
  8569. up.history[options.historyMethod](options.history);
  8570. }
  8571. if (u.isString(options.title)) {
  8572. return document.title = options.title;
  8573. }
  8574. };
  8575. swapElements = function(oldElement, newElement, pseudoClass, transition, options) {
  8576. var child, childNode, i, keepPlan, len, morphOptions, parent, promise, ref, wrapper;
  8577. transition || (transition = 'none');
  8578. if (options.source === 'keep') {
  8579. options = u.merge(options, {
  8580. source: source(oldElement)
  8581. });
  8582. }
  8583. setSource(newElement, options.source);
  8584. if (pseudoClass) {
  8585. wrapper = e.createFromSelector('.up-insertion');
  8586. while (childNode = newElement.firstChild) {
  8587. wrapper.appendChild(childNode);
  8588. }
  8589. if (pseudoClass === 'before') {
  8590. oldElement.insertAdjacentElement('afterbegin', wrapper);
  8591. } else {
  8592. oldElement.insertAdjacentElement('beforeend', wrapper);
  8593. }
  8594. ref = wrapper.children;
  8595. for (i = 0, len = ref.length; i < len; i++) {
  8596. child = ref[i];
  8597. hello(child, options);
  8598. }
  8599. promise = up.viewport.scrollAfterInsertFragment(wrapper, options);
  8600. promise = u.always(promise, up.animate(wrapper, transition, options));
  8601. promise = promise.then(function() {
  8602. return e.unwrap(wrapper);
  8603. });
  8604. return promise;
  8605. } else if (keepPlan = findKeepPlan(oldElement, newElement, options)) {
  8606. emitFragmentKept(keepPlan);
  8607. return Promise.resolve();
  8608. } else {
  8609. options.keepPlans = transferKeepableElements(oldElement, newElement, options);
  8610. parent = oldElement.parentNode;
  8611. morphOptions = u.merge(options, {
  8612. beforeStart: function() {
  8613. return markElementAsDestroying(oldElement);
  8614. },
  8615. afterInsert: function() {
  8616. return up.hello(newElement, options);
  8617. },
  8618. beforeDetach: function() {
  8619. return up.syntax.clean(oldElement);
  8620. },
  8621. afterDetach: function() {
  8622. e.remove(oldElement);
  8623. return emitFragmentDestroyed(oldElement, {
  8624. parent: parent,
  8625. log: false
  8626. });
  8627. }
  8628. });
  8629. return up.morph(oldElement, newElement, transition, morphOptions);
  8630. }
  8631. };
  8632. transferKeepableElements = function(oldElement, newElement, options) {
  8633. var i, keepPlans, keepable, keepableClone, len, plan, ref;
  8634. keepPlans = [];
  8635. if (options.keep) {
  8636. ref = oldElement.querySelectorAll('[up-keep]');
  8637. for (i = 0, len = ref.length; i < len; i++) {
  8638. keepable = ref[i];
  8639. if (plan = findKeepPlan(keepable, newElement, u.merge(options, {
  8640. descendantsOnly: true
  8641. }))) {
  8642. keepableClone = keepable.cloneNode(true);
  8643. e.replace(keepable, keepableClone);
  8644. e.replace(plan.newElement, keepable);
  8645. keepPlans.push(plan);
  8646. }
  8647. }
  8648. }
  8649. return keepPlans;
  8650. };
  8651. findKeepPlan = function(element, newElement, options) {
  8652. var keepEventArgs, keepable, partner, partnerSelector, plan;
  8653. if (options.keep) {
  8654. keepable = element;
  8655. if (partnerSelector = e.booleanOrStringAttr(keepable, 'up-keep')) {
  8656. u.isString(partnerSelector) || (partnerSelector = '&');
  8657. partnerSelector = e.resolveSelector(partnerSelector, keepable);
  8658. if (options.descendantsOnly) {
  8659. partner = e.first(newElement, partnerSelector);
  8660. } else {
  8661. partner = e.subtree(newElement, partnerSelector)[0];
  8662. }
  8663. if (partner && e.matches(partner, '[up-keep]')) {
  8664. plan = {
  8665. oldElement: keepable,
  8666. newElement: partner,
  8667. newData: up.syntax.data(partner)
  8668. };
  8669. keepEventArgs = {
  8670. target: keepable,
  8671. newFragment: partner,
  8672. newData: plan.newData,
  8673. log: ['Keeping element %o', keepable]
  8674. };
  8675. if (up.event.nobodyPrevents('up:fragment:keep', keepEventArgs)) {
  8676. return plan;
  8677. }
  8678. }
  8679. }
  8680. }
  8681. };
  8682. /***
  8683. Elements with an `up-keep` attribute will be persisted during
  8684. [fragment updates](/a-up-target).
  8685. For example:
  8686. <audio up-keep src="song.mp3"></audio>
  8687. The element you're keeping should have an umambiguous class name, ID or `up-id`
  8688. attribute so Unpoly can find its new position within the page update.
  8689. Emits events [`up:fragment:keep`](/up:fragment:keep) and [`up:fragment:kept`](/up:fragment:kept).
  8690. \#\#\# Controlling if an element will be kept
  8691. Unpoly will **only** keep an existing element if:
  8692. - The existing element has an `up-keep` attribute
  8693. - The response contains an element matching the CSS selector of the existing element
  8694. - The matching element *also* has an `up-keep` attribute
  8695. - The [`up:fragment:keep`](/up:fragment:keep) event that is [emitted](/up.emit) on the existing element
  8696. is not prevented by a event listener.
  8697. Let's say we want only keep an `<audio>` element as long as it plays
  8698. the same song (as identified by the tag's `src` attribute).
  8699. On the client we can achieve this by listening to an `up:keep:fragment` event
  8700. and preventing it if the `src` attribute of the old and new element differ:
  8701. up.compiler('audio', function(element) {
  8702. element.addEventListener('up:fragment:keep', function(event) {
  8703. if element.getAttribute('src') !== event.newElement.getAttribute('src') {
  8704. event.preventDefault()
  8705. }
  8706. })
  8707. })
  8708. If we don't want to solve this on the client, we can achieve the same effect
  8709. on the server. By setting the value of the `up-keep` attribute we can
  8710. define the CSS selector used for matching elements.
  8711. <audio up-keep="audio[src='song.mp3']" src="song.mp3"></audio>
  8712. Now, if a response no longer contains an `<audio src="song.mp3">` tag, the existing
  8713. element will be destroyed and replaced by a fragment from the response.
  8714. @selector [up-keep]
  8715. @stable
  8716. */
  8717. /***
  8718. This event is [emitted](/up.emit) before an existing element is [kept](/up-keep) during
  8719. a page update.
  8720. Event listeners can call `event.preventDefault()` on an `up:fragment:keep` event
  8721. to prevent the element from being persisted. If the event is prevented, the element
  8722. will be replaced by a fragment from the response.
  8723. @event up:fragment:keep
  8724. @param event.preventDefault()
  8725. Event listeners may call this method to prevent the element from being preserved.
  8726. @param {Element} event.target
  8727. The fragment that will be kept.
  8728. @param {Element} event.newFragment
  8729. The discarded element.
  8730. @param {Object} event.newData
  8731. The value of the [`up-data`](/up-data) attribute of the discarded element,
  8732. parsed as a JSON object.
  8733. @stable
  8734. */
  8735. /***
  8736. This event is [emitted](/up.emit) when an existing element has been [kept](/up-keep)
  8737. during a page update.
  8738. Event listeners can inspect the discarded update through `event.newElement`
  8739. and `event.newData` and then modify the preserved element when necessary.
  8740. @event up:fragment:kept
  8741. @param {Element} event.target
  8742. The fragment that has been kept.
  8743. @param {Element} event.newFragment
  8744. The discarded fragment.
  8745. @param {Object} event.newData
  8746. The value of the [`up-data`](/up-data) attribute of the discarded fragment,
  8747. parsed as a JSON object.
  8748. @stable
  8749. */
  8750. /***
  8751. Compiles a page fragment that has been inserted into the DOM
  8752. by external code.
  8753. **As long as you manipulate the DOM using Unpoly, you will never
  8754. need to call this method.** You only need to use `up.hello()` if the
  8755. DOM is manipulated without Unpoly' involvement, e.g. by setting
  8756. the `innerHTML` property or calling jQuery methods like
  8757. `html`, `insertAfter` or `appendTo`:
  8758. element = document.createElement('div')
  8759. element.innerHTML = '... HTML that needs to be activated ...'
  8760. up.hello(element)
  8761. This function emits the [`up:fragment:inserted`](/up:fragment:inserted)
  8762. event.
  8763. @function up.hello
  8764. @param {string|Element|jQuery} selectorOrElement
  8765. @param {string|Element|jQuery} [options.origin]
  8766. @param {string|Element|jQuery} [options.kept]
  8767. @return {Element}
  8768. The compiled element
  8769. @stable
  8770. */
  8771. hello = function(selectorOrElement, options) {
  8772. var element, i, keptElements, len, plan, ref;
  8773. element = e.get(selectorOrElement);
  8774. options = u.options(options, {
  8775. keepPlans: []
  8776. });
  8777. keptElements = [];
  8778. ref = options.keepPlans;
  8779. for (i = 0, len = ref.length; i < len; i++) {
  8780. plan = ref[i];
  8781. emitFragmentKept(plan);
  8782. keptElements.push(plan.oldElement);
  8783. }
  8784. up.syntax.compile(element, {
  8785. skip: keptElements
  8786. });
  8787. emitFragmentInserted(element, options);
  8788. return element;
  8789. };
  8790. /***
  8791. When any page fragment has been [inserted or updated](/up.replace),
  8792. this event is [emitted](/up.emit) on the fragment.
  8793. If you're looking to run code when a new fragment matches
  8794. a selector, use `up.compiler()` instead.
  8795. \#\#\# Example
  8796. up.on('up:fragment:inserted', function(event, fragment) {
  8797. console.log("Looks like we have a new %o!", fragment)
  8798. })
  8799. @event up:fragment:inserted
  8800. @param {Element} event.target
  8801. The fragment that has been inserted or updated.
  8802. @stable
  8803. */
  8804. emitFragmentInserted = function(element, options) {
  8805. return up.emit(element, 'up:fragment:inserted', {
  8806. log: ['Inserted fragment %o', element],
  8807. origin: options.origin
  8808. });
  8809. };
  8810. emitFragmentKept = function(keepPlan) {
  8811. var eventAttrs, keptElement;
  8812. keptElement = keepPlan.oldElement;
  8813. eventAttrs = {
  8814. target: keptElement,
  8815. newFragment: keepPlan.newElement,
  8816. newData: keepPlan.newData,
  8817. log: ['Kept fragment %o', keptElement]
  8818. };
  8819. return up.emit('up:fragment:kept', eventAttrs);
  8820. };
  8821. emitFragmentDestroyed = function(fragment, options) {
  8822. var log, parent;
  8823. if (shouldLogDestruction(fragment, options)) {
  8824. log = ['Destroyed fragment %o', fragment];
  8825. }
  8826. parent = options.parent || up.fail("Missing { parent } option");
  8827. return up.emit(parent, 'up:fragment:destroyed', {
  8828. fragment: fragment,
  8829. parent: parent,
  8830. log: log
  8831. });
  8832. };
  8833. isRealElement = function(element) {
  8834. return !e.closest(element, '.up-destroying');
  8835. };
  8836. /***
  8837. Returns the first element matching the given selector, but
  8838. ignores elements that are being [destroyed](/up.destroy) or that are being
  8839. removed by a [transition](/up.morph).
  8840. Returns `undefined` if no element matches these conditions.
  8841. \#\#\# Example
  8842. To select the first element with the selector `.foo`:
  8843. var fooInModal = up.fragment.first('.foo')
  8844. You may also pass a `{ layer }` option to only match elements witin a layer:
  8845. var fooInModal = up.fragment.first('.foo', { layer: 'modal' })
  8846. You may also pass a root element as a first argument:
  8847. var container = up.fragment.first('.container')
  8848. var fooInContainer = up.fragment.first(container, '.foo')
  8849. \#\#\# Similar features
  8850. - The [`.up-destroying`](/up-destroying) class is assigned to elements during their removal animation.
  8851. - The [`up.element.first()`](/up.element.first) function simply returns the first element matching a selector
  8852. without further filtering.
  8853. @function up.fragment.first
  8854. @param {Element|jQuery} [root=document]
  8855. The root element for the search. Only the root's children will be matched.
  8856. May be omitted to search through all elements in the `document`.
  8857. @param {string} selector
  8858. The selector to match
  8859. @param {string} [options.layer='auto']
  8860. The name of the layer in which to find the element.
  8861. Valid values are `'auto'`, `'page'`, `'modal'` and `'popup'`.
  8862. @param {string|Element|jQuery} [options.origin]
  8863. An second element or selector that can be referenced as `&` in the first selector:
  8864. var input = document.querySelector('input.email')
  8865. up.fragment.first('fieldset:has(&)', { origin: input }) // returns the <fieldset> containing input
  8866. @return {Element|undefined}
  8867. The first element that is neither a ghost or being destroyed,
  8868. or `undefined` if no such element was found.
  8869. @experimental
  8870. */
  8871. first = function() {
  8872. var args, layer, options, origin, ref, root, selector;
  8873. args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
  8874. options = u.extractOptions(args);
  8875. selector = args.pop();
  8876. root = args[0] || document;
  8877. layer = (ref = options.layer) != null ? ref : 'auto';
  8878. origin = options.origin;
  8879. selector = e.resolveSelector(selector, origin);
  8880. if (layer === 'auto') {
  8881. return firstInPriority(root, selector, origin);
  8882. } else {
  8883. return firstInLayer(root, selector, layer);
  8884. }
  8885. };
  8886. firstInPriority = function(parent, selector, origin) {
  8887. var layers, originLayer;
  8888. layers = ['popup', 'modal', 'page'];
  8889. if (origin) {
  8890. originLayer = layerOf(origin);
  8891. u.remove(layers, originLayer);
  8892. layers.unshift(originLayer);
  8893. }
  8894. return u.findResult(layers, function(layer) {
  8895. return firstInLayer(parent, selector, layer);
  8896. });
  8897. };
  8898. firstInLayer = function(parent, selector, layer) {
  8899. var elements;
  8900. elements = e.all(parent, selector);
  8901. return u.findResult(elements, function(element) {
  8902. if (isRealElement(element) && matchesLayer(element, layer)) {
  8903. return element;
  8904. }
  8905. });
  8906. };
  8907. /***
  8908. @function up.fragment.layerOf
  8909. @internal
  8910. */
  8911. layerOf = function(element) {
  8912. if (up.popup.contains(element)) {
  8913. return 'popup';
  8914. } else if (up.modal.contains(element)) {
  8915. return 'modal';
  8916. } else {
  8917. return 'page';
  8918. }
  8919. };
  8920. matchesLayer = function(element, layer) {
  8921. return !layer || layerOf(element) === layer;
  8922. };
  8923. /***
  8924. @function up.fragment.createPlaceHolder
  8925. @internal
  8926. */
  8927. createPlaceholder = function(selector, container) {
  8928. if (container == null) {
  8929. container = document.body;
  8930. }
  8931. return e.affix(container, selector, {
  8932. "class": 'up-placeholder'
  8933. });
  8934. };
  8935. /***
  8936. Destroys the given element or selector.
  8937. Takes care that all [`up.compiler()`](/up.compiler) destructors, if any, are called.
  8938. The element is removed from the DOM.
  8939. Note that if you choose to animate the element removal using `options.animate`,
  8940. the element won't be removed until after the animation has completed.
  8941. Emits events [`up:fragment:destroyed`](/up:fragment:destroyed).
  8942. @function up.destroy
  8943. @param {string|Element|jQuery} selectorOrElement
  8944. @param {string} [options.history]
  8945. A URL that will be pushed as a new history entry when the element begins destruction.
  8946. @param {string} [options.title]
  8947. The document title to set when the element begins destruction.
  8948. @param {string|Function(element, options): Promise} [options.animation='none']
  8949. The animation to use before the element is removed from the DOM.
  8950. @param {number} [options.duration]
  8951. The duration of the animation. See [`up.animate()`](/up.animate).
  8952. @param {number} [options.delay]
  8953. The delay before the animation starts. See [`up.animate()`](/up.animate).
  8954. @param {string} [options.easing]
  8955. The timing function that controls the animation's acceleration. [`up.animate()`](/up.animate).
  8956. @return {Promise}
  8957. A promise that will be fulfilled once the element has been removed from the DOM.
  8958. @stable
  8959. */
  8960. destroy = function(selectorOrElement, options) {
  8961. var animate, element, wipe;
  8962. element = e.get(selectorOrElement);
  8963. options = u.options(options, {
  8964. animation: false
  8965. });
  8966. if (!element) {
  8967. return Promise.resolve();
  8968. }
  8969. markElementAsDestroying(element);
  8970. updateHistoryAndTitle(options);
  8971. animate = function() {
  8972. var animateOptions;
  8973. animateOptions = up.motion.animateOptions(options);
  8974. return up.motion.animate(element, options.animation, animateOptions);
  8975. };
  8976. wipe = function() {
  8977. var parent;
  8978. parent = element.parentNode;
  8979. up.syntax.clean(element);
  8980. if (up.browser.canJQuery()) {
  8981. jQuery(element).remove();
  8982. } else {
  8983. e.remove(element);
  8984. }
  8985. return emitFragmentDestroyed(element, {
  8986. parent: parent,
  8987. log: options.log
  8988. });
  8989. };
  8990. return animate().then(wipe);
  8991. };
  8992. shouldLogDestruction = function(element, options) {
  8993. return options.log !== false && !e.matches(element, '.up-placeholder, .up-tooltip, .up-modal, .up-popup');
  8994. };
  8995. /***
  8996. Elements are assigned the `.up-destroying` class before they are [destroyed](/up.destroy)
  8997. or while they are being removed by a [transition](/up.morph).
  8998. If the removal is animated, the class is assigned before the animation starts.
  8999. To select an element while ignoring elements that are being destroyed,
  9000. see the [`up.fragment.first()`](/up.fragment.first) function.
  9001. @selector .up-destroying
  9002. @stable
  9003. */
  9004. markElementAsDestroying = function(element) {
  9005. element.classList.add('up-destroying');
  9006. return element.setAttribute('aria-hidden', 'true');
  9007. };
  9008. /***
  9009. This event is [emitted](/up.emit) after a page fragment was [destroyed](/up.destroy) and removed from the DOM.
  9010. If the destruction is animated, this event is emitted after the animation has ended.
  9011. The event is emitted on the parent element of the fragment that was removed.
  9012. @event up:fragment:destroyed
  9013. @param {Element} event.fragment
  9014. The detached element that has been removed from the DOM.
  9015. @param {Element} event.parent
  9016. The former parent element of the fragment that has now been detached from the DOM.
  9017. @param {Element} event.target
  9018. The former parent element of the fragment that has now been detached from the DOM.
  9019. @stable
  9020. */
  9021. /***
  9022. Replaces the given element with a fresh copy fetched from the server.
  9023. \#\#\# Example
  9024. up.on('new-mail', function() { up.reload('.inbox') })
  9025. Unpoly remembers the URL from which a fragment was loaded, so you
  9026. don't usually need to give an URL when reloading.
  9027. @function up.reload
  9028. @param {string|Element|jQuery} selectorOrElement
  9029. @param {Object} [options]
  9030. See options for [`up.replace()`](/up.replace)
  9031. @param {string} [options.url]
  9032. The URL from which to reload the fragment.
  9033. This defaults to the URL from which the fragment was originally loaded.
  9034. @stable
  9035. */
  9036. reload = function(selectorOrElement, options) {
  9037. var sourceUrl;
  9038. options = u.options(options, {
  9039. cache: false
  9040. });
  9041. sourceUrl = options.url || source(selectorOrElement);
  9042. return replace(selectorOrElement, sourceUrl, options);
  9043. };
  9044. up.on('up:app:boot', function() {
  9045. var body;
  9046. body = document.body;
  9047. setSource(body, up.browser.url());
  9048. return hello(body);
  9049. });
  9050. up.on('up:framework:reset', reset);
  9051. return {
  9052. createPlaceholder: createPlaceholder,
  9053. replace: replace,
  9054. reload: reload,
  9055. destroy: destroy,
  9056. extract: extract,
  9057. first: first,
  9058. source: source,
  9059. hello: hello,
  9060. config: config,
  9061. layerOf: layerOf
  9062. };
  9063. })();
  9064. up.replace = up.fragment.replace;
  9065. up.extract = up.fragment.extract;
  9066. up.reload = up.fragment.reload;
  9067. up.destroy = up.fragment.destroy;
  9068. up.hello = up.fragment.hello;
  9069. up.first = function() {
  9070. var args, ref;
  9071. args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
  9072. up.legacy.warn('up.first() has been renamed to up.fragment.first()');
  9073. return (ref = up.fragment).first.apply(ref, args);
  9074. };
  9075. up.legacy.renamedModule('flow', 'fragment');
  9076. up.legacy.renamedModule('dom', 'fragment');
  9077. }).call(this);
  9078. /***
  9079. Animation
  9080. =========
  9081. Whenever you [update a page fragment](/up.link) you can animate the change.
  9082. Let's say you are using an [`up-target`](/a-up-target) link to update an element
  9083. with content from the server. You can add an attribute [`up-transition`](/a-up-target#up-transition)
  9084. to smoothly fade out the old element while fading in the new element:
  9085. <a href="/users" up-target=".list" up-transition="cross-fade">Show users</a>
  9086. \#\#\# Transitions vs. animations
  9087. When we morph between an old and a new element, we call it a *transition*.
  9088. In contrast, when we animate a new element without simultaneously removing an
  9089. old element, we call it an *animation*.
  9090. An example for an animation is opening a new dialog. We can animate the appearance
  9091. of the dialog by adding an [`[up-animation]`](/a-up-modal#up-animation) attribute to the opening link:
  9092. <a href="/users" up-modal=".list" up-animation="move-from-top">Show users</a>
  9093. \#\#\# Which animations are available?
  9094. Unpoly ships with a number of [predefined transitions](/up.morph#named-transitions)
  9095. and [predefined animations](/up.animate#named-animations).
  9096. You can define custom animations using [`up.transition()`](/up.transition) and
  9097. [`up.animation()`](/up.animation).
  9098. @module up.motion
  9099. */
  9100. (function() {
  9101. var slice = [].slice;
  9102. up.motion = (function() {
  9103. var animCount, animate, animateNow, animateOptions, composeTransitionFn, config, defaultNamedAnimations, defaultNamedTransitions, e, findAnimationFn, findNamedAnimation, findTransitionFn, finish, isEnabled, isNone, morph, motionController, namedAnimations, namedTransitions, registerAnimation, registerTransition, reset, skipAnimate, snapshot, swapElementsDirectly, translateCss, u, willAnimate;
  9104. u = up.util;
  9105. e = up.element;
  9106. namedAnimations = {};
  9107. defaultNamedAnimations = {};
  9108. namedTransitions = {};
  9109. defaultNamedTransitions = {};
  9110. motionController = new up.MotionController('motion');
  9111. /***
  9112. Sets default options for animations and transitions.
  9113. @property up.motion.config
  9114. @param {number} [config.duration=300]
  9115. The default duration for all animations and transitions (in milliseconds).
  9116. @param {number} [config.delay=0]
  9117. The default delay for all animations and transitions (in milliseconds).
  9118. @param {string} [config.easing='ease']
  9119. The default timing function that controls the acceleration of animations and transitions.
  9120. See [W3C documentation](http://www.w3.org/TR/css3-transitions/#transition-timing-function)
  9121. for a list of pre-defined timing functions.
  9122. @param {boolean} [config.enabled=true]
  9123. Whether animation is enabled.
  9124. Set this to `false` to disable animation globally.
  9125. This can be useful in full-stack integration tests like a Selenium test suite.
  9126. Regardless of this setting, all animations will be skipped on browsers
  9127. that do not support [CSS transitions](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Transitions/Using_CSS_transitions).
  9128. @stable
  9129. */
  9130. config = new up.Config({
  9131. duration: 300,
  9132. delay: 0,
  9133. easing: 'ease',
  9134. enabled: true
  9135. });
  9136. reset = function() {
  9137. motionController.reset();
  9138. namedAnimations = u.copy(defaultNamedAnimations);
  9139. namedTransitions = u.copy(defaultNamedTransitions);
  9140. return config.reset();
  9141. };
  9142. /***
  9143. Returns whether Unpoly will perform animations.
  9144. Set [`up.motion.config.enabled`](/up.motion.config) `false` in order to disable animations globally.
  9145. @function up.motion.isEnabled
  9146. @return {boolean}
  9147. @stable
  9148. */
  9149. isEnabled = function() {
  9150. return config.enabled;
  9151. };
  9152. /***
  9153. Applies the given animation to the given element.
  9154. \#\#\# Example
  9155. up.animate('.warning', 'fade-in')
  9156. You can pass additional options:
  9157. up.animate('warning', '.fade-in', {
  9158. delay: 1000,
  9159. duration: 250,
  9160. easing: 'linear'
  9161. })
  9162. \#\#\# Named animations
  9163. The following animations are pre-defined:
  9164. | `fade-in` | Changes the element's opacity from 0% to 100% |
  9165. | `fade-out` | Changes the element's opacity from 100% to 0% |
  9166. | `move-to-top` | Moves the element upwards until it exits the screen at the top edge |
  9167. | `move-from-top` | Moves the element downwards from beyond the top edge of the screen until it reaches its current position |
  9168. | `move-to-bottom` | Moves the element downwards until it exits the screen at the bottom edge |
  9169. | `move-from-bottom` | Moves the element upwards from beyond the bottom edge of the screen until it reaches its current position |
  9170. | `move-to-left` | Moves the element leftwards until it exists the screen at the left edge |
  9171. | `move-from-left` | Moves the element rightwards from beyond the left edge of the screen until it reaches its current position |
  9172. | `move-to-right` | Moves the element rightwards until it exists the screen at the right edge |
  9173. | `move-from-right` | Moves the element leftwards from beyond the right edge of the screen until it reaches its current position |
  9174. | `none` | An animation that has no visible effect. Sounds useless at first, but can save you a lot of `if` statements. |
  9175. You can define additional named animations using [`up.animation()`](/up.animation).
  9176. \#\#\# Animating CSS properties directly
  9177. By passing an object instead of an animation name, you can animate
  9178. the CSS properties of the given element:
  9179. var warning = document.querySelector('.warning')
  9180. warning.style.opacity = 0
  9181. up.animate(warning, { opacity: 1 })
  9182. CSS properties must be given in `kebab-case`, not `camelCase`.
  9183. \#\#\# Multiple animations on the same element
  9184. Unpoly doesn't allow more than one concurrent animation on the same element.
  9185. If you attempt to animate an element that is already being animated,
  9186. the previous animation will instantly jump to its last frame before
  9187. the new animation begins.
  9188. @function up.animate
  9189. @param {Element|jQuery|string} elementOrSelector
  9190. The element to animate.
  9191. @param {string|Function(element, options): Promise|Object} animation
  9192. Can either be:
  9193. - The animation's name
  9194. - A function performing the animation
  9195. - An object of CSS attributes describing the last frame of the animation (using kebeb-case property names)
  9196. @param {number} [options.duration=300]
  9197. The duration of the animation, in milliseconds.
  9198. @param {number} [options.delay=0]
  9199. The delay before the animation starts, in milliseconds.
  9200. @param {string} [options.easing='ease']
  9201. The timing function that controls the animation's acceleration.
  9202. See [W3C documentation](http://www.w3.org/TR/css3-transitions/#transition-timing-function)
  9203. for a list of pre-defined timing functions.
  9204. @return {Promise}
  9205. A promise for the animation's end.
  9206. @stable
  9207. */
  9208. animate = function(elementOrSelector, animation, options) {
  9209. var animationFn, element, runNow, willRun;
  9210. element = e.get(elementOrSelector);
  9211. options = animateOptions(options);
  9212. animationFn = findAnimationFn(animation);
  9213. willRun = willAnimate(element, animation, options);
  9214. if (willRun) {
  9215. runNow = function() {
  9216. return animationFn(element, options);
  9217. };
  9218. return motionController.startFunction(element, runNow, options);
  9219. } else {
  9220. return skipAnimate(element, animation);
  9221. }
  9222. };
  9223. willAnimate = function(element, animationOrTransition, options) {
  9224. options = animateOptions(options);
  9225. return isEnabled() && !isNone(animationOrTransition) && options.duration > 0 && !e.isSingleton(element);
  9226. };
  9227. skipAnimate = function(element, animation) {
  9228. if (u.isOptions(animation)) {
  9229. e.setStyle(element, animation);
  9230. }
  9231. return Promise.resolve();
  9232. };
  9233. animCount = 0;
  9234. /***
  9235. Animates the given element's CSS properties using CSS transitions.
  9236. Does not track the animation, nor does it finishes existing animations
  9237. (use `up.motion.animate()` for that). It does, however, listen to the motionController's
  9238. finish event.
  9239. @function animateNow
  9240. @param {Element|jQuery|string} elementOrSelector
  9241. The element to animate.
  9242. @param {Object} lastFrame
  9243. The CSS properties that should be transitioned to.
  9244. @param {number} [options.duration=300]
  9245. The duration of the animation, in milliseconds.
  9246. @param {number} [options.delay=0]
  9247. The delay before the animation starts, in milliseconds.
  9248. @param {string} [options.easing='ease']
  9249. The timing function that controls the animation's acceleration.
  9250. See [W3C documentation](http://www.w3.org/TR/css3-transitions/#transition-timing-function)
  9251. for a list of pre-defined timing functions.
  9252. @return {Promise}
  9253. A promise that fulfills when the animation ends.
  9254. @internal
  9255. */
  9256. animateNow = function(element, lastFrame, options) {
  9257. var cssTransition;
  9258. options = u.merge(options, {
  9259. finishEvent: motionController.finishEvent
  9260. });
  9261. cssTransition = new up.CssTransition(element, lastFrame, options);
  9262. return cssTransition.start();
  9263. };
  9264. /***
  9265. Extracts animation-related options from the given options hash.
  9266. If `element` is given, also inspects the element for animation-related
  9267. attributes like `up-easing` or `up-duration`.
  9268. @param {Object} userOptions
  9269. @param {Element|jQuery} [element]
  9270. @param {Object} [moduleDefaults]
  9271. @function up.motion.animateOptions
  9272. @internal
  9273. */
  9274. animateOptions = function() {
  9275. var args, consolidatedOptions, element, moduleDefaults, ref, ref1, ref2, ref3, ref4, ref5, ref6, ref7, ref8, ref9, userOptions;
  9276. args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
  9277. userOptions = (ref = args.shift()) != null ? ref : {};
  9278. moduleDefaults = u.extractOptions(args);
  9279. element = args.pop() || e.none();
  9280. consolidatedOptions = {};
  9281. consolidatedOptions.easing = (ref1 = (ref2 = (ref3 = userOptions.easing) != null ? ref3 : element.getAttribute('up-easing')) != null ? ref2 : moduleDefaults.easing) != null ? ref1 : config.easing;
  9282. consolidatedOptions.duration = (ref4 = (ref5 = (ref6 = userOptions.duration) != null ? ref6 : e.numberAttr(element, 'up-duration')) != null ? ref5 : moduleDefaults.duration) != null ? ref4 : config.duration;
  9283. consolidatedOptions.delay = (ref7 = (ref8 = (ref9 = userOptions.delay) != null ? ref9 : e.numberAttr(element, 'up-delay')) != null ? ref8 : moduleDefaults.delay) != null ? ref7 : config.delay;
  9284. consolidatedOptions.trackMotion = userOptions.trackMotion;
  9285. return consolidatedOptions;
  9286. };
  9287. findNamedAnimation = function(name) {
  9288. return namedAnimations[name] || up.fail("Unknown animation %o", name);
  9289. };
  9290. /***
  9291. Completes [animations](/up.animate) and [transitions](/up.morph).
  9292. If called without arguments, all animations on the screen are completed.
  9293. If given an element (or selector), animations on that element and its children
  9294. are completed.
  9295. Animations are completed by jumping to the last animation frame instantly.
  9296. Promises returned by animation and transition functions instantly settle.
  9297. Emits the `up:motion:finish` event that is already handled by `up.animate()`.
  9298. Does nothing if there are no animation to complete.
  9299. @function up.motion.finish
  9300. @param {Element|jQuery|string} [elementOrSelector]
  9301. @return {Promise}
  9302. A promise that fulfills when animations and transitions have finished.
  9303. @stable
  9304. */
  9305. finish = function(elementOrSelector) {
  9306. return motionController.finish(elementOrSelector);
  9307. };
  9308. /***
  9309. This event is emitted on an [animating](/up.animating) element by `up.motion.finish()` to
  9310. request the animation to instantly finish and skip to the last frame.
  9311. Promises returned by completed animation functions are expected to settle.
  9312. Animations started by `up.animate()` already handle this event.
  9313. @event up:motion:finish
  9314. @param {Element} event.target
  9315. The animating element.
  9316. @experimental
  9317. */
  9318. /***
  9319. Performs an animated transition between the `source` and `target` elements.
  9320. Transitions are implement by performing two animations in parallel,
  9321. causing `source` to disappear and the `target` to appear.
  9322. - `target` is [inserted before](https://developer.mozilla.org/en-US/docs/Web/API/Node/insertBefore) `source`
  9323. - `source` is removed from the [document flow](https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Positioning) with `position: absolute`.
  9324. It will be positioned over its original place in the flow that is now occupied by `target`.
  9325. - Both `source` and `target` are animated in parallel
  9326. - `source` is removed from the DOM
  9327. \#\#\# Named transitions
  9328. The following transitions are pre-defined:
  9329. | `cross-fade` | Fades out the first element. Simultaneously fades in the second element. |
  9330. | `move-up` | Moves the first element upwards until it exits the screen at the top edge. Simultaneously moves the second element upwards from beyond the bottom edge of the screen until it reaches its current position. |
  9331. | `move-down` | Moves the first element downwards until it exits the screen at the bottom edge. Simultaneously moves the second element downwards from beyond the top edge of the screen until it reaches its current position. |
  9332. | `move-left` | Moves the first element leftwards until it exists the screen at the left edge. Simultaneously moves the second element leftwards from beyond the right edge of the screen until it reaches its current position. |
  9333. | `move-right` | Moves the first element rightwards until it exists the screen at the right edge. Simultaneously moves the second element rightwards from beyond the left edge of the screen until it reaches its current position. |
  9334. | `none` | A transition that has no visible effect. Sounds useless at first, but can save you a lot of `if` statements. |
  9335. You can define additional named transitions using [`up.transition()`](/up.transition).
  9336. You can also compose a transition from two [named animations](/named-animations).
  9337. separated by a slash character (`/`):
  9338. - `move-to-bottom/fade-in`
  9339. - `move-to-left/move-from-top`
  9340. \#\#\# Implementation details
  9341. During a transition both the old and new element occupy
  9342. the same position on the screen.
  9343. Since the CSS layout flow will usually not allow two elements to
  9344. overlay the same space, Unpoly:
  9345. - The old and new elements are cloned
  9346. - The old element is removed from the layout flow using `display: hidden`
  9347. - The new element is hidden, but still leaves space in the layout flow by setting `visibility: hidden`
  9348. - The clones are [absolutely positioned](https://developer.mozilla.org/en-US/docs/Web/CSS/position#Absolute_positioning)
  9349. over the original elements.
  9350. - The transition is applied to the cloned elements.
  9351. At no point will the hidden, original elements be animated.
  9352. - When the transition has finished, the clones are removed from the DOM and the new element is shown.
  9353. The old element remains hidden in the DOM.
  9354. @function up.morph
  9355. @param {Element|jQuery|string} source
  9356. @param {Element|jQuery|string} target
  9357. @param {Function(oldElement, newElement)|string} transition
  9358. @param {number} [options.duration=300]
  9359. The duration of the animation, in milliseconds.
  9360. @param {number} [options.delay=0]
  9361. The delay before the animation starts, in milliseconds.
  9362. @param {string} [options.easing='ease']
  9363. The timing function that controls the transition's acceleration.
  9364. See [W3C documentation](http://www.w3.org/TR/css3-transitions/#transition-timing-function)
  9365. for a list of pre-defined timing functions.
  9366. @param {boolean} [options.reveal=false]
  9367. Whether to reveal the new element by scrolling its parent viewport.
  9368. @return {Promise}
  9369. A promise that fulfills when the transition ends.
  9370. @stable
  9371. */
  9372. morph = function(oldElement, newElement, transitionObject, options) {
  9373. var afterDetach, afterInsert, beforeDetach, beforeStart, oldRemote, promise, scrollNew, scrollTopBeforeReveal, trackable, transitionFn, viewport, willMorph;
  9374. options = u.options(options);
  9375. u.assign(options, animateOptions(options));
  9376. oldElement = e.get(oldElement);
  9377. newElement = e.get(newElement);
  9378. transitionFn = findTransitionFn(transitionObject);
  9379. willMorph = willAnimate(oldElement, transitionFn, options);
  9380. beforeStart = u.pluckKey(options, 'beforeStart') || u.noop;
  9381. afterInsert = u.pluckKey(options, 'afterInsert') || u.noop;
  9382. beforeDetach = u.pluckKey(options, 'beforeDetach') || u.noop;
  9383. afterDetach = u.pluckKey(options, 'afterDetach') || u.noop;
  9384. beforeStart();
  9385. scrollNew = function() {
  9386. var scrollOptions;
  9387. scrollOptions = u.merge(options, {
  9388. scrollBehavior: 'auto'
  9389. });
  9390. return up.viewport.scrollAfterInsertFragment(newElement, scrollOptions);
  9391. };
  9392. if (willMorph) {
  9393. if (motionController.isActive(oldElement) && options.trackMotion === false) {
  9394. return transitionFn(oldElement, newElement, options);
  9395. }
  9396. up.puts('Morphing %o to %o with transition %o', oldElement, newElement, transitionObject);
  9397. viewport = up.viewport.closest(oldElement);
  9398. scrollTopBeforeReveal = viewport.scrollTop;
  9399. oldRemote = up.viewport.absolutize(oldElement, {
  9400. afterMeasure: function() {
  9401. e.insertBefore(oldElement, newElement);
  9402. return afterInsert();
  9403. }
  9404. });
  9405. trackable = function() {
  9406. var promise;
  9407. promise = scrollNew();
  9408. promise = promise.then(function() {
  9409. var scrollTopAfterReveal;
  9410. scrollTopAfterReveal = viewport.scrollTop;
  9411. oldRemote.moveBounds(0, scrollTopAfterReveal - scrollTopBeforeReveal);
  9412. return transitionFn(oldElement, newElement, options);
  9413. });
  9414. promise = promise.then(function() {
  9415. beforeDetach();
  9416. e.remove(oldRemote.bounds);
  9417. return afterDetach();
  9418. });
  9419. return promise;
  9420. };
  9421. return motionController.startFunction([oldElement, newElement], trackable, options);
  9422. } else {
  9423. beforeDetach();
  9424. swapElementsDirectly(oldElement, newElement);
  9425. afterInsert();
  9426. afterDetach();
  9427. promise = scrollNew();
  9428. return promise;
  9429. }
  9430. };
  9431. findTransitionFn = function(object) {
  9432. var namedTransition;
  9433. if (isNone(object)) {
  9434. return void 0;
  9435. } else if (u.isFunction(object)) {
  9436. return object;
  9437. } else if (u.isArray(object)) {
  9438. return composeTransitionFn.apply(null, object);
  9439. } else if (u.isString(object)) {
  9440. if (object.indexOf('/') >= 0) {
  9441. return composeTransitionFn.apply(null, object.split('/'));
  9442. } else if (namedTransition = namedTransitions[object]) {
  9443. return findTransitionFn(namedTransition);
  9444. }
  9445. } else {
  9446. return up.fail("Unknown transition %o", object);
  9447. }
  9448. };
  9449. composeTransitionFn = function(oldAnimation, newAnimation) {
  9450. var newAnimationFn, oldAnimationFn;
  9451. if (isNone(oldAnimation) && isNone(oldAnimation)) {
  9452. return void 0;
  9453. } else {
  9454. oldAnimationFn = findAnimationFn(oldAnimation) || u.asyncNoop;
  9455. newAnimationFn = findAnimationFn(newAnimation) || u.asyncNoop;
  9456. return function(oldElement, newElement, options) {
  9457. return Promise.all([oldAnimationFn(oldElement, options), newAnimationFn(newElement, options)]);
  9458. };
  9459. }
  9460. };
  9461. findAnimationFn = function(object) {
  9462. if (isNone(object)) {
  9463. return void 0;
  9464. } else if (u.isFunction(object)) {
  9465. return object;
  9466. } else if (u.isString(object)) {
  9467. return findNamedAnimation(object);
  9468. } else if (u.isOptions(object)) {
  9469. return function(element, options) {
  9470. return animateNow(element, object, options);
  9471. };
  9472. } else {
  9473. return up.fail('Unknown animation %o', object);
  9474. }
  9475. };
  9476. swapElementsDirectly = function(oldElement, newElement) {
  9477. return e.replace(oldElement, newElement);
  9478. };
  9479. /***
  9480. Defines a named transition that [morphs](/up.element) from one element to another.
  9481. \#\#\# Example
  9482. Here is the definition of the pre-defined `cross-fade` animation:
  9483. up.transition('cross-fade', (oldElement, newElement, options) ->
  9484. Promise.all([
  9485. up.animate(oldElement, 'fade-out', options),
  9486. up.animate(newElement, 'fade-in', options)
  9487. ])
  9488. )
  9489. It is recommended that your transitions use [`up.animate()`](/up.animate),
  9490. passing along the `options` that were passed to you.
  9491. If you choose to *not* use `up.animate()` and roll your own
  9492. logic instead, your code must honor the following contract:
  9493. 1. It must honor the options `{ delay, duration, easing }` if given.
  9494. 2. It must *not* remove any of the given elements from the DOM.
  9495. 3. It returns a promise that is fulfilled when the transition has ended.
  9496. 4. If during the animation an event `up:motion:finish` is emitted on
  9497. either element, the transition instantly jumps to the last frame
  9498. and resolves the returned promise.
  9499. Calling [`up.animate()`](/up.animate) with an object argument
  9500. will take care of all these points.
  9501. @function up.transition
  9502. @param {string} name
  9503. @param {Function(oldElement, newElement, options): Promise|Array} transition
  9504. @stable
  9505. */
  9506. registerTransition = function(name, transition) {
  9507. return namedTransitions[name] = findTransitionFn(transition);
  9508. };
  9509. /***
  9510. Defines a named animation.
  9511. Here is the definition of the pre-defined `fade-in` animation:
  9512. up.animation('fade-in', function(element, options) {
  9513. element.style.opacity = 0
  9514. up.animate(element, { opacity: 1 }, options)
  9515. })
  9516. It is recommended that your definitions always end by calling
  9517. calling [`up.animate()`](/up.animate) with an object argument, passing along
  9518. the `options` that were passed to you.
  9519. If you choose to *not* use `up.animate()` and roll your own
  9520. animation code instead, your code must honor the following contract:
  9521. 1. It must honor the options `{ delay, duration, easing }` if given
  9522. 2. It must *not* remove any of the given elements from the DOM.
  9523. 3. It returns a promise that is fulfilled when the transition has ended
  9524. 4. If during the animation an event `up:motion:finish` is emitted on
  9525. the given element, the transition instantly jumps to the last frame
  9526. and resolves the returned promise.
  9527. Calling [`up.animate()`](/up.animate) with an object argument
  9528. will take care of all these points.
  9529. @function up.animation
  9530. @param {string} name
  9531. @param {Function(element, options): Promise} animation
  9532. @stable
  9533. */
  9534. registerAnimation = function(name, animation) {
  9535. return namedAnimations[name] = findAnimationFn(animation);
  9536. };
  9537. snapshot = function() {
  9538. defaultNamedAnimations = u.copy(namedAnimations);
  9539. return defaultNamedTransitions = u.copy(namedTransitions);
  9540. };
  9541. /***
  9542. Returns whether the given animation option will cause the animation
  9543. to be skipped.
  9544. @function up.motion.isNone
  9545. @internal
  9546. */
  9547. isNone = function(animationOrTransition) {
  9548. return !animationOrTransition || animationOrTransition === 'none' || u.isBlank(animationOrTransition);
  9549. };
  9550. registerAnimation('fade-in', function(element, options) {
  9551. e.setStyle(element, {
  9552. opacity: 0
  9553. });
  9554. return animateNow(element, {
  9555. opacity: 1
  9556. }, options);
  9557. });
  9558. registerAnimation('fade-out', function(element, options) {
  9559. e.setStyle(element, {
  9560. opacity: 1
  9561. });
  9562. return animateNow(element, {
  9563. opacity: 0
  9564. }, options);
  9565. });
  9566. translateCss = function(x, y) {
  9567. return {
  9568. transform: "translate(" + x + "px, " + y + "px)"
  9569. };
  9570. };
  9571. registerAnimation('move-to-top', function(element, options) {
  9572. var box, travelDistance;
  9573. e.setStyle(element, translateCss(0, 0));
  9574. box = element.getBoundingClientRect();
  9575. travelDistance = box.top + box.height;
  9576. return animateNow(element, translateCss(0, -travelDistance), options);
  9577. });
  9578. registerAnimation('move-from-top', function(element, options) {
  9579. var box, travelDistance;
  9580. e.setStyle(element, translateCss(0, 0));
  9581. box = element.getBoundingClientRect();
  9582. travelDistance = box.top + box.height;
  9583. e.setStyle(element, translateCss(0, -travelDistance));
  9584. return animateNow(element, translateCss(0, 0), options);
  9585. });
  9586. registerAnimation('move-to-bottom', function(element, options) {
  9587. var box, travelDistance;
  9588. e.setStyle(element, translateCss(0, 0));
  9589. box = element.getBoundingClientRect();
  9590. travelDistance = e.root().clientHeight - box.top;
  9591. return animateNow(element, translateCss(0, travelDistance), options);
  9592. });
  9593. registerAnimation('move-from-bottom', function(element, options) {
  9594. var box, travelDistance;
  9595. e.setStyle(element, translateCss(0, 0));
  9596. box = element.getBoundingClientRect();
  9597. travelDistance = up.viewport.rootHeight() - box.top;
  9598. e.setStyle(element, translateCss(0, travelDistance));
  9599. return animateNow(element, translateCss(0, 0), options);
  9600. });
  9601. registerAnimation('move-to-left', function(element, options) {
  9602. var box, travelDistance;
  9603. e.setStyle(element, translateCss(0, 0));
  9604. box = element.getBoundingClientRect();
  9605. travelDistance = box.left + box.width;
  9606. return animateNow(element, translateCss(-travelDistance, 0), options);
  9607. });
  9608. registerAnimation('move-from-left', function(element, options) {
  9609. var box, travelDistance;
  9610. e.setStyle(element, translateCss(0, 0));
  9611. box = element.getBoundingClientRect();
  9612. travelDistance = box.left + box.width;
  9613. e.setStyle(element, translateCss(-travelDistance, 0));
  9614. return animateNow(element, translateCss(0, 0), options);
  9615. });
  9616. registerAnimation('move-to-right', function(element, options) {
  9617. var box, travelDistance;
  9618. e.setStyle(element, translateCss(0, 0));
  9619. box = element.getBoundingClientRect();
  9620. travelDistance = up.viewport.rootWidth() - box.left;
  9621. return animateNow(element, translateCss(travelDistance, 0), options);
  9622. });
  9623. registerAnimation('move-from-right', function(element, options) {
  9624. var box, travelDistance;
  9625. e.setStyle(element, translateCss(0, 0));
  9626. box = element.getBoundingClientRect();
  9627. travelDistance = up.viewport.rootWidth() - box.left;
  9628. e.setStyle(element, translateCss(travelDistance, 0));
  9629. return animateNow(element, translateCss(0, 0), options);
  9630. });
  9631. registerAnimation('roll-down', function(element, options) {
  9632. var deferred, previousHeightStr, styleMemo;
  9633. previousHeightStr = e.style(element, 'height');
  9634. styleMemo = e.setTemporaryStyle(element, {
  9635. height: '0px',
  9636. overflow: 'hidden'
  9637. });
  9638. deferred = animate(element, {
  9639. height: previousHeightStr
  9640. }, options);
  9641. deferred.then(styleMemo);
  9642. return deferred;
  9643. });
  9644. registerTransition('move-left', ['move-to-left', 'move-from-right']);
  9645. registerTransition('move-right', ['move-to-right', 'move-from-left']);
  9646. registerTransition('move-up', ['move-to-top', 'move-from-bottom']);
  9647. registerTransition('move-down', ['move-to-bottom', 'move-from-top']);
  9648. registerTransition('cross-fade', ['fade-out', 'fade-in']);
  9649. up.on('up:framework:booted', snapshot);
  9650. up.on('up:framework:reset', reset);
  9651. return {
  9652. morph: morph,
  9653. animate: animate,
  9654. animateOptions: animateOptions,
  9655. finish: finish,
  9656. finishCount: function() {
  9657. return motionController.finishCount;
  9658. },
  9659. transition: registerTransition,
  9660. animation: registerAnimation,
  9661. config: config,
  9662. isEnabled: isEnabled,
  9663. isNone: isNone
  9664. };
  9665. })();
  9666. up.transition = up.motion.transition;
  9667. up.animation = up.motion.animation;
  9668. up.morph = up.motion.morph;
  9669. up.animate = up.motion.animate;
  9670. }).call(this);
  9671. /***
  9672. AJAX acceleration
  9673. =================
  9674. Unpoly comes with a number of tricks to shorten the latency between browser and server.
  9675. \#\#\# Server responses are cached by default
  9676. Unpoly caches server responses for a few minutes,
  9677. making requests to these URLs return instantly.
  9678. All Unpoly functions and selectors go through this cache, unless
  9679. you explicitly pass a `{ cache: false }` option or set an `up-cache="false"` attribute.
  9680. The cache holds up to 70 responses for 5 minutes. You can configure the cache size and expiry using
  9681. [`up.proxy.config`](/up.proxy.config), or clear the cache manually using [`up.proxy.clear()`](/up.proxy.clear).
  9682. Also the entire cache is cleared with every non-`GET` request (like `POST` or `PUT`).
  9683. If you need to make cache-aware requests from your [custom JavaScript](/up.syntax),
  9684. use [`up.request()`](/up.request).
  9685. \#\#\# Preloading links
  9686. Unpoly also lets you speed up reaction times by [preloading
  9687. links](/a-up-preload) when the user hovers over the click area (or puts the mouse/finger
  9688. down). This way the response will already be cached when
  9689. the user releases the mouse/finger.
  9690. \#\#\# Spinners
  9691. You can listen to the [`up:proxy:slow`](/up:proxy:slow) event to implement a spinner
  9692. that appears during a long-running request.
  9693. \#\#\# More acceleration
  9694. Other Unpoly modules contain even more tricks to outsmart network latency:
  9695. - [Instantaneous feedback for links that are currently loading](/a.up-active)
  9696. - [Follow links on `mousedown` instead of `click`](/a-up-instant)
  9697. @module up.proxy
  9698. */
  9699. (function() {
  9700. var slice = [].slice;
  9701. up.proxy = (function() {
  9702. var ajax, alias, cache, cancelPreloadDelay, cancelSlowDelay, clear, config, e, get, isBusy, isIdle, isSafeMethod, load, loadEnded, loadOrQueue, loadStarted, makeRequest, pendingCount, pokeQueue, preload, preloadAfterDelay, preloadDelayTimer, queue, queuedLoaders, registerAliasForRedirect, remove, reset, responseReceived, set, slowDelayTimer, slowEventEmitted, startPreloadDelay, stopPreload, u, waitingLink, wrapMethod;
  9703. u = up.util;
  9704. e = up.element;
  9705. waitingLink = void 0;
  9706. preloadDelayTimer = void 0;
  9707. slowDelayTimer = void 0;
  9708. pendingCount = void 0;
  9709. slowEventEmitted = void 0;
  9710. queuedLoaders = [];
  9711. /***
  9712. @property up.proxy.config
  9713. @param {number} [config.preloadDelay=75]
  9714. The number of milliseconds to wait before [`[up-preload]`](/a-up-preload)
  9715. starts preloading.
  9716. @param {number} [config.cacheSize=70]
  9717. The maximum number of responses to cache.
  9718. If the size is exceeded, the oldest items will be dropped from the cache.
  9719. @param {number} [config.cacheExpiry=300000]
  9720. The number of milliseconds until a cache entry expires.
  9721. Defaults to 5 minutes.
  9722. @param {number} [config.slowDelay=300]
  9723. How long the proxy waits until emitting the [`up:proxy:slow` event](/up:proxy:slow).
  9724. Use this to prevent flickering of spinners.
  9725. @param {number} [config.maxRequests=4]
  9726. The maximum number of concurrent requests to allow before additional
  9727. requests are queued. This currently ignores preloading requests.
  9728. You might find it useful to set this to `1` in full-stack integration
  9729. tests (e.g. Selenium).
  9730. Note that your browser might [impose its own request limit](http://www.browserscope.org/?category=network)
  9731. regardless of what you configure here.
  9732. @param {Array<string>} [config.wrapMethods]
  9733. An array of uppercase HTTP method names. AJAX requests with one of these methods
  9734. will be converted into a `POST` request and carry their original method as a `_method`
  9735. parameter. This is to [prevent unexpected redirect behavior](https://makandracards.com/makandra/38347).
  9736. @param {Array<string>} [config.safeMethods]
  9737. An array of uppercase HTTP method names that are considered [safe](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.1.1).
  9738. The proxy cache will only cache safe requests and will clear the entire
  9739. cache after an unsafe request.
  9740. @stable
  9741. */
  9742. config = new up.Config({
  9743. slowDelay: 300,
  9744. preloadDelay: 75,
  9745. cacheSize: 70,
  9746. cacheExpiry: 1000 * 60 * 5,
  9747. maxRequests: 4,
  9748. wrapMethods: ['PATCH', 'PUT', 'DELETE'],
  9749. safeMethods: ['GET', 'OPTIONS', 'HEAD']
  9750. });
  9751. cache = new up.Cache({
  9752. size: function() {
  9753. return config.cacheSize;
  9754. },
  9755. expiry: function() {
  9756. return config.cacheExpiry;
  9757. },
  9758. key: function(request) {
  9759. return up.Request.wrap(request).cacheKey();
  9760. },
  9761. cachable: function(request) {
  9762. return up.Request.wrap(request).isCachable();
  9763. }
  9764. });
  9765. /***
  9766. Returns a cached response for the given request.
  9767. Returns `undefined` if the given request is not currently cached.
  9768. @function up.proxy.get
  9769. @return {Promise<up.Response>}
  9770. A promise for the response.
  9771. @experimental
  9772. */
  9773. get = function(request) {
  9774. var candidate, candidates, i, len, requestForBody, requestForHtml, response;
  9775. request = up.Request.wrap(request);
  9776. candidates = [request];
  9777. if (request.target !== 'html') {
  9778. requestForHtml = request.variant({
  9779. target: 'html'
  9780. });
  9781. candidates.push(requestForHtml);
  9782. if (request.target !== 'body') {
  9783. requestForBody = request.variant({
  9784. target: 'body'
  9785. });
  9786. candidates.push(requestForBody);
  9787. }
  9788. }
  9789. for (i = 0, len = candidates.length; i < len; i++) {
  9790. candidate = candidates[i];
  9791. if (response = cache.get(candidate)) {
  9792. return response;
  9793. }
  9794. }
  9795. };
  9796. cancelPreloadDelay = function() {
  9797. clearTimeout(preloadDelayTimer);
  9798. return preloadDelayTimer = null;
  9799. };
  9800. cancelSlowDelay = function() {
  9801. clearTimeout(slowDelayTimer);
  9802. return slowDelayTimer = null;
  9803. };
  9804. reset = function() {
  9805. waitingLink = null;
  9806. cancelPreloadDelay();
  9807. cancelSlowDelay();
  9808. pendingCount = 0;
  9809. config.reset();
  9810. cache.clear();
  9811. slowEventEmitted = false;
  9812. return queuedLoaders = [];
  9813. };
  9814. reset();
  9815. /***
  9816. Makes an AJAX request to the given URL.
  9817. \#\#\# Example
  9818. up.request('/search', { params: { query: 'sunshine' } }).then(function(response) {
  9819. console.log('The response text is %o', response.text)
  9820. }).catch(function() {
  9821. console.error('The request failed')
  9822. })
  9823. \#\#\# Caching
  9824. All responses are cached by default. If requesting a URL with a non-`GET` method, the response will
  9825. not be cached and the entire cache will be cleared.
  9826. You can configure caching with the [`up.proxy.config`](/up.proxy.config) property.
  9827. \#\#\# Events
  9828. If a network connection is attempted, the proxy will emit
  9829. a [`up:proxy:load`](/up:proxy:load) event with the `request` as its argument.
  9830. Once the response is received, a [`up:proxy:loaded`](/up:proxy:loaded) event will
  9831. be emitted.
  9832. @function up.request
  9833. @param {string} [url]
  9834. The URL for the request.
  9835. Instead of passing the URL as a string argument, you can also pass it as an `{ url }` option.
  9836. @param {string} [options.url]
  9837. You can omit the first string argument and pass the URL as
  9838. a `request` property instead.
  9839. @param {string} [options.method='GET']
  9840. The HTTP method for the options.
  9841. @param {boolean} [options.cache]
  9842. Whether to use a cached response for [safe](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.1.1)
  9843. requests, if available. If set to `false` a network connection will always be attempted.
  9844. @param {Object} [options.headers={}]
  9845. An object of additional HTTP headers.
  9846. @param {Object|FormData|string|Array} [options.params={}]
  9847. [Parameters](/up.Params) that should be sent as the request's payload.
  9848. @param {string} [options.timeout]
  9849. A timeout in milliseconds.
  9850. If [`up.proxy.config.maxRequests`](/up.proxy.config#config.maxRequests) is set, the timeout
  9851. will not include the time spent waiting in the queue.
  9852. @param {string} [options.target='body']
  9853. The CSS selector that will be sent as an [`X-Up-Target` header](/up.protocol#optimizing-responses).
  9854. @param {string} [options.failTarget='body']
  9855. The CSS selector that will be sent as an [`X-Up-Fail-Target` header](/up.protocol#optimizing-responses).
  9856. @return {Promise<up.Response>}
  9857. A promise for the response.
  9858. @stable
  9859. */
  9860. makeRequest = function() {
  9861. var args, ignoreCache, promise, request, requestOrOptions, url;
  9862. args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
  9863. if (u.isString(args[0])) {
  9864. url = args.shift();
  9865. }
  9866. requestOrOptions = args.shift() || {};
  9867. if (url) {
  9868. requestOrOptions.url = url;
  9869. }
  9870. request = up.Request.wrap(requestOrOptions);
  9871. if (!request.isSafe()) {
  9872. clear();
  9873. }
  9874. ignoreCache = request.cache === false;
  9875. if (!ignoreCache && (promise = get(request))) {
  9876. up.puts('Re-using cached response for %s %s', request.method, request.url);
  9877. } else {
  9878. promise = loadOrQueue(request);
  9879. set(request, promise);
  9880. promise["catch"](function() {
  9881. return remove(request);
  9882. });
  9883. }
  9884. if (!request.preload) {
  9885. loadStarted();
  9886. u.always(promise, loadEnded);
  9887. }
  9888. return promise;
  9889. };
  9890. /***
  9891. Makes an AJAX request to the given URL and caches the response.
  9892. The function returns a promise that fulfills with the response text.
  9893. \#\#\# Example
  9894. up.request('/search', { params: { query: 'sunshine' } }).then(function(text) {
  9895. console.log('The response text is %o', text)
  9896. }).catch(function() {
  9897. console.error('The request failed')
  9898. })
  9899. @function up.ajax
  9900. @param {string} [url]
  9901. The URL for the request.
  9902. Instead of passing the URL as a string argument, you can also pass it as an `{ url }` option.
  9903. @param {string} [request.url]
  9904. You can omit the first string argument and pass the URL as
  9905. a `request` property instead.
  9906. @param {string} [request.method='GET']
  9907. The HTTP method for the request.
  9908. @param {boolean} [request.cache]
  9909. Whether to use a cached response for [safe](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.1.1)
  9910. requests, if available. If set to `false` a network connection will always be attempted.
  9911. @param {Object} [request.headers={}]
  9912. An object of additional header key/value pairs to send along
  9913. with the request.
  9914. @param {Object|FormData|string|Array} [options.params]
  9915. [Parameters](/up.Params) that should be sent as the request's payload.
  9916. On IE 11 and Edge, `FormData` payloads require a [polyfill for `FormData#entries()`](https://github.com/jimmywarting/FormData).
  9917. @param {string} [request.timeout]
  9918. A timeout in milliseconds for the request.
  9919. If [`up.proxy.config.maxRequests`](/up.proxy.config#config.maxRequests) is set, the timeout
  9920. will not include the time spent waiting in the queue.
  9921. @return {Promise<string>}
  9922. A promise for the response text.
  9923. @deprecated
  9924. Use [`up.request()`](/up.request) instead.
  9925. */
  9926. ajax = function() {
  9927. var args;
  9928. args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
  9929. up.legacy.warn('up.ajax() has been deprecated. Use up.request() instead.');
  9930. return new Promise(function(resolve, reject) {
  9931. var pickResponseText;
  9932. pickResponseText = function(response) {
  9933. return resolve(response.text);
  9934. };
  9935. return makeRequest.apply(null, args).then(pickResponseText, reject);
  9936. });
  9937. };
  9938. /***
  9939. Returns `true` if the proxy is not currently waiting
  9940. for a request to finish. Returns `false` otherwise.
  9941. @function up.proxy.isIdle
  9942. @return {boolean}
  9943. Whether the proxy is idle
  9944. @experimental
  9945. */
  9946. isIdle = function() {
  9947. return pendingCount === 0;
  9948. };
  9949. /***
  9950. Returns `true` if the proxy is currently waiting
  9951. for a request to finish. Returns `false` otherwise.
  9952. @function up.proxy.isBusy
  9953. @return {boolean}
  9954. Whether the proxy is busy
  9955. @experimental
  9956. */
  9957. isBusy = function() {
  9958. return pendingCount > 0;
  9959. };
  9960. loadStarted = function() {
  9961. var emission;
  9962. pendingCount += 1;
  9963. if (!slowDelayTimer) {
  9964. emission = function() {
  9965. if (isBusy()) {
  9966. up.emit('up:proxy:slow', {
  9967. log: 'Proxy is slow to respond'
  9968. });
  9969. return slowEventEmitted = true;
  9970. }
  9971. };
  9972. return slowDelayTimer = u.timer(config.slowDelay, emission);
  9973. }
  9974. };
  9975. /***
  9976. This event is [emitted](/up.emit) when [AJAX requests](/up.request)
  9977. are taking long to finish.
  9978. By default Unpoly will wait 300 ms for an AJAX request to finish
  9979. before emitting `up:proxy:slow`. You can configure this time like this:
  9980. up.proxy.config.slowDelay = 150;
  9981. Once all responses have been received, an [`up:proxy:recover`](/up:proxy:recover)
  9982. will be emitted.
  9983. Note that if additional requests are made while Unpoly is already busy
  9984. waiting, **no** additional `up:proxy:slow` events will be triggered.
  9985. \#\#\# Spinners
  9986. You can [listen](/up.on) to the `up:proxy:slow`
  9987. and [`up:proxy:recover`](/up:proxy:recover) events to implement a spinner
  9988. that appears during a long-running request,
  9989. and disappears once the response has been received:
  9990. <div class="spinner">Please wait!</div>
  9991. Here is the JavaScript to make it alive:
  9992. up.compiler('.spinner', function(element) {
  9993. show = () => { up.element.show(element) }
  9994. hide = () => { up.element.hide(element) }
  9995. hide()
  9996. return [
  9997. up.on('up:proxy:slow', show),
  9998. up.on('up:proxy:recover', hide)
  9999. ]
  10000. })
  10001. The `up:proxy:slow` event will be emitted after a delay of 300 ms
  10002. to prevent the spinner from flickering on and off.
  10003. You can change (or remove) this delay by [configuring `up.proxy`](/up.proxy.config) like this:
  10004. up.proxy.config.slowDelay = 150;
  10005. @event up:proxy:slow
  10006. @stable
  10007. */
  10008. loadEnded = function() {
  10009. pendingCount -= 1;
  10010. if (isIdle()) {
  10011. cancelSlowDelay();
  10012. if (slowEventEmitted) {
  10013. up.emit('up:proxy:recover', {
  10014. log: 'Proxy has recovered from slow response'
  10015. });
  10016. return slowEventEmitted = false;
  10017. }
  10018. }
  10019. };
  10020. /***
  10021. This event is [emitted](/up.emit) when [AJAX requests](/up.request)
  10022. have [taken long to finish](/up:proxy:slow), but have finished now.
  10023. See [`up:proxy:slow`](/up:proxy:slow) for more documentation on
  10024. how to use this event for implementing a spinner that shows during
  10025. long-running requests.
  10026. @event up:proxy:recover
  10027. @stable
  10028. */
  10029. loadOrQueue = function(request) {
  10030. if (pendingCount < config.maxRequests) {
  10031. return load(request);
  10032. } else {
  10033. return queue(request);
  10034. }
  10035. };
  10036. queue = function(request) {
  10037. var loader;
  10038. up.puts('Queuing request for %s %s', request.method, request.url);
  10039. loader = function() {
  10040. return load(request);
  10041. };
  10042. loader = u.previewable(loader);
  10043. queuedLoaders.push(loader);
  10044. return loader.promise;
  10045. };
  10046. load = function(request) {
  10047. var eventProps, responsePromise;
  10048. eventProps = {
  10049. request: request,
  10050. log: ['Loading %s %s', request.method, request.url]
  10051. };
  10052. if (up.event.nobodyPrevents('up:proxy:load', eventProps)) {
  10053. responsePromise = request.send();
  10054. u.always(responsePromise, responseReceived);
  10055. u.always(responsePromise, pokeQueue);
  10056. return responsePromise;
  10057. } else {
  10058. u.microtask(pokeQueue);
  10059. return Promise.reject(new Error('Event up:proxy:load was prevented'));
  10060. }
  10061. };
  10062. /***
  10063. This event is [emitted](/up.emit) before an [AJAX request](/up.request)
  10064. is sent over the network.
  10065. @event up:proxy:load
  10066. @param {up.Request} event.request
  10067. @param event.preventDefault()
  10068. Event listeners may call this method to prevent the request from being sent.
  10069. @experimental
  10070. */
  10071. registerAliasForRedirect = function(response) {
  10072. var newRequest, request;
  10073. request = response.request;
  10074. if (response.url && request.url !== response.url) {
  10075. newRequest = request.variant({
  10076. method: response.method,
  10077. url: response.url
  10078. });
  10079. return up.proxy.alias(request, newRequest);
  10080. }
  10081. };
  10082. responseReceived = function(response) {
  10083. if (response.isFatalError()) {
  10084. return up.emit('up:proxy:fatal', {
  10085. log: 'Fatal error during request',
  10086. request: response.request,
  10087. response: response
  10088. });
  10089. } else {
  10090. if (!response.isError()) {
  10091. registerAliasForRedirect(response);
  10092. }
  10093. return up.emit('up:proxy:loaded', {
  10094. log: ['Server responded with HTTP %d (%d bytes)', response.status, response.text.length],
  10095. request: response.request,
  10096. response: response
  10097. });
  10098. }
  10099. };
  10100. /***
  10101. This event is [emitted](/up.emit) when the response to an
  10102. [AJAX request](/up.request) has been received.
  10103. Note that this event will also be emitted when the server signals an
  10104. error with an HTTP status like `500`. Only if the request
  10105. encounters a fatal error (like a loss of network connectivity),
  10106. [`up:proxy:fatal`](/up:proxy:fatal) is emitted instead.
  10107. @event up:proxy:loaded
  10108. @param {up.Request} event.request
  10109. @param {up.Response} event.response
  10110. @experimental
  10111. */
  10112. /***
  10113. This event is [emitted](/up.emit) when an [AJAX request](/up.request)
  10114. encounters fatal error like a timeout or loss of network connectivity.
  10115. Note that this event will *not* be emitted when the server produces an
  10116. error message with an HTTP status like `500`. When the server can produce
  10117. any response, [`up:proxy:loaded`](/up:proxy:loaded) is emitted instead.
  10118. @event up:proxy:fatal
  10119. */
  10120. pokeQueue = function() {
  10121. var base;
  10122. if (typeof (base = queuedLoaders.shift()) === "function") {
  10123. base();
  10124. }
  10125. return void 0;
  10126. };
  10127. /***
  10128. Makes the proxy assume that `newRequest` has the same response as the
  10129. already cached `oldRequest`.
  10130. Unpoly uses this internally when the user redirects from `/old` to `/new`.
  10131. In that case, both `/old` and `/new` will cache the same response from `/new`.
  10132. @function up.proxy.alias
  10133. @param {Object} oldRequest
  10134. @param {Object} newRequest
  10135. @experimental
  10136. */
  10137. alias = cache.alias;
  10138. /***
  10139. Manually stores a promise for the response to the given request.
  10140. @function up.proxy.set
  10141. @param {string} request.url
  10142. @param {string} [request.method='GET']
  10143. @param {string} [request.target='body']
  10144. @param {Promise<up.Response>} response
  10145. A promise for the response.
  10146. @experimental
  10147. */
  10148. set = cache.set;
  10149. /***
  10150. Manually removes the given request from the cache.
  10151. You can also [configure](/up.proxy.config) when the proxy
  10152. automatically removes cache entries.
  10153. @function up.proxy.remove
  10154. @param {string} request.url
  10155. @param {string} [request.method='GET']
  10156. @param {string} [request.target='body']
  10157. @experimental
  10158. */
  10159. remove = cache.remove;
  10160. /***
  10161. Removes all cache entries.
  10162. Unpoly also automatically clears the cache whenever it processes
  10163. a request with an [unsafe](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.1.1)
  10164. HTTP method like `POST`.
  10165. @function up.proxy.clear
  10166. @stable
  10167. */
  10168. clear = cache.clear;
  10169. preloadAfterDelay = function(link) {
  10170. var curriedPreload, delay;
  10171. delay = e.numberAttr(link, 'up-delay') || config.preloadDelay;
  10172. if (link !== waitingLink) {
  10173. waitingLink = link;
  10174. cancelPreloadDelay();
  10175. curriedPreload = function() {
  10176. u.muteRejection(preload(link));
  10177. return waitingLink = null;
  10178. };
  10179. return startPreloadDelay(curriedPreload, delay);
  10180. }
  10181. };
  10182. startPreloadDelay = function(block, delay) {
  10183. return preloadDelayTimer = setTimeout(block, delay);
  10184. };
  10185. stopPreload = function(link) {
  10186. if (link === waitingLink) {
  10187. waitingLink = void 0;
  10188. return cancelPreloadDelay();
  10189. }
  10190. };
  10191. /***
  10192. Preloads the given link.
  10193. When the link is clicked later, the response will already be cached,
  10194. making the interaction feel instant.
  10195. @function up.proxy.preload
  10196. @param {string|Element|jQuery} linkOrSelector
  10197. The element whose destination should be preloaded.
  10198. @param {Object} options
  10199. Options that will be passed to the function making the HTTP requests.
  10200. @return
  10201. A promise that will be fulfilled when the request was loaded and cached
  10202. @experimental
  10203. */
  10204. preload = function(linkOrSelector, options) {
  10205. var link, preloadEventAttrs;
  10206. link = e.get(linkOrSelector);
  10207. if (up.link.isSafe(link)) {
  10208. preloadEventAttrs = {
  10209. log: ['Preloading link %o', link],
  10210. target: link
  10211. };
  10212. return up.event.whenEmitted('up:link:preload', preloadEventAttrs).then(function() {
  10213. var variant;
  10214. variant = up.link.followVariantForLink(link);
  10215. return variant.preloadLink(link, options);
  10216. });
  10217. } else {
  10218. return Promise.reject(new Error("Won't preload unsafe link"));
  10219. }
  10220. };
  10221. /***
  10222. This event is [emitted](/up.emit) before a link is [preloaded](/up.preload).
  10223. @event up:link:preload
  10224. @param {Element} event.target
  10225. The link element that will be preloaded.
  10226. @param event.preventDefault()
  10227. Event listeners may call this method to prevent the link from being preloaded.
  10228. @stable
  10229. */
  10230. /***
  10231. @internal
  10232. */
  10233. isSafeMethod = function(method) {
  10234. return u.contains(config.safeMethods, method);
  10235. };
  10236. /***
  10237. @internal
  10238. */
  10239. wrapMethod = function(method, params) {
  10240. if (u.contains(config.wrapMethods, method)) {
  10241. params.add(up.protocol.config.methodParam, method);
  10242. method = 'POST';
  10243. }
  10244. return method;
  10245. };
  10246. /***
  10247. Links with an `up-preload` attribute will silently fetch their target
  10248. when the user hovers over the click area, or when the user puts her
  10249. mouse/finger down (before releasing).
  10250. When the link is clicked later, the response will already be cached,
  10251. making the interaction feel instant.
  10252. @selector a[up-preload]
  10253. @param [up-delay=75]
  10254. The number of milliseconds to wait between hovering
  10255. and preloading. Increasing this will lower the load in your server,
  10256. but will also make the interaction feel less instant.
  10257. @stable
  10258. */
  10259. up.compiler('a[up-preload], [up-href][up-preload]', function(link) {
  10260. if (up.link.isSafe(link)) {
  10261. link.addEventListener('mouseenter', function(event) {
  10262. if (up.link.shouldProcessEvent(event, link)) {
  10263. return preloadAfterDelay(link);
  10264. }
  10265. });
  10266. return link.addEventListener('mouseleave', function() {
  10267. return stopPreload(link);
  10268. });
  10269. }
  10270. });
  10271. up.on('up:framework:reset', reset);
  10272. return {
  10273. preload: preload,
  10274. ajax: ajax,
  10275. request: makeRequest,
  10276. get: get,
  10277. alias: alias,
  10278. clear: clear,
  10279. remove: remove,
  10280. isIdle: isIdle,
  10281. isBusy: isBusy,
  10282. isSafeMethod: isSafeMethod,
  10283. wrapMethod: wrapMethod,
  10284. config: config
  10285. };
  10286. })();
  10287. up.ajax = up.proxy.ajax;
  10288. up.request = up.proxy.request;
  10289. }).call(this);
  10290. /***
  10291. Linking to fragments
  10292. ====================
  10293. The `up.link` module lets you build links that update fragments instead of entire pages.
  10294. \#\#\# Motivation
  10295. In a traditional web application, the entire page is destroyed and re-created when the
  10296. user follows a link:
  10297. ![Traditional page flow](/images/tutorial/fragment_flow_vanilla.svg){:width="620" class="picture has_border is_sepia has_padding"}
  10298. This makes for an unfriendly experience:
  10299. - State changes caused by AJAX updates get lost during the page transition.
  10300. - Unsaved form changes get lost during the page transition.
  10301. - The JavaScript VM is reset during the page transition.
  10302. - If the page layout is composed from multiple scrollable containers
  10303. (e.g. a pane view), the scroll positions get lost during the page transition.
  10304. - The user sees a "flash" as the browser loads and renders the new page,
  10305. even if large portions of the old and new page are the same (navigation, layout, etc.).
  10306. Unpoly fixes this by letting you annotate links with an [`up-target`](/a-up-target)
  10307. attribute. The value of this attribute is a CSS selector that indicates which page
  10308. fragment to update. The server **still renders full HTML pages**, but we only use
  10309. the targeted fragments and discard the rest:
  10310. ![Unpoly page flow](/images/tutorial/fragment_flow_unpoly.svg){:width="620" class="picture has_border is_sepia has_padding"}
  10311. With this model, following links feels smooth. All transient DOM changes outside the updated fragment are preserved.
  10312. Pages also load much faster since the DOM, CSS and Javascript environments do not need to be
  10313. destroyed and recreated for every request.
  10314. \#\#\# Example
  10315. Let's say we are rendering three pages with a tabbed navigation to switch between screens:
  10316. Your HTML could look like this:
  10317. ```
  10318. <nav>
  10319. <a href="/pages/a">A</a>
  10320. <a href="/pages/b">B</a>
  10321. <a href="/pages/b">C</a>
  10322. </nav>
  10323. <article>
  10324. Page A
  10325. </article>
  10326. ```
  10327. Since we only want to update the `<article>` tag, we annotate the links
  10328. with an `up-target` attribute:
  10329. ```
  10330. <nav>
  10331. <a href="/pages/a" up-target="article">A</a>
  10332. <a href="/pages/b" up-target="article">B</a>
  10333. <a href="/pages/b" up-target="article">C</a>
  10334. </nav>
  10335. ```
  10336. Note that instead of `article` you can use any other CSS selector like `#main .article`.
  10337. With these [`up-target`](/a-up-target) annotations Unpoly only updates the targeted part of the screen.
  10338. The JavaScript environment will persist and the user will not see a white flash while the
  10339. new page is loading.
  10340. @module up.link
  10341. */
  10342. (function() {
  10343. up.link = (function() {
  10344. var DEFAULT_FOLLOW_VARIANT, addFollowVariant, allowDefault, defaultFollow, defaultPreload, e, follow, followMethod, followVariantForLink, followVariants, isFollowable, isSafe, makeFollowable, shouldProcessEvent, u, visit;
  10345. u = up.util;
  10346. e = up.element;
  10347. /***
  10348. Fetches this given URL with JavaScript and [replaces](/up.replace) the
  10349. current `<body>` element with the response's `<body>` element.
  10350. \#\#\# Example
  10351. This would replace the current page with the response for `/users`:
  10352. up.visit('/users')
  10353. @function up.visit
  10354. @param {string} url
  10355. The URL to visit.
  10356. @param {string} [options.target='body']
  10357. The selector to replace.
  10358. @param {Object} [options]
  10359. See options for [`up.replace()`](/up.replace)
  10360. @stable
  10361. */
  10362. visit = function(url, options) {
  10363. var ref, selector;
  10364. if (options == null) {
  10365. options = {};
  10366. }
  10367. selector = (ref = options.target) != null ? ref : 'body';
  10368. return up.replace(selector, url, options);
  10369. };
  10370. /***
  10371. Fetches the given link's `[href]` with JavaScript and [replaces](/up.replace) the current page with HTML from the response.
  10372. By default the page's `<body>` element will be replaced.
  10373. If the link has an attribute like `a[up-target]`
  10374. or `a[up-modal]`, the respective Unpoly behavior will be used.
  10375. Emits the event `up:link:follow`.
  10376. \#\#\# Examples
  10377. Assume we have a link with an `a[up-target]` attribute:
  10378. <a href="/users" up-target=".main">Users</a>
  10379. Calling `up.follow()` with this link will replace the page's `.main` fragment
  10380. as if the user had clicked on the link:
  10381. var link = document.querySelector('a')
  10382. up.follow(link)
  10383. @function up.follow
  10384. @param {Element|jQuery|string} linkOrSelector
  10385. An element or selector which is either an `<a>` tag or any element with an `[up-href]` attribute.
  10386. @param {string} [options.target]
  10387. The selector to replace.
  10388. Defaults to the link's `[up-target]`, `[up-modal]` or `[up-popup]` attribute.
  10389. If no target is given, the `<body>` element will be replaced.
  10390. @param {String} [options.url]
  10391. The URL to navigate to.
  10392. Defaults to the link's `[up-href]` or `[href]` attribute.
  10393. @param {boolean|string} [options.reveal=true]
  10394. Whether to [reveal](/up.reveal) the target fragment after it was replaced.
  10395. You can also pass a CSS selector for the element to reveal.
  10396. @param {boolean|string} [options.failReveal=true]
  10397. Whether to [reveal](/up.reveal) the target fragment when the server responds with an error.
  10398. You can also pass a CSS selector for the element to reveal.
  10399. @return {Promise}
  10400. A promise that will be fulfilled when the link destination
  10401. has been loaded and rendered.
  10402. @stable
  10403. */
  10404. follow = function(linkOrSelector, options) {
  10405. var link, variant;
  10406. link = e.get(linkOrSelector);
  10407. variant = followVariantForLink(link);
  10408. return variant.followLink(link, options);
  10409. };
  10410. /***
  10411. This event is [emitted](/up.emit) when a link is [followed](/up.follow) through Unpoly.
  10412. The event is emitted on the `<a>` element that is being followed.
  10413. @event up:link:follow
  10414. @param {Element} event.target
  10415. The link element that will be followed.
  10416. @param event.preventDefault()
  10417. Event listeners may call this method to prevent the link from being followed.
  10418. @stable
  10419. */
  10420. /***
  10421. @function defaultFollow
  10422. @internal
  10423. */
  10424. defaultFollow = function(link, options) {
  10425. var ref, ref1, ref2, ref3, ref4, target, url;
  10426. options = u.options(options);
  10427. url = (ref = (ref1 = options.url) != null ? ref1 : link.getAttribute('up-href')) != null ? ref : link.getAttribute('href');
  10428. target = (ref2 = options.target) != null ? ref2 : link.getAttribute('up-target');
  10429. if (options.failTarget == null) {
  10430. options.failTarget = link.getAttribute('up-fail-target');
  10431. }
  10432. if (options.fallback == null) {
  10433. options.fallback = link.getAttribute('up-fallback');
  10434. }
  10435. if (options.transition == null) {
  10436. options.transition = e.booleanOrStringAttr(link, 'up-transition');
  10437. }
  10438. if (options.failTransition == null) {
  10439. options.failTransition = e.booleanOrStringAttr(link, 'up-fail-transition');
  10440. }
  10441. if (options.history == null) {
  10442. options.history = e.booleanOrStringAttr(link, 'up-history');
  10443. }
  10444. if (options.reveal == null) {
  10445. options.reveal = (ref3 = e.booleanOrStringAttr(link, 'up-reveal')) != null ? ref3 : true;
  10446. }
  10447. if (options.failReveal == null) {
  10448. options.failReveal = (ref4 = e.booleanOrStringAttr(link, 'up-fail-reveal')) != null ? ref4 : true;
  10449. }
  10450. if (options.cache == null) {
  10451. options.cache = e.booleanAttr(link, 'up-cache');
  10452. }
  10453. if (options.restoreScroll == null) {
  10454. options.restoreScroll = e.booleanAttr(link, 'up-restore-scroll');
  10455. }
  10456. options.method = followMethod(link, options);
  10457. if (options.origin == null) {
  10458. options.origin = link;
  10459. }
  10460. if (options.layer == null) {
  10461. options.layer = link.getAttribute('up-layer');
  10462. }
  10463. if (options.failLayer == null) {
  10464. options.failLayer = link.getAttribute('up-fail-layer');
  10465. }
  10466. if (options.confirm == null) {
  10467. options.confirm = link.getAttribute('up-confirm');
  10468. }
  10469. if (options.scrollBehavior == null) {
  10470. options.scrollBehavior = link.getAttribute('up-scroll-behavior');
  10471. }
  10472. if (options.scrollSpeed == null) {
  10473. options.scrollSpeed = link.getAttribute('up-scroll-speed');
  10474. }
  10475. options = u.merge(options, up.motion.animateOptions(options, link));
  10476. return up.browser.whenConfirmed(options).then(function() {
  10477. return up.replace(target, url, options);
  10478. });
  10479. };
  10480. defaultPreload = function(link, options) {
  10481. options = u.options(options);
  10482. options.preload = true;
  10483. return defaultFollow(link, options);
  10484. };
  10485. /***
  10486. Returns the HTTP method that should be used when following the given link.
  10487. Looks at the link's `up-method` or `data-method` attribute.
  10488. Defaults to `"get"`.
  10489. @function up.link.followMethod
  10490. @param link
  10491. @param options.method {string}
  10492. @internal
  10493. */
  10494. followMethod = function(link, options) {
  10495. var rawMethod, ref, ref1, ref2;
  10496. if (options == null) {
  10497. options = {};
  10498. }
  10499. rawMethod = (ref = (ref1 = (ref2 = options.method) != null ? ref2 : link.getAttribute('up-method')) != null ? ref1 : link.getAttribute('data-method')) != null ? ref : 'GET';
  10500. return rawMethod.toUpperCase();
  10501. };
  10502. /***
  10503. No-op that is called when we allow a browser's default action to go through,
  10504. so we can spy on it in unit tests. See `link_spec.js`.
  10505. @function allowDefault
  10506. @internal
  10507. */
  10508. allowDefault = function(event) {};
  10509. followVariants = [];
  10510. /***
  10511. Registers the given handler for links with the given selector.
  10512. This does more than a simple `click` handler:
  10513. - It also handles `[up-instant]`
  10514. - It also handles `[up-href]`
  10515. @function up.link.addFollowVariant
  10516. @param {string} simplifiedSelector
  10517. A selector without `a` or `[up-href]`, e.g. `[up-target]`
  10518. @param {Function(element, options)} options.follow
  10519. @param {Function(element, options)} options.preload
  10520. @internal
  10521. */
  10522. addFollowVariant = function(simplifiedSelector, options) {
  10523. var variant;
  10524. variant = new up.FollowVariant(simplifiedSelector, options);
  10525. followVariants.push(variant);
  10526. variant.registerEvents();
  10527. return variant;
  10528. };
  10529. /***
  10530. Returns whether the given link will be [followed](/up.follow) by Unpoly
  10531. instead of making a full page load.
  10532. A link will be followed by Unpoly if it has an attribute
  10533. like `a[up-target]` or `a[up-modal]`.
  10534. @function up.link.isFollowable
  10535. @param {Element|jQuery|string} linkOrSelector
  10536. The link to check.
  10537. @experimental
  10538. */
  10539. isFollowable = function(linkOrSelector) {
  10540. linkOrSelector = e.get(linkOrSelector);
  10541. return !!followVariantForLink(linkOrSelector, {
  10542. "default": false
  10543. });
  10544. };
  10545. /***
  10546. Returns the handler function that can be used to follow the given link.
  10547. E.g. it wil return a handler calling `up.modal.follow` if the link is a `[up-modal]`,
  10548. but a handler calling `up.link.follow` if the links is `[up-target]`.
  10549. @param {Element} link
  10550. @return {Object}
  10551. @internal
  10552. */
  10553. followVariantForLink = function(link, options) {
  10554. var variant;
  10555. if (options == null) {
  10556. options = {};
  10557. }
  10558. variant = u.find(followVariants, function(variant) {
  10559. return variant.matchesLink(link);
  10560. });
  10561. if (options["default"] !== false) {
  10562. variant || (variant = DEFAULT_FOLLOW_VARIANT);
  10563. }
  10564. return variant;
  10565. };
  10566. /***
  10567. Makes sure that the given link will be [followed](/up.follow)
  10568. by Unpoly instead of making a full page load.
  10569. This is done by giving the link an `a[up-follow]` attribute
  10570. unless it already have it an attribute like `a[up-target]` or `a[up-modal]`.
  10571. @function up.link.makeFollowable
  10572. @param {Element|jQuery|string} linkOrSelector
  10573. The link to process.
  10574. @experimental
  10575. */
  10576. makeFollowable = function(link) {
  10577. if (!isFollowable(link)) {
  10578. return link.setAttribute('up-follow', '');
  10579. }
  10580. };
  10581. shouldProcessEvent = function(event, link) {
  10582. var betterTarget, betterTargetSelector, target;
  10583. target = event.target;
  10584. if (!u.isUnmodifiedMouseEvent(event)) {
  10585. return false;
  10586. }
  10587. if (target === link) {
  10588. return true;
  10589. }
  10590. betterTargetSelector = "a, [up-href], " + (up.form.fieldSelector());
  10591. betterTarget = e.closest(target, betterTargetSelector);
  10592. return !betterTarget || betterTarget === link;
  10593. };
  10594. /***
  10595. Returns whether the given link has a [safe](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.1.1)
  10596. HTTP method like `GET`.
  10597. @function up.link.isSafe
  10598. @experimental
  10599. */
  10600. isSafe = function(selectorOrLink, options) {
  10601. var method;
  10602. method = followMethod(selectorOrLink, options);
  10603. return up.proxy.isSafeMethod(method);
  10604. };
  10605. /***
  10606. [Follows](/up.follow) this link with JavaScript and replaces a CSS selector
  10607. on the current page with a corresponding element from the response.
  10608. \#\#\# Example
  10609. This will update the fragment `<div class="main">` with the same element
  10610. fetched from `/posts/5`:
  10611. <a href="/posts/5" up-target=".main">Read post</a>
  10612. \#\#\# Updating multiple fragments
  10613. You can update multiple fragments from a single request by separating
  10614. separators with a comma (like in CSS).
  10615. For instance, if opening a post should
  10616. also update a bubble showing the number of unread posts, you might
  10617. do this:
  10618. <a href="/posts/5" up-target=".main, .unread-count">Read post</a>
  10619. \#\#\# Appending or prepending content
  10620. By default Unpoly will replace the given selector with the same
  10621. selector from the server response. Instead of replacing you
  10622. can *append* the loaded content to the existing content by using the
  10623. `:after` pseudo selector. In the same fashion, you can use `:before`
  10624. to indicate that you would like the *prepend* the loaded content.
  10625. A practical example would be a paginated list of items. Below the list is
  10626. a button to load the next page. You can append to the existing list
  10627. by using `:after` in the `up-target` selector like this:
  10628. <ul class="tasks">
  10629. <li>Wash car</li>
  10630. <li>Purchase supplies</li>
  10631. <li>Fix tent</li>
  10632. </ul>
  10633. <a href="/page/2" class="next-page" up-target=".tasks:after, .next-page">
  10634. Load more tasks
  10635. </a>
  10636. \#\#\# Following elements that are no links
  10637. You can also use `[up-target]` to turn an arbitrary element into a link.
  10638. In this case, put the link's destination into the `[up-href]` attribute:
  10639. <button up-target=".main" up-href="/foo/bar">Go</button>
  10640. Note that using any element other than `<a>` will prevent users from
  10641. opening the destination in a new tab.
  10642. @selector a[up-target]
  10643. @param {string} up-target
  10644. The CSS selector to replace
  10645. Inside the CSS selector you may refer to this link as `&` ([like in Sass](https://sass-lang.com/documentation/file.SASS_REFERENCE.html#parent-selector)).
  10646. @param {string} [up-method='get']
  10647. The HTTP method to use for the request.
  10648. @param {string} [up-transition='none']
  10649. The [transition](/up.motion) to use for morphing between the old and new elements.
  10650. @param [up-fail-target='body']
  10651. The CSS selector to replace if the server responds with an error.
  10652. Inside the CSS selector you may refer to this link as `&` ([like in Sass](https://sass-lang.com/documentation/file.SASS_REFERENCE.html#parent-selector)).
  10653. @param {string} [up-fail-transition='none']
  10654. The [transition](/up.motion) to use for morphing between the old and new elements
  10655. when the server responds with an error.
  10656. @param {string} [up-fallback]
  10657. The selector to update when the original target was not found in the page.
  10658. @param {string} [up-href]
  10659. The destination URL to follow.
  10660. If omitted, the the link's `href` attribute will be used.
  10661. @param {string} [up-confirm]
  10662. A message that will be displayed in a cancelable confirmation dialog
  10663. before the link is followed.
  10664. @param {string} [up-reveal='true']
  10665. Whether to reveal the target element after it was replaced.
  10666. You can also pass a CSS selector for the element to reveal.
  10667. Inside the CSS selector you may refer to this link as `&` ([like in Sass](https://sass-lang.com/documentation/file.SASS_REFERENCE.html#parent-selector)).
  10668. @param {string} [up-fail-reveal='true']
  10669. Whether to reveal the target element when the server responds with an error.
  10670. You can also pass a CSS selector for the element to reveal.
  10671. Inside the CSS selector you may refer to this link as `&` ([like in Sass](https://sass-lang.com/documentation/file.SASS_REFERENCE.html#parent-selector)).
  10672. @param {string} [up-restore-scroll='false']
  10673. Whether to restore previously known scroll position of all viewports
  10674. within the target selector.
  10675. @param {string} [up-cache]
  10676. Whether to force the use of a cached response (`true`)
  10677. or never use the cache (`false`)
  10678. or make an educated guess (default).
  10679. @param {string} [up-layer='auto']
  10680. The name of the layer that ought to be updated. Valid values are
  10681. `'auto'`, `'page'`, `'modal'` and `'popup'`.
  10682. If set to `'auto'` (default), Unpoly will try to find a match in the link's layer.
  10683. If no match was found in that layer,
  10684. Unpoly will search in other layers, starting from the topmost layer.
  10685. @param {string} [up-fail-layer='auto']
  10686. The name of the layer that ought to be updated if the server sends a
  10687. non-200 status code.
  10688. @param [up-history]
  10689. Whether to push an entry to the browser history when following the link.
  10690. Set this to `'false'` to prevent the URL bar from being updated.
  10691. Set this to a URL string to update the history with the given URL.
  10692. @stable
  10693. */
  10694. DEFAULT_FOLLOW_VARIANT = addFollowVariant('[up-target], [up-follow]', {
  10695. follow: function(link, options) {
  10696. return defaultFollow(link, options);
  10697. },
  10698. preload: function(link, options) {
  10699. return defaultPreload(link, options);
  10700. }
  10701. });
  10702. /***
  10703. Fetches this link's `[href]` with JavaScript and [replaces](/up.replace) the
  10704. current `<body>` element with the response's `<body>` element.
  10705. To only update a fragment instead of the entire `<body>`, see `a[up-target]`.
  10706. \#\#\# Example
  10707. <a href="/users" up-follow>User list</a>
  10708. \#\#\# Turn any element into a link
  10709. You can also use `[up-follow]` to turn an arbitrary element into a link.
  10710. In this case, put the link's destination into the `up-href` attribute:
  10711. <span up-follow up-href="/foo/bar">Go</span>
  10712. Note that using any element other than `<a>` will prevent users from
  10713. opening the destination in a new tab.
  10714. @selector a[up-follow]
  10715. @param {string} [up-method='get']
  10716. The HTTP method to use for the request.
  10717. @param [up-fail-target='body']
  10718. The selector to replace if the server responds with an error.
  10719. @param {string} [up-fallback]
  10720. The selector to update when the original target was not found in the page.
  10721. @param {string} [up-transition='none']
  10722. The [transition](/up.motion) to use for morphing between the old and new elements.
  10723. @param {string} [up-fail-transition='none']
  10724. The [transition](/up.motion) to use for morphing between the old and new elements
  10725. when the server responds with an error.
  10726. @param [up-href]
  10727. The destination URL to follow.
  10728. If omitted, the the link's `href` attribute will be used.
  10729. @param {string} [up-confirm]
  10730. A message that will be displayed in a cancelable confirmation dialog
  10731. before the link is followed.
  10732. @param {string} [up-history]
  10733. Whether to push an entry to the browser history when following the link.
  10734. Set this to `'false'` to prevent the URL bar from being updated.
  10735. Set this to a URL string to update the history with the given URL.
  10736. @param [up-restore-scroll='false']
  10737. Whether to restore the scroll position of all viewports
  10738. within the response.
  10739. @stable
  10740. */
  10741. /***
  10742. By adding an `up-instant` attribute to a link, the destination will be
  10743. fetched on `mousedown` instead of `click` (`mouseup`).
  10744. <a href="/users" up-target=".main" up-instant>User list</a>
  10745. This will save precious milliseconds that otherwise spent
  10746. on waiting for the user to release the mouse button. Since an
  10747. AJAX request will be triggered right way, the interaction will
  10748. appear faster.
  10749. Note that using `[up-instant]` will prevent a user from canceling a
  10750. click by moving the mouse away from the link. However, for
  10751. navigation actions this isn't needed. E.g. popular operation
  10752. systems switch tabs on `mousedown` instead of `click`.
  10753. `[up-instant]` will also work for links that open [modals](/up.modal) or [popups](/up.popup).
  10754. @selector a[up-instant]
  10755. @stable
  10756. */
  10757. /***
  10758. [Follows](/up.follow) this link *as fast as possible*.
  10759. This is done by:
  10760. - [Following the link through AJAX](/a-up-target) instead of a full page load
  10761. - [Preloading the link's destination URL](/a-up-preload)
  10762. - [Triggering the link on `mousedown`](/a-up-instant) instead of on `click`
  10763. \#\#\# Example
  10764. Use `up-dash` like this:
  10765. <a href="/users" up-dash=".main">User list</a>
  10766. This is shorthand for:
  10767. <a href="/users" up-target=".main" up-instant up-preload>User list</a>
  10768. @selector a[up-dash]
  10769. @param {string} [up-dash='body']
  10770. The CSS selector to replace
  10771. Inside the CSS selector you may refer to this link as `&` ([like in Sass](https://sass-lang.com/documentation/file.SASS_REFERENCE.html#parent-selector)).
  10772. @stable
  10773. */
  10774. up.macro('[up-dash]', function(element) {
  10775. var newAttrs, target;
  10776. target = e.booleanOrStringAttr(element, 'up-dash');
  10777. element.removeAttribute('up-dash');
  10778. newAttrs = {
  10779. 'up-preload': '',
  10780. 'up-instant': ''
  10781. };
  10782. if (target === true) {
  10783. makeFollowable(element);
  10784. } else {
  10785. newAttrs['up-target'] = target;
  10786. }
  10787. return e.setMissingAttrs(element, newAttrs);
  10788. });
  10789. /***
  10790. Add an `[up-expand]` attribute to any element to enlarge the click area of a
  10791. descendant link.
  10792. `[up-expand]` honors all the Unppoly attributes in expanded links, like
  10793. `a[up-target]`, `a[up-instant]` or `a[up-preload]`.
  10794. It also expands links that open [modals](/up.modal) or [popups](/up.popup).
  10795. \#\#\# Example
  10796. <div class="notification" up-expand>
  10797. Record was saved!
  10798. <a href="/records">Close</a>
  10799. </div>
  10800. In the example above, clicking anywhere within `.notification` element
  10801. would [follow](/up.follow) the *Close* link.
  10802. \#\#\# Elements with multiple contained links
  10803. If a container contains more than one link, you can set the value of the
  10804. `up-expand` attribute to a CSS selector to define which link should be expanded:
  10805. <div class="notification" up-expand=".close">
  10806. Record was saved!
  10807. <a class="details" href="/records/5">Details</a>
  10808. <a class="close" href="/records">Close</a>
  10809. </div>
  10810. \#\#\# Limitations
  10811. `[up-expand]` has some limitations for advanced browser users:
  10812. - Users won't be able to right-click the expanded area to open a context menu
  10813. - Users won't be able to `CTRL`+click the expanded area to open a new tab
  10814. To overcome these limitations, consider nesting the entire clickable area in an actual `<a>` tag.
  10815. [It's OK to put block elements inside an anchor tag](https://makandracards.com/makandra/43549-it-s-ok-to-put-block-elements-inside-an-a-tag).
  10816. @selector [up-expand]
  10817. @param {string} [up-expand]
  10818. A CSS selector that defines which containing link should be expanded.
  10819. If omitted, the first link in this element will be expanded.
  10820. @stable
  10821. */
  10822. up.macro('[up-expand]', function(area) {
  10823. var attribute, childLink, childLinks, i, len, name, newAttrs, ref, selector, upAttributePattern;
  10824. selector = area.getAttribute('up-expand') || 'a, [up-href]';
  10825. childLinks = e.all(area, selector);
  10826. if (childLink = childLinks[0]) {
  10827. upAttributePattern = /^up-/;
  10828. newAttrs = {};
  10829. newAttrs['up-href'] = childLink.getAttribute('href');
  10830. ref = childLink.attributes;
  10831. for (i = 0, len = ref.length; i < len; i++) {
  10832. attribute = ref[i];
  10833. name = attribute.name;
  10834. if (name.match(upAttributePattern)) {
  10835. newAttrs[name] = attribute.value;
  10836. }
  10837. }
  10838. e.setMissingAttrs(area, newAttrs);
  10839. area.removeAttribute('up-expand');
  10840. return makeFollowable(area);
  10841. }
  10842. });
  10843. return {
  10844. visit: visit,
  10845. follow: follow,
  10846. makeFollowable: makeFollowable,
  10847. isSafe: isSafe,
  10848. isFollowable: isFollowable,
  10849. shouldProcessEvent: shouldProcessEvent,
  10850. followMethod: followMethod,
  10851. addFollowVariant: addFollowVariant,
  10852. followVariantForLink: followVariantForLink,
  10853. allowDefault: allowDefault
  10854. };
  10855. })();
  10856. up.visit = up.link.visit;
  10857. up.follow = up.link.follow;
  10858. }).call(this);
  10859. /***
  10860. Forms
  10861. =====
  10862. Unpoly comes with functionality to [submit](/form-up-target) and [validate](/input-up-validate)
  10863. forms without leaving the current page. This means you can replace page fragments,
  10864. open dialogs with sub-forms, etc. all without losing form state.
  10865. @module up.form
  10866. */
  10867. (function() {
  10868. var slice = [].slice;
  10869. up.form = (function() {
  10870. var autosubmit, closestContainer, config, e, fieldSelector, findFields, findSubmissionFields, findSwitcherForTarget, findValidateTarget, observe, observeCallbackFromElement, reset, submit, submitButtonSelector, submittingButton, switchTarget, switchTargets, switcherValues, u, validate;
  10871. u = up.util;
  10872. e = up.element;
  10873. /***
  10874. Sets default options for form submission and validation.
  10875. @property up.form.config
  10876. @param {number} [config.observeDelay=0]
  10877. The number of miliseconds to wait before [`up.observe()`](/up.observe) runs the callback
  10878. after the input value changes. Use this to limit how often the callback
  10879. will be invoked for a fast typist.
  10880. @param {Array} [config.validateTargets=['[up-fieldset]:has(&)', 'fieldset:has(&)', 'label:has(&)', 'form:has(&)']]
  10881. An array of CSS selectors that are searched around a form field
  10882. that wants to [validate](/up.validate). The first matching selector
  10883. will be updated with the validation messages from the server.
  10884. By default this looks for a `<fieldset>`, `<label>` or `<form>`
  10885. around the validating input field.
  10886. @param {string} [config.fields]
  10887. An array of CSS selectors that represent form fields, such as `input` or `select`.
  10888. @param {string} [config.submitButtons]
  10889. An array of CSS selectors that represent submit buttons, such as `input[type=submit]`.
  10890. @stable
  10891. */
  10892. config = new up.Config({
  10893. validateTargets: ['[up-fieldset]:has(&)', 'fieldset:has(&)', 'label:has(&)', 'form:has(&)'],
  10894. fields: ['select', 'input:not([type=submit]):not([type=image])', 'button[type]:not([type=submit])', 'textarea'],
  10895. submitButtons: ['input[type=submit]', 'input[type=image]', 'button[type=submit]', 'button:not([type])'],
  10896. observeDelay: 0
  10897. });
  10898. reset = function() {
  10899. return config.reset();
  10900. };
  10901. /***
  10902. @function up.form.fieldSelector
  10903. @internal
  10904. */
  10905. fieldSelector = function(suffix) {
  10906. if (suffix == null) {
  10907. suffix = '';
  10908. }
  10909. return config.fields.map(function(field) {
  10910. return field + suffix;
  10911. }).join(',');
  10912. };
  10913. /***
  10914. Returns a list of form fields within the given element.
  10915. You can configure what Unpoly considers a form field by adding CSS selectors to the
  10916. [`up.form.config.fields`](/up.form.config#config.fields) array.
  10917. If the given element is itself a form field, a list of that given element is returned.
  10918. @function up.form.fields
  10919. @param {Element|jQuery} root
  10920. The element to scan for contained form fields.
  10921. If the element is itself a form field, a list of that element is returned.
  10922. @return {NodeList<Element>|Array<Element>}
  10923. @experimental
  10924. */
  10925. findFields = function(root) {
  10926. var fields, outsideFieldSelector, outsideFields;
  10927. root = e.get(root);
  10928. fields = e.subtree(root, fieldSelector());
  10929. if (e.matches(root, 'form[id]')) {
  10930. outsideFieldSelector = fieldSelector(e.attributeSelector('form', root.id));
  10931. outsideFields = e.all(outsideFieldSelector);
  10932. fields.push.apply(fields, outsideFields);
  10933. fields = u.uniq(fields);
  10934. }
  10935. return fields;
  10936. };
  10937. /****
  10938. @function up.form.submissionFields
  10939. @internal
  10940. */
  10941. findSubmissionFields = function(root) {
  10942. var button, fields;
  10943. fields = findFields(root);
  10944. if (button = submittingButton(root)) {
  10945. fields = u.toArray(fields);
  10946. fields.push(button);
  10947. }
  10948. return fields;
  10949. };
  10950. /***
  10951. @function up.form.submittingButton
  10952. @internal
  10953. */
  10954. submittingButton = function(form) {
  10955. var focusedElement, selector;
  10956. selector = submitButtonSelector();
  10957. focusedElement = document.activeElement;
  10958. if (focusedElement && e.matches(focusedElement, selector) && form.contains(focusedElement)) {
  10959. return focusedElement;
  10960. } else {
  10961. return e.first(form, selector);
  10962. }
  10963. };
  10964. /***
  10965. @function up.form.submitButtonSelector
  10966. @internal
  10967. */
  10968. submitButtonSelector = function() {
  10969. return config.submitButtons.join(',');
  10970. };
  10971. /***
  10972. Submits a form via AJAX and updates a page fragment with the response.
  10973. up.submit('form.new-user', { target: '.main' })
  10974. Instead of loading a new page, the form is submitted via AJAX.
  10975. The response is parsed for a CSS selector and the matching elements will
  10976. replace corresponding elements on the current page.
  10977. The unobtrusive variant of this is the [`form[up-target]`](/form-up-target) selector.
  10978. See the documentation for [`form[up-target]`](/form-up-target) for more
  10979. information on how AJAX form submissions work in Unpoly.
  10980. Emits the event [`up:form:submit`](/up:form:submit).
  10981. @function up.submit
  10982. @param {Element|jQuery|string} formOrSelector
  10983. A reference or selector for the form to submit.
  10984. If the argument points to an element that is not a form,
  10985. Unpoly will search its ancestors for the closest form.
  10986. @param {string} [options.url]
  10987. The URL where to submit the form.
  10988. Defaults to the form's `action` attribute, or to the current URL of the browser window.
  10989. @param {string} [options.method='post']
  10990. The HTTP method used for the form submission.
  10991. Defaults to the form's `up-method`, `data-method` or `method` attribute, or to `'post'`
  10992. if none of these attributes are given.
  10993. @param {string} [options.target]
  10994. The CSS selector to update when the form submission succeeds (server responds with status 200).
  10995. Defaults to the form's `up-target` attribute.
  10996. Inside the CSS selector you may refer to the form as `&` ([like in Sass](https://sass-lang.com/documentation/file.SASS_REFERENCE.html#parent-selector)).
  10997. @param {string} [options.failTarget]
  10998. The CSS selector to update when the form submission fails (server responds with non-200 status).
  10999. Defaults to the form's `up-fail-target` attribute, or to an auto-generated
  11000. selector that matches the form itself.
  11001. Inside the CSS selector you may refer to the form as `&` ([like in Sass](https://sass-lang.com/documentation/file.SASS_REFERENCE.html#parent-selector)).
  11002. @param {string} [options.fallback]
  11003. The selector to update when the original target was not found in the page.
  11004. Defaults to the form's `up-fallback` attribute.
  11005. @param {boolean|string} [options.history=true]
  11006. Successful form submissions will add a history entry and change the browser's
  11007. location bar if the form either uses the `GET` method or the response redirected
  11008. to another page (this requires the `unpoly-rails` gem).
  11009. If you want to prevent history changes in any case, set this to `false`.
  11010. If you pass a string, it is used as the URL for the browser history.
  11011. @param {string} [options.transition='none']
  11012. The transition to use when a successful form submission updates the `options.target` selector.
  11013. Defaults to the form's `up-transition` attribute, or to `'none'`.
  11014. @param {string} [options.failTransition='none']
  11015. The transition to use when a failed form submission updates the `options.failTarget` selector.
  11016. Defaults to the form's `up-fail-transition` attribute, or to `options.transition`, or to `'none'`.
  11017. @param {number} [options.duration]
  11018. The duration of the transition. See [`up.morph()`](/up.morph).
  11019. @param {number} [options.delay]
  11020. The delay before the transition starts. See [`up.morph()`](/up.morph).
  11021. @param {string} [options.easing]
  11022. The timing function that controls the transition's acceleration. [`up.morph()`](/up.morph).
  11023. @param {Element|string} [options.reveal=true]
  11024. Whether to reveal the target fragment after it was replaced.
  11025. You can also pass a CSS selector for the element to reveal.
  11026. @param {boolean|string} [options.failReveal=true]
  11027. Whether to [reveal](/up.reveal) the target fragment when the server responds with an error.
  11028. You can also pass a CSS selector for the element to reveal.
  11029. @param {boolean} [options.restoreScroll]
  11030. If set to `true`, this will attempt to [`restore scroll positions`](/up.restoreScroll)
  11031. previously seen on the destination URL.
  11032. @param {boolean} [options.cache]
  11033. Whether to force the use of a cached response (`true`)
  11034. or never use the cache (`false`)
  11035. or make an educated guess (`undefined`).
  11036. By default only responses to `GET` requests are cached
  11037. for a few minutes.
  11038. @param {Object} [options.headers={}]
  11039. An object of additional header key/value pairs to send along
  11040. with the request.
  11041. @param {string} [options.layer='auto']
  11042. The name of the layer that ought to be updated. Valid values are
  11043. `'auto'`, `'page'`, `'modal'` and `'popup'`.
  11044. If set to `'auto'` (default), Unpoly will try to find a match in the form's layer.
  11045. @param {string} [options.failLayer='auto']
  11046. The name of the layer that ought to be updated if the server sends a non-200 status code.
  11047. @return {Promise}
  11048. A promise for the successful form submission.
  11049. @stable
  11050. */
  11051. submit = function(formOrSelector, options) {
  11052. var form, ref, ref1, ref10, ref2, ref3, ref4, ref5, ref6, ref7, ref8, ref9, target, url;
  11053. options = u.options(options);
  11054. form = e.get(formOrSelector);
  11055. form = e.closest(form, 'form');
  11056. target = (ref = (ref1 = options.target) != null ? ref1 : form.getAttribute('up-target')) != null ? ref : 'body';
  11057. if (options.failTarget == null) {
  11058. options.failTarget = (ref2 = form.getAttribute('up-fail-target')) != null ? ref2 : e.toSelector(form);
  11059. }
  11060. if (options.reveal == null) {
  11061. options.reveal = (ref3 = e.booleanOrStringAttr(form, 'up-reveal')) != null ? ref3 : true;
  11062. }
  11063. if (options.failReveal == null) {
  11064. options.failReveal = (ref4 = e.booleanOrStringAttr(form, 'up-fail-reveal')) != null ? ref4 : true;
  11065. }
  11066. if (options.fallback == null) {
  11067. options.fallback = form.getAttribute('up-fallback');
  11068. }
  11069. if (options.history == null) {
  11070. options.history = (ref5 = e.booleanOrStringAttr(form, 'up-history')) != null ? ref5 : true;
  11071. }
  11072. if (options.transition == null) {
  11073. options.transition = e.booleanOrStringAttr(form, 'up-transition');
  11074. }
  11075. if (options.failTransition == null) {
  11076. options.failTransition = e.booleanOrStringAttr(form, 'up-fail-transition');
  11077. }
  11078. if (options.method == null) {
  11079. options.method = u.normalizeMethod((ref6 = (ref7 = (ref8 = form.getAttribute('up-method')) != null ? ref8 : form.getAttribute('data-method')) != null ? ref7 : form.getAttribute('method')) != null ? ref6 : 'post');
  11080. }
  11081. if (options.cache == null) {
  11082. options.cache = e.booleanAttr(form, 'up-cache');
  11083. }
  11084. if (options.restoreScroll == null) {
  11085. options.restoreScroll = e.booleanAttr(form, 'up-restore-scroll');
  11086. }
  11087. if (options.origin == null) {
  11088. options.origin = form;
  11089. }
  11090. if (options.layer == null) {
  11091. options.layer = form.getAttribute('up-layer');
  11092. }
  11093. if (options.failLayer == null) {
  11094. options.failLayer = form.getAttribute('up-fail-layer');
  11095. }
  11096. options.params = up.Params.fromForm(form);
  11097. options = u.merge(options, up.motion.animateOptions(options, form));
  11098. if (options.validate) {
  11099. options.headers || (options.headers = {});
  11100. options.transition = false;
  11101. options.failTransition = false;
  11102. options.headers[up.protocol.config.validateHeader] = options.validate;
  11103. }
  11104. url = (ref9 = (ref10 = options.url) != null ? ref10 : form.getAttribute('action')) != null ? ref9 : up.browser.url();
  11105. if (options.method === 'GET') {
  11106. url = up.Params.stripURL(url);
  11107. }
  11108. return up.event.whenEmitted('up:form:submit', {
  11109. log: 'Submitting form',
  11110. target: form
  11111. }).then(function() {
  11112. var promise;
  11113. up.feedback.start(form);
  11114. if (!(up.browser.canPushState() || options.history === false)) {
  11115. form.submit();
  11116. return u.unresolvablePromise();
  11117. }
  11118. promise = up.replace(target, url, options);
  11119. u.always(promise, function() {
  11120. return up.feedback.stop(form);
  11121. });
  11122. return promise;
  11123. });
  11124. };
  11125. /***
  11126. This event is [emitted](/up.emit) when a form is [submitted](/up.submit) through Unpoly.
  11127. The event is emitted on the`<form>` element.
  11128. @event up:form:submit
  11129. @param {Element} event.target
  11130. The `<form>` element that will be submitted.
  11131. @param event.preventDefault()
  11132. Event listeners may call this method to prevent the form from being submitted.
  11133. @stable
  11134. */
  11135. /***
  11136. Observes form fields and runs a callback when a value changes.
  11137. This is useful for observing text fields while the user is typing.
  11138. The unobtrusive variant of this is the [`[up-observe]`](/up-observe) attribute.
  11139. \#\#\# Example
  11140. The following would print to the console whenever an input field changes:
  11141. up.observe('input.query', function(value) {
  11142. console.log('Query is now %o', value)
  11143. })
  11144. Instead of a single form field, you can also pass multiple fields,
  11145. a `<form>` or any container that contains form fields.
  11146. The callback will be run if any of the given fields change:
  11147. up.observe('form', function(value, name) {
  11148. console.log('The value of %o is now %o', name, value)
  11149. })
  11150. You may also pass the `{ batch: true }` option to receive all
  11151. changes since the last callback in a single object:
  11152. up.observe('form', { batch: true }, function(diff) {
  11153. console.log('Observed one or more changes: %o', diff)
  11154. })
  11155. @function up.observe
  11156. @param {string|Element|Array<Element>|jQuery} elements
  11157. The form fields that will be observed.
  11158. You can pass one or more fields, a `<form>` or any container that contains form fields.
  11159. The callback will be run if any of the given fields change.
  11160. @param {boolean} [options.batch=false]
  11161. If set to `true`, the `onChange` callback will receive multiple
  11162. detected changes in a single diff object as its argument.
  11163. @param {number} [options.delay=up.form.config.observeDelay]
  11164. The number of miliseconds to wait before executing the callback
  11165. after the input value changes. Use this to limit how often the callback
  11166. will be invoked for a fast typist.
  11167. @param {Function(value, name): string} onChange
  11168. The callback to run when the field's value changes.
  11169. If given as a function, it receives two arguments (`value`, `name`).
  11170. `value` is a string with the new attribute value and `string` is the name
  11171. of the form field that changed.
  11172. If given as a string, it will be evaled as JavaScript code in a context where
  11173. (`value`, `name`) are set.
  11174. @return {Function()}
  11175. A destructor function that removes the observe watch when called.
  11176. @stable
  11177. */
  11178. observe = function() {
  11179. var args, callback, elements, fields, observer, options, ref, ref1, ref2, ref3;
  11180. elements = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];
  11181. elements = e.list(elements);
  11182. fields = u.flatMap(elements, findFields);
  11183. callback = (ref = (ref1 = u.extractCallback(args)) != null ? ref1 : observeCallbackFromElement(elements[0])) != null ? ref : up.fail('up.observe: No change callback given');
  11184. options = u.extractOptions(args);
  11185. options.delay = (ref2 = (ref3 = options.delay) != null ? ref3 : e.numberAttr(elements[0], 'up-delay')) != null ? ref2 : config.observeDelay;
  11186. observer = new up.FieldObserver(fields, options, callback);
  11187. observer.start();
  11188. return observer.stop;
  11189. };
  11190. observeCallbackFromElement = function(element) {
  11191. var rawCallback;
  11192. if (rawCallback = element.getAttribute('up-observe')) {
  11193. return new Function('value', 'name', rawCallback);
  11194. }
  11195. };
  11196. /***
  11197. [Observes](/up.observe) a field or form and submits the form when a value changes.
  11198. Both the form and the changed field will be assigned a CSS class [`form-up-active`](/form-up-active)
  11199. while the autosubmitted form is processing.
  11200. The unobtrusive variant of this is the [`up-autosubmit`](/form-up-autosubmit) attribute.
  11201. @function up.autosubmit
  11202. @param {string|Element|jQuery} selectorOrElement
  11203. The field or form to observe.
  11204. @param {Object} [options]
  11205. See options for [`up.observe()`](/up.observe)
  11206. @return {Function()}
  11207. A destructor function that removes the observe watch when called.
  11208. @stable
  11209. */
  11210. autosubmit = function(selectorOrElement, options) {
  11211. return observe(selectorOrElement, options, function() {
  11212. return submit(selectorOrElement);
  11213. });
  11214. };
  11215. findValidateTarget = function(field, options) {
  11216. var option, ref;
  11217. option = (ref = options.target) != null ? ref : field.getAttribute('up-validate');
  11218. option || (option = u.findResult(config.validateTargets, function(defaultTarget) {
  11219. var resolvedDefault;
  11220. resolvedDefault = e.resolveSelector(defaultTarget, options.origin);
  11221. if (e.first(resolvedDefault)) {
  11222. return resolvedDefault;
  11223. }
  11224. }));
  11225. if (!option) {
  11226. up.fail('Could not find validation target for %o (tried defaults %o)', field, config.validateTargets);
  11227. }
  11228. return e.resolveSelector(option, options.origin);
  11229. };
  11230. /***
  11231. Performs a server-side validation of a form field.
  11232. `up.validate()` submits the given field's form with an additional `X-Up-Validate`
  11233. HTTP header. Upon seeing this header, the server is expected to validate (but not save)
  11234. the form submission and render a new copy of the form with validation errors.
  11235. The unobtrusive variant of this is the [`input[up-validate]`](/input-up-validate) selector.
  11236. See the documentation for [`input[up-validate]`](/input-up-validate) for more information
  11237. on how server-side validation works in Unpoly.
  11238. \#\#\# Example
  11239. up.validate('input[name=email]', { target: '.email-errors' })
  11240. @function up.validate
  11241. @param {string|Element|jQuery} fieldOrSelector
  11242. @param {string|Element|jQuery} [options.target]
  11243. @return {Promise}
  11244. A promise that is fulfilled when the server-side
  11245. validation is received and the form was updated.
  11246. @stable
  11247. */
  11248. validate = function(fieldOrSelector, options) {
  11249. var field, promise, ref;
  11250. field = e.get(fieldOrSelector);
  11251. options = u.options(options);
  11252. options.origin = field;
  11253. options.target = findValidateTarget(field, options);
  11254. options.failTarget = options.target;
  11255. if (options.reveal == null) {
  11256. options.reveal = (ref = e.booleanOrStringAttr(field, 'up-reveal')) != null ? ref : false;
  11257. }
  11258. options.history = false;
  11259. options.validate = field.getAttribute('name') || ':none';
  11260. options = u.merge(options, up.motion.animateOptions(options, field));
  11261. promise = up.submit(field, options);
  11262. return promise;
  11263. };
  11264. switcherValues = function(field) {
  11265. var checkedButton, form, groupName, meta, value, values;
  11266. value = void 0;
  11267. meta = void 0;
  11268. if (e.matches(field, 'input[type=checkbox]')) {
  11269. if (field.checked) {
  11270. value = field.value;
  11271. meta = ':checked';
  11272. } else {
  11273. meta = ':unchecked';
  11274. }
  11275. } else if (e.matches(field, 'input[type=radio]')) {
  11276. form = closestContainer(field);
  11277. groupName = field.getAttribute('name');
  11278. checkedButton = form.querySelector("input[type=radio]" + (e.attributeSelector('name', groupName)) + ":checked");
  11279. if (checkedButton) {
  11280. meta = ':checked';
  11281. value = checkedButton.value;
  11282. } else {
  11283. meta = ':unchecked';
  11284. }
  11285. } else {
  11286. value = field.value;
  11287. }
  11288. values = [];
  11289. if (u.isPresent(value)) {
  11290. values.push(value);
  11291. values.push(':present');
  11292. } else {
  11293. values.push(':blank');
  11294. }
  11295. if (u.isPresent(meta)) {
  11296. values.push(meta);
  11297. }
  11298. return values;
  11299. };
  11300. /***
  11301. Shows or hides a target selector depending on the value.
  11302. See [`input[up-switch]`](/input-up-switch) for more documentation and examples.
  11303. This function does not currently have a very useful API outside
  11304. of our use for `up-switch`'s UJS behavior, that's why it's currently
  11305. still marked `@internal`.
  11306. @function up.form.switchTargets
  11307. @param {Element} switcher
  11308. @param {string} [options.target]
  11309. The target selectors to switch.
  11310. Defaults to an `[up-switch]` attribute on the given field.
  11311. @internal
  11312. */
  11313. switchTargets = function(switcher, options) {
  11314. var fieldValues, form, ref, targetSelector;
  11315. if (options == null) {
  11316. options = {};
  11317. }
  11318. targetSelector = (ref = options.target) != null ? ref : switcher.getAttribute('up-switch');
  11319. form = closestContainer(switcher);
  11320. u.isPresent(targetSelector) || up.fail("No switch target given for %o", switcher);
  11321. fieldValues = switcherValues(switcher);
  11322. return u.each(e.all(form, targetSelector), function(target) {
  11323. return switchTarget(target, fieldValues);
  11324. });
  11325. };
  11326. /***
  11327. @internal
  11328. */
  11329. switchTarget = function(target, fieldValues) {
  11330. var hideValues, show, showValues;
  11331. fieldValues || (fieldValues = switcherValues(findSwitcherForTarget(target)));
  11332. if (hideValues = target.getAttribute('up-hide-for')) {
  11333. hideValues = u.splitValues(hideValues);
  11334. show = u.intersect(fieldValues, hideValues).length === 0;
  11335. } else {
  11336. if (showValues = target.getAttribute('up-show-for')) {
  11337. showValues = u.splitValues(showValues);
  11338. } else {
  11339. showValues = [':present', ':checked'];
  11340. }
  11341. show = u.intersect(fieldValues, showValues).length > 0;
  11342. }
  11343. e.toggle(target, show);
  11344. return target.classList.add('up-switched');
  11345. };
  11346. /***
  11347. @internal
  11348. */
  11349. findSwitcherForTarget = function(target) {
  11350. var form, switcher, switchers;
  11351. form = closestContainer(target);
  11352. switchers = e.all(form, '[up-switch]');
  11353. switcher = u.find(switchers, function(switcher) {
  11354. var targetSelector;
  11355. targetSelector = switcher.getAttribute('up-switch');
  11356. return e.matches(target, targetSelector);
  11357. });
  11358. return switcher || u.fail('Could not find [up-switch] field for %o', target);
  11359. };
  11360. closestContainer = function(element) {
  11361. return e.closest(element, 'form, body');
  11362. };
  11363. /***
  11364. Forms with an `up-target` attribute are [submitted via AJAX](/up.submit)
  11365. instead of triggering a full page reload.
  11366. <form method="post" action="/users" up-target=".main">
  11367. ...
  11368. </form>
  11369. The server response is searched for the selector given in `up-target`.
  11370. The selector content is then [replaced](/up.replace) in the current page.
  11371. The programmatic variant of this is the [`up.submit()`](/up.submit) function.
  11372. \#\#\# Failed submission
  11373. When the server was unable to save the form due to invalid params,
  11374. it will usually re-render an updated copy of the form with
  11375. validation messages.
  11376. For Unpoly to be able to detect a failed form submission,
  11377. the form must be re-rendered with a non-200 HTTP status code.
  11378. We recommend to use either 400 (bad request) or
  11379. 422 (unprocessable entity).
  11380. In Ruby on Rails, you can pass a
  11381. [`:status` option to `render`](http://guides.rubyonrails.org/layouts_and_rendering.html#the-status-option)
  11382. for this:
  11383. class UsersController < ApplicationController
  11384. def create
  11385. user_params = params[:user].permit(:email, :password)
  11386. @user = User.new(user_params)
  11387. if @user.save?
  11388. sign_in @user
  11389. else
  11390. render 'form', status: :bad_request
  11391. end
  11392. end
  11393. end
  11394. Note that you can also use
  11395. [`input[up-validate]`](/input-up-validate) to perform server-side
  11396. validations while the user is completing fields.
  11397. \#\#\# Redirects
  11398. Unpoly requires an additional response header to detect redirects,
  11399. which are otherwise undetectable for an AJAX client.
  11400. After the form's action performs a redirect, the next response should echo
  11401. the new request's URL as a response header `X-Up-Location`.
  11402. If you are using Unpoly via the `unpoly-rails` gem, these headers
  11403. are set automatically for every request.
  11404. \#\#\# Giving feedback while the form is processing
  11405. The `<form>` element will be assigned a CSS class [`up-active`](/form.up-active) while
  11406. the submission is loading.
  11407. You can also [implement a spinner](/up.proxy/#spinners)
  11408. by [listening](/up.on) to the [`up:proxy:slow`](/up:proxy:slow)
  11409. and [`up:proxy:recover`](/up:proxy:recover) events.
  11410. @selector form[up-target]
  11411. @param {string} up-target
  11412. The CSS selector to [replace](/up.replace) if the form submission is successful (200 status code).
  11413. Inside the CSS selector you may refer to this form as `&` ([like in Sass](https://sass-lang.com/documentation/file.SASS_REFERENCE.html#parent-selector)).
  11414. @param {string} [up-fail-target]
  11415. The CSS selector to [replace](/up.replace) if the form submission is not successful (non-200 status code).
  11416. Inside the CSS selector you may refer to this form as `&` ([like in Sass](https://sass-lang.com/documentation/file.SASS_REFERENCE.html#parent-selector)).
  11417. If omitted, Unpoly will replace the `<form>` tag itself, assuming that the server has echoed the form with validation errors.
  11418. @param [up-fallback]
  11419. The selector to replace if the server responds with an error.
  11420. @param {string} [up-transition]
  11421. The animation to use when the form is replaced after a successful submission.
  11422. @param {string} [up-fail-transition]
  11423. The animation to use when the form is replaced after a failed submission.
  11424. @param [up-history]
  11425. Whether to push a browser history entry after a successful form submission.
  11426. By default the form's target URL is used. If the form redirects to another URL,
  11427. the redirect target will be used.
  11428. Set this to `'false'` to prevent the URL bar from being updated.
  11429. Set this to a URL string to update the history with the given URL.
  11430. @param {string} [up-method]
  11431. The HTTP method to be used to submit the form (`get`, `post`, `put`, `delete`, `patch`).
  11432. Alternately you can use an attribute `data-method`
  11433. ([Rails UJS](https://github.com/rails/jquery-ujs/wiki/Unobtrusive-scripting-support-for-jQuery))
  11434. or `method` (vanilla HTML) for the same purpose.
  11435. @param {string} [up-layer='auto']
  11436. The name of the layer that ought to be updated. Valid values are
  11437. `'auto'`, `'page'`, `'modal'` and `'popup'`.
  11438. If set to `'auto'` (default), Unpoly will try to find a match in the form's layer.
  11439. If no match was found in that layer,
  11440. Unpoly will search in other layers, starting from the topmost layer.
  11441. @param {string} [up-fail-layer='auto']
  11442. The name of the layer that ought to be updated if the server sends a
  11443. non-200 status code.
  11444. @param {string} [up-reveal='true']
  11445. Whether to reveal the target element after it was replaced.
  11446. You can also pass a CSS selector for the element to reveal.
  11447. Inside the CSS selector you may refer to the form as `&` ([like in Sass](https://sass-lang.com/documentation/file.SASS_REFERENCE.html#parent-selector)).
  11448. @param {string} [up-fail-reveal='true']
  11449. Whether to reveal the target element when the server responds with an error.
  11450. You can also pass a CSS selector for the element to reveal. You may use this, for example,
  11451. to reveal the first validation error message:
  11452. <form up-target=".content" up-fail-reveal=".error">
  11453. ...
  11454. </form>
  11455. Inside the CSS selector you may refer to the form as `&` ([like in Sass](https://sass-lang.com/documentation/file.SASS_REFERENCE.html#parent-selector)).
  11456. @param {string} [up-restore-scroll='false']
  11457. Whether to restore previously known scroll position of all viewports
  11458. within the target selector.
  11459. @param {string} [up-cache]
  11460. Whether to force the use of a cached response (`true`)
  11461. or never use the cache (`false`)
  11462. or make an educated guess (`undefined`).
  11463. By default only responses to `GET` requests are cached for a few minutes.
  11464. @stable
  11465. */
  11466. up.on('submit', 'form[up-target]', function(event, form) {
  11467. up.event.consumeAction(event);
  11468. return u.muteRejection(submit(form));
  11469. });
  11470. /***
  11471. When a form field with this attribute is changed, the form is validated on the server
  11472. and is updated with validation messages.
  11473. To validate the form, Unpoly will submit the form with an additional `X-Up-Validate` HTTP header.
  11474. When seeing this header, the server is expected to validate (but not save)
  11475. the form submission and render a new copy of the form with validation errors.
  11476. The programmatic variant of this is the [`up.validate()`](/up.validate) function.
  11477. \#\#\# Example
  11478. Let's look at a standard registration form that asks for an e-mail and password:
  11479. <form action="/users">
  11480. <label>
  11481. E-mail: <input type="text" name="email" />
  11482. </label>
  11483. <label>
  11484. Password: <input type="password" name="password" />
  11485. </label>
  11486. <button type="submit">Register</button>
  11487. </form>
  11488. When the user changes the `email` field, we want to validate that
  11489. the e-mail address is valid and still available. Also we want to
  11490. change the `password` field for the minimum required password length.
  11491. We can do this by giving both fields an `up-validate` attribute:
  11492. <form action="/users">
  11493. <label>
  11494. E-mail: <input type="text" name="email" up-validate />
  11495. </label>
  11496. <label>
  11497. Password: <input type="password" name="password" up-validate />
  11498. </label>
  11499. <button type="submit">Register</button>
  11500. </form>
  11501. Whenever a field with `up-validate` changes, the form is POSTed to
  11502. `/users` with an additional `X-Up-Validate` HTTP header.
  11503. When seeing this header, the server is expected to validate (but not save)
  11504. the form submission and render a new copy of the form with validation errors.
  11505. In Ruby on Rails the processing action should behave like this:
  11506. class UsersController < ApplicationController
  11507. * This action handles POST /users
  11508. def create
  11509. user_params = params[:user].permit(:email, :password)
  11510. @user = User.new(user_params)
  11511. if request.headers['X-Up-Validate']
  11512. @user.valid? # run validations, but don't save to the database
  11513. render 'form' # render form with error messages
  11514. elsif @user.save?
  11515. sign_in @user
  11516. else
  11517. render 'form', status: :bad_request
  11518. end
  11519. end
  11520. end
  11521. Note that if you're using the `unpoly-rails` gem you can simply say `up.validate?`
  11522. instead of manually checking for `request.headers['X-Up-Validate']`.
  11523. The server now renders an updated copy of the form with eventual validation errors:
  11524. <form action="/users">
  11525. <label class="has-error">
  11526. E-mail: <input type="text" name="email" value="foo@bar.com" />
  11527. Has already been taken!
  11528. </label>
  11529. <button type="submit">Register</button>
  11530. </form>
  11531. The `<label>` around the e-mail field is now updated to have the `has-error`
  11532. class and display the validation message.
  11533. \#\#\# How validation results are displayed
  11534. Although the server will usually respond to a validation with a complete,
  11535. fresh copy of the form, Unpoly will by default not update the entire form.
  11536. This is done in order to preserve volatile state such as the scroll position
  11537. of `<textarea>` elements.
  11538. By default Unpoly looks for a `<fieldset>`, `<label>` or `<form>`
  11539. around the validating input field, or any element with an
  11540. `up-fieldset` attribute.
  11541. With the Bootstrap bindings, Unpoly will also look
  11542. for a container with the `form-group` class.
  11543. You can change this default behavior by setting [`up.form.config.validateTargets`](/up.form.config#config.validateTargets):
  11544. // Always update the entire form containing the current field ("&")
  11545. up.form.config.validateTargets = ['form &']
  11546. You can also individually override what to update by setting the `up-validate`
  11547. attribute to a CSS selector:
  11548. <input type="text" name="email" up-validate=".email-errors">
  11549. <span class="email-errors"></span>
  11550. \#\#\# Updating dependent fields
  11551. The `[up-validate]` behavior is also a great way to partially update a form
  11552. when one fields depends on the value of another field.
  11553. Let's say you have a form with one `<select>` to pick a department (sales, engineering, ...)
  11554. and another `<select>` to pick an employeee from the selected department:
  11555. <form action="/contracts">
  11556. <select name="department">...</select> <!-- options for all departments -->
  11557. <select name="employeed">...</select> <!-- options for employees of selected department -->
  11558. </form>
  11559. The list of employees needs to be updated as the appartment changes:
  11560. <form action="/contracts">
  11561. <select name="department" up-validate="[name=employee]">...</select>
  11562. <select name="employee">...</select>
  11563. </form>
  11564. In order to update the `department` field in addition to the `employee` field, you could say
  11565. `up-validate="&, [name=employee]"`, or simply `up-validate="form"` to update the entire form.
  11566. @selector input[up-validate]
  11567. @param {string} up-validate
  11568. The CSS selector to update with the server response.
  11569. This defaults to a fieldset or form group around the validating field.
  11570. @stable
  11571. */
  11572. /***
  11573. Performs [server-side validation](/input-up-validate) when any fieldset within this form changes.
  11574. You can configure what Unpoly considers a fieldset by adding CSS selectors to the
  11575. [`up.form.config.validateTargets`](/up.form.config#config.validateTargets) array.
  11576. @selector form[up-validate]
  11577. @stable
  11578. */
  11579. up.on('change', '[up-validate]', function(event) {
  11580. var field;
  11581. field = findFields(event.target)[0];
  11582. return u.muteRejection(validate(field));
  11583. });
  11584. /***
  11585. Show or hide elements when a `<select>` or `<input>` has a given value.
  11586. \#\#\# Example: Select options
  11587. The controlling form field gets an `up-switch` attribute with a selector for the elements to show or hide:
  11588. <select name="advancedness" up-switch=".target">
  11589. <option value="basic">Basic parts</option>
  11590. <option value="advanced">Advanced parts</option>
  11591. <option value="very-advanced">Very advanced parts</option>
  11592. </select>
  11593. The target elements can use [`[up-show-for]`](/up-show-for) and [`[up-hide-for]`](/up-hide-for)
  11594. attributes to indicate for which values they should be shown or hidden:
  11595. <div class="target" up-show-for="basic">
  11596. only shown for advancedness = basic
  11597. </div>
  11598. <div class="target" up-hide-for="basic">
  11599. hidden for advancedness = basic
  11600. </div>
  11601. <div class="target" up-show-for="advanced very-advanced">
  11602. shown for advancedness = advanced or very-advanced
  11603. </div>
  11604. \#\#\# Example: Text field
  11605. The controlling `<input>` gets an `up-switch` attribute with a selector for the elements to show or hide:
  11606. <input type="text" name="user" up-switch=".target">
  11607. <div class="target" up-show-for="alice">
  11608. only shown for user alice
  11609. </div>
  11610. You can also use the pseudo-values `:blank` to match an empty input value,
  11611. or `:present` to match a non-empty input value:
  11612. <input type="text" name="user" up-switch=".target">
  11613. <div class="target" up-show-for=":blank">
  11614. please enter a username
  11615. </div>
  11616. \#\#\# Example: Checkbox
  11617. For checkboxes you can match against the pseudo-values `:checked` or `:unchecked`:
  11618. <input type="checkbox" name="flag" up-switch=".target">
  11619. <div class="target" up-show-for=":checked">
  11620. only shown when checkbox is checked
  11621. </div>
  11622. <div class="target" up-show-for=":cunhecked">
  11623. only shown when checkbox is unchecked
  11624. </div>
  11625. Of course you can also match against the `value` property of the checkbox element:
  11626. <input type="checkbox" name="flag" value="active" up-switch=".target">
  11627. <div class="target" up-show-for="active">
  11628. only shown when checkbox is checked
  11629. </div>
  11630. @selector input[up-switch]
  11631. @param {string} up-switch
  11632. A CSS selector for elements whose visibility depends on this field's value.
  11633. @stable
  11634. */
  11635. /***
  11636. Only shows this element if an input field with [`[up-switch]`](/input-up-switch) has one of the given values.
  11637. See [`input[up-switch]`](/input-up-switch) for more documentation and examples.
  11638. @selector [up-show-for]
  11639. @param {string} [up-show-for]
  11640. A space-separated list of input values for which this element should be shown.
  11641. @stable
  11642. */
  11643. /***
  11644. Hides this element if an input field with [`[up-switch]`](/input-up-switch) has one of the given values.
  11645. See [`input[up-switch]`](/input-up-switch) for more documentation and examples.
  11646. @selector [up-hide-for]
  11647. @param {string} [up-hide-for]
  11648. A space-separated list of input values for which this element should be hidden.
  11649. @stable
  11650. */
  11651. up.compiler('[up-switch]', function(switcher) {
  11652. return switchTargets(switcher);
  11653. });
  11654. up.on('change', '[up-switch]', function(event, switcher) {
  11655. return switchTargets(switcher);
  11656. });
  11657. up.compiler('[up-show-for]:not(.up-switched), [up-hide-for]:not(.up-switched)', function(element) {
  11658. return switchTarget(element);
  11659. });
  11660. /***
  11661. Observes this field and runs a callback when a value changes.
  11662. This is useful for observing text fields while the user is typing.
  11663. If you want to submit the form after a change see [`input[up-autosubmit]`](/input-up-autosubmit).
  11664. The programmatic variant of this is the [`up.observe()`](/up.observe) function.
  11665. \#\#\# Example
  11666. The following would run a global `showSuggestions(value)` function
  11667. whenever the `<input>` changes:
  11668. <input name="query" up-observe="showSuggestions(value)">
  11669. \#\#\# Callback context
  11670. The script given to `[up-observe]` runs with the following context:
  11671. | Name | Type | Description |
  11672. | -------- | --------- | ------------------------------------- |
  11673. | `value` | `string` | The current value of the field |
  11674. | `this` | `Element` | The form field |
  11675. | `$field` | `jQuery` | The form field as a jQuery collection |
  11676. \#\#\# Observing radio buttons
  11677. Multiple radio buttons with the same `[name]` (a radio button group)
  11678. produce a single value for the form.
  11679. To observe radio buttons group, use the `[up-observe]` attribute on an
  11680. element that contains all radio button elements with a given name:
  11681. <div up-observe="formatSelected(value)">
  11682. <input type="radio" name="format" value="html"> HTML format
  11683. <input type="radio" name="format" value="pdf"> PDF format
  11684. <input type="radio" name="format" value="txt"> Text format
  11685. </div>
  11686. @selector input[up-observe]
  11687. @param {string} up-observe
  11688. The code to run when the field's value changes.
  11689. @param {string} up-delay
  11690. The number of miliseconds to wait after a change before the code is run.
  11691. @stable
  11692. */
  11693. /***
  11694. Observes this form and runs a callback when any field changes.
  11695. This is useful for observing text fields while the user is typing.
  11696. If you want to submit the form after a change see [`input[up-autosubmit]`](/input-up-autosubmit).
  11697. The programmatic variant of this is the [`up.observe()`](/up.observe) function.
  11698. \#\#\# Example
  11699. The would call a function `somethingChanged(value)`
  11700. when any `<input>` within the `<form>` changes:
  11701. <form up-observe="somethingChanged(value)">
  11702. <input name="foo">
  11703. <input name="bar">
  11704. </form>
  11705. \#\#\# Callback context
  11706. The script given to `up-observe` runs with the following context:
  11707. | Name | Type | Description |
  11708. | -------- | --------- | ------------------------------------- |
  11709. | `value` | `string` | The current value of the field |
  11710. | `this` | `Element` | The form field |
  11711. | `$field` | `jQuery` | The form field as a jQuery collection |
  11712. @selector form[up-observe]
  11713. @param {string} up-observe
  11714. The code to run when any field's value changes.
  11715. @param {string} up-delay
  11716. The number of miliseconds to wait after a change before the code is run.
  11717. @stable
  11718. */
  11719. up.compiler('[up-observe]', function(formOrField) {
  11720. return observe(formOrField);
  11721. });
  11722. /***
  11723. Submits this field's form when this field changes its values.
  11724. Both the form and the changed field will be assigned a CSS class [`up-active`](/form-up-active)
  11725. while the autosubmitted form is loading.
  11726. The programmatic variant of this is the [`up.autosubmit()`](/up.autosubmit) function.
  11727. \#\#\# Example
  11728. The following would automatically submit the form when the query is changed:
  11729. <form method="GET" action="/search">
  11730. <input type="search" name="query" up-autosubmit>
  11731. <input type="checkbox" name="archive"> Include archive
  11732. </form>
  11733. \#\#\# Auto-submitting radio buttons
  11734. Multiple radio buttons with the same `[name]` (a radio button group)
  11735. produce a single value for the form.
  11736. To auto-submit radio buttons group, use the `[up-submit]` attribute on an
  11737. element that contains all radio button elements with a given name:
  11738. <div up-autosubmit>
  11739. <input type="radio" name="format" value="html"> HTML format
  11740. <input type="radio" name="format" value="pdf"> PDF format
  11741. <input type="radio" name="format" value="txt"> Text format
  11742. </div>
  11743. @selector input[up-autosubmit]
  11744. @param {string} up-delay
  11745. The number of miliseconds to wait after a change before the form is submitted.
  11746. @stable
  11747. */
  11748. /***
  11749. Submits the form when *any* field changes.
  11750. Both the form and the field will be assigned a CSS class [`up-active`](/form-up-active)
  11751. while the autosubmitted form is loading.
  11752. The programmatic variant of this is the [`up.autosubmit()`](/up.autosubmit) function.
  11753. \#\#\# Example
  11754. This will submit the form when either query or checkbox was changed:
  11755. <form method="GET" action="/search" up-autosubmit>
  11756. <input type="search" name="query">
  11757. <input type="checkbox" name="archive"> Include archive
  11758. </form>
  11759. @selector form[up-autosubmit]
  11760. @param {string} up-delay
  11761. The number of miliseconds to wait after a change before the form is submitted.
  11762. @stable
  11763. */
  11764. up.compiler('[up-autosubmit]', function(formOrField) {
  11765. return autosubmit(formOrField);
  11766. });
  11767. up.compiler('[autofocus]', {
  11768. batch: true
  11769. }, function(inputs) {
  11770. return u.last(inputs).focus();
  11771. });
  11772. up.on('up:framework:reset', reset);
  11773. return {
  11774. config: config,
  11775. submit: submit,
  11776. observe: observe,
  11777. validate: validate,
  11778. autosubmit: autosubmit,
  11779. fieldSelector: fieldSelector,
  11780. fields: findFields,
  11781. submissionFields: findSubmissionFields
  11782. };
  11783. })();
  11784. up.submit = up.form.submit;
  11785. up.observe = up.form.observe;
  11786. up.autosubmit = up.form.autosubmit;
  11787. up.validate = up.form.validate;
  11788. }).call(this);
  11789. /***
  11790. Pop-up overlays
  11791. ===============
  11792. Instead of [linking to a page fragment](/up.link), you can choose
  11793. to show a fragment in a popup overlay that rolls down from an anchoring element.
  11794. To open a popup, add an [`up-popup` attribute](/a-up-popup) to a link:
  11795. <a href="/options" up-popup=".menu">Show options</a>
  11796. When this link is clicked, Unpoly will request the path `/options` and extract
  11797. an element matching the selector `.menu` from the response. The matching element
  11798. will then be placed in the popup overlay.
  11799. \#\#\# Closing behavior
  11800. The popup closes when the user clicks anywhere outside the popup area.
  11801. The popup also closes *when a link within the popup changes a fragment behind the popup*.
  11802. This is useful to have the popup interact with the page that
  11803. opened it, e.g. by updating parts of a larger form.
  11804. To disable this behavior, give the opening link an [`up-sticky`](/a-up-popup#up-sticky) attribute.
  11805. \#\#\# Customizing the popup design
  11806. Popups have a minimal default design:
  11807. - Popup contents are displayed in a white box
  11808. - There is a a subtle box shadow around the popup
  11809. - The box will grow to fit the popup contents
  11810. The easiest way to change how the popup looks is to override the
  11811. [default CSS styles](https://github.com/unpoly/unpoly/blob/master/lib/assets/stylesheets/unpoly/popup.sass).
  11812. The HTML of a popup element looks like this:
  11813. <div class="up-popup">
  11814. <div class="up-popup-content">
  11815. Fragment content here
  11816. </div>
  11817. </div>
  11818. The popup element is appended to the [viewport](/up.viewport) of the anchor element.
  11819. @module up.popup
  11820. */
  11821. (function() {
  11822. up.popup = (function() {
  11823. var attachAsap, attachNow, autoclose, chain, closeAsap, closeNow, config, contains, createHiddenFrame, discardHistory, e, isOpen, preloadNow, reset, state, syncPosition, toggleAsap, u, unveilFrame;
  11824. u = up.util;
  11825. e = up.element;
  11826. /***
  11827. Sets default options for future popups.
  11828. @property up.popup.config
  11829. @param {string} [config.position='bottom']
  11830. Defines on which side of the opening element the popup is attached.
  11831. Valid values are `'top'`, `'right'`, `'bottom'` and `'left'`.
  11832. @param {string} [config.align='left']
  11833. Defines the alignment of the popup along its side.
  11834. When the popup's `{ position }` is `'top'` or `'bottom'`, valid `{ align }` values are `'left'`, `center'` and `'right'`.
  11835. When the popup's `{ position }` is `'left'` or `'right'`, valid `{ align }` values are `top'`, `center'` and `bottom'`.
  11836. @param {string} [config.history=false]
  11837. Whether opening a popup will add a browser history entry.
  11838. @param {string} [config.openAnimation='fade-in']
  11839. The animation used to open a popup.
  11840. @param {string} [config.closeAnimation='fade-out']
  11841. The animation used to close a popup.
  11842. @param {string} [config.openDuration]
  11843. The duration of the open animation (in milliseconds).
  11844. @param {string} [config.closeDuration]
  11845. The duration of the close animation (in milliseconds).
  11846. @param {string} [config.openEasing]
  11847. The timing function controlling the acceleration of the opening animation.
  11848. @param {string} [config.closeEasing]
  11849. The timing function controlling the acceleration of the closing animation.
  11850. @param {boolean} [options.sticky=false]
  11851. If set to `true`, the popup remains
  11852. open even it changes the page in the background.
  11853. @stable
  11854. */
  11855. config = new up.Config({
  11856. openAnimation: 'fade-in',
  11857. closeAnimation: 'fade-out',
  11858. openDuration: 150,
  11859. closeDuration: 100,
  11860. openEasing: null,
  11861. closeEasing: null,
  11862. position: 'bottom',
  11863. align: 'left',
  11864. history: false
  11865. });
  11866. /***
  11867. Returns the URL from which the current popup's contents were loaded.
  11868. Returns `undefined` if no popup is open.
  11869. @function up.popup.url
  11870. @return {string}
  11871. the source URL
  11872. @stable
  11873. */
  11874. /***
  11875. Returns the URL of the page or modal behind the popup.
  11876. @function up.popup.coveredUrl
  11877. @return {string}
  11878. @experimental
  11879. */
  11880. state = new up.Config({
  11881. phase: 'closed',
  11882. anchor: null,
  11883. popup: null,
  11884. content: null,
  11885. tether: null,
  11886. position: null,
  11887. align: null,
  11888. sticky: null,
  11889. url: null,
  11890. coveredUrl: null,
  11891. coveredTitle: null
  11892. });
  11893. chain = new up.DivertibleChain();
  11894. reset = function() {
  11895. var ref;
  11896. if ((ref = state.tether) != null) {
  11897. ref.destroy();
  11898. }
  11899. state.reset();
  11900. chain.reset();
  11901. return config.reset();
  11902. };
  11903. discardHistory = function() {
  11904. state.coveredTitle = null;
  11905. return state.coveredUrl = null;
  11906. };
  11907. createHiddenFrame = function(targetSelector) {
  11908. state.tether = new up.Tether(u.only(state, 'anchor', 'position', 'align'));
  11909. state.popup = e.affix(state.tether.root, '.up-popup', {
  11910. 'up-position': state.position,
  11911. 'up-align': state.align
  11912. });
  11913. state.content = e.affix(state.popup, '.up-popup-content');
  11914. up.fragment.createPlaceholder(targetSelector, state.content);
  11915. return e.hide(state.popup);
  11916. };
  11917. unveilFrame = function() {
  11918. return e.show(state.popup);
  11919. };
  11920. /***
  11921. Forces the popup to update its position relative to its anchor element.
  11922. Unpoly automatically keep popups aligned when
  11923. the document is resized or scrolled. Complex layout changes may make
  11924. it necessary to call this function.
  11925. @function up.popup.sync
  11926. @experimental
  11927. */
  11928. syncPosition = function() {
  11929. var ref;
  11930. return (ref = state.tether) != null ? ref.sync() : void 0;
  11931. };
  11932. /***
  11933. Returns whether popup modal is currently open.
  11934. @function up.popup.isOpen
  11935. @return {boolean}
  11936. @stable
  11937. */
  11938. isOpen = function() {
  11939. return state.phase === 'opened' || state.phase === 'opening';
  11940. };
  11941. /***
  11942. Attaches a popup overlay to the given element or selector.
  11943. Emits events [`up:popup:open`](/up:popup:open) and [`up:popup:opened`](/up:popup:opened).
  11944. @function up.popup.attach
  11945. @param {Element|jQuery|string} anchor
  11946. The element to which the popup will be attached.
  11947. @param {string} [options.url]
  11948. The URL from which to fetch the popup contents.
  11949. If omitted, the `href` or `up-href` attribute of the anchor element will be used.
  11950. Will be ignored if `options.html` is given.
  11951. @param {string} [options.target]
  11952. A CSS selector that will be extracted from the response and placed into the popup.
  11953. @param {string} [options.position='bottom']
  11954. Defines on which side of the opening element the popup is attached.
  11955. Valid values are `'top'`, `'right'`, `'bottom'` and `'left'`.
  11956. @param {string} [options.align='left']
  11957. Defines the alignment of the popup along its side.
  11958. When the popup's `{ position }` is `'top'` or `'bottom'`, valid `{ align }` values are `'left'`, `center'` and `'right'`.
  11959. When the popup's `{ position }` is `'left'` or `'right'`, valid `{ align }` values are `top'`, `center'` and `bottom'`.
  11960. @param {string} [options.html]
  11961. A string of HTML from which to extract the popup contents. No network request will be made.
  11962. @param {string} [options.confirm]
  11963. A message that will be displayed in a cancelable confirmation dialog
  11964. before the modal is being opened.
  11965. @param {string} [options.animation]
  11966. The animation to use when opening the popup.
  11967. @param {number} [options.duration]
  11968. The duration of the animation. See [`up.animate()`](/up.animate).
  11969. @param {number} [options.delay]
  11970. The delay before the animation starts. See [`up.animate()`](/up.animate).
  11971. @param {string} [options.easing]
  11972. The timing function that controls the animation's acceleration. [`up.animate()`](/up.animate).
  11973. @param {string} [options.method="GET"]
  11974. Override the request method.
  11975. @param {boolean} [options.sticky=false]
  11976. If set to `true`, the popup remains
  11977. open even if the page changes in the background.
  11978. @param {boolean} [options.history=false]
  11979. @return {Promise}
  11980. A promise that will be fulfilled when the popup has been loaded and
  11981. the opening animation has completed.
  11982. @stable
  11983. */
  11984. attachAsap = function(elementOrSelector, options) {
  11985. return chain.asap(closeNow, (function() {
  11986. return attachNow(elementOrSelector, options);
  11987. }));
  11988. };
  11989. attachNow = function(elementOrSelector, options) {
  11990. var align, anchor, animateOptions, extractOptions, html, position, ref, ref1, ref10, ref2, ref3, ref4, ref5, ref6, ref7, ref8, ref9, target, url;
  11991. anchor = e.get(elementOrSelector);
  11992. if (options == null) {
  11993. options = {};
  11994. }
  11995. url = (ref = (ref1 = u.pluckKey(options, 'url')) != null ? ref1 : anchor.getAttribute('up-href')) != null ? ref : anchor.getAttribute('href');
  11996. html = u.pluckKey(options, 'html');
  11997. url || html || up.fail('up.popup.attach() requires either an { url } or { html } option');
  11998. target = (ref2 = u.pluckKey(options, 'target')) != null ? ref2 : anchor.getAttribute('up-popup') || up.fail('No target selector given for [up-popup]');
  11999. position = (ref3 = (ref4 = options.position) != null ? ref4 : anchor.getAttribute('up-position')) != null ? ref3 : config.position;
  12000. align = (ref5 = (ref6 = options.align) != null ? ref6 : anchor.getAttribute('up-align')) != null ? ref5 : config.align;
  12001. if (options.animation == null) {
  12002. options.animation = (ref7 = anchor.getAttribute('up-animation')) != null ? ref7 : config.openAnimation;
  12003. }
  12004. if (options.sticky == null) {
  12005. options.sticky = (ref8 = e.booleanAttr(anchor, 'up-sticky')) != null ? ref8 : config.sticky;
  12006. }
  12007. options.history = up.browser.canPushState() ? (ref9 = (ref10 = options.history) != null ? ref10 : e.booleanOrStringAttr(anchor, 'up-history')) != null ? ref9 : config.history : false;
  12008. if (options.confirm == null) {
  12009. options.confirm = anchor.getAttribute('up-confirm');
  12010. }
  12011. options.method = up.link.followMethod(anchor, options);
  12012. options.layer = 'popup';
  12013. if (options.failTarget == null) {
  12014. options.failTarget = anchor.getAttribute('up-fail-target');
  12015. }
  12016. if (options.failLayer == null) {
  12017. options.failLayer = anchor.getAttribute('up-fail-layer');
  12018. }
  12019. options.provideTarget = function() {
  12020. return createHiddenFrame(target);
  12021. };
  12022. animateOptions = up.motion.animateOptions(options, anchor, {
  12023. duration: config.openDuration,
  12024. easing: config.openEasing
  12025. });
  12026. extractOptions = u.merge(options, {
  12027. animation: false
  12028. });
  12029. if (options.preload && url) {
  12030. return up.replace(target, url, options);
  12031. }
  12032. return up.browser.whenConfirmed(options).then(function() {
  12033. return up.event.whenEmitted('up:popup:open', {
  12034. url: url,
  12035. anchor: anchor,
  12036. log: 'Opening popup'
  12037. }).then(function() {
  12038. var promise;
  12039. state.phase = 'opening';
  12040. state.anchor = anchor;
  12041. state.position = position;
  12042. state.align = align;
  12043. if (options.history) {
  12044. state.coveredUrl = up.browser.url();
  12045. state.coveredTitle = document.title;
  12046. }
  12047. state.sticky = options.sticky;
  12048. if (html) {
  12049. promise = up.extract(target, html, extractOptions);
  12050. } else {
  12051. promise = up.replace(target, url, extractOptions);
  12052. }
  12053. promise = promise.then(function() {
  12054. unveilFrame();
  12055. syncPosition();
  12056. return up.animate(state.popup, options.animation, animateOptions);
  12057. });
  12058. promise = promise.then(function() {
  12059. state.phase = 'opened';
  12060. return up.emit(state.popup, 'up:popup:opened', {
  12061. anchor: state.anchor,
  12062. log: 'Popup opened'
  12063. });
  12064. });
  12065. return promise;
  12066. });
  12067. });
  12068. };
  12069. /***
  12070. This event is [emitted](/up.emit) when a popup is starting to open.
  12071. @event up:popup:open
  12072. @param {Element} event.anchor
  12073. The element to which the popup will be attached.
  12074. @param event.preventDefault()
  12075. Event listeners may call this method to prevent the popup from opening.
  12076. @stable
  12077. */
  12078. /***
  12079. This event is [emitted](/up.emit) when a popup has finished opening.
  12080. @event up:popup:opened
  12081. @param {Element} event.anchor
  12082. The element to which the popup was attached.
  12083. @stable
  12084. */
  12085. /***
  12086. Closes a currently opened popup overlay.
  12087. Does nothing if no popup is currently open.
  12088. Emits events [`up:popup:close`](/up:popup:close) and [`up:popup:closed`](/up:popup:closed).
  12089. @function up.popup.close
  12090. @param {Object} options
  12091. See options for [`up.animate()`](/up.animate).
  12092. @return {Promise}
  12093. A promise that will be fulfilled once the modal's close
  12094. animation has finished.
  12095. @stable
  12096. */
  12097. closeAsap = function(options) {
  12098. return chain.asap(function() {
  12099. return closeNow(options);
  12100. });
  12101. };
  12102. closeNow = function(options) {
  12103. var animateOptions;
  12104. if (!isOpen()) {
  12105. return Promise.resolve();
  12106. }
  12107. options = u.options(options, {
  12108. animation: config.closeAnimation,
  12109. history: state.coveredUrl,
  12110. title: state.coveredTitle
  12111. });
  12112. animateOptions = up.motion.animateOptions(options, {
  12113. duration: config.closeDuration,
  12114. easing: config.closeEasing
  12115. });
  12116. u.assign(options, animateOptions);
  12117. return up.event.whenEmitted('up:popup:close', {
  12118. anchor: state.anchor,
  12119. log: 'Closing popup'
  12120. }).then(function() {
  12121. state.phase = 'closing';
  12122. state.url = null;
  12123. state.coveredUrl = null;
  12124. state.coveredTitle = null;
  12125. return up.destroy(state.popup, options).then(function() {
  12126. state.phase = 'closed';
  12127. state.tether.destroy();
  12128. state.tether = null;
  12129. state.popup = null;
  12130. state.content = null;
  12131. state.anchor = null;
  12132. state.sticky = null;
  12133. return up.emit('up:popup:closed', {
  12134. anchor: state.anchor,
  12135. log: 'Popup closed'
  12136. });
  12137. });
  12138. });
  12139. };
  12140. preloadNow = function(link, options) {
  12141. options = u.options(options);
  12142. options.preload = true;
  12143. return attachNow(link, options);
  12144. };
  12145. toggleAsap = function(link, options) {
  12146. if (link.classList.contains('up-current')) {
  12147. return closeAsap();
  12148. } else {
  12149. return attachAsap(link, options);
  12150. }
  12151. };
  12152. /***
  12153. This event is [emitted](/up.emit) when a popup dialog
  12154. is starting to [close](/up.popup.close).
  12155. @event up:popup:close
  12156. @param {Element} event.anchor
  12157. The element to which the popup is attached.
  12158. @param event.preventDefault()
  12159. Event listeners may call this method to prevent the popup from closing.
  12160. @stable
  12161. */
  12162. /***
  12163. This event is [emitted](/up.emit) when a popup dialog
  12164. is done [closing](/up.popup.close).
  12165. @event up:popup:closed
  12166. @param {Element} event.anchor
  12167. The element to which the popup was attached.
  12168. @stable
  12169. */
  12170. autoclose = function() {
  12171. if (!state.sticky) {
  12172. discardHistory();
  12173. return closeAsap();
  12174. }
  12175. };
  12176. /***
  12177. Returns whether the given element or selector is contained
  12178. within the current popup.
  12179. @methods up.popup.contains
  12180. @param {string} elementOrSelector
  12181. The element to test
  12182. @return {boolean}
  12183. @stable
  12184. */
  12185. contains = function(elementOrSelector) {
  12186. var element;
  12187. element = e.get(elementOrSelector);
  12188. return !!e.closest(element, '.up-popup');
  12189. };
  12190. /***
  12191. Opens this link's destination of in a popup overlay:
  12192. <a href="/decks" up-popup=".deck_list">Switch deck</a>
  12193. If the `up-sticky` attribute is set, the dialog does not auto-close
  12194. if a page fragment behind the popup overlay updates:
  12195. <a href="/decks" up-popup=".deck_list">Switch deck</a>
  12196. <a href="/settings" up-popup=".options" up-sticky>Settings</a>
  12197. @selector a[up-popup]
  12198. @param {string} up-popup
  12199. The CSS selector that will be extracted from the response and
  12200. displayed in a popup overlay.
  12201. @param {string} [up-position]
  12202. Defines on which side of the opening element the popup is attached.
  12203. Valid values are `'top'`, `'right'`, `'bottom'` and `'left'`.
  12204. @param {string} [up-align]
  12205. Defines the alignment of the popup along its side.
  12206. When the popup's `{ position }` is `'top'` or `'bottom'`, valid `{ align }` values are `'left'`, `center'` and `'right'`.
  12207. When the popup's `{ position }` is `'left'` or `'right'`, valid `{ align }` values are `top'`, `center'` and `bottom'`.
  12208. @param {string} [up-confirm]
  12209. A message that will be displayed in a cancelable confirmation dialog
  12210. before the popup is opened.
  12211. @param {string} [up-method='GET']
  12212. Override the request method.
  12213. @param [up-sticky]
  12214. If set to `true`, the popup remains
  12215. open even if the page changes in the background.
  12216. @param {string} [up-history='false']
  12217. Whether to push an entry to the browser history for the popup's source URL.
  12218. Set this to `'false'` to prevent the URL bar from being updated.
  12219. Set this to a URL string to update the history with the given URL.
  12220. @stable
  12221. */
  12222. up.link.addFollowVariant('[up-popup]', {
  12223. follow: function(link, options) {
  12224. return toggleAsap(link, options);
  12225. },
  12226. preload: function(link, options) {
  12227. return preloadNow(link, options);
  12228. }
  12229. });
  12230. up.on('click up:action:consumed', function(event) {
  12231. var target;
  12232. target = event.target;
  12233. if (!e.closest(target, '.up-popup, [up-popup]')) {
  12234. return u.muteRejection(closeAsap());
  12235. }
  12236. });
  12237. up.on('up:fragment:inserted', function(event, fragment) {
  12238. var newSource;
  12239. if (contains(fragment)) {
  12240. if (newSource = fragment.getAttribute('up-source')) {
  12241. return state.url = newSource;
  12242. }
  12243. } else if (event.origin && contains(event.origin)) {
  12244. return u.muteRejection(autoclose());
  12245. }
  12246. });
  12247. up.event.onEscape(function() {
  12248. return u.muteRejection(closeAsap());
  12249. });
  12250. /***
  12251. When this element is clicked, a currently open [popup](/up.popup) is closed.
  12252. Does nothing if no popup is currently open.
  12253. \#\#\# Example
  12254. Clickin on this `<span>` will close a currently open popup:
  12255. <span class='up-close'>Close this popup</span>
  12256. When a popup changes the current URL, you might need to deal with content being displayed
  12257. as either a popup or a full page.
  12258. To make a link that closes the current popup, but follows to
  12259. a fallback destination if no popup is open:
  12260. <a href="/fallback" up-close>Okay</a>
  12261. @selector .up-popup [up-close]
  12262. @stable
  12263. */
  12264. up.on('click', '.up-popup [up-close]', function(event) {
  12265. u.muteRejection(closeAsap());
  12266. return up.event.consumeAction(event);
  12267. });
  12268. up.on('up:history:restore', function() {
  12269. return u.muteRejection(closeAsap());
  12270. });
  12271. up.on('up:framework:reset', reset);
  12272. return {
  12273. attach: attachAsap,
  12274. close: closeAsap,
  12275. url: function() {
  12276. return state.url;
  12277. },
  12278. coveredUrl: function() {
  12279. return state.coveredUrl;
  12280. },
  12281. config: config,
  12282. contains: contains,
  12283. isOpen: isOpen,
  12284. sync: syncPosition
  12285. };
  12286. })();
  12287. }).call(this);
  12288. /***
  12289. Modal dialogs
  12290. =============
  12291. Instead of [linking to a page fragment](/up.link), you can choose to show a fragment
  12292. in a modal dialog. The existing page will remain open in the background.
  12293. To open a modal, add an [`[up-modal]`](/a-up-modal) attribute to a link:
  12294. <a href="/blogs" up-modal=".blog-list">Switch blog</a>
  12295. When this link is clicked, Unpoly will request the path `/blogs` and extract
  12296. an element matching the selector `.blog-list` from the response. The matching element
  12297. will then be placed in a modal dialog.
  12298. \#\#\# Closing behavior
  12299. By default the dialog automatically closes
  12300. *when a link inside a modal changes a fragment behind the modal*.
  12301. This is useful to have the dialog interact with the page that
  12302. opened it, e.g. by updating parts of a larger form.
  12303. To disable this behavior, give the opening link an [`up-sticky`](/a-up-modal#up-sticky) attribute:
  12304. \#\#\# Customizing the dialog design
  12305. Dialogs have a minimal default design:
  12306. - Contents are displayed in a white box with a subtle box shadow
  12307. - The box will grow to fit the dialog contents, but never grow larger than the screen
  12308. - The box is placed over a semi-transparent backdrop to dim the rest of the page
  12309. - There is a button to close the dialog in the top-right corner
  12310. The easiest way to change how the dialog looks is to override the
  12311. [default CSS styles](https://github.com/unpoly/unpoly/blob/master/lib/assets/stylesheets/unpoly/modal.sass).
  12312. By default the dialog uses the following DOM structure:
  12313. <div class="up-modal">
  12314. <div class="up-modal-backdrop">
  12315. <div class="up-modal-viewport">
  12316. <div class="up-modal-dialog">
  12317. <div class="up-modal-content">
  12318. <!-- the matching element will be placed here -->
  12319. </div>
  12320. <div class="up-modal-close" up-close>X</div>
  12321. </div>
  12322. </div>
  12323. </div>
  12324. You can change this structure by setting [`up.modal.config.template`](/up.modal.config#config.template) to a new template string
  12325. or function.
  12326. @module up.modal
  12327. */
  12328. (function() {
  12329. up.modal = (function() {
  12330. var animate, autoclose, bodyShifter, chain, closeAsap, closeNow, config, contains, createHiddenFrame, discardHistory, e, extractAsap, flavor, flavorDefault, flavorOverrides, flavors, followAsap, isOpen, markAsAnimating, openAsap, openNow, part, preloadNow, reset, state, templateHtml, u, unveilFrame, validateTarget, visitAsap;
  12331. u = up.util;
  12332. e = up.element;
  12333. /***
  12334. Sets default options for future modals.
  12335. @property up.modal.config
  12336. @param {string} [config.history=true]
  12337. Whether opening a modal will add a browser history entry.
  12338. @param {number} [config.width]
  12339. The width of the dialog as a CSS value like `'400px'` or `'50%'`.
  12340. Defaults to `undefined`, meaning that the dialog will grow to fit its contents
  12341. until it reaches `config.maxWidth`. Leaving this as `undefined` will
  12342. also allow you to control the width using CSS on `.up-modal-dialog´.
  12343. @param {number} [config.maxWidth]
  12344. The width of the dialog as a CSS value like `'400px'` or `50%`.
  12345. You can set this to `undefined` to make the dialog fit its contents.
  12346. Be aware however, that e.g. Bootstrap stretches input elements
  12347. to `width: 100%`, meaning the dialog will also stretch to the full
  12348. width of the screen.
  12349. @param {number} [config.height='auto']
  12350. The height of the dialog in pixels.
  12351. Defaults to `undefined`, meaning that the dialog will grow to fit its contents.
  12352. @param {string|Function(config): string} [config.template]
  12353. A string containing the HTML structure of the modal.
  12354. You can supply an alternative template string, but make sure that it
  12355. defines tag with the classes `up-modal`, `up-modal-dialog` and `up-modal-content`.
  12356. You can also supply a function that returns a HTML string.
  12357. The function will be called with the modal options (merged from these defaults
  12358. and any per-open overrides) whenever a modal opens.
  12359. @param {string} [config.closeLabel='×']
  12360. The label of the button that closes the dialog.
  12361. @param {boolean} [config.closable=true]
  12362. When `true`, the modal will render a close icon and close when the user
  12363. clicks on the backdrop or presses Escape.
  12364. When `false`, you need to either supply an element with `[up-close]` or
  12365. close the modal manually with `up.modal.close()`.
  12366. @param {string} [config.openAnimation='fade-in']
  12367. The animation used to open the viewport around the dialog.
  12368. @param {string} [config.closeAnimation='fade-out']
  12369. The animation used to close the viewport the dialog.
  12370. @param {string} [config.backdropOpenAnimation='fade-in']
  12371. The animation used to open the backdrop that dims the page below the dialog.
  12372. @param {string} [config.backdropCloseAnimation='fade-out']
  12373. The animation used to close the backdrop that dims the page below the dialog.
  12374. @param {number} [config.openDuration]
  12375. The duration of the open animation (in milliseconds).
  12376. @param {number} [config.closeDuration]
  12377. The duration of the close animation (in milliseconds).
  12378. @param {string} [config.openEasing]
  12379. The timing function controlling the acceleration of the opening animation.
  12380. @param {string} [config.closeEasing]
  12381. The timing function controlling the acceleration of the closing animation.
  12382. @param {boolean} [options.sticky=false]
  12383. If set to `true`, the modal remains
  12384. open even it changes the page in the background.
  12385. @param {string} [options.flavor='default']
  12386. The default [flavor](/up.modal.flavors).
  12387. @stable
  12388. */
  12389. config = new up.Config({
  12390. maxWidth: null,
  12391. width: null,
  12392. height: null,
  12393. history: true,
  12394. openAnimation: 'fade-in',
  12395. closeAnimation: 'fade-out',
  12396. openDuration: null,
  12397. closeDuration: null,
  12398. openEasing: null,
  12399. closeEasing: null,
  12400. backdropOpenAnimation: 'fade-in',
  12401. backdropCloseAnimation: 'fade-out',
  12402. closeLabel: '×',
  12403. closable: true,
  12404. sticky: false,
  12405. flavor: 'default',
  12406. position: null,
  12407. template: function(options) {
  12408. return "<div class=\"up-modal\">\n <div class=\"up-modal-backdrop\"></div>\n <div class=\"up-modal-viewport\">\n <div class=\"up-modal-dialog\">\n <div class=\"up-modal-content\"></div>\n <div class=\"up-modal-close\" up-close>" + options.closeLabel + "</div>\n </div>\n </div>\n</div>";
  12409. }
  12410. });
  12411. /***
  12412. Define modal variants with their own default configuration, CSS or HTML template.
  12413. \#\#\# Example
  12414. Unpoly's [`[up-drawer]`](/a-up-drawer) is implemented as a modal flavor:
  12415. up.modal.flavors.drawer = {
  12416. openAnimation: 'move-from-right',
  12417. closeAnimation: 'move-to-right'
  12418. }
  12419. Modals with that flavor will have a container with an `up-flavor` attribute:
  12420. <div class='up-modal' up-flavor='drawer'>
  12421. ...
  12422. </div>
  12423. We can target the `up-flavor` attribute to override the default dialog styles:
  12424. .up-modal[up-flavor='drawer'] {
  12425. .up-modal-dialog {
  12426. margin: 0; // Remove margin so drawer starts at the screen edge
  12427. max-width: 350px; // Set drawer size
  12428. }
  12429. .up-modal-content {
  12430. min-height: 100vh; // Stretch background to full window height
  12431. }
  12432. }
  12433. @property up.modal.flavors
  12434. @param {Object} flavors
  12435. An object where the keys are flavor names (e.g. `'drawer') and
  12436. the values are the respective default configurations.
  12437. @experimental
  12438. */
  12439. flavors = new up.Config({
  12440. "default": {}
  12441. });
  12442. /***
  12443. Returns the source URL for the fragment displayed in the current modal overlay,
  12444. or `undefined` if no modal is currently open.
  12445. @function up.modal.url
  12446. @return {string}
  12447. the source URL
  12448. @stable
  12449. */
  12450. /***
  12451. Returns the URL of the page behind the modal overlay.
  12452. @function up.modal.coveredUrl
  12453. @return {string}
  12454. @experimental
  12455. */
  12456. state = new up.Config({
  12457. phase: 'closed',
  12458. anchorElement: null,
  12459. modalElement: null,
  12460. sticky: null,
  12461. closable: null,
  12462. flavor: null,
  12463. url: null,
  12464. coveredUrl: null,
  12465. coveredTitle: null,
  12466. position: null
  12467. });
  12468. bodyShifter = new up.BodyShifter();
  12469. chain = new up.DivertibleChain();
  12470. reset = function() {
  12471. if (state.modalElement) {
  12472. e.remove(state.modalElement);
  12473. }
  12474. bodyShifter.unshift();
  12475. state.reset();
  12476. chain.reset();
  12477. config.reset();
  12478. return flavors.reset();
  12479. };
  12480. templateHtml = function() {
  12481. var template;
  12482. template = flavorDefault('template');
  12483. return u.evalOption(template, {
  12484. closeLabel: flavorDefault('closeLabel')
  12485. });
  12486. };
  12487. discardHistory = function() {
  12488. state.coveredTitle = null;
  12489. return state.coveredUrl = null;
  12490. };
  12491. part = function(name) {
  12492. var selector;
  12493. selector = ".up-modal-" + name;
  12494. return state.modalElement.querySelector(selector);
  12495. };
  12496. createHiddenFrame = function(target, options) {
  12497. var closeElement, contentElement, dialogStyles, html, modalElement;
  12498. html = templateHtml();
  12499. state.modalElement = modalElement = e.createFromHtml(html);
  12500. modalElement.setAttribute('aria-modal', 'true');
  12501. modalElement.setAttribute('up-flavor', state.flavor);
  12502. if (u.isPresent(state.position)) {
  12503. modalElement.setAttribute('up-position', state.position);
  12504. }
  12505. dialogStyles = u.only(options, 'width', 'maxWidth', 'height');
  12506. e.setStyle(part('dialog'), dialogStyles);
  12507. if (!state.closable) {
  12508. closeElement = part('close');
  12509. e.remove(closeElement);
  12510. }
  12511. contentElement = part('content');
  12512. up.fragment.createPlaceholder(target, contentElement);
  12513. e.hide(modalElement);
  12514. return document.body.appendChild(modalElement);
  12515. };
  12516. unveilFrame = function() {
  12517. return e.show(state.modalElement);
  12518. };
  12519. /***
  12520. Returns whether a modal is currently open.
  12521. This also returns `true` if the modal is in an opening or closing animation.
  12522. @function up.modal.isOpen
  12523. @return {boolean}
  12524. @stable
  12525. */
  12526. isOpen = function() {
  12527. return state.phase === 'opened' || state.phase === 'opening';
  12528. };
  12529. /***
  12530. Opens the given link's destination in a modal overlay:
  12531. var link = document.querySelector('a')
  12532. up.modal.follow(link)
  12533. Any option attributes for [`a[up-modal]`](/a-up-modal) will be honored.
  12534. Emits events [`up:modal:open`](/up:modal:open) and [`up:modal:opened`](/up:modal:opened).
  12535. @function up.modal.follow
  12536. @param {Element|jQuery|string} linkOrSelector
  12537. The link to follow.
  12538. @param {string} [options.target]
  12539. The selector to extract from the response and open in a modal dialog.
  12540. @param {number} [options.width]
  12541. The width of the dialog in pixels.
  12542. By [default](/up.modal.config) the dialog will grow to fit its contents.
  12543. @param {number} [options.height]
  12544. The width of the dialog in pixels.
  12545. By [default](/up.modal.config) the dialog will grow to fit its contents.
  12546. @param {boolean} [options.sticky=false]
  12547. If set to `true`, the modal remains
  12548. open even it changes the page in the background.
  12549. @param {boolean} [config.closable=true]
  12550. When `true`, the modal will render a close icon and close when the user
  12551. clicks on the backdrop or presses Escape.
  12552. When `false`, you need to either supply an element with `[up-close]` or
  12553. close the modal manually with `up.modal.close()`.
  12554. @param {string} [options.confirm]
  12555. A message that will be displayed in a cancelable confirmation dialog
  12556. before the modal is being opened.
  12557. @param {string} [options.method="GET"]
  12558. Override the request method.
  12559. @param {boolean} [options.history=true]
  12560. Whether to add a browser history entry for the modal's source URL.
  12561. @param {string} [options.animation]
  12562. The animation to use when opening the modal.
  12563. @param {number} [options.duration]
  12564. The duration of the animation. See [`up.animate()`](/up.animate).
  12565. @param {number} [options.delay]
  12566. The delay before the animation starts. See [`up.animate()`](/up.animate).
  12567. @param {string} [options.easing]
  12568. The timing function that controls the animation's acceleration. [`up.animate()`](/up.animate).
  12569. @return {Promise}
  12570. A promise that will be fulfilled when the modal has been loaded and
  12571. the opening animation has completed.
  12572. @stable
  12573. */
  12574. followAsap = function(linkOrSelector, options) {
  12575. options = u.options(options);
  12576. options.link = e.get(linkOrSelector);
  12577. return openAsap(options);
  12578. };
  12579. preloadNow = function(link, options) {
  12580. options = u.options(options);
  12581. options.link = link;
  12582. options.preload = true;
  12583. return openNow(options);
  12584. };
  12585. /***
  12586. Opens a modal for the given URL.
  12587. \#\#\# Example
  12588. up.modal.visit('/foo', { target: '.list' })
  12589. This will request `/foo`, extract the `.list` selector from the response
  12590. and open the selected container in a modal dialog.
  12591. Emits events [`up:modal:open`](/up:modal:open) and [`up:modal:opened`](/up:modal:opened).
  12592. @function up.modal.visit
  12593. @param {string} url
  12594. The URL to load.
  12595. @param {string} options.target
  12596. The CSS selector to extract from the response.
  12597. The extracted content will be placed into the dialog window.
  12598. @param {Object} options
  12599. See options for [`up.modal.follow()`](/up.modal.follow).
  12600. @return {Promise}
  12601. A promise that will be fulfilled when the modal has been loaded and the opening
  12602. animation has completed.
  12603. @stable
  12604. */
  12605. visitAsap = function(url, options) {
  12606. options = u.options(options);
  12607. options.url = url;
  12608. return openAsap(options);
  12609. };
  12610. /***
  12611. [Extracts](/up.extract) the given CSS selector from the given HTML string and
  12612. opens the results in a modal.
  12613. \#\#\# Example
  12614. var html = 'before <div class="content">inner</div> after';
  12615. up.modal.extract('.content', html)
  12616. The would open a modal with the following contents:
  12617. <div class="content">inner</div>
  12618. Emits events [`up:modal:open`](/up:modal:open) and [`up:modal:opened`](/up:modal:opened).
  12619. @function up.modal.extract
  12620. @param {string} selector
  12621. The CSS selector to extract from the HTML.
  12622. @param {string} html
  12623. The HTML containing the modal content.
  12624. @param {Object} options
  12625. See options for [`up.modal.follow()`](/up.modal.follow).
  12626. @return {Promise}
  12627. A promise that will be fulfilled when the modal has been opened and the opening
  12628. animation has completed.
  12629. @stable
  12630. */
  12631. extractAsap = function(selector, html, options) {
  12632. options = u.options(options);
  12633. options.html = html;
  12634. if (options.history == null) {
  12635. options.history = false;
  12636. }
  12637. options.target = selector;
  12638. return openAsap(options);
  12639. };
  12640. openAsap = function(options) {
  12641. return chain.asap(closeNow, (function() {
  12642. return openNow(options);
  12643. }));
  12644. };
  12645. openNow = function(options) {
  12646. var animateOptions, html, link, ref, ref1, ref10, ref11, ref12, ref13, ref2, ref3, ref4, ref5, ref6, ref7, ref8, ref9, target, url;
  12647. options = u.options(options);
  12648. link = u.pluckKey(options, 'link') || e.none();
  12649. url = (ref = (ref1 = u.pluckKey(options, 'url')) != null ? ref1 : link.getAttribute('up-href')) != null ? ref : link.getAttribute('href');
  12650. html = u.pluckKey(options, 'html');
  12651. target = (ref2 = u.pluckKey(options, 'target')) != null ? ref2 : link.getAttribute('up-modal');
  12652. validateTarget(target);
  12653. if (options.flavor == null) {
  12654. options.flavor = (ref3 = link.getAttribute('up-flavor')) != null ? ref3 : config.flavor;
  12655. }
  12656. if (options.position == null) {
  12657. options.position = (ref4 = link.getAttribute('up-position')) != null ? ref4 : flavorDefault('position', options.flavor);
  12658. }
  12659. options.position = u.evalOption(options.position, {
  12660. link: link
  12661. });
  12662. if (options.width == null) {
  12663. options.width = (ref5 = link.getAttribute('up-width')) != null ? ref5 : flavorDefault('width', options.flavor);
  12664. }
  12665. if (options.maxWidth == null) {
  12666. options.maxWidth = (ref6 = link.getAttribute('up-max-width')) != null ? ref6 : flavorDefault('maxWidth', options.flavor);
  12667. }
  12668. if (options.height == null) {
  12669. options.height = (ref7 = link.getAttribute('up-height')) != null ? ref7 : flavorDefault('height');
  12670. }
  12671. if (options.animation == null) {
  12672. options.animation = (ref8 = link.getAttribute('up-animation')) != null ? ref8 : flavorDefault('openAnimation', options.flavor);
  12673. }
  12674. options.animation = u.evalOption(options.animation, {
  12675. position: options.position
  12676. });
  12677. if (options.backdropAnimation == null) {
  12678. options.backdropAnimation = (ref9 = link.getAttribute('up-backdrop-animation')) != null ? ref9 : flavorDefault('backdropOpenAnimation', options.flavor);
  12679. }
  12680. options.backdropAnimation = u.evalOption(options.backdropAnimation, {
  12681. position: options.position
  12682. });
  12683. if (options.sticky == null) {
  12684. options.sticky = (ref10 = e.booleanAttr(link, 'up-sticky')) != null ? ref10 : flavorDefault('sticky', options.flavor);
  12685. }
  12686. if (options.closable == null) {
  12687. options.closable = (ref11 = e.booleanAttr(link, 'up-closable')) != null ? ref11 : flavorDefault('closable', options.flavor);
  12688. }
  12689. if (options.confirm == null) {
  12690. options.confirm = link.getAttribute('up-confirm');
  12691. }
  12692. options.method = up.link.followMethod(link, options);
  12693. options.layer = 'modal';
  12694. if (options.failTarget == null) {
  12695. options.failTarget = link.getAttribute('up-fail-target');
  12696. }
  12697. if (options.failLayer == null) {
  12698. options.failLayer = (ref12 = link.getAttribute('up-fail-layer')) != null ? ref12 : 'auto';
  12699. }
  12700. animateOptions = up.motion.animateOptions(options, link, {
  12701. duration: flavorDefault('openDuration', options.flavor),
  12702. easing: flavorDefault('openEasing', options.flavor)
  12703. });
  12704. if (options.history == null) {
  12705. options.history = (ref13 = e.booleanOrStringAttr(link, 'up-history')) != null ? ref13 : flavorDefault('history', options.flavor);
  12706. }
  12707. if (!up.browser.canPushState()) {
  12708. options.history = false;
  12709. }
  12710. options.provideTarget = function() {
  12711. return createHiddenFrame(target, options);
  12712. };
  12713. if (options.preload) {
  12714. return up.replace(target, url, options);
  12715. }
  12716. return up.browser.whenConfirmed(options).then(function() {
  12717. return up.event.whenEmitted('up:modal:open', {
  12718. url: url,
  12719. log: 'Opening modal'
  12720. }).then(function() {
  12721. var extractOptions, promise;
  12722. state.phase = 'opening';
  12723. state.flavor = options.flavor;
  12724. state.sticky = options.sticky;
  12725. state.closable = options.closable;
  12726. state.position = options.position;
  12727. if (options.history) {
  12728. state.coveredUrl = up.browser.url();
  12729. state.coveredTitle = document.title;
  12730. }
  12731. extractOptions = u.merge(options, {
  12732. animation: false
  12733. });
  12734. if (html) {
  12735. promise = up.extract(target, html, extractOptions);
  12736. } else {
  12737. promise = up.replace(target, url, extractOptions);
  12738. }
  12739. promise = promise.then(function() {
  12740. bodyShifter.shift();
  12741. unveilFrame();
  12742. return animate(options.animation, options.backdropAnimation, animateOptions);
  12743. });
  12744. promise = promise.then(function() {
  12745. state.phase = 'opened';
  12746. return up.emit('up:modal:opened', {
  12747. log: 'Modal opened'
  12748. });
  12749. });
  12750. return promise;
  12751. });
  12752. });
  12753. };
  12754. validateTarget = function(target) {
  12755. if (u.isBlank(target)) {
  12756. return up.fail('Cannot open a modal without a target selector');
  12757. } else if (target === 'body') {
  12758. return up.fail('Cannot open the <body> in a modal');
  12759. }
  12760. };
  12761. /***
  12762. This event is [emitted](/up.emit) when a modal dialog is starting to open.
  12763. @event up:modal:open
  12764. @param event.preventDefault()
  12765. Event listeners may call this method to prevent the modal from opening.
  12766. @stable
  12767. */
  12768. /***
  12769. This event is [emitted](/up.emit) when a modal dialog has finished opening.
  12770. @event up:modal:opened
  12771. @stable
  12772. */
  12773. /***
  12774. Closes a currently opened modal overlay.
  12775. Does nothing if no modal is currently open.
  12776. Emits events [`up:modal:close`](/up:modal:close) and [`up:modal:closed`](/up:modal:closed).
  12777. @function up.modal.close
  12778. @param {Object} options
  12779. See options for [`up.animate()`](/up.animate)
  12780. @return {Promise}
  12781. A promise that will be fulfilled once the modal's close
  12782. animation has finished.
  12783. @stable
  12784. */
  12785. closeAsap = function(options) {
  12786. return chain.asap(function() {
  12787. return closeNow(options);
  12788. });
  12789. };
  12790. closeNow = function(options) {
  12791. var animateOptions, backdropCloseAnimation, destroyOptions, ref, ref1, viewportCloseAnimation;
  12792. options = u.options(options);
  12793. if (!isOpen()) {
  12794. return Promise.resolve();
  12795. }
  12796. viewportCloseAnimation = (ref = options.animation) != null ? ref : flavorDefault('closeAnimation');
  12797. viewportCloseAnimation = u.evalOption(viewportCloseAnimation, {
  12798. position: state.position
  12799. });
  12800. backdropCloseAnimation = (ref1 = options.backdropAnimation) != null ? ref1 : flavorDefault('backdropCloseAnimation');
  12801. backdropCloseAnimation = u.evalOption(backdropCloseAnimation, {
  12802. position: state.position
  12803. });
  12804. animateOptions = up.motion.animateOptions(options, {
  12805. duration: flavorDefault('closeDuration'),
  12806. easing: flavorDefault('closeEasing')
  12807. });
  12808. destroyOptions = u.options(u.except(options, 'animation', 'duration', 'easing', 'delay'), {
  12809. history: state.coveredUrl,
  12810. title: state.coveredTitle
  12811. });
  12812. return up.event.whenEmitted(state.modalElement, 'up:modal:close', {
  12813. log: 'Closing modal'
  12814. }).then(function() {
  12815. var promise;
  12816. state.phase = 'closing';
  12817. state.url = null;
  12818. state.coveredUrl = null;
  12819. state.coveredTitle = null;
  12820. promise = animate(viewportCloseAnimation, backdropCloseAnimation, animateOptions);
  12821. promise = promise.then(function() {
  12822. return up.destroy(state.modalElement, destroyOptions);
  12823. });
  12824. promise = promise.then(function() {
  12825. bodyShifter.unshift();
  12826. state.phase = 'closed';
  12827. state.modalElement = null;
  12828. state.flavor = null;
  12829. state.sticky = null;
  12830. state.closable = null;
  12831. state.position = null;
  12832. return up.emit('up:modal:closed', {
  12833. log: 'Modal closed'
  12834. });
  12835. });
  12836. return promise;
  12837. });
  12838. };
  12839. markAsAnimating = function(isAnimating) {
  12840. if (isAnimating == null) {
  12841. isAnimating = true;
  12842. }
  12843. return e.toggleClass(state.modalElement, 'up-modal-animating', isAnimating);
  12844. };
  12845. animate = function(viewportAnimation, backdropAnimation, animateOptions) {
  12846. var promise;
  12847. if (up.motion.isNone(viewportAnimation)) {
  12848. return Promise.resolve();
  12849. } else {
  12850. markAsAnimating();
  12851. promise = Promise.all([up.animate(part('viewport'), viewportAnimation, animateOptions), up.animate(part('backdrop'), backdropAnimation, animateOptions)]);
  12852. promise = promise.then(function() {
  12853. return markAsAnimating(false);
  12854. });
  12855. return promise;
  12856. }
  12857. };
  12858. /***
  12859. This event is [emitted](/up.emit) when a modal dialog
  12860. is starting to [close](/up.modal.close).
  12861. @event up:modal:close
  12862. @param event.preventDefault()
  12863. Event listeners may call this method to prevent the modal from closing.
  12864. @stable
  12865. */
  12866. /***
  12867. This event is [emitted](/up.emit) when a modal dialog
  12868. is done [closing](/up.modal.close).
  12869. @event up:modal:closed
  12870. @stable
  12871. */
  12872. autoclose = function() {
  12873. if (!state.sticky) {
  12874. discardHistory();
  12875. return closeAsap();
  12876. }
  12877. };
  12878. /***
  12879. Returns whether the given element or selector is contained
  12880. within the current modal.
  12881. @function up.modal.contains
  12882. @param {string} elementOrSelector
  12883. The element to test
  12884. @return {boolean}
  12885. @stable
  12886. */
  12887. contains = function(elementOrSelector) {
  12888. var element;
  12889. element = e.get(elementOrSelector);
  12890. return !!e.closest(element, '.up-modal');
  12891. };
  12892. flavor = function(name, overrideConfig) {
  12893. if (overrideConfig == null) {
  12894. overrideConfig = {};
  12895. }
  12896. up.legacy.warn('up.modal.flavor() is deprecated. Use the up.modal.flavors property instead.');
  12897. return u.assign(flavorOverrides(name), overrideConfig);
  12898. };
  12899. /***
  12900. Returns a config object for the given flavor.
  12901. Properties in that config should be preferred to the defaults in
  12902. [`/up.modal.config`](/up.modal.config).
  12903. @function flavorOverrides
  12904. @internal
  12905. */
  12906. flavorOverrides = function(flavor) {
  12907. return flavors[flavor] || (flavors[flavor] = {});
  12908. };
  12909. /***
  12910. Returns the config option for the current flavor.
  12911. @function flavorDefault
  12912. @internal
  12913. */
  12914. flavorDefault = function(key, flavorName) {
  12915. var value;
  12916. if (flavorName == null) {
  12917. flavorName = state.flavor;
  12918. }
  12919. if (flavorName) {
  12920. value = flavorOverrides(flavorName)[key];
  12921. }
  12922. if (u.isMissing(value)) {
  12923. value = config[key];
  12924. }
  12925. return value;
  12926. };
  12927. /***
  12928. Clicking this link will load the destination via AJAX and open
  12929. the given selector in a modal dialog.
  12930. \#\#\# Example
  12931. <a href="/blogs" up-modal=".blog-list">Switch blog</a>
  12932. Clicking would request the path `/blog` and select `.blog-list` from
  12933. the HTML response. Unpoly will dim the page
  12934. and place the matching `.blog-list` tag in
  12935. a modal dialog.
  12936. @selector a[up-modal]
  12937. @param {string} up-modal
  12938. The CSS selector that will be extracted from the response and displayed in a modal dialog.
  12939. @param {string} [up-confirm]
  12940. A message that will be displayed in a cancelable confirmation dialog
  12941. before the modal is opened.
  12942. @param {string} [up-method='GET']
  12943. Override the request method.
  12944. @param {string} [up-sticky]
  12945. If set to `"true"`, the modal remains
  12946. open even if the page changes in the background.
  12947. @param {boolean} [up-closable]
  12948. When `true`, the modal will render a close icon and close when the user
  12949. clicks on the backdrop or presses Escape.
  12950. When `false`, you need to either supply an element with `[up-close]` or
  12951. close the modal manually with `up.modal.close()`.
  12952. @param {string} [up-animation]
  12953. The animation to use when opening the viewport containing the dialog.
  12954. @param {string} [up-backdrop-animation]
  12955. The animation to use when opening the backdrop that dims the page below the dialog.
  12956. @param {string} [up-height]
  12957. The width of the dialog in pixels.
  12958. By [default](/up.modal.config) the dialog will grow to fit its contents.
  12959. @param {string} [up-width]
  12960. The width of the dialog in pixels.
  12961. By [default](/up.modal.config) the dialog will grow to fit its contents.
  12962. @param {string} [up-history]
  12963. Whether to push an entry to the browser history for the modal's source URL.
  12964. Set this to `'false'` to prevent the URL bar from being updated.
  12965. Set this to a URL string to update the history with the given URL.
  12966. @stable
  12967. */
  12968. up.link.addFollowVariant('[up-modal]', {
  12969. follow: function(link, options) {
  12970. return followAsap(link, options);
  12971. },
  12972. preload: function(link, options) {
  12973. return preloadNow(link, options);
  12974. }
  12975. });
  12976. up.on('click', '.up-modal', function(event) {
  12977. var target;
  12978. if (!state.closable) {
  12979. return;
  12980. }
  12981. target = event.target;
  12982. if (!(e.closest(target, '.up-modal-dialog') || e.closest(target, '[up-modal]'))) {
  12983. up.event.consumeAction(event);
  12984. return u.muteRejection(closeAsap());
  12985. }
  12986. });
  12987. up.on('up:fragment:inserted', function(event, fragment) {
  12988. var newSource;
  12989. if (contains(fragment)) {
  12990. if (newSource = fragment.getAttribute('up-source')) {
  12991. return state.url = newSource;
  12992. }
  12993. } else if (event.origin && contains(event.origin) && !up.popup.contains(fragment)) {
  12994. return u.muteRejection(autoclose());
  12995. }
  12996. });
  12997. up.event.onEscape(function() {
  12998. if (state.closable) {
  12999. return u.muteRejection(closeAsap());
  13000. }
  13001. });
  13002. /***
  13003. When this element is clicked, closes a currently open dialog.
  13004. Does nothing if no modal is currently open.
  13005. To make a link that closes the current modal, but follows to
  13006. a fallback destination if no modal is open:
  13007. <a href="/fallback" up-close>Okay</a>
  13008. @selector .up-modal [up-close]
  13009. @stable
  13010. */
  13011. up.on('click', '.up-modal [up-close]', function(event) {
  13012. u.muteRejection(closeAsap());
  13013. return up.event.consumeAction(event);
  13014. });
  13015. /***
  13016. Clicking this link will load the destination via AJAX and open
  13017. the given selector in a modal drawer that slides in from the edge of the screen.
  13018. You can configure drawers using the [`up.modal.flavors.drawer`](/up.modal.flavors.drawer) property.
  13019. \#\#\# Example
  13020. <a href="/blogs" up-drawer=".blog-list">Switch blog</a>
  13021. Clicking would request the path `/blog` and select `.blog-list` from
  13022. the HTML response. Unpoly will dim the page
  13023. and place the matching `.blog-list` tag will be placed in
  13024. a modal drawer.
  13025. @selector a[up-drawer]
  13026. @param {string} up-drawer
  13027. The CSS selector to extract from the response and open in the drawer.
  13028. @param {string} [up-position='auto']
  13029. The side from which the drawer slides in.
  13030. Valid values are `'left'`, `'right'` and `'auto'`. If set to `'auto'`, the
  13031. drawer will slide in from left if the opening link is on the left half of the screen.
  13032. Otherwise it will slide in from the right.
  13033. @stable
  13034. */
  13035. up.macro('a[up-drawer], [up-href][up-drawer]', function(link) {
  13036. var target;
  13037. target = link.getAttribute('up-drawer');
  13038. return e.setAttrs(link, {
  13039. 'up-modal': target,
  13040. 'up-flavor': 'drawer'
  13041. });
  13042. });
  13043. /***
  13044. Sets default options for future drawers.
  13045. @property up.modal.flavors.drawer
  13046. @param {Object} config
  13047. Default options for future drawers.
  13048. See [`up.modal.config`](/up.modal.config) for available options.
  13049. @experimental
  13050. */
  13051. flavors.drawer = {
  13052. openAnimation: function(options) {
  13053. switch (options.position) {
  13054. case 'left':
  13055. return 'move-from-left';
  13056. case 'right':
  13057. return 'move-from-right';
  13058. }
  13059. },
  13060. closeAnimation: function(options) {
  13061. switch (options.position) {
  13062. case 'left':
  13063. return 'move-to-left';
  13064. case 'right':
  13065. return 'move-to-right';
  13066. }
  13067. },
  13068. position: function(options) {
  13069. if (u.isPresent(options.link)) {
  13070. return u.horizontalScreenHalf(options.link);
  13071. } else {
  13072. return 'left';
  13073. }
  13074. }
  13075. };
  13076. up.on('up:history:restore', function() {
  13077. return u.muteRejection(closeAsap());
  13078. });
  13079. up.on('up:framework:reset', reset);
  13080. return {
  13081. visit: visitAsap,
  13082. follow: followAsap,
  13083. extract: extractAsap,
  13084. close: closeAsap,
  13085. url: function() {
  13086. return state.url;
  13087. },
  13088. coveredUrl: function() {
  13089. return state.coveredUrl;
  13090. },
  13091. config: config,
  13092. flavors: flavors,
  13093. contains: contains,
  13094. isOpen: isOpen,
  13095. flavor: flavor
  13096. };
  13097. })();
  13098. }).call(this);
  13099. /***
  13100. Tooltips
  13101. ========
  13102. Unpoly comes with a basic tooltip implementation.
  13103. Add an [`up-tooltip`](/up-tooltip) attribute to any HTML tag to show a tooltip whenever
  13104. the user hovers over the element:
  13105. <a href="/decks" up-tooltip="Show all decks">Decks</a>
  13106. \#\#\# Styling
  13107. The default styles
  13108. render a tooltip with white text on a gray background.
  13109. A gray triangle points to the element.
  13110. To change the styling, simply override the [CSS rules](https://github.com/unpoly/unpoly/blob/master/lib/assets/stylesheets/unpoly/tooltip.sass) for the `.up-tooltip` selector and its `:after`
  13111. selector that is used for the triangle.
  13112. The HTML of a tooltip element looks like this:
  13113. <div class="up-tooltip">
  13114. <div class="up-tooltip-content">
  13115. Tooltip text here
  13116. </div>
  13117. </div>
  13118. The tooltip element is appended to the [viewport](/up.viewport) of the anchor element.
  13119. @module up.tooltip
  13120. */
  13121. (function() {
  13122. up.tooltip = (function() {
  13123. var attachAsap, attachNow, chain, closeAsap, closeNow, config, createElement, e, isOpen, reset, state, syncPosition, u;
  13124. u = up.util;
  13125. e = up.element;
  13126. /***
  13127. Configures defaults for future tooltips.
  13128. @property up.tooltip.config
  13129. @param {string} [config.position]
  13130. The default position of tooltips relative to the opening element.
  13131. Valid values are `'top'`, `'right'`, `'bottom'` or `'left'`.
  13132. @param {string} [config.align]
  13133. Defines the alignment of the tooltip along its side.
  13134. When the tooltip's `{ position }` is `'top'` or `'bottom'`, valid `{ align }` values are `'left'`, `center'` and `'right'`.
  13135. When the tooltip's `{ position }` is `'left'` or `'right'`, valid `{ align }` values are `top'`, `center'` and `bottom'`.
  13136. @param {string} [config.openAnimation='fade-in']
  13137. The animation used to open a tooltip.
  13138. @param {string} [config.closeAnimation='fade-out']
  13139. The animation used to close a tooltip.
  13140. @param {number} [config.openDuration]
  13141. The duration of the open animation (in milliseconds).
  13142. @param {number} [config.closeDuration]
  13143. The duration of the close animation (in milliseconds).
  13144. @param {string} [config.openEasing]
  13145. The timing function controlling the acceleration of the opening animation.
  13146. @param {string} [config.closeEasing]
  13147. The timing function controlling the acceleration of the closing animation.
  13148. @stable
  13149. */
  13150. config = new up.Config({
  13151. position: 'top',
  13152. align: 'center',
  13153. openAnimation: 'fade-in',
  13154. closeAnimation: 'fade-out',
  13155. openDuration: 100,
  13156. closeDuration: 50,
  13157. openEasing: null,
  13158. closeEasing: null
  13159. });
  13160. state = new up.Config({
  13161. phase: 'closed',
  13162. anchor: null,
  13163. tooltip: null,
  13164. content: null,
  13165. tether: null,
  13166. position: null,
  13167. align: null
  13168. });
  13169. chain = new up.DivertibleChain();
  13170. reset = function() {
  13171. var ref;
  13172. if ((ref = state.tether) != null) {
  13173. ref.destroy();
  13174. }
  13175. state.reset();
  13176. chain.reset();
  13177. return config.reset();
  13178. };
  13179. createElement = function(options) {
  13180. state.tether = new up.Tether(u.only(state, 'anchor', 'position', 'align'));
  13181. state.tooltip = e.affix(state.tether.root, '.up-tooltip', {
  13182. 'up-position': state.position,
  13183. 'up-align': state.align
  13184. });
  13185. state.content = e.affix(state.tooltip, '.up-tooltip-content');
  13186. if (options.text) {
  13187. return state.content.innerText = options.text;
  13188. } else {
  13189. return state.content.innerHTML = options.html;
  13190. }
  13191. };
  13192. /***
  13193. Forces the tooltip to update its position relative to its anchor element.
  13194. Unpoly will automatically keep tooltips aligned when
  13195. the document is resized or scrolled. Complex layout changes may make
  13196. it necessary to call this function.
  13197. @function up.tooltip.sync
  13198. @experimental
  13199. */
  13200. syncPosition = function() {
  13201. var ref;
  13202. return (ref = state.tether) != null ? ref.sync() : void 0;
  13203. };
  13204. /***
  13205. Opens a tooltip over the given element.
  13206. The unobtrusive variant of this is the [`[up-tooltip]`](/up-tooltip) selector.
  13207. \#\#\# Examples
  13208. In order to attach a tooltip to a `<span class="help">?</span>`:
  13209. up.tooltip.attach('.help', { text: 'Useful info' })
  13210. @function up.tooltip.attach
  13211. @param {Element|jQuery|string} elementOrSelector
  13212. @param {string} [options.text]
  13213. The text to display in the tooltip.
  13214. Any HTML control characters will be escaped.
  13215. If you need to use HTML formatting in the tooltip, use `options.html` instead.
  13216. @param {string} [options.html]
  13217. The HTML to display in the tooltip unescaped.
  13218. Make sure to escape any user-provided text before passing it as this option,
  13219. or use `options.text` (which automatically escapes).
  13220. @param {string} [options.position]
  13221. The tooltip's position relative to the opening element.
  13222. Valid values are `'top'`, `'right'`, `'bottom'` or `'left'`.
  13223. @param {string} [options.align]
  13224. Defines the alignment of the tooltip along its side.
  13225. When the tooltip's `{ position }` is `'top'` or `'bottom'`, valid `{ align }` values are `'left'`, `center'` and `'right'`.
  13226. When the tooltip's `{ position }` is `'left'` or `'right'`, valid `{ align }` values are `top'`, `center'` and `bottom'`.
  13227. @param {string} [options.animation]
  13228. The [animation](/up.motion) to use when opening the tooltip.
  13229. @return {Promise}
  13230. A promise that will be fulfilled when the tooltip's opening animation has finished.
  13231. @stable
  13232. */
  13233. attachAsap = function(elementOrSelector, options) {
  13234. return chain.asap(closeNow, (function() {
  13235. return attachNow(elementOrSelector, options);
  13236. }));
  13237. };
  13238. attachNow = function(elementOrSelector, options) {
  13239. var align, anchor, animateOptions, animation, html, position, ref, ref1, ref2, ref3, ref4, ref5, ref6, ref7, text;
  13240. if (options == null) {
  13241. options = {};
  13242. }
  13243. anchor = e.get(elementOrSelector);
  13244. html = (ref = options.html) != null ? ref : anchor.getAttribute('up-tooltip-html');
  13245. text = (ref1 = options.text) != null ? ref1 : anchor.getAttribute('up-tooltip');
  13246. position = (ref2 = (ref3 = options.position) != null ? ref3 : anchor.getAttribute('up-position')) != null ? ref2 : config.position;
  13247. align = (ref4 = (ref5 = options.align) != null ? ref5 : anchor.getAttribute('up-align')) != null ? ref4 : config.align;
  13248. animation = (ref6 = (ref7 = options.animation) != null ? ref7 : e.booleanOrStringAttr(anchor, 'up-animation')) != null ? ref6 : config.openAnimation;
  13249. animateOptions = up.motion.animateOptions(options, anchor, {
  13250. duration: config.openDuration,
  13251. easing: config.openEasing
  13252. });
  13253. state.phase = 'opening';
  13254. state.anchor = anchor;
  13255. state.position = position;
  13256. state.align = align;
  13257. createElement({
  13258. text: text,
  13259. html: html
  13260. });
  13261. syncPosition();
  13262. return up.animate(state.tooltip, animation, animateOptions).then(function() {
  13263. return state.phase = 'opened';
  13264. });
  13265. };
  13266. /***
  13267. Closes a currently shown tooltip.
  13268. Does nothing if no tooltip is currently shown.
  13269. @function up.tooltip.close
  13270. @param {Object} options
  13271. See options for [`up.animate()`](/up.animate).
  13272. @return {Promise}
  13273. A promise for the end of the closing animation.
  13274. @stable
  13275. */
  13276. closeAsap = function(options) {
  13277. return chain.asap(function() {
  13278. return closeNow(options);
  13279. });
  13280. };
  13281. closeNow = function(options) {
  13282. var animateOptions;
  13283. if (!isOpen()) {
  13284. return Promise.resolve();
  13285. }
  13286. options = u.options(options, {
  13287. animation: config.closeAnimation
  13288. });
  13289. animateOptions = up.motion.animateOptions(options, {
  13290. duration: config.closeDuration,
  13291. easing: config.closeEasing
  13292. });
  13293. u.assign(options, animateOptions);
  13294. state.phase = 'closing';
  13295. return up.destroy(state.tooltip, options).then(function() {
  13296. state.phase = 'closed';
  13297. state.tether.destroy();
  13298. state.tether = null;
  13299. state.tooltip = null;
  13300. state.content = null;
  13301. return state.anchor = null;
  13302. });
  13303. };
  13304. /***
  13305. Returns whether a tooltip is currently showing.
  13306. @function up.tooltip.isOpen
  13307. @stable
  13308. */
  13309. isOpen = function() {
  13310. return state.phase === 'opening' || state.phase === 'opened';
  13311. };
  13312. /***
  13313. Displays a tooltip with text content when hovering the mouse over this element.
  13314. \#\#\# Example
  13315. <a href="/decks" up-tooltip="Show all decks">Decks</a>
  13316. To make the tooltip appear below the element instead of above the element,
  13317. add an `up-position` attribute:
  13318. <a href="/decks" up-tooltip="Show all decks" up-position="bottom">Decks</a>
  13319. @selector [up-tooltip]
  13320. @param {string} [up-animation]
  13321. The animation used to open the tooltip.
  13322. Defaults to [`up.tooltip.config.openAnimation`](/up.tooltip.config).
  13323. @param {string} [up-position]
  13324. The tooltip's position relative to the opening element.
  13325. Valid values are `'top'`, `'right'`, `'bottom'` or `'left'`.
  13326. @param {string} [up-align]
  13327. Defines the alignment of the tooltip along its side.
  13328. When the tooltip's `{ position }` is `'top'` or `'bottom'`, valid `{ align }` values are `'left'`, `center'` and `'right'`.
  13329. When the tooltip's `{ position }` is `'left'` or `'right'`, valid `{ align }` values are `top'`, `center'` and `bottom'`.
  13330. @stable
  13331. */
  13332. /***
  13333. Displays a tooltip with HTML content when hovering the mouse over this element:
  13334. <a href="/decks" up-tooltip-html="Show &lt;b&gt;all&lt;/b&gt; decks">Decks</a>
  13335. @selector [up-tooltip-html]
  13336. @stable
  13337. */
  13338. up.compiler('[up-tooltip], [up-tooltip-html]', function(opener) {
  13339. opener.addEventListener('mouseenter', function() {
  13340. return attachAsap(opener);
  13341. });
  13342. return opener.addEventListener('mouseleave', function() {
  13343. return closeAsap();
  13344. });
  13345. });
  13346. up.on('click up:action:consumed', function(_event) {
  13347. return closeAsap();
  13348. });
  13349. up.on('up:framework:reset', reset);
  13350. up.event.onEscape(function() {
  13351. return closeAsap();
  13352. });
  13353. return {
  13354. config: config,
  13355. attach: attachAsap,
  13356. isOpen: isOpen,
  13357. close: closeAsap,
  13358. sync: syncPosition
  13359. };
  13360. })();
  13361. }).call(this);
  13362. /***
  13363. Navigation feedback
  13364. ===================
  13365. The `up.feedback` module adds useful CSS classes to links while they are loading,
  13366. or when they point to the current URL. By styling these classes you may
  13367. provide instant feedback to user interactions. This improves the perceived speed of your interface.
  13368. \#\#\# Example
  13369. Let's say we have an navigation bar with two links, pointing to `/foo` and `/bar` respectively:
  13370. <div up-nav>
  13371. <a href="/foo" up-follow>Foo</a>
  13372. <a href="/bar" up-follow>Bar</a>
  13373. </div>
  13374. If the current URL is `/foo`, the first link is automatically marked with an [`.up-current`](/a.up-current) class:
  13375. <div up-nav>
  13376. <a href="/foo" up-follow class="up-current">Foo</a>
  13377. <a href="/bar" up-follow>Bar</a>
  13378. </div>
  13379. When the user clicks on the `/bar` link, the link will receive the [`up-active`](/a.up-active) class while it is waiting
  13380. for the server to respond:
  13381. <div up-nav>
  13382. <a href="/foo" up-follow class="up-current">Foo</a>
  13383. <a href="/bar" up-follow class="up-active">Bar</a>
  13384. </div>
  13385. Once the response is received the URL will change to `/bar` and the `up-active` class is removed:
  13386. <div up-nav>
  13387. <a href="/foo" up-follow>Foo</a>
  13388. <a href="/bar" up-follow class="up-current">Bar</a>
  13389. </div>
  13390. @module up.feedback
  13391. */
  13392. (function() {
  13393. up.feedback = (function() {
  13394. var CLASS_ACTIVE, SELECTOR_LINK, buildCurrentUrlSet, buildSectionUrls, config, currentUrlSet, e, findActivatableArea, navSelector, normalizeUrl, previousUrlSet, reset, sectionUrls, start, stop, u, updateAllNavigationSections, updateAllNavigationSectionsIfLocationChanged, updateCurrentClassForLinks, updateNavigationSectionsInNewFragment;
  13395. u = up.util;
  13396. e = up.element;
  13397. /***
  13398. Sets default options for this module.
  13399. @property up.feedback.config
  13400. @param {Array<string>} [config.currentClasses]
  13401. An array of classes to set on [links that point the current location](/a.up-current).
  13402. @param {Array<string>} [config.navs]
  13403. An array of CSS selectors that match [navigation components](/up-nav).
  13404. @stable
  13405. */
  13406. config = new up.Config({
  13407. currentClasses: ['up-current'],
  13408. navs: ['[up-nav]']
  13409. });
  13410. previousUrlSet = void 0;
  13411. currentUrlSet = void 0;
  13412. reset = function() {
  13413. config.reset();
  13414. previousUrlSet = void 0;
  13415. return currentUrlSet = void 0;
  13416. };
  13417. CLASS_ACTIVE = 'up-active';
  13418. SELECTOR_LINK = 'a, [up-href]';
  13419. navSelector = function() {
  13420. return config.navs.join(',');
  13421. };
  13422. normalizeUrl = function(url) {
  13423. if (u.isPresent(url)) {
  13424. return u.normalizeUrl(url, {
  13425. stripTrailingSlash: true
  13426. });
  13427. }
  13428. };
  13429. sectionUrls = function(section) {
  13430. var urls;
  13431. if (!(urls = section.upNormalizedUrls)) {
  13432. urls = buildSectionUrls(section);
  13433. section.upNormalizedUrls = urls;
  13434. }
  13435. return urls;
  13436. };
  13437. buildSectionUrls = function(section) {
  13438. var attr, i, j, len, len1, ref, ref1, url, urls, value;
  13439. urls = [];
  13440. if (up.link.isSafe(section)) {
  13441. ref = ['href', 'up-href', 'up-alias'];
  13442. for (i = 0, len = ref.length; i < len; i++) {
  13443. attr = ref[i];
  13444. if (value = section.getAttribute(attr)) {
  13445. ref1 = u.splitValues(value);
  13446. for (j = 0, len1 = ref1.length; j < len1; j++) {
  13447. url = ref1[j];
  13448. if (url !== '#') {
  13449. url = normalizeUrl(url);
  13450. urls.push(url);
  13451. }
  13452. }
  13453. }
  13454. }
  13455. }
  13456. return urls;
  13457. };
  13458. buildCurrentUrlSet = function() {
  13459. var urls;
  13460. urls = [up.browser.url(), up.modal.url(), up.modal.coveredUrl(), up.popup.url(), up.popup.coveredUrl()];
  13461. return new up.UrlSet(urls, {
  13462. normalizeUrl: normalizeUrl
  13463. });
  13464. };
  13465. updateAllNavigationSectionsIfLocationChanged = function() {
  13466. previousUrlSet = currentUrlSet;
  13467. currentUrlSet = buildCurrentUrlSet();
  13468. if (!u.isEqual(currentUrlSet, previousUrlSet)) {
  13469. return updateAllNavigationSections(document.body);
  13470. }
  13471. };
  13472. updateAllNavigationSections = function(root) {
  13473. var navs, sections;
  13474. navs = e.subtree(root, navSelector());
  13475. sections = u.flatMap(navs, function(nav) {
  13476. return e.subtree(nav, SELECTOR_LINK);
  13477. });
  13478. return updateCurrentClassForLinks(sections);
  13479. };
  13480. updateNavigationSectionsInNewFragment = function(fragment) {
  13481. var sections;
  13482. if (e.closest(fragment, navSelector())) {
  13483. sections = e.subtree(fragment, SELECTOR_LINK);
  13484. return updateCurrentClassForLinks(sections);
  13485. } else {
  13486. return updateAllNavigationSections(fragment);
  13487. }
  13488. };
  13489. updateCurrentClassForLinks = function(links) {
  13490. currentUrlSet || (currentUrlSet = buildCurrentUrlSet());
  13491. return u.each(links, function(link) {
  13492. var classList, i, j, klass, len, len1, ref, ref1, results, results1, urls;
  13493. urls = sectionUrls(link);
  13494. classList = link.classList;
  13495. if (currentUrlSet.matchesAny(urls)) {
  13496. ref = config.currentClasses;
  13497. results = [];
  13498. for (i = 0, len = ref.length; i < len; i++) {
  13499. klass = ref[i];
  13500. results.push(classList.add(klass));
  13501. }
  13502. return results;
  13503. } else {
  13504. ref1 = config.currentClasses;
  13505. results1 = [];
  13506. for (j = 0, len1 = ref1.length; j < len1; j++) {
  13507. klass = ref1[j];
  13508. results1.push(classList.remove(klass));
  13509. }
  13510. return results1;
  13511. }
  13512. });
  13513. };
  13514. /***
  13515. @function findActivatableArea
  13516. @param {string|Element|jQuery} elementOrSelector
  13517. @internal
  13518. */
  13519. findActivatableArea = function(element) {
  13520. element = e.get(element);
  13521. return e.ancestor(element, SELECTOR_LINK) || element;
  13522. };
  13523. /***
  13524. Marks the given element as currently loading, by assigning the CSS class [`up-active`](/a.up-active).
  13525. This happens automatically when following links or submitting forms through the Unpoly API.
  13526. Use this function if you make custom network calls from your own JavaScript code.
  13527. If the given element is a link within an [expanded click area](/up-expand),
  13528. the class will be assigned to the expanded area.
  13529. \#\#\# Example
  13530. var button = document.querySelector('button')
  13531. button.addEventListener('click', () => {
  13532. up.feedback.start(button)
  13533. up.request(...).then(() => {
  13534. up.feedback.stop(button)
  13535. })
  13536. })
  13537. @method up.feedback.start
  13538. @param {Element|jQuery|string} element
  13539. The element to mark as active
  13540. @internal
  13541. */
  13542. start = function(element) {
  13543. return findActivatableArea(element).classList.add(CLASS_ACTIVE);
  13544. };
  13545. /***
  13546. Links that are currently [loading through Unpoly](/form-up-target)
  13547. are assigned the `up-active` class automatically.
  13548. Style `.up-active` in your CSS to improve the perceived responsiveness
  13549. of your user interface.
  13550. The `up-active` class will be removed when the link is done loading.
  13551. \#\#\# Example
  13552. We have a link:
  13553. <a href="/foo" up-follow>Foo</a>
  13554. The user clicks on the link. While the request is loading,
  13555. the link has the `up-active` class:
  13556. <a href="/foo" up-follow class="up-active">Foo</a>
  13557. Once the link destination has loaded and rendered, the `up-active` class
  13558. is removed and the [`up-current`](/a.up-current) class is added:
  13559. <a href="/foo" up-follow class="up-current">Foo</a>
  13560. @selector a.up-active
  13561. @stable
  13562. */
  13563. /***
  13564. Forms that are currently [loading through Unpoly](/a-up-target)
  13565. are assigned the `up-active` class automatically.
  13566. Style `.up-active` in your CSS to improve the perceived responsiveness
  13567. of your user interface.
  13568. The `up-active` class will be removed as soon as the response to the
  13569. form submission has been received.
  13570. \#\#\# Example
  13571. We have a form:
  13572. <form up-target=".foo">
  13573. <button type="submit">Submit</button>
  13574. </form>
  13575. The user clicks on the submit button. While the form is being submitted
  13576. and waiting for the server to respond, the form has the `up-active` class:
  13577. <form up-target=".foo" class="up-active">
  13578. <button type="submit">Submit</button>
  13579. </form>
  13580. Once the link destination has loaded and rendered, the `up-active` class
  13581. is removed.
  13582. @selector form.up-active
  13583. @stable
  13584. */
  13585. /***
  13586. Marks the given element as no longer loading, by removing the CSS class [`up-active`](/a.up-active).
  13587. This happens automatically when network requests initiated by the Unpoly API have completed.
  13588. Use this function if you make custom network calls from your own JavaScript code.
  13589. @function up.feedback.stop
  13590. @param {Element|jQuery|string} element
  13591. The link or form that has finished loading.
  13592. @internal
  13593. */
  13594. stop = function(element) {
  13595. return findActivatableArea(element).classList.remove(CLASS_ACTIVE);
  13596. };
  13597. /***
  13598. Marks this element as a navigation component, such as a menu or navigation bar.
  13599. When a link within an `[up-nav]` element points to the current location, it is assigned the `.up-current` class. When the browser navigates to another location, the class is removed automatically.
  13600. You may also assign `[up-nav]` to an individual link instead of an navigational container.
  13601. If you don't want to manually add this attribute to every navigational element, you can configure selectors to automatically match your navigation components in [`up.feedback.config.navs`](/up.feedback.config#config.navs).
  13602. \#\#\# Example
  13603. Let's take a simple menu with two links. The menu has been marked with the `[up-nav]` attribute:
  13604. <div up-nav>
  13605. <a href="/foo">Foo</a>
  13606. <a href="/bar">Bar</a>
  13607. </div>
  13608. If the browser location changes to `/foo`, the first link is marked as `.up-current`:
  13609. <div up-nav>
  13610. <a href="/foo" class="up-current">Foo</a>
  13611. <a href="/bar">Bar</a>
  13612. </div>
  13613. If the browser location changes to `/bar`, the first link automatically loses its `.up-current` class. Now the second link is marked as `.up-current`:
  13614. <div up-nav>
  13615. <a href="/foo">Foo</a>
  13616. <a href="/bar" class="up-current">Bar</a>
  13617. </div>
  13618. \#\#\# What is considered to be "current"?
  13619. The current location is considered to be either:
  13620. - the URL displayed in the browser window's location bar
  13621. - the source URL of a [modal dialog](/up.modal)
  13622. - the URL of the page behind a [modal dialog](/up.modal)
  13623. - the source URL of a [popup overlay](/up.popup)
  13624. - the URL of the content behind a [popup overlay](/up.popup)
  13625. A link matches the current location (and is marked as `.up-current`) if it matches either:
  13626. - the link's `href` attribute
  13627. - the link's `up-href` attribute
  13628. - a space-separated list of URLs in the link's `up-alias` attribute
  13629. \#\#\# Matching URL by pattern
  13630. You can mark a link as `.up-current` whenever the current URL matches a prefix or suffix.
  13631. To do so, include an asterisk (`*`) in the `up-alias` attribute.
  13632. For instance, the following `[up-nav]` link is highlighted for both `/reports` and `/reports/123`:
  13633. <a up-nav href="/reports" up-alias="/reports/*">Reports</a>
  13634. @selector [up-nav]
  13635. @stable
  13636. */
  13637. /***
  13638. When a link within an `[up-nav]` element points to the current location, it is assigned the `.up-current` class.
  13639. See [`[up-nav]`](/up-nav) for more documentation and examples.
  13640. @selector [up-nav] a.up-current
  13641. @stable
  13642. */
  13643. up.on('up:history:pushed up:history:replaced up:history:restored up:modal:opened up:modal:closed up:popup:opened up:popup:closed', function(event) {
  13644. return updateAllNavigationSectionsIfLocationChanged();
  13645. });
  13646. up.on('up:fragment:inserted', function(event, newFragment) {
  13647. return updateNavigationSectionsInNewFragment(newFragment);
  13648. });
  13649. up.on('up:framework:reset', reset);
  13650. return {
  13651. config: config,
  13652. start: start,
  13653. stop: stop
  13654. };
  13655. })();
  13656. up.legacy.renamedModule('navigation', 'feedback');
  13657. }).call(this);
  13658. /***
  13659. Passive updates
  13660. ===============
  13661. This work-in-progress package will contain functionality to
  13662. passively receive updates from the server.
  13663. @module up.radio
  13664. */
  13665. (function() {
  13666. up.radio = (function() {
  13667. var config, hungrySelector, reset, u;
  13668. u = up.util;
  13669. /***
  13670. Configures defaults for passive updates.
  13671. @property up.radio.config
  13672. @param {Array<string>} [options.hungry]
  13673. An array of CSS selectors that is replaced whenever a matching element is found in a response.
  13674. These elements are replaced even when they were not targeted directly.
  13675. By default this contains the [`[up-hungry]`](/up-hungry) attribute.
  13676. @param {string} [options.hungryTransition=null]
  13677. The transition to use when a [hungry element](/up-hungry) is replacing itself
  13678. while another target is replaced.
  13679. By default this is not set and the original replacement's transition is used.
  13680. @stable
  13681. */
  13682. config = new up.Config({
  13683. hungry: ['[up-hungry]'],
  13684. hungryTransition: null
  13685. });
  13686. reset = function() {
  13687. return config.reset();
  13688. };
  13689. /***
  13690. @function up.radio.hungrySelector
  13691. @internal
  13692. */
  13693. hungrySelector = function() {
  13694. return config.hungry.join(',');
  13695. };
  13696. /***
  13697. Elements with this attribute are [updated](/up.replace) whenever there is a
  13698. matching element found in a successful response. The element is replaced even
  13699. when it isn't [targeted](/a-up-target) directly.
  13700. Use cases for this are unread message counters or notification flashes.
  13701. Such elements often live in the layout, outside of the content area that is
  13702. being replaced.
  13703. @selector [up-hungry]
  13704. @stable
  13705. */
  13706. up.on('up:framework:reset', reset);
  13707. return {
  13708. config: config,
  13709. hungrySelector: hungrySelector
  13710. };
  13711. })();
  13712. }).call(this);
  13713. /***
  13714. Play nice with Rails UJS
  13715. ========================
  13716. */
  13717. (function() {
  13718. up.rails = (function() {
  13719. var e, isRails, u;
  13720. u = up.util;
  13721. e = up.element;
  13722. isRails = function() {
  13723. var ref;
  13724. return !!(window.Rails || ((ref = window.jQuery) != null ? ref.rails : void 0));
  13725. };
  13726. return u.each(['method', 'confirm'], function(feature) {
  13727. var dataAttribute, upAttribute;
  13728. dataAttribute = "data-" + feature;
  13729. upAttribute = "up-" + feature;
  13730. return up.macro("[" + dataAttribute + "]", function(element) {
  13731. var replacement;
  13732. if (isRails() && up.link.isFollowable(element)) {
  13733. replacement = {};
  13734. replacement[upAttribute] = element.getAttribute(dataAttribute);
  13735. e.setMissingAttrs(element, replacement);
  13736. return element.removeAttribute(dataAttribute);
  13737. }
  13738. });
  13739. });
  13740. })();
  13741. }).call(this);
  13742. (function() {
  13743. up.framework.boot();
  13744. }).call(this);