12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022702370247025702670277028702970307031703270337034703570367037703870397040704170427043704470457046704770487049705070517052705370547055705670577058705970607061706270637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106710771087109711071117112711371147115711671177118711971207121712271237124712571267127712871297130713171327133713471357136713771387139714071417142714371447145714671477148714971507151715271537154715571567157715871597160716171627163716471657166716771687169717071717172717371747175717671777178717971807181718271837184718571867187718871897190719171927193719471957196719771987199720072017202720372047205720672077208720972107211721272137214721572167217721872197220722172227223722472257226722772287229723072317232723372347235723672377238723972407241724272437244724572467247724872497250725172527253725472557256725772587259726072617262726372647265726672677268726972707271727272737274727572767277727872797280728172827283728472857286728772887289729072917292729372947295729672977298729973007301730273037304730573067307730873097310731173127313731473157316731773187319732073217322732373247325732673277328732973307331733273337334733573367337733873397340734173427343734473457346734773487349735073517352735373547355735673577358735973607361736273637364736573667367736873697370737173727373737473757376737773787379738073817382738373847385738673877388738973907391739273937394739573967397739873997400740174027403740474057406740774087409741074117412741374147415741674177418741974207421742274237424742574267427742874297430743174327433743474357436743774387439744074417442744374447445744674477448744974507451745274537454745574567457745874597460746174627463746474657466746774687469747074717472747374747475747674777478747974807481748274837484748574867487748874897490749174927493749474957496749774987499750075017502750375047505750675077508750975107511751275137514751575167517751875197520752175227523752475257526752775287529753075317532753375347535753675377538753975407541754275437544754575467547754875497550755175527553755475557556755775587559756075617562756375647565756675677568756975707571757275737574757575767577757875797580758175827583758475857586758775887589759075917592759375947595759675977598759976007601760276037604760576067607760876097610761176127613761476157616761776187619762076217622762376247625762676277628762976307631763276337634763576367637763876397640764176427643764476457646764776487649765076517652765376547655765676577658765976607661766276637664766576667667766876697670767176727673767476757676767776787679768076817682768376847685768676877688768976907691769276937694769576967697769876997700770177027703770477057706770777087709771077117712771377147715771677177718771977207721772277237724772577267727772877297730773177327733773477357736773777387739774077417742774377447745774677477748774977507751775277537754775577567757775877597760776177627763776477657766776777687769777077717772777377747775777677777778777977807781778277837784778577867787778877897790779177927793779477957796779777987799780078017802780378047805780678077808780978107811781278137814781578167817781878197820782178227823782478257826782778287829783078317832783378347835783678377838783978407841784278437844784578467847784878497850785178527853785478557856785778587859786078617862786378647865786678677868786978707871787278737874787578767877787878797880788178827883788478857886788778887889789078917892789378947895789678977898789979007901790279037904790579067907790879097910791179127913791479157916791779187919792079217922792379247925792679277928792979307931793279337934793579367937793879397940794179427943794479457946794779487949795079517952795379547955795679577958795979607961796279637964796579667967796879697970797179727973797479757976797779787979798079817982798379847985798679877988798979907991799279937994799579967997799879998000800180028003800480058006800780088009801080118012801380148015801680178018801980208021802280238024802580268027802880298030803180328033803480358036803780388039804080418042804380448045804680478048804980508051805280538054805580568057805880598060806180628063806480658066806780688069807080718072807380748075807680778078807980808081808280838084808580868087808880898090809180928093809480958096809780988099810081018102810381048105810681078108810981108111811281138114811581168117811881198120812181228123812481258126812781288129813081318132813381348135813681378138813981408141814281438144814581468147814881498150815181528153815481558156815781588159816081618162816381648165816681678168816981708171817281738174817581768177817881798180818181828183818481858186818781888189819081918192819381948195819681978198819982008201820282038204820582068207820882098210821182128213821482158216821782188219822082218222822382248225822682278228822982308231823282338234823582368237823882398240824182428243824482458246824782488249825082518252825382548255825682578258825982608261826282638264826582668267826882698270827182728273827482758276827782788279828082818282828382848285828682878288828982908291829282938294829582968297829882998300830183028303830483058306830783088309831083118312831383148315831683178318831983208321832283238324832583268327832883298330833183328333833483358336833783388339834083418342834383448345834683478348834983508351835283538354835583568357835883598360836183628363836483658366836783688369837083718372837383748375837683778378837983808381838283838384838583868387838883898390839183928393839483958396839783988399840084018402840384048405840684078408840984108411841284138414841584168417841884198420842184228423842484258426842784288429843084318432843384348435843684378438843984408441844284438444844584468447844884498450845184528453845484558456845784588459846084618462846384648465846684678468846984708471847284738474847584768477847884798480848184828483848484858486848784888489849084918492849384948495849684978498849985008501850285038504850585068507850885098510851185128513851485158516851785188519852085218522852385248525852685278528852985308531853285338534853585368537853885398540854185428543854485458546854785488549855085518552855385548555855685578558855985608561856285638564856585668567856885698570857185728573857485758576857785788579858085818582858385848585858685878588858985908591859285938594859585968597859885998600860186028603860486058606860786088609861086118612861386148615861686178618861986208621862286238624862586268627862886298630863186328633863486358636863786388639864086418642864386448645864686478648864986508651865286538654865586568657865886598660866186628663866486658666866786688669867086718672867386748675867686778678867986808681868286838684868586868687868886898690869186928693869486958696869786988699870087018702870387048705870687078708870987108711871287138714871587168717871887198720872187228723872487258726872787288729873087318732873387348735873687378738873987408741874287438744874587468747874887498750875187528753875487558756875787588759876087618762876387648765876687678768876987708771877287738774877587768777877887798780878187828783878487858786878787888789879087918792879387948795879687978798879988008801880288038804880588068807880888098810881188128813881488158816881788188819882088218822882388248825882688278828882988308831883288338834883588368837883888398840884188428843884488458846884788488849885088518852885388548855885688578858885988608861886288638864886588668867886888698870887188728873887488758876887788788879888088818882888388848885888688878888888988908891889288938894889588968897889888998900890189028903890489058906890789088909891089118912891389148915891689178918891989208921892289238924892589268927892889298930893189328933893489358936893789388939894089418942894389448945894689478948894989508951895289538954895589568957895889598960896189628963896489658966896789688969897089718972897389748975897689778978897989808981898289838984898589868987898889898990899189928993899489958996899789988999900090019002900390049005900690079008900990109011901290139014901590169017901890199020902190229023902490259026902790289029903090319032903390349035903690379038903990409041904290439044904590469047904890499050905190529053905490559056905790589059906090619062906390649065906690679068906990709071907290739074907590769077907890799080908190829083908490859086908790889089909090919092909390949095909690979098909991009101910291039104910591069107910891099110911191129113911491159116911791189119912091219122912391249125912691279128912991309131913291339134913591369137913891399140914191429143914491459146914791489149915091519152915391549155915691579158915991609161916291639164916591669167916891699170917191729173917491759176917791789179918091819182918391849185918691879188918991909191919291939194919591969197919891999200920192029203920492059206920792089209921092119212921392149215921692179218921992209221922292239224922592269227922892299230923192329233923492359236923792389239924092419242924392449245924692479248924992509251925292539254925592569257925892599260926192629263926492659266926792689269927092719272927392749275927692779278927992809281928292839284928592869287928892899290929192929293929492959296929792989299930093019302930393049305930693079308930993109311931293139314931593169317931893199320932193229323932493259326932793289329933093319332933393349335933693379338933993409341934293439344934593469347934893499350935193529353935493559356935793589359936093619362936393649365936693679368936993709371937293739374937593769377937893799380938193829383938493859386938793889389939093919392939393949395939693979398939994009401940294039404940594069407940894099410941194129413941494159416941794189419942094219422942394249425942694279428942994309431943294339434943594369437943894399440944194429443944494459446944794489449945094519452945394549455945694579458945994609461946294639464946594669467946894699470947194729473947494759476947794789479948094819482948394849485948694879488948994909491949294939494949594969497949894999500950195029503950495059506950795089509951095119512951395149515951695179518951995209521952295239524952595269527952895299530953195329533953495359536953795389539954095419542954395449545954695479548954995509551955295539554955595569557955895599560956195629563956495659566956795689569957095719572957395749575957695779578957995809581958295839584958595869587958895899590959195929593959495959596959795989599960096019602960396049605960696079608960996109611961296139614961596169617961896199620962196229623962496259626962796289629963096319632963396349635963696379638963996409641964296439644964596469647964896499650965196529653965496559656965796589659966096619662966396649665966696679668966996709671967296739674967596769677967896799680968196829683968496859686968796889689969096919692969396949695969696979698969997009701970297039704970597069707970897099710971197129713971497159716971797189719972097219722972397249725972697279728972997309731973297339734973597369737973897399740974197429743974497459746974797489749975097519752975397549755975697579758975997609761976297639764976597669767976897699770977197729773977497759776977797789779978097819782978397849785978697879788978997909791979297939794979597969797979897999800980198029803980498059806980798089809981098119812981398149815981698179818981998209821982298239824982598269827982898299830983198329833983498359836983798389839984098419842984398449845984698479848984998509851985298539854985598569857985898599860986198629863986498659866986798689869987098719872987398749875987698779878987998809881988298839884988598869887988898899890989198929893989498959896989798989899990099019902990399049905990699079908990999109911991299139914991599169917991899199920992199229923992499259926992799289929993099319932993399349935993699379938993999409941994299439944994599469947994899499950995199529953995499559956995799589959996099619962996399649965996699679968996999709971997299739974997599769977997899799980998199829983998499859986998799889989999099919992999399949995999699979998999910000100011000210003100041000510006100071000810009100101001110012100131001410015100161001710018100191002010021100221002310024100251002610027100281002910030100311003210033100341003510036100371003810039100401004110042100431004410045100461004710048100491005010051100521005310054100551005610057100581005910060100611006210063100641006510066100671006810069100701007110072100731007410075100761007710078100791008010081100821008310084100851008610087100881008910090100911009210093100941009510096100971009810099101001010110102101031010410105101061010710108101091011010111101121011310114101151011610117101181011910120101211012210123101241012510126101271012810129101301013110132101331013410135101361013710138101391014010141101421014310144101451014610147101481014910150101511015210153101541015510156101571015810159101601016110162101631016410165101661016710168101691017010171101721017310174101751017610177101781017910180101811018210183101841018510186101871018810189101901019110192101931019410195101961019710198101991020010201102021020310204102051020610207102081020910210102111021210213102141021510216102171021810219102201022110222102231022410225102261022710228102291023010231102321023310234102351023610237102381023910240102411024210243102441024510246102471024810249102501025110252102531025410255102561025710258102591026010261102621026310264102651026610267102681026910270102711027210273102741027510276102771027810279102801028110282102831028410285102861028710288102891029010291102921029310294102951029610297102981029910300103011030210303103041030510306103071030810309103101031110312103131031410315103161031710318103191032010321103221032310324103251032610327103281032910330103311033210333103341033510336103371033810339103401034110342103431034410345103461034710348103491035010351103521035310354103551035610357103581035910360103611036210363103641036510366103671036810369103701037110372103731037410375103761037710378103791038010381103821038310384103851038610387103881038910390103911039210393103941039510396103971039810399104001040110402104031040410405104061040710408104091041010411104121041310414104151041610417104181041910420104211042210423104241042510426104271042810429104301043110432104331043410435104361043710438104391044010441104421044310444104451044610447104481044910450104511045210453104541045510456104571045810459104601046110462104631046410465104661046710468104691047010471104721047310474104751047610477104781047910480104811048210483104841048510486104871048810489104901049110492104931049410495104961049710498104991050010501105021050310504105051050610507105081050910510105111051210513105141051510516105171051810519105201052110522105231052410525105261052710528105291053010531105321053310534105351053610537105381053910540105411054210543105441054510546105471054810549105501055110552105531055410555105561055710558105591056010561105621056310564105651056610567105681056910570105711057210573105741057510576105771057810579105801058110582105831058410585105861058710588105891059010591105921059310594105951059610597105981059910600106011060210603106041060510606106071060810609106101061110612106131061410615106161061710618106191062010621106221062310624106251062610627106281062910630106311063210633106341063510636106371063810639106401064110642106431064410645106461064710648106491065010651106521065310654106551065610657106581065910660106611066210663106641066510666106671066810669106701067110672106731067410675106761067710678106791068010681106821068310684106851068610687106881068910690106911069210693106941069510696106971069810699107001070110702107031070410705107061070710708107091071010711107121071310714107151071610717107181071910720107211072210723107241072510726107271072810729107301073110732107331073410735107361073710738107391074010741107421074310744107451074610747107481074910750107511075210753107541075510756107571075810759107601076110762107631076410765107661076710768107691077010771107721077310774107751077610777107781077910780107811078210783107841078510786107871078810789107901079110792107931079410795107961079710798107991080010801108021080310804108051080610807108081080910810108111081210813108141081510816108171081810819108201082110822108231082410825108261082710828108291083010831108321083310834108351083610837108381083910840108411084210843108441084510846108471084810849108501085110852108531085410855108561085710858108591086010861108621086310864108651086610867108681086910870108711087210873108741087510876108771087810879108801088110882108831088410885108861088710888108891089010891108921089310894108951089610897108981089910900109011090210903109041090510906109071090810909109101091110912109131091410915109161091710918109191092010921109221092310924109251092610927109281092910930109311093210933109341093510936109371093810939109401094110942109431094410945109461094710948109491095010951109521095310954109551095610957109581095910960109611096210963109641096510966109671096810969109701097110972109731097410975109761097710978109791098010981109821098310984109851098610987109881098910990109911099210993109941099510996109971099810999110001100111002110031100411005110061100711008110091101011011110121101311014110151101611017110181101911020110211102211023110241102511026110271102811029110301103111032110331103411035110361103711038110391104011041110421104311044110451104611047110481104911050110511105211053110541105511056110571105811059110601106111062110631106411065110661106711068110691107011071110721107311074110751107611077110781107911080110811108211083110841108511086110871108811089110901109111092110931109411095110961109711098110991110011101111021110311104111051110611107111081110911110111111111211113111141111511116111171111811119111201112111122111231112411125111261112711128111291113011131111321113311134111351113611137111381113911140111411114211143111441114511146111471114811149111501115111152111531115411155111561115711158111591116011161111621116311164111651116611167111681116911170111711117211173111741117511176111771117811179111801118111182111831118411185111861118711188111891119011191111921119311194111951119611197111981119911200112011120211203112041120511206112071120811209112101121111212112131121411215112161121711218112191122011221112221122311224112251122611227112281122911230112311123211233112341123511236112371123811239112401124111242112431124411245112461124711248112491125011251112521125311254112551125611257112581125911260112611126211263112641126511266112671126811269112701127111272112731127411275112761127711278112791128011281112821128311284112851128611287112881128911290112911129211293112941129511296112971129811299113001130111302113031130411305113061130711308113091131011311113121131311314113151131611317113181131911320113211132211323113241132511326113271132811329113301133111332113331133411335113361133711338113391134011341113421134311344113451134611347113481134911350113511135211353113541135511356113571135811359113601136111362113631136411365113661136711368113691137011371113721137311374113751137611377113781137911380113811138211383113841138511386113871138811389113901139111392113931139411395113961139711398113991140011401114021140311404114051140611407114081140911410114111141211413114141141511416114171141811419114201142111422114231142411425114261142711428114291143011431114321143311434114351143611437114381143911440114411144211443114441144511446114471144811449114501145111452114531145411455114561145711458114591146011461114621146311464114651146611467114681146911470114711147211473114741147511476114771147811479114801148111482114831148411485114861148711488114891149011491114921149311494114951149611497114981149911500115011150211503115041150511506115071150811509115101151111512115131151411515115161151711518115191152011521115221152311524115251152611527115281152911530115311153211533115341153511536115371153811539115401154111542115431154411545115461154711548115491155011551115521155311554115551155611557115581155911560115611156211563115641156511566115671156811569115701157111572115731157411575115761157711578115791158011581115821158311584115851158611587115881158911590115911159211593115941159511596115971159811599116001160111602116031160411605116061160711608116091161011611116121161311614116151161611617116181161911620116211162211623116241162511626116271162811629116301163111632116331163411635116361163711638116391164011641116421164311644116451164611647116481164911650116511165211653116541165511656116571165811659116601166111662116631166411665116661166711668116691167011671116721167311674116751167611677116781167911680116811168211683116841168511686116871168811689116901169111692116931169411695116961169711698116991170011701117021170311704117051170611707117081170911710117111171211713117141171511716117171171811719117201172111722117231172411725117261172711728117291173011731117321173311734117351173611737117381173911740117411174211743117441174511746117471174811749117501175111752117531175411755117561175711758117591176011761117621176311764117651176611767117681176911770117711177211773117741177511776117771177811779117801178111782117831178411785117861178711788117891179011791117921179311794117951179611797117981179911800118011180211803118041180511806118071180811809118101181111812118131181411815118161181711818118191182011821118221182311824118251182611827118281182911830118311183211833118341183511836118371183811839118401184111842118431184411845118461184711848118491185011851118521185311854118551185611857118581185911860118611186211863118641186511866118671186811869118701187111872118731187411875118761187711878118791188011881118821188311884118851188611887118881188911890118911189211893118941189511896118971189811899119001190111902119031190411905119061190711908119091191011911119121191311914119151191611917119181191911920119211192211923119241192511926119271192811929119301193111932119331193411935119361193711938119391194011941119421194311944119451194611947119481194911950119511195211953119541195511956119571195811959119601196111962119631196411965119661196711968119691197011971119721197311974119751197611977119781197911980119811198211983119841198511986119871198811989119901199111992119931199411995119961199711998119991200012001120021200312004120051200612007120081200912010120111201212013120141201512016120171201812019120201202112022120231202412025120261202712028120291203012031120321203312034120351203612037120381203912040120411204212043120441204512046120471204812049120501205112052120531205412055120561205712058120591206012061120621206312064120651206612067120681206912070120711207212073120741207512076120771207812079120801208112082120831208412085120861208712088120891209012091120921209312094120951209612097120981209912100121011210212103121041210512106121071210812109121101211112112121131211412115121161211712118121191212012121121221212312124121251212612127121281212912130121311213212133121341213512136121371213812139121401214112142121431214412145121461214712148121491215012151121521215312154121551215612157121581215912160121611216212163121641216512166121671216812169121701217112172121731217412175121761217712178121791218012181121821218312184121851218612187121881218912190121911219212193121941219512196121971219812199122001220112202122031220412205122061220712208122091221012211122121221312214122151221612217122181221912220122211222212223122241222512226122271222812229122301223112232122331223412235122361223712238122391224012241122421224312244122451224612247122481224912250122511225212253122541225512256122571225812259122601226112262122631226412265122661226712268122691227012271122721227312274122751227612277122781227912280122811228212283122841228512286122871228812289122901229112292122931229412295122961229712298122991230012301123021230312304123051230612307123081230912310123111231212313123141231512316123171231812319123201232112322123231232412325123261232712328123291233012331123321233312334123351233612337123381233912340123411234212343123441234512346123471234812349123501235112352123531235412355123561235712358123591236012361123621236312364123651236612367123681236912370123711237212373123741237512376123771237812379123801238112382123831238412385123861238712388123891239012391123921239312394123951239612397123981239912400124011240212403124041240512406124071240812409124101241112412124131241412415124161241712418124191242012421124221242312424124251242612427124281242912430124311243212433124341243512436124371243812439124401244112442124431244412445124461244712448124491245012451124521245312454124551245612457124581245912460124611246212463124641246512466124671246812469124701247112472124731247412475124761247712478124791248012481124821248312484124851248612487124881248912490124911249212493124941249512496124971249812499125001250112502125031250412505125061250712508125091251012511125121251312514125151251612517125181251912520125211252212523125241252512526125271252812529125301253112532125331253412535125361253712538125391254012541125421254312544125451254612547125481254912550125511255212553125541255512556125571255812559125601256112562125631256412565125661256712568125691257012571125721257312574125751257612577125781257912580125811258212583125841258512586125871258812589125901259112592125931259412595125961259712598125991260012601126021260312604126051260612607126081260912610126111261212613126141261512616126171261812619126201262112622126231262412625126261262712628126291263012631126321263312634126351263612637126381263912640126411264212643126441264512646126471264812649126501265112652126531265412655126561265712658126591266012661126621266312664126651266612667126681266912670126711267212673126741267512676126771267812679126801268112682126831268412685126861268712688126891269012691126921269312694126951269612697126981269912700127011270212703127041270512706127071270812709127101271112712127131271412715127161271712718127191272012721127221272312724127251272612727127281272912730127311273212733127341273512736127371273812739127401274112742127431274412745127461274712748127491275012751127521275312754127551275612757127581275912760127611276212763127641276512766127671276812769127701277112772127731277412775127761277712778127791278012781127821278312784127851278612787127881278912790127911279212793127941279512796127971279812799128001280112802128031280412805128061280712808128091281012811128121281312814128151281612817128181281912820128211282212823128241282512826128271282812829128301283112832128331283412835128361283712838128391284012841128421284312844128451284612847128481284912850128511285212853128541285512856128571285812859128601286112862128631286412865128661286712868128691287012871128721287312874128751287612877128781287912880128811288212883128841288512886128871288812889128901289112892128931289412895128961289712898128991290012901129021290312904129051290612907129081290912910129111291212913129141291512916129171291812919129201292112922129231292412925129261292712928129291293012931129321293312934129351293612937129381293912940129411294212943129441294512946129471294812949129501295112952129531295412955129561295712958129591296012961129621296312964129651296612967129681296912970129711297212973129741297512976129771297812979129801298112982129831298412985129861298712988129891299012991129921299312994129951299612997129981299913000130011300213003130041300513006130071300813009130101301113012130131301413015130161301713018130191302013021130221302313024130251302613027130281302913030130311303213033130341303513036130371303813039130401304113042130431304413045130461304713048130491305013051130521305313054130551305613057130581305913060130611306213063130641306513066130671306813069130701307113072130731307413075130761307713078130791308013081130821308313084130851308613087130881308913090130911309213093130941309513096130971309813099131001310113102131031310413105131061310713108131091311013111131121311313114131151311613117131181311913120131211312213123131241312513126131271312813129131301313113132131331313413135131361313713138131391314013141131421314313144131451314613147131481314913150131511315213153131541315513156131571315813159131601316113162131631316413165131661316713168131691317013171131721317313174131751317613177131781317913180131811318213183131841318513186131871318813189131901319113192131931319413195131961319713198131991320013201132021320313204132051320613207132081320913210132111321213213132141321513216132171321813219132201322113222132231322413225132261322713228132291323013231132321323313234132351323613237132381323913240132411324213243132441324513246132471324813249132501325113252132531325413255132561325713258132591326013261132621326313264132651326613267132681326913270132711327213273132741327513276132771327813279132801328113282132831328413285132861328713288132891329013291132921329313294132951329613297132981329913300133011330213303133041330513306133071330813309133101331113312133131331413315133161331713318133191332013321133221332313324133251332613327133281332913330133311333213333133341333513336133371333813339133401334113342133431334413345133461334713348133491335013351133521335313354133551335613357133581335913360133611336213363133641336513366133671336813369133701337113372133731337413375133761337713378133791338013381133821338313384133851338613387133881338913390133911339213393133941339513396133971339813399134001340113402134031340413405134061340713408134091341013411134121341313414134151341613417134181341913420134211342213423134241342513426134271342813429134301343113432134331343413435134361343713438134391344013441134421344313444134451344613447134481344913450134511345213453134541345513456134571345813459134601346113462134631346413465134661346713468134691347013471134721347313474134751347613477134781347913480134811348213483134841348513486134871348813489134901349113492134931349413495134961349713498134991350013501135021350313504135051350613507135081350913510135111351213513135141351513516135171351813519135201352113522135231352413525135261352713528135291353013531135321353313534135351353613537135381353913540135411354213543135441354513546135471354813549135501355113552135531355413555135561355713558135591356013561135621356313564135651356613567135681356913570135711357213573135741357513576135771357813579135801358113582135831358413585135861358713588135891359013591135921359313594135951359613597135981359913600136011360213603136041360513606136071360813609136101361113612136131361413615136161361713618136191362013621136221362313624136251362613627136281362913630136311363213633136341363513636136371363813639136401364113642136431364413645136461364713648136491365013651136521365313654136551365613657136581365913660136611366213663136641366513666136671366813669136701367113672136731367413675136761367713678136791368013681136821368313684136851368613687136881368913690136911369213693136941369513696136971369813699137001370113702137031370413705137061370713708137091371013711137121371313714137151371613717137181371913720137211372213723137241372513726137271372813729137301373113732137331373413735137361373713738137391374013741137421374313744137451374613747137481374913750137511375213753137541375513756137571375813759137601376113762137631376413765137661376713768137691377013771137721377313774137751377613777137781377913780137811378213783137841378513786137871378813789137901379113792137931379413795137961379713798137991380013801138021380313804138051380613807138081380913810138111381213813138141381513816138171381813819138201382113822138231382413825138261382713828138291383013831138321383313834138351383613837138381383913840138411384213843138441384513846138471384813849138501385113852138531385413855138561385713858138591386013861138621386313864138651386613867138681386913870138711387213873138741387513876138771387813879138801388113882138831388413885138861388713888138891389013891138921389313894138951389613897138981389913900139011390213903139041390513906139071390813909139101391113912139131391413915139161391713918139191392013921139221392313924139251392613927139281392913930139311393213933139341393513936139371393813939139401394113942139431394413945139461394713948139491395013951139521395313954139551395613957139581395913960139611396213963139641396513966139671396813969139701397113972139731397413975139761397713978139791398013981139821398313984139851398613987139881398913990139911399213993139941399513996139971399813999140001400114002140031400414005140061400714008140091401014011140121401314014140151401614017140181401914020140211402214023140241402514026140271402814029140301403114032140331403414035140361403714038140391404014041140421404314044140451404614047140481404914050140511405214053140541405514056140571405814059140601406114062140631406414065140661406714068140691407014071140721407314074140751407614077140781407914080140811408214083140841408514086140871408814089140901409114092140931409414095140961409714098140991410014101141021410314104141051410614107141081410914110141111411214113141141411514116141171411814119141201412114122141231412414125141261412714128141291413014131141321413314134141351413614137141381413914140141411414214143141441414514146141471414814149141501415114152141531415414155141561415714158141591416014161141621416314164141651416614167141681416914170141711417214173141741417514176141771417814179141801418114182141831418414185141861418714188141891419014191141921419314194141951419614197141981419914200142011420214203142041420514206142071420814209142101421114212142131421414215142161421714218142191422014221142221422314224142251422614227142281422914230142311423214233142341423514236142371423814239142401424114242142431424414245142461424714248142491425014251142521425314254142551425614257142581425914260142611426214263142641426514266142671426814269142701427114272142731427414275142761427714278142791428014281142821428314284142851428614287142881428914290142911429214293142941429514296142971429814299143001430114302143031430414305143061430714308143091431014311143121431314314143151431614317143181431914320143211432214323143241432514326143271432814329143301433114332143331433414335143361433714338143391434014341143421434314344143451434614347143481434914350143511435214353143541435514356143571435814359143601436114362143631436414365143661436714368143691437014371143721437314374143751437614377143781437914380143811438214383143841438514386143871438814389143901439114392143931439414395143961439714398143991440014401144021440314404144051440614407144081440914410144111441214413144141441514416144171441814419144201442114422144231442414425144261442714428144291443014431144321443314434144351443614437144381443914440144411444214443144441444514446144471444814449144501445114452144531445414455144561445714458144591446014461144621446314464144651446614467144681446914470144711447214473144741447514476144771447814479144801448114482144831448414485144861448714488144891449014491144921449314494144951449614497144981449914500145011450214503145041450514506145071450814509145101451114512145131451414515145161451714518145191452014521145221452314524145251452614527145281452914530145311453214533145341453514536145371453814539145401454114542145431454414545145461454714548145491455014551145521455314554145551455614557145581455914560145611456214563145641456514566145671456814569145701457114572145731457414575145761457714578145791458014581145821458314584145851458614587145881458914590145911459214593145941459514596145971459814599146001460114602146031460414605146061460714608146091461014611146121461314614146151461614617146181461914620146211462214623146241462514626146271462814629146301463114632146331463414635146361463714638146391464014641146421464314644146451464614647146481464914650146511465214653146541465514656146571465814659146601466114662146631466414665146661466714668146691467014671146721467314674146751467614677146781467914680146811468214683146841468514686146871468814689146901469114692146931469414695146961469714698146991470014701147021470314704147051470614707147081470914710147111471214713147141471514716147171471814719147201472114722147231472414725147261472714728147291473014731147321473314734147351473614737147381473914740147411474214743147441474514746147471474814749147501475114752147531475414755147561475714758147591476014761147621476314764147651476614767147681476914770147711477214773147741477514776147771477814779147801478114782147831478414785147861478714788147891479014791147921479314794147951479614797147981479914800148011480214803148041480514806148071480814809148101481114812148131481414815148161481714818148191482014821148221482314824148251482614827148281482914830148311483214833148341483514836148371483814839148401484114842148431484414845148461484714848148491485014851148521485314854148551485614857148581485914860148611486214863148641486514866148671486814869148701487114872148731487414875148761487714878148791488014881148821488314884148851488614887148881488914890148911489214893148941489514896148971489814899149001490114902149031490414905149061490714908149091491014911149121491314914149151491614917149181491914920149211492214923149241492514926149271492814929149301493114932149331493414935149361493714938149391494014941149421494314944149451494614947149481494914950149511495214953149541495514956149571495814959149601496114962149631496414965149661496714968149691497014971149721497314974149751497614977149781497914980149811498214983149841498514986149871498814989149901499114992149931499414995149961499714998149991500015001150021500315004150051500615007150081500915010150111501215013150141501515016150171501815019150201502115022150231502415025150261502715028150291503015031150321503315034150351503615037150381503915040150411504215043150441504515046150471504815049150501505115052150531505415055150561505715058150591506015061150621506315064150651506615067150681506915070150711507215073150741507515076150771507815079150801508115082150831508415085150861508715088150891509015091150921509315094150951509615097150981509915100151011510215103151041510515106151071510815109151101511115112151131511415115151161511715118151191512015121151221512315124151251512615127151281512915130151311513215133151341513515136151371513815139151401514115142151431514415145151461514715148151491515015151151521515315154151551515615157151581515915160151611516215163151641516515166151671516815169151701517115172151731517415175151761517715178151791518015181151821518315184151851518615187151881518915190151911519215193151941519515196151971519815199152001520115202152031520415205152061520715208152091521015211152121521315214152151521615217152181521915220152211522215223152241522515226152271522815229152301523115232152331523415235152361523715238152391524015241152421524315244152451524615247152481524915250152511525215253152541525515256152571525815259152601526115262152631526415265152661526715268152691527015271152721527315274152751527615277152781527915280152811528215283152841528515286152871528815289152901529115292152931529415295152961529715298152991530015301153021530315304153051530615307153081530915310153111531215313153141531515316153171531815319153201532115322153231532415325153261532715328153291533015331153321533315334153351533615337153381533915340153411534215343153441534515346153471534815349153501535115352153531535415355153561535715358153591536015361153621536315364153651536615367153681536915370153711537215373153741537515376153771537815379153801538115382153831538415385153861538715388153891539015391153921539315394153951539615397153981539915400154011540215403154041540515406154071540815409154101541115412154131541415415154161541715418154191542015421154221542315424154251542615427154281542915430154311543215433154341543515436154371543815439154401544115442154431544415445154461544715448154491545015451154521545315454154551545615457154581545915460154611546215463154641546515466154671546815469154701547115472154731547415475154761547715478154791548015481154821548315484154851548615487154881548915490154911549215493154941549515496154971549815499155001550115502155031550415505155061550715508155091551015511155121551315514155151551615517155181551915520155211552215523155241552515526155271552815529155301553115532155331553415535155361553715538155391554015541155421554315544155451554615547155481554915550155511555215553155541555515556155571555815559155601556115562155631556415565155661556715568155691557015571155721557315574155751557615577155781557915580155811558215583155841558515586155871558815589155901559115592155931559415595155961559715598155991560015601156021560315604156051560615607156081560915610156111561215613156141561515616156171561815619156201562115622156231562415625156261562715628156291563015631156321563315634156351563615637156381563915640156411564215643156441564515646156471564815649156501565115652156531565415655156561565715658156591566015661156621566315664156651566615667156681566915670156711567215673156741567515676156771567815679156801568115682156831568415685156861568715688156891569015691156921569315694156951569615697156981569915700157011570215703157041570515706157071570815709157101571115712157131571415715157161571715718157191572015721157221572315724157251572615727157281572915730157311573215733157341573515736157371573815739157401574115742157431574415745157461574715748157491575015751157521575315754157551575615757157581575915760157611576215763157641576515766157671576815769157701577115772157731577415775157761577715778157791578015781157821578315784157851578615787157881578915790157911579215793157941579515796157971579815799158001580115802158031580415805158061580715808158091581015811158121581315814158151581615817158181581915820158211582215823158241582515826158271582815829158301583115832158331583415835158361583715838158391584015841158421584315844158451584615847158481584915850158511585215853158541585515856158571585815859158601586115862158631586415865158661586715868158691587015871158721587315874158751587615877158781587915880158811588215883158841588515886158871588815889158901589115892158931589415895158961589715898158991590015901159021590315904159051590615907159081590915910159111591215913159141591515916159171591815919159201592115922159231592415925159261592715928159291593015931159321593315934159351593615937159381593915940159411594215943159441594515946159471594815949159501595115952159531595415955159561595715958159591596015961159621596315964159651596615967159681596915970159711597215973159741597515976159771597815979159801598115982159831598415985159861598715988159891599015991159921599315994159951599615997159981599916000160011600216003160041600516006160071600816009160101601116012160131601416015160161601716018160191602016021160221602316024160251602616027160281602916030160311603216033160341603516036160371603816039160401604116042160431604416045160461604716048160491605016051160521605316054160551605616057160581605916060160611606216063160641606516066160671606816069160701607116072160731607416075160761607716078160791608016081160821608316084160851608616087160881608916090160911609216093160941609516096160971609816099161001610116102161031610416105161061610716108161091611016111161121611316114161151611616117161181611916120161211612216123161241612516126161271612816129161301613116132161331613416135161361613716138161391614016141161421614316144161451614616147161481614916150161511615216153161541615516156161571615816159161601616116162161631616416165161661616716168161691617016171161721617316174161751617616177161781617916180161811618216183161841618516186161871618816189161901619116192161931619416195161961619716198161991620016201162021620316204162051620616207162081620916210162111621216213162141621516216162171621816219162201622116222162231622416225162261622716228162291623016231162321623316234162351623616237162381623916240162411624216243162441624516246162471624816249162501625116252162531625416255162561625716258162591626016261162621626316264162651626616267162681626916270162711627216273162741627516276162771627816279162801628116282162831628416285162861628716288162891629016291162921629316294162951629616297162981629916300163011630216303163041630516306163071630816309163101631116312163131631416315163161631716318163191632016321163221632316324163251632616327163281632916330163311633216333163341633516336163371633816339163401634116342163431634416345163461634716348163491635016351163521635316354163551635616357163581635916360163611636216363163641636516366163671636816369163701637116372163731637416375163761637716378163791638016381163821638316384163851638616387163881638916390163911639216393163941639516396163971639816399164001640116402164031640416405164061640716408164091641016411164121641316414164151641616417164181641916420164211642216423164241642516426164271642816429164301643116432164331643416435164361643716438164391644016441164421644316444164451644616447164481644916450164511645216453164541645516456164571645816459164601646116462164631646416465164661646716468164691647016471164721647316474164751647616477164781647916480164811648216483164841648516486164871648816489164901649116492164931649416495164961649716498164991650016501165021650316504165051650616507165081650916510165111651216513165141651516516165171651816519165201652116522165231652416525165261652716528165291653016531165321653316534165351653616537165381653916540165411654216543165441654516546165471654816549165501655116552165531655416555165561655716558165591656016561165621656316564165651656616567165681656916570165711657216573165741657516576165771657816579165801658116582165831658416585165861658716588165891659016591165921659316594165951659616597165981659916600166011660216603166041660516606166071660816609166101661116612166131661416615166161661716618166191662016621166221662316624166251662616627166281662916630166311663216633166341663516636166371663816639166401664116642166431664416645166461664716648166491665016651166521665316654166551665616657166581665916660166611666216663166641666516666166671666816669166701667116672166731667416675166761667716678166791668016681166821668316684166851668616687166881668916690166911669216693166941669516696166971669816699167001670116702167031670416705167061670716708167091671016711167121671316714167151671616717167181671916720167211672216723167241672516726167271672816729167301673116732167331673416735167361673716738167391674016741167421674316744167451674616747167481674916750167511675216753167541675516756167571675816759167601676116762167631676416765167661676716768167691677016771167721677316774167751677616777167781677916780167811678216783167841678516786167871678816789167901679116792167931679416795167961679716798167991680016801168021680316804168051680616807168081680916810168111681216813168141681516816168171681816819168201682116822168231682416825168261682716828168291683016831168321683316834168351683616837168381683916840168411684216843168441684516846168471684816849168501685116852168531685416855168561685716858168591686016861168621686316864168651686616867168681686916870168711687216873168741687516876168771687816879168801688116882168831688416885168861688716888168891689016891168921689316894168951689616897168981689916900169011690216903169041690516906169071690816909169101691116912169131691416915169161691716918169191692016921169221692316924169251692616927169281692916930169311693216933169341693516936169371693816939169401694116942169431694416945169461694716948169491695016951169521695316954169551695616957169581695916960169611696216963169641696516966169671696816969169701697116972169731697416975169761697716978169791698016981169821698316984169851698616987169881698916990169911699216993169941699516996169971699816999170001700117002170031700417005170061700717008170091701017011170121701317014170151701617017170181701917020170211702217023170241702517026170271702817029170301703117032170331703417035170361703717038170391704017041170421704317044170451704617047170481704917050170511705217053170541705517056170571705817059170601706117062170631706417065170661706717068170691707017071170721707317074170751707617077170781707917080170811708217083170841708517086170871708817089170901709117092170931709417095170961709717098170991710017101171021710317104171051710617107171081710917110171111711217113171141711517116171171711817119171201712117122171231712417125171261712717128171291713017131171321713317134171351713617137171381713917140171411714217143171441714517146171471714817149171501715117152171531715417155171561715717158171591716017161171621716317164171651716617167171681716917170171711717217173171741717517176171771717817179171801718117182171831718417185171861718717188171891719017191171921719317194171951719617197171981719917200172011720217203172041720517206172071720817209172101721117212172131721417215172161721717218172191722017221172221722317224172251722617227172281722917230172311723217233172341723517236172371723817239172401724117242172431724417245172461724717248172491725017251172521725317254172551725617257172581725917260172611726217263172641726517266172671726817269172701727117272172731727417275172761727717278172791728017281172821728317284172851728617287172881728917290172911729217293172941729517296172971729817299173001730117302173031730417305173061730717308173091731017311173121731317314173151731617317173181731917320173211732217323173241732517326173271732817329173301733117332173331733417335173361733717338173391734017341173421734317344173451734617347173481734917350173511735217353173541735517356173571735817359173601736117362173631736417365173661736717368173691737017371173721737317374173751737617377173781737917380173811738217383173841738517386173871738817389173901739117392173931739417395173961739717398173991740017401174021740317404174051740617407174081740917410174111741217413174141741517416174171741817419174201742117422174231742417425174261742717428174291743017431174321743317434174351743617437174381743917440174411744217443174441744517446174471744817449174501745117452174531745417455174561745717458174591746017461174621746317464174651746617467174681746917470174711747217473174741747517476174771747817479174801748117482174831748417485174861748717488174891749017491174921749317494174951749617497174981749917500175011750217503175041750517506175071750817509175101751117512175131751417515175161751717518175191752017521175221752317524175251752617527175281752917530175311753217533175341753517536175371753817539175401754117542175431754417545175461754717548175491755017551175521755317554175551755617557175581755917560175611756217563175641756517566175671756817569175701757117572175731757417575175761757717578175791758017581175821758317584175851758617587175881758917590175911759217593175941759517596175971759817599176001760117602176031760417605176061760717608176091761017611176121761317614176151761617617176181761917620176211762217623176241762517626176271762817629176301763117632176331763417635176361763717638176391764017641176421764317644176451764617647176481764917650176511765217653176541765517656176571765817659176601766117662176631766417665176661766717668176691767017671176721767317674176751767617677176781767917680176811768217683176841768517686176871768817689176901769117692176931769417695176961769717698176991770017701177021770317704177051770617707177081770917710177111771217713177141771517716177171771817719177201772117722177231772417725177261772717728177291773017731177321773317734177351773617737177381773917740177411774217743177441774517746177471774817749177501775117752177531775417755177561775717758177591776017761177621776317764177651776617767177681776917770177711777217773177741777517776177771777817779177801778117782177831778417785177861778717788177891779017791177921779317794177951779617797177981779917800178011780217803178041780517806178071780817809178101781117812178131781417815178161781717818178191782017821178221782317824178251782617827178281782917830178311783217833178341783517836178371783817839178401784117842178431784417845178461784717848178491785017851178521785317854178551785617857178581785917860178611786217863178641786517866178671786817869178701787117872178731787417875178761787717878178791788017881178821788317884178851788617887178881788917890178911789217893178941789517896178971789817899179001790117902179031790417905179061790717908179091791017911179121791317914179151791617917179181791917920179211792217923179241792517926179271792817929179301793117932179331793417935179361793717938179391794017941179421794317944179451794617947179481794917950179511795217953179541795517956179571795817959179601796117962179631796417965179661796717968179691797017971179721797317974179751797617977179781797917980179811798217983179841798517986179871798817989179901799117992179931799417995179961799717998179991800018001180021800318004180051800618007180081800918010180111801218013180141801518016180171801818019180201802118022180231802418025180261802718028180291803018031180321803318034180351803618037180381803918040180411804218043180441804518046180471804818049180501805118052180531805418055180561805718058180591806018061180621806318064180651806618067180681806918070180711807218073180741807518076180771807818079180801808118082180831808418085180861808718088180891809018091180921809318094180951809618097180981809918100181011810218103181041810518106181071810818109181101811118112181131811418115181161811718118181191812018121181221812318124181251812618127181281812918130181311813218133181341813518136181371813818139181401814118142181431814418145181461814718148181491815018151181521815318154181551815618157181581815918160181611816218163181641816518166181671816818169181701817118172181731817418175181761817718178181791818018181181821818318184181851818618187181881818918190181911819218193181941819518196181971819818199182001820118202182031820418205182061820718208182091821018211182121821318214182151821618217182181821918220182211822218223182241822518226182271822818229182301823118232182331823418235182361823718238182391824018241182421824318244182451824618247182481824918250182511825218253182541825518256182571825818259182601826118262182631826418265182661826718268182691827018271182721827318274182751827618277182781827918280182811828218283182841828518286182871828818289182901829118292182931829418295182961829718298182991830018301183021830318304183051830618307183081830918310183111831218313183141831518316183171831818319183201832118322183231832418325183261832718328183291833018331183321833318334183351833618337183381833918340183411834218343183441834518346183471834818349183501835118352183531835418355183561835718358183591836018361183621836318364183651836618367183681836918370183711837218373183741837518376183771837818379183801838118382183831838418385183861838718388183891839018391183921839318394183951839618397183981839918400184011840218403184041840518406184071840818409184101841118412184131841418415184161841718418184191842018421184221842318424184251842618427184281842918430184311843218433184341843518436184371843818439184401844118442184431844418445184461844718448184491845018451184521845318454184551845618457184581845918460184611846218463184641846518466184671846818469184701847118472184731847418475184761847718478184791848018481184821848318484184851848618487184881848918490184911849218493184941849518496184971849818499185001850118502185031850418505185061850718508185091851018511185121851318514185151851618517185181851918520185211852218523185241852518526185271852818529185301853118532185331853418535185361853718538185391854018541185421854318544185451854618547185481854918550185511855218553185541855518556185571855818559185601856118562185631856418565185661856718568185691857018571185721857318574185751857618577185781857918580185811858218583185841858518586185871858818589185901859118592185931859418595185961859718598185991860018601186021860318604186051860618607186081860918610186111861218613186141861518616186171861818619186201862118622186231862418625186261862718628186291863018631186321863318634186351863618637186381863918640186411864218643186441864518646186471864818649186501865118652186531865418655186561865718658186591866018661186621866318664186651866618667186681866918670186711867218673186741867518676186771867818679186801868118682186831868418685186861868718688186891869018691186921869318694186951869618697186981869918700187011870218703187041870518706187071870818709187101871118712187131871418715187161871718718187191872018721187221872318724187251872618727187281872918730187311873218733187341873518736187371873818739187401874118742187431874418745187461874718748187491875018751187521875318754187551875618757187581875918760187611876218763187641876518766187671876818769187701877118772187731877418775187761877718778187791878018781187821878318784187851878618787187881878918790187911879218793187941879518796187971879818799188001880118802188031880418805188061880718808188091881018811188121881318814188151881618817188181881918820188211882218823188241882518826188271882818829188301883118832188331883418835188361883718838188391884018841188421884318844188451884618847188481884918850188511885218853188541885518856188571885818859188601886118862188631886418865188661886718868188691887018871188721887318874188751887618877188781887918880188811888218883188841888518886188871888818889188901889118892188931889418895188961889718898188991890018901189021890318904189051890618907189081890918910189111891218913189141891518916189171891818919189201892118922189231892418925189261892718928189291893018931189321893318934189351893618937189381893918940189411894218943189441894518946189471894818949189501895118952189531895418955189561895718958189591896018961189621896318964189651896618967189681896918970189711897218973189741897518976189771897818979189801898118982189831898418985189861898718988189891899018991189921899318994189951899618997189981899919000190011900219003190041900519006190071900819009190101901119012190131901419015190161901719018190191902019021190221902319024190251902619027190281902919030190311903219033190341903519036190371903819039190401904119042190431904419045190461904719048190491905019051190521905319054190551905619057190581905919060190611906219063190641906519066190671906819069190701907119072190731907419075190761907719078190791908019081190821908319084190851908619087190881908919090190911909219093190941909519096190971909819099191001910119102191031910419105191061910719108191091911019111191121911319114191151911619117191181911919120191211912219123191241912519126191271912819129191301913119132191331913419135191361913719138191391914019141191421914319144191451914619147191481914919150191511915219153191541915519156191571915819159191601916119162191631916419165191661916719168191691917019171191721917319174191751917619177191781917919180191811918219183191841918519186191871918819189191901919119192191931919419195191961919719198191991920019201192021920319204192051920619207192081920919210192111921219213192141921519216192171921819219192201922119222192231922419225192261922719228192291923019231192321923319234192351923619237192381923919240192411924219243192441924519246192471924819249192501925119252192531925419255192561925719258192591926019261192621926319264192651926619267192681926919270192711927219273192741927519276192771927819279192801928119282192831928419285192861928719288192891929019291192921929319294192951929619297192981929919300193011930219303193041930519306193071930819309193101931119312193131931419315193161931719318193191932019321193221932319324193251932619327193281932919330193311933219333193341933519336193371933819339193401934119342193431934419345193461934719348193491935019351193521935319354193551935619357193581935919360193611936219363193641936519366193671936819369193701937119372193731937419375193761937719378193791938019381193821938319384193851938619387193881938919390193911939219393193941939519396193971939819399194001940119402194031940419405194061940719408194091941019411194121941319414194151941619417194181941919420194211942219423194241942519426194271942819429194301943119432194331943419435194361943719438194391944019441194421944319444194451944619447194481944919450194511945219453194541945519456194571945819459194601946119462194631946419465194661946719468194691947019471194721947319474194751947619477194781947919480194811948219483194841948519486194871948819489194901949119492194931949419495194961949719498194991950019501195021950319504195051950619507195081950919510195111951219513195141951519516195171951819519195201952119522195231952419525195261952719528195291953019531195321953319534195351953619537195381953919540195411954219543195441954519546195471954819549195501955119552195531955419555195561955719558195591956019561195621956319564195651956619567195681956919570195711957219573195741957519576195771957819579195801958119582195831958419585195861958719588195891959019591195921959319594195951959619597195981959919600196011960219603196041960519606196071960819609196101961119612196131961419615196161961719618196191962019621196221962319624196251962619627196281962919630196311963219633196341963519636196371963819639196401964119642196431964419645196461964719648196491965019651196521965319654196551965619657196581965919660196611966219663196641966519666196671966819669196701967119672196731967419675196761967719678196791968019681196821968319684196851968619687196881968919690196911969219693196941969519696196971969819699197001970119702197031970419705197061970719708197091971019711197121971319714197151971619717197181971919720197211972219723197241972519726197271972819729197301973119732197331973419735197361973719738197391974019741197421974319744197451974619747197481974919750197511975219753197541975519756197571975819759197601976119762197631976419765197661976719768197691977019771197721977319774197751977619777197781977919780197811978219783197841978519786197871978819789197901979119792197931979419795197961979719798197991980019801198021980319804198051980619807198081980919810198111981219813198141981519816198171981819819198201982119822198231982419825198261982719828198291983019831198321983319834198351983619837198381983919840198411984219843198441984519846198471984819849198501985119852198531985419855198561985719858198591986019861198621986319864198651986619867198681986919870198711987219873198741987519876198771987819879198801988119882198831988419885198861988719888198891989019891198921989319894198951989619897198981989919900199011990219903199041990519906199071990819909199101991119912199131991419915199161991719918199191992019921199221992319924199251992619927199281992919930199311993219933199341993519936199371993819939199401994119942199431994419945199461994719948199491995019951199521995319954199551995619957199581995919960199611996219963199641996519966199671996819969199701997119972199731997419975199761997719978199791998019981199821998319984199851998619987199881998919990199911999219993199941999519996199971999819999200002000120002200032000420005200062000720008200092001020011200122001320014200152001620017200182001920020200212002220023200242002520026200272002820029200302003120032200332003420035200362003720038200392004020041200422004320044200452004620047200482004920050200512005220053200542005520056200572005820059200602006120062200632006420065200662006720068200692007020071200722007320074200752007620077200782007920080200812008220083200842008520086200872008820089200902009120092200932009420095200962009720098200992010020101201022010320104201052010620107201082010920110201112011220113201142011520116201172011820119201202012120122201232012420125201262012720128201292013020131201322013320134201352013620137201382013920140201412014220143201442014520146201472014820149201502015120152201532015420155201562015720158201592016020161201622016320164201652016620167201682016920170201712017220173201742017520176201772017820179201802018120182201832018420185201862018720188201892019020191201922019320194201952019620197201982019920200202012020220203202042020520206202072020820209202102021120212202132021420215202162021720218202192022020221202222022320224202252022620227202282022920230202312023220233202342023520236202372023820239202402024120242202432024420245202462024720248202492025020251202522025320254202552025620257202582025920260202612026220263202642026520266202672026820269202702027120272202732027420275202762027720278202792028020281202822028320284202852028620287202882028920290202912029220293202942029520296202972029820299203002030120302203032030420305203062030720308203092031020311203122031320314203152031620317203182031920320203212032220323203242032520326203272032820329203302033120332203332033420335203362033720338203392034020341203422034320344203452034620347203482034920350203512035220353203542035520356203572035820359203602036120362203632036420365203662036720368203692037020371203722037320374203752037620377203782037920380203812038220383203842038520386203872038820389203902039120392203932039420395203962039720398203992040020401204022040320404204052040620407204082040920410204112041220413204142041520416204172041820419204202042120422204232042420425204262042720428204292043020431204322043320434204352043620437204382043920440204412044220443204442044520446204472044820449204502045120452204532045420455204562045720458204592046020461204622046320464204652046620467204682046920470204712047220473204742047520476204772047820479204802048120482204832048420485204862048720488204892049020491204922049320494204952049620497204982049920500205012050220503205042050520506205072050820509205102051120512205132051420515205162051720518205192052020521205222052320524205252052620527205282052920530205312053220533205342053520536205372053820539205402054120542205432054420545205462054720548205492055020551205522055320554205552055620557205582055920560205612056220563205642056520566205672056820569205702057120572205732057420575205762057720578205792058020581205822058320584205852058620587205882058920590205912059220593205942059520596205972059820599206002060120602206032060420605206062060720608206092061020611206122061320614206152061620617206182061920620206212062220623206242062520626206272062820629206302063120632206332063420635206362063720638206392064020641206422064320644206452064620647206482064920650206512065220653206542065520656206572065820659206602066120662206632066420665206662066720668206692067020671206722067320674206752067620677206782067920680206812068220683206842068520686206872068820689206902069120692206932069420695206962069720698206992070020701207022070320704207052070620707207082070920710207112071220713207142071520716207172071820719207202072120722207232072420725207262072720728207292073020731207322073320734207352073620737207382073920740207412074220743207442074520746207472074820749207502075120752207532075420755207562075720758207592076020761207622076320764207652076620767207682076920770207712077220773207742077520776207772077820779207802078120782207832078420785207862078720788207892079020791207922079320794207952079620797207982079920800208012080220803208042080520806208072080820809208102081120812208132081420815208162081720818208192082020821208222082320824208252082620827208282082920830208312083220833208342083520836208372083820839208402084120842208432084420845208462084720848208492085020851208522085320854208552085620857208582085920860208612086220863208642086520866208672086820869208702087120872208732087420875208762087720878208792088020881208822088320884208852088620887208882088920890208912089220893208942089520896208972089820899209002090120902209032090420905209062090720908209092091020911209122091320914209152091620917209182091920920209212092220923209242092520926209272092820929209302093120932209332093420935209362093720938209392094020941209422094320944209452094620947209482094920950209512095220953209542095520956209572095820959209602096120962209632096420965209662096720968209692097020971209722097320974209752097620977209782097920980209812098220983209842098520986209872098820989209902099120992209932099420995209962099720998209992100021001210022100321004210052100621007210082100921010210112101221013210142101521016210172101821019210202102121022210232102421025210262102721028210292103021031210322103321034210352103621037210382103921040210412104221043210442104521046210472104821049210502105121052210532105421055210562105721058210592106021061210622106321064210652106621067210682106921070210712107221073210742107521076210772107821079210802108121082210832108421085210862108721088210892109021091210922109321094210952109621097210982109921100211012110221103211042110521106211072110821109211102111121112211132111421115211162111721118211192112021121211222112321124211252112621127211282112921130211312113221133211342113521136211372113821139211402114121142211432114421145211462114721148211492115021151211522115321154211552115621157211582115921160211612116221163211642116521166211672116821169211702117121172211732117421175211762117721178211792118021181211822118321184211852118621187211882118921190211912119221193211942119521196211972119821199212002120121202212032120421205212062120721208212092121021211212122121321214212152121621217212182121921220212212122221223212242122521226212272122821229212302123121232212332123421235212362123721238212392124021241212422124321244212452124621247212482124921250212512125221253212542125521256212572125821259212602126121262212632126421265212662126721268212692127021271212722127321274212752127621277212782127921280212812128221283212842128521286212872128821289212902129121292212932129421295212962129721298212992130021301213022130321304213052130621307213082130921310213112131221313213142131521316213172131821319213202132121322213232132421325213262132721328213292133021331213322133321334213352133621337213382133921340213412134221343213442134521346213472134821349213502135121352213532135421355213562135721358213592136021361213622136321364213652136621367213682136921370213712137221373213742137521376213772137821379213802138121382213832138421385213862138721388213892139021391213922139321394213952139621397213982139921400214012140221403214042140521406214072140821409214102141121412214132141421415214162141721418214192142021421214222142321424214252142621427214282142921430214312143221433214342143521436214372143821439214402144121442214432144421445214462144721448214492145021451214522145321454214552145621457214582145921460214612146221463214642146521466214672146821469214702147121472214732147421475214762147721478214792148021481214822148321484214852148621487214882148921490214912149221493214942149521496214972149821499215002150121502215032150421505215062150721508215092151021511215122151321514215152151621517215182151921520215212152221523215242152521526215272152821529215302153121532215332153421535215362153721538215392154021541215422154321544215452154621547215482154921550215512155221553215542155521556215572155821559215602156121562215632156421565215662156721568215692157021571215722157321574215752157621577215782157921580215812158221583215842158521586215872158821589215902159121592215932159421595215962159721598215992160021601216022160321604216052160621607216082160921610216112161221613216142161521616216172161821619216202162121622216232162421625216262162721628216292163021631216322163321634216352163621637216382163921640216412164221643216442164521646216472164821649216502165121652216532165421655216562165721658216592166021661216622166321664216652166621667216682166921670216712167221673216742167521676216772167821679216802168121682216832168421685216862168721688216892169021691216922169321694216952169621697216982169921700217012170221703217042170521706217072170821709217102171121712217132171421715217162171721718217192172021721217222172321724217252172621727217282172921730217312173221733217342173521736217372173821739217402174121742217432174421745217462174721748217492175021751217522175321754217552175621757217582175921760217612176221763217642176521766217672176821769217702177121772217732177421775217762177721778217792178021781217822178321784217852178621787217882178921790217912179221793217942179521796217972179821799218002180121802218032180421805218062180721808218092181021811218122181321814218152181621817218182181921820218212182221823218242182521826218272182821829218302183121832218332183421835218362183721838218392184021841218422184321844218452184621847218482184921850218512185221853218542185521856218572185821859218602186121862218632186421865218662186721868218692187021871218722187321874218752187621877218782187921880218812188221883218842188521886218872188821889218902189121892218932189421895218962189721898218992190021901219022190321904219052190621907219082190921910219112191221913219142191521916219172191821919219202192121922219232192421925219262192721928219292193021931219322193321934219352193621937219382193921940219412194221943219442194521946219472194821949219502195121952219532195421955219562195721958219592196021961219622196321964219652196621967219682196921970219712197221973219742197521976219772197821979219802198121982219832198421985219862198721988219892199021991219922199321994219952199621997219982199922000220012200222003220042200522006220072200822009220102201122012220132201422015220162201722018220192202022021220222202322024220252202622027220282202922030220312203222033220342203522036220372203822039220402204122042220432204422045220462204722048220492205022051220522205322054220552205622057220582205922060220612206222063220642206522066220672206822069220702207122072220732207422075220762207722078220792208022081220822208322084220852208622087220882208922090220912209222093220942209522096220972209822099221002210122102221032210422105221062210722108221092211022111221122211322114221152211622117221182211922120221212212222123221242212522126221272212822129221302213122132221332213422135221362213722138221392214022141221422214322144221452214622147221482214922150221512215222153221542215522156221572215822159221602216122162221632216422165221662216722168221692217022171221722217322174221752217622177221782217922180221812218222183221842218522186221872218822189221902219122192221932219422195221962219722198221992220022201222022220322204222052220622207222082220922210222112221222213222142221522216222172221822219222202222122222222232222422225222262222722228222292223022231222322223322234222352223622237222382223922240222412224222243222442224522246222472224822249222502225122252222532225422255222562225722258222592226022261222622226322264222652226622267222682226922270222712227222273222742227522276222772227822279222802228122282222832228422285222862228722288222892229022291222922229322294222952229622297222982229922300223012230222303223042230522306223072230822309223102231122312223132231422315223162231722318223192232022321223222232322324223252232622327223282232922330223312233222333223342233522336223372233822339223402234122342223432234422345223462234722348223492235022351223522235322354223552235622357223582235922360223612236222363223642236522366223672236822369223702237122372223732237422375223762237722378223792238022381223822238322384223852238622387223882238922390223912239222393223942239522396223972239822399224002240122402224032240422405224062240722408224092241022411224122241322414224152241622417224182241922420224212242222423224242242522426224272242822429224302243122432224332243422435224362243722438224392244022441224422244322444224452244622447224482244922450224512245222453224542245522456224572245822459224602246122462224632246422465224662246722468224692247022471224722247322474224752247622477224782247922480224812248222483224842248522486224872248822489224902249122492224932249422495224962249722498224992250022501225022250322504225052250622507225082250922510225112251222513225142251522516225172251822519225202252122522225232252422525225262252722528225292253022531225322253322534225352253622537225382253922540225412254222543225442254522546225472254822549225502255122552225532255422555225562255722558225592256022561225622256322564225652256622567225682256922570225712257222573225742257522576225772257822579225802258122582225832258422585225862258722588225892259022591225922259322594225952259622597225982259922600226012260222603226042260522606226072260822609226102261122612226132261422615226162261722618226192262022621226222262322624226252262622627226282262922630226312263222633226342263522636226372263822639226402264122642226432264422645226462264722648226492265022651226522265322654226552265622657226582265922660226612266222663226642266522666226672266822669226702267122672226732267422675226762267722678226792268022681226822268322684226852268622687226882268922690226912269222693226942269522696226972269822699227002270122702227032270422705227062270722708227092271022711227122271322714227152271622717227182271922720227212272222723227242272522726227272272822729227302273122732227332273422735227362273722738227392274022741227422274322744227452274622747227482274922750227512275222753227542275522756227572275822759227602276122762227632276422765227662276722768227692277022771227722277322774227752277622777227782277922780227812278222783227842278522786227872278822789227902279122792227932279422795227962279722798227992280022801228022280322804228052280622807228082280922810228112281222813228142281522816228172281822819228202282122822228232282422825228262282722828228292283022831228322283322834228352283622837228382283922840228412284222843228442284522846228472284822849228502285122852228532285422855228562285722858228592286022861228622286322864228652286622867228682286922870228712287222873228742287522876228772287822879228802288122882228832288422885228862288722888228892289022891228922289322894228952289622897228982289922900229012290222903229042290522906229072290822909229102291122912229132291422915229162291722918229192292022921229222292322924229252292622927229282292922930229312293222933229342293522936229372293822939229402294122942229432294422945229462294722948229492295022951229522295322954229552295622957229582295922960229612296222963229642296522966229672296822969229702297122972229732297422975229762297722978229792298022981229822298322984229852298622987229882298922990229912299222993229942299522996229972299822999230002300123002230032300423005230062300723008230092301023011230122301323014230152301623017230182301923020230212302223023230242302523026230272302823029230302303123032230332303423035230362303723038230392304023041230422304323044230452304623047230482304923050230512305223053230542305523056230572305823059230602306123062230632306423065230662306723068230692307023071230722307323074230752307623077230782307923080230812308223083230842308523086230872308823089230902309123092230932309423095230962309723098230992310023101231022310323104231052310623107231082310923110231112311223113231142311523116231172311823119231202312123122231232312423125231262312723128231292313023131231322313323134231352313623137231382313923140231412314223143231442314523146231472314823149231502315123152231532315423155231562315723158231592316023161231622316323164231652316623167231682316923170231712317223173231742317523176231772317823179231802318123182231832318423185231862318723188231892319023191231922319323194231952319623197231982319923200232012320223203232042320523206232072320823209232102321123212232132321423215232162321723218232192322023221232222322323224232252322623227232282322923230232312323223233232342323523236232372323823239232402324123242232432324423245232462324723248232492325023251232522325323254232552325623257232582325923260232612326223263232642326523266232672326823269232702327123272232732327423275232762327723278232792328023281232822328323284232852328623287232882328923290232912329223293232942329523296232972329823299233002330123302233032330423305233062330723308233092331023311233122331323314233152331623317233182331923320233212332223323233242332523326233272332823329233302333123332233332333423335233362333723338233392334023341233422334323344233452334623347233482334923350233512335223353233542335523356233572335823359233602336123362233632336423365233662336723368233692337023371233722337323374233752337623377233782337923380233812338223383233842338523386233872338823389233902339123392233932339423395233962339723398233992340023401234022340323404234052340623407234082340923410234112341223413234142341523416234172341823419234202342123422234232342423425234262342723428234292343023431234322343323434234352343623437234382343923440234412344223443234442344523446234472344823449234502345123452234532345423455234562345723458234592346023461234622346323464234652346623467234682346923470234712347223473234742347523476234772347823479234802348123482234832348423485234862348723488234892349023491234922349323494234952349623497234982349923500235012350223503235042350523506235072350823509235102351123512235132351423515235162351723518235192352023521235222352323524235252352623527235282352923530235312353223533235342353523536235372353823539235402354123542235432354423545235462354723548235492355023551235522355323554235552355623557235582355923560235612356223563235642356523566235672356823569235702357123572235732357423575235762357723578235792358023581235822358323584235852358623587235882358923590235912359223593235942359523596235972359823599236002360123602236032360423605236062360723608236092361023611236122361323614236152361623617236182361923620236212362223623236242362523626236272362823629236302363123632236332363423635236362363723638236392364023641236422364323644236452364623647236482364923650236512365223653236542365523656236572365823659236602366123662236632366423665236662366723668236692367023671236722367323674236752367623677236782367923680236812368223683236842368523686236872368823689236902369123692236932369423695236962369723698236992370023701237022370323704237052370623707237082370923710237112371223713237142371523716237172371823719237202372123722237232372423725237262372723728237292373023731237322373323734237352373623737237382373923740237412374223743237442374523746237472374823749237502375123752237532375423755237562375723758237592376023761237622376323764237652376623767237682376923770237712377223773237742377523776237772377823779237802378123782237832378423785237862378723788237892379023791237922379323794237952379623797237982379923800238012380223803238042380523806238072380823809238102381123812238132381423815238162381723818238192382023821238222382323824238252382623827238282382923830238312383223833238342383523836238372383823839238402384123842238432384423845238462384723848238492385023851238522385323854238552385623857238582385923860238612386223863238642386523866238672386823869238702387123872238732387423875238762387723878238792388023881238822388323884238852388623887238882388923890238912389223893238942389523896238972389823899239002390123902239032390423905239062390723908239092391023911239122391323914239152391623917239182391923920239212392223923239242392523926239272392823929239302393123932239332393423935239362393723938239392394023941239422394323944239452394623947239482394923950239512395223953239542395523956239572395823959239602396123962239632396423965239662396723968239692397023971239722397323974239752397623977239782397923980239812398223983239842398523986239872398823989239902399123992239932399423995239962399723998239992400024001240022400324004240052400624007240082400924010240112401224013240142401524016240172401824019240202402124022240232402424025240262402724028240292403024031240322403324034240352403624037240382403924040240412404224043240442404524046240472404824049240502405124052240532405424055240562405724058240592406024061240622406324064240652406624067240682406924070240712407224073240742407524076240772407824079240802408124082240832408424085240862408724088240892409024091240922409324094240952409624097240982409924100241012410224103241042410524106241072410824109241102411124112241132411424115241162411724118241192412024121241222412324124241252412624127241282412924130241312413224133241342413524136241372413824139241402414124142241432414424145241462414724148241492415024151241522415324154241552415624157241582415924160241612416224163241642416524166241672416824169241702417124172241732417424175241762417724178241792418024181241822418324184241852418624187241882418924190241912419224193241942419524196241972419824199242002420124202242032420424205242062420724208242092421024211242122421324214242152421624217242182421924220242212422224223242242422524226242272422824229242302423124232242332423424235242362423724238242392424024241242422424324244242452424624247242482424924250242512425224253242542425524256242572425824259242602426124262242632426424265242662426724268242692427024271242722427324274242752427624277242782427924280242812428224283242842428524286242872428824289242902429124292242932429424295242962429724298242992430024301243022430324304243052430624307243082430924310243112431224313243142431524316243172431824319243202432124322243232432424325243262432724328243292433024331243322433324334243352433624337243382433924340243412434224343243442434524346243472434824349243502435124352243532435424355243562435724358243592436024361243622436324364243652436624367243682436924370243712437224373243742437524376243772437824379243802438124382243832438424385243862438724388243892439024391243922439324394243952439624397243982439924400244012440224403244042440524406244072440824409244102441124412244132441424415244162441724418244192442024421244222442324424244252442624427244282442924430244312443224433244342443524436244372443824439244402444124442244432444424445244462444724448244492445024451244522445324454244552445624457244582445924460244612446224463244642446524466244672446824469244702447124472244732447424475244762447724478244792448024481244822448324484244852448624487244882448924490244912449224493244942449524496244972449824499245002450124502245032450424505245062450724508245092451024511245122451324514245152451624517245182451924520245212452224523245242452524526245272452824529245302453124532245332453424535245362453724538245392454024541245422454324544245452454624547245482454924550245512455224553245542455524556245572455824559245602456124562245632456424565245662456724568245692457024571245722457324574245752457624577245782457924580245812458224583245842458524586245872458824589245902459124592245932459424595245962459724598245992460024601246022460324604246052460624607246082460924610246112461224613246142461524616246172461824619246202462124622246232462424625246262462724628246292463024631246322463324634246352463624637246382463924640246412464224643246442464524646246472464824649246502465124652246532465424655246562465724658246592466024661246622466324664246652466624667246682466924670246712467224673246742467524676246772467824679246802468124682246832468424685246862468724688246892469024691246922469324694246952469624697246982469924700247012470224703247042470524706247072470824709247102471124712247132471424715247162471724718247192472024721247222472324724247252472624727247282472924730247312473224733247342473524736247372473824739247402474124742247432474424745247462474724748247492475024751247522475324754247552475624757247582475924760247612476224763247642476524766247672476824769247702477124772247732477424775247762477724778247792478024781247822478324784247852478624787247882478924790247912479224793247942479524796247972479824799248002480124802248032480424805248062480724808248092481024811248122481324814248152481624817248182481924820248212482224823248242482524826248272482824829248302483124832248332483424835248362483724838248392484024841248422484324844248452484624847248482484924850248512485224853248542485524856248572485824859248602486124862248632486424865248662486724868248692487024871248722487324874248752487624877248782487924880248812488224883248842488524886248872488824889248902489124892248932489424895248962489724898248992490024901249022490324904249052490624907249082490924910249112491224913249142491524916249172491824919249202492124922249232492424925249262492724928249292493024931249322493324934249352493624937249382493924940249412494224943249442494524946249472494824949249502495124952249532495424955249562495724958249592496024961249622496324964249652496624967249682496924970249712497224973249742497524976249772497824979249802498124982249832498424985249862498724988249892499024991249922499324994249952499624997249982499925000250012500225003250042500525006250072500825009250102501125012250132501425015250162501725018250192502025021250222502325024250252502625027250282502925030250312503225033250342503525036250372503825039250402504125042250432504425045250462504725048250492505025051250522505325054250552505625057250582505925060250612506225063250642506525066250672506825069250702507125072250732507425075250762507725078250792508025081250822508325084250852508625087250882508925090250912509225093250942509525096250972509825099251002510125102251032510425105251062510725108251092511025111251122511325114251152511625117251182511925120251212512225123251242512525126251272512825129251302513125132251332513425135251362513725138251392514025141251422514325144251452514625147251482514925150251512515225153251542515525156251572515825159251602516125162251632516425165251662516725168251692517025171251722517325174251752517625177251782517925180251812518225183251842518525186251872518825189251902519125192251932519425195251962519725198251992520025201252022520325204252052520625207252082520925210252112521225213252142521525216252172521825219252202522125222252232522425225252262522725228252292523025231252322523325234252352523625237252382523925240252412524225243252442524525246252472524825249252502525125252252532525425255252562525725258252592526025261252622526325264252652526625267252682526925270252712527225273252742527525276252772527825279252802528125282252832528425285252862528725288252892529025291252922529325294252952529625297252982529925300253012530225303253042530525306253072530825309253102531125312253132531425315253162531725318253192532025321253222532325324253252532625327253282532925330253312533225333253342533525336253372533825339253402534125342253432534425345253462534725348253492535025351253522535325354253552535625357253582535925360253612536225363253642536525366253672536825369253702537125372253732537425375253762537725378253792538025381253822538325384253852538625387253882538925390253912539225393253942539525396253972539825399254002540125402254032540425405254062540725408254092541025411254122541325414254152541625417254182541925420254212542225423254242542525426254272542825429254302543125432254332543425435254362543725438254392544025441254422544325444254452544625447254482544925450254512545225453254542545525456254572545825459254602546125462254632546425465254662546725468254692547025471254722547325474254752547625477254782547925480254812548225483254842548525486254872548825489254902549125492254932549425495254962549725498254992550025501255022550325504255052550625507255082550925510255112551225513255142551525516255172551825519255202552125522255232552425525255262552725528255292553025531255322553325534255352553625537255382553925540255412554225543255442554525546255472554825549255502555125552255532555425555255562555725558255592556025561255622556325564255652556625567255682556925570255712557225573255742557525576255772557825579255802558125582255832558425585255862558725588255892559025591255922559325594255952559625597255982559925600256012560225603256042560525606256072560825609256102561125612256132561425615256162561725618256192562025621256222562325624256252562625627256282562925630256312563225633256342563525636256372563825639256402564125642256432564425645256462564725648256492565025651256522565325654256552565625657256582565925660256612566225663256642566525666256672566825669256702567125672256732567425675256762567725678256792568025681256822568325684256852568625687256882568925690256912569225693256942569525696256972569825699257002570125702257032570425705257062570725708257092571025711257122571325714257152571625717257182571925720257212572225723257242572525726257272572825729257302573125732257332573425735257362573725738257392574025741257422574325744257452574625747257482574925750257512575225753257542575525756257572575825759257602576125762257632576425765257662576725768257692577025771257722577325774257752577625777257782577925780257812578225783257842578525786257872578825789257902579125792257932579425795257962579725798257992580025801258022580325804258052580625807258082580925810258112581225813258142581525816258172581825819258202582125822258232582425825258262582725828258292583025831258322583325834258352583625837258382583925840258412584225843258442584525846258472584825849258502585125852258532585425855258562585725858258592586025861258622586325864258652586625867258682586925870258712587225873258742587525876258772587825879258802588125882258832588425885258862588725888258892589025891258922589325894258952589625897258982589925900259012590225903259042590525906259072590825909259102591125912259132591425915259162591725918259192592025921259222592325924259252592625927259282592925930259312593225933259342593525936259372593825939259402594125942259432594425945259462594725948259492595025951259522595325954259552595625957259582595925960259612596225963259642596525966259672596825969259702597125972259732597425975259762597725978259792598025981259822598325984259852598625987259882598925990259912599225993259942599525996259972599825999260002600126002260032600426005260062600726008260092601026011260122601326014260152601626017260182601926020260212602226023260242602526026260272602826029260302603126032260332603426035260362603726038260392604026041260422604326044260452604626047260482604926050260512605226053260542605526056260572605826059260602606126062260632606426065260662606726068260692607026071260722607326074260752607626077260782607926080260812608226083260842608526086260872608826089260902609126092260932609426095260962609726098260992610026101261022610326104261052610626107261082610926110261112611226113261142611526116261172611826119261202612126122261232612426125261262612726128261292613026131261322613326134261352613626137261382613926140261412614226143261442614526146261472614826149261502615126152261532615426155261562615726158261592616026161261622616326164261652616626167261682616926170261712617226173261742617526176261772617826179261802618126182261832618426185261862618726188261892619026191261922619326194261952619626197261982619926200262012620226203262042620526206262072620826209262102621126212262132621426215262162621726218262192622026221262222622326224262252622626227262282622926230262312623226233262342623526236262372623826239262402624126242262432624426245262462624726248262492625026251262522625326254262552625626257262582625926260262612626226263262642626526266262672626826269262702627126272262732627426275262762627726278262792628026281262822628326284262852628626287262882628926290262912629226293262942629526296262972629826299263002630126302263032630426305263062630726308263092631026311263122631326314263152631626317263182631926320263212632226323263242632526326263272632826329263302633126332263332633426335263362633726338263392634026341263422634326344263452634626347263482634926350263512635226353263542635526356263572635826359263602636126362263632636426365263662636726368263692637026371263722637326374263752637626377263782637926380263812638226383263842638526386263872638826389263902639126392263932639426395263962639726398263992640026401264022640326404264052640626407264082640926410264112641226413264142641526416264172641826419264202642126422264232642426425264262642726428264292643026431264322643326434264352643626437264382643926440264412644226443264442644526446264472644826449264502645126452264532645426455264562645726458264592646026461264622646326464264652646626467264682646926470264712647226473264742647526476264772647826479264802648126482264832648426485264862648726488264892649026491264922649326494264952649626497264982649926500265012650226503265042650526506265072650826509265102651126512265132651426515265162651726518265192652026521265222652326524265252652626527265282652926530265312653226533265342653526536265372653826539265402654126542265432654426545265462654726548265492655026551265522655326554265552655626557265582655926560265612656226563265642656526566265672656826569265702657126572265732657426575265762657726578265792658026581265822658326584265852658626587265882658926590265912659226593265942659526596265972659826599266002660126602266032660426605266062660726608266092661026611266122661326614266152661626617266182661926620266212662226623266242662526626266272662826629266302663126632266332663426635266362663726638266392664026641266422664326644266452664626647266482664926650266512665226653266542665526656266572665826659266602666126662266632666426665266662666726668266692667026671266722667326674266752667626677266782667926680266812668226683266842668526686266872668826689266902669126692266932669426695266962669726698266992670026701267022670326704267052670626707267082670926710267112671226713267142671526716267172671826719267202672126722267232672426725267262672726728267292673026731267322673326734267352673626737267382673926740267412674226743267442674526746267472674826749267502675126752267532675426755267562675726758267592676026761267622676326764267652676626767267682676926770267712677226773267742677526776267772677826779267802678126782267832678426785267862678726788267892679026791267922679326794267952679626797267982679926800268012680226803268042680526806268072680826809268102681126812268132681426815268162681726818268192682026821268222682326824268252682626827268282682926830268312683226833268342683526836268372683826839268402684126842268432684426845268462684726848268492685026851268522685326854268552685626857268582685926860268612686226863268642686526866268672686826869268702687126872268732687426875268762687726878268792688026881268822688326884268852688626887268882688926890268912689226893268942689526896268972689826899269002690126902269032690426905269062690726908269092691026911269122691326914269152691626917269182691926920269212692226923269242692526926269272692826929269302693126932269332693426935269362693726938269392694026941269422694326944269452694626947269482694926950269512695226953269542695526956269572695826959269602696126962269632696426965269662696726968269692697026971269722697326974269752697626977269782697926980269812698226983269842698526986269872698826989269902699126992269932699426995269962699726998269992700027001270022700327004270052700627007270082700927010270112701227013270142701527016270172701827019270202702127022270232702427025270262702727028270292703027031270322703327034270352703627037270382703927040270412704227043270442704527046270472704827049270502705127052270532705427055270562705727058270592706027061270622706327064270652706627067270682706927070270712707227073270742707527076270772707827079270802708127082270832708427085270862708727088270892709027091270922709327094270952709627097270982709927100271012710227103271042710527106271072710827109271102711127112271132711427115271162711727118271192712027121271222712327124271252712627127271282712927130271312713227133271342713527136271372713827139271402714127142271432714427145271462714727148271492715027151271522715327154271552715627157271582715927160271612716227163271642716527166271672716827169271702717127172271732717427175271762717727178271792718027181271822718327184271852718627187271882718927190271912719227193271942719527196271972719827199272002720127202272032720427205272062720727208272092721027211272122721327214272152721627217272182721927220272212722227223272242722527226272272722827229272302723127232272332723427235272362723727238272392724027241272422724327244272452724627247272482724927250272512725227253272542725527256272572725827259272602726127262272632726427265272662726727268272692727027271272722727327274272752727627277272782727927280272812728227283272842728527286272872728827289272902729127292272932729427295272962729727298272992730027301273022730327304273052730627307273082730927310273112731227313273142731527316273172731827319273202732127322273232732427325273262732727328273292733027331273322733327334273352733627337273382733927340273412734227343273442734527346273472734827349273502735127352273532735427355273562735727358273592736027361273622736327364273652736627367273682736927370273712737227373273742737527376273772737827379273802738127382273832738427385273862738727388273892739027391273922739327394273952739627397273982739927400274012740227403274042740527406274072740827409274102741127412274132741427415274162741727418274192742027421274222742327424274252742627427274282742927430274312743227433274342743527436274372743827439274402744127442274432744427445274462744727448274492745027451274522745327454274552745627457274582745927460274612746227463274642746527466274672746827469274702747127472274732747427475274762747727478274792748027481274822748327484274852748627487274882748927490274912749227493274942749527496274972749827499275002750127502275032750427505275062750727508275092751027511275122751327514275152751627517275182751927520275212752227523275242752527526275272752827529275302753127532275332753427535275362753727538275392754027541275422754327544275452754627547275482754927550275512755227553275542755527556275572755827559275602756127562275632756427565275662756727568275692757027571275722757327574275752757627577275782757927580275812758227583275842758527586275872758827589275902759127592275932759427595275962759727598275992760027601276022760327604276052760627607276082760927610276112761227613276142761527616276172761827619276202762127622276232762427625276262762727628276292763027631276322763327634276352763627637276382763927640276412764227643276442764527646276472764827649276502765127652276532765427655276562765727658276592766027661276622766327664276652766627667276682766927670276712767227673276742767527676276772767827679276802768127682276832768427685276862768727688276892769027691276922769327694276952769627697276982769927700277012770227703277042770527706277072770827709277102771127712277132771427715277162771727718277192772027721277222772327724277252772627727277282772927730277312773227733277342773527736277372773827739277402774127742277432774427745277462774727748277492775027751277522775327754277552775627757277582775927760277612776227763277642776527766277672776827769277702777127772277732777427775277762777727778277792778027781277822778327784277852778627787277882778927790277912779227793277942779527796277972779827799278002780127802278032780427805278062780727808278092781027811278122781327814278152781627817278182781927820278212782227823278242782527826278272782827829278302783127832278332783427835278362783727838278392784027841278422784327844278452784627847278482784927850278512785227853278542785527856278572785827859278602786127862278632786427865278662786727868278692787027871278722787327874278752787627877278782787927880278812788227883278842788527886278872788827889278902789127892278932789427895278962789727898278992790027901279022790327904279052790627907279082790927910279112791227913279142791527916279172791827919279202792127922279232792427925279262792727928279292793027931279322793327934279352793627937279382793927940279412794227943279442794527946279472794827949279502795127952279532795427955279562795727958279592796027961279622796327964279652796627967279682796927970279712797227973279742797527976279772797827979279802798127982279832798427985279862798727988279892799027991279922799327994279952799627997279982799928000280012800228003280042800528006280072800828009280102801128012280132801428015280162801728018280192802028021280222802328024280252802628027280282802928030280312803228033280342803528036280372803828039280402804128042280432804428045280462804728048280492805028051280522805328054280552805628057280582805928060280612806228063280642806528066280672806828069280702807128072280732807428075280762807728078280792808028081280822808328084280852808628087280882808928090280912809228093280942809528096280972809828099281002810128102281032810428105281062810728108281092811028111281122811328114281152811628117281182811928120281212812228123281242812528126281272812828129281302813128132281332813428135281362813728138281392814028141281422814328144281452814628147281482814928150281512815228153281542815528156281572815828159281602816128162281632816428165281662816728168281692817028171281722817328174281752817628177281782817928180281812818228183281842818528186281872818828189281902819128192281932819428195281962819728198281992820028201282022820328204282052820628207282082820928210282112821228213282142821528216282172821828219282202822128222282232822428225282262822728228282292823028231282322823328234282352823628237282382823928240282412824228243282442824528246282472824828249282502825128252282532825428255282562825728258282592826028261282622826328264282652826628267282682826928270282712827228273282742827528276282772827828279282802828128282282832828428285282862828728288282892829028291282922829328294282952829628297282982829928300283012830228303283042830528306283072830828309283102831128312283132831428315283162831728318283192832028321283222832328324283252832628327283282832928330283312833228333283342833528336283372833828339283402834128342283432834428345283462834728348283492835028351283522835328354283552835628357283582835928360283612836228363283642836528366283672836828369283702837128372283732837428375283762837728378283792838028381283822838328384283852838628387283882838928390283912839228393283942839528396283972839828399284002840128402284032840428405284062840728408284092841028411284122841328414284152841628417284182841928420284212842228423284242842528426284272842828429284302843128432284332843428435284362843728438284392844028441284422844328444284452844628447284482844928450284512845228453284542845528456284572845828459284602846128462284632846428465284662846728468284692847028471284722847328474284752847628477284782847928480284812848228483284842848528486284872848828489284902849128492284932849428495284962849728498284992850028501285022850328504285052850628507285082850928510285112851228513285142851528516285172851828519285202852128522285232852428525285262852728528285292853028531285322853328534285352853628537285382853928540285412854228543285442854528546285472854828549285502855128552285532855428555285562855728558285592856028561285622856328564285652856628567285682856928570285712857228573285742857528576285772857828579285802858128582285832858428585285862858728588285892859028591285922859328594285952859628597285982859928600286012860228603286042860528606286072860828609286102861128612286132861428615286162861728618286192862028621286222862328624286252862628627286282862928630286312863228633286342863528636286372863828639286402864128642286432864428645286462864728648286492865028651286522865328654286552865628657286582865928660286612866228663286642866528666286672866828669286702867128672286732867428675286762867728678286792868028681286822868328684286852868628687286882868928690286912869228693286942869528696286972869828699287002870128702287032870428705287062870728708287092871028711287122871328714287152871628717287182871928720287212872228723287242872528726287272872828729287302873128732287332873428735287362873728738287392874028741287422874328744287452874628747287482874928750287512875228753287542875528756287572875828759287602876128762287632876428765287662876728768287692877028771287722877328774287752877628777287782877928780287812878228783287842878528786287872878828789287902879128792287932879428795287962879728798287992880028801288022880328804288052880628807288082880928810288112881228813288142881528816288172881828819288202882128822288232882428825288262882728828288292883028831288322883328834288352883628837288382883928840288412884228843288442884528846288472884828849288502885128852288532885428855288562885728858288592886028861288622886328864288652886628867288682886928870288712887228873288742887528876288772887828879288802888128882288832888428885288862888728888288892889028891288922889328894288952889628897288982889928900289012890228903289042890528906289072890828909289102891128912289132891428915289162891728918289192892028921289222892328924289252892628927289282892928930289312893228933289342893528936289372893828939289402894128942289432894428945289462894728948289492895028951289522895328954289552895628957289582895928960289612896228963289642896528966289672896828969289702897128972289732897428975289762897728978289792898028981289822898328984289852898628987289882898928990289912899228993289942899528996289972899828999290002900129002290032900429005290062900729008290092901029011290122901329014290152901629017290182901929020290212902229023290242902529026290272902829029290302903129032290332903429035290362903729038290392904029041290422904329044290452904629047290482904929050290512905229053290542905529056290572905829059290602906129062290632906429065290662906729068290692907029071290722907329074290752907629077290782907929080290812908229083290842908529086290872908829089290902909129092290932909429095290962909729098290992910029101291022910329104291052910629107291082910929110291112911229113291142911529116291172911829119291202912129122291232912429125291262912729128291292913029131291322913329134291352913629137291382913929140291412914229143291442914529146291472914829149291502915129152291532915429155291562915729158291592916029161291622916329164291652916629167291682916929170291712917229173291742917529176291772917829179291802918129182291832918429185291862918729188291892919029191291922919329194291952919629197291982919929200292012920229203292042920529206292072920829209292102921129212292132921429215292162921729218292192922029221292222922329224292252922629227292282922929230292312923229233292342923529236292372923829239292402924129242292432924429245292462924729248292492925029251292522925329254292552925629257292582925929260292612926229263292642926529266292672926829269292702927129272292732927429275292762927729278292792928029281292822928329284292852928629287292882928929290292912929229293292942929529296292972929829299293002930129302293032930429305293062930729308293092931029311293122931329314293152931629317293182931929320293212932229323293242932529326293272932829329293302933129332293332933429335293362933729338293392934029341293422934329344293452934629347293482934929350293512935229353293542935529356293572935829359293602936129362293632936429365293662936729368293692937029371293722937329374293752937629377293782937929380293812938229383293842938529386293872938829389293902939129392293932939429395293962939729398293992940029401294022940329404294052940629407294082940929410294112941229413294142941529416294172941829419294202942129422294232942429425294262942729428294292943029431294322943329434294352943629437294382943929440294412944229443294442944529446294472944829449294502945129452294532945429455294562945729458294592946029461294622946329464294652946629467294682946929470294712947229473294742947529476294772947829479294802948129482294832948429485294862948729488294892949029491294922949329494294952949629497294982949929500295012950229503295042950529506295072950829509295102951129512295132951429515295162951729518295192952029521295222952329524295252952629527295282952929530295312953229533295342953529536295372953829539295402954129542295432954429545295462954729548295492955029551295522955329554295552955629557295582955929560295612956229563295642956529566295672956829569295702957129572295732957429575295762957729578295792958029581295822958329584295852958629587295882958929590295912959229593295942959529596295972959829599296002960129602296032960429605296062960729608296092961029611296122961329614296152961629617296182961929620296212962229623296242962529626296272962829629296302963129632296332963429635296362963729638296392964029641296422964329644296452964629647296482964929650296512965229653296542965529656296572965829659296602966129662296632966429665296662966729668296692967029671296722967329674296752967629677296782967929680296812968229683296842968529686296872968829689296902969129692296932969429695296962969729698296992970029701297022970329704297052970629707297082970929710297112971229713297142971529716297172971829719297202972129722297232972429725297262972729728297292973029731297322973329734297352973629737297382973929740297412974229743297442974529746297472974829749297502975129752297532975429755297562975729758297592976029761297622976329764297652976629767297682976929770297712977229773297742977529776297772977829779297802978129782297832978429785297862978729788297892979029791297922979329794297952979629797297982979929800298012980229803298042980529806298072980829809298102981129812298132981429815298162981729818298192982029821298222982329824298252982629827298282982929830298312983229833298342983529836298372983829839298402984129842298432984429845298462984729848298492985029851298522985329854298552985629857298582985929860298612986229863298642986529866298672986829869298702987129872298732987429875298762987729878298792988029881298822988329884298852988629887298882988929890298912989229893298942989529896298972989829899299002990129902299032990429905299062990729908299092991029911299122991329914299152991629917299182991929920299212992229923299242992529926299272992829929299302993129932299332993429935299362993729938299392994029941299422994329944299452994629947299482994929950299512995229953299542995529956299572995829959299602996129962299632996429965299662996729968299692997029971299722997329974299752997629977299782997929980299812998229983299842998529986299872998829989299902999129992299932999429995299962999729998299993000030001300023000330004300053000630007300083000930010300113001230013300143001530016300173001830019300203002130022300233002430025300263002730028300293003030031300323003330034300353003630037300383003930040300413004230043300443004530046300473004830049300503005130052300533005430055300563005730058300593006030061300623006330064300653006630067300683006930070300713007230073300743007530076300773007830079300803008130082300833008430085300863008730088300893009030091300923009330094300953009630097300983009930100301013010230103301043010530106301073010830109301103011130112301133011430115301163011730118301193012030121301223012330124301253012630127301283012930130301313013230133301343013530136301373013830139301403014130142301433014430145301463014730148301493015030151301523015330154301553015630157301583015930160301613016230163301643016530166301673016830169301703017130172301733017430175301763017730178301793018030181301823018330184301853018630187301883018930190301913019230193301943019530196301973019830199302003020130202302033020430205302063020730208302093021030211302123021330214302153021630217302183021930220302213022230223302243022530226302273022830229302303023130232302333023430235302363023730238302393024030241302423024330244302453024630247302483024930250302513025230253302543025530256302573025830259302603026130262302633026430265302663026730268302693027030271302723027330274302753027630277302783027930280302813028230283302843028530286302873028830289302903029130292302933029430295302963029730298302993030030301303023030330304303053030630307303083030930310303113031230313303143031530316303173031830319303203032130322303233032430325303263032730328303293033030331303323033330334303353033630337303383033930340303413034230343303443034530346303473034830349303503035130352303533035430355303563035730358303593036030361303623036330364303653036630367303683036930370303713037230373303743037530376303773037830379303803038130382303833038430385303863038730388303893039030391303923039330394303953039630397303983039930400304013040230403304043040530406304073040830409304103041130412304133041430415304163041730418304193042030421304223042330424304253042630427304283042930430304313043230433304343043530436304373043830439304403044130442304433044430445304463044730448304493045030451304523045330454304553045630457304583045930460304613046230463304643046530466304673046830469304703047130472304733047430475304763047730478304793048030481304823048330484304853048630487304883048930490304913049230493304943049530496304973049830499305003050130502305033050430505305063050730508305093051030511305123051330514305153051630517305183051930520305213052230523305243052530526305273052830529305303053130532305333053430535305363053730538305393054030541305423054330544305453054630547305483054930550305513055230553305543055530556305573055830559305603056130562305633056430565305663056730568305693057030571305723057330574305753057630577305783057930580305813058230583305843058530586305873058830589305903059130592305933059430595305963059730598305993060030601306023060330604306053060630607306083060930610306113061230613306143061530616306173061830619306203062130622306233062430625306263062730628306293063030631306323063330634306353063630637306383063930640306413064230643306443064530646306473064830649306503065130652306533065430655306563065730658306593066030661306623066330664306653066630667306683066930670306713067230673306743067530676306773067830679306803068130682306833068430685306863068730688306893069030691306923069330694306953069630697306983069930700307013070230703307043070530706307073070830709307103071130712307133071430715307163071730718307193072030721307223072330724307253072630727307283072930730307313073230733307343073530736307373073830739307403074130742307433074430745307463074730748307493075030751307523075330754307553075630757307583075930760307613076230763307643076530766307673076830769307703077130772307733077430775307763077730778307793078030781307823078330784307853078630787307883078930790307913079230793307943079530796307973079830799308003080130802308033080430805308063080730808308093081030811308123081330814308153081630817308183081930820308213082230823308243082530826308273082830829308303083130832308333083430835308363083730838308393084030841308423084330844308453084630847308483084930850308513085230853308543085530856308573085830859308603086130862308633086430865308663086730868308693087030871308723087330874308753087630877308783087930880308813088230883308843088530886308873088830889308903089130892308933089430895308963089730898308993090030901309023090330904309053090630907309083090930910309113091230913309143091530916309173091830919309203092130922309233092430925309263092730928309293093030931309323093330934309353093630937309383093930940309413094230943309443094530946309473094830949309503095130952309533095430955309563095730958309593096030961309623096330964309653096630967309683096930970309713097230973309743097530976309773097830979309803098130982309833098430985309863098730988309893099030991309923099330994309953099630997309983099931000310013100231003310043100531006310073100831009310103101131012310133101431015310163101731018310193102031021310223102331024310253102631027310283102931030310313103231033310343103531036310373103831039310403104131042310433104431045310463104731048310493105031051310523105331054310553105631057310583105931060310613106231063310643106531066310673106831069310703107131072310733107431075310763107731078310793108031081310823108331084310853108631087310883108931090310913109231093310943109531096310973109831099311003110131102311033110431105311063110731108311093111031111311123111331114311153111631117311183111931120311213112231123311243112531126311273112831129311303113131132311333113431135311363113731138311393114031141311423114331144311453114631147311483114931150311513115231153311543115531156311573115831159311603116131162311633116431165311663116731168311693117031171311723117331174311753117631177311783117931180311813118231183311843118531186311873118831189311903119131192311933119431195311963119731198311993120031201312023120331204312053120631207312083120931210312113121231213312143121531216312173121831219312203122131222312233122431225312263122731228312293123031231312323123331234312353123631237312383123931240312413124231243312443124531246312473124831249312503125131252312533125431255312563125731258312593126031261312623126331264312653126631267312683126931270312713127231273312743127531276312773127831279312803128131282312833128431285312863128731288312893129031291312923129331294312953129631297312983129931300313013130231303313043130531306313073130831309313103131131312313133131431315313163131731318313193132031321313223132331324313253132631327313283132931330313313133231333313343133531336313373133831339313403134131342313433134431345313463134731348313493135031351313523135331354313553135631357313583135931360313613136231363313643136531366313673136831369313703137131372313733137431375313763137731378313793138031381313823138331384313853138631387313883138931390313913139231393313943139531396313973139831399314003140131402314033140431405314063140731408314093141031411314123141331414314153141631417314183141931420314213142231423314243142531426314273142831429314303143131432314333143431435314363143731438314393144031441314423144331444314453144631447314483144931450314513145231453314543145531456314573145831459314603146131462314633146431465314663146731468314693147031471314723147331474314753147631477314783147931480314813148231483314843148531486314873148831489314903149131492314933149431495314963149731498314993150031501315023150331504315053150631507315083150931510315113151231513315143151531516315173151831519315203152131522315233152431525315263152731528315293153031531315323153331534315353153631537315383153931540315413154231543315443154531546315473154831549315503155131552315533155431555315563155731558315593156031561315623156331564315653156631567315683156931570315713157231573315743157531576315773157831579315803158131582315833158431585315863158731588315893159031591315923159331594315953159631597315983159931600316013160231603316043160531606316073160831609316103161131612316133161431615316163161731618316193162031621316223162331624316253162631627316283162931630316313163231633316343163531636316373163831639316403164131642316433164431645316463164731648316493165031651316523165331654316553165631657316583165931660316613166231663316643166531666316673166831669316703167131672316733167431675316763167731678316793168031681316823168331684316853168631687316883168931690316913169231693316943169531696316973169831699317003170131702317033170431705317063170731708317093171031711317123171331714317153171631717317183171931720317213172231723317243172531726317273172831729317303173131732317333173431735317363173731738317393174031741317423174331744317453174631747317483174931750317513175231753317543175531756317573175831759317603176131762317633176431765317663176731768317693177031771317723177331774317753177631777317783177931780317813178231783317843178531786317873178831789317903179131792317933179431795317963179731798317993180031801318023180331804318053180631807318083180931810318113181231813318143181531816318173181831819318203182131822318233182431825318263182731828318293183031831318323183331834318353183631837318383183931840318413184231843318443184531846318473184831849318503185131852318533185431855318563185731858318593186031861318623186331864318653186631867318683186931870318713187231873318743187531876318773187831879318803188131882318833188431885318863188731888318893189031891318923189331894318953189631897318983189931900319013190231903319043190531906319073190831909319103191131912319133191431915319163191731918319193192031921319223192331924319253192631927319283192931930319313193231933319343193531936319373193831939319403194131942319433194431945319463194731948319493195031951319523195331954319553195631957319583195931960319613196231963319643196531966319673196831969319703197131972319733197431975319763197731978319793198031981319823198331984319853198631987319883198931990319913199231993319943199531996319973199831999320003200132002320033200432005320063200732008320093201032011320123201332014320153201632017320183201932020320213202232023320243202532026320273202832029320303203132032320333203432035320363203732038320393204032041320423204332044320453204632047320483204932050320513205232053320543205532056320573205832059320603206132062320633206432065320663206732068320693207032071320723207332074320753207632077320783207932080320813208232083320843208532086320873208832089320903209132092320933209432095320963209732098320993210032101321023210332104321053210632107321083210932110321113211232113321143211532116321173211832119321203212132122321233212432125321263212732128321293213032131321323213332134321353213632137321383213932140321413214232143321443214532146321473214832149321503215132152321533215432155321563215732158321593216032161321623216332164321653216632167321683216932170321713217232173321743217532176321773217832179321803218132182321833218432185321863218732188321893219032191321923219332194321953219632197321983219932200322013220232203322043220532206322073220832209322103221132212322133221432215322163221732218322193222032221322223222332224322253222632227322283222932230322313223232233322343223532236322373223832239322403224132242322433224432245322463224732248322493225032251322523225332254322553225632257322583225932260322613226232263322643226532266322673226832269322703227132272322733227432275322763227732278322793228032281322823228332284322853228632287322883228932290322913229232293322943229532296322973229832299323003230132302323033230432305323063230732308323093231032311323123231332314323153231632317323183231932320323213232232323323243232532326323273232832329323303233132332323333233432335323363233732338323393234032341323423234332344323453234632347323483234932350323513235232353323543235532356323573235832359323603236132362323633236432365323663236732368323693237032371323723237332374323753237632377323783237932380323813238232383323843238532386323873238832389323903239132392323933239432395323963239732398323993240032401324023240332404324053240632407324083240932410324113241232413324143241532416324173241832419324203242132422324233242432425324263242732428324293243032431324323243332434324353243632437324383243932440324413244232443324443244532446324473244832449324503245132452324533245432455324563245732458324593246032461324623246332464324653246632467324683246932470324713247232473324743247532476324773247832479324803248132482324833248432485324863248732488324893249032491324923249332494324953249632497324983249932500325013250232503325043250532506325073250832509325103251132512325133251432515325163251732518325193252032521325223252332524325253252632527325283252932530325313253232533325343253532536325373253832539325403254132542325433254432545325463254732548325493255032551325523255332554325553255632557325583255932560325613256232563325643256532566325673256832569325703257132572325733257432575325763257732578325793258032581325823258332584325853258632587325883258932590325913259232593325943259532596325973259832599326003260132602326033260432605326063260732608326093261032611326123261332614326153261632617326183261932620326213262232623326243262532626326273262832629326303263132632326333263432635326363263732638326393264032641326423264332644326453264632647326483264932650326513265232653326543265532656326573265832659326603266132662326633266432665326663266732668326693267032671326723267332674326753267632677326783267932680326813268232683326843268532686326873268832689326903269132692326933269432695326963269732698326993270032701327023270332704327053270632707327083270932710327113271232713327143271532716327173271832719327203272132722327233272432725327263272732728327293273032731327323273332734327353273632737327383273932740327413274232743327443274532746327473274832749327503275132752327533275432755327563275732758327593276032761327623276332764327653276632767327683276932770327713277232773327743277532776327773277832779327803278132782327833278432785327863278732788327893279032791327923279332794327953279632797327983279932800328013280232803328043280532806328073280832809328103281132812328133281432815328163281732818328193282032821328223282332824328253282632827328283282932830328313283232833328343283532836328373283832839328403284132842328433284432845328463284732848328493285032851328523285332854328553285632857328583285932860328613286232863328643286532866328673286832869328703287132872328733287432875328763287732878328793288032881328823288332884328853288632887328883288932890328913289232893328943289532896328973289832899329003290132902329033290432905329063290732908329093291032911329123291332914329153291632917329183291932920329213292232923329243292532926329273292832929329303293132932329333293432935329363293732938329393294032941329423294332944329453294632947329483294932950329513295232953329543295532956329573295832959329603296132962329633296432965329663296732968329693297032971329723297332974329753297632977329783297932980329813298232983329843298532986329873298832989329903299132992329933299432995329963299732998329993300033001330023300333004330053300633007330083300933010330113301233013330143301533016330173301833019330203302133022330233302433025330263302733028330293303033031330323303333034330353303633037330383303933040330413304233043330443304533046330473304833049330503305133052330533305433055330563305733058330593306033061330623306333064330653306633067330683306933070330713307233073330743307533076330773307833079330803308133082330833308433085330863308733088330893309033091330923309333094330953309633097330983309933100331013310233103331043310533106331073310833109331103311133112331133311433115331163311733118331193312033121331223312333124331253312633127331283312933130331313313233133331343313533136331373313833139331403314133142331433314433145331463314733148331493315033151331523315333154331553315633157331583315933160331613316233163331643316533166331673316833169331703317133172331733317433175331763317733178331793318033181331823318333184331853318633187331883318933190331913319233193331943319533196331973319833199332003320133202332033320433205332063320733208332093321033211332123321333214332153321633217332183321933220332213322233223332243322533226332273322833229332303323133232332333323433235332363323733238332393324033241332423324333244332453324633247332483324933250332513325233253332543325533256332573325833259332603326133262332633326433265332663326733268332693327033271332723327333274332753327633277332783327933280332813328233283332843328533286332873328833289332903329133292332933329433295332963329733298332993330033301333023330333304333053330633307333083330933310333113331233313333143331533316333173331833319333203332133322333233332433325333263332733328333293333033331333323333333334333353333633337333383333933340333413334233343333443334533346333473334833349333503335133352333533335433355333563335733358333593336033361333623336333364333653336633367333683336933370333713337233373333743337533376333773337833379333803338133382333833338433385333863338733388333893339033391333923339333394333953339633397333983339933400334013340233403334043340533406334073340833409334103341133412334133341433415334163341733418334193342033421334223342333424334253342633427334283342933430334313343233433334343343533436334373343833439334403344133442334433344433445334463344733448334493345033451334523345333454334553345633457334583345933460334613346233463334643346533466334673346833469334703347133472334733347433475334763347733478334793348033481334823348333484334853348633487334883348933490334913349233493334943349533496334973349833499335003350133502335033350433505335063350733508335093351033511335123351333514335153351633517335183351933520335213352233523335243352533526335273352833529335303353133532335333353433535335363353733538335393354033541335423354333544335453354633547335483354933550335513355233553335543355533556335573355833559335603356133562335633356433565335663356733568335693357033571335723357333574335753357633577335783357933580335813358233583335843358533586335873358833589335903359133592335933359433595335963359733598335993360033601336023360333604336053360633607336083360933610336113361233613336143361533616336173361833619336203362133622336233362433625336263362733628336293363033631336323363333634336353363633637336383363933640336413364233643336443364533646336473364833649336503365133652336533365433655336563365733658336593366033661336623366333664336653366633667336683366933670336713367233673336743367533676336773367833679336803368133682336833368433685336863368733688336893369033691336923369333694336953369633697336983369933700337013370233703337043370533706337073370833709337103371133712337133371433715337163371733718337193372033721337223372333724337253372633727337283372933730337313373233733337343373533736337373373833739337403374133742337433374433745337463374733748337493375033751337523375333754337553375633757337583375933760337613376233763337643376533766337673376833769337703377133772337733377433775337763377733778337793378033781337823378333784337853378633787337883378933790337913379233793337943379533796337973379833799338003380133802338033380433805338063380733808338093381033811338123381333814338153381633817338183381933820338213382233823338243382533826338273382833829338303383133832338333383433835338363383733838338393384033841338423384333844338453384633847338483384933850338513385233853338543385533856338573385833859338603386133862338633386433865338663386733868338693387033871338723387333874338753387633877338783387933880338813388233883338843388533886338873388833889338903389133892338933389433895338963389733898338993390033901339023390333904339053390633907339083390933910339113391233913339143391533916339173391833919339203392133922339233392433925339263392733928339293393033931339323393333934339353393633937339383393933940339413394233943339443394533946339473394833949339503395133952339533395433955339563395733958339593396033961339623396333964339653396633967339683396933970339713397233973339743397533976339773397833979339803398133982339833398433985339863398733988339893399033991339923399333994339953399633997339983399934000340013400234003340043400534006340073400834009340103401134012340133401434015340163401734018340193402034021340223402334024340253402634027340283402934030340313403234033340343403534036340373403834039340403404134042340433404434045340463404734048340493405034051340523405334054340553405634057340583405934060340613406234063340643406534066340673406834069340703407134072340733407434075340763407734078340793408034081340823408334084340853408634087340883408934090340913409234093340943409534096340973409834099341003410134102341033410434105341063410734108341093411034111341123411334114341153411634117341183411934120341213412234123341243412534126341273412834129341303413134132341333413434135341363413734138341393414034141341423414334144341453414634147341483414934150341513415234153341543415534156341573415834159341603416134162341633416434165341663416734168341693417034171341723417334174341753417634177341783417934180341813418234183341843418534186341873418834189341903419134192341933419434195341963419734198341993420034201342023420334204342053420634207342083420934210342113421234213342143421534216342173421834219342203422134222342233422434225342263422734228342293423034231342323423334234342353423634237342383423934240342413424234243342443424534246342473424834249342503425134252342533425434255342563425734258342593426034261342623426334264342653426634267342683426934270342713427234273342743427534276342773427834279342803428134282342833428434285342863428734288342893429034291342923429334294342953429634297342983429934300343013430234303343043430534306343073430834309343103431134312343133431434315343163431734318343193432034321343223432334324343253432634327343283432934330343313433234333343343433534336343373433834339343403434134342343433434434345343463434734348343493435034351343523435334354343553435634357343583435934360343613436234363343643436534366343673436834369343703437134372343733437434375343763437734378343793438034381343823438334384343853438634387343883438934390343913439234393343943439534396343973439834399344003440134402344033440434405344063440734408344093441034411344123441334414344153441634417344183441934420344213442234423344243442534426344273442834429344303443134432344333443434435344363443734438344393444034441344423444334444344453444634447344483444934450344513445234453344543445534456344573445834459344603446134462344633446434465344663446734468344693447034471344723447334474344753447634477344783447934480344813448234483344843448534486344873448834489344903449134492344933449434495344963449734498344993450034501345023450334504345053450634507345083450934510345113451234513345143451534516345173451834519345203452134522345233452434525345263452734528345293453034531345323453334534345353453634537345383453934540345413454234543345443454534546345473454834549345503455134552345533455434555345563455734558345593456034561345623456334564345653456634567345683456934570345713457234573345743457534576345773457834579345803458134582345833458434585345863458734588345893459034591345923459334594345953459634597345983459934600346013460234603346043460534606346073460834609346103461134612346133461434615346163461734618346193462034621346223462334624346253462634627346283462934630346313463234633346343463534636346373463834639346403464134642346433464434645346463464734648346493465034651346523465334654346553465634657346583465934660346613466234663346643466534666346673466834669346703467134672346733467434675346763467734678346793468034681346823468334684346853468634687346883468934690346913469234693346943469534696346973469834699347003470134702347033470434705347063470734708347093471034711347123471334714347153471634717347183471934720347213472234723347243472534726347273472834729347303473134732347333473434735347363473734738347393474034741347423474334744347453474634747347483474934750347513475234753347543475534756347573475834759347603476134762347633476434765347663476734768347693477034771347723477334774347753477634777347783477934780347813478234783347843478534786347873478834789347903479134792347933479434795347963479734798347993480034801348023480334804348053480634807348083480934810348113481234813348143481534816348173481834819348203482134822348233482434825348263482734828348293483034831348323483334834348353483634837348383483934840348413484234843348443484534846348473484834849348503485134852348533485434855348563485734858348593486034861348623486334864348653486634867348683486934870348713487234873348743487534876348773487834879348803488134882348833488434885348863488734888348893489034891348923489334894348953489634897348983489934900349013490234903349043490534906349073490834909349103491134912349133491434915349163491734918349193492034921349223492334924349253492634927349283492934930349313493234933349343493534936349373493834939349403494134942349433494434945349463494734948349493495034951349523495334954349553495634957349583495934960349613496234963349643496534966349673496834969349703497134972349733497434975349763497734978349793498034981349823498334984349853498634987349883498934990349913499234993349943499534996349973499834999350003500135002350033500435005350063500735008350093501035011350123501335014350153501635017350183501935020350213502235023350243502535026350273502835029350303503135032350333503435035350363503735038350393504035041350423504335044350453504635047350483504935050350513505235053350543505535056350573505835059350603506135062350633506435065350663506735068350693507035071350723507335074350753507635077350783507935080350813508235083350843508535086350873508835089350903509135092350933509435095350963509735098350993510035101351023510335104351053510635107351083510935110351113511235113351143511535116351173511835119351203512135122351233512435125351263512735128351293513035131351323513335134351353513635137351383513935140351413514235143351443514535146351473514835149351503515135152351533515435155351563515735158351593516035161351623516335164351653516635167351683516935170351713517235173351743517535176351773517835179351803518135182351833518435185351863518735188351893519035191351923519335194351953519635197351983519935200352013520235203352043520535206352073520835209352103521135212352133521435215352163521735218352193522035221352223522335224352253522635227352283522935230352313523235233352343523535236352373523835239352403524135242352433524435245352463524735248352493525035251352523525335254352553525635257352583525935260352613526235263352643526535266352673526835269352703527135272352733527435275352763527735278352793528035281352823528335284352853528635287352883528935290352913529235293352943529535296352973529835299353003530135302353033530435305353063530735308353093531035311353123531335314353153531635317353183531935320353213532235323353243532535326353273532835329353303533135332353333533435335353363533735338353393534035341353423534335344353453534635347353483534935350353513535235353353543535535356353573535835359353603536135362353633536435365353663536735368353693537035371353723537335374353753537635377353783537935380353813538235383353843538535386353873538835389353903539135392353933539435395353963539735398353993540035401354023540335404354053540635407354083540935410354113541235413354143541535416354173541835419354203542135422354233542435425354263542735428354293543035431354323543335434354353543635437354383543935440354413544235443354443544535446354473544835449354503545135452354533545435455354563545735458354593546035461354623546335464354653546635467354683546935470354713547235473354743547535476354773547835479354803548135482354833548435485354863548735488354893549035491354923549335494354953549635497354983549935500355013550235503355043550535506355073550835509355103551135512355133551435515355163551735518355193552035521355223552335524355253552635527355283552935530355313553235533355343553535536355373553835539355403554135542355433554435545355463554735548355493555035551355523555335554355553555635557355583555935560355613556235563355643556535566355673556835569355703557135572355733557435575355763557735578355793558035581355823558335584355853558635587355883558935590355913559235593355943559535596355973559835599356003560135602356033560435605356063560735608356093561035611356123561335614356153561635617356183561935620356213562235623356243562535626356273562835629356303563135632356333563435635356363563735638356393564035641356423564335644356453564635647356483564935650356513565235653356543565535656356573565835659356603566135662356633566435665356663566735668356693567035671356723567335674356753567635677356783567935680356813568235683356843568535686356873568835689356903569135692356933569435695356963569735698356993570035701357023570335704357053570635707357083570935710357113571235713357143571535716357173571835719357203572135722357233572435725357263572735728357293573035731357323573335734357353573635737357383573935740357413574235743357443574535746357473574835749357503575135752357533575435755357563575735758357593576035761357623576335764357653576635767357683576935770357713577235773357743577535776357773577835779357803578135782357833578435785357863578735788357893579035791357923579335794357953579635797357983579935800358013580235803358043580535806358073580835809358103581135812358133581435815358163581735818358193582035821358223582335824358253582635827358283582935830358313583235833358343583535836358373583835839358403584135842358433584435845358463584735848358493585035851358523585335854358553585635857358583585935860358613586235863358643586535866358673586835869358703587135872358733587435875358763587735878358793588035881358823588335884358853588635887358883588935890358913589235893358943589535896358973589835899359003590135902359033590435905359063590735908359093591035911359123591335914359153591635917359183591935920359213592235923359243592535926359273592835929359303593135932359333593435935359363593735938359393594035941359423594335944359453594635947359483594935950359513595235953359543595535956359573595835959359603596135962359633596435965359663596735968359693597035971359723597335974359753597635977359783597935980359813598235983359843598535986359873598835989359903599135992359933599435995359963599735998359993600036001360023600336004360053600636007360083600936010360113601236013360143601536016360173601836019360203602136022360233602436025360263602736028360293603036031360323603336034360353603636037360383603936040360413604236043360443604536046360473604836049360503605136052360533605436055360563605736058360593606036061360623606336064360653606636067360683606936070360713607236073360743607536076360773607836079360803608136082360833608436085360863608736088360893609036091360923609336094360953609636097360983609936100361013610236103361043610536106361073610836109361103611136112361133611436115361163611736118361193612036121361223612336124361253612636127361283612936130361313613236133361343613536136361373613836139361403614136142361433614436145361463614736148361493615036151361523615336154361553615636157361583615936160361613616236163361643616536166361673616836169361703617136172361733617436175361763617736178361793618036181361823618336184361853618636187361883618936190361913619236193361943619536196361973619836199362003620136202362033620436205362063620736208362093621036211362123621336214362153621636217362183621936220362213622236223362243622536226362273622836229362303623136232362333623436235362363623736238362393624036241362423624336244362453624636247362483624936250362513625236253362543625536256362573625836259362603626136262362633626436265362663626736268362693627036271362723627336274362753627636277362783627936280362813628236283362843628536286362873628836289362903629136292362933629436295362963629736298362993630036301363023630336304363053630636307363083630936310363113631236313363143631536316363173631836319363203632136322363233632436325363263632736328363293633036331363323633336334363353633636337363383633936340363413634236343363443634536346363473634836349363503635136352363533635436355363563635736358363593636036361363623636336364363653636636367363683636936370363713637236373363743637536376363773637836379363803638136382363833638436385363863638736388363893639036391363923639336394363953639636397363983639936400364013640236403364043640536406364073640836409364103641136412364133641436415364163641736418364193642036421364223642336424364253642636427364283642936430364313643236433364343643536436364373643836439364403644136442364433644436445364463644736448364493645036451364523645336454364553645636457364583645936460364613646236463364643646536466364673646836469364703647136472364733647436475364763647736478364793648036481364823648336484364853648636487364883648936490364913649236493364943649536496364973649836499365003650136502365033650436505365063650736508365093651036511365123651336514365153651636517365183651936520365213652236523365243652536526365273652836529365303653136532365333653436535365363653736538365393654036541365423654336544365453654636547365483654936550365513655236553365543655536556365573655836559365603656136562365633656436565365663656736568365693657036571365723657336574365753657636577365783657936580365813658236583365843658536586365873658836589365903659136592365933659436595365963659736598365993660036601366023660336604366053660636607366083660936610366113661236613366143661536616366173661836619366203662136622366233662436625366263662736628366293663036631366323663336634366353663636637366383663936640366413664236643366443664536646366473664836649366503665136652366533665436655366563665736658366593666036661366623666336664366653666636667366683666936670366713667236673366743667536676366773667836679366803668136682366833668436685366863668736688366893669036691366923669336694366953669636697366983669936700367013670236703367043670536706367073670836709367103671136712367133671436715367163671736718367193672036721367223672336724367253672636727367283672936730367313673236733367343673536736367373673836739367403674136742367433674436745367463674736748367493675036751367523675336754367553675636757367583675936760367613676236763367643676536766367673676836769367703677136772367733677436775367763677736778367793678036781367823678336784367853678636787367883678936790367913679236793367943679536796367973679836799368003680136802368033680436805368063680736808368093681036811368123681336814368153681636817368183681936820368213682236823368243682536826368273682836829368303683136832368333683436835368363683736838368393684036841368423684336844368453684636847368483684936850368513685236853368543685536856368573685836859368603686136862368633686436865368663686736868368693687036871368723687336874368753687636877368783687936880368813688236883368843688536886368873688836889368903689136892368933689436895368963689736898368993690036901369023690336904369053690636907369083690936910369113691236913369143691536916369173691836919369203692136922369233692436925369263692736928369293693036931369323693336934369353693636937369383693936940369413694236943369443694536946369473694836949369503695136952369533695436955369563695736958369593696036961369623696336964369653696636967369683696936970369713697236973369743697536976369773697836979369803698136982369833698436985369863698736988369893699036991369923699336994369953699636997369983699937000370013700237003370043700537006370073700837009370103701137012370133701437015370163701737018370193702037021370223702337024370253702637027370283702937030370313703237033370343703537036370373703837039370403704137042370433704437045370463704737048370493705037051370523705337054370553705637057370583705937060370613706237063370643706537066370673706837069370703707137072370733707437075370763707737078370793708037081370823708337084370853708637087370883708937090370913709237093370943709537096370973709837099371003710137102371033710437105371063710737108371093711037111371123711337114371153711637117371183711937120371213712237123371243712537126371273712837129371303713137132371333713437135371363713737138371393714037141371423714337144371453714637147371483714937150371513715237153371543715537156371573715837159371603716137162371633716437165371663716737168371693717037171371723717337174371753717637177371783717937180371813718237183371843718537186371873718837189371903719137192371933719437195371963719737198371993720037201372023720337204372053720637207372083720937210372113721237213372143721537216372173721837219372203722137222372233722437225372263722737228372293723037231372323723337234372353723637237372383723937240372413724237243372443724537246372473724837249372503725137252372533725437255372563725737258372593726037261372623726337264372653726637267372683726937270372713727237273372743727537276372773727837279372803728137282372833728437285372863728737288372893729037291372923729337294372953729637297372983729937300373013730237303373043730537306373073730837309373103731137312373133731437315373163731737318373193732037321373223732337324373253732637327373283732937330373313733237333373343733537336373373733837339373403734137342373433734437345373463734737348373493735037351373523735337354373553735637357373583735937360373613736237363373643736537366373673736837369373703737137372373733737437375373763737737378373793738037381373823738337384373853738637387373883738937390373913739237393373943739537396373973739837399374003740137402374033740437405374063740737408374093741037411374123741337414374153741637417374183741937420374213742237423374243742537426374273742837429374303743137432374333743437435374363743737438374393744037441374423744337444374453744637447374483744937450374513745237453374543745537456374573745837459374603746137462374633746437465374663746737468374693747037471374723747337474374753747637477374783747937480374813748237483374843748537486374873748837489374903749137492374933749437495374963749737498374993750037501375023750337504375053750637507375083750937510375113751237513375143751537516375173751837519375203752137522375233752437525375263752737528375293753037531375323753337534375353753637537375383753937540375413754237543375443754537546375473754837549375503755137552375533755437555375563755737558375593756037561375623756337564375653756637567375683756937570375713757237573375743757537576375773757837579375803758137582375833758437585375863758737588375893759037591375923759337594375953759637597375983759937600376013760237603376043760537606376073760837609376103761137612376133761437615376163761737618376193762037621376223762337624376253762637627376283762937630376313763237633376343763537636376373763837639376403764137642376433764437645376463764737648376493765037651376523765337654376553765637657376583765937660376613766237663376643766537666376673766837669376703767137672376733767437675376763767737678376793768037681376823768337684376853768637687376883768937690376913769237693376943769537696376973769837699377003770137702377033770437705377063770737708377093771037711377123771337714377153771637717377183771937720377213772237723377243772537726377273772837729377303773137732377333773437735377363773737738377393774037741377423774337744377453774637747377483774937750377513775237753377543775537756377573775837759377603776137762377633776437765377663776737768377693777037771377723777337774377753777637777377783777937780377813778237783377843778537786377873778837789377903779137792377933779437795377963779737798377993780037801378023780337804378053780637807378083780937810378113781237813378143781537816378173781837819378203782137822378233782437825378263782737828378293783037831378323783337834378353783637837378383783937840378413784237843378443784537846378473784837849378503785137852378533785437855378563785737858378593786037861378623786337864378653786637867378683786937870378713787237873378743787537876378773787837879378803788137882378833788437885378863788737888378893789037891378923789337894378953789637897378983789937900379013790237903379043790537906379073790837909379103791137912379133791437915379163791737918379193792037921379223792337924379253792637927379283792937930379313793237933379343793537936379373793837939379403794137942379433794437945379463794737948379493795037951379523795337954379553795637957379583795937960379613796237963379643796537966379673796837969379703797137972379733797437975379763797737978379793798037981379823798337984379853798637987379883798937990379913799237993379943799537996379973799837999380003800138002380033800438005380063800738008380093801038011380123801338014380153801638017380183801938020380213802238023380243802538026380273802838029380303803138032380333803438035380363803738038380393804038041380423804338044380453804638047380483804938050380513805238053380543805538056380573805838059380603806138062380633806438065380663806738068380693807038071380723807338074380753807638077380783807938080380813808238083380843808538086380873808838089380903809138092380933809438095380963809738098380993810038101381023810338104381053810638107381083810938110381113811238113381143811538116381173811838119381203812138122381233812438125381263812738128381293813038131381323813338134381353813638137381383813938140381413814238143381443814538146381473814838149381503815138152381533815438155381563815738158381593816038161381623816338164381653816638167381683816938170381713817238173381743817538176381773817838179381803818138182381833818438185381863818738188381893819038191381923819338194381953819638197381983819938200382013820238203382043820538206382073820838209382103821138212382133821438215382163821738218382193822038221382223822338224382253822638227382283822938230382313823238233382343823538236382373823838239382403824138242382433824438245382463824738248382493825038251382523825338254382553825638257382583825938260382613826238263382643826538266382673826838269382703827138272382733827438275382763827738278382793828038281382823828338284382853828638287382883828938290382913829238293382943829538296382973829838299383003830138302383033830438305383063830738308383093831038311383123831338314383153831638317383183831938320383213832238323383243832538326383273832838329383303833138332383333833438335383363833738338383393834038341383423834338344383453834638347383483834938350383513835238353383543835538356383573835838359383603836138362383633836438365383663836738368383693837038371383723837338374383753837638377383783837938380383813838238383383843838538386383873838838389383903839138392383933839438395383963839738398383993840038401384023840338404384053840638407384083840938410384113841238413384143841538416384173841838419384203842138422384233842438425384263842738428384293843038431384323843338434384353843638437384383843938440384413844238443384443844538446384473844838449384503845138452384533845438455384563845738458384593846038461384623846338464384653846638467384683846938470384713847238473384743847538476384773847838479384803848138482384833848438485384863848738488384893849038491384923849338494384953849638497384983849938500385013850238503385043850538506385073850838509385103851138512385133851438515385163851738518385193852038521385223852338524385253852638527385283852938530385313853238533385343853538536385373853838539385403854138542385433854438545385463854738548385493855038551385523855338554385553855638557385583855938560385613856238563385643856538566385673856838569385703857138572385733857438575385763857738578385793858038581385823858338584385853858638587385883858938590385913859238593385943859538596385973859838599386003860138602386033860438605386063860738608386093861038611386123861338614386153861638617386183861938620386213862238623386243862538626386273862838629386303863138632386333863438635386363863738638386393864038641386423864338644386453864638647386483864938650386513865238653386543865538656386573865838659386603866138662386633866438665386663866738668386693867038671386723867338674386753867638677386783867938680386813868238683386843868538686386873868838689386903869138692386933869438695386963869738698386993870038701387023870338704387053870638707387083870938710387113871238713387143871538716387173871838719387203872138722387233872438725387263872738728387293873038731387323873338734387353873638737387383873938740387413874238743387443874538746387473874838749387503875138752387533875438755387563875738758387593876038761387623876338764387653876638767387683876938770387713877238773387743877538776387773877838779387803878138782387833878438785387863878738788387893879038791387923879338794387953879638797387983879938800388013880238803388043880538806388073880838809388103881138812388133881438815388163881738818388193882038821388223882338824388253882638827388283882938830388313883238833388343883538836388373883838839388403884138842388433884438845388463884738848388493885038851388523885338854388553885638857388583885938860388613886238863388643886538866388673886838869388703887138872388733887438875388763887738878388793888038881388823888338884388853888638887388883888938890388913889238893388943889538896388973889838899389003890138902389033890438905389063890738908389093891038911389123891338914389153891638917389183891938920389213892238923389243892538926389273892838929389303893138932389333893438935389363893738938389393894038941389423894338944389453894638947389483894938950389513895238953389543895538956389573895838959389603896138962389633896438965389663896738968389693897038971389723897338974389753897638977389783897938980389813898238983389843898538986389873898838989389903899138992389933899438995389963899738998389993900039001390023900339004390053900639007390083900939010390113901239013390143901539016390173901839019390203902139022390233902439025390263902739028390293903039031390323903339034390353903639037390383903939040390413904239043390443904539046390473904839049390503905139052390533905439055390563905739058390593906039061390623906339064390653906639067390683906939070390713907239073390743907539076390773907839079390803908139082390833908439085390863908739088390893909039091390923909339094390953909639097390983909939100391013910239103391043910539106391073910839109391103911139112391133911439115391163911739118391193912039121391223912339124391253912639127391283912939130391313913239133391343913539136391373913839139391403914139142391433914439145391463914739148391493915039151391523915339154391553915639157391583915939160391613916239163391643916539166391673916839169391703917139172391733917439175391763917739178391793918039181391823918339184391853918639187391883918939190391913919239193391943919539196391973919839199392003920139202392033920439205392063920739208392093921039211392123921339214392153921639217392183921939220392213922239223392243922539226392273922839229392303923139232392333923439235392363923739238392393924039241392423924339244392453924639247392483924939250392513925239253392543925539256392573925839259392603926139262392633926439265392663926739268392693927039271392723927339274392753927639277392783927939280392813928239283392843928539286392873928839289392903929139292392933929439295392963929739298392993930039301393023930339304393053930639307393083930939310393113931239313393143931539316393173931839319393203932139322393233932439325393263932739328393293933039331393323933339334393353933639337393383933939340393413934239343393443934539346393473934839349393503935139352393533935439355393563935739358393593936039361393623936339364393653936639367393683936939370393713937239373393743937539376393773937839379393803938139382393833938439385393863938739388393893939039391393923939339394393953939639397393983939939400394013940239403394043940539406394073940839409394103941139412394133941439415394163941739418394193942039421394223942339424394253942639427394283942939430394313943239433394343943539436394373943839439394403944139442394433944439445394463944739448394493945039451394523945339454394553945639457394583945939460394613946239463394643946539466394673946839469394703947139472394733947439475394763947739478394793948039481394823948339484394853948639487394883948939490394913949239493394943949539496394973949839499395003950139502395033950439505395063950739508395093951039511395123951339514395153951639517395183951939520395213952239523395243952539526395273952839529395303953139532395333953439535395363953739538395393954039541395423954339544395453954639547395483954939550395513955239553395543955539556395573955839559395603956139562395633956439565395663956739568395693957039571395723957339574395753957639577395783957939580395813958239583395843958539586395873958839589395903959139592395933959439595395963959739598395993960039601396023960339604396053960639607396083960939610396113961239613396143961539616396173961839619396203962139622396233962439625396263962739628396293963039631396323963339634396353963639637396383963939640396413964239643396443964539646396473964839649396503965139652396533965439655396563965739658396593966039661396623966339664396653966639667396683966939670396713967239673396743967539676396773967839679396803968139682396833968439685396863968739688396893969039691396923969339694396953969639697396983969939700397013970239703397043970539706397073970839709397103971139712397133971439715397163971739718397193972039721397223972339724397253972639727397283972939730397313973239733397343973539736397373973839739397403974139742397433974439745397463974739748397493975039751397523975339754397553975639757397583975939760397613976239763397643976539766397673976839769397703977139772397733977439775397763977739778397793978039781397823978339784397853978639787397883978939790397913979239793397943979539796397973979839799398003980139802398033980439805398063980739808398093981039811398123981339814398153981639817398183981939820398213982239823398243982539826398273982839829398303983139832398333983439835398363983739838398393984039841398423984339844398453984639847398483984939850398513985239853398543985539856398573985839859398603986139862398633986439865398663986739868398693987039871398723987339874398753987639877398783987939880398813988239883398843988539886398873988839889398903989139892398933989439895398963989739898398993990039901399023990339904399053990639907399083990939910399113991239913399143991539916399173991839919399203992139922399233992439925399263992739928399293993039931399323993339934399353993639937399383993939940399413994239943399443994539946399473994839949399503995139952399533995439955399563995739958399593996039961399623996339964399653996639967399683996939970399713997239973399743997539976399773997839979399803998139982399833998439985399863998739988399893999039991399923999339994399953999639997399983999940000400014000240003400044000540006400074000840009400104001140012400134001440015400164001740018400194002040021400224002340024400254002640027400284002940030400314003240033400344003540036400374003840039400404004140042400434004440045400464004740048400494005040051400524005340054400554005640057400584005940060400614006240063400644006540066400674006840069400704007140072400734007440075400764007740078400794008040081400824008340084400854008640087400884008940090400914009240093400944009540096400974009840099401004010140102401034010440105401064010740108401094011040111401124011340114401154011640117401184011940120401214012240123401244012540126401274012840129401304013140132401334013440135401364013740138401394014040141401424014340144401454014640147401484014940150401514015240153401544015540156401574015840159401604016140162401634016440165401664016740168401694017040171401724017340174401754017640177401784017940180401814018240183401844018540186401874018840189401904019140192401934019440195401964019740198401994020040201402024020340204402054020640207402084020940210402114021240213402144021540216402174021840219402204022140222402234022440225402264022740228402294023040231402324023340234402354023640237402384023940240402414024240243402444024540246402474024840249402504025140252402534025440255402564025740258402594026040261402624026340264402654026640267402684026940270402714027240273402744027540276402774027840279402804028140282402834028440285402864028740288402894029040291402924029340294402954029640297402984029940300403014030240303403044030540306403074030840309403104031140312403134031440315403164031740318403194032040321403224032340324403254032640327403284032940330403314033240333403344033540336403374033840339403404034140342403434034440345403464034740348403494035040351403524035340354403554035640357403584035940360403614036240363403644036540366403674036840369403704037140372403734037440375403764037740378403794038040381403824038340384403854038640387403884038940390403914039240393403944039540396403974039840399404004040140402404034040440405404064040740408404094041040411404124041340414404154041640417404184041940420404214042240423404244042540426404274042840429404304043140432404334043440435404364043740438404394044040441404424044340444404454044640447404484044940450404514045240453404544045540456404574045840459404604046140462404634046440465404664046740468404694047040471404724047340474404754047640477404784047940480404814048240483404844048540486404874048840489404904049140492404934049440495404964049740498404994050040501405024050340504405054050640507405084050940510405114051240513405144051540516405174051840519405204052140522405234052440525405264052740528405294053040531405324053340534405354053640537405384053940540405414054240543405444054540546405474054840549405504055140552405534055440555405564055740558405594056040561405624056340564405654056640567405684056940570405714057240573405744057540576405774057840579405804058140582405834058440585405864058740588405894059040591405924059340594405954059640597405984059940600406014060240603406044060540606406074060840609406104061140612406134061440615406164061740618406194062040621406224062340624406254062640627406284062940630406314063240633406344063540636406374063840639406404064140642406434064440645406464064740648406494065040651406524065340654406554065640657406584065940660406614066240663406644066540666406674066840669406704067140672406734067440675406764067740678406794068040681406824068340684406854068640687406884068940690406914069240693406944069540696406974069840699407004070140702407034070440705407064070740708407094071040711407124071340714407154071640717407184071940720407214072240723407244072540726407274072840729407304073140732407334073440735407364073740738407394074040741407424074340744407454074640747407484074940750407514075240753407544075540756407574075840759407604076140762407634076440765407664076740768407694077040771407724077340774407754077640777407784077940780407814078240783407844078540786407874078840789407904079140792407934079440795407964079740798407994080040801408024080340804408054080640807408084080940810408114081240813408144081540816408174081840819408204082140822408234082440825408264082740828408294083040831408324083340834408354083640837408384083940840408414084240843408444084540846408474084840849408504085140852408534085440855408564085740858408594086040861408624086340864408654086640867408684086940870408714087240873408744087540876408774087840879408804088140882408834088440885408864088740888408894089040891408924089340894408954089640897408984089940900409014090240903409044090540906409074090840909409104091140912409134091440915409164091740918409194092040921409224092340924409254092640927409284092940930409314093240933409344093540936409374093840939409404094140942409434094440945409464094740948409494095040951409524095340954409554095640957409584095940960409614096240963409644096540966409674096840969409704097140972409734097440975409764097740978409794098040981409824098340984409854098640987409884098940990409914099240993409944099540996409974099840999410004100141002410034100441005410064100741008410094101041011410124101341014410154101641017410184101941020410214102241023410244102541026410274102841029410304103141032410334103441035410364103741038410394104041041410424104341044410454104641047410484104941050410514105241053410544105541056410574105841059410604106141062410634106441065410664106741068410694107041071410724107341074410754107641077410784107941080410814108241083410844108541086410874108841089410904109141092410934109441095410964109741098410994110041101411024110341104411054110641107411084110941110411114111241113411144111541116411174111841119411204112141122411234112441125411264112741128411294113041131411324113341134411354113641137411384113941140411414114241143411444114541146411474114841149411504115141152411534115441155411564115741158411594116041161411624116341164411654116641167411684116941170411714117241173411744117541176411774117841179411804118141182411834118441185411864118741188411894119041191411924119341194411954119641197411984119941200412014120241203412044120541206412074120841209412104121141212412134121441215412164121741218412194122041221412224122341224412254122641227412284122941230412314123241233412344123541236412374123841239412404124141242412434124441245412464124741248412494125041251412524125341254412554125641257412584125941260412614126241263412644126541266412674126841269412704127141272412734127441275412764127741278412794128041281412824128341284412854128641287412884128941290412914129241293412944129541296412974129841299413004130141302413034130441305413064130741308413094131041311413124131341314413154131641317413184131941320413214132241323413244132541326413274132841329413304133141332413334133441335413364133741338413394134041341413424134341344413454134641347413484134941350413514135241353413544135541356413574135841359413604136141362413634136441365413664136741368413694137041371413724137341374413754137641377413784137941380413814138241383413844138541386413874138841389413904139141392413934139441395413964139741398413994140041401414024140341404414054140641407414084140941410414114141241413414144141541416414174141841419414204142141422414234142441425414264142741428414294143041431414324143341434414354143641437414384143941440414414144241443414444144541446414474144841449414504145141452414534145441455414564145741458414594146041461414624146341464414654146641467414684146941470414714147241473414744147541476414774147841479414804148141482414834148441485414864148741488414894149041491414924149341494414954149641497414984149941500415014150241503415044150541506415074150841509415104151141512415134151441515415164151741518415194152041521415224152341524415254152641527415284152941530415314153241533415344153541536415374153841539415404154141542415434154441545415464154741548415494155041551415524155341554415554155641557415584155941560415614156241563415644156541566415674156841569415704157141572415734157441575415764157741578415794158041581415824158341584415854158641587415884158941590415914159241593415944159541596415974159841599416004160141602416034160441605416064160741608416094161041611416124161341614416154161641617416184161941620416214162241623416244162541626416274162841629416304163141632416334163441635416364163741638416394164041641416424164341644416454164641647416484164941650416514165241653416544165541656416574165841659416604166141662416634166441665416664166741668416694167041671416724167341674416754167641677416784167941680416814168241683416844168541686416874168841689416904169141692416934169441695416964169741698416994170041701417024170341704417054170641707417084170941710417114171241713417144171541716417174171841719417204172141722417234172441725417264172741728417294173041731417324173341734417354173641737417384173941740417414174241743417444174541746417474174841749417504175141752417534175441755417564175741758417594176041761417624176341764417654176641767417684176941770417714177241773417744177541776417774177841779417804178141782417834178441785417864178741788417894179041791417924179341794417954179641797417984179941800418014180241803 |
- /*!
- * @overview Ember - JavaScript Application Framework
- * @copyright Copyright 2011-2014 Tilde Inc. and contributors
- * Portions Copyright 2006-2011 Strobe Inc.
- * Portions Copyright 2008-2011 Apple Inc. All rights reserved.
- * @license Licensed under MIT license
- * See https://raw.github.com/emberjs/ember.js/master/LICENSE
- * @version 1.4.0
- */
- (function() {
- /*global __fail__*/
- /**
- Ember Debug
- @module ember
- @submodule ember-debug
- */
- /**
- @class Ember
- */
- if ('undefined' === typeof Ember) {
- Ember = {};
- if ('undefined' !== typeof window) {
- window.Em = window.Ember = Em = Ember;
- }
- }
- // This needs to be kept in sync with the logic in
- // `packages/ember-metal/lib/core.js`.
- //
- // This is duplicated here to ensure that `Ember.ENV`
- // is setup even if `Ember` is not loaded yet.
- if (Ember.ENV) {
- // do nothing if Ember.ENV is already setup
- } else if ('undefined' !== typeof EmberENV) {
- Ember.ENV = EmberENV;
- } else if('undefined' !== typeof ENV) {
- Ember.ENV = ENV;
- } else {
- Ember.ENV = {};
- }
- if (!('MANDATORY_SETTER' in Ember.ENV)) {
- Ember.ENV.MANDATORY_SETTER = true; // default to true for debug dist
- }
- /**
- Define an assertion that will throw an exception if the condition is not
- met. Ember build tools will remove any calls to `Ember.assert()` when
- doing a production build. Example:
- ```javascript
- // Test for truthiness
- Ember.assert('Must pass a valid object', obj);
- // Fail unconditionally
- Ember.assert('This code path should never be run')
- ```
- @method assert
- @param {String} desc A description of the assertion. This will become
- the text of the Error thrown if the assertion fails.
- @param {Boolean} test Must be truthy for the assertion to pass. If
- falsy, an exception will be thrown.
- */
- Ember.assert = function(desc, test) {
- if (!test) {
- throw new Ember.Error("Assertion Failed: " + desc);
- }
- };
- /**
- Display a warning with the provided message. Ember build tools will
- remove any calls to `Ember.warn()` when doing a production build.
- @method warn
- @param {String} message A warning to display.
- @param {Boolean} test An optional boolean. If falsy, the warning
- will be displayed.
- */
- Ember.warn = function(message, test) {
- if (!test) {
- Ember.Logger.warn("WARNING: "+message);
- if ('trace' in Ember.Logger) Ember.Logger.trace();
- }
- };
- /**
- Display a debug notice. Ember build tools will remove any calls to
- `Ember.debug()` when doing a production build.
- ```javascript
- Ember.debug("I'm a debug notice!");
- ```
- @method debug
- @param {String} message A debug message to display.
- */
- Ember.debug = function(message) {
- Ember.Logger.debug("DEBUG: "+message);
- };
- /**
- Display a deprecation warning with the provided message and a stack trace
- (Chrome and Firefox only). Ember build tools will remove any calls to
- `Ember.deprecate()` when doing a production build.
- @method deprecate
- @param {String} message A description of the deprecation.
- @param {Boolean} test An optional boolean. If falsy, the deprecation
- will be displayed.
- */
- Ember.deprecate = function(message, test) {
- if (Ember.TESTING_DEPRECATION) { return; }
- if (arguments.length === 1) { test = false; }
- if (test) { return; }
- if (Ember.ENV.RAISE_ON_DEPRECATION) { throw new Ember.Error(message); }
- var error;
- // When using new Error, we can't do the arguments check for Chrome. Alternatives are welcome
- try { __fail__.fail(); } catch (e) { error = e; }
- if (Ember.LOG_STACKTRACE_ON_DEPRECATION && error.stack) {
- var stack, stackStr = '';
- if (error['arguments']) {
- // Chrome
- stack = error.stack.replace(/^\s+at\s+/gm, '').
- replace(/^([^\(]+?)([\n$])/gm, '{anonymous}($1)$2').
- replace(/^Object.<anonymous>\s*\(([^\)]+)\)/gm, '{anonymous}($1)').split('\n');
- stack.shift();
- } else {
- // Firefox
- stack = error.stack.replace(/(?:\n@:0)?\s+$/m, '').
- replace(/^\(/gm, '{anonymous}(').split('\n');
- }
- stackStr = "\n " + stack.slice(2).join("\n ");
- message = message + stackStr;
- }
- Ember.Logger.warn("DEPRECATION: "+message);
- };
- /**
- Alias an old, deprecated method with its new counterpart.
- Display a deprecation warning with the provided message and a stack trace
- (Chrome and Firefox only) when the assigned method is called.
- Ember build tools will not remove calls to `Ember.deprecateFunc()`, though
- no warnings will be shown in production.
- ```javascript
- Ember.oldMethod = Ember.deprecateFunc("Please use the new, updated method", Ember.newMethod);
- ```
- @method deprecateFunc
- @param {String} message A description of the deprecation.
- @param {Function} func The new function called to replace its deprecated counterpart.
- @return {Function} a new function that wrapped the original function with a deprecation warning
- */
- Ember.deprecateFunc = function(message, func) {
- return function() {
- Ember.deprecate(message);
- return func.apply(this, arguments);
- };
- };
- // Inform the developer about the Ember Inspector if not installed.
- if (!Ember.testing) {
- var isFirefox = typeof InstallTrigger !== 'undefined';
- var isChrome = !!window.chrome && !window.opera;
- if (typeof window !== 'undefined' && (isFirefox || isChrome) && window.addEventListener) {
- window.addEventListener("load", function() {
- if (document.body && document.body.dataset && !document.body.dataset.emberExtension) {
- var downloadURL;
- if(isChrome) {
- downloadURL = 'https://chrome.google.com/webstore/detail/ember-inspector/bmdblncegkenkacieihfhpjfppoconhi';
- } else if(isFirefox) {
- downloadURL = 'https://addons.mozilla.org/en-US/firefox/addon/ember-inspector/'
- }
- Ember.debug('For more advanced debugging, install the Ember Inspector from ' + downloadURL);
- }
- }, false);
- }
- }
- })();
- /*!
- * @overview Ember - JavaScript Application Framework
- * @copyright Copyright 2011-2014 Tilde Inc. and contributors
- * Portions Copyright 2006-2011 Strobe Inc.
- * Portions Copyright 2008-2011 Apple Inc. All rights reserved.
- * @license Licensed under MIT license
- * See https://raw.github.com/emberjs/ember.js/master/LICENSE
- * @version 1.4.0
- */
- (function() {
- var define, requireModule, require, requirejs;
- (function() {
- var registry = {}, seen = {};
- define = function(name, deps, callback) {
- registry[name] = { deps: deps, callback: callback };
- };
- requirejs = require = requireModule = function(name) {
- requirejs._eak_seen = registry;
- if (seen[name]) { return seen[name]; }
- seen[name] = {};
- if (!registry[name]) {
- throw new Error("Could not find module " + name);
- }
- var mod = registry[name],
- deps = mod.deps,
- callback = mod.callback,
- reified = [],
- exports;
- for (var i=0, l=deps.length; i<l; i++) {
- if (deps[i] === 'exports') {
- reified.push(exports = {});
- } else {
- reified.push(requireModule(resolve(deps[i])));
- }
- }
- var value = callback.apply(this, reified);
- return seen[name] = exports || value;
- function resolve(child) {
- if (child.charAt(0) !== '.') { return child; }
- var parts = child.split("/");
- var parentBase = name.split("/").slice(0, -1);
- for (var i=0, l=parts.length; i<l; i++) {
- var part = parts[i];
- if (part === '..') { parentBase.pop(); }
- else if (part === '.') { continue; }
- else { parentBase.push(part); }
- }
- return parentBase.join("/");
- }
- };
- })();
- (function() {
- /*globals Em:true ENV EmberENV MetamorphENV:true */
- /**
- @module ember
- @submodule ember-metal
- */
- /**
- All Ember methods and functions are defined inside of this namespace. You
- generally should not add new properties to this namespace as it may be
- overwritten by future versions of Ember.
- You can also use the shorthand `Em` instead of `Ember`.
- Ember-Runtime is a framework that provides core functions for Ember including
- cross-platform functions, support for property observing and objects. Its
- focus is on small size and performance. You can use this in place of or
- along-side other cross-platform libraries such as jQuery.
- The core Runtime framework is based on the jQuery API with a number of
- performance optimizations.
- @class Ember
- @static
- @version 1.4.0
- */
- if ('undefined' === typeof Ember) {
- // Create core object. Make it act like an instance of Ember.Namespace so that
- // objects assigned to it are given a sane string representation.
- Ember = {};
- }
- // Default imports, exports and lookup to the global object;
- var imports = Ember.imports = Ember.imports || this;
- var exports = Ember.exports = Ember.exports || this;
- var lookup = Ember.lookup = Ember.lookup || this;
- // aliases needed to keep minifiers from removing the global context
- exports.Em = exports.Ember = Em = Ember;
- // Make sure these are set whether Ember was already defined or not
- Ember.isNamespace = true;
- Ember.toString = function() { return "Ember"; };
- /**
- @property VERSION
- @type String
- @default '1.4.0'
- @static
- */
- Ember.VERSION = '1.4.0';
- /**
- Standard environmental variables. You can define these in a global `EmberENV`
- variable before loading Ember to control various configuration settings.
- For backwards compatibility with earlier versions of Ember the global `ENV`
- variable will be used if `EmberENV` is not defined.
- @property ENV
- @type Hash
- */
- // This needs to be kept in sync with the logic in
- // `packages/ember-debug/lib/main.js`.
- if (Ember.ENV) {
- // do nothing if Ember.ENV is already setup
- } else if ('undefined' !== typeof EmberENV) {
- Ember.ENV = EmberENV;
- } else if('undefined' !== typeof ENV) {
- Ember.ENV = ENV;
- } else {
- Ember.ENV = {};
- }
- Ember.config = Ember.config || {};
- // We disable the RANGE API by default for performance reasons
- if ('undefined' === typeof Ember.ENV.DISABLE_RANGE_API) {
- Ember.ENV.DISABLE_RANGE_API = true;
- }
- if ("undefined" === typeof MetamorphENV) {
- exports.MetamorphENV = {};
- }
- MetamorphENV.DISABLE_RANGE_API = Ember.ENV.DISABLE_RANGE_API;
- /**
- Hash of enabled Canary features. Add to before creating your application.
- You can also define `ENV.FEATURES` if you need to enable features flagged at runtime.
- @property FEATURES
- @type Hash
- */
- Ember.FEATURES = Ember.ENV.FEATURES || {};
- /**
- Test that a feature is enabled. Parsed by Ember's build tools to leave
- experimental features out of beta/stable builds.
- You can define the following configuration options:
- * `ENV.ENABLE_ALL_FEATURES` - force all features to be enabled.
- * `ENV.ENABLE_OPTIONAL_FEATURES` - enable any features that have not been explicitly
- enabled/disabled.
- @method isEnabled
- @param {string} feature
- */
- Ember.FEATURES.isEnabled = function(feature) {
- var featureValue = Ember.FEATURES[feature];
- if (Ember.ENV.ENABLE_ALL_FEATURES) {
- return true;
- } else if (featureValue === true || featureValue === false || featureValue === undefined) {
- return featureValue;
- } else if (Ember.ENV.ENABLE_OPTIONAL_FEATURES) {
- return true;
- } else {
- return false;
- }
- };
- // ..........................................................
- // BOOTSTRAP
- //
- /**
- Determines whether Ember should enhances some built-in object prototypes to
- provide a more friendly API. If enabled, a few methods will be added to
- `Function`, `String`, and `Array`. `Object.prototype` will not be enhanced,
- which is the one that causes most trouble for people.
- In general we recommend leaving this option set to true since it rarely
- conflicts with other code. If you need to turn it off however, you can
- define an `ENV.EXTEND_PROTOTYPES` config to disable it.
- @property EXTEND_PROTOTYPES
- @type Boolean
- @default true
- */
- Ember.EXTEND_PROTOTYPES = Ember.ENV.EXTEND_PROTOTYPES;
- if (typeof Ember.EXTEND_PROTOTYPES === 'undefined') {
- Ember.EXTEND_PROTOTYPES = true;
- }
- /**
- Determines whether Ember logs a full stack trace during deprecation warnings
- @property LOG_STACKTRACE_ON_DEPRECATION
- @type Boolean
- @default true
- */
- Ember.LOG_STACKTRACE_ON_DEPRECATION = (Ember.ENV.LOG_STACKTRACE_ON_DEPRECATION !== false);
- /**
- Determines whether Ember should add ECMAScript 5 shims to older browsers.
- @property SHIM_ES5
- @type Boolean
- @default Ember.EXTEND_PROTOTYPES
- */
- Ember.SHIM_ES5 = (Ember.ENV.SHIM_ES5 === false) ? false : Ember.EXTEND_PROTOTYPES;
- /**
- Determines whether Ember logs info about version of used libraries
- @property LOG_VERSION
- @type Boolean
- @default true
- */
- Ember.LOG_VERSION = (Ember.ENV.LOG_VERSION === false) ? false : true;
- /**
- Empty function. Useful for some operations. Always returns `this`.
- @method K
- @private
- @return {Object}
- */
- Ember.K = function() { return this; };
- // Stub out the methods defined by the ember-debug package in case it's not loaded
- if ('undefined' === typeof Ember.assert) { Ember.assert = Ember.K; }
- if ('undefined' === typeof Ember.warn) { Ember.warn = Ember.K; }
- if ('undefined' === typeof Ember.debug) { Ember.debug = Ember.K; }
- if ('undefined' === typeof Ember.deprecate) { Ember.deprecate = Ember.K; }
- if ('undefined' === typeof Ember.deprecateFunc) {
- Ember.deprecateFunc = function(_, func) { return func; };
- }
- /**
- Previously we used `Ember.$.uuid`, however `$.uuid` has been removed from
- jQuery master. We'll just bootstrap our own uuid now.
- @property uuid
- @type Number
- @private
- */
- Ember.uuid = 0;
- /**
- Merge the contents of two objects together into the first object.
- ```javascript
- Ember.merge({first: 'Tom'}, {last: 'Dale'}); // {first: 'Tom', last: 'Dale'}
- var a = {first: 'Yehuda'}, b = {last: 'Katz'};
- Ember.merge(a, b); // a == {first: 'Yehuda', last: 'Katz'}, b == {last: 'Katz'}
- ```
- @method merge
- @for Ember
- @param {Object} original The object to merge into
- @param {Object} updates The object to copy properties from
- @return {Object}
- */
- Ember.merge = function(original, updates) {
- for (var prop in updates) {
- if (!updates.hasOwnProperty(prop)) { continue; }
- original[prop] = updates[prop];
- }
- return original;
- };
- /**
- Returns true if the passed value is null or undefined. This avoids errors
- from JSLint complaining about use of ==, which can be technically
- confusing.
- ```javascript
- Ember.isNone(); // true
- Ember.isNone(null); // true
- Ember.isNone(undefined); // true
- Ember.isNone(''); // false
- Ember.isNone([]); // false
- Ember.isNone(function() {}); // false
- ```
- @method isNone
- @for Ember
- @param {Object} obj Value to test
- @return {Boolean}
- */
- Ember.isNone = function(obj) {
- return obj === null || obj === undefined;
- };
- Ember.none = Ember.deprecateFunc("Ember.none is deprecated. Please use Ember.isNone instead.", Ember.isNone);
- /**
- Verifies that a value is `null` or an empty string, empty array,
- or empty function.
- Constrains the rules on `Ember.isNone` by returning false for empty
- string and empty arrays.
- ```javascript
- Ember.isEmpty(); // true
- Ember.isEmpty(null); // true
- Ember.isEmpty(undefined); // true
- Ember.isEmpty(''); // true
- Ember.isEmpty([]); // true
- Ember.isEmpty('Adam Hawkins'); // false
- Ember.isEmpty([0,1,2]); // false
- ```
- @method isEmpty
- @for Ember
- @param {Object} obj Value to test
- @return {Boolean}
- */
- Ember.isEmpty = function(obj) {
- return Ember.isNone(obj) || (obj.length === 0 && typeof obj !== 'function') || (typeof obj === 'object' && Ember.get(obj, 'length') === 0);
- };
- Ember.empty = Ember.deprecateFunc("Ember.empty is deprecated. Please use Ember.isEmpty instead.", Ember.isEmpty) ;
- })();
- (function() {
- /*globals Node */
- /**
- @module ember-metal
- */
- /**
- Platform specific methods and feature detectors needed by the framework.
- @class platform
- @namespace Ember
- @static
- */
- var platform = Ember.platform = {};
- /**
- Identical to `Object.create()`. Implements if not available natively.
- @method create
- @for Ember
- */
- Ember.create = Object.create;
- // IE8 has Object.create but it couldn't treat property descriptors.
- if (Ember.create) {
- if (Ember.create({a: 1}, {a: {value: 2}}).a !== 2) {
- Ember.create = null;
- }
- }
- // STUB_OBJECT_CREATE allows us to override other libraries that stub
- // Object.create different than we would prefer
- if (!Ember.create || Ember.ENV.STUB_OBJECT_CREATE) {
- var K = function() {};
- Ember.create = function(obj, props) {
- K.prototype = obj;
- obj = new K();
- if (props) {
- K.prototype = obj;
- for (var prop in props) {
- K.prototype[prop] = props[prop].value;
- }
- obj = new K();
- }
- K.prototype = null;
- return obj;
- };
- Ember.create.isSimulated = true;
- }
- var defineProperty = Object.defineProperty;
- var canRedefineProperties, canDefinePropertyOnDOM;
- // Catch IE8 where Object.defineProperty exists but only works on DOM elements
- if (defineProperty) {
- try {
- defineProperty({}, 'a',{get:function() {}});
- } catch (e) {
- defineProperty = null;
- }
- }
- if (defineProperty) {
- // Detects a bug in Android <3.2 where you cannot redefine a property using
- // Object.defineProperty once accessors have already been set.
- canRedefineProperties = (function() {
- var obj = {};
- defineProperty(obj, 'a', {
- configurable: true,
- enumerable: true,
- get: function() { },
- set: function() { }
- });
- defineProperty(obj, 'a', {
- configurable: true,
- enumerable: true,
- writable: true,
- value: true
- });
- return obj.a === true;
- })();
- // This is for Safari 5.0, which supports Object.defineProperty, but not
- // on DOM nodes.
- canDefinePropertyOnDOM = (function() {
- try {
- defineProperty(document.createElement('div'), 'definePropertyOnDOM', {});
- return true;
- } catch(e) { }
- return false;
- })();
- if (!canRedefineProperties) {
- defineProperty = null;
- } else if (!canDefinePropertyOnDOM) {
- defineProperty = function(obj, keyName, desc) {
- var isNode;
- if (typeof Node === "object") {
- isNode = obj instanceof Node;
- } else {
- isNode = typeof obj === "object" && typeof obj.nodeType === "number" && typeof obj.nodeName === "string";
- }
- if (isNode) {
- // TODO: Should we have a warning here?
- return (obj[keyName] = desc.value);
- } else {
- return Object.defineProperty(obj, keyName, desc);
- }
- };
- }
- }
- /**
- @class platform
- @namespace Ember
- */
- /**
- Identical to `Object.defineProperty()`. Implements as much functionality
- as possible if not available natively.
- @method defineProperty
- @param {Object} obj The object to modify
- @param {String} keyName property name to modify
- @param {Object} desc descriptor hash
- @return {void}
- */
- platform.defineProperty = defineProperty;
- /**
- Set to true if the platform supports native getters and setters.
- @property hasPropertyAccessors
- @final
- */
- platform.hasPropertyAccessors = true;
- if (!platform.defineProperty) {
- platform.hasPropertyAccessors = false;
- platform.defineProperty = function(obj, keyName, desc) {
- if (!desc.get) { obj[keyName] = desc.value; }
- };
- platform.defineProperty.isSimulated = true;
- }
- if (Ember.ENV.MANDATORY_SETTER && !platform.hasPropertyAccessors) {
- Ember.ENV.MANDATORY_SETTER = false;
- }
- })();
- (function() {
- /*jshint newcap:false*/
- /**
- @module ember-metal
- */
- // NOTE: There is a bug in jshint that doesn't recognize `Object()` without `new`
- // as being ok unless both `newcap:false` and not `use strict`.
- // https://github.com/jshint/jshint/issues/392
- // Testing this is not ideal, but we want to use native functions
- // if available, but not to use versions created by libraries like Prototype
- var isNativeFunc = function(func) {
- // This should probably work in all browsers likely to have ES5 array methods
- return func && Function.prototype.toString.call(func).indexOf('[native code]') > -1;
- };
- // From: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/array/map
- var arrayMap = isNativeFunc(Array.prototype.map) ? Array.prototype.map : function(fun /*, thisp */) {
- //"use strict";
- if (this === void 0 || this === null) {
- throw new TypeError();
- }
- var t = Object(this);
- var len = t.length >>> 0;
- if (typeof fun !== "function") {
- throw new TypeError();
- }
- var res = new Array(len);
- var thisp = arguments[1];
- for (var i = 0; i < len; i++) {
- if (i in t) {
- res[i] = fun.call(thisp, t[i], i, t);
- }
- }
- return res;
- };
- // From: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/array/foreach
- var arrayForEach = isNativeFunc(Array.prototype.forEach) ? Array.prototype.forEach : function(fun /*, thisp */) {
- //"use strict";
- if (this === void 0 || this === null) {
- throw new TypeError();
- }
- var t = Object(this);
- var len = t.length >>> 0;
- if (typeof fun !== "function") {
- throw new TypeError();
- }
- var thisp = arguments[1];
- for (var i = 0; i < len; i++) {
- if (i in t) {
- fun.call(thisp, t[i], i, t);
- }
- }
- };
- var arrayIndexOf = isNativeFunc(Array.prototype.indexOf) ? Array.prototype.indexOf : function (obj, fromIndex) {
- if (fromIndex === null || fromIndex === undefined) { fromIndex = 0; }
- else if (fromIndex < 0) { fromIndex = Math.max(0, this.length + fromIndex); }
- for (var i = fromIndex, j = this.length; i < j; i++) {
- if (this[i] === obj) { return i; }
- }
- return -1;
- };
- /**
- Array polyfills to support ES5 features in older browsers.
- @namespace Ember
- @property ArrayPolyfills
- */
- Ember.ArrayPolyfills = {
- map: arrayMap,
- forEach: arrayForEach,
- indexOf: arrayIndexOf
- };
- if (Ember.SHIM_ES5) {
- if (!Array.prototype.map) {
- Array.prototype.map = arrayMap;
- }
- if (!Array.prototype.forEach) {
- Array.prototype.forEach = arrayForEach;
- }
- if (!Array.prototype.indexOf) {
- Array.prototype.indexOf = arrayIndexOf;
- }
- }
- })();
- (function() {
- var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];
- /**
- A subclass of the JavaScript Error object for use in Ember.
- @class Error
- @namespace Ember
- @extends Error
- @constructor
- */
- Ember.Error = function() {
- var tmp = Error.apply(this, arguments);
- // Adds a `stack` property to the given error object that will yield the
- // stack trace at the time captureStackTrace was called.
- // When collecting the stack trace all frames above the topmost call
- // to this function, including that call, will be left out of the
- // stack trace.
- // This is useful because we can hide Ember implementation details
- // that are not very helpful for the user.
- if (Error.captureStackTrace) {
- Error.captureStackTrace(this, Ember.Error);
- }
- // Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work.
- for (var idx = 0; idx < errorProps.length; idx++) {
- this[errorProps[idx]] = tmp[errorProps[idx]];
- }
- };
- Ember.Error.prototype = Ember.create(Error.prototype);
- // ..........................................................
- // ERROR HANDLING
- //
- /**
- A function may be assigned to `Ember.onerror` to be called when Ember
- internals encounter an error. This is useful for specialized error handling
- and reporting code.
- ```javascript
- Ember.onerror = function(error) {
- Em.$.ajax('/report-error', 'POST', {
- stack: error.stack,
- otherInformation: 'whatever app state you want to provide'
- });
- };
- ```
- @event onerror
- @for Ember
- @param {Exception} error the error object
- */
- Ember.onerror = null;
- /**
- Wrap code block in a try/catch if `Ember.onerror` is set.
- @private
- @method handleErrors
- @for Ember
- @param {Function} func
- @param [context]
- */
- Ember.handleErrors = function(func, context) {
- // Unfortunately in some browsers we lose the backtrace if we rethrow the existing error,
- // so in the event that we don't have an `onerror` handler we don't wrap in a try/catch
- if ('function' === typeof Ember.onerror) {
- try {
- return func.call(context || this);
- } catch (error) {
- Ember.onerror(error);
- }
- } else {
- return func.call(context || this);
- }
- };
- })();
- (function() {
- /**
- @module ember-metal
- */
- /**
- Prefix used for guids through out Ember.
- @private
- */
- Ember.GUID_PREFIX = 'ember';
- var o_defineProperty = Ember.platform.defineProperty,
- o_create = Ember.create,
- // Used for guid generation...
- GUID_KEY = '__ember'+ (+ new Date()),
- uuid = 0,
- numberCache = [],
- stringCache = {};
- var MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER;
- /**
- A unique key used to assign guids and other private metadata to objects.
- If you inspect an object in your browser debugger you will often see these.
- They can be safely ignored.
- On browsers that support it, these properties are added with enumeration
- disabled so they won't show up when you iterate over your properties.
- @private
- @property GUID_KEY
- @for Ember
- @type String
- @final
- */
- Ember.GUID_KEY = GUID_KEY;
- var GUID_DESC = {
- writable: false,
- configurable: false,
- enumerable: false,
- value: null
- };
- /**
- Generates a new guid, optionally saving the guid to the object that you
- pass in. You will rarely need to use this method. Instead you should
- call `Ember.guidFor(obj)`, which return an existing guid if available.
- @private
- @method generateGuid
- @for Ember
- @param {Object} [obj] Object the guid will be used for. If passed in, the guid will
- be saved on the object and reused whenever you pass the same object
- again.
- If no object is passed, just generate a new guid.
- @param {String} [prefix] Prefix to place in front of the guid. Useful when you want to
- separate the guid into separate namespaces.
- @return {String} the guid
- */
- Ember.generateGuid = function generateGuid(obj, prefix) {
- if (!prefix) prefix = Ember.GUID_PREFIX;
- var ret = (prefix + (uuid++));
- if (obj) {
- GUID_DESC.value = ret;
- o_defineProperty(obj, GUID_KEY, GUID_DESC);
- }
- return ret;
- };
- /**
- Returns a unique id for the object. If the object does not yet have a guid,
- one will be assigned to it. You can call this on any object,
- `Ember.Object`-based or not, but be aware that it will add a `_guid`
- property.
- You can also use this method on DOM Element objects.
- @private
- @method guidFor
- @for Ember
- @param {Object} obj any object, string, number, Element, or primitive
- @return {String} the unique guid for this instance.
- */
- Ember.guidFor = function guidFor(obj) {
- // special cases where we don't want to add a key to object
- if (obj === undefined) return "(undefined)";
- if (obj === null) return "(null)";
- var ret;
- var type = typeof obj;
- // Don't allow prototype changes to String etc. to change the guidFor
- switch(type) {
- case 'number':
- ret = numberCache[obj];
- if (!ret) ret = numberCache[obj] = 'nu'+obj;
- return ret;
- case 'string':
- ret = stringCache[obj];
- if (!ret) ret = stringCache[obj] = 'st'+(uuid++);
- return ret;
- case 'boolean':
- return obj ? '(true)' : '(false)';
- default:
- if (obj[GUID_KEY]) return obj[GUID_KEY];
- if (obj === Object) return '(Object)';
- if (obj === Array) return '(Array)';
- ret = 'ember'+(uuid++);
- GUID_DESC.value = ret;
- o_defineProperty(obj, GUID_KEY, GUID_DESC);
- return ret;
- }
- };
- // ..........................................................
- // META
- //
- var META_DESC = Ember.META_DESC = {
- writable: true,
- configurable: false,
- enumerable: false,
- value: null
- };
- var META_KEY = Ember.GUID_KEY+'_meta';
- /**
- The key used to store meta information on object for property observing.
- @property META_KEY
- @for Ember
- @private
- @final
- @type String
- */
- Ember.META_KEY = META_KEY;
- var isDefinePropertySimulated = Ember.platform.defineProperty.isSimulated;
- function Meta(obj) {
- this.descs = {};
- this.watching = {};
- this.cache = {};
- this.source = obj;
- }
- Meta.prototype = {
- descs: null,
- deps: null,
- watching: null,
- listeners: null,
- cache: null,
- source: null,
- mixins: null,
- bindings: null,
- chains: null,
- chainWatchers: null,
- values: null,
- proto: null
- };
- if (isDefinePropertySimulated) {
- // on platforms that don't support enumerable false
- // make meta fail jQuery.isPlainObject() to hide from
- // jQuery.extend() by having a property that fails
- // hasOwnProperty check.
- Meta.prototype.__preventPlainObject__ = true;
- // Without non-enumerable properties, meta objects will be output in JSON
- // unless explicitly suppressed
- Meta.prototype.toJSON = function () { };
- }
- // Placeholder for non-writable metas.
- var EMPTY_META = new Meta(null);
- if (MANDATORY_SETTER) { EMPTY_META.values = {}; }
- Ember.EMPTY_META = EMPTY_META;
- /**
- Retrieves the meta hash for an object. If `writable` is true ensures the
- hash is writable for this object as well.
- The meta object contains information about computed property descriptors as
- well as any watched properties and other information. You generally will
- not access this information directly but instead work with higher level
- methods that manipulate this hash indirectly.
- @method meta
- @for Ember
- @private
- @param {Object} obj The object to retrieve meta for
- @param {Boolean} [writable=true] Pass `false` if you do not intend to modify
- the meta hash, allowing the method to avoid making an unnecessary copy.
- @return {Object} the meta hash for an object
- */
- Ember.meta = function meta(obj, writable) {
- var ret = obj[META_KEY];
- if (writable===false) return ret || EMPTY_META;
- if (!ret) {
- if (!isDefinePropertySimulated) o_defineProperty(obj, META_KEY, META_DESC);
- ret = new Meta(obj);
- if (MANDATORY_SETTER) { ret.values = {}; }
- obj[META_KEY] = ret;
- // make sure we don't accidentally try to create constructor like desc
- ret.descs.constructor = null;
- } else if (ret.source !== obj) {
- if (!isDefinePropertySimulated) o_defineProperty(obj, META_KEY, META_DESC);
- ret = o_create(ret);
- ret.descs = o_create(ret.descs);
- ret.watching = o_create(ret.watching);
- ret.cache = {};
- ret.source = obj;
- if (MANDATORY_SETTER) { ret.values = o_create(ret.values); }
- obj[META_KEY] = ret;
- }
- return ret;
- };
- Ember.getMeta = function getMeta(obj, property) {
- var meta = Ember.meta(obj, false);
- return meta[property];
- };
- Ember.setMeta = function setMeta(obj, property, value) {
- var meta = Ember.meta(obj, true);
- meta[property] = value;
- return value;
- };
- /**
- @deprecated
- @private
- In order to store defaults for a class, a prototype may need to create
- a default meta object, which will be inherited by any objects instantiated
- from the class's constructor.
- However, the properties of that meta object are only shallow-cloned,
- so if a property is a hash (like the event system's `listeners` hash),
- it will by default be shared across all instances of that class.
- This method allows extensions to deeply clone a series of nested hashes or
- other complex objects. For instance, the event system might pass
- `['listeners', 'foo:change', 'ember157']` to `prepareMetaPath`, which will
- walk down the keys provided.
- For each key, if the key does not exist, it is created. If it already
- exists and it was inherited from its constructor, the constructor's
- key is cloned.
- You can also pass false for `writable`, which will simply return
- undefined if `prepareMetaPath` discovers any part of the path that
- shared or undefined.
- @method metaPath
- @for Ember
- @param {Object} obj The object whose meta we are examining
- @param {Array} path An array of keys to walk down
- @param {Boolean} writable whether or not to create a new meta
- (or meta property) if one does not already exist or if it's
- shared with its constructor
- */
- Ember.metaPath = function metaPath(obj, path, writable) {
- Ember.deprecate("Ember.metaPath is deprecated and will be removed from future releases.");
- var meta = Ember.meta(obj, writable), keyName, value;
- for (var i=0, l=path.length; i<l; i++) {
- keyName = path[i];
- value = meta[keyName];
- if (!value) {
- if (!writable) { return undefined; }
- value = meta[keyName] = { __ember_source__: obj };
- } else if (value.__ember_source__ !== obj) {
- if (!writable) { return undefined; }
- value = meta[keyName] = o_create(value);
- value.__ember_source__ = obj;
- }
- meta = value;
- }
- return value;
- };
- /**
- Wraps the passed function so that `this._super` will point to the superFunc
- when the function is invoked. This is the primitive we use to implement
- calls to super.
- @private
- @method wrap
- @for Ember
- @param {Function} func The function to call
- @param {Function} superFunc The super function.
- @return {Function} wrapped function.
- */
- Ember.wrap = function(func, superFunc) {
- function K() {}
- function superWrapper() {
- var ret, sup = this._super;
- this._super = superFunc || K;
- ret = func.apply(this, arguments);
- this._super = sup;
- return ret;
- }
- superWrapper.wrappedFunction = func;
- superWrapper.__ember_observes__ = func.__ember_observes__;
- superWrapper.__ember_observesBefore__ = func.__ember_observesBefore__;
- superWrapper.__ember_listens__ = func.__ember_listens__;
- return superWrapper;
- };
- /**
- Returns true if the passed object is an array or Array-like.
- Ember Array Protocol:
- - the object has an objectAt property
- - the object is a native Array
- - the object is an Object, and has a length property
- Unlike `Ember.typeOf` this method returns true even if the passed object is
- not formally array but appears to be array-like (i.e. implements `Ember.Array`)
- ```javascript
- Ember.isArray(); // false
- Ember.isArray([]); // true
- Ember.isArray( Ember.ArrayProxy.create({ content: [] }) ); // true
- ```
- @method isArray
- @for Ember
- @param {Object} obj The object to test
- @return {Boolean} true if the passed object is an array or Array-like
- */
- Ember.isArray = function(obj) {
- if (!obj || obj.setInterval) { return false; }
- if (Array.isArray && Array.isArray(obj)) { return true; }
- if (Ember.Array && Ember.Array.detect(obj)) { return true; }
- if ((obj.length !== undefined) && 'object'===typeof obj) { return true; }
- return false;
- };
- /**
- Forces the passed object to be part of an array. If the object is already
- an array or array-like, returns the object. Otherwise adds the object to
- an array. If obj is `null` or `undefined`, returns an empty array.
- ```javascript
- Ember.makeArray(); // []
- Ember.makeArray(null); // []
- Ember.makeArray(undefined); // []
- Ember.makeArray('lindsay'); // ['lindsay']
- Ember.makeArray([1,2,42]); // [1,2,42]
- var controller = Ember.ArrayProxy.create({ content: [] });
- Ember.makeArray(controller) === controller; // true
- ```
- @method makeArray
- @for Ember
- @param {Object} obj the object
- @return {Array}
- */
- Ember.makeArray = function(obj) {
- if (obj === null || obj === undefined) { return []; }
- return Ember.isArray(obj) ? obj : [obj];
- };
- function canInvoke(obj, methodName) {
- return !!(obj && typeof obj[methodName] === 'function');
- }
- /**
- Checks to see if the `methodName` exists on the `obj`.
- ```javascript
- var foo = {bar: Ember.K, baz: null};
- Ember.canInvoke(foo, 'bar'); // true
- Ember.canInvoke(foo, 'baz'); // false
- Ember.canInvoke(foo, 'bat'); // false
- ```
- @method canInvoke
- @for Ember
- @param {Object} obj The object to check for the method
- @param {String} methodName The method name to check for
- @return {Boolean}
- */
- Ember.canInvoke = canInvoke;
- /**
- Checks to see if the `methodName` exists on the `obj`,
- and if it does, invokes it with the arguments passed.
- ```javascript
- var d = new Date('03/15/2013');
- Ember.tryInvoke(d, 'getTime'); // 1363320000000
- Ember.tryInvoke(d, 'setFullYear', [2014]); // 1394856000000
- Ember.tryInvoke(d, 'noSuchMethod', [2014]); // undefined
- ```
- @method tryInvoke
- @for Ember
- @param {Object} obj The object to check for the method
- @param {String} methodName The method name to check for
- @param {Array} [args] The arguments to pass to the method
- @return {*} the return value of the invoked method or undefined if it cannot be invoked
- */
- Ember.tryInvoke = function(obj, methodName, args) {
- if (canInvoke(obj, methodName)) {
- return obj[methodName].apply(obj, args || []);
- }
- };
- // https://github.com/emberjs/ember.js/pull/1617
- var needsFinallyFix = (function() {
- var count = 0;
- try{
- try { }
- finally {
- count++;
- throw new Error('needsFinallyFixTest');
- }
- } catch (e) {}
- return count !== 1;
- })();
- /**
- Provides try { } finally { } functionality, while working
- around Safari's double finally bug.
- ```javascript
- var tryable = function() {
- someResource.lock();
- runCallback(); // May throw error.
- };
- var finalizer = function() {
- someResource.unlock();
- };
- Ember.tryFinally(tryable, finalizer);
- ```
- @method tryFinally
- @for Ember
- @param {Function} tryable The function to run the try callback
- @param {Function} finalizer The function to run the finally callback
- @param {Object} [binding] The optional calling object. Defaults to 'this'
- @return {*} The return value is the that of the finalizer,
- unless that value is undefined, in which case it is the return value
- of the tryable
- */
- if (needsFinallyFix) {
- Ember.tryFinally = function(tryable, finalizer, binding) {
- var result, finalResult, finalError;
- binding = binding || this;
- try {
- result = tryable.call(binding);
- } finally {
- try {
- finalResult = finalizer.call(binding);
- } catch (e) {
- finalError = e;
- }
- }
- if (finalError) { throw finalError; }
- return (finalResult === undefined) ? result : finalResult;
- };
- } else {
- Ember.tryFinally = function(tryable, finalizer, binding) {
- var result, finalResult;
- binding = binding || this;
- try {
- result = tryable.call(binding);
- } finally {
- finalResult = finalizer.call(binding);
- }
- return (finalResult === undefined) ? result : finalResult;
- };
- }
- /**
- Provides try { } catch finally { } functionality, while working
- around Safari's double finally bug.
- ```javascript
- var tryable = function() {
- for (i=0, l=listeners.length; i<l; i++) {
- listener = listeners[i];
- beforeValues[i] = listener.before(name, time(), payload);
- }
- return callback.call(binding);
- };
- var catchable = function(e) {
- payload = payload || {};
- payload.exception = e;
- };
- var finalizer = function() {
- for (i=0, l=listeners.length; i<l; i++) {
- listener = listeners[i];
- listener.after(name, time(), payload, beforeValues[i]);
- }
- };
- Ember.tryCatchFinally(tryable, catchable, finalizer);
- ```
- @method tryCatchFinally
- @for Ember
- @param {Function} tryable The function to run the try callback
- @param {Function} catchable The function to run the catchable callback
- @param {Function} finalizer The function to run the finally callback
- @param {Object} [binding] The optional calling object. Defaults to 'this'
- @return {*} The return value is the that of the finalizer,
- unless that value is undefined, in which case it is the return value
- of the tryable.
- */
- if (needsFinallyFix) {
- Ember.tryCatchFinally = function(tryable, catchable, finalizer, binding) {
- var result, finalResult, finalError;
- binding = binding || this;
- try {
- result = tryable.call(binding);
- } catch(error) {
- result = catchable.call(binding, error);
- } finally {
- try {
- finalResult = finalizer.call(binding);
- } catch (e) {
- finalError = e;
- }
- }
- if (finalError) { throw finalError; }
- return (finalResult === undefined) ? result : finalResult;
- };
- } else {
- Ember.tryCatchFinally = function(tryable, catchable, finalizer, binding) {
- var result, finalResult;
- binding = binding || this;
- try {
- result = tryable.call(binding);
- } catch(error) {
- result = catchable.call(binding, error);
- } finally {
- finalResult = finalizer.call(binding);
- }
- return (finalResult === undefined) ? result : finalResult;
- };
- }
- // ........................................
- // TYPING & ARRAY MESSAGING
- //
- var TYPE_MAP = {};
- var t = "Boolean Number String Function Array Date RegExp Object".split(" ");
- Ember.ArrayPolyfills.forEach.call(t, function(name) {
- TYPE_MAP[ "[object " + name + "]" ] = name.toLowerCase();
- });
- var toString = Object.prototype.toString;
- /**
- Returns a consistent type for the passed item.
- Use this instead of the built-in `typeof` to get the type of an item.
- It will return the same result across all browsers and includes a bit
- more detail. Here is what will be returned:
- | Return Value | Meaning |
- |---------------|------------------------------------------------------|
- | 'string' | String primitive or String object. |
- | 'number' | Number primitive or Number object. |
- | 'boolean' | Boolean primitive or Boolean object. |
- | 'null' | Null value |
- | 'undefined' | Undefined value |
- | 'function' | A function |
- | 'array' | An instance of Array |
- | 'regexp' | An instance of RegExp |
- | 'date' | An instance of Date |
- | 'class' | An Ember class (created using Ember.Object.extend()) |
- | 'instance' | An Ember object instance |
- | 'error' | An instance of the Error object |
- | 'object' | A JavaScript object not inheriting from Ember.Object |
- Examples:
- ```javascript
- Ember.typeOf(); // 'undefined'
- Ember.typeOf(null); // 'null'
- Ember.typeOf(undefined); // 'undefined'
- Ember.typeOf('michael'); // 'string'
- Ember.typeOf(new String('michael')); // 'string'
- Ember.typeOf(101); // 'number'
- Ember.typeOf(new Number(101)); // 'number'
- Ember.typeOf(true); // 'boolean'
- Ember.typeOf(new Boolean(true)); // 'boolean'
- Ember.typeOf(Ember.makeArray); // 'function'
- Ember.typeOf([1,2,90]); // 'array'
- Ember.typeOf(/abc/); // 'regexp'
- Ember.typeOf(new Date()); // 'date'
- Ember.typeOf(Ember.Object.extend()); // 'class'
- Ember.typeOf(Ember.Object.create()); // 'instance'
- Ember.typeOf(new Error('teamocil')); // 'error'
- // "normal" JavaScript object
- Ember.typeOf({a: 'b'}); // 'object'
- ```
- @method typeOf
- @for Ember
- @param {Object} item the item to check
- @return {String} the type
- */
- Ember.typeOf = function(item) {
- var ret;
- ret = (item === null || item === undefined) ? String(item) : TYPE_MAP[toString.call(item)] || 'object';
- if (ret === 'function') {
- if (Ember.Object && Ember.Object.detect(item)) ret = 'class';
- } else if (ret === 'object') {
- if (item instanceof Error) ret = 'error';
- else if (Ember.Object && item instanceof Ember.Object) ret = 'instance';
- else if (item instanceof Date) ret = 'date';
- }
- return ret;
- };
- /**
- Convenience method to inspect an object. This method will attempt to
- convert the object into a useful string description.
- It is a pretty simple implementation. If you want something more robust,
- use something like JSDump: https://github.com/NV/jsDump
- @method inspect
- @for Ember
- @param {Object} obj The object you want to inspect.
- @return {String} A description of the object
- */
- Ember.inspect = function(obj) {
- var type = Ember.typeOf(obj);
- if (type === 'array') {
- return '[' + obj + ']';
- }
- if (type !== 'object') {
- return obj + '';
- }
- var v, ret = [];
- for(var key in obj) {
- if (obj.hasOwnProperty(key)) {
- v = obj[key];
- if (v === 'toString') { continue; } // ignore useless items
- if (Ember.typeOf(v) === 'function') { v = "function() { ... }"; }
- ret.push(key + ": " + v);
- }
- }
- return "{" + ret.join(", ") + "}";
- };
- })();
- (function() {
- // Ember.tryCatchFinally
- /**
- The purpose of the Ember Instrumentation module is
- to provide efficient, general-purpose instrumentation
- for Ember.
- Subscribe to a listener by using `Ember.subscribe`:
- ```javascript
- Ember.subscribe("render", {
- before: function(name, timestamp, payload) {
- },
- after: function(name, timestamp, payload) {
- }
- });
- ```
- If you return a value from the `before` callback, that same
- value will be passed as a fourth parameter to the `after`
- callback.
- Instrument a block of code by using `Ember.instrument`:
- ```javascript
- Ember.instrument("render.handlebars", payload, function() {
- // rendering logic
- }, binding);
- ```
- Event names passed to `Ember.instrument` are namespaced
- by periods, from more general to more specific. Subscribers
- can listen for events by whatever level of granularity they
- are interested in.
- In the above example, the event is `render.handlebars`,
- and the subscriber listened for all events beginning with
- `render`. It would receive callbacks for events named
- `render`, `render.handlebars`, `render.container`, or
- even `render.handlebars.layout`.
- @class Instrumentation
- @namespace Ember
- @static
- */
- Ember.Instrumentation = {};
- var subscribers = [], cache = {};
- var populateListeners = function(name) {
- var listeners = [], subscriber;
- for (var i=0, l=subscribers.length; i<l; i++) {
- subscriber = subscribers[i];
- if (subscriber.regex.test(name)) {
- listeners.push(subscriber.object);
- }
- }
- cache[name] = listeners;
- return listeners;
- };
- var time = (function() {
- var perf = 'undefined' !== typeof window ? window.performance || {} : {};
- var fn = perf.now || perf.mozNow || perf.webkitNow || perf.msNow || perf.oNow;
- // fn.bind will be available in all the browsers that support the advanced window.performance... ;-)
- return fn ? fn.bind(perf) : function() { return +new Date(); };
- })();
- /**
- Notifies event's subscribers, calls `before` and `after` hooks.
- @method instrument
- @namespace Ember.Instrumentation
- @param {String} [name] Namespaced event name.
- @param {Object} payload
- @param {Function} callback Function that you're instrumenting.
- @param {Object} binding Context that instrument function is called with.
- */
- Ember.Instrumentation.instrument = function(name, payload, callback, binding) {
- var listeners = cache[name], timeName, ret;
- if (Ember.STRUCTURED_PROFILE) {
- timeName = name + ": " + payload.object;
- console.time(timeName);
- }
- if (!listeners) {
- listeners = populateListeners(name);
- }
- if (listeners.length === 0) {
- ret = callback.call(binding);
- if (Ember.STRUCTURED_PROFILE) { console.timeEnd(timeName); }
- return ret;
- }
- var beforeValues = [], listener, i, l;
- function tryable() {
- for (i=0, l=listeners.length; i<l; i++) {
- listener = listeners[i];
- beforeValues[i] = listener.before(name, time(), payload);
- }
- return callback.call(binding);
- }
- function catchable(e) {
- payload = payload || {};
- payload.exception = e;
- }
- function finalizer() {
- for (i=0, l=listeners.length; i<l; i++) {
- listener = listeners[i];
- listener.after(name, time(), payload, beforeValues[i]);
- }
- if (Ember.STRUCTURED_PROFILE) {
- console.timeEnd(timeName);
- }
- }
- return Ember.tryCatchFinally(tryable, catchable, finalizer);
- };
- /**
- Subscribes to a particular event or instrumented block of code.
- @method subscribe
- @namespace Ember.Instrumentation
- @param {String} [pattern] Namespaced event name.
- @param {Object} [object] Before and After hooks.
- @return {Subscriber}
- */
- Ember.Instrumentation.subscribe = function(pattern, object) {
- var paths = pattern.split("."), path, regex = [];
- for (var i=0, l=paths.length; i<l; i++) {
- path = paths[i];
- if (path === "*") {
- regex.push("[^\\.]*");
- } else {
- regex.push(path);
- }
- }
- regex = regex.join("\\.");
- regex = regex + "(\\..*)?";
- var subscriber = {
- pattern: pattern,
- regex: new RegExp("^" + regex + "$"),
- object: object
- };
- subscribers.push(subscriber);
- cache = {};
- return subscriber;
- };
- /**
- Unsubscribes from a particular event or instrumented block of code.
- @method unsubscribe
- @namespace Ember.Instrumentation
- @param {Object} [subscriber]
- */
- Ember.Instrumentation.unsubscribe = function(subscriber) {
- var index;
- for (var i=0, l=subscribers.length; i<l; i++) {
- if (subscribers[i] === subscriber) {
- index = i;
- }
- }
- subscribers.splice(index, 1);
- cache = {};
- };
- /**
- Resets `Ember.Instrumentation` by flushing list of subscribers.
- @method reset
- @namespace Ember.Instrumentation
- */
- Ember.Instrumentation.reset = function() {
- subscribers = [];
- cache = {};
- };
- Ember.instrument = Ember.Instrumentation.instrument;
- Ember.subscribe = Ember.Instrumentation.subscribe;
- })();
- (function() {
- var map, forEach, indexOf, splice, filter;
- map = Array.prototype.map || Ember.ArrayPolyfills.map;
- forEach = Array.prototype.forEach || Ember.ArrayPolyfills.forEach;
- indexOf = Array.prototype.indexOf || Ember.ArrayPolyfills.indexOf;
- filter = Array.prototype.filter || Ember.ArrayPolyfills.filter;
- splice = Array.prototype.splice;
- /**
- * Defines some convenience methods for working with Enumerables.
- * `Ember.EnumerableUtils` uses `Ember.ArrayPolyfills` when necessary.
- *
- * @class EnumerableUtils
- * @namespace Ember
- * @static
- * */
- var utils = Ember.EnumerableUtils = {
- /**
- * Calls the map function on the passed object with a specified callback. This
- * uses `Ember.ArrayPolyfill`'s-map method when necessary.
- *
- * @method map
- * @param {Object} obj The object that should be mapped
- * @param {Function} callback The callback to execute
- * @param {Object} thisArg Value to use as this when executing *callback*
- *
- * @return {Array} An array of mapped values.
- */
- map: function(obj, callback, thisArg) {
- return obj.map ? obj.map.call(obj, callback, thisArg) : map.call(obj, callback, thisArg);
- },
- /**
- * Calls the forEach function on the passed object with a specified callback. This
- * uses `Ember.ArrayPolyfill`'s-forEach method when necessary.
- *
- * @method forEach
- * @param {Object} obj The object to call forEach on
- * @param {Function} callback The callback to execute
- * @param {Object} thisArg Value to use as this when executing *callback*
- *
- */
- forEach: function(obj, callback, thisArg) {
- return obj.forEach ? obj.forEach.call(obj, callback, thisArg) : forEach.call(obj, callback, thisArg);
- },
- /**
- * Calls the filter function on the passed object with a specified callback. This
- * uses `Ember.ArrayPolyfill`'s-filter method when necessary.
- *
- * @method filter
- * @param {Object} obj The object to call filter on
- * @param {Function} callback The callback to execute
- * @param {Object} thisArg Value to use as this when executing *callback*
- *
- * @return {Array} An array containing the filtered values
- */
- filter: function(obj, callback, thisArg) {
- return obj.filter ? obj.filter.call(obj, callback, thisArg) : filter.call(obj, callback, thisArg);
- },
- /**
- * Calls the indexOf function on the passed object with a specified callback. This
- * uses `Ember.ArrayPolyfill`'s-indexOf method when necessary.
- *
- * @method indexOf
- * @param {Object} obj The object to call indexOn on
- * @param {Function} callback The callback to execute
- * @param {Object} index The index to start searching from
- *
- */
- indexOf: function(obj, element, index) {
- return obj.indexOf ? obj.indexOf.call(obj, element, index) : indexOf.call(obj, element, index);
- },
- /**
- * Returns an array of indexes of the first occurrences of the passed elements
- * on the passed object.
- *
- * ```javascript
- * var array = [1, 2, 3, 4, 5];
- * Ember.EnumerableUtils.indexesOf(array, [2, 5]); // [1, 4]
- *
- * var fubar = "Fubarr";
- * Ember.EnumerableUtils.indexesOf(fubar, ['b', 'r']); // [2, 4]
- * ```
- *
- * @method indexesOf
- * @param {Object} obj The object to check for element indexes
- * @param {Array} elements The elements to search for on *obj*
- *
- * @return {Array} An array of indexes.
- *
- */
- indexesOf: function(obj, elements) {
- return elements === undefined ? [] : utils.map(elements, function(item) {
- return utils.indexOf(obj, item);
- });
- },
- /**
- * Adds an object to an array. If the array already includes the object this
- * method has no effect.
- *
- * @method addObject
- * @param {Array} array The array the passed item should be added to
- * @param {Object} item The item to add to the passed array
- *
- * @return 'undefined'
- */
- addObject: function(array, item) {
- var index = utils.indexOf(array, item);
- if (index === -1) { array.push(item); }
- },
- /**
- * Removes an object from an array. If the array does not contain the passed
- * object this method has no effect.
- *
- * @method removeObject
- * @param {Array} array The array to remove the item from.
- * @param {Object} item The item to remove from the passed array.
- *
- * @return 'undefined'
- */
- removeObject: function(array, item) {
- var index = utils.indexOf(array, item);
- if (index !== -1) { array.splice(index, 1); }
- },
- _replace: function(array, idx, amt, objects) {
- var args = [].concat(objects), chunk, ret = [],
- // https://code.google.com/p/chromium/issues/detail?id=56588
- size = 60000, start = idx, ends = amt, count;
- while (args.length) {
- count = ends > size ? size : ends;
- if (count <= 0) { count = 0; }
- chunk = args.splice(0, size);
- chunk = [start, count].concat(chunk);
- start += size;
- ends -= count;
- ret = ret.concat(splice.apply(array, chunk));
- }
- return ret;
- },
- /**
- * Replaces objects in an array with the passed objects.
- *
- * ```javascript
- * var array = [1,2,3];
- * Ember.EnumerableUtils.replace(array, 1, 2, [4, 5]); // [1, 4, 5]
- *
- * var array = [1,2,3];
- * Ember.EnumerableUtils.replace(array, 1, 1, [4, 5]); // [1, 4, 5, 3]
- *
- * var array = [1,2,3];
- * Ember.EnumerableUtils.replace(array, 10, 1, [4, 5]); // [1, 2, 3, 4, 5]
- * ```
- *
- * @method replace
- * @param {Array} array The array the objects should be inserted into.
- * @param {Number} idx Starting index in the array to replace. If *idx* >=
- * length, then append to the end of the array.
- * @param {Number} amt Number of elements that should be remove from the array,
- * starting at *idx*
- * @param {Array} objects An array of zero or more objects that should be
- * inserted into the array at *idx*
- *
- * @return {Array} The changed array.
- */
- replace: function(array, idx, amt, objects) {
- if (array.replace) {
- return array.replace(idx, amt, objects);
- } else {
- return utils._replace(array, idx, amt, objects);
- }
- },
- /**
- * Calculates the intersection of two arrays. This method returns a new array
- * filled with the records that the two passed arrays share with each other.
- * If there is no intersection, an empty array will be returned.
- *
- * ```javascript
- * var array1 = [1, 2, 3, 4, 5];
- * var array2 = [1, 3, 5, 6, 7];
- *
- * Ember.EnumerableUtils.intersection(array1, array2); // [1, 3, 5]
- *
- * var array1 = [1, 2, 3];
- * var array2 = [4, 5, 6];
- *
- * Ember.EnumerableUtils.intersection(array1, array2); // []
- * ```
- *
- * @method intersection
- * @param {Array} array1 The first array
- * @param {Array} array2 The second array
- *
- * @return {Array} The intersection of the two passed arrays.
- */
- intersection: function(array1, array2) {
- var intersection = [];
- utils.forEach(array1, function(element) {
- if (utils.indexOf(array2, element) >= 0) {
- intersection.push(element);
- }
- });
- return intersection;
- }
- };
- })();
- (function() {
- /**
- @module ember-metal
- */
- var META_KEY = Ember.META_KEY, get;
- var MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER;
- var IS_GLOBAL_PATH = /^([A-Z$]|([0-9][A-Z$])).*[\.\*]/;
- var HAS_THIS = /^this[\.\*]/;
- var FIRST_KEY = /^([^\.\*]+)/;
- // ..........................................................
- // GET AND SET
- //
- // If we are on a platform that supports accessors we can use those.
- // Otherwise simulate accessors by looking up the property directly on the
- // object.
- /**
- Gets the value of a property on an object. If the property is computed,
- the function will be invoked. If the property is not defined but the
- object implements the `unknownProperty` method then that will be invoked.
- If you plan to run on IE8 and older browsers then you should use this
- method anytime you want to retrieve a property on an object that you don't
- know for sure is private. (Properties beginning with an underscore '_'
- are considered private.)
- On all newer browsers, you only need to use this method to retrieve
- properties if the property might not be defined on the object and you want
- to respect the `unknownProperty` handler. Otherwise you can ignore this
- method.
- Note that if the object itself is `undefined`, this method will throw
- an error.
- @method get
- @for Ember
- @param {Object} obj The object to retrieve from.
- @param {String} keyName The property key to retrieve
- @return {Object} the property value or `null`.
- */
- get = function get(obj, keyName) {
- // Helpers that operate with 'this' within an #each
- if (keyName === '') {
- return obj;
- }
- if (!keyName && 'string'===typeof obj) {
- keyName = obj;
- obj = null;
- }
- Ember.assert("Cannot call get with "+ keyName +" key.", !!keyName);
- Ember.assert("Cannot call get with '"+ keyName +"' on an undefined object.", obj !== undefined);
- if (obj === null || keyName.indexOf('.') !== -1) {
- return getPath(obj, keyName);
- }
- var meta = obj[META_KEY], desc = meta && meta.descs[keyName], ret;
- if (desc) {
- return desc.get(obj, keyName);
- } else {
- if (MANDATORY_SETTER && meta && meta.watching[keyName] > 0) {
- ret = meta.values[keyName];
- } else {
- ret = obj[keyName];
- }
- if (ret === undefined &&
- 'object' === typeof obj && !(keyName in obj) && 'function' === typeof obj.unknownProperty) {
- return obj.unknownProperty(keyName);
- }
- return ret;
- }
- };
- // Currently used only by Ember Data tests
- if (Ember.config.overrideAccessors) {
- Ember.get = get;
- Ember.config.overrideAccessors();
- get = Ember.get;
- }
- /**
- Normalizes a target/path pair to reflect that actual target/path that should
- be observed, etc. This takes into account passing in global property
- paths (i.e. a path beginning with a captial letter not defined on the
- target) and * separators.
- @private
- @method normalizeTuple
- @for Ember
- @param {Object} target The current target. May be `null`.
- @param {String} path A path on the target or a global property path.
- @return {Array} a temporary array with the normalized target/path pair.
- */
- var normalizeTuple = Ember.normalizeTuple = function(target, path) {
- var hasThis = HAS_THIS.test(path),
- isGlobal = !hasThis && IS_GLOBAL_PATH.test(path),
- key;
- if (!target || isGlobal) target = Ember.lookup;
- if (hasThis) path = path.slice(5);
- if (target === Ember.lookup) {
- key = path.match(FIRST_KEY)[0];
- target = get(target, key);
- path = path.slice(key.length+1);
- }
- // must return some kind of path to be valid else other things will break.
- if (!path || path.length===0) throw new Ember.Error('Path cannot be empty');
- return [ target, path ];
- };
- var getPath = Ember._getPath = function(root, path) {
- var hasThis, parts, tuple, idx, len;
- // If there is no root and path is a key name, return that
- // property from the global object.
- // E.g. get('Ember') -> Ember
- if (root === null && path.indexOf('.') === -1) { return get(Ember.lookup, path); }
- // detect complicated paths and normalize them
- hasThis = HAS_THIS.test(path);
- if (!root || hasThis) {
- tuple = normalizeTuple(root, path);
- root = tuple[0];
- path = tuple[1];
- tuple.length = 0;
- }
- parts = path.split(".");
- len = parts.length;
- for (idx = 0; root != null && idx < len; idx++) {
- root = get(root, parts[idx], true);
- if (root && root.isDestroyed) { return undefined; }
- }
- return root;
- };
- Ember.getWithDefault = function(root, key, defaultValue) {
- var value = get(root, key);
- if (value === undefined) { return defaultValue; }
- return value;
- };
- Ember.get = get;
- })();
- (function() {
- /**
- @module ember-metal
- */
- var o_create = Ember.create,
- metaFor = Ember.meta,
- META_KEY = Ember.META_KEY,
- a_slice = [].slice,
- /* listener flags */
- ONCE = 1, SUSPENDED = 2;
- /*
- The event system uses a series of nested hashes to store listeners on an
- object. When a listener is registered, or when an event arrives, these
- hashes are consulted to determine which target and action pair to invoke.
- The hashes are stored in the object's meta hash, and look like this:
- // Object's meta hash
- {
- listeners: { // variable name: `listenerSet`
- "foo:changed": [ // variable name: `actions`
- target, method, flags
- ]
- }
- }
- */
- function indexOf(array, target, method) {
- var index = -1;
- for (var i = 0, l = array.length; i < l; i += 3) {
- if (target === array[i] && method === array[i+1]) { index = i; break; }
- }
- return index;
- }
- function actionsFor(obj, eventName) {
- var meta = metaFor(obj, true),
- actions;
- if (!meta.listeners) { meta.listeners = {}; }
- if (!meta.hasOwnProperty('listeners')) {
- // setup inherited copy of the listeners object
- meta.listeners = o_create(meta.listeners);
- }
- actions = meta.listeners[eventName];
- // if there are actions, but the eventName doesn't exist in our listeners, then copy them from the prototype
- if (actions && !meta.listeners.hasOwnProperty(eventName)) {
- actions = meta.listeners[eventName] = meta.listeners[eventName].slice();
- } else if (!actions) {
- actions = meta.listeners[eventName] = [];
- }
- return actions;
- }
- function actionsUnion(obj, eventName, otherActions) {
- var meta = obj[META_KEY],
- actions = meta && meta.listeners && meta.listeners[eventName];
- if (!actions) { return; }
- for (var i = actions.length - 3; i >= 0; i -= 3) {
- var target = actions[i],
- method = actions[i+1],
- flags = actions[i+2],
- actionIndex = indexOf(otherActions, target, method);
- if (actionIndex === -1) {
- otherActions.push(target, method, flags);
- }
- }
- }
- function actionsDiff(obj, eventName, otherActions) {
- var meta = obj[META_KEY],
- actions = meta && meta.listeners && meta.listeners[eventName],
- diffActions = [];
- if (!actions) { return; }
- for (var i = actions.length - 3; i >= 0; i -= 3) {
- var target = actions[i],
- method = actions[i+1],
- flags = actions[i+2],
- actionIndex = indexOf(otherActions, target, method);
- if (actionIndex !== -1) { continue; }
- otherActions.push(target, method, flags);
- diffActions.push(target, method, flags);
- }
- return diffActions;
- }
- /**
- Add an event listener
- @method addListener
- @for Ember
- @param obj
- @param {String} eventName
- @param {Object|Function} targetOrMethod A target object or a function
- @param {Function|String} method A function or the name of a function to be called on `target`
- @param {Boolean} once A flag whether a function should only be called once
- */
- function addListener(obj, eventName, target, method, once) {
- Ember.assert("You must pass at least an object and event name to Ember.addListener", !!obj && !!eventName);
- if (!method && 'function' === typeof target) {
- method = target;
- target = null;
- }
- var actions = actionsFor(obj, eventName),
- actionIndex = indexOf(actions, target, method),
- flags = 0;
- if (once) flags |= ONCE;
- if (actionIndex !== -1) { return; }
- actions.push(target, method, flags);
- if ('function' === typeof obj.didAddListener) {
- obj.didAddListener(eventName, target, method);
- }
- }
- /**
- Remove an event listener
- Arguments should match those passed to `Ember.addListener`.
- @method removeListener
- @for Ember
- @param obj
- @param {String} eventName
- @param {Object|Function} targetOrMethod A target object or a function
- @param {Function|String} method A function or the name of a function to be called on `target`
- */
- function removeListener(obj, eventName, target, method) {
- Ember.assert("You must pass at least an object and event name to Ember.removeListener", !!obj && !!eventName);
- if (!method && 'function' === typeof target) {
- method = target;
- target = null;
- }
- function _removeListener(target, method) {
- var actions = actionsFor(obj, eventName),
- actionIndex = indexOf(actions, target, method);
- // action doesn't exist, give up silently
- if (actionIndex === -1) { return; }
- actions.splice(actionIndex, 3);
- if ('function' === typeof obj.didRemoveListener) {
- obj.didRemoveListener(eventName, target, method);
- }
- }
- if (method) {
- _removeListener(target, method);
- } else {
- var meta = obj[META_KEY],
- actions = meta && meta.listeners && meta.listeners[eventName];
- if (!actions) { return; }
- for (var i = actions.length - 3; i >= 0; i -= 3) {
- _removeListener(actions[i], actions[i+1]);
- }
- }
- }
- /**
- Suspend listener during callback.
- This should only be used by the target of the event listener
- when it is taking an action that would cause the event, e.g.
- an object might suspend its property change listener while it is
- setting that property.
- @private
- @method suspendListener
- @for Ember
- @param obj
- @param {String} eventName
- @param {Object|Function} targetOrMethod A target object or a function
- @param {Function|String} method A function or the name of a function to be called on `target`
- @param {Function} callback
- */
- function suspendListener(obj, eventName, target, method, callback) {
- if (!method && 'function' === typeof target) {
- method = target;
- target = null;
- }
- var actions = actionsFor(obj, eventName),
- actionIndex = indexOf(actions, target, method);
- if (actionIndex !== -1) {
- actions[actionIndex+2] |= SUSPENDED; // mark the action as suspended
- }
- function tryable() { return callback.call(target); }
- function finalizer() { if (actionIndex !== -1) { actions[actionIndex+2] &= ~SUSPENDED; } }
- return Ember.tryFinally(tryable, finalizer);
- }
- /**
- Suspends multiple listeners during a callback.
- @private
- @method suspendListeners
- @for Ember
- @param obj
- @param {Array} eventName Array of event names
- @param {Object|Function} targetOrMethod A target object or a function
- @param {Function|String} method A function or the name of a function to be called on `target`
- @param {Function} callback
- */
- function suspendListeners(obj, eventNames, target, method, callback) {
- if (!method && 'function' === typeof target) {
- method = target;
- target = null;
- }
- var suspendedActions = [],
- actionsList = [],
- eventName, actions, i, l;
- for (i=0, l=eventNames.length; i<l; i++) {
- eventName = eventNames[i];
- actions = actionsFor(obj, eventName);
- var actionIndex = indexOf(actions, target, method);
- if (actionIndex !== -1) {
- actions[actionIndex+2] |= SUSPENDED;
- suspendedActions.push(actionIndex);
- actionsList.push(actions);
- }
- }
- function tryable() { return callback.call(target); }
- function finalizer() {
- for (var i = 0, l = suspendedActions.length; i < l; i++) {
- var actionIndex = suspendedActions[i];
- actionsList[i][actionIndex+2] &= ~SUSPENDED;
- }
- }
- return Ember.tryFinally(tryable, finalizer);
- }
- /**
- Return a list of currently watched events
- @private
- @method watchedEvents
- @for Ember
- @param obj
- */
- function watchedEvents(obj) {
- var listeners = obj[META_KEY].listeners, ret = [];
- if (listeners) {
- for(var eventName in listeners) {
- if (listeners[eventName]) { ret.push(eventName); }
- }
- }
- return ret;
- }
- /**
- Send an event. The execution of suspended listeners
- is skipped, and once listeners are removed. A listener without
- a target is executed on the passed object. If an array of actions
- is not passed, the actions stored on the passed object are invoked.
- @method sendEvent
- @for Ember
- @param obj
- @param {String} eventName
- @param {Array} params Optional parameters for each listener.
- @param {Array} actions Optional array of actions (listeners).
- @return true
- */
- function sendEvent(obj, eventName, params, actions) {
- // first give object a chance to handle it
- if (obj !== Ember && 'function' === typeof obj.sendEvent) {
- obj.sendEvent(eventName, params);
- }
- if (!actions) {
- var meta = obj[META_KEY];
- actions = meta && meta.listeners && meta.listeners[eventName];
- }
- if (!actions) { return; }
- for (var i = actions.length - 3; i >= 0; i -= 3) { // looping in reverse for once listeners
- var target = actions[i], method = actions[i+1], flags = actions[i+2];
- if (!method) { continue; }
- if (flags & SUSPENDED) { continue; }
- if (flags & ONCE) { removeListener(obj, eventName, target, method); }
- if (!target) { target = obj; }
- if ('string' === typeof method) { method = target[method]; }
- if (params) {
- method.apply(target, params);
- } else {
- method.call(target);
- }
- }
- return true;
- }
- /**
- @private
- @method hasListeners
- @for Ember
- @param obj
- @param {String} eventName
- */
- function hasListeners(obj, eventName) {
- var meta = obj[META_KEY],
- actions = meta && meta.listeners && meta.listeners[eventName];
- return !!(actions && actions.length);
- }
- /**
- @private
- @method listenersFor
- @for Ember
- @param obj
- @param {String} eventName
- */
- function listenersFor(obj, eventName) {
- var ret = [];
- var meta = obj[META_KEY],
- actions = meta && meta.listeners && meta.listeners[eventName];
- if (!actions) { return ret; }
- for (var i = 0, l = actions.length; i < l; i += 3) {
- var target = actions[i],
- method = actions[i+1];
- ret.push([target, method]);
- }
- return ret;
- }
- /**
- Define a property as a function that should be executed when
- a specified event or events are triggered.
- ``` javascript
- var Job = Ember.Object.extend({
- logCompleted: Ember.on('completed', function(){
- console.log('Job completed!');
- })
- });
- var job = Job.create();
- Ember.sendEvent(job, 'completed'); // Logs "Job completed!"
- ```
- @method on
- @for Ember
- @param {String} eventNames*
- @param {Function} func
- @return func
- */
- Ember.on = function(){
- var func = a_slice.call(arguments, -1)[0],
- events = a_slice.call(arguments, 0, -1);
- func.__ember_listens__ = events;
- return func;
- };
- Ember.addListener = addListener;
- Ember.removeListener = removeListener;
- Ember._suspendListener = suspendListener;
- Ember._suspendListeners = suspendListeners;
- Ember.sendEvent = sendEvent;
- Ember.hasListeners = hasListeners;
- Ember.watchedEvents = watchedEvents;
- Ember.listenersFor = listenersFor;
- Ember.listenersDiff = actionsDiff;
- Ember.listenersUnion = actionsUnion;
- })();
- (function() {
- var guidFor = Ember.guidFor,
- sendEvent = Ember.sendEvent;
- /*
- this.observerSet = {
- [senderGuid]: { // variable name: `keySet`
- [keyName]: listIndex
- }
- },
- this.observers = [
- {
- sender: obj,
- keyName: keyName,
- eventName: eventName,
- listeners: [
- [target, method, flags]
- ]
- },
- ...
- ]
- */
- var ObserverSet = Ember._ObserverSet = function() {
- this.clear();
- };
- ObserverSet.prototype.add = function(sender, keyName, eventName) {
- var observerSet = this.observerSet,
- observers = this.observers,
- senderGuid = guidFor(sender),
- keySet = observerSet[senderGuid],
- index;
- if (!keySet) {
- observerSet[senderGuid] = keySet = {};
- }
- index = keySet[keyName];
- if (index === undefined) {
- index = observers.push({
- sender: sender,
- keyName: keyName,
- eventName: eventName,
- listeners: []
- }) - 1;
- keySet[keyName] = index;
- }
- return observers[index].listeners;
- };
- ObserverSet.prototype.flush = function() {
- var observers = this.observers, i, len, observer, sender;
- this.clear();
- for (i=0, len=observers.length; i < len; ++i) {
- observer = observers[i];
- sender = observer.sender;
- if (sender.isDestroying || sender.isDestroyed) { continue; }
- sendEvent(sender, observer.eventName, [sender, observer.keyName], observer.listeners);
- }
- };
- ObserverSet.prototype.clear = function() {
- this.observerSet = {};
- this.observers = [];
- };
- })();
- (function() {
- var META_KEY = Ember.META_KEY,
- guidFor = Ember.guidFor,
- tryFinally = Ember.tryFinally,
- sendEvent = Ember.sendEvent,
- listenersUnion = Ember.listenersUnion,
- listenersDiff = Ember.listenersDiff,
- ObserverSet = Ember._ObserverSet,
- beforeObserverSet = new ObserverSet(),
- observerSet = new ObserverSet(),
- deferred = 0;
- // ..........................................................
- // PROPERTY CHANGES
- //
- /**
- This function is called just before an object property is about to change.
- It will notify any before observers and prepare caches among other things.
- Normally you will not need to call this method directly but if for some
- reason you can't directly watch a property you can invoke this method
- manually along with `Ember.propertyDidChange()` which you should call just
- after the property value changes.
- @method propertyWillChange
- @for Ember
- @param {Object} obj The object with the property that will change
- @param {String} keyName The property key (or path) that will change.
- @return {void}
- */
- function propertyWillChange(obj, keyName) {
- var m = obj[META_KEY],
- watching = (m && m.watching[keyName] > 0) || keyName === 'length',
- proto = m && m.proto,
- desc = m && m.descs[keyName];
- if (!watching) { return; }
- if (proto === obj) { return; }
- if (desc && desc.willChange) { desc.willChange(obj, keyName); }
- dependentKeysWillChange(obj, keyName, m);
- chainsWillChange(obj, keyName, m);
- notifyBeforeObservers(obj, keyName);
- }
- Ember.propertyWillChange = propertyWillChange;
- /**
- This function is called just after an object property has changed.
- It will notify any observers and clear caches among other things.
- Normally you will not need to call this method directly but if for some
- reason you can't directly watch a property you can invoke this method
- manually along with `Ember.propertyWillChange()` which you should call just
- before the property value changes.
- @method propertyDidChange
- @for Ember
- @param {Object} obj The object with the property that will change
- @param {String} keyName The property key (or path) that will change.
- @return {void}
- */
- function propertyDidChange(obj, keyName) {
- var m = obj[META_KEY],
- watching = (m && m.watching[keyName] > 0) || keyName === 'length',
- proto = m && m.proto,
- desc = m && m.descs[keyName];
- if (proto === obj) { return; }
- // shouldn't this mean that we're watching this key?
- if (desc && desc.didChange) { desc.didChange(obj, keyName); }
- if (!watching && keyName !== 'length') { return; }
- dependentKeysDidChange(obj, keyName, m);
- chainsDidChange(obj, keyName, m, false);
- notifyObservers(obj, keyName);
- }
- Ember.propertyDidChange = propertyDidChange;
- var WILL_SEEN, DID_SEEN;
- // called whenever a property is about to change to clear the cache of any dependent keys (and notify those properties of changes, etc...)
- function dependentKeysWillChange(obj, depKey, meta) {
- if (obj.isDestroying) { return; }
- var seen = WILL_SEEN, top = !seen;
- if (top) { seen = WILL_SEEN = {}; }
- iterDeps(propertyWillChange, obj, depKey, seen, meta);
- if (top) { WILL_SEEN = null; }
- }
- // called whenever a property has just changed to update dependent keys
- function dependentKeysDidChange(obj, depKey, meta) {
- if (obj.isDestroying) { return; }
- var seen = DID_SEEN, top = !seen;
- if (top) { seen = DID_SEEN = {}; }
- iterDeps(propertyDidChange, obj, depKey, seen, meta);
- if (top) { DID_SEEN = null; }
- }
- function iterDeps(method, obj, depKey, seen, meta) {
- var guid = guidFor(obj);
- if (!seen[guid]) seen[guid] = {};
- if (seen[guid][depKey]) return;
- seen[guid][depKey] = true;
- var deps = meta.deps;
- deps = deps && deps[depKey];
- if (deps) {
- for(var key in deps) {
- var desc = meta.descs[key];
- if (desc && desc._suspended === obj) continue;
- method(obj, key);
- }
- }
- }
- function chainsWillChange(obj, keyName, m) {
- if (!(m.hasOwnProperty('chainWatchers') &&
- m.chainWatchers[keyName])) {
- return;
- }
- var nodes = m.chainWatchers[keyName],
- events = [],
- i, l;
- for(i = 0, l = nodes.length; i < l; i++) {
- nodes[i].willChange(events);
- }
- for (i = 0, l = events.length; i < l; i += 2) {
- propertyWillChange(events[i], events[i+1]);
- }
- }
- function chainsDidChange(obj, keyName, m, suppressEvents) {
- if (!(m && m.hasOwnProperty('chainWatchers') &&
- m.chainWatchers[keyName])) {
- return;
- }
- var nodes = m.chainWatchers[keyName],
- events = suppressEvents ? null : [],
- i, l;
- for(i = 0, l = nodes.length; i < l; i++) {
- nodes[i].didChange(events);
- }
- if (suppressEvents) {
- return;
- }
- for (i = 0, l = events.length; i < l; i += 2) {
- propertyDidChange(events[i], events[i+1]);
- }
- }
- Ember.overrideChains = function(obj, keyName, m) {
- chainsDidChange(obj, keyName, m, true);
- };
- /**
- @method beginPropertyChanges
- @chainable
- @private
- */
- function beginPropertyChanges() {
- deferred++;
- }
- Ember.beginPropertyChanges = beginPropertyChanges;
- /**
- @method endPropertyChanges
- @private
- */
- function endPropertyChanges() {
- deferred--;
- if (deferred<=0) {
- beforeObserverSet.clear();
- observerSet.flush();
- }
- }
- Ember.endPropertyChanges = endPropertyChanges;
- /**
- Make a series of property changes together in an
- exception-safe way.
- ```javascript
- Ember.changeProperties(function() {
- obj1.set('foo', mayBlowUpWhenSet);
- obj2.set('bar', baz);
- });
- ```
- @method changeProperties
- @param {Function} callback
- @param [binding]
- */
- Ember.changeProperties = function(cb, binding) {
- beginPropertyChanges();
- tryFinally(cb, endPropertyChanges, binding);
- };
- function notifyBeforeObservers(obj, keyName) {
- if (obj.isDestroying) { return; }
- var eventName = keyName + ':before', listeners, diff;
- if (deferred) {
- listeners = beforeObserverSet.add(obj, keyName, eventName);
- diff = listenersDiff(obj, eventName, listeners);
- sendEvent(obj, eventName, [obj, keyName], diff);
- } else {
- sendEvent(obj, eventName, [obj, keyName]);
- }
- }
- function notifyObservers(obj, keyName) {
- if (obj.isDestroying) { return; }
- var eventName = keyName + ':change', listeners;
- if (deferred) {
- listeners = observerSet.add(obj, keyName, eventName);
- listenersUnion(obj, eventName, listeners);
- } else {
- sendEvent(obj, eventName, [obj, keyName]);
- }
- }
- })();
- (function() {
- // META_KEY
- // _getPath
- // propertyWillChange, propertyDidChange
- var META_KEY = Ember.META_KEY,
- MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER,
- IS_GLOBAL = /^([A-Z$]|([0-9][A-Z$]))/,
- getPath = Ember._getPath;
- /**
- Sets the value of a property on an object, respecting computed properties
- and notifying observers and other listeners of the change. If the
- property is not defined but the object implements the `setUnknownProperty`
- method then that will be invoked as well.
- @method set
- @for Ember
- @param {Object} obj The object to modify.
- @param {String} keyName The property key to set
- @param {Object} value The value to set
- @return {Object} the passed value.
- */
- var set = function set(obj, keyName, value, tolerant) {
- if (typeof obj === 'string') {
- Ember.assert("Path '" + obj + "' must be global if no obj is given.", IS_GLOBAL.test(obj));
- value = keyName;
- keyName = obj;
- obj = null;
- }
- Ember.assert("Cannot call set with "+ keyName +" key.", !!keyName);
- if (!obj || keyName.indexOf('.') !== -1) {
- return setPath(obj, keyName, value, tolerant);
- }
- Ember.assert("You need to provide an object and key to `set`.", !!obj && keyName !== undefined);
- Ember.assert('calling set on destroyed object', !obj.isDestroyed);
- var meta = obj[META_KEY], desc = meta && meta.descs[keyName],
- isUnknown, currentValue;
- if (desc) {
- desc.set(obj, keyName, value);
- } else {
- isUnknown = 'object' === typeof obj && !(keyName in obj);
- // setUnknownProperty is called if `obj` is an object,
- // the property does not already exist, and the
- // `setUnknownProperty` method exists on the object
- if (isUnknown && 'function' === typeof obj.setUnknownProperty) {
- obj.setUnknownProperty(keyName, value);
- } else if (meta && meta.watching[keyName] > 0) {
- if (MANDATORY_SETTER) {
- currentValue = meta.values[keyName];
- } else {
- currentValue = obj[keyName];
- }
- // only trigger a change if the value has changed
- if (value !== currentValue) {
- Ember.propertyWillChange(obj, keyName);
- if (MANDATORY_SETTER) {
- if ((currentValue === undefined && !(keyName in obj)) || !obj.propertyIsEnumerable(keyName)) {
- Ember.defineProperty(obj, keyName, null, value); // setup mandatory setter
- } else {
- meta.values[keyName] = value;
- }
- } else {
- obj[keyName] = value;
- }
- Ember.propertyDidChange(obj, keyName);
- }
- } else {
- obj[keyName] = value;
- }
- }
- return value;
- };
- // Currently used only by Ember Data tests
- if (Ember.config.overrideAccessors) {
- Ember.set = set;
- Ember.config.overrideAccessors();
- set = Ember.set;
- }
- function setPath(root, path, value, tolerant) {
- var keyName;
- // get the last part of the path
- keyName = path.slice(path.lastIndexOf('.') + 1);
- // get the first part of the part
- path = (path === keyName) ? keyName : path.slice(0, path.length-(keyName.length+1));
- // unless the path is this, look up the first part to
- // get the root
- if (path !== 'this') {
- root = getPath(root, path);
- }
- if (!keyName || keyName.length === 0) {
- throw new Ember.Error('Property set failed: You passed an empty path');
- }
- if (!root) {
- if (tolerant) { return; }
- else { throw new Ember.Error('Property set failed: object in path "'+path+'" could not be found or was destroyed.'); }
- }
- return set(root, keyName, value);
- }
- Ember.set = set;
- /**
- Error-tolerant form of `Ember.set`. Will not blow up if any part of the
- chain is `undefined`, `null`, or destroyed.
- This is primarily used when syncing bindings, which may try to update after
- an object has been destroyed.
- @method trySet
- @for Ember
- @param {Object} obj The object to modify.
- @param {String} path The property path to set
- @param {Object} value The value to set
- */
- Ember.trySet = function(root, path, value) {
- return set(root, path, value, true);
- };
- })();
- (function() {
- /**
- @module ember-metal
- */
- /*
- JavaScript (before ES6) does not have a Map implementation. Objects,
- which are often used as dictionaries, may only have Strings as keys.
- Because Ember has a way to get a unique identifier for every object
- via `Ember.guidFor`, we can implement a performant Map with arbitrary
- keys. Because it is commonly used in low-level bookkeeping, Map is
- implemented as a pure JavaScript object for performance.
- This implementation follows the current iteration of the ES6 proposal for
- maps (http://wiki.ecmascript.org/doku.php?id=harmony:simple_maps_and_sets),
- with two exceptions. First, because we need our implementation to be pleasant
- on older browsers, we do not use the `delete` name (using `remove` instead).
- Second, as we do not have the luxury of in-VM iteration, we implement a
- forEach method for iteration.
- Map is mocked out to look like an Ember object, so you can do
- `Ember.Map.create()` for symmetry with other Ember classes.
- */
- var set = Ember.set,
- guidFor = Ember.guidFor,
- indexOf = Ember.ArrayPolyfills.indexOf;
- var copy = function(obj) {
- var output = {};
- for (var prop in obj) {
- if (obj.hasOwnProperty(prop)) { output[prop] = obj[prop]; }
- }
- return output;
- };
- var copyMap = function(original, newObject) {
- var keys = original.keys.copy(),
- values = copy(original.values);
- newObject.keys = keys;
- newObject.values = values;
- newObject.length = original.length;
- return newObject;
- };
- /**
- This class is used internally by Ember and Ember Data.
- Please do not use it at this time. We plan to clean it up
- and add many tests soon.
- @class OrderedSet
- @namespace Ember
- @constructor
- @private
- */
- var OrderedSet = Ember.OrderedSet = function() {
- this.clear();
- };
- /**
- @method create
- @static
- @return {Ember.OrderedSet}
- */
- OrderedSet.create = function() {
- return new OrderedSet();
- };
- OrderedSet.prototype = {
- /**
- @method clear
- */
- clear: function() {
- this.presenceSet = {};
- this.list = [];
- },
- /**
- @method add
- @param obj
- */
- add: function(obj) {
- var guid = guidFor(obj),
- presenceSet = this.presenceSet,
- list = this.list;
- if (guid in presenceSet) { return; }
- presenceSet[guid] = true;
- list.push(obj);
- },
- /**
- @method remove
- @param obj
- */
- remove: function(obj) {
- var guid = guidFor(obj),
- presenceSet = this.presenceSet,
- list = this.list;
- delete presenceSet[guid];
- var index = indexOf.call(list, obj);
- if (index > -1) {
- list.splice(index, 1);
- }
- },
- /**
- @method isEmpty
- @return {Boolean}
- */
- isEmpty: function() {
- return this.list.length === 0;
- },
- /**
- @method has
- @param obj
- @return {Boolean}
- */
- has: function(obj) {
- var guid = guidFor(obj),
- presenceSet = this.presenceSet;
- return guid in presenceSet;
- },
- /**
- @method forEach
- @param {Function} fn
- @param self
- */
- forEach: function(fn, self) {
- // allow mutation during iteration
- var list = this.toArray();
- for (var i = 0, j = list.length; i < j; i++) {
- fn.call(self, list[i]);
- }
- },
- /**
- @method toArray
- @return {Array}
- */
- toArray: function() {
- return this.list.slice();
- },
- /**
- @method copy
- @return {Ember.OrderedSet}
- */
- copy: function() {
- var set = new OrderedSet();
- set.presenceSet = copy(this.presenceSet);
- set.list = this.toArray();
- return set;
- }
- };
- /**
- A Map stores values indexed by keys. Unlike JavaScript's
- default Objects, the keys of a Map can be any JavaScript
- object.
- Internally, a Map has two data structures:
- 1. `keys`: an OrderedSet of all of the existing keys
- 2. `values`: a JavaScript Object indexed by the `Ember.guidFor(key)`
- When a key/value pair is added for the first time, we
- add the key to the `keys` OrderedSet, and create or
- replace an entry in `values`. When an entry is deleted,
- we delete its entry in `keys` and `values`.
- @class Map
- @namespace Ember
- @private
- @constructor
- */
- var Map = Ember.Map = function() {
- this.keys = Ember.OrderedSet.create();
- this.values = {};
- };
- /**
- @method create
- @static
- */
- Map.create = function() {
- return new Map();
- };
- Map.prototype = {
- /**
- This property will change as the number of objects in the map changes.
-
- @property length
- @type number
- @default 0
- */
- length: 0,
-
-
- /**
- Retrieve the value associated with a given key.
- @method get
- @param {*} key
- @return {*} the value associated with the key, or `undefined`
- */
- get: function(key) {
- var values = this.values,
- guid = guidFor(key);
- return values[guid];
- },
- /**
- Adds a value to the map. If a value for the given key has already been
- provided, the new value will replace the old value.
- @method set
- @param {*} key
- @param {*} value
- */
- set: function(key, value) {
- var keys = this.keys,
- values = this.values,
- guid = guidFor(key);
- keys.add(key);
- values[guid] = value;
- set(this, 'length', keys.list.length);
- },
- /**
- Removes a value from the map for an associated key.
- @method remove
- @param {*} key
- @return {Boolean} true if an item was removed, false otherwise
- */
- remove: function(key) {
- // don't use ES6 "delete" because it will be annoying
- // to use in browsers that are not ES6 friendly;
- var keys = this.keys,
- values = this.values,
- guid = guidFor(key);
- if (values.hasOwnProperty(guid)) {
- keys.remove(key);
- delete values[guid];
- set(this, 'length', keys.list.length);
- return true;
- } else {
- return false;
- }
- },
- /**
- Check whether a key is present.
- @method has
- @param {*} key
- @return {Boolean} true if the item was present, false otherwise
- */
- has: function(key) {
- var values = this.values,
- guid = guidFor(key);
- return values.hasOwnProperty(guid);
- },
- /**
- Iterate over all the keys and values. Calls the function once
- for each key, passing in the key and value, in that order.
- The keys are guaranteed to be iterated over in insertion order.
- @method forEach
- @param {Function} callback
- @param {*} self if passed, the `this` value inside the
- callback. By default, `this` is the map.
- */
- forEach: function(callback, self) {
- var keys = this.keys,
- values = this.values;
- keys.forEach(function(key) {
- var guid = guidFor(key);
- callback.call(self, key, values[guid]);
- });
- },
- /**
- @method copy
- @return {Ember.Map}
- */
- copy: function() {
- return copyMap(this, new Map());
- }
- };
- /**
- @class MapWithDefault
- @namespace Ember
- @extends Ember.Map
- @private
- @constructor
- @param [options]
- @param {*} [options.defaultValue]
- */
- var MapWithDefault = Ember.MapWithDefault = function(options) {
- Map.call(this);
- this.defaultValue = options.defaultValue;
- };
- /**
- @method create
- @static
- @param [options]
- @param {*} [options.defaultValue]
- @return {Ember.MapWithDefault|Ember.Map} If options are passed, returns
- `Ember.MapWithDefault` otherwise returns `Ember.Map`
- */
- MapWithDefault.create = function(options) {
- if (options) {
- return new MapWithDefault(options);
- } else {
- return new Map();
- }
- };
- MapWithDefault.prototype = Ember.create(Map.prototype);
- /**
- Retrieve the value associated with a given key.
- @method get
- @param {*} key
- @return {*} the value associated with the key, or the default value
- */
- MapWithDefault.prototype.get = function(key) {
- var hasValue = this.has(key);
- if (hasValue) {
- return Map.prototype.get.call(this, key);
- } else {
- var defaultValue = this.defaultValue(key);
- this.set(key, defaultValue);
- return defaultValue;
- }
- };
- /**
- @method copy
- @return {Ember.MapWithDefault}
- */
- MapWithDefault.prototype.copy = function() {
- return copyMap(this, new MapWithDefault({
- defaultValue: this.defaultValue
- }));
- };
- })();
- (function() {
- function consoleMethod(name) {
- var consoleObj, logToConsole;
- if (Ember.imports.console) {
- consoleObj = Ember.imports.console;
- } else if (typeof console !== 'undefined') {
- consoleObj = console;
- }
- var method = typeof consoleObj === 'object' ? consoleObj[name] : null;
- if (method) {
- // Older IE doesn't support apply, but Chrome needs it
- if (method.apply) {
- logToConsole = function() {
- method.apply(consoleObj, arguments);
- };
- logToConsole.displayName = 'console.' + name;
- return logToConsole;
- } else {
- return function() {
- var message = Array.prototype.join.call(arguments, ', ');
- method(message);
- };
- }
- }
- }
- function assertPolyfill(test, message) {
- if (!test) {
- try {
- // attempt to preserve the stack
- throw new Ember.Error("assertion failed: " + message);
- } catch(error) {
- setTimeout(function() {
- throw error;
- }, 0);
- }
- }
- }
- /**
- Inside Ember-Metal, simply uses the methods from `imports.console`.
- Override this to provide more robust logging functionality.
- @class Logger
- @namespace Ember
- */
- Ember.Logger = {
- /**
- Logs the arguments to the console.
- You can pass as many arguments as you want and they will be joined together with a space.
- ```javascript
- var foo = 1;
- Ember.Logger.log('log value of foo:', foo); // "log value of foo: 1" will be printed to the console
- ```
- @method log
- @for Ember.Logger
- @param {*} arguments
- */
- log: consoleMethod('log') || Ember.K,
- /**
- Prints the arguments to the console with a warning icon.
- You can pass as many arguments as you want and they will be joined together with a space.
- ```javascript
- Ember.Logger.warn('Something happened!'); // "Something happened!" will be printed to the console with a warning icon.
- ```
- @method warn
- @for Ember.Logger
- @param {*} arguments
- */
- warn: consoleMethod('warn') || Ember.K,
- /**
- Prints the arguments to the console with an error icon, red text and a stack trace.
- You can pass as many arguments as you want and they will be joined together with a space.
- ```javascript
- Ember.Logger.error('Danger! Danger!'); // "Danger! Danger!" will be printed to the console in red text.
- ```
- @method error
- @for Ember.Logger
- @param {*} arguments
- */
- error: consoleMethod('error') || Ember.K,
- /**
- Logs the arguments to the console.
- You can pass as many arguments as you want and they will be joined together with a space.
- ```javascript
- var foo = 1;
- Ember.Logger.info('log value of foo:', foo); // "log value of foo: 1" will be printed to the console
- ```
- @method info
- @for Ember.Logger
- @param {*} arguments
- */
- info: consoleMethod('info') || Ember.K,
- /**
- Logs the arguments to the console in blue text.
- You can pass as many arguments as you want and they will be joined together with a space.
- ```javascript
- var foo = 1;
- Ember.Logger.debug('log value of foo:', foo); // "log value of foo: 1" will be printed to the console
- ```
- @method debug
- @for Ember.Logger
- @param {*} arguments
- */
- debug: consoleMethod('debug') || consoleMethod('info') || Ember.K,
- /**
- If the value passed into `Ember.Logger.assert` is not truthy it will throw an error with a stack trace.
- ```javascript
- Ember.Logger.assert(true); // undefined
- Ember.Logger.assert(true === false); // Throws an Assertion failed error.
- ```
- @method assert
- @for Ember.Logger
- @param {Boolean} bool Value to test
- */
- assert: consoleMethod('assert') || assertPolyfill
- };
- })();
- (function() {
- /**
- @module ember-metal
- */
- var META_KEY = Ember.META_KEY,
- metaFor = Ember.meta,
- objectDefineProperty = Ember.platform.defineProperty;
- var MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER;
- // ..........................................................
- // DESCRIPTOR
- //
- /**
- Objects of this type can implement an interface to respond to requests to
- get and set. The default implementation handles simple properties.
- You generally won't need to create or subclass this directly.
- @class Descriptor
- @namespace Ember
- @private
- @constructor
- */
- Ember.Descriptor = function() {};
- // ..........................................................
- // DEFINING PROPERTIES API
- //
- var MANDATORY_SETTER_FUNCTION = Ember.MANDATORY_SETTER_FUNCTION = function(value) {
- Ember.assert("You must use Ember.set() to access this property (of " + this + ")", false);
- };
- var DEFAULT_GETTER_FUNCTION = Ember.DEFAULT_GETTER_FUNCTION = function(name) {
- return function() {
- var meta = this[META_KEY];
- return meta && meta.values[name];
- };
- };
- /**
- NOTE: This is a low-level method used by other parts of the API. You almost
- never want to call this method directly. Instead you should use
- `Ember.mixin()` to define new properties.
- Defines a property on an object. This method works much like the ES5
- `Object.defineProperty()` method except that it can also accept computed
- properties and other special descriptors.
- Normally this method takes only three parameters. However if you pass an
- instance of `Ember.Descriptor` as the third param then you can pass an
- optional value as the fourth parameter. This is often more efficient than
- creating new descriptor hashes for each property.
- ## Examples
- ```javascript
- // ES5 compatible mode
- Ember.defineProperty(contact, 'firstName', {
- writable: true,
- configurable: false,
- enumerable: true,
- value: 'Charles'
- });
- // define a simple property
- Ember.defineProperty(contact, 'lastName', undefined, 'Jolley');
- // define a computed property
- Ember.defineProperty(contact, 'fullName', Ember.computed(function() {
- return this.firstName+' '+this.lastName;
- }).property('firstName', 'lastName'));
- ```
- @private
- @method defineProperty
- @for Ember
- @param {Object} obj the object to define this property on. This may be a prototype.
- @param {String} keyName the name of the property
- @param {Ember.Descriptor} [desc] an instance of `Ember.Descriptor` (typically a
- computed property) or an ES5 descriptor.
- You must provide this or `data` but not both.
- @param {*} [data] something other than a descriptor, that will
- become the explicit value of this property.
- */
- Ember.defineProperty = function(obj, keyName, desc, data, meta) {
- var descs, existingDesc, watching, value;
- if (!meta) meta = metaFor(obj);
- descs = meta.descs;
- existingDesc = meta.descs[keyName];
- watching = meta.watching[keyName] > 0;
- if (existingDesc instanceof Ember.Descriptor) {
- existingDesc.teardown(obj, keyName);
- }
- if (desc instanceof Ember.Descriptor) {
- value = desc;
- descs[keyName] = desc;
- if (MANDATORY_SETTER && watching) {
- objectDefineProperty(obj, keyName, {
- configurable: true,
- enumerable: true,
- writable: true,
- value: undefined // make enumerable
- });
- } else {
- obj[keyName] = undefined; // make enumerable
- }
- } else {
- descs[keyName] = undefined; // shadow descriptor in proto
- if (desc == null) {
- value = data;
- if (MANDATORY_SETTER && watching) {
- meta.values[keyName] = data;
- objectDefineProperty(obj, keyName, {
- configurable: true,
- enumerable: true,
- set: MANDATORY_SETTER_FUNCTION,
- get: DEFAULT_GETTER_FUNCTION(keyName)
- });
- } else {
- obj[keyName] = data;
- }
- } else {
- value = desc;
- // compatibility with ES5
- objectDefineProperty(obj, keyName, desc);
- }
- }
- // if key is being watched, override chains that
- // were initialized with the prototype
- if (watching) { Ember.overrideChains(obj, keyName, meta); }
- // The `value` passed to the `didDefineProperty` hook is
- // either the descriptor or data, whichever was passed.
- if (obj.didDefineProperty) { obj.didDefineProperty(obj, keyName, value); }
- return this;
- };
- })();
- (function() {
- var get = Ember.get;
- /**
- To get multiple properties at once, call `Ember.getProperties`
- with an object followed by a list of strings or an array:
- ```javascript
- Ember.getProperties(record, 'firstName', 'lastName', 'zipCode'); // { firstName: 'John', lastName: 'Doe', zipCode: '10011' }
- ```
- is equivalent to:
- ```javascript
- Ember.getProperties(record, ['firstName', 'lastName', 'zipCode']); // { firstName: 'John', lastName: 'Doe', zipCode: '10011' }
- ```
- @method getProperties
- @param obj
- @param {String...|Array} list of keys to get
- @return {Hash}
- */
- Ember.getProperties = function(obj) {
- var ret = {},
- propertyNames = arguments,
- i = 1;
- if (arguments.length === 2 && Ember.typeOf(arguments[1]) === 'array') {
- i = 0;
- propertyNames = arguments[1];
- }
- for(var len = propertyNames.length; i < len; i++) {
- ret[propertyNames[i]] = get(obj, propertyNames[i]);
- }
- return ret;
- };
- })();
- (function() {
- var changeProperties = Ember.changeProperties,
- set = Ember.set;
- /**
- Set a list of properties on an object. These properties are set inside
- a single `beginPropertyChanges` and `endPropertyChanges` batch, so
- observers will be buffered.
- ```javascript
- anObject.setProperties({
- firstName: "Stanley",
- lastName: "Stuart",
- age: "21"
- })
- ```
- @method setProperties
- @param self
- @param {Object} hash
- @return self
- */
- Ember.setProperties = function(self, hash) {
- changeProperties(function() {
- for(var prop in hash) {
- if (hash.hasOwnProperty(prop)) { set(self, prop, hash[prop]); }
- }
- });
- return self;
- };
- })();
- (function() {
- var metaFor = Ember.meta, // utils.js
- typeOf = Ember.typeOf, // utils.js
- MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER,
- o_defineProperty = Ember.platform.defineProperty;
- Ember.watchKey = function(obj, keyName, meta) {
- // can't watch length on Array - it is special...
- if (keyName === 'length' && typeOf(obj) === 'array') { return; }
- var m = meta || metaFor(obj), watching = m.watching;
- // activate watching first time
- if (!watching[keyName]) {
- watching[keyName] = 1;
- if ('function' === typeof obj.willWatchProperty) {
- obj.willWatchProperty(keyName);
- }
- if (MANDATORY_SETTER && keyName in obj) {
- m.values[keyName] = obj[keyName];
- o_defineProperty(obj, keyName, {
- configurable: true,
- enumerable: obj.propertyIsEnumerable(keyName),
- set: Ember.MANDATORY_SETTER_FUNCTION,
- get: Ember.DEFAULT_GETTER_FUNCTION(keyName)
- });
- }
- } else {
- watching[keyName] = (watching[keyName] || 0) + 1;
- }
- };
- Ember.unwatchKey = function(obj, keyName, meta) {
- var m = meta || metaFor(obj), watching = m.watching;
- if (watching[keyName] === 1) {
- watching[keyName] = 0;
- if ('function' === typeof obj.didUnwatchProperty) {
- obj.didUnwatchProperty(keyName);
- }
- if (MANDATORY_SETTER && keyName in obj) {
- o_defineProperty(obj, keyName, {
- configurable: true,
- enumerable: obj.propertyIsEnumerable(keyName),
- set: function(val) {
- // redefine to set as enumerable
- o_defineProperty(obj, keyName, {
- configurable: true,
- writable: true,
- enumerable: true,
- value: val
- });
- delete m.values[keyName];
- },
- get: Ember.DEFAULT_GETTER_FUNCTION(keyName)
- });
- }
- } else if (watching[keyName] > 1) {
- watching[keyName]--;
- }
- };
- })();
- (function() {
- var metaFor = Ember.meta, // utils.js
- get = Ember.get, // property_get.js
- normalizeTuple = Ember.normalizeTuple, // property_get.js
- forEach = Ember.ArrayPolyfills.forEach, // array.js
- warn = Ember.warn,
- watchKey = Ember.watchKey,
- unwatchKey = Ember.unwatchKey,
- FIRST_KEY = /^([^\.\*]+)/,
- META_KEY = Ember.META_KEY;
- function firstKey(path) {
- return path.match(FIRST_KEY)[0];
- }
- var pendingQueue = [];
- // attempts to add the pendingQueue chains again. If some of them end up
- // back in the queue and reschedule is true, schedules a timeout to try
- // again.
- Ember.flushPendingChains = function() {
- if (pendingQueue.length === 0) { return; } // nothing to do
- var queue = pendingQueue;
- pendingQueue = [];
- forEach.call(queue, function(q) { q[0].add(q[1]); });
- warn('Watching an undefined global, Ember expects watched globals to be setup by the time the run loop is flushed, check for typos', pendingQueue.length === 0);
- };
- function addChainWatcher(obj, keyName, node) {
- if (!obj || ('object' !== typeof obj)) { return; } // nothing to do
- var m = metaFor(obj), nodes = m.chainWatchers;
- if (!m.hasOwnProperty('chainWatchers')) {
- nodes = m.chainWatchers = {};
- }
- if (!nodes[keyName]) { nodes[keyName] = []; }
- nodes[keyName].push(node);
- watchKey(obj, keyName, m);
- }
- var removeChainWatcher = Ember.removeChainWatcher = function(obj, keyName, node) {
- if (!obj || 'object' !== typeof obj) { return; } // nothing to do
- var m = obj[META_KEY];
- if (m && !m.hasOwnProperty('chainWatchers')) { return; } // nothing to do
- var nodes = m && m.chainWatchers;
- if (nodes && nodes[keyName]) {
- nodes = nodes[keyName];
- for (var i = 0, l = nodes.length; i < l; i++) {
- if (nodes[i] === node) { nodes.splice(i, 1); }
- }
- }
- unwatchKey(obj, keyName, m);
- };
- // A ChainNode watches a single key on an object. If you provide a starting
- // value for the key then the node won't actually watch it. For a root node
- // pass null for parent and key and object for value.
- var ChainNode = Ember._ChainNode = function(parent, key, value) {
- this._parent = parent;
- this._key = key;
- // _watching is true when calling get(this._parent, this._key) will
- // return the value of this node.
- //
- // It is false for the root of a chain (because we have no parent)
- // and for global paths (because the parent node is the object with
- // the observer on it)
- this._watching = value===undefined;
- this._value = value;
- this._paths = {};
- if (this._watching) {
- this._object = parent.value();
- if (this._object) { addChainWatcher(this._object, this._key, this); }
- }
- // Special-case: the EachProxy relies on immediate evaluation to
- // establish its observers.
- //
- // TODO: Replace this with an efficient callback that the EachProxy
- // can implement.
- if (this._parent && this._parent._key === '@each') {
- this.value();
- }
- };
- var ChainNodePrototype = ChainNode.prototype;
- function lazyGet(obj, key) {
- if (!obj) return undefined;
- var meta = obj[META_KEY];
- // check if object meant only to be a prototype
- if (meta && meta.proto === obj) return undefined;
- if (key === "@each") return get(obj, key);
- // if a CP only return cached value
- var desc = meta && meta.descs[key];
- if (desc && desc._cacheable) {
- if (key in meta.cache) {
- return meta.cache[key];
- } else {
- return undefined;
- }
- }
- return get(obj, key);
- }
- ChainNodePrototype.value = function() {
- if (this._value === undefined && this._watching) {
- var obj = this._parent.value();
- this._value = lazyGet(obj, this._key);
- }
- return this._value;
- };
- ChainNodePrototype.destroy = function() {
- if (this._watching) {
- var obj = this._object;
- if (obj) { removeChainWatcher(obj, this._key, this); }
- this._watching = false; // so future calls do nothing
- }
- };
- // copies a top level object only
- ChainNodePrototype.copy = function(obj) {
- var ret = new ChainNode(null, null, obj),
- paths = this._paths, path;
- for (path in paths) {
- if (paths[path] <= 0) { continue; } // this check will also catch non-number vals.
- ret.add(path);
- }
- return ret;
- };
- // called on the root node of a chain to setup watchers on the specified
- // path.
- ChainNodePrototype.add = function(path) {
- var obj, tuple, key, src, paths;
- paths = this._paths;
- paths[path] = (paths[path] || 0) + 1;
- obj = this.value();
- tuple = normalizeTuple(obj, path);
- // the path was a local path
- if (tuple[0] && tuple[0] === obj) {
- path = tuple[1];
- key = firstKey(path);
- path = path.slice(key.length+1);
- // global path, but object does not exist yet.
- // put into a queue and try to connect later.
- } else if (!tuple[0]) {
- pendingQueue.push([this, path]);
- tuple.length = 0;
- return;
- // global path, and object already exists
- } else {
- src = tuple[0];
- key = path.slice(0, 0-(tuple[1].length+1));
- path = tuple[1];
- }
- tuple.length = 0;
- this.chain(key, path, src);
- };
- // called on the root node of a chain to teardown watcher on the specified
- // path
- ChainNodePrototype.remove = function(path) {
- var obj, tuple, key, src, paths;
- paths = this._paths;
- if (paths[path] > 0) { paths[path]--; }
- obj = this.value();
- tuple = normalizeTuple(obj, path);
- if (tuple[0] === obj) {
- path = tuple[1];
- key = firstKey(path);
- path = path.slice(key.length+1);
- } else {
- src = tuple[0];
- key = path.slice(0, 0-(tuple[1].length+1));
- path = tuple[1];
- }
- tuple.length = 0;
- this.unchain(key, path);
- };
- ChainNodePrototype.count = 0;
- ChainNodePrototype.chain = function(key, path, src) {
- var chains = this._chains, node;
- if (!chains) { chains = this._chains = {}; }
- node = chains[key];
- if (!node) { node = chains[key] = new ChainNode(this, key, src); }
- node.count++; // count chains...
- // chain rest of path if there is one
- if (path && path.length>0) {
- key = firstKey(path);
- path = path.slice(key.length+1);
- node.chain(key, path); // NOTE: no src means it will observe changes...
- }
- };
- ChainNodePrototype.unchain = function(key, path) {
- var chains = this._chains, node = chains[key];
- // unchain rest of path first...
- if (path && path.length>1) {
- key = firstKey(path);
- path = path.slice(key.length+1);
- node.unchain(key, path);
- }
- // delete node if needed.
- node.count--;
- if (node.count<=0) {
- delete chains[node._key];
- node.destroy();
- }
- };
- ChainNodePrototype.willChange = function(events) {
- var chains = this._chains;
- if (chains) {
- for(var key in chains) {
- if (!chains.hasOwnProperty(key)) { continue; }
- chains[key].willChange(events);
- }
- }
- if (this._parent) { this._parent.chainWillChange(this, this._key, 1, events); }
- };
- ChainNodePrototype.chainWillChange = function(chain, path, depth, events) {
- if (this._key) { path = this._key + '.' + path; }
- if (this._parent) {
- this._parent.chainWillChange(this, path, depth+1, events);
- } else {
- if (depth > 1) {
- events.push(this.value(), path);
- }
- path = 'this.' + path;
- if (this._paths[path] > 0) {
- events.push(this.value(), path);
- }
- }
- };
- ChainNodePrototype.chainDidChange = function(chain, path, depth, events) {
- if (this._key) { path = this._key + '.' + path; }
- if (this._parent) {
- this._parent.chainDidChange(this, path, depth+1, events);
- } else {
- if (depth > 1) {
- events.push(this.value(), path);
- }
- path = 'this.' + path;
- if (this._paths[path] > 0) {
- events.push(this.value(), path);
- }
- }
- };
- ChainNodePrototype.didChange = function(events) {
- // invalidate my own value first.
- if (this._watching) {
- var obj = this._parent.value();
- if (obj !== this._object) {
- removeChainWatcher(this._object, this._key, this);
- this._object = obj;
- addChainWatcher(obj, this._key, this);
- }
- this._value = undefined;
- // Special-case: the EachProxy relies on immediate evaluation to
- // establish its observers.
- if (this._parent && this._parent._key === '@each')
- this.value();
- }
- // then notify chains...
- var chains = this._chains;
- if (chains) {
- for(var key in chains) {
- if (!chains.hasOwnProperty(key)) { continue; }
- chains[key].didChange(events);
- }
- }
- // if no events are passed in then we only care about the above wiring update
- if (events === null) { return; }
- // and finally tell parent about my path changing...
- if (this._parent) { this._parent.chainDidChange(this, this._key, 1, events); }
- };
- Ember.finishChains = function(obj) {
- // We only create meta if we really have to
- var m = obj[META_KEY], chains = m && m.chains;
- if (chains) {
- if (chains.value() !== obj) {
- metaFor(obj).chains = chains = chains.copy(obj);
- } else {
- chains.didChange(null);
- }
- }
- };
- })();
- (function() {
- /**
- @module ember-metal
- */
- var forEach = Ember.EnumerableUtils.forEach,
- BRACE_EXPANSION = /^((?:[^\.]*\.)*)\{(.*)\}$/;
- /**
- Expands `pattern`, invoking `callback` for each expansion.
- The only pattern supported is brace-expansion, anything else will be passed
- once to `callback` directly. Brace expansion can only appear at the end of a
- pattern, for example as the last item in a chain.
- Example
- ```js
- function echo(arg){ console.log(arg); }
- Ember.expandProperties('foo.bar', echo); //=> 'foo.bar'
- Ember.expandProperties('{foo,bar}', echo); //=> 'foo', 'bar'
- Ember.expandProperties('foo.{bar,baz}', echo); //=> 'foo.bar', 'foo.baz'
- Ember.expandProperties('{foo,bar}.baz', echo); //=> '{foo,bar}.baz'
- ```
- @method
- @private
- @param {string} pattern The property pattern to expand.
- @param {function} callback The callback to invoke. It is invoked once per
- expansion, and is passed the expansion.
- */
- Ember.expandProperties = function (pattern, callback) {
- var match, prefix, list;
- if (match = BRACE_EXPANSION.exec(pattern)) {
- prefix = match[1];
- list = match[2];
- forEach(list.split(','), function (suffix) {
- callback(prefix + suffix);
- });
- } else {
- callback(pattern);
- }
- };
- })();
- (function() {
- var metaFor = Ember.meta, // utils.js
- typeOf = Ember.typeOf, // utils.js
- ChainNode = Ember._ChainNode; // chains.js
- // get the chains for the current object. If the current object has
- // chains inherited from the proto they will be cloned and reconfigured for
- // the current object.
- function chainsFor(obj, meta) {
- var m = meta || metaFor(obj), ret = m.chains;
- if (!ret) {
- ret = m.chains = new ChainNode(null, null, obj);
- } else if (ret.value() !== obj) {
- ret = m.chains = ret.copy(obj);
- }
- return ret;
- }
- Ember.watchPath = function(obj, keyPath, meta) {
- // can't watch length on Array - it is special...
- if (keyPath === 'length' && typeOf(obj) === 'array') { return; }
- var m = meta || metaFor(obj), watching = m.watching;
- if (!watching[keyPath]) { // activate watching first time
- watching[keyPath] = 1;
- chainsFor(obj, m).add(keyPath);
- } else {
- watching[keyPath] = (watching[keyPath] || 0) + 1;
- }
- };
- Ember.unwatchPath = function(obj, keyPath, meta) {
- var m = meta || metaFor(obj), watching = m.watching;
- if (watching[keyPath] === 1) {
- watching[keyPath] = 0;
- chainsFor(obj, m).remove(keyPath);
- } else if (watching[keyPath] > 1) {
- watching[keyPath]--;
- }
- };
- })();
- (function() {
- /**
- @module ember-metal
- */
- var metaFor = Ember.meta, // utils.js
- GUID_KEY = Ember.GUID_KEY, // utils.js
- META_KEY = Ember.META_KEY, // utils.js
- removeChainWatcher = Ember.removeChainWatcher,
- watchKey = Ember.watchKey, // watch_key.js
- unwatchKey = Ember.unwatchKey,
- watchPath = Ember.watchPath, // watch_path.js
- unwatchPath = Ember.unwatchPath,
- typeOf = Ember.typeOf, // utils.js
- generateGuid = Ember.generateGuid,
- IS_PATH = /[\.\*]/;
- // returns true if the passed path is just a keyName
- function isKeyName(path) {
- return path==='*' || !IS_PATH.test(path);
- }
- /**
- Starts watching a property on an object. Whenever the property changes,
- invokes `Ember.propertyWillChange` and `Ember.propertyDidChange`. This is the
- primitive used by observers and dependent keys; usually you will never call
- this method directly but instead use higher level methods like
- `Ember.addObserver()`
- @private
- @method watch
- @for Ember
- @param obj
- @param {String} keyName
- */
- Ember.watch = function(obj, _keyPath, m) {
- // can't watch length on Array - it is special...
- if (_keyPath === 'length' && typeOf(obj) === 'array') { return; }
- if (isKeyName(_keyPath)) {
- watchKey(obj, _keyPath, m);
- } else {
- watchPath(obj, _keyPath, m);
- }
- };
- Ember.isWatching = function isWatching(obj, key) {
- var meta = obj[META_KEY];
- return (meta && meta.watching[key]) > 0;
- };
- Ember.watch.flushPending = Ember.flushPendingChains;
- Ember.unwatch = function(obj, _keyPath, m) {
- // can't watch length on Array - it is special...
- if (_keyPath === 'length' && typeOf(obj) === 'array') { return; }
- if (isKeyName(_keyPath)) {
- unwatchKey(obj, _keyPath, m);
- } else {
- unwatchPath(obj, _keyPath, m);
- }
- };
- /**
- Call on an object when you first beget it from another object. This will
- setup any chained watchers on the object instance as needed. This method is
- safe to call multiple times.
- @private
- @method rewatch
- @for Ember
- @param obj
- */
- Ember.rewatch = function(obj) {
- var m = obj[META_KEY], chains = m && m.chains;
- // make sure the object has its own guid.
- if (GUID_KEY in obj && !obj.hasOwnProperty(GUID_KEY)) {
- generateGuid(obj);
- }
- // make sure any chained watchers update.
- if (chains && chains.value() !== obj) {
- m.chains = chains.copy(obj);
- }
- };
- var NODE_STACK = [];
- /**
- Tears down the meta on an object so that it can be garbage collected.
- Multiple calls will have no effect.
- @method destroy
- @for Ember
- @param {Object} obj the object to destroy
- @return {void}
- */
- Ember.destroy = function (obj) {
- var meta = obj[META_KEY], node, nodes, key, nodeObject;
- if (meta) {
- obj[META_KEY] = null;
- // remove chainWatchers to remove circular references that would prevent GC
- node = meta.chains;
- if (node) {
- NODE_STACK.push(node);
- // process tree
- while (NODE_STACK.length > 0) {
- node = NODE_STACK.pop();
- // push children
- nodes = node._chains;
- if (nodes) {
- for (key in nodes) {
- if (nodes.hasOwnProperty(key)) {
- NODE_STACK.push(nodes[key]);
- }
- }
- }
- // remove chainWatcher in node object
- if (node._watching) {
- nodeObject = node._object;
- if (nodeObject) {
- removeChainWatcher(nodeObject, node._key, node);
- }
- }
- }
- }
- }
- };
- })();
- (function() {
- /**
- @module ember-metal
- */
- Ember.warn("The CP_DEFAULT_CACHEABLE flag has been removed and computed properties are always cached by default. Use `volatile` if you don't want caching.", Ember.ENV.CP_DEFAULT_CACHEABLE !== false);
- var get = Ember.get,
- set = Ember.set,
- metaFor = Ember.meta,
- a_slice = [].slice,
- o_create = Ember.create,
- META_KEY = Ember.META_KEY,
- watch = Ember.watch,
- unwatch = Ember.unwatch;
- var expandProperties = Ember.expandProperties;
- // ..........................................................
- // DEPENDENT KEYS
- //
- // data structure:
- // meta.deps = {
- // 'depKey': {
- // 'keyName': count,
- // }
- // }
- /*
- This function returns a map of unique dependencies for a
- given object and key.
- */
- function keysForDep(depsMeta, depKey) {
- var keys = depsMeta[depKey];
- if (!keys) {
- // if there are no dependencies yet for a the given key
- // create a new empty list of dependencies for the key
- keys = depsMeta[depKey] = {};
- } else if (!depsMeta.hasOwnProperty(depKey)) {
- // otherwise if the dependency list is inherited from
- // a superclass, clone the hash
- keys = depsMeta[depKey] = o_create(keys);
- }
- return keys;
- }
- function metaForDeps(meta) {
- return keysForDep(meta, 'deps');
- }
- function addDependentKeys(desc, obj, keyName, meta) {
- // the descriptor has a list of dependent keys, so
- // add all of its dependent keys.
- var depKeys = desc._dependentKeys, depsMeta, idx, len, depKey, keys;
- if (!depKeys) return;
- depsMeta = metaForDeps(meta);
- for(idx = 0, len = depKeys.length; idx < len; idx++) {
- depKey = depKeys[idx];
- // Lookup keys meta for depKey
- keys = keysForDep(depsMeta, depKey);
- // Increment the number of times depKey depends on keyName.
- keys[keyName] = (keys[keyName] || 0) + 1;
- // Watch the depKey
- watch(obj, depKey, meta);
- }
- }
- function removeDependentKeys(desc, obj, keyName, meta) {
- // the descriptor has a list of dependent keys, so
- // add all of its dependent keys.
- var depKeys = desc._dependentKeys, depsMeta, idx, len, depKey, keys;
- if (!depKeys) return;
- depsMeta = metaForDeps(meta);
- for(idx = 0, len = depKeys.length; idx < len; idx++) {
- depKey = depKeys[idx];
- // Lookup keys meta for depKey
- keys = keysForDep(depsMeta, depKey);
- // Increment the number of times depKey depends on keyName.
- keys[keyName] = (keys[keyName] || 0) - 1;
- // Watch the depKey
- unwatch(obj, depKey, meta);
- }
- }
- // ..........................................................
- // COMPUTED PROPERTY
- //
- /**
- A computed property transforms an objects function into a property.
- By default the function backing the computed property will only be called
- once and the result will be cached. You can specify various properties
- that your computed property is dependent on. This will force the cached
- result to be recomputed if the dependencies are modified.
- In the following example we declare a computed property (by calling
- `.property()` on the fullName function) and setup the properties
- dependencies (depending on firstName and lastName). The fullName function
- will be called once (regardless of how many times it is accessed) as long
- as it's dependencies have not been changed. Once firstName or lastName are updated
- any future calls (or anything bound) to fullName will incorporate the new
- values.
- ```javascript
- Person = Ember.Object.extend({
- // these will be supplied by `create`
- firstName: null,
- lastName: null,
- fullName: function() {
- var firstName = this.get('firstName');
- var lastName = this.get('lastName');
- return firstName + ' ' + lastName;
- }.property('firstName', 'lastName')
- });
- var tom = Person.create({
- firstName: "Tom",
- lastName: "Dale"
- });
- tom.get('fullName') // "Tom Dale"
- ```
- You can also define what Ember should do when setting a computed property.
- If you try to set a computed property, it will be invoked with the key and
- value you want to set it to. You can also accept the previous value as the
- third parameter.
- ```javascript
- Person = Ember.Object.extend({
- // these will be supplied by `create`
- firstName: null,
- lastName: null,
- fullName: function(key, value, oldValue) {
- // getter
- if (arguments.length === 1) {
- var firstName = this.get('firstName');
- var lastName = this.get('lastName');
- return firstName + ' ' + lastName;
- // setter
- } else {
- var name = value.split(" ");
- this.set('firstName', name[0]);
- this.set('lastName', name[1]);
- return value;
- }
- }.property('firstName', 'lastName')
- });
- var person = Person.create();
- person.set('fullName', "Peter Wagenet");
- person.get('firstName') // Peter
- person.get('lastName') // Wagenet
- ```
- @class ComputedProperty
- @namespace Ember
- @extends Ember.Descriptor
- @constructor
- */
- function ComputedProperty(func, opts) {
- this.func = func;
-
- this._dependentKeys = opts && opts.dependentKeys;
-
- this._cacheable = (opts && opts.cacheable !== undefined) ? opts.cacheable : true;
- this._readOnly = opts && (opts.readOnly !== undefined || !!opts.readOnly);
- }
- Ember.ComputedProperty = ComputedProperty;
- ComputedProperty.prototype = new Ember.Descriptor();
- var ComputedPropertyPrototype = ComputedProperty.prototype;
- /**
- Properties are cacheable by default. Computed property will automatically
- cache the return value of your function until one of the dependent keys changes.
- Call `volatile()` to set it into non-cached mode. When in this mode
- the computed property will not automatically cache the return value.
- However, if a property is properly observable, there is no reason to disable
- caching.
- @method cacheable
- @param {Boolean} aFlag optional set to `false` to disable caching
- @return {Ember.ComputedProperty} this
- @chainable
- */
- ComputedPropertyPrototype.cacheable = function(aFlag) {
- this._cacheable = aFlag !== false;
- return this;
- };
- /**
- Call on a computed property to set it into non-cached mode. When in this
- mode the computed property will not automatically cache the return value.
- ```javascript
- MyApp.outsideService = Ember.Object.extend({
- value: function() {
- return OutsideService.getValue();
- }.property().volatile()
- }).create();
- ```
- @method volatile
- @return {Ember.ComputedProperty} this
- @chainable
- */
- ComputedPropertyPrototype.volatile = function() {
- return this.cacheable(false);
- };
- /**
- Call on a computed property to set it into read-only mode. When in this
- mode the computed property will throw an error when set.
- ```javascript
- MyApp.Person = Ember.Object.extend({
- guid: function() {
- return 'guid-guid-guid';
- }.property().readOnly()
- });
- MyApp.person = MyApp.Person.create();
- MyApp.person.set('guid', 'new-guid'); // will throw an exception
- ```
- @method readOnly
- @return {Ember.ComputedProperty} this
- @chainable
- */
- ComputedPropertyPrototype.readOnly = function(readOnly) {
- this._readOnly = readOnly === undefined || !!readOnly;
- return this;
- };
- /**
- Sets the dependent keys on this computed property. Pass any number of
- arguments containing key paths that this computed property depends on.
- ```javascript
- MyApp.President = Ember.Object.extend({
- fullName: Ember.computed(function() {
- return this.get('firstName') + ' ' + this.get('lastName');
- // Tell Ember that this computed property depends on firstName
- // and lastName
- }).property('firstName', 'lastName')
- });
- MyApp.president = MyApp.President.create({
- firstName: 'Barack',
- lastName: 'Obama',
- });
- MyApp.president.get('fullName'); // Barack Obama
- ```
- @method property
- @param {String} path* zero or more property paths
- @return {Ember.ComputedProperty} this
- @chainable
- */
- ComputedPropertyPrototype.property = function() {
- var args;
- var addArg = function (property) {
- args.push(property);
- };
- args = [];
- for (var i = 0, l = arguments.length; i < l; i++) {
- expandProperties(arguments[i], addArg);
- }
-
- this._dependentKeys = args;
-
- return this;
- };
- /**
- In some cases, you may want to annotate computed properties with additional
- metadata about how they function or what values they operate on. For example,
- computed property functions may close over variables that are then no longer
- available for introspection.
- You can pass a hash of these values to a computed property like this:
- ```
- person: function() {
- var personId = this.get('personId');
- return App.Person.create({ id: personId });
- }.property().meta({ type: App.Person })
- ```
- The hash that you pass to the `meta()` function will be saved on the
- computed property descriptor under the `_meta` key. Ember runtime
- exposes a public API for retrieving these values from classes,
- via the `metaForProperty()` function.
- @method meta
- @param {Hash} meta
- @chainable
- */
- ComputedPropertyPrototype.meta = function(meta) {
- if (arguments.length === 0) {
- return this._meta || {};
- } else {
- this._meta = meta;
- return this;
- }
- };
- /* impl descriptor API */
- ComputedPropertyPrototype.didChange = function(obj, keyName) {
- // _suspended is set via a CP.set to ensure we don't clear
- // the cached value set by the setter
- if (this._cacheable && this._suspended !== obj) {
- var meta = metaFor(obj);
- if (keyName in meta.cache) {
- delete meta.cache[keyName];
- removeDependentKeys(this, obj, keyName, meta);
- }
- }
- };
- function finishChains(chainNodes)
- {
- for (var i=0, l=chainNodes.length; i<l; i++) {
- chainNodes[i].didChange(null);
- }
- }
- /**
- Access the value of the function backing the computed property.
- If this property has already been cached, return the cached result.
- Otherwise, call the function passing the property name as an argument.
- ```javascript
- Person = Ember.Object.extend({
- fullName: function(keyName) {
- // the keyName parameter is 'fullName' in this case.
- return this.get('firstName') + ' ' + this.get('lastName');
- }.property('firstName', 'lastName')
- });
- var tom = Person.create({
- firstName: "Tom",
- lastName: "Dale"
- });
- tom.get('fullName') // "Tom Dale"
- ```
- @method get
- @param {String} keyName The key being accessed.
- @return {Object} The return value of the function backing the CP.
- */
- ComputedPropertyPrototype.get = function(obj, keyName) {
- var ret, cache, meta, chainNodes;
- if (this._cacheable) {
- meta = metaFor(obj);
- cache = meta.cache;
- if (keyName in cache) { return cache[keyName]; }
- ret = cache[keyName] = this.func.call(obj, keyName);
- chainNodes = meta.chainWatchers && meta.chainWatchers[keyName];
- if (chainNodes) { finishChains(chainNodes); }
- addDependentKeys(this, obj, keyName, meta);
- } else {
- ret = this.func.call(obj, keyName);
- }
- return ret;
- };
- /**
- Set the value of a computed property. If the function that backs your
- computed property does not accept arguments then the default action for
- setting would be to define the property on the current object, and set
- the value of the property to the value being set.
- Generally speaking if you intend for your computed property to be set
- your backing function should accept either two or three arguments.
- @method set
- @param {String} keyName The key being accessed.
- @param {Object} newValue The new value being assigned.
- @param {String} oldValue The old value being replaced.
- @return {Object} The return value of the function backing the CP.
- */
- ComputedPropertyPrototype.set = function(obj, keyName, value) {
- var cacheable = this._cacheable,
- func = this.func,
- meta = metaFor(obj, cacheable),
- watched = meta.watching[keyName],
- oldSuspended = this._suspended,
- hadCachedValue = false,
- cache = meta.cache,
- funcArgLength, cachedValue, ret;
- if (this._readOnly) {
- throw new Ember.Error('Cannot Set: ' + keyName + ' on: ' + Ember.inspect(obj));
- }
- this._suspended = obj;
- try {
- if (cacheable && cache.hasOwnProperty(keyName)) {
- cachedValue = cache[keyName];
- hadCachedValue = true;
- }
- // Check if the CP has been wrapped. If if has, use the
- // length from the wrapped function.
- funcArgLength = (func.wrappedFunction ? func.wrappedFunction.length : func.length);
- // For backwards-compatibility with computed properties
- // that check for arguments.length === 2 to determine if
- // they are being get or set, only pass the old cached
- // value if the computed property opts into a third
- // argument.
- if (funcArgLength === 3) {
- ret = func.call(obj, keyName, value, cachedValue);
- } else if (funcArgLength === 2) {
- ret = func.call(obj, keyName, value);
- } else {
- Ember.defineProperty(obj, keyName, null, cachedValue);
- Ember.set(obj, keyName, value);
- return;
- }
- if (hadCachedValue && cachedValue === ret) { return; }
- if (watched) { Ember.propertyWillChange(obj, keyName); }
- if (hadCachedValue) {
- delete cache[keyName];
- }
- if (cacheable) {
- if (!hadCachedValue) {
- addDependentKeys(this, obj, keyName, meta);
- }
- cache[keyName] = ret;
- }
- if (watched) { Ember.propertyDidChange(obj, keyName); }
- } finally {
- this._suspended = oldSuspended;
- }
- return ret;
- };
- /* called before property is overridden */
- ComputedPropertyPrototype.teardown = function(obj, keyName) {
- var meta = metaFor(obj);
- if (keyName in meta.cache) {
- removeDependentKeys(this, obj, keyName, meta);
- }
- if (this._cacheable) { delete meta.cache[keyName]; }
- return null; // no value to restore
- };
- /**
- This helper returns a new property descriptor that wraps the passed
- computed property function. You can use this helper to define properties
- with mixins or via `Ember.defineProperty()`.
- The function you pass will be used to both get and set property values.
- The function should accept two parameters, key and value. If value is not
- undefined you should set the value first. In either case return the
- current value of the property.
- @method computed
- @for Ember
- @param {Function} func The computed property function.
- @return {Ember.ComputedProperty} property descriptor instance
- */
- Ember.computed = function(func) {
- var args;
- if (arguments.length > 1) {
- args = a_slice.call(arguments, 0, -1);
- func = a_slice.call(arguments, -1)[0];
- }
- if (typeof func !== "function") {
- throw new Ember.Error("Computed Property declared without a property function");
- }
- var cp = new ComputedProperty(func);
- if (args) {
- cp.property.apply(cp, args);
- }
- return cp;
- };
- /**
- Returns the cached value for a property, if one exists.
- This can be useful for peeking at the value of a computed
- property that is generated lazily, without accidentally causing
- it to be created.
- @method cacheFor
- @for Ember
- @param {Object} obj the object whose property you want to check
- @param {String} key the name of the property whose cached value you want
- to return
- @return {Object} the cached value
- */
- Ember.cacheFor = function cacheFor(obj, key) {
- var meta = obj[META_KEY],
- cache = meta && meta.cache;
- if (cache && key in cache) {
- return cache[key];
- }
- };
- function getProperties(self, propertyNames) {
- var ret = {};
- for(var i = 0; i < propertyNames.length; i++) {
- ret[propertyNames[i]] = get(self, propertyNames[i]);
- }
- return ret;
- }
- var registerComputed, registerComputedWithProperties;
- registerComputed = function (name, macro) {
- Ember.computed[name] = function(dependentKey) {
- var args = a_slice.call(arguments);
- return Ember.computed(dependentKey, function() {
- return macro.apply(this, args);
- });
- };
- };
- registerComputedWithProperties = function(name, macro) {
- Ember.computed[name] = function() {
- var properties = a_slice.call(arguments);
- var computed = Ember.computed(function() {
- return macro.apply(this, [getProperties(this, properties)]);
- });
- return computed.property.apply(computed, properties);
- };
- };
- /**
- A computed property that returns true if the value of the dependent
- property is null, an empty string, empty array, or empty function.
- Note: When using `Ember.computed.empty` to watch an array make sure to
- use the `array.[]` syntax so the computed can subscribe to transitions
- from empty to non-empty states.
- Example
- ```javascript
- var ToDoList = Ember.Object.extend({
- done: Ember.computed.empty('todos.[]') // detect array changes
- });
- var todoList = ToDoList.create({todos: ['Unit Test', 'Documentation', 'Release']});
- todoList.get('done'); // false
- todoList.get('todos').clear(); // []
- todoList.get('done'); // true
- ```
- @method computed.empty
- @for Ember
- @param {String} dependentKey
- @return {Ember.ComputedProperty} computed property which negate
- the original value for property
- */
- registerComputed('empty', function(dependentKey) {
- return Ember.isEmpty(get(this, dependentKey));
- });
- /**
- A computed property that returns true if the value of the dependent
- property is NOT null, an empty string, empty array, or empty function.
- Note: When using `Ember.computed.notEmpty` to watch an array make sure to
- use the `array.[]` syntax so the computed can subscribe to transitions
- from empty to non-empty states.
- Example
- ```javascript
- var Hamster = Ember.Object.extend({
- hasStuff: Ember.computed.notEmpty('backpack.[]')
- });
- var hamster = Hamster.create({backpack: ['Food', 'Sleeping Bag', 'Tent']});
- hamster.get('hasStuff'); // true
- hamster.get('backpack').clear(); // []
- hamster.get('hasStuff'); // false
- ```
- @method computed.notEmpty
- @for Ember
- @param {String} dependentKey
- @return {Ember.ComputedProperty} computed property which returns true if
- original value for property is not empty.
- */
- registerComputed('notEmpty', function(dependentKey) {
- return !Ember.isEmpty(get(this, dependentKey));
- });
- /**
- A computed property that returns true if the value of the dependent
- property is null or undefined. This avoids errors from JSLint complaining
- about use of ==, which can be technically confusing.
- Example
- ```javascript
- var Hamster = Ember.Object.extend({
- isHungry: Ember.computed.none('food')
- });
- var hamster = Hamster.create();
- hamster.get('isHungry'); // true
- hamster.set('food', 'Banana');
- hamster.get('isHungry'); // false
- hamster.set('food', null);
- hamster.get('isHungry'); // true
- ```
- @method computed.none
- @for Ember
- @param {String} dependentKey
- @return {Ember.ComputedProperty} computed property which
- returns true if original value for property is null or undefined.
- */
- registerComputed('none', function(dependentKey) {
- return Ember.isNone(get(this, dependentKey));
- });
- /**
- A computed property that returns the inverse boolean value
- of the original value for the dependent property.
- Example
- ```javascript
- var User = Ember.Object.extend({
- isAnonymous: Ember.computed.not('loggedIn')
- });
- var user = User.create({loggedIn: false});
- user.get('isAnonymous'); // true
- user.set('loggedIn', true);
- user.get('isAnonymous'); // false
- ```
- @method computed.not
- @for Ember
- @param {String} dependentKey
- @return {Ember.ComputedProperty} computed property which returns
- inverse of the original value for property
- */
- registerComputed('not', function(dependentKey) {
- return !get(this, dependentKey);
- });
- /**
- A computed property that converts the provided dependent property
- into a boolean value.
- ```javascript
- var Hamster = Ember.Object.extend({
- hasBananas: Ember.computed.bool('numBananas')
- });
- var hamster = Hamster.create();
- hamster.get('hasBananas'); // false
- hamster.set('numBananas', 0);
- hamster.get('hasBananas'); // false
- hamster.set('numBananas', 1);
- hamster.get('hasBananas'); // true
- hamster.set('numBananas', null);
- hamster.get('hasBananas'); // false
- ```
- @method computed.bool
- @for Ember
- @param {String} dependentKey
- @return {Ember.ComputedProperty} computed property which converts
- to boolean the original value for property
- */
- registerComputed('bool', function(dependentKey) {
- return !!get(this, dependentKey);
- });
- /**
- A computed property which matches the original value for the
- dependent property against a given RegExp, returning `true`
- if they values matches the RegExp and `false` if it does not.
- Example
- ```javascript
- var User = Ember.Object.extend({
- hasValidEmail: Ember.computed.match('email', /^.+@.+\..+$/)
- });
- var user = User.create({loggedIn: false});
- user.get('hasValidEmail'); // false
- user.set('email', '');
- user.get('hasValidEmail'); // false
- user.set('email', 'ember_hamster@example.com');
- user.get('hasValidEmail'); // true
- ```
- @method computed.match
- @for Ember
- @param {String} dependentKey
- @param {RegExp} regexp
- @return {Ember.ComputedProperty} computed property which match
- the original value for property against a given RegExp
- */
- registerComputed('match', function(dependentKey, regexp) {
- var value = get(this, dependentKey);
- return typeof value === 'string' ? regexp.test(value) : false;
- });
- /**
- A computed property that returns true if the provided dependent property
- is equal to the given value.
- Example
- ```javascript
- var Hamster = Ember.Object.extend({
- napTime: Ember.computed.equal('state', 'sleepy')
- });
- var hamster = Hamster.create();
- hamster.get('napTime'); // false
- hamster.set('state', 'sleepy');
- hamster.get('napTime'); // true
- hamster.set('state', 'hungry');
- hamster.get('napTime'); // false
- ```
- @method computed.equal
- @for Ember
- @param {String} dependentKey
- @param {String|Number|Object} value
- @return {Ember.ComputedProperty} computed property which returns true if
- the original value for property is equal to the given value.
- */
- registerComputed('equal', function(dependentKey, value) {
- return get(this, dependentKey) === value;
- });
- /**
- A computed property that returns true if the provied dependent property
- is greater than the provided value.
- Example
- ```javascript
- var Hamster = Ember.Object.extend({
- hasTooManyBananas: Ember.computed.gt('numBananas', 10)
- });
- var hamster = Hamster.create();
- hamster.get('hasTooManyBananas'); // false
- hamster.set('numBananas', 3);
- hamster.get('hasTooManyBananas'); // false
- hamster.set('numBananas', 11);
- hamster.get('hasTooManyBananas'); // true
- ```
- @method computed.gt
- @for Ember
- @param {String} dependentKey
- @param {Number} value
- @return {Ember.ComputedProperty} computed property which returns true if
- the original value for property is greater then given value.
- */
- registerComputed('gt', function(dependentKey, value) {
- return get(this, dependentKey) > value;
- });
- /**
- A computed property that returns true if the provided dependent property
- is greater than or equal to the provided value.
- Example
- ```javascript
- var Hamster = Ember.Object.extend({
- hasTooManyBananas: Ember.computed.gte('numBananas', 10)
- });
- var hamster = Hamster.create();
- hamster.get('hasTooManyBananas'); // false
- hamster.set('numBananas', 3);
- hamster.get('hasTooManyBananas'); // false
- hamster.set('numBananas', 10);
- hamster.get('hasTooManyBananas'); // true
- ```
- @method computed.gte
- @for Ember
- @param {String} dependentKey
- @param {Number} value
- @return {Ember.ComputedProperty} computed property which returns true if
- the original value for property is greater or equal then given value.
- */
- registerComputed('gte', function(dependentKey, value) {
- return get(this, dependentKey) >= value;
- });
- /**
- A computed property that returns true if the provided dependent property
- is less than the provided value.
- Example
- ```javascript
- var Hamster = Ember.Object.extend({
- needsMoreBananas: Ember.computed.lt('numBananas', 3)
- });
- var hamster = Hamster.create();
- hamster.get('needsMoreBananas'); // true
- hamster.set('numBananas', 3);
- hamster.get('needsMoreBananas'); // false
- hamster.set('numBananas', 2);
- hamster.get('needsMoreBananas'); // true
- ```
- @method computed.lt
- @for Ember
- @param {String} dependentKey
- @param {Number} value
- @return {Ember.ComputedProperty} computed property which returns true if
- the original value for property is less then given value.
- */
- registerComputed('lt', function(dependentKey, value) {
- return get(this, dependentKey) < value;
- });
- /**
- A computed property that returns true if the provided dependent property
- is less than or equal to the provided value.
- Example
- ```javascript
- var Hamster = Ember.Object.extend({
- needsMoreBananas: Ember.computed.lte('numBananas', 3)
- });
- var hamster = Hamster.create();
- hamster.get('needsMoreBananas'); // true
- hamster.set('numBananas', 5);
- hamster.get('needsMoreBananas'); // false
- hamster.set('numBananas', 3);
- hamster.get('needsMoreBananas'); // true
- ```
- @method computed.lte
- @for Ember
- @param {String} dependentKey
- @param {Number} value
- @return {Ember.ComputedProperty} computed property which returns true if
- the original value for property is less or equal then given value.
- */
- registerComputed('lte', function(dependentKey, value) {
- return get(this, dependentKey) <= value;
- });
- /**
- A computed property that performs a logical `and` on the
- original values for the provided dependent properties.
- Example
- ```javascript
- var Hamster = Ember.Object.extend({
- readyForCamp: Ember.computed.and('hasTent', 'hasBackpack')
- });
- var hamster = Hamster.create();
- hamster.get('readyForCamp'); // false
- hamster.set('hasTent', true);
- hamster.get('readyForCamp'); // false
- hamster.set('hasBackpack', true);
- hamster.get('readyForCamp'); // true
- ```
- @method computed.and
- @for Ember
- @param {String} dependentKey*
- @return {Ember.ComputedProperty} computed property which performs
- a logical `and` on the values of all the original values for properties.
- */
- registerComputedWithProperties('and', function(properties) {
- for (var key in properties) {
- if (properties.hasOwnProperty(key) && !properties[key]) {
- return false;
- }
- }
- return true;
- });
- /**
- A computed property which performs a logical `or` on the
- original values for the provided dependent properties.
- Example
- ```javascript
- var Hamster = Ember.Object.extend({
- readyForRain: Ember.computed.or('hasJacket', 'hasUmbrella')
- });
- var hamster = Hamster.create();
- hamster.get('readyForRain'); // false
- hamster.set('hasJacket', true);
- hamster.get('readyForRain'); // true
- ```
- @method computed.or
- @for Ember
- @param {String} dependentKey*
- @return {Ember.ComputedProperty} computed property which performs
- a logical `or` on the values of all the original values for properties.
- */
- registerComputedWithProperties('or', function(properties) {
- for (var key in properties) {
- if (properties.hasOwnProperty(key) && properties[key]) {
- return true;
- }
- }
- return false;
- });
- /**
- A computed property that returns the first truthy value
- from a list of dependent properties.
- Example
- ```javascript
- var Hamster = Ember.Object.extend({
- hasClothes: Ember.computed.any('hat', 'shirt')
- });
- var hamster = Hamster.create();
- hamster.get('hasClothes'); // null
- hamster.set('shirt', 'Hawaiian Shirt');
- hamster.get('hasClothes'); // 'Hawaiian Shirt'
- ```
- @method computed.any
- @for Ember
- @param {String} dependentKey*
- @return {Ember.ComputedProperty} computed property which returns
- the first truthy value of given list of properties.
- */
- registerComputedWithProperties('any', function(properties) {
- for (var key in properties) {
- if (properties.hasOwnProperty(key) && properties[key]) {
- return properties[key];
- }
- }
- return null;
- });
- /**
- A computed property that returns the array of values
- for the provided dependent properties.
- Example
- ```javascript
- var Hamster = Ember.Object.extend({
- clothes: Ember.computed.collect('hat', 'shirt')
- });
- var hamster = Hamster.create();
- hamster.get('clothes'); // [null, null]
- hamster.set('hat', 'Camp Hat');
- hamster.set('shirt', 'Camp Shirt');
- hamster.get('clothes'); // ['Camp Hat', 'Camp Shirt']
- ```
- @method computed.collect
- @for Ember
- @param {String} dependentKey*
- @return {Ember.ComputedProperty} computed property which maps
- values of all passed properties in to an array.
- */
- registerComputedWithProperties('collect', function(properties) {
- var res = [];
- for (var key in properties) {
- if (properties.hasOwnProperty(key)) {
- if (Ember.isNone(properties[key])) {
- res.push(null);
- } else {
- res.push(properties[key]);
- }
- }
- }
- return res;
- });
- /**
- Creates a new property that is an alias for another property
- on an object. Calls to `get` or `set` this property behave as
- though they were called on the original property.
- ```javascript
- Person = Ember.Object.extend({
- name: 'Alex Matchneer',
- nomen: Ember.computed.alias('name')
- });
- alex = Person.create();
- alex.get('nomen'); // 'Alex Matchneer'
- alex.get('name'); // 'Alex Matchneer'
- alex.set('nomen', '@machty');
- alex.get('name'); // '@machty'
- ```
- @method computed.alias
- @for Ember
- @param {String} dependentKey
- @return {Ember.ComputedProperty} computed property which creates an
- alias to the original value for property.
- */
- Ember.computed.alias = function(dependentKey) {
- return Ember.computed(dependentKey, function(key, value) {
- if (arguments.length > 1) {
- set(this, dependentKey, value);
- return value;
- } else {
- return get(this, dependentKey);
- }
- });
- };
- /**
- Where `computed.alias` aliases `get` and `set`, and allows for bidirectional
- data flow, `computed.oneWay` only provides an aliased `get`. The `set` will
- not mutate the upstream property, rather causes the current property to
- become the value set. This causes the downstream property to permentantly
- diverge from the upstream property.
- Example
- ```javascript
- User = Ember.Object.extend({
- firstName: null,
- lastName: null,
- nickName: Ember.computed.oneWay('firstName')
- });
- user = User.create({
- firstName: 'Teddy',
- lastName: 'Zeenny'
- });
- user.get('nickName');
- # 'Teddy'
- user.set('nickName', 'TeddyBear');
- # 'TeddyBear'
- user.get('firstName');
- # 'Teddy'
- ```
- @method computed.oneWay
- @for Ember
- @param {String} dependentKey
- @return {Ember.ComputedProperty} computed property which creates a
- one way computed property to the original value for property.
- */
- Ember.computed.oneWay = function(dependentKey) {
- return Ember.computed(dependentKey, function() {
- return get(this, dependentKey);
- });
- };
- /**
- A computed property that acts like a standard getter and setter,
- but returns the value at the provided `defaultPath` if the
- property itself has not been set to a value
- Example
- ```javascript
- var Hamster = Ember.Object.extend({
- wishList: Ember.computed.defaultTo('favoriteFood')
- });
- var hamster = Hamster.create({favoriteFood: 'Banana'});
- hamster.get('wishList'); // 'Banana'
- hamster.set('wishList', 'More Unit Tests');
- hamster.get('wishList'); // 'More Unit Tests'
- hamster.get('favoriteFood'); // 'Banana'
- ```
- @method computed.defaultTo
- @for Ember
- @param {String} defaultPath
- @return {Ember.ComputedProperty} computed property which acts like
- a standard getter and setter, but defaults to the value from `defaultPath`.
- */
- Ember.computed.defaultTo = function(defaultPath) {
- return Ember.computed(function(key, newValue, cachedValue) {
- if (arguments.length === 1) {
- return cachedValue != null ? cachedValue : get(this, defaultPath);
- }
- return newValue != null ? newValue : get(this, defaultPath);
- });
- };
- })();
- (function() {
- // Ember.tryFinally
- /**
- @module ember-metal
- */
- var AFTER_OBSERVERS = ':change',
- BEFORE_OBSERVERS = ':before';
- function changeEvent(keyName) {
- return keyName+AFTER_OBSERVERS;
- }
- function beforeEvent(keyName) {
- return keyName+BEFORE_OBSERVERS;
- }
- /**
- @method addObserver
- @param obj
- @param {String} path
- @param {Object|Function} targetOrMethod
- @param {Function|String} [method]
- */
- Ember.addObserver = function(obj, _path, target, method) {
- Ember.addListener(obj, changeEvent(_path), target, method);
- Ember.watch(obj, _path);
- return this;
- };
- Ember.observersFor = function(obj, path) {
- return Ember.listenersFor(obj, changeEvent(path));
- };
- /**
- @method removeObserver
- @param obj
- @param {String} path
- @param {Object|Function} targetOrMethod
- @param {Function|String} [method]
- */
- Ember.removeObserver = function(obj, _path, target, method) {
- Ember.unwatch(obj, _path);
- Ember.removeListener(obj, changeEvent(_path), target, method);
- return this;
- };
- /**
- @method addBeforeObserver
- @param obj
- @param {String} path
- @param {Object|Function} targetOrMethod
- @param {Function|String} [method]
- */
- Ember.addBeforeObserver = function(obj, _path, target, method) {
- Ember.addListener(obj, beforeEvent(_path), target, method);
- Ember.watch(obj, _path);
- return this;
- };
- // Suspend observer during callback.
- //
- // This should only be used by the target of the observer
- // while it is setting the observed path.
- Ember._suspendBeforeObserver = function(obj, path, target, method, callback) {
- return Ember._suspendListener(obj, beforeEvent(path), target, method, callback);
- };
- Ember._suspendObserver = function(obj, path, target, method, callback) {
- return Ember._suspendListener(obj, changeEvent(path), target, method, callback);
- };
- var map = Ember.ArrayPolyfills.map;
- Ember._suspendBeforeObservers = function(obj, paths, target, method, callback) {
- var events = map.call(paths, beforeEvent);
- return Ember._suspendListeners(obj, events, target, method, callback);
- };
- Ember._suspendObservers = function(obj, paths, target, method, callback) {
- var events = map.call(paths, changeEvent);
- return Ember._suspendListeners(obj, events, target, method, callback);
- };
- Ember.beforeObserversFor = function(obj, path) {
- return Ember.listenersFor(obj, beforeEvent(path));
- };
- /**
- @method removeBeforeObserver
- @param obj
- @param {String} path
- @param {Object|Function} targetOrMethod
- @param {Function|String} [method]
- */
- Ember.removeBeforeObserver = function(obj, _path, target, method) {
- Ember.unwatch(obj, _path);
- Ember.removeListener(obj, beforeEvent(_path), target, method);
- return this;
- };
- })();
- (function() {
- define("backburner/queue",
- ["exports"],
- function(__exports__) {
- "use strict";
- function Queue(daq, name, options) {
- this.daq = daq;
- this.name = name;
- this.options = options;
- this._queue = [];
- }
- Queue.prototype = {
- daq: null,
- name: null,
- options: null,
- _queue: null,
- push: function(target, method, args, stack) {
- var queue = this._queue;
- queue.push(target, method, args, stack);
- return {queue: this, target: target, method: method};
- },
- pushUnique: function(target, method, args, stack) {
- var queue = this._queue, currentTarget, currentMethod, i, l;
- for (i = 0, l = queue.length; i < l; i += 4) {
- currentTarget = queue[i];
- currentMethod = queue[i+1];
- if (currentTarget === target && currentMethod === method) {
- queue[i+2] = args; // replace args
- queue[i+3] = stack; // replace stack
- return {queue: this, target: target, method: method}; // TODO: test this code path
- }
- }
- this._queue.push(target, method, args, stack);
- return {queue: this, target: target, method: method};
- },
- // TODO: remove me, only being used for Ember.run.sync
- flush: function() {
- var queue = this._queue,
- options = this.options,
- before = options && options.before,
- after = options && options.after,
- target, method, args, stack, i, l = queue.length;
- if (l && before) { before(); }
- for (i = 0; i < l; i += 4) {
- target = queue[i];
- method = queue[i+1];
- args = queue[i+2];
- stack = queue[i+3]; // Debugging assistance
- // TODO: error handling
- if (args && args.length > 0) {
- method.apply(target, args);
- } else {
- method.call(target);
- }
- }
- if (l && after) { after(); }
- // check if new items have been added
- if (queue.length > l) {
- this._queue = queue.slice(l);
- this.flush();
- } else {
- this._queue.length = 0;
- }
- },
- cancel: function(actionToCancel) {
- var queue = this._queue, currentTarget, currentMethod, i, l;
- for (i = 0, l = queue.length; i < l; i += 4) {
- currentTarget = queue[i];
- currentMethod = queue[i+1];
- if (currentTarget === actionToCancel.target && currentMethod === actionToCancel.method) {
- queue.splice(i, 4);
- return true;
- }
- }
- // if not found in current queue
- // could be in the queue that is being flushed
- queue = this._queueBeingFlushed;
- if (!queue) {
- return;
- }
- for (i = 0, l = queue.length; i < l; i += 4) {
- currentTarget = queue[i];
- currentMethod = queue[i+1];
- if (currentTarget === actionToCancel.target && currentMethod === actionToCancel.method) {
- // don't mess with array during flush
- // just nullify the method
- queue[i+1] = null;
- return true;
- }
- }
- }
- };
- __exports__.Queue = Queue;
- });
- define("backburner/deferred_action_queues",
- ["backburner/queue","exports"],
- function(__dependency1__, __exports__) {
- "use strict";
- var Queue = __dependency1__.Queue;
- function DeferredActionQueues(queueNames, options) {
- var queues = this.queues = {};
- this.queueNames = queueNames = queueNames || [];
- var queueName;
- for (var i = 0, l = queueNames.length; i < l; i++) {
- queueName = queueNames[i];
- queues[queueName] = new Queue(this, queueName, options[queueName]);
- }
- }
- DeferredActionQueues.prototype = {
- queueNames: null,
- queues: null,
- schedule: function(queueName, target, method, args, onceFlag, stack) {
- var queues = this.queues,
- queue = queues[queueName];
- if (!queue) { throw new Error("You attempted to schedule an action in a queue (" + queueName + ") that doesn't exist"); }
- if (onceFlag) {
- return queue.pushUnique(target, method, args, stack);
- } else {
- return queue.push(target, method, args, stack);
- }
- },
- flush: function() {
- var queues = this.queues,
- queueNames = this.queueNames,
- queueName, queue, queueItems, priorQueueNameIndex,
- queueNameIndex = 0, numberOfQueues = queueNames.length;
- outerloop:
- while (queueNameIndex < numberOfQueues) {
- queueName = queueNames[queueNameIndex];
- queue = queues[queueName];
- queueItems = queue._queueBeingFlushed = queue._queue.slice();
- queue._queue = [];
- var options = queue.options,
- before = options && options.before,
- after = options && options.after,
- target, method, args, stack,
- queueIndex = 0, numberOfQueueItems = queueItems.length;
- if (numberOfQueueItems && before) { before(); }
- while (queueIndex < numberOfQueueItems) {
- target = queueItems[queueIndex];
- method = queueItems[queueIndex+1];
- args = queueItems[queueIndex+2];
- stack = queueItems[queueIndex+3]; // Debugging assistance
- if (typeof method === 'string') { method = target[method]; }
- // method could have been nullified / canceled during flush
- if (method) {
- // TODO: error handling
- if (args && args.length > 0) {
- method.apply(target, args);
- } else {
- method.call(target);
- }
- }
- queueIndex += 4;
- }
- queue._queueBeingFlushed = null;
- if (numberOfQueueItems && after) { after(); }
- if ((priorQueueNameIndex = indexOfPriorQueueWithActions(this, queueNameIndex)) !== -1) {
- queueNameIndex = priorQueueNameIndex;
- continue outerloop;
- }
- queueNameIndex++;
- }
- }
- };
- function indexOfPriorQueueWithActions(daq, currentQueueIndex) {
- var queueName, queue;
- for (var i = 0, l = currentQueueIndex; i <= l; i++) {
- queueName = daq.queueNames[i];
- queue = daq.queues[queueName];
- if (queue._queue.length) { return i; }
- }
- return -1;
- }
- __exports__.DeferredActionQueues = DeferredActionQueues;
- });
- define("backburner",
- ["backburner/deferred_action_queues","exports"],
- function(__dependency1__, __exports__) {
- "use strict";
- var DeferredActionQueues = __dependency1__.DeferredActionQueues;
- var slice = [].slice,
- pop = [].pop,
- throttlers = [],
- debouncees = [],
- timers = [],
- autorun, laterTimer, laterTimerExpiresAt,
- global = this,
- NUMBER = /\d+/;
- function isCoercableNumber(number) {
- return typeof number === 'number' || NUMBER.test(number);
- }
- function Backburner(queueNames, options) {
- this.queueNames = queueNames;
- this.options = options || {};
- if (!this.options.defaultQueue) {
- this.options.defaultQueue = queueNames[0];
- }
- this.instanceStack = [];
- }
- Backburner.prototype = {
- queueNames: null,
- options: null,
- currentInstance: null,
- instanceStack: null,
- begin: function() {
- var onBegin = this.options && this.options.onBegin,
- previousInstance = this.currentInstance;
- if (previousInstance) {
- this.instanceStack.push(previousInstance);
- }
- this.currentInstance = new DeferredActionQueues(this.queueNames, this.options);
- if (onBegin) {
- onBegin(this.currentInstance, previousInstance);
- }
- },
- end: function() {
- var onEnd = this.options && this.options.onEnd,
- currentInstance = this.currentInstance,
- nextInstance = null;
- try {
- currentInstance.flush();
- } finally {
- this.currentInstance = null;
- if (this.instanceStack.length) {
- nextInstance = this.instanceStack.pop();
- this.currentInstance = nextInstance;
- }
- if (onEnd) {
- onEnd(currentInstance, nextInstance);
- }
- }
- },
- run: function(target, method /*, args */) {
- var ret;
- this.begin();
- if (!method) {
- method = target;
- target = null;
- }
- if (typeof method === 'string') {
- method = target[method];
- }
- // Prevent Safari double-finally.
- var finallyAlreadyCalled = false;
- try {
- if (arguments.length > 2) {
- ret = method.apply(target, slice.call(arguments, 2));
- } else {
- ret = method.call(target);
- }
- } finally {
- if (!finallyAlreadyCalled) {
- finallyAlreadyCalled = true;
- this.end();
- }
- }
- return ret;
- },
- defer: function(queueName, target, method /* , args */) {
- if (!method) {
- method = target;
- target = null;
- }
- if (typeof method === 'string') {
- method = target[method];
- }
- var stack = this.DEBUG ? new Error() : undefined,
- args = arguments.length > 3 ? slice.call(arguments, 3) : undefined;
- if (!this.currentInstance) { createAutorun(this); }
- return this.currentInstance.schedule(queueName, target, method, args, false, stack);
- },
- deferOnce: function(queueName, target, method /* , args */) {
- if (!method) {
- method = target;
- target = null;
- }
- if (typeof method === 'string') {
- method = target[method];
- }
- var stack = this.DEBUG ? new Error() : undefined,
- args = arguments.length > 3 ? slice.call(arguments, 3) : undefined;
- if (!this.currentInstance) { createAutorun(this); }
- return this.currentInstance.schedule(queueName, target, method, args, true, stack);
- },
- setTimeout: function() {
- var args = slice.call(arguments);
- var length = args.length;
- var method, wait, target;
- var self = this;
- var methodOrTarget, methodOrWait, methodOrArgs;
- if (length === 0) {
- return;
- } else if (length === 1) {
- method = args.shift();
- wait = 0;
- } else if (length === 2) {
- methodOrTarget = args[0];
- methodOrWait = args[1];
- if (typeof methodOrWait === 'function' || typeof methodOrTarget[methodOrWait] === 'function') {
- target = args.shift();
- method = args.shift();
- wait = 0;
- } else if (isCoercableNumber(methodOrWait)) {
- method = args.shift();
- wait = args.shift();
- } else {
- method = args.shift();
- wait = 0;
- }
- } else {
- var last = args[args.length - 1];
- if (isCoercableNumber(last)) {
- wait = args.pop();
- }
- methodOrTarget = args[0];
- methodOrArgs = args[1];
- if (typeof methodOrArgs === 'function' || (typeof methodOrArgs === 'string' &&
- methodOrTarget !== null &&
- methodOrArgs in methodOrTarget)) {
- target = args.shift();
- method = args.shift();
- } else {
- method = args.shift();
- }
- }
- var executeAt = (+new Date()) + parseInt(wait, 10);
- if (typeof method === 'string') {
- method = target[method];
- }
- function fn() {
- method.apply(target, args);
- }
- // find position to insert - TODO: binary search
- var i, l;
- for (i = 0, l = timers.length; i < l; i += 2) {
- if (executeAt < timers[i]) { break; }
- }
- timers.splice(i, 0, executeAt, fn);
- updateLaterTimer(self, executeAt, wait);
- return fn;
- },
- throttle: function(target, method /* , args, wait */) {
- var self = this,
- args = arguments,
- wait = parseInt(pop.call(args), 10),
- throttler,
- index,
- timer;
- index = findThrottler(target, method);
- if (index > -1) { return throttlers[index]; } // throttled
- timer = global.setTimeout(function() {
- self.run.apply(self, args);
- var index = findThrottler(target, method);
- if (index > -1) { throttlers.splice(index, 1); }
- }, wait);
- throttler = [target, method, timer];
- throttlers.push(throttler);
- return throttler;
- },
- debounce: function(target, method /* , args, wait, [immediate] */) {
- var self = this,
- args = arguments,
- immediate = pop.call(args),
- wait,
- index,
- debouncee,
- timer;
- if (typeof immediate === "number" || typeof immediate === "string") {
- wait = immediate;
- immediate = false;
- } else {
- wait = pop.call(args);
- }
- wait = parseInt(wait, 10);
- // Remove debouncee
- index = findDebouncee(target, method);
- if (index > -1) {
- debouncee = debouncees[index];
- debouncees.splice(index, 1);
- clearTimeout(debouncee[2]);
- }
- timer = global.setTimeout(function() {
- if (!immediate) {
- self.run.apply(self, args);
- }
- var index = findDebouncee(target, method);
- if (index > -1) {
- debouncees.splice(index, 1);
- }
- }, wait);
- if (immediate && index === -1) {
- self.run.apply(self, args);
- }
- debouncee = [target, method, timer];
- debouncees.push(debouncee);
- return debouncee;
- },
- cancelTimers: function() {
- var i, len;
- for (i = 0, len = throttlers.length; i < len; i++) {
- clearTimeout(throttlers[i][2]);
- }
- throttlers = [];
- for (i = 0, len = debouncees.length; i < len; i++) {
- clearTimeout(debouncees[i][2]);
- }
- debouncees = [];
- if (laterTimer) {
- clearTimeout(laterTimer);
- laterTimer = null;
- }
- timers = [];
- if (autorun) {
- clearTimeout(autorun);
- autorun = null;
- }
- },
- hasTimers: function() {
- return !!timers.length || autorun;
- },
- cancel: function(timer) {
- var timerType = typeof timer;
- if (timer && timerType === 'object' && timer.queue && timer.method) { // we're cancelling a deferOnce
- return timer.queue.cancel(timer);
- } else if (timerType === 'function') { // we're cancelling a setTimeout
- for (var i = 0, l = timers.length; i < l; i += 2) {
- if (timers[i + 1] === timer) {
- timers.splice(i, 2); // remove the two elements
- return true;
- }
- }
- } else if (Object.prototype.toString.call(timer) === "[object Array]"){ // we're cancelling a throttle or debounce
- return this._cancelItem(findThrottler, throttlers, timer) ||
- this._cancelItem(findDebouncee, debouncees, timer);
- } else {
- return; // timer was null or not a timer
- }
- },
- _cancelItem: function(findMethod, array, timer){
- var item,
- index;
- if (timer.length < 3) { return false; }
- index = findMethod(timer[0], timer[1]);
- if(index > -1) {
- item = array[index];
- if(item[2] === timer[2]){
- array.splice(index, 1);
- clearTimeout(timer[2]);
- return true;
- }
- }
- return false;
- }
- };
- Backburner.prototype.schedule = Backburner.prototype.defer;
- Backburner.prototype.scheduleOnce = Backburner.prototype.deferOnce;
- Backburner.prototype.later = Backburner.prototype.setTimeout;
- function createAutorun(backburner) {
- backburner.begin();
- autorun = global.setTimeout(function() {
- autorun = null;
- backburner.end();
- });
- }
- function updateLaterTimer(self, executeAt, wait) {
- if (!laterTimer || executeAt < laterTimerExpiresAt) {
- if (laterTimer) {
- clearTimeout(laterTimer);
- }
- laterTimer = global.setTimeout(function() {
- laterTimer = null;
- laterTimerExpiresAt = null;
- executeTimers(self);
- }, wait);
- laterTimerExpiresAt = executeAt;
- }
- }
- function executeTimers(self) {
- var now = +new Date(),
- time, fns, i, l;
- self.run(function() {
- // TODO: binary search
- for (i = 0, l = timers.length; i < l; i += 2) {
- time = timers[i];
- if (time > now) { break; }
- }
- fns = timers.splice(0, i);
- for (i = 1, l = fns.length; i < l; i += 2) {
- self.schedule(self.options.defaultQueue, null, fns[i]);
- }
- });
- if (timers.length) {
- updateLaterTimer(self, timers[0], timers[0] - now);
- }
- }
- function findDebouncee(target, method) {
- var debouncee,
- index = -1;
- for (var i = 0, l = debouncees.length; i < l; i++) {
- debouncee = debouncees[i];
- if (debouncee[0] === target && debouncee[1] === method) {
- index = i;
- break;
- }
- }
- return index;
- }
- function findThrottler(target, method) {
- var throttler,
- index = -1;
- for (var i = 0, l = throttlers.length; i < l; i++) {
- throttler = throttlers[i];
- if (throttler[0] === target && throttler[1] === method) {
- index = i;
- break;
- }
- }
- return index;
- }
- __exports__.Backburner = Backburner;
- });
- })();
- (function() {
- var onBegin = function(current) {
- Ember.run.currentRunLoop = current;
- };
- var onEnd = function(current, next) {
- Ember.run.currentRunLoop = next;
- };
- var Backburner = requireModule('backburner').Backburner,
- backburner = new Backburner(['sync', 'actions', 'destroy'], {
- sync: {
- before: Ember.beginPropertyChanges,
- after: Ember.endPropertyChanges
- },
- defaultQueue: 'actions',
- onBegin: onBegin,
- onEnd: onEnd
- }),
- slice = [].slice,
- concat = [].concat;
- // ..........................................................
- // Ember.run - this is ideally the only public API the dev sees
- //
- /**
- Runs the passed target and method inside of a RunLoop, ensuring any
- deferred actions including bindings and views updates are flushed at the
- end.
- Normally you should not need to invoke this method yourself. However if
- you are implementing raw event handlers when interfacing with other
- libraries or plugins, you should probably wrap all of your code inside this
- call.
- ```javascript
- Ember.run(function() {
- // code to be execute within a RunLoop
- });
- ```
- @class run
- @namespace Ember
- @static
- @constructor
- @param {Object} [target] target of method to call
- @param {Function|String} method Method to invoke.
- May be a function or a string. If you pass a string
- then it will be looked up on the passed target.
- @param {Object} [args*] Any additional arguments you wish to pass to the method.
- @return {Object} return value from invoking the passed function.
- */
- Ember.run = function(target, method) {
- var ret;
- if (Ember.onerror) {
- try {
- ret = backburner.run.apply(backburner, arguments);
- } catch (e) {
- Ember.onerror(e);
- }
- } else {
- ret = backburner.run.apply(backburner, arguments);
- }
- return ret;
- };
- /**
- If no run-loop is present, it creates a new one. If a run loop is
- present it will queue itself to run on the existing run-loops action
- queue.
- Please note: This is not for normal usage, and should be used sparingly.
- If invoked when not within a run loop:
- ```javascript
- Ember.run.join(function() {
- // creates a new run-loop
- });
- ```
- Alternatively, if called within an existing run loop:
- ```javascript
- Ember.run(function() {
- // creates a new run-loop
- Ember.run.join(function() {
- // joins with the existing run-loop, and queues for invocation on
- // the existing run-loops action queue.
- });
- });
- ```
- @method join
- @namespace Ember
- @param {Object} [target] target of method to call
- @param {Function|String} method Method to invoke.
- May be a function or a string. If you pass a string
- then it will be looked up on the passed target.
- @param {Object} [args*] Any additional arguments you wish to pass to the method.
- @return {Object} Return value from invoking the passed function. Please note,
- when called within an existing loop, no return value is possible.
- */
- Ember.run.join = function(target, method /* args */) {
- if (!Ember.run.currentRunLoop) {
- return Ember.run.apply(Ember.run, arguments);
- }
- var args = slice.call(arguments);
- args.unshift('actions');
- Ember.run.schedule.apply(Ember.run, args);
- };
- /**
- Provides a useful utility for when integrating with non-Ember libraries
- that provide asynchronous callbacks.
- Ember utilizes a run-loop to batch and coalesce changes. This works by
- marking the start and end of Ember-related Javascript execution.
- When using events such as a View's click handler, Ember wraps the event
- handler in a run-loop, but when integrating with non-Ember libraries this
- can be tedious.
- For example, the following is rather verbose but is the correct way to combine
- third-party events and Ember code.
- ```javascript
- var that = this;
- jQuery(window).on('resize', function(){
- Ember.run(function(){
- that.handleResize();
- });
- });
- ```
- To reduce the boilerplate, the following can be used to construct a
- run-loop-wrapped callback handler.
- ```javascript
- jQuery(window).on('resize', Ember.run.bind(this, this.triggerResize));
- ```
- @method bind
- @namespace Ember.run
- @param {Object} [target] target of method to call
- @param {Function|String} method Method to invoke.
- May be a function or a string. If you pass a string
- then it will be looked up on the passed target.
- @param {Object} [args*] Any additional arguments you wish to pass to the method.
- @return {Object} return value from invoking the passed function. Please note,
- when called within an existing loop, no return value is possible.
- */
- Ember.run.bind = function(target, method /* args*/) {
- var args = arguments;
- return function() {
- return Ember.run.join.apply(Ember.run, args);
- };
- };
- Ember.run.backburner = backburner;
- var run = Ember.run;
- Ember.run.currentRunLoop = null;
- Ember.run.queues = backburner.queueNames;
- /**
- Begins a new RunLoop. Any deferred actions invoked after the begin will
- be buffered until you invoke a matching call to `Ember.run.end()`. This is
- a lower-level way to use a RunLoop instead of using `Ember.run()`.
- ```javascript
- Ember.run.begin();
- // code to be execute within a RunLoop
- Ember.run.end();
- ```
- @method begin
- @return {void}
- */
- Ember.run.begin = function() {
- backburner.begin();
- };
- /**
- Ends a RunLoop. This must be called sometime after you call
- `Ember.run.begin()` to flush any deferred actions. This is a lower-level way
- to use a RunLoop instead of using `Ember.run()`.
- ```javascript
- Ember.run.begin();
- // code to be execute within a RunLoop
- Ember.run.end();
- ```
- @method end
- @return {void}
- */
- Ember.run.end = function() {
- backburner.end();
- };
- /**
- Array of named queues. This array determines the order in which queues
- are flushed at the end of the RunLoop. You can define your own queues by
- simply adding the queue name to this array. Normally you should not need
- to inspect or modify this property.
- @property queues
- @type Array
- @default ['sync', 'actions', 'destroy']
- */
- /**
- Adds the passed target/method and any optional arguments to the named
- queue to be executed at the end of the RunLoop. If you have not already
- started a RunLoop when calling this method one will be started for you
- automatically.
- At the end of a RunLoop, any methods scheduled in this way will be invoked.
- Methods will be invoked in an order matching the named queues defined in
- the `Ember.run.queues` property.
- ```javascript
- Ember.run.schedule('sync', this, function() {
- // this will be executed in the first RunLoop queue, when bindings are synced
- console.log("scheduled on sync queue");
- });
- Ember.run.schedule('actions', this, function() {
- // this will be executed in the 'actions' queue, after bindings have synced.
- console.log("scheduled on actions queue");
- });
- // Note the functions will be run in order based on the run queues order.
- // Output would be:
- // scheduled on sync queue
- // scheduled on actions queue
- ```
- @method schedule
- @param {String} queue The name of the queue to schedule against.
- Default queues are 'sync' and 'actions'
- @param {Object} [target] target object to use as the context when invoking a method.
- @param {String|Function} method The method to invoke. If you pass a string it
- will be resolved on the target object at the time the scheduled item is
- invoked allowing you to change the target function.
- @param {Object} [arguments*] Optional arguments to be passed to the queued method.
- @return {void}
- */
- Ember.run.schedule = function(queue, target, method) {
- checkAutoRun();
- backburner.schedule.apply(backburner, arguments);
- };
- // Used by global test teardown
- Ember.run.hasScheduledTimers = function() {
- return backburner.hasTimers();
- };
- // Used by global test teardown
- Ember.run.cancelTimers = function () {
- backburner.cancelTimers();
- };
- /**
- Immediately flushes any events scheduled in the 'sync' queue. Bindings
- use this queue so this method is a useful way to immediately force all
- bindings in the application to sync.
- You should call this method anytime you need any changed state to propagate
- throughout the app immediately without repainting the UI (which happens
- in the later 'render' queue added by the `ember-views` package).
- ```javascript
- Ember.run.sync();
- ```
- @method sync
- @return {void}
- */
- Ember.run.sync = function() {
- if (backburner.currentInstance) {
- backburner.currentInstance.queues.sync.flush();
- }
- };
- /**
- Invokes the passed target/method and optional arguments after a specified
- period if time. The last parameter of this method must always be a number
- of milliseconds.
- You should use this method whenever you need to run some action after a
- period of time instead of using `setTimeout()`. This method will ensure that
- items that expire during the same script execution cycle all execute
- together, which is often more efficient than using a real setTimeout.
- ```javascript
- Ember.run.later(myContext, function() {
- // code here will execute within a RunLoop in about 500ms with this == myContext
- }, 500);
- ```
- @method later
- @param {Object} [target] target of method to invoke
- @param {Function|String} method The method to invoke.
- If you pass a string it will be resolved on the
- target at the time the method is invoked.
- @param {Object} [args*] Optional arguments to pass to the timeout.
- @param {Number} wait Number of milliseconds to wait.
- @return {String} a string you can use to cancel the timer in
- `Ember.run.cancel` later.
- */
- Ember.run.later = function(target, method) {
- return backburner.later.apply(backburner, arguments);
- };
- /**
- Schedule a function to run one time during the current RunLoop. This is equivalent
- to calling `scheduleOnce` with the "actions" queue.
- @method once
- @param {Object} [target] The target of the method to invoke.
- @param {Function|String} method The method to invoke.
- If you pass a string it will be resolved on the
- target at the time the method is invoked.
- @param {Object} [args*] Optional arguments to pass to the timeout.
- @return {Object} Timer information for use in cancelling, see `Ember.run.cancel`.
- */
- Ember.run.once = function(target, method) {
- checkAutoRun();
- var args = slice.call(arguments);
- args.unshift('actions');
- return backburner.scheduleOnce.apply(backburner, args);
- };
- /**
- Schedules a function to run one time in a given queue of the current RunLoop.
- Calling this method with the same queue/target/method combination will have
- no effect (past the initial call).
- Note that although you can pass optional arguments these will not be
- considered when looking for duplicates. New arguments will replace previous
- calls.
- ```javascript
- Ember.run(function() {
- var sayHi = function() { console.log('hi'); }
- Ember.run.scheduleOnce('afterRender', myContext, sayHi);
- Ember.run.scheduleOnce('afterRender', myContext, sayHi);
- // sayHi will only be executed once, in the afterRender queue of the RunLoop
- });
- ```
- Also note that passing an anonymous function to `Ember.run.scheduleOnce` will
- not prevent additional calls with an identical anonymous function from
- scheduling the items multiple times, e.g.:
- ```javascript
- function scheduleIt() {
- Ember.run.scheduleOnce('actions', myContext, function() { console.log("Closure"); });
- }
- scheduleIt();
- scheduleIt();
- // "Closure" will print twice, even though we're using `Ember.run.scheduleOnce`,
- // because the function we pass to it is anonymous and won't match the
- // previously scheduled operation.
- ```
- Available queues, and their order, can be found at `Ember.run.queues`
- @method scheduleOnce
- @param {String} [queue] The name of the queue to schedule against. Default queues are 'sync' and 'actions'.
- @param {Object} [target] The target of the method to invoke.
- @param {Function|String} method The method to invoke.
- If you pass a string it will be resolved on the
- target at the time the method is invoked.
- @param {Object} [args*] Optional arguments to pass to the timeout.
- @return {Object} Timer information for use in cancelling, see `Ember.run.cancel`.
- */
- Ember.run.scheduleOnce = function(queue, target, method) {
- checkAutoRun();
- return backburner.scheduleOnce.apply(backburner, arguments);
- };
- /**
- Schedules an item to run from within a separate run loop, after
- control has been returned to the system. This is equivalent to calling
- `Ember.run.later` with a wait time of 1ms.
- ```javascript
- Ember.run.next(myContext, function() {
- // code to be executed in the next run loop,
- // which will be scheduled after the current one
- });
- ```
- Multiple operations scheduled with `Ember.run.next` will coalesce
- into the same later run loop, along with any other operations
- scheduled by `Ember.run.later` that expire right around the same
- time that `Ember.run.next` operations will fire.
- Note that there are often alternatives to using `Ember.run.next`.
- For instance, if you'd like to schedule an operation to happen
- after all DOM element operations have completed within the current
- run loop, you can make use of the `afterRender` run loop queue (added
- by the `ember-views` package, along with the preceding `render` queue
- where all the DOM element operations happen). Example:
- ```javascript
- App.MyCollectionView = Ember.CollectionView.extend({
- didInsertElement: function() {
- Ember.run.scheduleOnce('afterRender', this, 'processChildElements');
- },
- processChildElements: function() {
- // ... do something with collectionView's child view
- // elements after they've finished rendering, which
- // can't be done within the CollectionView's
- // `didInsertElement` hook because that gets run
- // before the child elements have been added to the DOM.
- }
- });
- ```
- One benefit of the above approach compared to using `Ember.run.next` is
- that you will be able to perform DOM/CSS operations before unprocessed
- elements are rendered to the screen, which may prevent flickering or
- other artifacts caused by delaying processing until after rendering.
- The other major benefit to the above approach is that `Ember.run.next`
- introduces an element of non-determinism, which can make things much
- harder to test, due to its reliance on `setTimeout`; it's much harder
- to guarantee the order of scheduled operations when they are scheduled
- outside of the current run loop, i.e. with `Ember.run.next`.
- @method next
- @param {Object} [target] target of method to invoke
- @param {Function|String} method The method to invoke.
- If you pass a string it will be resolved on the
- target at the time the method is invoked.
- @param {Object} [args*] Optional arguments to pass to the timeout.
- @return {Object} Timer information for use in cancelling, see `Ember.run.cancel`.
- */
- Ember.run.next = function() {
- var args = slice.call(arguments);
- args.push(1);
- return backburner.later.apply(backburner, args);
- };
- /**
- Cancels a scheduled item. Must be a value returned by `Ember.run.later()`,
- `Ember.run.once()`, `Ember.run.next()`, `Ember.run.debounce()`, or
- `Ember.run.throttle()`.
- ```javascript
- var runNext = Ember.run.next(myContext, function() {
- // will not be executed
- });
- Ember.run.cancel(runNext);
- var runLater = Ember.run.later(myContext, function() {
- // will not be executed
- }, 500);
- Ember.run.cancel(runLater);
- var runOnce = Ember.run.once(myContext, function() {
- // will not be executed
- });
- Ember.run.cancel(runOnce);
- var throttle = Ember.run.throttle(myContext, function() {
- // will not be executed
- }, 1);
- Ember.run.cancel(throttle);
- var debounce = Ember.run.debounce(myContext, function() {
- // will not be executed
- }, 1);
- Ember.run.cancel(debounce);
- var debounceImmediate = Ember.run.debounce(myContext, function() {
- // will be executed since we passed in true (immediate)
- }, 100, true);
- // the 100ms delay until this method can be called again will be cancelled
- Ember.run.cancel(debounceImmediate);
- ```
- ```
- ```
- @method cancel
- @param {Object} timer Timer object to cancel
- @return {Boolean} true if cancelled or false/undefined if it wasn't found
- */
- Ember.run.cancel = function(timer) {
- return backburner.cancel(timer);
- };
- /**
- Delay calling the target method until the debounce period has elapsed
- with no additional debounce calls. If `debounce` is called again before
- the specified time has elapsed, the timer is reset and the entire period
- must pass again before the target method is called.
- This method should be used when an event may be called multiple times
- but the action should only be called once when the event is done firing.
- A common example is for scroll events where you only want updates to
- happen once scrolling has ceased.
- ```javascript
- var myFunc = function() { console.log(this.name + ' ran.'); };
- var myContext = {name: 'debounce'};
- Ember.run.debounce(myContext, myFunc, 150);
- // less than 150ms passes
- Ember.run.debounce(myContext, myFunc, 150);
- // 150ms passes
- // myFunc is invoked with context myContext
- // console logs 'debounce ran.' one time.
- ```
- Immediate allows you to run the function immediately, but debounce
- other calls for this function until the wait time has elapsed. If
- `debounce` is called again before the specified time has elapsed,
- the timer is reset and the entire period msut pass again before
- the method can be called again.
- ```javascript
- var myFunc = function() { console.log(this.name + ' ran.'); };
- var myContext = {name: 'debounce'};
- Ember.run.debounce(myContext, myFunc, 150, true);
- // console logs 'debounce ran.' one time immediately.
- // 100ms passes
- Ember.run.debounce(myContext, myFunc, 150, true);
- // 150ms passes and nothing else is logged to the console and
- // the debouncee is no longer being watched
- Ember.run.debounce(myContext, myFunc, 150, true);
- // console logs 'debounce ran.' one time immediately.
- // 150ms passes and nothing else is logged tot he console and
- // the debouncee is no longer being watched
- ```
- @method debounce
- @param {Object} [target] target of method to invoke
- @param {Function|String} method The method to invoke.
- May be a function or a string. If you pass a string
- then it will be looked up on the passed target.
- @param {Object} [args*] Optional arguments to pass to the timeout.
- @param {Number} wait Number of milliseconds to wait.
- @param {Boolean} immediate Trigger the function on the leading instead of the trailing edge of the wait interval.
- @return {Array} Timer information for use in cancelling, see `Ember.run.cancel`.
- */
- Ember.run.debounce = function() {
- return backburner.debounce.apply(backburner, arguments);
- };
- /**
- Ensure that the target method is never called more frequently than
- the specified spacing period.
- ```javascript
- var myFunc = function() { console.log(this.name + ' ran.'); };
- var myContext = {name: 'throttle'};
- Ember.run.throttle(myContext, myFunc, 150);
- // 50ms passes
- Ember.run.throttle(myContext, myFunc, 150);
- // 50ms passes
- Ember.run.throttle(myContext, myFunc, 150);
- // 50ms passes
- Ember.run.throttle(myContext, myFunc, 150);
- // 150ms passes
- // myFunc is invoked with context myContext
- // console logs 'throttle ran.' twice, 150ms apart.
- ```
- @method throttle
- @param {Object} [target] target of method to invoke
- @param {Function|String} method The method to invoke.
- May be a function or a string. If you pass a string
- then it will be looked up on the passed target.
- @param {Object} [args*] Optional arguments to pass to the timeout.
- @param {Number} spacing Number of milliseconds to space out requests.
- @return {Array} Timer information for use in cancelling, see `Ember.run.cancel`.
- */
- Ember.run.throttle = function() {
- return backburner.throttle.apply(backburner, arguments);
- };
- // Make sure it's not an autorun during testing
- function checkAutoRun() {
- if (!Ember.run.currentRunLoop) {
- Ember.assert("You have turned on testing mode, which disabled the run-loop's autorun. You will need to wrap any code with asynchronous side-effects in an Ember.run", !Ember.testing);
- }
- }
- })();
- (function() {
- // Ember.Logger
- // get
- // set
- // guidFor, meta
- // addObserver, removeObserver
- // Ember.run.schedule
- /**
- @module ember-metal
- */
- // ..........................................................
- // CONSTANTS
- //
- /**
- Debug parameter you can turn on. This will log all bindings that fire to
- the console. This should be disabled in production code. Note that you
- can also enable this from the console or temporarily.
- @property LOG_BINDINGS
- @for Ember
- @type Boolean
- @default false
- */
- Ember.LOG_BINDINGS = false || !!Ember.ENV.LOG_BINDINGS;
- var get = Ember.get,
- set = Ember.set,
- guidFor = Ember.guidFor,
- IS_GLOBAL = /^([A-Z$]|([0-9][A-Z$]))/;
- /**
- Returns true if the provided path is global (e.g., `MyApp.fooController.bar`)
- instead of local (`foo.bar.baz`).
- @method isGlobalPath
- @for Ember
- @private
- @param {String} path
- @return Boolean
- */
- var isGlobalPath = Ember.isGlobalPath = function(path) {
- return IS_GLOBAL.test(path);
- };
- function getWithGlobals(obj, path) {
- return get(isGlobalPath(path) ? Ember.lookup : obj, path);
- }
- // ..........................................................
- // BINDING
- //
- var Binding = function(toPath, fromPath) {
- this._direction = 'fwd';
- this._from = fromPath;
- this._to = toPath;
- this._directionMap = Ember.Map.create();
- };
- /**
- @class Binding
- @namespace Ember
- */
- Binding.prototype = {
- /**
- This copies the Binding so it can be connected to another object.
- @method copy
- @return {Ember.Binding} `this`
- */
- copy: function () {
- var copy = new Binding(this._to, this._from);
- if (this._oneWay) { copy._oneWay = true; }
- return copy;
- },
- // ..........................................................
- // CONFIG
- //
- /**
- This will set `from` property path to the specified value. It will not
- attempt to resolve this property path to an actual object until you
- connect the binding.
- The binding will search for the property path starting at the root object
- you pass when you `connect()` the binding. It follows the same rules as
- `get()` - see that method for more information.
- @method from
- @param {String} path the property path to connect to
- @return {Ember.Binding} `this`
- */
- from: function(path) {
- this._from = path;
- return this;
- },
- /**
- This will set the `to` property path to the specified value. It will not
- attempt to resolve this property path to an actual object until you
- connect the binding.
- The binding will search for the property path starting at the root object
- you pass when you `connect()` the binding. It follows the same rules as
- `get()` - see that method for more information.
- @method to
- @param {String|Tuple} path A property path or tuple
- @return {Ember.Binding} `this`
- */
- to: function(path) {
- this._to = path;
- return this;
- },
- /**
- Configures the binding as one way. A one-way binding will relay changes
- on the `from` side to the `to` side, but not the other way around. This
- means that if you change the `to` side directly, the `from` side may have
- a different value.
- @method oneWay
- @return {Ember.Binding} `this`
- */
- oneWay: function() {
- this._oneWay = true;
- return this;
- },
- /**
- @method toString
- @return {String} string representation of binding
- */
- toString: function() {
- var oneWay = this._oneWay ? '[oneWay]' : '';
- return "Ember.Binding<" + guidFor(this) + ">(" + this._from + " -> " + this._to + ")" + oneWay;
- },
- // ..........................................................
- // CONNECT AND SYNC
- //
- /**
- Attempts to connect this binding instance so that it can receive and relay
- changes. This method will raise an exception if you have not set the
- from/to properties yet.
- @method connect
- @param {Object} obj The root object for this binding.
- @return {Ember.Binding} `this`
- */
- connect: function(obj) {
- Ember.assert('Must pass a valid object to Ember.Binding.connect()', !!obj);
- var fromPath = this._from, toPath = this._to;
- Ember.trySet(obj, toPath, getWithGlobals(obj, fromPath));
- // add an observer on the object to be notified when the binding should be updated
- Ember.addObserver(obj, fromPath, this, this.fromDidChange);
- // if the binding is a two-way binding, also set up an observer on the target
- if (!this._oneWay) { Ember.addObserver(obj, toPath, this, this.toDidChange); }
- this._readyToSync = true;
- return this;
- },
- /**
- Disconnects the binding instance. Changes will no longer be relayed. You
- will not usually need to call this method.
- @method disconnect
- @param {Object} obj The root object you passed when connecting the binding.
- @return {Ember.Binding} `this`
- */
- disconnect: function(obj) {
- Ember.assert('Must pass a valid object to Ember.Binding.disconnect()', !!obj);
- var twoWay = !this._oneWay;
- // remove an observer on the object so we're no longer notified of
- // changes that should update bindings.
- Ember.removeObserver(obj, this._from, this, this.fromDidChange);
- // if the binding is two-way, remove the observer from the target as well
- if (twoWay) { Ember.removeObserver(obj, this._to, this, this.toDidChange); }
- this._readyToSync = false; // disable scheduled syncs...
- return this;
- },
- // ..........................................................
- // PRIVATE
- //
- /* called when the from side changes */
- fromDidChange: function(target) {
- this._scheduleSync(target, 'fwd');
- },
- /* called when the to side changes */
- toDidChange: function(target) {
- this._scheduleSync(target, 'back');
- },
- _scheduleSync: function(obj, dir) {
- var directionMap = this._directionMap;
- var existingDir = directionMap.get(obj);
- // if we haven't scheduled the binding yet, schedule it
- if (!existingDir) {
- Ember.run.schedule('sync', this, this._sync, obj);
- directionMap.set(obj, dir);
- }
- // If both a 'back' and 'fwd' sync have been scheduled on the same object,
- // default to a 'fwd' sync so that it remains deterministic.
- if (existingDir === 'back' && dir === 'fwd') {
- directionMap.set(obj, 'fwd');
- }
- },
- _sync: function(obj) {
- var log = Ember.LOG_BINDINGS;
- // don't synchronize destroyed objects or disconnected bindings
- if (obj.isDestroyed || !this._readyToSync) { return; }
- // get the direction of the binding for the object we are
- // synchronizing from
- var directionMap = this._directionMap;
- var direction = directionMap.get(obj);
- var fromPath = this._from, toPath = this._to;
- directionMap.remove(obj);
- // if we're synchronizing from the remote object...
- if (direction === 'fwd') {
- var fromValue = getWithGlobals(obj, this._from);
- if (log) {
- Ember.Logger.log(' ', this.toString(), '->', fromValue, obj);
- }
- if (this._oneWay) {
- Ember.trySet(obj, toPath, fromValue);
- } else {
- Ember._suspendObserver(obj, toPath, this, this.toDidChange, function () {
- Ember.trySet(obj, toPath, fromValue);
- });
- }
- // if we're synchronizing *to* the remote object
- } else if (direction === 'back') {
- var toValue = get(obj, this._to);
- if (log) {
- Ember.Logger.log(' ', this.toString(), '<-', toValue, obj);
- }
- Ember._suspendObserver(obj, fromPath, this, this.fromDidChange, function () {
- Ember.trySet(Ember.isGlobalPath(fromPath) ? Ember.lookup : obj, fromPath, toValue);
- });
- }
- }
- };
- function mixinProperties(to, from) {
- for (var key in from) {
- if (from.hasOwnProperty(key)) {
- to[key] = from[key];
- }
- }
- }
- mixinProperties(Binding, {
- /*
- See `Ember.Binding.from`.
- @method from
- @static
- */
- from: function() {
- var C = this, binding = new C();
- return binding.from.apply(binding, arguments);
- },
- /*
- See `Ember.Binding.to`.
- @method to
- @static
- */
- to: function() {
- var C = this, binding = new C();
- return binding.to.apply(binding, arguments);
- },
- /**
- Creates a new Binding instance and makes it apply in a single direction.
- A one-way binding will relay changes on the `from` side object (supplied
- as the `from` argument) the `to` side, but not the other way around.
- This means that if you change the "to" side directly, the "from" side may have
- a different value.
- See `Binding.oneWay`.
- @method oneWay
- @param {String} from from path.
- @param {Boolean} [flag] (Optional) passing nothing here will make the
- binding `oneWay`. You can instead pass `false` to disable `oneWay`, making the
- binding two way again.
- @return {Ember.Binding} `this`
- */
- oneWay: function(from, flag) {
- var C = this, binding = new C(null, from);
- return binding.oneWay(flag);
- }
- });
- /**
- An `Ember.Binding` connects the properties of two objects so that whenever
- the value of one property changes, the other property will be changed also.
- ## Automatic Creation of Bindings with `/^*Binding/`-named Properties
- You do not usually create Binding objects directly but instead describe
- bindings in your class or object definition using automatic binding
- detection.
- Properties ending in a `Binding` suffix will be converted to `Ember.Binding`
- instances. The value of this property should be a string representing a path
- to another object or a custom binding instanced created using Binding helpers
- (see "One Way Bindings"):
- ```
- valueBinding: "MyApp.someController.title"
- ```
- This will create a binding from `MyApp.someController.title` to the `value`
- property of your object instance automatically. Now the two values will be
- kept in sync.
- ## One Way Bindings
- One especially useful binding customization you can use is the `oneWay()`
- helper. This helper tells Ember that you are only interested in
- receiving changes on the object you are binding from. For example, if you
- are binding to a preference and you want to be notified if the preference
- has changed, but your object will not be changing the preference itself, you
- could do:
- ```
- bigTitlesBinding: Ember.Binding.oneWay("MyApp.preferencesController.bigTitles")
- ```
- This way if the value of `MyApp.preferencesController.bigTitles` changes the
- `bigTitles` property of your object will change also. However, if you
- change the value of your `bigTitles` property, it will not update the
- `preferencesController`.
- One way bindings are almost twice as fast to setup and twice as fast to
- execute because the binding only has to worry about changes to one side.
- You should consider using one way bindings anytime you have an object that
- may be created frequently and you do not intend to change a property; only
- to monitor it for changes (such as in the example above).
- ## Adding Bindings Manually
- All of the examples above show you how to configure a custom binding, but the
- result of these customizations will be a binding template, not a fully active
- Binding instance. The binding will actually become active only when you
- instantiate the object the binding belongs to. It is useful however, to
- understand what actually happens when the binding is activated.
- For a binding to function it must have at least a `from` property and a `to`
- property. The `from` property path points to the object/key that you want to
- bind from while the `to` path points to the object/key you want to bind to.
- When you define a custom binding, you are usually describing the property
- you want to bind from (such as `MyApp.someController.value` in the examples
- above). When your object is created, it will automatically assign the value
- you want to bind `to` based on the name of your binding key. In the
- examples above, during init, Ember objects will effectively call
- something like this on your binding:
- ```javascript
- binding = Ember.Binding.from(this.valueBinding).to("value");
- ```
- This creates a new binding instance based on the template you provide, and
- sets the to path to the `value` property of the new object. Now that the
- binding is fully configured with a `from` and a `to`, it simply needs to be
- connected to become active. This is done through the `connect()` method:
- ```javascript
- binding.connect(this);
- ```
- Note that when you connect a binding you pass the object you want it to be
- connected to. This object will be used as the root for both the from and
- to side of the binding when inspecting relative paths. This allows the
- binding to be automatically inherited by subclassed objects as well.
- Now that the binding is connected, it will observe both the from and to side
- and relay changes.
- If you ever needed to do so (you almost never will, but it is useful to
- understand this anyway), you could manually create an active binding by
- using the `Ember.bind()` helper method. (This is the same method used by
- to setup your bindings on objects):
- ```javascript
- Ember.bind(MyApp.anotherObject, "value", "MyApp.someController.value");
- ```
- Both of these code fragments have the same effect as doing the most friendly
- form of binding creation like so:
- ```javascript
- MyApp.anotherObject = Ember.Object.create({
- valueBinding: "MyApp.someController.value",
- // OTHER CODE FOR THIS OBJECT...
- });
- ```
- Ember's built in binding creation method makes it easy to automatically
- create bindings for you. You should always use the highest-level APIs
- available, even if you understand how it works underneath.
- @class Binding
- @namespace Ember
- @since Ember 0.9
- */
- Ember.Binding = Binding;
- /**
- Global helper method to create a new binding. Just pass the root object
- along with a `to` and `from` path to create and connect the binding.
- @method bind
- @for Ember
- @param {Object} obj The root object of the transform.
- @param {String} to The path to the 'to' side of the binding.
- Must be relative to obj.
- @param {String} from The path to the 'from' side of the binding.
- Must be relative to obj or a global path.
- @return {Ember.Binding} binding instance
- */
- Ember.bind = function(obj, to, from) {
- return new Ember.Binding(to, from).connect(obj);
- };
- /**
- @method oneWay
- @for Ember
- @param {Object} obj The root object of the transform.
- @param {String} to The path to the 'to' side of the binding.
- Must be relative to obj.
- @param {String} from The path to the 'from' side of the binding.
- Must be relative to obj or a global path.
- @return {Ember.Binding} binding instance
- */
- Ember.oneWay = function(obj, to, from) {
- return new Ember.Binding(to, from).oneWay().connect(obj);
- };
- })();
- (function() {
- /**
- @module ember
- @submodule ember-metal
- */
- var Mixin, REQUIRED, Alias,
- a_map = Ember.ArrayPolyfills.map,
- a_indexOf = Ember.ArrayPolyfills.indexOf,
- a_forEach = Ember.ArrayPolyfills.forEach,
- a_slice = [].slice,
- o_create = Ember.create,
- defineProperty = Ember.defineProperty,
- guidFor = Ember.guidFor,
- metaFor = Ember.meta,
- META_KEY = Ember.META_KEY;
- var expandProperties = Ember.expandProperties;
- function mixinsMeta(obj) {
- var m = metaFor(obj, true), ret = m.mixins;
- if (!ret) {
- ret = m.mixins = {};
- } else if (!m.hasOwnProperty('mixins')) {
- ret = m.mixins = o_create(ret);
- }
- return ret;
- }
- function initMixin(mixin, args) {
- if (args && args.length > 0) {
- mixin.mixins = a_map.call(args, function(x) {
- if (x instanceof Mixin) { return x; }
- // Note: Manually setup a primitive mixin here. This is the only
- // way to actually get a primitive mixin. This way normal creation
- // of mixins will give you combined mixins...
- var mixin = new Mixin();
- mixin.properties = x;
- return mixin;
- });
- }
- return mixin;
- }
- function isMethod(obj) {
- return 'function' === typeof obj &&
- obj.isMethod !== false &&
- obj !== Boolean && obj !== Object && obj !== Number && obj !== Array && obj !== Date && obj !== String;
- }
- var CONTINUE = {};
- function mixinProperties(mixinsMeta, mixin) {
- var guid;
- if (mixin instanceof Mixin) {
- guid = guidFor(mixin);
- if (mixinsMeta[guid]) { return CONTINUE; }
- mixinsMeta[guid] = mixin;
- return mixin.properties;
- } else {
- return mixin; // apply anonymous mixin properties
- }
- }
- function concatenatedMixinProperties(concatProp, props, values, base) {
- var concats;
- // reset before adding each new mixin to pickup concats from previous
- concats = values[concatProp] || base[concatProp];
- if (props[concatProp]) {
- concats = concats ? concats.concat(props[concatProp]) : props[concatProp];
- }
- return concats;
- }
- function giveDescriptorSuper(meta, key, property, values, descs) {
- var superProperty;
- // Computed properties override methods, and do not call super to them
- if (values[key] === undefined) {
- // Find the original descriptor in a parent mixin
- superProperty = descs[key];
- }
- // If we didn't find the original descriptor in a parent mixin, find
- // it on the original object.
- superProperty = superProperty || meta.descs[key];
- if (!superProperty || !(superProperty instanceof Ember.ComputedProperty)) {
- return property;
- }
- // Since multiple mixins may inherit from the same parent, we need
- // to clone the computed property so that other mixins do not receive
- // the wrapped version.
- property = o_create(property);
- property.func = Ember.wrap(property.func, superProperty.func);
- return property;
- }
- function giveMethodSuper(obj, key, method, values, descs) {
- var superMethod;
- // Methods overwrite computed properties, and do not call super to them.
- if (descs[key] === undefined) {
- // Find the original method in a parent mixin
- superMethod = values[key];
- }
- // If we didn't find the original value in a parent mixin, find it in
- // the original object
- superMethod = superMethod || obj[key];
- // Only wrap the new method if the original method was a function
- if ('function' !== typeof superMethod) {
- return method;
- }
- return Ember.wrap(method, superMethod);
- }
- function applyConcatenatedProperties(obj, key, value, values) {
- var baseValue = values[key] || obj[key];
- if (baseValue) {
- if ('function' === typeof baseValue.concat) {
- return baseValue.concat(value);
- } else {
- return Ember.makeArray(baseValue).concat(value);
- }
- } else {
- return Ember.makeArray(value);
- }
- }
- function applyMergedProperties(obj, key, value, values) {
- var baseValue = values[key] || obj[key];
- if (!baseValue) { return value; }
- var newBase = Ember.merge({}, baseValue);
- for (var prop in value) {
- if (!value.hasOwnProperty(prop)) { continue; }
- var propValue = value[prop];
- if (isMethod(propValue)) {
- // TODO: support for Computed Properties, etc?
- newBase[prop] = giveMethodSuper(obj, prop, propValue, baseValue, {});
- } else {
- newBase[prop] = propValue;
- }
- }
- return newBase;
- }
- function addNormalizedProperty(base, key, value, meta, descs, values, concats, mergings) {
- if (value instanceof Ember.Descriptor) {
- if (value === REQUIRED && descs[key]) { return CONTINUE; }
- // Wrap descriptor function to implement
- // _super() if needed
- if (value.func) {
- value = giveDescriptorSuper(meta, key, value, values, descs);
- }
- descs[key] = value;
- values[key] = undefined;
- } else {
- if ((concats && a_indexOf.call(concats, key) >= 0) ||
- key === 'concatenatedProperties' ||
- key === 'mergedProperties') {
- value = applyConcatenatedProperties(base, key, value, values);
- } else if ((mergings && a_indexOf.call(mergings, key) >= 0)) {
- value = applyMergedProperties(base, key, value, values);
- } else if (isMethod(value)) {
- value = giveMethodSuper(base, key, value, values, descs);
- }
- descs[key] = undefined;
- values[key] = value;
- }
- }
- function mergeMixins(mixins, m, descs, values, base, keys) {
- var mixin, props, key, concats, mergings, meta;
- function removeKeys(keyName) {
- delete descs[keyName];
- delete values[keyName];
- }
- for(var i=0, l=mixins.length; i<l; i++) {
- mixin = mixins[i];
- Ember.assert('Expected hash or Mixin instance, got ' + Object.prototype.toString.call(mixin),
- typeof mixin === 'object' && mixin !== null && Object.prototype.toString.call(mixin) !== '[object Array]');
- props = mixinProperties(m, mixin);
- if (props === CONTINUE) { continue; }
- if (props) {
- meta = metaFor(base);
- if (base.willMergeMixin) { base.willMergeMixin(props); }
- concats = concatenatedMixinProperties('concatenatedProperties', props, values, base);
- mergings = concatenatedMixinProperties('mergedProperties', props, values, base);
- for (key in props) {
- if (!props.hasOwnProperty(key)) { continue; }
- keys.push(key);
- addNormalizedProperty(base, key, props[key], meta, descs, values, concats, mergings);
- }
- // manually copy toString() because some JS engines do not enumerate it
- if (props.hasOwnProperty('toString')) { base.toString = props.toString; }
- } else if (mixin.mixins) {
- mergeMixins(mixin.mixins, m, descs, values, base, keys);
- if (mixin._without) { a_forEach.call(mixin._without, removeKeys); }
- }
- }
- }
- var IS_BINDING = Ember.IS_BINDING = /^.+Binding$/;
- function detectBinding(obj, key, value, m) {
- if (IS_BINDING.test(key)) {
- var bindings = m.bindings;
- if (!bindings) {
- bindings = m.bindings = {};
- } else if (!m.hasOwnProperty('bindings')) {
- bindings = m.bindings = o_create(m.bindings);
- }
- bindings[key] = value;
- }
- }
- function connectBindings(obj, m) {
- // TODO Mixin.apply(instance) should disconnect binding if exists
- var bindings = m.bindings, key, binding, to;
- if (bindings) {
- for (key in bindings) {
- binding = bindings[key];
- if (binding) {
- to = key.slice(0, -7); // strip Binding off end
- if (binding instanceof Ember.Binding) {
- binding = binding.copy(); // copy prototypes' instance
- binding.to(to);
- } else { // binding is string path
- binding = new Ember.Binding(to, binding);
- }
- binding.connect(obj);
- obj[key] = binding;
- }
- }
- // mark as applied
- m.bindings = {};
- }
- }
- function finishPartial(obj, m) {
- connectBindings(obj, m || metaFor(obj));
- return obj;
- }
- function followAlias(obj, desc, m, descs, values) {
- var altKey = desc.methodName, value;
- if (descs[altKey] || values[altKey]) {
- value = values[altKey];
- desc = descs[altKey];
- } else if (m.descs[altKey]) {
- desc = m.descs[altKey];
- value = undefined;
- } else {
- desc = undefined;
- value = obj[altKey];
- }
- return { desc: desc, value: value };
- }
- function updateObserversAndListeners(obj, key, observerOrListener, pathsKey, updateMethod) {
- var paths = observerOrListener[pathsKey];
- if (paths) {
- for (var i=0, l=paths.length; i<l; i++) {
- Ember[updateMethod](obj, paths[i], null, key);
- }
- }
- }
- function replaceObserversAndListeners(obj, key, observerOrListener) {
- var prev = obj[key];
- if ('function' === typeof prev) {
- updateObserversAndListeners(obj, key, prev, '__ember_observesBefore__', 'removeBeforeObserver');
- updateObserversAndListeners(obj, key, prev, '__ember_observes__', 'removeObserver');
- updateObserversAndListeners(obj, key, prev, '__ember_listens__', 'removeListener');
- }
- if ('function' === typeof observerOrListener) {
- updateObserversAndListeners(obj, key, observerOrListener, '__ember_observesBefore__', 'addBeforeObserver');
- updateObserversAndListeners(obj, key, observerOrListener, '__ember_observes__', 'addObserver');
- updateObserversAndListeners(obj, key, observerOrListener, '__ember_listens__', 'addListener');
- }
- }
- function applyMixin(obj, mixins, partial) {
- var descs = {}, values = {}, m = metaFor(obj),
- key, value, desc, keys = [];
- // Go through all mixins and hashes passed in, and:
- //
- // * Handle concatenated properties
- // * Handle merged properties
- // * Set up _super wrapping if necessary
- // * Set up computed property descriptors
- // * Copying `toString` in broken browsers
- mergeMixins(mixins, mixinsMeta(obj), descs, values, obj, keys);
- for(var i = 0, l = keys.length; i < l; i++) {
- key = keys[i];
- if (key === 'constructor' || !values.hasOwnProperty(key)) { continue; }
- desc = descs[key];
- value = values[key];
- if (desc === REQUIRED) { continue; }
- while (desc && desc instanceof Alias) {
- var followed = followAlias(obj, desc, m, descs, values);
- desc = followed.desc;
- value = followed.value;
- }
- if (desc === undefined && value === undefined) { continue; }
- replaceObserversAndListeners(obj, key, value);
- detectBinding(obj, key, value, m);
- defineProperty(obj, key, desc, value, m);
- }
- if (!partial) { // don't apply to prototype
- finishPartial(obj, m);
- }
- return obj;
- }
- /**
- @method mixin
- @for Ember
- @param obj
- @param mixins*
- @return obj
- */
- Ember.mixin = function(obj) {
- var args = a_slice.call(arguments, 1);
- applyMixin(obj, args, false);
- return obj;
- };
- /**
- The `Ember.Mixin` class allows you to create mixins, whose properties can be
- added to other classes. For instance,
- ```javascript
- App.Editable = Ember.Mixin.create({
- edit: function() {
- console.log('starting to edit');
- this.set('isEditing', true);
- },
- isEditing: false
- });
- // Mix mixins into classes by passing them as the first arguments to
- // .extend.
- App.CommentView = Ember.View.extend(App.Editable, {
- template: Ember.Handlebars.compile('{{#if view.isEditing}}...{{else}}...{{/if}}')
- });
- commentView = App.CommentView.create();
- commentView.edit(); // outputs 'starting to edit'
- ```
- Note that Mixins are created with `Ember.Mixin.create`, not
- `Ember.Mixin.extend`.
- Note that mixins extend a constructor's prototype so arrays and object literals
- defined as properties will be shared amongst objects that implement the mixin.
- If you want to define a property in a mixin that is not shared, you can define
- it either as a computed property or have it be created on initialization of the object.
- ```javascript
- //filters array will be shared amongst any object implementing mixin
- App.Filterable = Ember.Mixin.create({
- filters: Ember.A()
- });
- //filters will be a separate array for every object implementing the mixin
- App.Filterable = Ember.Mixin.create({
- filters: Ember.computed(function(){return Ember.A();})
- });
- //filters will be created as a separate array during the object's initialization
- App.Filterable = Ember.Mixin.create({
- init: function() {
- this._super();
- this.set("filters", Ember.A());
- }
- });
- ```
- @class Mixin
- @namespace Ember
- */
- Ember.Mixin = function() { return initMixin(this, arguments); };
- Mixin = Ember.Mixin;
- Mixin.prototype = {
- properties: null,
- mixins: null,
- ownerConstructor: null
- };
- Mixin._apply = applyMixin;
- Mixin.applyPartial = function(obj) {
- var args = a_slice.call(arguments, 1);
- return applyMixin(obj, args, true);
- };
- Mixin.finishPartial = finishPartial;
- Ember.anyUnprocessedMixins = false;
- /**
- @method create
- @static
- @param arguments*
- */
- Mixin.create = function() {
- Ember.anyUnprocessedMixins = true;
- var M = this;
- return initMixin(new M(), arguments);
- };
- var MixinPrototype = Mixin.prototype;
- /**
- @method reopen
- @param arguments*
- */
- MixinPrototype.reopen = function() {
- var mixin, tmp;
- if (this.properties) {
- mixin = Mixin.create();
- mixin.properties = this.properties;
- delete this.properties;
- this.mixins = [mixin];
- } else if (!this.mixins) {
- this.mixins = [];
- }
- var len = arguments.length, mixins = this.mixins, idx;
- for(idx=0; idx < len; idx++) {
- mixin = arguments[idx];
- Ember.assert('Expected hash or Mixin instance, got ' + Object.prototype.toString.call(mixin),
- typeof mixin === 'object' && mixin !== null && Object.prototype.toString.call(mixin) !== '[object Array]');
- if (mixin instanceof Mixin) {
- mixins.push(mixin);
- } else {
- tmp = Mixin.create();
- tmp.properties = mixin;
- mixins.push(tmp);
- }
- }
- return this;
- };
- /**
- @method apply
- @param obj
- @return applied object
- */
- MixinPrototype.apply = function(obj) {
- return applyMixin(obj, [this], false);
- };
- MixinPrototype.applyPartial = function(obj) {
- return applyMixin(obj, [this], true);
- };
- function _detect(curMixin, targetMixin, seen) {
- var guid = guidFor(curMixin);
- if (seen[guid]) { return false; }
- seen[guid] = true;
- if (curMixin === targetMixin) { return true; }
- var mixins = curMixin.mixins, loc = mixins ? mixins.length : 0;
- while (--loc >= 0) {
- if (_detect(mixins[loc], targetMixin, seen)) { return true; }
- }
- return false;
- }
- /**
- @method detect
- @param obj
- @return {Boolean}
- */
- MixinPrototype.detect = function(obj) {
- if (!obj) { return false; }
- if (obj instanceof Mixin) { return _detect(obj, this, {}); }
- var m = obj[META_KEY],
- mixins = m && m.mixins;
- if (mixins) {
- return !!mixins[guidFor(this)];
- }
- return false;
- };
- MixinPrototype.without = function() {
- var ret = new Mixin(this);
- ret._without = a_slice.call(arguments);
- return ret;
- };
- function _keys(ret, mixin, seen) {
- if (seen[guidFor(mixin)]) { return; }
- seen[guidFor(mixin)] = true;
- if (mixin.properties) {
- var props = mixin.properties;
- for (var key in props) {
- if (props.hasOwnProperty(key)) { ret[key] = true; }
- }
- } else if (mixin.mixins) {
- a_forEach.call(mixin.mixins, function(x) { _keys(ret, x, seen); });
- }
- }
- MixinPrototype.keys = function() {
- var keys = {}, seen = {}, ret = [];
- _keys(keys, this, seen);
- for(var key in keys) {
- if (keys.hasOwnProperty(key)) { ret.push(key); }
- }
- return ret;
- };
- // returns the mixins currently applied to the specified object
- // TODO: Make Ember.mixin
- Mixin.mixins = function(obj) {
- var m = obj[META_KEY],
- mixins = m && m.mixins, ret = [];
- if (!mixins) { return ret; }
- for (var key in mixins) {
- var mixin = mixins[key];
- // skip primitive mixins since these are always anonymous
- if (!mixin.properties) { ret.push(mixin); }
- }
- return ret;
- };
- REQUIRED = new Ember.Descriptor();
- REQUIRED.toString = function() { return '(Required Property)'; };
- /**
- Denotes a required property for a mixin
- @method required
- @for Ember
- */
- Ember.required = function() {
- return REQUIRED;
- };
- Alias = function(methodName) {
- this.methodName = methodName;
- };
- Alias.prototype = new Ember.Descriptor();
- /**
- Makes a method available via an additional name.
- ```javascript
- App.Person = Ember.Object.extend({
- name: function() {
- return 'Tomhuda Katzdale';
- },
- moniker: Ember.aliasMethod('name')
- });
- var goodGuy = App.Person.create()
- ```
- @method aliasMethod
- @for Ember
- @param {String} methodName name of the method to alias
- @return {Ember.Descriptor}
- */
- Ember.aliasMethod = function(methodName) {
- return new Alias(methodName);
- };
- // ..........................................................
- // OBSERVER HELPER
- //
- /**
- Specify a method that observes property changes.
- ```javascript
- Ember.Object.extend({
- valueObserver: Ember.observer('value', function() {
- // Executes whenever the "value" property changes
- })
- });
- ```
- In the future this method may become asynchronous. If you want to ensure
- synchronous behavior, use `immediateObserver`.
- Also available as `Function.prototype.observes` if prototype extensions are
- enabled.
- @method observer
- @for Ember
- @param {String} propertyNames*
- @param {Function} func
- @return func
- */
- Ember.observer = function() {
- var func = a_slice.call(arguments, -1)[0];
- var paths;
- var addWatchedProperty = function (path) { paths.push(path); };
- var _paths = a_slice.call(arguments, 0, -1);
- if (typeof func !== "function") {
- // revert to old, soft-deprecated argument ordering
- func = arguments[0];
- _paths = a_slice.call(arguments, 1);
- }
- paths = [];
- for (var i=0; i<_paths.length; ++i) {
- expandProperties(_paths[i], addWatchedProperty);
- }
- if (typeof func !== "function") {
- throw new Ember.Error("Ember.observer called without a function");
- }
- func.__ember_observes__ = paths;
- return func;
- };
- /**
- Specify a method that observes property changes.
- ```javascript
- Ember.Object.extend({
- valueObserver: Ember.immediateObserver('value', function() {
- // Executes whenever the "value" property changes
- })
- });
- ```
- In the future, `Ember.observer` may become asynchronous. In this event,
- `Ember.immediateObserver` will maintain the synchronous behavior.
- Also available as `Function.prototype.observesImmediately` if prototype extensions are
- enabled.
- @method immediateObserver
- @for Ember
- @param {String} propertyNames*
- @param {Function} func
- @return func
- */
- Ember.immediateObserver = function() {
- for (var i=0, l=arguments.length; i<l; i++) {
- var arg = arguments[i];
- Ember.assert("Immediate observers must observe internal properties only, not properties on other objects.", typeof arg !== "string" || arg.indexOf('.') === -1);
- }
- return Ember.observer.apply(this, arguments);
- };
- /**
- When observers fire, they are called with the arguments `obj`, `keyName`.
- Note, `@each.property` observer is called per each add or replace of an element
- and it's not called with a specific enumeration item.
- A `beforeObserver` fires before a property changes.
- A `beforeObserver` is an alternative form of `.observesBefore()`.
- ```javascript
- App.PersonView = Ember.View.extend({
- friends: [{ name: 'Tom' }, { name: 'Stefan' }, { name: 'Kris' }],
- valueWillChange: Ember.beforeObserver('content.value', function(obj, keyName) {
- this.changingFrom = obj.get(keyName);
- }),
- valueDidChange: Ember.observer('content.value', function(obj, keyName) {
- // only run if updating a value already in the DOM
- if (this.get('state') === 'inDOM') {
- var color = obj.get(keyName) > this.changingFrom ? 'green' : 'red';
- // logic
- }
- }),
- friendsDidChange: Ember.observer('friends.@each.name', function(obj, keyName) {
- // some logic
- // obj.get(keyName) returns friends array
- })
- });
- ```
- Also available as `Function.prototype.observesBefore` if prototype extensions are
- enabled.
- @method beforeObserver
- @for Ember
- @param {String} propertyNames*
- @param {Function} func
- @return func
- */
- Ember.beforeObserver = function() {
- var func = a_slice.call(arguments, -1)[0];
- var paths;
- var addWatchedProperty = function(path) { paths.push(path); };
- var _paths = a_slice.call(arguments, 0, -1);
- if (typeof func !== "function") {
- // revert to old, soft-deprecated argument ordering
- func = arguments[0];
- _paths = a_slice.call(arguments, 1);
- }
- paths = [];
- for (var i=0; i<_paths.length; ++i) {
- expandProperties(_paths[i], addWatchedProperty);
- }
- if (typeof func !== "function") {
- throw new Ember.Error("Ember.beforeObserver called without a function");
- }
- func.__ember_observesBefore__ = paths;
- return func;
- };
- })();
- (function() {
- // Provides a way to register library versions with ember.
- var forEach = Ember.EnumerableUtils.forEach,
- indexOf = Ember.EnumerableUtils.indexOf;
- Ember.libraries = function() {
- var libraries = [];
- var coreLibIndex = 0;
- var getLibrary = function(name) {
- for (var i = 0; i < libraries.length; i++) {
- if (libraries[i].name === name) {
- return libraries[i];
- }
- }
- };
- libraries.register = function(name, version) {
- if (!getLibrary(name)) {
- libraries.push({name: name, version: version});
- }
- };
- libraries.registerCoreLibrary = function(name, version) {
- if (!getLibrary(name)) {
- libraries.splice(coreLibIndex++, 0, {name: name, version: version});
- }
- };
- libraries.deRegister = function(name) {
- var lib = getLibrary(name);
- if (lib) libraries.splice(indexOf(libraries, lib), 1);
- };
- libraries.each = function (callback) {
- forEach(libraries, function(lib) {
- callback(lib.name, lib.version);
- });
- };
- return libraries;
- }();
- Ember.libraries.registerCoreLibrary('Ember', Ember.VERSION);
- })();
- (function() {
- /**
- Ember Metal
- @module ember
- @submodule ember-metal
- */
- })();
- (function() {
- /**
- @class RSVP
- @module RSVP
- */
- define("rsvp/all",
- ["./promise","exports"],
- function(__dependency1__, __exports__) {
- "use strict";
- var Promise = __dependency1__["default"];
- /**
- This is a convenient alias for `RSVP.Promise.all`.
- @method all
- @for RSVP
- @param {Array} array Array of promises.
- @param {String} label An optional label. This is useful
- for tooling.
- @static
- */
- __exports__["default"] = function all(array, label) {
- return Promise.all(array, label);
- };
- });
- define("rsvp/all_settled",
- ["./promise","./utils","exports"],
- function(__dependency1__, __dependency2__, __exports__) {
- "use strict";
- var Promise = __dependency1__["default"];
- var isArray = __dependency2__.isArray;
- var isNonThenable = __dependency2__.isNonThenable;
- /**
- `RSVP.allSettled` is similar to `RSVP.all`, but instead of implementing
- a fail-fast method, it waits until all the promises have returned and
- shows you all the results. This is useful if you want to handle multiple
- promises' failure states together as a set.
- Returns a promise that is fulfilled when all the given promises have been
- settled. The return promise is fulfilled with an array of the states of
- the promises passed into the `promises` array argument.
- Each state object will either indicate fulfillment or rejection, and
- provide the corresponding value or reason. The states will take one of
- the following formats:
- ```javascript
- { state: 'fulfilled', value: value }
- or
- { state: 'rejected', reason: reason }
- ```
- Example:
- ```javascript
- var promise1 = RSVP.Promise.resolve(1);
- var promise2 = RSVP.Promise.reject(new Error('2'));
- var promise3 = RSVP.Promise.reject(new Error('3'));
- var promises = [ promise1, promise2, promise3 ];
- RSVP.allSettled(promises).then(function(array){
- // array == [
- // { state: 'fulfilled', value: 1 },
- // { state: 'rejected', reason: Error },
- // { state: 'rejected', reason: Error }
- // ]
- // Note that for the second item, reason.message will be "2", and for the
- // third item, reason.message will be "3".
- }, function(error) {
- // Not run. (This block would only be called if allSettled had failed,
- // for instance if passed an incorrect argument type.)
- });
- ```
- @method allSettled
- @for RSVP
- @param {Array} promises
- @param {String} label - optional string that describes the promise.
- Useful for tooling.
- @return {Promise} promise that is fulfilled with an array of the settled
- states of the constituent promises.
- @static
- */
- __exports__["default"] = function allSettled(entries, label) {
- return new Promise(function(resolve, reject) {
- if (!isArray(entries)) {
- throw new TypeError('You must pass an array to allSettled.');
- }
- var remaining = entries.length;
- var entry;
- if (remaining === 0) {
- resolve([]);
- return;
- }
- var results = new Array(remaining);
- function fulfilledResolver(index) {
- return function(value) {
- resolveAll(index, fulfilled(value));
- };
- }
- function rejectedResolver(index) {
- return function(reason) {
- resolveAll(index, rejected(reason));
- };
- }
- function resolveAll(index, value) {
- results[index] = value;
- if (--remaining === 0) {
- resolve(results);
- }
- }
- for (var index = 0; index < entries.length; index++) {
- entry = entries[index];
- if (isNonThenable(entry)) {
- resolveAll(index, fulfilled(entry));
- } else {
- Promise.cast(entry).then(fulfilledResolver(index), rejectedResolver(index));
- }
- }
- }, label);
- };
- function fulfilled(value) {
- return { state: 'fulfilled', value: value };
- }
- function rejected(reason) {
- return { state: 'rejected', reason: reason };
- }
- });
- define("rsvp/config",
- ["./events","exports"],
- function(__dependency1__, __exports__) {
- "use strict";
- var EventTarget = __dependency1__["default"];
- var config = {
- instrument: false
- };
- EventTarget.mixin(config);
- function configure(name, value) {
- if (name === 'onerror') {
- // handle for legacy users that expect the actual
- // error to be passed to their function added via
- // `RSVP.configure('onerror', someFunctionHere);`
- config.on('error', value);
- return;
- }
- if (arguments.length === 2) {
- config[name] = value;
- } else {
- return config[name];
- }
- }
- __exports__.config = config;
- __exports__.configure = configure;
- });
- define("rsvp/defer",
- ["./promise","exports"],
- function(__dependency1__, __exports__) {
- "use strict";
- var Promise = __dependency1__["default"];
- /**
- `RSVP.defer` returns an object similar to jQuery's `$.Deferred`.
- `RSVP.defer` should be used when porting over code reliant on `$.Deferred`'s
- interface. New code should use the `RSVP.Promise` constructor instead.
- The object returned from `RSVP.defer` is a plain object with three properties:
- * promise - an `RSVP.Promise`.
- * reject - a function that causes the `promise` property on this object to
- become rejected
- * resolve - a function that causes the `promise` property on this object to
- become fulfilled.
- Example:
- ```javascript
- var deferred = RSVP.defer();
- deferred.resolve("Success!");
- defered.promise.then(function(value){
- // value here is "Success!"
- });
- ```
- @method defer
- @for RSVP
- @param {String} label optional string for labeling the promise.
- Useful for tooling.
- @return {Object}
- */
- __exports__["default"] = function defer(label) {
- var deferred = { };
- deferred.promise = new Promise(function(resolve, reject) {
- deferred.resolve = resolve;
- deferred.reject = reject;
- }, label);
- return deferred;
- };
- });
- define("rsvp/events",
- ["exports"],
- function(__exports__) {
- "use strict";
- var indexOf = function(callbacks, callback) {
- for (var i=0, l=callbacks.length; i<l; i++) {
- if (callbacks[i] === callback) { return i; }
- }
- return -1;
- };
- var callbacksFor = function(object) {
- var callbacks = object._promiseCallbacks;
- if (!callbacks) {
- callbacks = object._promiseCallbacks = {};
- }
- return callbacks;
- };
- /**
- @class RSVP.EventTarget
- */
- __exports__["default"] = {
- /**
- `RSVP.EventTarget.mixin` extends an object with EventTarget methods. For
- Example:
- ```javascript
- var object = {};
- RSVP.EventTarget.mixin(object);
- object.on("finished", function(event) {
- // handle event
- });
- object.trigger("finished", { detail: value });
- ```
- `EventTarget.mixin` also works with prototypes:
- ```javascript
- var Person = function() {};
- RSVP.EventTarget.mixin(Person.prototype);
- var yehuda = new Person();
- var tom = new Person();
- yehuda.on("poke", function(event) {
- console.log("Yehuda says OW");
- });
- tom.on("poke", function(event) {
- console.log("Tom says OW");
- });
- yehuda.trigger("poke");
- tom.trigger("poke");
- ```
- @method mixin
- @param {Object} object object to extend with EventTarget methods
- @private
- */
- mixin: function(object) {
- object.on = this.on;
- object.off = this.off;
- object.trigger = this.trigger;
- object._promiseCallbacks = undefined;
- return object;
- },
- /**
- Registers a callback to be executed when `eventName` is triggered
- ```javascript
- object.on('event', function(eventInfo){
- // handle the event
- });
- object.trigger('event');
- ```
- @method on
- @param {String} eventName name of the event to listen for
- @param {Function} callback function to be called when the event is triggered.
- @private
- */
- on: function(eventName, callback) {
- var allCallbacks = callbacksFor(this), callbacks;
- callbacks = allCallbacks[eventName];
- if (!callbacks) {
- callbacks = allCallbacks[eventName] = [];
- }
- if (indexOf(callbacks, callback) === -1) {
- callbacks.push(callback);
- }
- },
- /**
- You can use `off` to stop firing a particular callback for an event:
- ```javascript
- function doStuff() { // do stuff! }
- object.on('stuff', doStuff);
- object.trigger('stuff'); // doStuff will be called
- // Unregister ONLY the doStuff callback
- object.off('stuff', doStuff);
- object.trigger('stuff'); // doStuff will NOT be called
- ```
- If you don't pass a `callback` argument to `off`, ALL callbacks for the
- event will not be executed when the event fires. For example:
- ```javascript
- var callback1 = function(){};
- var callback2 = function(){};
- object.on('stuff', callback1);
- object.on('stuff', callback2);
- object.trigger('stuff'); // callback1 and callback2 will be executed.
- object.off('stuff');
- object.trigger('stuff'); // callback1 and callback2 will not be executed!
- ```
- @method off
- @param {String} eventName event to stop listening to
- @param {Function} callback optional argument. If given, only the function
- given will be removed from the event's callback queue. If no `callback`
- argument is given, all callbacks will be removed from the event's callback
- queue.
- @private
- */
- off: function(eventName, callback) {
- var allCallbacks = callbacksFor(this), callbacks, index;
- if (!callback) {
- allCallbacks[eventName] = [];
- return;
- }
- callbacks = allCallbacks[eventName];
- index = indexOf(callbacks, callback);
- if (index !== -1) { callbacks.splice(index, 1); }
- },
- /**
- Use `trigger` to fire custom events. For example:
- ```javascript
- object.on('foo', function(){
- console.log('foo event happened!');
- });
- object.trigger('foo');
- // 'foo event happened!' logged to the console
- ```
- You can also pass a value as a second argument to `trigger` that will be
- passed as an argument to all event listeners for the event:
- ```javascript
- object.on('foo', function(value){
- console.log(value.name);
- });
- object.trigger('foo', { name: 'bar' });
- // 'bar' logged to the console
- ```
- @method trigger
- @param {String} eventName name of the event to be triggered
- @param {Any} options optional value to be passed to any event handlers for
- the given `eventName`
- @private
- */
- trigger: function(eventName, options) {
- var allCallbacks = callbacksFor(this),
- callbacks, callbackTuple, callback, binding;
- if (callbacks = allCallbacks[eventName]) {
- // Don't cache the callbacks.length since it may grow
- for (var i=0; i<callbacks.length; i++) {
- callback = callbacks[i];
- callback(options);
- }
- }
- }
- };
- });
- define("rsvp/filter",
- ["./all","./map","./utils","exports"],
- function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
- "use strict";
- var all = __dependency1__["default"];
- var map = __dependency2__["default"];
- var isFunction = __dependency3__.isFunction;
- var isArray = __dependency3__.isArray;
- /**
- `RSVP.filter` is similar to JavaScript's native `filter` method, except that it
- waits for all promises to become fulfilled before running the `filterFn` on
- each item in given to `promises`. `RSVP.filter` returns a promise that will
- become fulfilled with the result of running `filterFn` on the values the
- promises become fulfilled with.
- For example:
- ```javascript
- var promise1 = RSVP.resolve(1);
- var promise2 = RSVP.resolve(2);
- var promise3 = RSVP.resolve(3);
- var filterFn = function(item){
- return item > 1;
- };
- RSVP.filter(promises, filterFn).then(function(result){
- // result is [ 2, 3 ]
- });
- ```
- If any of the `promises` given to `RSVP.filter` are rejected, the first promise
- that is rejected will be given as an argument to the returned promise's
- rejection handler. For example:
- ```javascript
- var promise1 = RSVP.resolve(1);
- var promise2 = RSVP.reject(new Error("2"));
- var promise3 = RSVP.reject(new Error("3"));
- var promises = [ promise1, promise2, promise3 ];
- var filterFn = function(item){
- return item > 1;
- };
- RSVP.filter(promises, filterFn).then(function(array){
- // Code here never runs because there are rejected promises!
- }, function(reason) {
- // reason.message === "2"
- });
- ```
- `RSVP.filter` will also wait for any promises returned from `filterFn`.
- For instance, you may want to fetch a list of users then return a subset
- of those users based on some asynchronous operation:
- ```javascript
- var alice = { name: 'alice' };
- var bob = { name: 'bob' };
- var users = [ alice, bob ];
- var promises = users.map(function(user){
- return RSVP.resolve(user);
- });
- var filterFn = function(user){
- // Here, Alice has permissions to create a blog post, but Bob does not.
- return getPrivilegesForUser(user).then(function(privs){
- return privs.can_create_blog_post === true;
- });
- };
- RSVP.filter(promises, filterFn).then(function(users){
- // true, because the server told us only Alice can create a blog post.
- users.length === 1;
- // false, because Alice is the only user present in `users`
- users[0] === bob;
- });
- ```
- @method filter
- @for RSVP
- @param {Array} promises
- @param {Function} filterFn - function to be called on each resolved value to
- filter the final results.
- @param {String} label optional string describing the promise. Useful for
- tooling.
- @return {Promise}
- */
- function filter(promises, filterFn, label) {
- return all(promises, label).then(function(values){
- if (!isArray(promises)) {
- throw new TypeError('You must pass an array to filter.');
- }
- if (!isFunction(filterFn)){
- throw new TypeError("You must pass a function to filter's second argument.");
- }
- return map(promises, filterFn, label).then(function(filterResults){
- var i,
- valuesLen = values.length,
- filtered = [];
- for (i = 0; i < valuesLen; i++){
- if(filterResults[i]) filtered.push(values[i]);
- }
- return filtered;
- });
- });
- }
- __exports__["default"] = filter;
- });
- define("rsvp/hash",
- ["./promise","./utils","exports"],
- function(__dependency1__, __dependency2__, __exports__) {
- "use strict";
- var Promise = __dependency1__["default"];
- var isNonThenable = __dependency2__.isNonThenable;
- var keysOf = __dependency2__.keysOf;
- /**
- `RSVP.hash` is similar to `RSVP.all`, but takes an object instead of an array
- for its `promises` argument.
- Returns a promise that is fulfilled when all the given promises have been
- fulfilled, or rejected if any of them become rejected. The returned promise
- is fulfilled with a hash that has the same key names as the `promises` object
- argument. If any of the values in the object are not promises, they will
- simply be copied over to the fulfilled object.
- Example:
- ```javascript
- var promises = {
- myPromise: RSVP.resolve(1),
- yourPromise: RSVP.resolve(2),
- theirPromise: RSVP.resolve(3),
- notAPromise: 4
- };
- RSVP.hash(promises).then(function(hash){
- // hash here is an object that looks like:
- // {
- // myPromise: 1,
- // yourPromise: 2,
- // theirPromise: 3,
- // notAPromise: 4
- // }
- });
- ````
- If any of the `promises` given to `RSVP.hash` are rejected, the first promise
- that is rejected will be given as the reason to the rejection handler.
- Example:
- ```javascript
- var promises = {
- myPromise: RSVP.resolve(1),
- rejectedPromise: RSVP.reject(new Error("rejectedPromise")),
- anotherRejectedPromise: RSVP.reject(new Error("anotherRejectedPromise")),
- };
- RSVP.hash(promises).then(function(hash){
- // Code here never runs because there are rejected promises!
- }, function(reason) {
- // reason.message === "rejectedPromise"
- });
- ```
- An important note: `RSVP.hash` is intended for plain JavaScript objects that
- are just a set of keys and values. `RSVP.hash` will NOT preserve prototype
- chains.
- Example:
- ```javascript
- function MyConstructor(){
- this.example = RSVP.resolve("Example");
- }
- MyConstructor.prototype = {
- protoProperty: RSVP.resolve("Proto Property")
- };
- var myObject = new MyConstructor();
- RSVP.hash(myObject).then(function(hash){
- // protoProperty will not be present, instead you will just have an
- // object that looks like:
- // {
- // example: "Example"
- // }
- //
- // hash.hasOwnProperty('protoProperty'); // false
- // 'undefined' === typeof hash.protoProperty
- });
- ```
- @method hash
- @for RSVP
- @param {Object} promises
- @param {String} label optional string that describes the promise.
- Useful for tooling.
- @return {Promise} promise that is fulfilled when all properties of `promises`
- have been fulfilled, or rejected if any of them become rejected.
- @static
- */
- __exports__["default"] = function hash(object, label) {
- return new Promise(function(resolve, reject){
- var results = {};
- var keys = keysOf(object);
- var remaining = keys.length;
- var entry, property;
- if (remaining === 0) {
- resolve(results);
- return;
- }
- function fulfilledTo(property) {
- return function(value) {
- results[property] = value;
- if (--remaining === 0) {
- resolve(results);
- }
- };
- }
- function onRejection(reason) {
- remaining = 0;
- reject(reason);
- }
- for (var i = 0; i < keys.length; i++) {
- property = keys[i];
- entry = object[property];
- if (isNonThenable(entry)) {
- results[property] = entry;
- if (--remaining === 0) {
- resolve(results);
- }
- } else {
- Promise.cast(entry).then(fulfilledTo(property), onRejection);
- }
- }
- });
- };
- });
- define("rsvp/instrument",
- ["./config","./utils","exports"],
- function(__dependency1__, __dependency2__, __exports__) {
- "use strict";
- var config = __dependency1__.config;
- var now = __dependency2__.now;
- __exports__["default"] = function instrument(eventName, promise, child) {
- // instrumentation should not disrupt normal usage.
- try {
- config.trigger(eventName, {
- guid: promise._guidKey + promise._id,
- eventName: eventName,
- detail: promise._detail,
- childGuid: child && promise._guidKey + child._id,
- label: promise._label,
- timeStamp: now(),
- stack: new Error(promise._label).stack
- });
- } catch(error) {
- setTimeout(function(){
- throw error;
- }, 0);
- }
- };
- });
- define("rsvp/map",
- ["./promise","./all","./utils","exports"],
- function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
- "use strict";
- var Promise = __dependency1__["default"];
- var all = __dependency2__["default"];
- var isArray = __dependency3__.isArray;
- var isFunction = __dependency3__.isFunction;
- /**
- `RSVP.map` is similar to JavaScript's native `map` method, except that it
- waits for all promises to become fulfilled before running the `mapFn` on
- each item in given to `promises`. `RSVP.map` returns a promise that will
- become fulfilled with the result of running `mapFn` on the values the promises
- become fulfilled with.
- For example:
- ```javascript
- var promise1 = RSVP.resolve(1);
- var promise2 = RSVP.resolve(2);
- var promise3 = RSVP.resolve(3);
- var promises = [ promise1, promise2, promise3 ];
- var mapFn = function(item){
- return item + 1;
- };
- RSVP.map(promises, mapFn).then(function(result){
- // result is [ 2, 3, 4 ]
- });
- ```
- If any of the `promises` given to `RSVP.map` are rejected, the first promise
- that is rejected will be given as an argument to the returned promise's
- rejection handler. For example:
- ```javascript
- var promise1 = RSVP.resolve(1);
- var promise2 = RSVP.reject(new Error("2"));
- var promise3 = RSVP.reject(new Error("3"));
- var promises = [ promise1, promise2, promise3 ];
- var mapFn = function(item){
- return item + 1;
- };
- RSVP.map(promises, mapFn).then(function(array){
- // Code here never runs because there are rejected promises!
- }, function(reason) {
- // reason.message === "2"
- });
- ```
- `RSVP.map` will also wait if a promise is returned from `mapFn`. For example,
- say you want to get all comments from a set of blog posts, but you need
- the blog posts first becuase they contain a url to those comments.
- ```javscript
- var mapFn = function(blogPost){
- // getComments does some ajax and returns an RSVP.Promise that is fulfilled
- // with some comments data
- return getComments(blogPost.comments_url);
- };
- // getBlogPosts does some ajax and returns an RSVP.Promise that is fulfilled
- // with some blog post data
- RSVP.map(getBlogPosts(), mapFn).then(function(comments){
- // comments is the result of asking the server for the comments
- // of all blog posts returned from getBlogPosts()
- });
- ```
- @method map
- @for RSVP
- @param {Array} promises
- @param {Function} mapFn function to be called on each fulfilled promise.
- @param {String} label optional string for labeling the promise.
- Useful for tooling.
- @return {Promise} promise that is fulfilled with the result of calling
- `mapFn` on each fulfilled promise or value when they become fulfilled.
- The promise will be rejected if any of the given `promises` become rejected.
- @static
- */
- __exports__["default"] = function map(promises, mapFn, label) {
- return all(promises, label).then(function(results){
- if (!isArray(promises)) {
- throw new TypeError('You must pass an array to map.');
- }
- if (!isFunction(mapFn)){
- throw new TypeError("You must pass a function to map's second argument.");
- }
- var resultLen = results.length,
- mappedResults = [],
- i;
- for (i = 0; i < resultLen; i++){
- mappedResults.push(mapFn(results[i]));
- }
- return all(mappedResults, label);
- });
- };
- });
- define("rsvp/node",
- ["./promise","exports"],
- function(__dependency1__, __exports__) {
- "use strict";
- var Promise = __dependency1__["default"];
- var slice = Array.prototype.slice;
- function makeNodeCallbackFor(resolve, reject) {
- return function (error, value) {
- if (error) {
- reject(error);
- } else if (arguments.length > 2) {
- resolve(slice.call(arguments, 1));
- } else {
- resolve(value);
- }
- };
- }
- /**
- `RSVP.denodeify` takes a "node-style" function and returns a function that
- will return an `RSVP.Promise`. You can use `denodeify` in Node.js or the
- browser when you'd prefer to use promises over using callbacks. For example,
- `denodeify` transforms the following:
- ```javascript
- var fs = require('fs');
- fs.readFile('myfile.txt', function(err, data){
- if (err) return handleError(err);
- handleData(data);
- });
- ```
- into:
- ```javascript
- var fs = require('fs');
- var readFile = RSVP.denodeify(fs.readFile);
- readFile('myfile.txt').then(handleData, handleError);
- ```
- Using `denodeify` makes it easier to compose asynchronous operations instead
- of using callbacks. For example, instead of:
- ```javascript
- var fs = require('fs');
- var log = require('some-async-logger');
- fs.readFile('myfile.txt', function(err, data){
- if (err) return handleError(err);
- fs.writeFile('myfile2.txt', data, function(err){
- if (err) throw err;
- log('success', function(err) {
- if (err) throw err;
- });
- });
- });
- ```
- You can chain the operations together using `then` from the returned promise:
- ```javascript
- var fs = require('fs');
- var denodeify = RSVP.denodeify;
- var readFile = denodeify(fs.readFile);
- var writeFile = denodeify(fs.writeFile);
- var log = denodeify(require('some-async-logger'));
- readFile('myfile.txt').then(function(data){
- return writeFile('myfile2.txt', data);
- }).then(function(){
- return log('SUCCESS');
- }).then(function(){
- // success handler
- }, function(reason){
- // rejection handler
- });
- ```
- @method denodeify
- @for RSVP
- @param {Function} nodeFunc a "node-style" function that takes a callback as
- its last argument. The callback expects an error to be passed as its first
- argument (if an error occurred, otherwise null), and the value from the
- operation as its second argument ("function(err, value){ }").
- @param {Any} binding optional argument for binding the "this" value when
- calling the `nodeFunc` function.
- @return {Function} a function that wraps `nodeFunc` to return an
- `RSVP.Promise`
- @static
- */
- __exports__["default"] = function denodeify(nodeFunc, binding) {
- return function() {
- var nodeArgs = slice.call(arguments), resolve, reject;
- var thisArg = this || binding;
- return new Promise(function(resolve, reject) {
- Promise.all(nodeArgs).then(function(nodeArgs) {
- try {
- nodeArgs.push(makeNodeCallbackFor(resolve, reject));
- nodeFunc.apply(thisArg, nodeArgs);
- } catch(e) {
- reject(e);
- }
- });
- });
- };
- };
- });
- define("rsvp/promise",
- ["./config","./events","./instrument","./utils","./promise/cast","./promise/all","./promise/race","./promise/resolve","./promise/reject","exports"],
- function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __exports__) {
- "use strict";
- var config = __dependency1__.config;
- var EventTarget = __dependency2__["default"];
- var instrument = __dependency3__["default"];
- var objectOrFunction = __dependency4__.objectOrFunction;
- var isFunction = __dependency4__.isFunction;
- var now = __dependency4__.now;
- var cast = __dependency5__["default"];
- var all = __dependency6__["default"];
- var race = __dependency7__["default"];
- var Resolve = __dependency8__["default"];
- var Reject = __dependency9__["default"];
- var guidKey = 'rsvp_' + now() + '-';
- var counter = 0;
- function noop() {}
- __exports__["default"] = Promise;
- /**
- Promise objects represent the eventual result of an asynchronous operation. The
- primary way of interacting with a promise is through its `then` method, which
- registers callbacks to receive either a promise’s eventual value or the reason
- why the promise cannot be fulfilled.
- Terminology
- -----------
- - `promise` is an object or function with a `then` method whose behavior conforms to this specification.
- - `thenable` is an object or function that defines a `then` method.
- - `value` is any legal JavaScript value (including undefined, a thenable, or a promise).
- - `exception` is a value that is thrown using the throw statement.
- - `reason` is a value that indicates why a promise was rejected.
- - `settled` the final resting state of a promise, fulfilled or rejected.
- A promise can be in one of three states: pending, fulfilled, or rejected.
- Promises that are fulfilled have a fulfillment value and are in the fulfilled
- state. Promises that are rejected have a rejection reason and are in the
- rejected state. A fulfillment value is never a thenable. Similarly, a
- rejection reason is never a thenable.
- Promises can also be said to *resolve* a value. If this value is also a
- promise, then the original promise's settled state will match the value's
- settled state. So a promise that *resolves* a promise that rejects will
- itself reject, and a promise that *resolves* a promise that fulfills will
- itself fulfill.
- Basic Usage:
- ------------
- ```js
- var promise = new Promise(function(resolve, reject) {
- // on success
- resolve(value);
- // on failure
- reject(reason);
- });
- promise.then(function(value) {
- // on fulfillment
- }, function(reason) {
- // on rejection
- });
- ```
- Advanced Usage:
- ---------------
- Promises shine when abstracting away asynchronous interactions such as
- `XMLHttpRequest`s.
- ```js
- function getJSON(url) {
- return new Promise(function(resolve, reject){
- var xhr = new XMLHttpRequest();
- xhr.open('GET', url);
- xhr.onreadystatechange = handler;
- xhr.responseType = 'json';
- xhr.setRequestHeader('Accept', 'application/json');
- xhr.send();
- function handler() {
- if (this.readyState === this.DONE) {
- if (this.status === 200) {
- resolve(this.response);
- } else {
- reject(new Error("getJSON: `" + url + "` failed with status: [" + this.status + "]");
- }
- }
- };
- });
- }
- getJSON('/posts.json').then(function(json) {
- // on fulfillment
- }, function(reason) {
- // on rejection
- });
- ```
- Unlike callbacks, promises are great composable primitives.
- ```js
- Promise.all([
- getJSON('/posts'),
- getJSON('/comments')
- ]).then(function(values){
- values[0] // => postsJSON
- values[1] // => commentsJSON
- return values;
- });
- ```
- @class RSVP.Promise
- @param {function}
- @param {String} label optional string for labeling the promise.
- Useful for tooling.
- @constructor
- */
- function Promise(resolver, label) {
- if (!isFunction(resolver)) {
- throw new TypeError('You must pass a resolver function as the first argument to the promise constructor');
- }
- if (!(this instanceof Promise)) {
- throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.");
- }
- this._id = counter++;
- this._label = label;
- this._subscribers = [];
- if (config.instrument) {
- instrument('created', this);
- }
- if (noop !== resolver) {
- invokeResolver(resolver, this);
- }
- }
- function invokeResolver(resolver, promise) {
- function resolvePromise(value) {
- resolve(promise, value);
- }
- function rejectPromise(reason) {
- reject(promise, reason);
- }
- try {
- resolver(resolvePromise, rejectPromise);
- } catch(e) {
- rejectPromise(e);
- }
- }
- Promise.cast = cast;
- Promise.all = all;
- Promise.race = race;
- Promise.resolve = Resolve;
- Promise.reject = Reject;
- var PENDING = void 0;
- var SEALED = 0;
- var FULFILLED = 1;
- var REJECTED = 2;
- function subscribe(parent, child, onFulfillment, onRejection) {
- var subscribers = parent._subscribers;
- var length = subscribers.length;
- subscribers[length] = child;
- subscribers[length + FULFILLED] = onFulfillment;
- subscribers[length + REJECTED] = onRejection;
- }
- function publish(promise, settled) {
- var child, callback, subscribers = promise._subscribers, detail = promise._detail;
- if (config.instrument) {
- instrument(settled === FULFILLED ? 'fulfilled' : 'rejected', promise);
- }
- for (var i = 0; i < subscribers.length; i += 3) {
- child = subscribers[i];
- callback = subscribers[i + settled];
- invokeCallback(settled, child, callback, detail);
- }
- promise._subscribers = null;
- }
- Promise.prototype = {
- constructor: Promise,
- _id: undefined,
- _guidKey: guidKey,
- _label: undefined,
- _state: undefined,
- _detail: undefined,
- _subscribers: undefined,
- _onerror: function (reason) {
- config.trigger('error', reason);
- },
- /**
- The primary way of interacting with a promise is through its `then` method,
- which registers callbacks to receive either a promise's eventual value or the
- reason why the promise cannot be fulfilled.
- ```js
- findUser().then(function(user){
- // user is available
- }, function(reason){
- // user is unavailable, and you are given the reason why
- });
- ```
- Chaining
- --------
- The return value of `then` is itself a promise. This second, "downstream"
- promise is resolved with the return value of the first promise's fulfillment
- or rejection handler, or rejected if the handler throws an exception.
- ```js
- findUser().then(function (user) {
- return user.name;
- }, function (reason) {
- return "default name";
- }).then(function (userName) {
- // If `findUser` fulfilled, `userName` will be the user's name, otherwise it
- // will be `"default name"`
- });
- findUser().then(function (user) {
- throw new Error("Found user, but still unhappy");
- }, function (reason) {
- throw new Error("`findUser` rejected and we're unhappy");
- }).then(function (value) {
- // never reached
- }, function (reason) {
- // if `findUser` fulfilled, `reason` will be "Found user, but still unhappy".
- // If `findUser` rejected, `reason` will be "`findUser` rejected and we're unhappy".
- });
- ```
- If the downstream promise does not specify a rejection handler, rejection reasons will be propagated further downstream.
- ```js
- findUser().then(function (user) {
- throw new PedagogicalException("Upstream error");
- }).then(function (value) {
- // never reached
- }).then(function (value) {
- // never reached
- }, function (reason) {
- // The `PedgagocialException` is propagated all the way down to here
- });
- ```
- Assimilation
- ------------
- Sometimes the value you want to propagate to a downstream promise can only be
- retrieved asynchronously. This can be achieved by returning a promise in the
- fulfillment or rejection handler. The downstream promise will then be pending
- until the returned promise is settled. This is called *assimilation*.
- ```js
- findUser().then(function (user) {
- return findCommentsByAuthor(user);
- }).then(function (comments) {
- // The user's comments are now available
- });
- ```
- If the assimliated promise rejects, then the downstream promise will also reject.
- ```js
- findUser().then(function (user) {
- return findCommentsByAuthor(user);
- }).then(function (comments) {
- // If `findCommentsByAuthor` fulfills, we'll have the value here
- }, function (reason) {
- // If `findCommentsByAuthor` rejects, we'll have the reason here
- });
- ```
- Simple Example
- --------------
- Synchronous Example
- ```javascript
- var result;
- try {
- result = findResult();
- // success
- } catch(reason) {
- // failure
- }
- ```
- Errback Example
- ```js
- findResult(function(result, err){
- if (err) {
- // failure
- } else {
- // success
- }
- });
- ```
- Promise Example;
- ```javascript
- findResult().then(function(result){
- // success
- }, function(reason){
- // failure
- });
- ```
- Advanced Example
- --------------
- Synchronous Example
- ```javascript
- var author, books;
- try {
- author = findAuthor();
- books = findBooksByAuthor(author);
- // success
- } catch(reason) {
- // failure
- }
- ```
- Errback Example
- ```js
- function foundBooks(books) {
- }
- function failure(reason) {
- }
- findAuthor(function(author, err){
- if (err) {
- failure(err);
- // failure
- } else {
- try {
- findBoooksByAuthor(author, function(books, err) {
- if (err) {
- failure(err);
- } else {
- try {
- foundBooks(books);
- } catch(reason) {
- failure(reason);
- }
- }
- });
- } catch(error) {
- failure(err);
- }
- // success
- }
- });
- ```
- Promise Example;
- ```javascript
- findAuthor().
- then(findBooksByAuthor).
- then(function(books){
- // found books
- }).catch(function(reason){
- // something went wrong
- });
- ```
- @method then
- @param {Function} onFulfilled
- @param {Function} onRejected
- @param {String} label optional string for labeling the promise.
- Useful for tooling.
- @return {Promise}
- */
- then: function(onFulfillment, onRejection, label) {
- var promise = this;
- this._onerror = null;
- var thenPromise = new this.constructor(noop, label);
- if (this._state) {
- var callbacks = arguments;
- config.async(function invokePromiseCallback() {
- invokeCallback(promise._state, thenPromise, callbacks[promise._state - 1], promise._detail);
- });
- } else {
- subscribe(this, thenPromise, onFulfillment, onRejection);
- }
- if (config.instrument) {
- instrument('chained', promise, thenPromise);
- }
- return thenPromise;
- },
- /**
- `catch` is simply sugar for `then(undefined, onRejection)` which makes it the same
- as the catch block of a try/catch statement.
- ```js
- function findAuthor(){
- throw new Error("couldn't find that author");
- }
- // synchronous
- try {
- findAuthor();
- } catch(reason) {
- // something went wrong
- }
- // async with promises
- findAuthor().catch(function(reason){
- // something went wrong
- });
- ```
- @method catch
- @param {Function} onRejection
- @param {String} label optional string for labeling the promise.
- Useful for tooling.
- @return {Promise}
- */
- 'catch': function(onRejection, label) {
- return this.then(null, onRejection, label);
- },
- /**
- `finally` will be invoked regardless of the promise's fate just as native
- try/catch/finally behaves
- Synchronous example:
- ```js
- findAuthor() {
- if (Math.random() > 0.5) {
- throw new Error();
- }
- return new Author();
- }
- try {
- return findAuthor(); // succeed or fail
- } catch(error) {
- return findOtherAuther();
- } finally {
- // always runs
- // doesn't affect the return value
- }
- ```
- Asynchronous example:
- ```js
- findAuthor().catch(function(reason){
- return findOtherAuther();
- }).finally(function(){
- // author was either found, or not
- });
- ```
- @method finally
- @param {Function} callback
- @param {String} label optional string for labeling the promise.
- Useful for tooling.
- @return {Promise}
- */
- 'finally': function(callback, label) {
- var constructor = this.constructor;
- return this.then(function(value) {
- return constructor.cast(callback()).then(function(){
- return value;
- });
- }, function(reason) {
- return constructor.cast(callback()).then(function(){
- throw reason;
- });
- }, label);
- }
- };
- function invokeCallback(settled, promise, callback, detail) {
- var hasCallback = isFunction(callback),
- value, error, succeeded, failed;
- if (hasCallback) {
- try {
- value = callback(detail);
- succeeded = true;
- } catch(e) {
- failed = true;
- error = e;
- }
- } else {
- value = detail;
- succeeded = true;
- }
- if (handleThenable(promise, value)) {
- return;
- } else if (hasCallback && succeeded) {
- resolve(promise, value);
- } else if (failed) {
- reject(promise, error);
- } else if (settled === FULFILLED) {
- resolve(promise, value);
- } else if (settled === REJECTED) {
- reject(promise, value);
- }
- }
- function handleThenable(promise, value) {
- var then = null,
- resolved;
- try {
- if (promise === value) {
- throw new TypeError("A promises callback cannot return that same promise.");
- }
- if (objectOrFunction(value)) {
- then = value.then;
- if (isFunction(then)) {
- then.call(value, function(val) {
- if (resolved) { return true; }
- resolved = true;
- if (value !== val) {
- resolve(promise, val);
- } else {
- fulfill(promise, val);
- }
- }, function(val) {
- if (resolved) { return true; }
- resolved = true;
- reject(promise, val);
- }, 'derived from: ' + (promise._label || ' unknown promise'));
- return true;
- }
- }
- } catch (error) {
- if (resolved) { return true; }
- reject(promise, error);
- return true;
- }
- return false;
- }
- function resolve(promise, value) {
- if (promise === value) {
- fulfill(promise, value);
- } else if (!handleThenable(promise, value)) {
- fulfill(promise, value);
- }
- }
- function fulfill(promise, value) {
- if (promise._state !== PENDING) { return; }
- promise._state = SEALED;
- promise._detail = value;
- config.async(publishFulfillment, promise);
- }
- function reject(promise, reason) {
- if (promise._state !== PENDING) { return; }
- promise._state = SEALED;
- promise._detail = reason;
- config.async(publishRejection, promise);
- }
- function publishFulfillment(promise) {
- publish(promise, promise._state = FULFILLED);
- }
- function publishRejection(promise) {
- if (promise._onerror) {
- promise._onerror(promise._detail);
- }
- publish(promise, promise._state = REJECTED);
- }
- });
- define("rsvp/promise/all",
- ["../utils","exports"],
- function(__dependency1__, __exports__) {
- "use strict";
- var isArray = __dependency1__.isArray;
- var isNonThenable = __dependency1__.isNonThenable;
- /**
- `RSVP.Promise.all` accepts an array of promises, and returns a new promise which
- is fulfilled with an array of fulfillment values for the passed promises, or
- rejected with the reason of the first passed promise to be rejected. It casts all
- elements of the passed iterable to promises as it runs this algorithm.
- Example:
- ```javascript
- var promise1 = RSVP.resolve(1);
- var promise2 = RSVP.resolve(2);
- var promise3 = RSVP.resolve(3);
- var promises = [ promise1, promise2, promise3 ];
- RSVP.Promise.all(promises).then(function(array){
- // The array here would be [ 1, 2, 3 ];
- });
- ```
- If any of the `promises` given to `RSVP.all` are rejected, the first promise
- that is rejected will be given as an argument to the returned promises's
- rejection handler. For example:
- Example:
- ```javascript
- var promise1 = RSVP.resolve(1);
- var promise2 = RSVP.reject(new Error("2"));
- var promise3 = RSVP.reject(new Error("3"));
- var promises = [ promise1, promise2, promise3 ];
- RSVP.Promise.all(promises).then(function(array){
- // Code here never runs because there are rejected promises!
- }, function(error) {
- // error.message === "2"
- });
- ```
- @method all
- @for Ember.RSVP.Promise
- @param {Array} entries array of promises
- @param {String} label optional string for labeling the promise.
- Useful for tooling.
- @return {Promise} promise that is fulfilled when all `promises` have been
- fulfilled, or rejected if any of them become rejected.
- @static
- */
- __exports__["default"] = function all(entries, label) {
- /*jshint validthis:true */
- var Constructor = this;
- return new Constructor(function(resolve, reject) {
- if (!isArray(entries)) {
- throw new TypeError('You must pass an array to all.');
- }
- var remaining = entries.length;
- var results = new Array(remaining);
- var entry, pending = true;
- if (remaining === 0) {
- resolve(results);
- return;
- }
- function fulfillmentAt(index) {
- return function(value) {
- results[index] = value;
- if (--remaining === 0) {
- resolve(results);
- }
- };
- }
- function onRejection(reason) {
- remaining = 0;
- reject(reason);
- }
- for (var index = 0; index < entries.length; index++) {
- entry = entries[index];
- if (isNonThenable(entry)) {
- results[index] = entry;
- if (--remaining === 0) {
- resolve(results);
- }
- } else {
- Constructor.cast(entry).then(fulfillmentAt(index), onRejection);
- }
- }
- }, label);
- };
- });
- define("rsvp/promise/cast",
- ["exports"],
- function(__exports__) {
- "use strict";
- /**
- `RSVP.Promise.cast` coerces its argument to a promise, or returns the
- argument if it is already a promise which shares a constructor with the caster.
- Example:
- ```javascript
- var promise = RSVP.Promise.resolve(1);
- var casted = RSVP.Promise.cast(promise);
- console.log(promise === casted); // true
- ```
- In the case of a promise whose constructor does not match, it is assimilated.
- The resulting promise will fulfill or reject based on the outcome of the
- promise being casted.
- Example:
- ```javascript
- var thennable = $.getJSON('/api/foo');
- var casted = RSVP.Promise.cast(thennable);
- console.log(thennable === casted); // false
- console.log(casted instanceof RSVP.Promise) // true
- casted.then(function(data) {
- // data is the value getJSON fulfills with
- });
- ```
- In the case of a non-promise, a promise which will fulfill with that value is
- returned.
- Example:
- ```javascript
- var value = 1; // could be a number, boolean, string, undefined...
- var casted = RSVP.Promise.cast(value);
- console.log(value === casted); // false
- console.log(casted instanceof RSVP.Promise) // true
- casted.then(function(val) {
- val === value // => true
- });
- ```
- `RSVP.Promise.cast` is similar to `RSVP.Promise.resolve`, but `RSVP.Promise.cast` differs in the
- following ways:
- * `RSVP.Promise.cast` serves as a memory-efficient way of getting a promise, when you
- have something that could either be a promise or a value. RSVP.resolve
- will have the same effect but will create a new promise wrapper if the
- argument is a promise.
- * `RSVP.Promise.cast` is a way of casting incoming thenables or promise subclasses to
- promises of the exact class specified, so that the resulting object's `then` is
- ensured to have the behavior of the constructor you are calling cast on (i.e., RSVP.Promise).
- @method cast
- @param {Object} object to be casted
- @param {String} label optional string for labeling the promise.
- Useful for tooling.
- @return {Promise} promise
- @static
- */
- __exports__["default"] = function cast(object, label) {
- /*jshint validthis:true */
- var Constructor = this;
- if (object && typeof object === 'object' && object.constructor === Constructor) {
- return object;
- }
- return new Constructor(function(resolve) {
- resolve(object);
- }, label);
- };
- });
- define("rsvp/promise/race",
- ["../utils","exports"],
- function(__dependency1__, __exports__) {
- "use strict";
- /* global toString */
- var isArray = __dependency1__.isArray;
- var isFunction = __dependency1__.isFunction;
- var isNonThenable = __dependency1__.isNonThenable;
- /**
- `RSVP.Promise.race` returns a new promise which is settled in the same way as the
- first passed promise to settle.
- Example:
- ```javascript
- var promise1 = new RSVP.Promise(function(resolve, reject){
- setTimeout(function(){
- resolve("promise 1");
- }, 200);
- });
- var promise2 = new RSVP.Promise(function(resolve, reject){
- setTimeout(function(){
- resolve("promise 2");
- }, 100);
- });
- RSVP.Promise.race([promise1, promise2]).then(function(result){
- // result === "promise 2" because it was resolved before promise1
- // was resolved.
- });
- ```
- `RSVP.Promise.race` is deterministic in that only the state of the first
- settled promise matters. For example, even if other promises given to the
- `promises` array argument are resolved, but the first settled promise has
- become rejected before the other promises became fulfilled, the returned
- promise will become rejected:
- ```javascript
- var promise1 = new RSVP.Promise(function(resolve, reject){
- setTimeout(function(){
- resolve("promise 1");
- }, 200);
- });
- var promise2 = new RSVP.Promise(function(resolve, reject){
- setTimeout(function(){
- reject(new Error("promise 2"));
- }, 100);
- });
- RSVP.Promise.race([promise1, promise2]).then(function(result){
- // Code here never runs
- }, function(reason){
- // reason.message === "promise2" because promise 2 became rejected before
- // promise 1 became fulfilled
- });
- ```
- An example real-world use case is implementing timeouts:
- ```javascript
- RSVP.Promise.race([ajax('foo.json'), timeout(5000)])
- ```
- @method race
- @param {Array} promises array of promises to observe
- @param {String} label optional string for describing the promise returned.
- Useful for tooling.
- @return {Promise} a promise which settles in the same way as the first passed
- promise to settle.
- @static
- */
- __exports__["default"] = function race(entries, label) {
- /*jshint validthis:true */
- var Constructor = this, entry;
- return new Constructor(function(resolve, reject) {
- if (!isArray(entries)) {
- throw new TypeError('You must pass an array to race.');
- }
- var pending = true;
- function onFulfillment(value) { if (pending) { pending = false; resolve(value); } }
- function onRejection(reason) { if (pending) { pending = false; reject(reason); } }
- for (var i = 0; i < entries.length; i++) {
- entry = entries[i];
- if (isNonThenable(entry)) {
- pending = false;
- resolve(entry);
- return;
- } else {
- Constructor.cast(entry).then(onFulfillment, onRejection);
- }
- }
- }, label);
- };
- });
- define("rsvp/promise/reject",
- ["exports"],
- function(__exports__) {
- "use strict";
- /**
- `RSVP.Promise.reject` returns a promise rejected with the passed `reason`.
- It is shorthand for the following:
- ```javascript
- var promise = new RSVP.Promise(function(resolve, reject){
- reject(new Error('WHOOPS'));
- });
- promise.then(function(value){
- // Code here doesn't run because the promise is rejected!
- }, function(reason){
- // reason.message === 'WHOOPS'
- });
- ```
- Instead of writing the above, your code now simply becomes the following:
- ```javascript
- var promise = RSVP.Promise.reject(new Error('WHOOPS'));
- promise.then(function(value){
- // Code here doesn't run because the promise is rejected!
- }, function(reason){
- // reason.message === 'WHOOPS'
- });
- ```
- @method reject
- @param {Any} reason value that the returned promise will be rejected with.
- @param {String} label optional string for identifying the returned promise.
- Useful for tooling.
- @return {Promise} a promise rejected with the given `reason`.
- @static
- */
- __exports__["default"] = function reject(reason, label) {
- /*jshint validthis:true */
- var Constructor = this;
- return new Constructor(function (resolve, reject) {
- reject(reason);
- }, label);
- };
- });
- define("rsvp/promise/resolve",
- ["exports"],
- function(__exports__) {
- "use strict";
- /**
- `RSVP.Promise.resolve` returns a promise that will become resolved with the
- passed `value`. It is shorthand for the following:
- ```javascript
- var promise = new RSVP.Promise(function(resolve, reject){
- resolve(1);
- });
- promise.then(function(value){
- // value === 1
- });
- ```
- Instead of writing the above, your code now simply becomes the following:
- ```javascript
- var promise = RSVP.Promise.resolve(1);
- promise.then(function(value){
- // value === 1
- });
- ```
- @method resolve
- @param {Any} value value that the returned promise will be resolved with
- @param {String} label optional string for identifying the returned promise.
- Useful for tooling.
- @return {Promise} a promise that will become fulfilled with the given
- `value`
- @static
- */
- __exports__["default"] = function resolve(value, label) {
- /*jshint validthis:true */
- var Constructor = this;
- return new Constructor(function(resolve, reject) {
- resolve(value);
- }, label);
- };
- });
- define("rsvp/race",
- ["./promise","exports"],
- function(__dependency1__, __exports__) {
- "use strict";
- var Promise = __dependency1__["default"];
- /**
- This is a convenient alias for `RSVP.Promise.race`.
- @method race
- @param {Array} array Array of promises.
- @param {String} label An optional label. This is useful
- for tooling.
- @static
- */
- __exports__["default"] = function race(array, label) {
- return Promise.race(array, label);
- };
- });
- define("rsvp/reject",
- ["./promise","exports"],
- function(__dependency1__, __exports__) {
- "use strict";
- var Promise = __dependency1__["default"];
- /**
- This is a convenient alias for `RSVP.Promise.reject`.
- @method reject
- @for RSVP
- @param {Any} reason value that the returned promise will be rejected with.
- @param {String} label optional string for identifying the returned promise.
- Useful for tooling.
- @return {Promise} a promise rejected with the given `reason`.
- @static
- */
- __exports__["default"] = function reject(reason, label) {
- return Promise.reject(reason, label);
- };
- });
- define("rsvp/resolve",
- ["./promise","exports"],
- function(__dependency1__, __exports__) {
- "use strict";
- var Promise = __dependency1__["default"];
- /**
- This is a convenient alias for `RSVP.Promise.resolve`.
- @method resolve
- @for RSVP
- @param {Any} value value that the returned promise will be resolved with
- @param {String} label optional string for identifying the returned promise.
- Useful for tooling.
- @return {Promise} a promise that will become fulfilled with the given
- `value`
- @static
- */
- __exports__["default"] = function resolve(value, label) {
- return Promise.resolve(value, label);
- };
- });
- define("rsvp/rethrow",
- ["exports"],
- function(__exports__) {
- "use strict";
- /**
- `RSVP.rethrow` will rethrow an error on the next turn of the JavaScript event
- loop in order to aid debugging.
- Promises A+ specifies that any exceptions that occur with a promise must be
- caught by the promises implementation and bubbled to the last handler. For
- this reason, it is recommended that you always specify a second rejection
- handler function to `then`. However, `RSVP.rethrow` will throw the exception
- outside of the promise, so it bubbles up to your console if in the browser,
- or domain/cause uncaught exception in Node. `rethrow` will also throw the
- error again so the error can be handled by the promise per the spec.
- ```javascript
- function throws(){
- throw new Error('Whoops!');
- }
- var promise = new RSVP.Promise(function(resolve, reject){
- throws();
- });
- promise.catch(RSVP.rethrow).then(function(){
- // Code here doesn't run because the promise became rejected due to an
- // error!
- }, function (err){
- // handle the error here
- });
- ```
- The 'Whoops' error will be thrown on the next turn of the event loop
- and you can watch for it in your console. You can also handle it using a
- rejection handler given to `.then` or `.catch` on the returned promise.
- @method rethrow
- @for RSVP
- @param {Error} reason reason the promise became rejected.
- @throws Error
- @static
- */
- __exports__["default"] = function rethrow(reason) {
- setTimeout(function() {
- throw reason;
- });
- throw reason;
- };
- });
- define("rsvp/utils",
- ["exports"],
- function(__exports__) {
- "use strict";
- function objectOrFunction(x) {
- return typeof x === "function" || (typeof x === "object" && x !== null);
- }
- __exports__.objectOrFunction = objectOrFunction;function isFunction(x) {
- return typeof x === "function";
- }
- __exports__.isFunction = isFunction;function isNonThenable(x) {
- return !objectOrFunction(x);
- }
- __exports__.isNonThenable = isNonThenable;function isArray(x) {
- return Object.prototype.toString.call(x) === "[object Array]";
- }
- __exports__.isArray = isArray;// Date.now is not available in browsers < IE9
- // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now#Compatibility
- var now = Date.now || function() { return new Date().getTime(); };
- __exports__.now = now;
- var keysOf = Object.keys || function(object) {
- var result = [];
- for (var prop in object) {
- result.push(prop);
- }
- return result;
- };
- __exports__.keysOf = keysOf;
- });
- define("rsvp",
- ["./rsvp/promise","./rsvp/events","./rsvp/node","./rsvp/all","./rsvp/all_settled","./rsvp/race","./rsvp/hash","./rsvp/rethrow","./rsvp/defer","./rsvp/config","./rsvp/map","./rsvp/resolve","./rsvp/reject","./rsvp/filter","exports"],
- function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __dependency13__, __dependency14__, __exports__) {
- "use strict";
- var Promise = __dependency1__["default"];
- var EventTarget = __dependency2__["default"];
- var denodeify = __dependency3__["default"];
- var all = __dependency4__["default"];
- var allSettled = __dependency5__["default"];
- var race = __dependency6__["default"];
- var hash = __dependency7__["default"];
- var rethrow = __dependency8__["default"];
- var defer = __dependency9__["default"];
- var config = __dependency10__.config;
- var configure = __dependency10__.configure;
- var map = __dependency11__["default"];
- var resolve = __dependency12__["default"];
- var reject = __dependency13__["default"];
- var filter = __dependency14__["default"];
- function async(callback, arg) {
- config.async(callback, arg);
- }
- function on() {
- config.on.apply(config, arguments);
- }
- function off() {
- config.off.apply(config, arguments);
- }
- // Set up instrumentation through `window.__PROMISE_INTRUMENTATION__`
- if (typeof window !== 'undefined' && typeof window.__PROMISE_INSTRUMENTATION__ === 'object') {
- var callbacks = window.__PROMISE_INSTRUMENTATION__;
- configure('instrument', true);
- for (var eventName in callbacks) {
- if (callbacks.hasOwnProperty(eventName)) {
- on(eventName, callbacks[eventName]);
- }
- }
- }
- __exports__.Promise = Promise;
- __exports__.EventTarget = EventTarget;
- __exports__.all = all;
- __exports__.allSettled = allSettled;
- __exports__.race = race;
- __exports__.hash = hash;
- __exports__.rethrow = rethrow;
- __exports__.defer = defer;
- __exports__.denodeify = denodeify;
- __exports__.configure = configure;
- __exports__.on = on;
- __exports__.off = off;
- __exports__.resolve = resolve;
- __exports__.reject = reject;
- __exports__.async = async;
- __exports__.map = map;
- __exports__.filter = filter;
- });
- })();
- (function() {
- /**
- Public api for the container is still in flux.
- The public api, specified on the application namespace should be considered the stable api.
- // @module container
- @private
- */
- /*
- Flag to enable/disable model factory injections (disabled by default)
- If model factory injections are enabled, models should not be
- accessed globally (only through `container.lookupFactory('model:modelName'))`);
- */
- Ember.MODEL_FACTORY_INJECTIONS = false || !!Ember.ENV.MODEL_FACTORY_INJECTIONS;
- define("container",
- [],
- function() {
- "use strict";
- // A safe and simple inheriting object.
- function InheritingDict(parent) {
- this.parent = parent;
- this.dict = {};
- }
- InheritingDict.prototype = {
- /**
- @property parent
- @type InheritingDict
- @default null
- */
- parent: null,
- /**
- Object used to store the current nodes data.
- @property dict
- @type Object
- @default Object
- */
- dict: null,
- /**
- Retrieve the value given a key, if the value is present at the current
- level use it, otherwise walk up the parent hierarchy and try again. If
- no matching key is found, return undefined.
- @method get
- @param {String} key
- @return {any}
- */
- get: function(key) {
- var dict = this.dict;
- if (dict.hasOwnProperty(key)) {
- return dict[key];
- }
- if (this.parent) {
- return this.parent.get(key);
- }
- },
- /**
- Set the given value for the given key, at the current level.
- @method set
- @param {String} key
- @param {Any} value
- */
- set: function(key, value) {
- this.dict[key] = value;
- },
- /**
- Delete the given key
- @method remove
- @param {String} key
- */
- remove: function(key) {
- delete this.dict[key];
- },
- /**
- Check for the existence of given a key, if the key is present at the current
- level return true, otherwise walk up the parent hierarchy and try again. If
- no matching key is found, return false.
- @method has
- @param {String} key
- @return {Boolean}
- */
- has: function(key) {
- var dict = this.dict;
- if (dict.hasOwnProperty(key)) {
- return true;
- }
- if (this.parent) {
- return this.parent.has(key);
- }
- return false;
- },
- /**
- Iterate and invoke a callback for each local key-value pair.
- @method eachLocal
- @param {Function} callback
- @param {Object} binding
- */
- eachLocal: function(callback, binding) {
- var dict = this.dict;
- for (var prop in dict) {
- if (dict.hasOwnProperty(prop)) {
- callback.call(binding, prop, dict[prop]);
- }
- }
- }
- };
- // A lightweight container that helps to assemble and decouple components.
- // Public api for the container is still in flux.
- // The public api, specified on the application namespace should be considered the stable api.
- function Container(parent) {
- this.parent = parent;
- this.children = [];
- this.resolver = parent && parent.resolver || function() {};
- this.registry = new InheritingDict(parent && parent.registry);
- this.cache = new InheritingDict(parent && parent.cache);
- this.factoryCache = new InheritingDict(parent && parent.factoryCache);
- this.resolveCache = new InheritingDict(parent && parent.resolveCache);
- this.typeInjections = new InheritingDict(parent && parent.typeInjections);
- this.injections = {};
- this.factoryTypeInjections = new InheritingDict(parent && parent.factoryTypeInjections);
- this.factoryInjections = {};
- this._options = new InheritingDict(parent && parent._options);
- this._typeOptions = new InheritingDict(parent && parent._typeOptions);
- }
- Container.prototype = {
- /**
- @property parent
- @type Container
- @default null
- */
- parent: null,
- /**
- @property children
- @type Array
- @default []
- */
- children: null,
- /**
- @property resolver
- @type function
- */
- resolver: null,
- /**
- @property registry
- @type InheritingDict
- */
- registry: null,
- /**
- @property cache
- @type InheritingDict
- */
- cache: null,
- /**
- @property typeInjections
- @type InheritingDict
- */
- typeInjections: null,
- /**
- @property injections
- @type Object
- @default {}
- */
- injections: null,
- /**
- @private
- @property _options
- @type InheritingDict
- @default null
- */
- _options: null,
- /**
- @private
- @property _typeOptions
- @type InheritingDict
- */
- _typeOptions: null,
- /**
- Returns a new child of the current container. These children are configured
- to correctly inherit from the current container.
- @method child
- @return {Container}
- */
- child: function() {
- var container = new Container(this);
- this.children.push(container);
- return container;
- },
- /**
- Sets a key-value pair on the current container. If a parent container,
- has the same key, once set on a child, the parent and child will diverge
- as expected.
- @method set
- @param {Object} object
- @param {String} key
- @param {any} value
- */
- set: function(object, key, value) {
- object[key] = value;
- },
- /**
- Registers a factory for later injection.
- Example:
- ```javascript
- var container = new Container();
- container.register('model:user', Person, {singleton: false });
- container.register('fruit:favorite', Orange);
- container.register('communication:main', Email, {singleton: false});
- ```
- @method register
- @param {String} fullName
- @param {Function} factory
- @param {Object} options
- */
- register: function(fullName, factory, options) {
- validateFullName(fullName);
- if (factory === undefined) {
- throw new TypeError('Attempting to register an unknown factory: `' + fullName + '`');
- }
- var normalizedName = this.normalize(fullName);
- if (this.cache.has(normalizedName)) {
- throw new Error('Cannot re-register: `' + fullName +'`, as it has already been looked up.');
- }
- this.registry.set(normalizedName, factory);
- this._options.set(normalizedName, options || {});
- },
- /**
- Unregister a fullName
- ```javascript
- var container = new Container();
- container.register('model:user', User);
- container.lookup('model:user') instanceof User //=> true
- container.unregister('model:user')
- container.lookup('model:user') === undefined //=> true
- ```
- @method unregister
- @param {String} fullName
- */
- unregister: function(fullName) {
- validateFullName(fullName);
- var normalizedName = this.normalize(fullName);
- this.registry.remove(normalizedName);
- this.cache.remove(normalizedName);
- this.factoryCache.remove(normalizedName);
- this.resolveCache.remove(normalizedName);
- this._options.remove(normalizedName);
- },
- /**
- Given a fullName return the corresponding factory.
- By default `resolve` will retrieve the factory from
- its container's registry.
- ```javascript
- var container = new Container();
- container.register('api:twitter', Twitter);
- container.resolve('api:twitter') // => Twitter
- ```
- Optionally the container can be provided with a custom resolver.
- If provided, `resolve` will first provide the custom resolver
- the oppertunity to resolve the fullName, otherwise it will fallback
- to the registry.
- ```javascript
- var container = new Container();
- container.resolver = function(fullName) {
- // lookup via the module system of choice
- };
- // the twitter factory is added to the module system
- container.resolve('api:twitter') // => Twitter
- ```
- @method resolve
- @param {String} fullName
- @return {Function} fullName's factory
- */
- resolve: function(fullName) {
- validateFullName(fullName);
- var normalizedName = this.normalize(fullName);
- var cached = this.resolveCache.get(normalizedName);
- if (cached) { return cached; }
- var resolved = this.resolver(normalizedName) || this.registry.get(normalizedName);
- this.resolveCache.set(normalizedName, resolved);
- return resolved;
- },
- /**
- A hook that can be used to describe how the resolver will
- attempt to find the factory.
- For example, the default Ember `.describe` returns the full
- class name (including namespace) where Ember's resolver expects
- to find the `fullName`.
- @method describe
- @param {String} fullName
- @return {string} described fullName
- */
- describe: function(fullName) {
- return fullName;
- },
- /**
- A hook to enable custom fullName normalization behaviour
- @method normalize
- @param {String} fullName
- @return {string} normalized fullName
- */
- normalize: function(fullName) {
- return fullName;
- },
- /**
- @method makeToString
- @param {any} factory
- @param {string} fullName
- @return {function} toString function
- */
- makeToString: function(factory, fullName) {
- return factory.toString();
- },
- /**
- Given a fullName return a corresponding instance.
- The default behaviour is for lookup to return a singleton instance.
- The singleton is scoped to the container, allowing multiple containers
- to all have their own locally scoped singletons.
- ```javascript
- var container = new Container();
- container.register('api:twitter', Twitter);
- var twitter = container.lookup('api:twitter');
- twitter instanceof Twitter; // => true
- // by default the container will return singletons
- var twitter2 = container.lookup('api:twitter');
- twitter instanceof Twitter; // => true
- twitter === twitter2; //=> true
- ```
- If singletons are not wanted an optional flag can be provided at lookup.
- ```javascript
- var container = new Container();
- container.register('api:twitter', Twitter);
- var twitter = container.lookup('api:twitter', { singleton: false });
- var twitter2 = container.lookup('api:twitter', { singleton: false });
- twitter === twitter2; //=> false
- ```
- @method lookup
- @param {String} fullName
- @param {Object} options
- @return {any}
- */
- lookup: function(fullName, options) {
- validateFullName(fullName);
- return lookup(this, this.normalize(fullName), options);
- },
- /**
- Given a fullName return the corresponding factory.
- @method lookupFactory
- @param {String} fullName
- @return {any}
- */
- lookupFactory: function(fullName) {
- validateFullName(fullName);
- return factoryFor(this, this.normalize(fullName));
- },
- /**
- Given a fullName check if the container is aware of its factory
- or singleton instance.
- @method has
- @param {String} fullName
- @return {Boolean}
- */
- has: function(fullName) {
- validateFullName(fullName);
- return has(this, this.normalize(fullName));
- },
- /**
- Allow registering options for all factories of a type.
- ```javascript
- var container = new Container();
- // if all of type `connection` must not be singletons
- container.optionsForType('connection', { singleton: false });
- container.register('connection:twitter', TwitterConnection);
- container.register('connection:facebook', FacebookConnection);
- var twitter = container.lookup('connection:twitter');
- var twitter2 = container.lookup('connection:twitter');
- twitter === twitter2; // => false
- var facebook = container.lookup('connection:facebook');
- var facebook2 = container.lookup('connection:facebook');
- facebook === facebook2; // => false
- ```
- @method optionsForType
- @param {String} type
- @param {Object} options
- */
- optionsForType: function(type, options) {
- if (this.parent) { illegalChildOperation('optionsForType'); }
- this._typeOptions.set(type, options);
- },
- /**
- @method options
- @param {String} type
- @param {Object} options
- */
- options: function(type, options) {
- this.optionsForType(type, options);
- },
- /**
- Used only via `injection`.
- Provides a specialized form of injection, specifically enabling
- all objects of one type to be injected with a reference to another
- object.
- For example, provided each object of type `controller` needed a `router`.
- one would do the following:
- ```javascript
- var container = new Container();
- container.register('router:main', Router);
- container.register('controller:user', UserController);
- container.register('controller:post', PostController);
- container.typeInjection('controller', 'router', 'router:main');
- var user = container.lookup('controller:user');
- var post = container.lookup('controller:post');
- user.router instanceof Router; //=> true
- post.router instanceof Router; //=> true
- // both controllers share the same router
- user.router === post.router; //=> true
- ```
- @private
- @method typeInjection
- @param {String} type
- @param {String} property
- @param {String} fullName
- */
- typeInjection: function(type, property, fullName) {
- validateFullName(fullName);
- if (this.parent) { illegalChildOperation('typeInjection'); }
- addTypeInjection(this.typeInjections, type, property, fullName);
- },
- /**
- Defines injection rules.
- These rules are used to inject dependencies onto objects when they
- are instantiated.
- Two forms of injections are possible:
- * Injecting one fullName on another fullName
- * Injecting one fullName on a type
- Example:
- ```javascript
- var container = new Container();
- container.register('source:main', Source);
- container.register('model:user', User);
- container.register('model:post', Post);
- // injecting one fullName on another fullName
- // eg. each user model gets a post model
- container.injection('model:user', 'post', 'model:post');
- // injecting one fullName on another type
- container.injection('model', 'source', 'source:main');
- var user = container.lookup('model:user');
- var post = container.lookup('model:post');
- user.source instanceof Source; //=> true
- post.source instanceof Source; //=> true
- user.post instanceof Post; //=> true
- // and both models share the same source
- user.source === post.source; //=> true
- ```
- @method injection
- @param {String} factoryName
- @param {String} property
- @param {String} injectionName
- */
- injection: function(fullName, property, injectionName) {
- if (this.parent) { illegalChildOperation('injection'); }
- validateFullName(injectionName);
- var normalizedInjectionName = this.normalize(injectionName);
- if (fullName.indexOf(':') === -1) {
- return this.typeInjection(fullName, property, normalizedInjectionName);
- }
- validateFullName(fullName);
- var normalizedName = this.normalize(fullName);
- addInjection(this.injections, normalizedName, property, normalizedInjectionName);
- },
- /**
- Used only via `factoryInjection`.
- Provides a specialized form of injection, specifically enabling
- all factory of one type to be injected with a reference to another
- object.
- For example, provided each factory of type `model` needed a `store`.
- one would do the following:
- ```javascript
- var container = new Container();
- container.register('store:main', SomeStore);
- container.factoryTypeInjection('model', 'store', 'store:main');
- var store = container.lookup('store:main');
- var UserFactory = container.lookupFactory('model:user');
- UserFactory.store instanceof SomeStore; //=> true
- ```
- @private
- @method factoryTypeInjection
- @param {String} type
- @param {String} property
- @param {String} fullName
- */
- factoryTypeInjection: function(type, property, fullName) {
- if (this.parent) { illegalChildOperation('factoryTypeInjection'); }
- addTypeInjection(this.factoryTypeInjections, type, property, this.normalize(fullName));
- },
- /**
- Defines factory injection rules.
- Similar to regular injection rules, but are run against factories, via
- `Container#lookupFactory`.
- These rules are used to inject objects onto factories when they
- are looked up.
- Two forms of injections are possible:
- * Injecting one fullName on another fullName
- * Injecting one fullName on a type
- Example:
- ```javascript
- var container = new Container();
- container.register('store:main', Store);
- container.register('store:secondary', OtherStore);
- container.register('model:user', User);
- container.register('model:post', Post);
- // injecting one fullName on another type
- container.factoryInjection('model', 'store', 'store:main');
- // injecting one fullName on another fullName
- container.factoryInjection('model:post', 'secondaryStore', 'store:secondary');
- var UserFactory = container.lookupFactory('model:user');
- var PostFactory = container.lookupFactory('model:post');
- var store = container.lookup('store:main');
- UserFactory.store instanceof Store; //=> true
- UserFactory.secondaryStore instanceof OtherStore; //=> false
- PostFactory.store instanceof Store; //=> true
- PostFactory.secondaryStore instanceof OtherStore; //=> true
- // and both models share the same source instance
- UserFactory.store === PostFactory.store; //=> true
- ```
- @method factoryInjection
- @param {String} factoryName
- @param {String} property
- @param {String} injectionName
- */
- factoryInjection: function(fullName, property, injectionName) {
- if (this.parent) { illegalChildOperation('injection'); }
- var normalizedName = this.normalize(fullName);
- var normalizedInjectionName = this.normalize(injectionName);
- validateFullName(injectionName);
- if (fullName.indexOf(':') === -1) {
- return this.factoryTypeInjection(normalizedName, property, normalizedInjectionName);
- }
- validateFullName(fullName);
- addInjection(this.factoryInjections, normalizedName, property, normalizedInjectionName);
- },
- /**
- A depth first traversal, destroying the container, its descendant containers and all
- their managed objects.
- @method destroy
- */
- destroy: function() {
- for (var i=0, l=this.children.length; i<l; i++) {
- this.children[i].destroy();
- }
- this.children = [];
- eachDestroyable(this, function(item) {
- item.destroy();
- });
- this.parent = undefined;
- this.isDestroyed = true;
- },
- /**
- @method reset
- */
- reset: function() {
- for (var i=0, l=this.children.length; i<l; i++) {
- resetCache(this.children[i]);
- }
- resetCache(this);
- }
- };
- function has(container, fullName){
- if (container.cache.has(fullName)) {
- return true;
- }
- return !!container.resolve(fullName);
- }
- function lookup(container, fullName, options) {
- options = options || {};
- if (container.cache.has(fullName) && options.singleton !== false) {
- return container.cache.get(fullName);
- }
- var value = instantiate(container, fullName);
- if (value === undefined) { return; }
- if (isSingleton(container, fullName) && options.singleton !== false) {
- container.cache.set(fullName, value);
- }
- return value;
- }
- function illegalChildOperation(operation) {
- throw new Error(operation + " is not currently supported on child containers");
- }
- function isSingleton(container, fullName) {
- var singleton = option(container, fullName, 'singleton');
- return singleton !== false;
- }
- function buildInjections(container, injections) {
- var hash = {};
- if (!injections) { return hash; }
- var injection, injectable;
- for (var i=0, l=injections.length; i<l; i++) {
- injection = injections[i];
- injectable = lookup(container, injection.fullName);
- if (injectable !== undefined) {
- hash[injection.property] = injectable;
- } else {
- throw new Error('Attempting to inject an unknown injection: `' + injection.fullName + '`');
- }
- }
- return hash;
- }
- function option(container, fullName, optionName) {
- var options = container._options.get(fullName);
- if (options && options[optionName] !== undefined) {
- return options[optionName];
- }
- var type = fullName.split(":")[0];
- options = container._typeOptions.get(type);
- if (options) {
- return options[optionName];
- }
- }
- function factoryFor(container, fullName) {
- var name = fullName;
- var factory = container.resolve(name);
- var injectedFactory;
- var cache = container.factoryCache;
- var type = fullName.split(":")[0];
- if (factory === undefined) { return; }
- if (cache.has(fullName)) {
- return cache.get(fullName);
- }
- if (!factory || typeof factory.extend !== 'function' || (!Ember.MODEL_FACTORY_INJECTIONS && type === 'model')) {
- // TODO: think about a 'safe' merge style extension
- // for now just fallback to create time injection
- return factory;
- } else {
- var injections = injectionsFor(container, fullName);
- var factoryInjections = factoryInjectionsFor(container, fullName);
- factoryInjections._toString = container.makeToString(factory, fullName);
- injectedFactory = factory.extend(injections);
- injectedFactory.reopenClass(factoryInjections);
- cache.set(fullName, injectedFactory);
- return injectedFactory;
- }
- }
- function injectionsFor(container ,fullName) {
- var splitName = fullName.split(":"),
- type = splitName[0],
- injections = [];
- injections = injections.concat(container.typeInjections.get(type) || []);
- injections = injections.concat(container.injections[fullName] || []);
- injections = buildInjections(container, injections);
- injections._debugContainerKey = fullName;
- injections.container = container;
- return injections;
- }
- function factoryInjectionsFor(container, fullName) {
- var splitName = fullName.split(":"),
- type = splitName[0],
- factoryInjections = [];
- factoryInjections = factoryInjections.concat(container.factoryTypeInjections.get(type) || []);
- factoryInjections = factoryInjections.concat(container.factoryInjections[fullName] || []);
- factoryInjections = buildInjections(container, factoryInjections);
- factoryInjections._debugContainerKey = fullName;
- return factoryInjections;
- }
- function instantiate(container, fullName) {
- var factory = factoryFor(container, fullName);
- if (option(container, fullName, 'instantiate') === false) {
- return factory;
- }
- if (factory) {
- if (typeof factory.extend === 'function') {
- // assume the factory was extendable and is already injected
- return factory.create();
- } else {
- // assume the factory was extendable
- // to create time injections
- // TODO: support new'ing for instantiation and merge injections for pure JS Functions
- return factory.create(injectionsFor(container, fullName));
- }
- }
- }
- function eachDestroyable(container, callback) {
- container.cache.eachLocal(function(key, value) {
- if (option(container, key, 'instantiate') === false) { return; }
- callback(value);
- });
- }
- function resetCache(container) {
- container.cache.eachLocal(function(key, value) {
- if (option(container, key, 'instantiate') === false) { return; }
- value.destroy();
- });
- container.cache.dict = {};
- }
- function addTypeInjection(rules, type, property, fullName) {
- var injections = rules.get(type);
- if (!injections) {
- injections = [];
- rules.set(type, injections);
- }
- injections.push({
- property: property,
- fullName: fullName
- });
- }
- var VALID_FULL_NAME_REGEXP = /^[^:]+.+:[^:]+$/;
- function validateFullName(fullName) {
- if (!VALID_FULL_NAME_REGEXP.test(fullName)) {
- throw new TypeError('Invalid Fullname, expected: `type:name` got: ' + fullName);
- }
- }
- function addInjection(rules, factoryName, property, injectionName) {
- var injections = rules[factoryName] = rules[factoryName] || [];
- injections.push({ property: property, fullName: injectionName });
- }
- return Container;
- });
- })();
- (function() {
- /*globals ENV */
- /**
- @module ember
- @submodule ember-runtime
- */
- var indexOf = Ember.EnumerableUtils.indexOf;
- /**
- This will compare two javascript values of possibly different types.
- It will tell you which one is greater than the other by returning:
- - -1 if the first is smaller than the second,
- - 0 if both are equal,
- - 1 if the first is greater than the second.
- The order is calculated based on `Ember.ORDER_DEFINITION`, if types are different.
- In case they have the same type an appropriate comparison for this type is made.
- ```javascript
- Ember.compare('hello', 'hello'); // 0
- Ember.compare('abc', 'dfg'); // -1
- Ember.compare(2, 1); // 1
- ```
- @method compare
- @for Ember
- @param {Object} v First value to compare
- @param {Object} w Second value to compare
- @return {Number} -1 if v < w, 0 if v = w and 1 if v > w.
- */
- Ember.compare = function compare(v, w) {
- if (v === w) { return 0; }
- var type1 = Ember.typeOf(v);
- var type2 = Ember.typeOf(w);
- var Comparable = Ember.Comparable;
- if (Comparable) {
- if (type1==='instance' && Comparable.detect(v.constructor)) {
- return v.constructor.compare(v, w);
- }
- if (type2 === 'instance' && Comparable.detect(w.constructor)) {
- return 1-w.constructor.compare(w, v);
- }
- }
- // If we haven't yet generated a reverse-mapping of Ember.ORDER_DEFINITION,
- // do so now.
- var mapping = Ember.ORDER_DEFINITION_MAPPING;
- if (!mapping) {
- var order = Ember.ORDER_DEFINITION;
- mapping = Ember.ORDER_DEFINITION_MAPPING = {};
- var idx, len;
- for (idx = 0, len = order.length; idx < len; ++idx) {
- mapping[order[idx]] = idx;
- }
- // We no longer need Ember.ORDER_DEFINITION.
- delete Ember.ORDER_DEFINITION;
- }
- var type1Index = mapping[type1];
- var type2Index = mapping[type2];
- if (type1Index < type2Index) { return -1; }
- if (type1Index > type2Index) { return 1; }
- // types are equal - so we have to check values now
- switch (type1) {
- case 'boolean':
- case 'number':
- if (v < w) { return -1; }
- if (v > w) { return 1; }
- return 0;
- case 'string':
- var comp = v.localeCompare(w);
- if (comp < 0) { return -1; }
- if (comp > 0) { return 1; }
- return 0;
- case 'array':
- var vLen = v.length;
- var wLen = w.length;
- var l = Math.min(vLen, wLen);
- var r = 0;
- var i = 0;
- while (r === 0 && i < l) {
- r = compare(v[i],w[i]);
- i++;
- }
- if (r !== 0) { return r; }
- // all elements are equal now
- // shorter array should be ordered first
- if (vLen < wLen) { return -1; }
- if (vLen > wLen) { return 1; }
- // arrays are equal now
- return 0;
- case 'instance':
- if (Ember.Comparable && Ember.Comparable.detect(v)) {
- return v.compare(v, w);
- }
- return 0;
- case 'date':
- var vNum = v.getTime();
- var wNum = w.getTime();
- if (vNum < wNum) { return -1; }
- if (vNum > wNum) { return 1; }
- return 0;
- default:
- return 0;
- }
- };
- function _copy(obj, deep, seen, copies) {
- var ret, loc, key;
- // primitive data types are immutable, just return them.
- if ('object' !== typeof obj || obj===null) return obj;
- // avoid cyclical loops
- if (deep && (loc=indexOf(seen, obj))>=0) return copies[loc];
- Ember.assert('Cannot clone an Ember.Object that does not implement Ember.Copyable', !(obj instanceof Ember.Object) || (Ember.Copyable && Ember.Copyable.detect(obj)));
- // IMPORTANT: this specific test will detect a native array only. Any other
- // object will need to implement Copyable.
- if (Ember.typeOf(obj) === 'array') {
- ret = obj.slice();
- if (deep) {
- loc = ret.length;
- while(--loc>=0) ret[loc] = _copy(ret[loc], deep, seen, copies);
- }
- } else if (Ember.Copyable && Ember.Copyable.detect(obj)) {
- ret = obj.copy(deep, seen, copies);
- } else {
- ret = {};
- for(key in obj) {
- if (!obj.hasOwnProperty(key)) continue;
- // Prevents browsers that don't respect non-enumerability from
- // copying internal Ember properties
- if (key.substring(0,2) === '__') continue;
- ret[key] = deep ? _copy(obj[key], deep, seen, copies) : obj[key];
- }
- }
- if (deep) {
- seen.push(obj);
- copies.push(ret);
- }
- return ret;
- }
- /**
- Creates a clone of the passed object. This function can take just about
- any type of object and create a clone of it, including primitive values
- (which are not actually cloned because they are immutable).
- If the passed object implements the `clone()` method, then this function
- will simply call that method and return the result.
- @method copy
- @for Ember
- @param {Object} obj The object to clone
- @param {Boolean} deep If true, a deep copy of the object is made
- @return {Object} The cloned object
- */
- Ember.copy = function(obj, deep) {
- // fast paths
- if ('object' !== typeof obj || obj===null) return obj; // can't copy primitives
- if (Ember.Copyable && Ember.Copyable.detect(obj)) return obj.copy(deep);
- return _copy(obj, deep, deep ? [] : null, deep ? [] : null);
- };
- /**
- Compares two objects, returning true if they are logically equal. This is
- a deeper comparison than a simple triple equal. For sets it will compare the
- internal objects. For any other object that implements `isEqual()` it will
- respect that method.
- ```javascript
- Ember.isEqual('hello', 'hello'); // true
- Ember.isEqual(1, 2); // false
- Ember.isEqual([4,2], [4,2]); // false
- ```
- @method isEqual
- @for Ember
- @param {Object} a first object to compare
- @param {Object} b second object to compare
- @return {Boolean}
- */
- Ember.isEqual = function(a, b) {
- if (a && 'function'===typeof a.isEqual) return a.isEqual(b);
- return a === b;
- };
- // Used by Ember.compare
- Ember.ORDER_DEFINITION = Ember.ENV.ORDER_DEFINITION || [
- 'undefined',
- 'null',
- 'boolean',
- 'number',
- 'string',
- 'array',
- 'object',
- 'instance',
- 'function',
- 'class',
- 'date'
- ];
- /**
- Returns all of the keys defined on an object or hash. This is useful
- when inspecting objects for debugging. On browsers that support it, this
- uses the native `Object.keys` implementation.
- @method keys
- @for Ember
- @param {Object} obj
- @return {Array} Array containing keys of obj
- */
- Ember.keys = Object.keys;
- if (!Ember.keys || Ember.create.isSimulated) {
- var prototypeProperties = [
- 'constructor',
- 'hasOwnProperty',
- 'isPrototypeOf',
- 'propertyIsEnumerable',
- 'valueOf',
- 'toLocaleString',
- 'toString'
- ],
- pushPropertyName = function(obj, array, key) {
- // Prevents browsers that don't respect non-enumerability from
- // copying internal Ember properties
- if (key.substring(0,2) === '__') return;
- if (key === '_super') return;
- if (indexOf(array, key) >= 0) return;
- if (!obj.hasOwnProperty(key)) return;
- array.push(key);
- };
- Ember.keys = function(obj) {
- var ret = [], key;
- for (key in obj) {
- pushPropertyName(obj, ret, key);
- }
- // IE8 doesn't enumerate property that named the same as prototype properties.
- for (var i = 0, l = prototypeProperties.length; i < l; i++) {
- key = prototypeProperties[i];
- pushPropertyName(obj, ret, key);
- }
- return ret;
- };
- }
- })();
- (function() {
- /**
- @module ember
- @submodule ember-runtime
- */
- var STRING_DASHERIZE_REGEXP = (/[ _]/g);
- var STRING_DASHERIZE_CACHE = {};
- var STRING_DECAMELIZE_REGEXP = (/([a-z\d])([A-Z])/g);
- var STRING_CAMELIZE_REGEXP = (/(\-|_|\.|\s)+(.)?/g);
- var STRING_UNDERSCORE_REGEXP_1 = (/([a-z\d])([A-Z]+)/g);
- var STRING_UNDERSCORE_REGEXP_2 = (/\-|\s+/g);
- var STRING_PARAMETERIZE_REGEXP_1 = (/[_|\/|\s]+/g);
- var STRING_PARAMETERIZE_REGEXP_2 = (/[^a-z0-9\-]+/gi);
- var STRING_PARAMETERIZE_REGEXP_3 = (/[\-]+/g);
- var STRING_PARAMETERIZE_REGEXP_4 = (/^-+|-+$/g);
- /**
- Defines the hash of localized strings for the current language. Used by
- the `Ember.String.loc()` helper. To localize, add string values to this
- hash.
- @property STRINGS
- @for Ember
- @type Hash
- */
- Ember.STRINGS = {};
- /**
- Defines string helper methods including string formatting and localization.
- Unless `Ember.EXTEND_PROTOTYPES.String` is `false` these methods will also be
- added to the `String.prototype` as well.
- @class String
- @namespace Ember
- @static
- */
- Ember.String = {
- /**
- Apply formatting options to the string. This will look for occurrences
- of "%@" in your string and substitute them with the arguments you pass into
- this method. If you want to control the specific order of replacement,
- you can add a number after the key as well to indicate which argument
- you want to insert.
- Ordered insertions are most useful when building loc strings where values
- you need to insert may appear in different orders.
- ```javascript
- "Hello %@ %@".fmt('John', 'Doe'); // "Hello John Doe"
- "Hello %@2, %@1".fmt('John', 'Doe'); // "Hello Doe, John"
- ```
- @method fmt
- @param {String} str The string to format
- @param {Array} formats An array of parameters to interpolate into string.
- @return {String} formatted string
- */
- fmt: function(str, formats) {
- // first, replace any ORDERED replacements.
- var idx = 0; // the current index for non-numerical replacements
- return str.replace(/%@([0-9]+)?/g, function(s, argIndex) {
- argIndex = (argIndex) ? parseInt(argIndex, 10) - 1 : idx++;
- s = formats[argIndex];
- return (s === null) ? '(null)' : (s === undefined) ? '' : Ember.inspect(s);
- }) ;
- },
- /**
- Formats the passed string, but first looks up the string in the localized
- strings hash. This is a convenient way to localize text. See
- `Ember.String.fmt()` for more information on formatting.
- Note that it is traditional but not required to prefix localized string
- keys with an underscore or other character so you can easily identify
- localized strings.
- ```javascript
- Ember.STRINGS = {
- '_Hello World': 'Bonjour le monde',
- '_Hello %@ %@': 'Bonjour %@ %@'
- };
- Ember.String.loc("_Hello World"); // 'Bonjour le monde';
- Ember.String.loc("_Hello %@ %@", ["John", "Smith"]); // "Bonjour John Smith";
- ```
- @method loc
- @param {String} str The string to format
- @param {Array} formats Optional array of parameters to interpolate into string.
- @return {String} formatted string
- */
- loc: function(str, formats) {
- str = Ember.STRINGS[str] || str;
- return Ember.String.fmt(str, formats) ;
- },
- /**
- Splits a string into separate units separated by spaces, eliminating any
- empty strings in the process. This is a convenience method for split that
- is mostly useful when applied to the `String.prototype`.
- ```javascript
- Ember.String.w("alpha beta gamma").forEach(function(key) {
- console.log(key);
- });
- // > alpha
- // > beta
- // > gamma
- ```
- @method w
- @param {String} str The string to split
- @return {String} split string
- */
- w: function(str) { return str.split(/\s+/); },
- /**
- Converts a camelized string into all lower case separated by underscores.
- ```javascript
- 'innerHTML'.decamelize(); // 'inner_html'
- 'action_name'.decamelize(); // 'action_name'
- 'css-class-name'.decamelize(); // 'css-class-name'
- 'my favorite items'.decamelize(); // 'my favorite items'
- ```
- @method decamelize
- @param {String} str The string to decamelize.
- @return {String} the decamelized string.
- */
- decamelize: function(str) {
- return str.replace(STRING_DECAMELIZE_REGEXP, '$1_$2').toLowerCase();
- },
- /**
- Replaces underscores, spaces, or camelCase with dashes.
- ```javascript
- 'innerHTML'.dasherize(); // 'inner-html'
- 'action_name'.dasherize(); // 'action-name'
- 'css-class-name'.dasherize(); // 'css-class-name'
- 'my favorite items'.dasherize(); // 'my-favorite-items'
- ```
- @method dasherize
- @param {String} str The string to dasherize.
- @return {String} the dasherized string.
- */
- dasherize: function(str) {
- var cache = STRING_DASHERIZE_CACHE,
- hit = cache.hasOwnProperty(str),
- ret;
- if (hit) {
- return cache[str];
- } else {
- ret = Ember.String.decamelize(str).replace(STRING_DASHERIZE_REGEXP,'-');
- cache[str] = ret;
- }
- return ret;
- },
- /**
- Returns the lowerCamelCase form of a string.
- ```javascript
- 'innerHTML'.camelize(); // 'innerHTML'
- 'action_name'.camelize(); // 'actionName'
- 'css-class-name'.camelize(); // 'cssClassName'
- 'my favorite items'.camelize(); // 'myFavoriteItems'
- 'My Favorite Items'.camelize(); // 'myFavoriteItems'
- ```
- @method camelize
- @param {String} str The string to camelize.
- @return {String} the camelized string.
- */
- camelize: function(str) {
- return str.replace(STRING_CAMELIZE_REGEXP, function(match, separator, chr) {
- return chr ? chr.toUpperCase() : '';
- }).replace(/^([A-Z])/, function(match, separator, chr) {
- return match.toLowerCase();
- });
- },
- /**
- Returns the UpperCamelCase form of a string.
- ```javascript
- 'innerHTML'.classify(); // 'InnerHTML'
- 'action_name'.classify(); // 'ActionName'
- 'css-class-name'.classify(); // 'CssClassName'
- 'my favorite items'.classify(); // 'MyFavoriteItems'
- ```
- @method classify
- @param {String} str the string to classify
- @return {String} the classified string
- */
- classify: function(str) {
- var parts = str.split("."),
- out = [];
- for (var i=0, l=parts.length; i<l; i++) {
- var camelized = Ember.String.camelize(parts[i]);
- out.push(camelized.charAt(0).toUpperCase() + camelized.substr(1));
- }
- return out.join(".");
- },
- /**
- More general than decamelize. Returns the lower\_case\_and\_underscored
- form of a string.
- ```javascript
- 'innerHTML'.underscore(); // 'inner_html'
- 'action_name'.underscore(); // 'action_name'
- 'css-class-name'.underscore(); // 'css_class_name'
- 'my favorite items'.underscore(); // 'my_favorite_items'
- ```
- @method underscore
- @param {String} str The string to underscore.
- @return {String} the underscored string.
- */
- underscore: function(str) {
- return str.replace(STRING_UNDERSCORE_REGEXP_1, '$1_$2').
- replace(STRING_UNDERSCORE_REGEXP_2, '_').toLowerCase();
- },
- /**
- Returns the Capitalized form of a string
- ```javascript
- 'innerHTML'.capitalize() // 'InnerHTML'
- 'action_name'.capitalize() // 'Action_name'
- 'css-class-name'.capitalize() // 'Css-class-name'
- 'my favorite items'.capitalize() // 'My favorite items'
- ```
- @method capitalize
- @param {String} str The string to capitalize.
- @return {String} The capitalized string.
- */
- capitalize: function(str) {
- return str.charAt(0).toUpperCase() + str.substr(1);
- }
- };
- })();
- (function() {
- /**
- @module ember
- @submodule ember-runtime
- */
- var fmt = Ember.String.fmt,
- w = Ember.String.w,
- loc = Ember.String.loc,
- camelize = Ember.String.camelize,
- decamelize = Ember.String.decamelize,
- dasherize = Ember.String.dasherize,
- underscore = Ember.String.underscore,
- capitalize = Ember.String.capitalize,
- classify = Ember.String.classify;
- if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.String) {
- /**
- See [Ember.String.fmt](/api/classes/Ember.String.html#method_fmt).
- @method fmt
- @for String
- */
- String.prototype.fmt = function() {
- return fmt(this, arguments);
- };
- /**
- See [Ember.String.w](/api/classes/Ember.String.html#method_w).
- @method w
- @for String
- */
- String.prototype.w = function() {
- return w(this);
- };
- /**
- See [Ember.String.loc](/api/classes/Ember.String.html#method_loc).
- @method loc
- @for String
- */
- String.prototype.loc = function() {
- return loc(this, arguments);
- };
- /**
- See [Ember.String.camelize](/api/classes/Ember.String.html#method_camelize).
- @method camelize
- @for String
- */
- String.prototype.camelize = function() {
- return camelize(this);
- };
- /**
- See [Ember.String.decamelize](/api/classes/Ember.String.html#method_decamelize).
- @method decamelize
- @for String
- */
- String.prototype.decamelize = function() {
- return decamelize(this);
- };
- /**
- See [Ember.String.dasherize](/api/classes/Ember.String.html#method_dasherize).
- @method dasherize
- @for String
- */
- String.prototype.dasherize = function() {
- return dasherize(this);
- };
- /**
- See [Ember.String.underscore](/api/classes/Ember.String.html#method_underscore).
- @method underscore
- @for String
- */
- String.prototype.underscore = function() {
- return underscore(this);
- };
- /**
- See [Ember.String.classify](/api/classes/Ember.String.html#method_classify).
- @method classify
- @for String
- */
- String.prototype.classify = function() {
- return classify(this);
- };
- /**
- See [Ember.String.capitalize](/api/classes/Ember.String.html#method_capitalize).
- @method capitalize
- @for String
- */
- String.prototype.capitalize = function() {
- return capitalize(this);
- };
-
- }
- })();
- (function() {
- /**
- @module ember
- @submodule ember-runtime
- */
- var get = Ember.get,
- set = Ember.set,
- slice = Array.prototype.slice,
- getProperties = Ember.getProperties;
- /**
- ## Overview
- This mixin provides properties and property observing functionality, core
- features of the Ember object model.
- Properties and observers allow one object to observe changes to a
- property on another object. This is one of the fundamental ways that
- models, controllers and views communicate with each other in an Ember
- application.
- Any object that has this mixin applied can be used in observer
- operations. That includes `Ember.Object` and most objects you will
- interact with as you write your Ember application.
- Note that you will not generally apply this mixin to classes yourself,
- but you will use the features provided by this module frequently, so it
- is important to understand how to use it.
- ## Using `get()` and `set()`
- Because of Ember's support for bindings and observers, you will always
- access properties using the get method, and set properties using the
- set method. This allows the observing objects to be notified and
- computed properties to be handled properly.
- More documentation about `get` and `set` are below.
- ## Observing Property Changes
- You typically observe property changes simply by adding the `observes`
- call to the end of your method declarations in classes that you write.
- For example:
- ```javascript
- Ember.Object.extend({
- valueObserver: function() {
- // Executes whenever the "value" property changes
- }.observes('value')
- });
- ```
- Although this is the most common way to add an observer, this capability
- is actually built into the `Ember.Object` class on top of two methods
- defined in this mixin: `addObserver` and `removeObserver`. You can use
- these two methods to add and remove observers yourself if you need to
- do so at runtime.
- To add an observer for a property, call:
- ```javascript
- object.addObserver('propertyKey', targetObject, targetAction)
- ```
- This will call the `targetAction` method on the `targetObject` whenever
- the value of the `propertyKey` changes.
- Note that if `propertyKey` is a computed property, the observer will be
- called when any of the property dependencies are changed, even if the
- resulting value of the computed property is unchanged. This is necessary
- because computed properties are not computed until `get` is called.
- @class Observable
- @namespace Ember
- */
- Ember.Observable = Ember.Mixin.create({
- /**
- Retrieves the value of a property from the object.
- This method is usually similar to using `object[keyName]` or `object.keyName`,
- however it supports both computed properties and the unknownProperty
- handler.
- Because `get` unifies the syntax for accessing all these kinds
- of properties, it can make many refactorings easier, such as replacing a
- simple property with a computed property, or vice versa.
- ### Computed Properties
- Computed properties are methods defined with the `property` modifier
- declared at the end, such as:
- ```javascript
- fullName: function() {
- return this.get('firstName') + ' ' + this.get('lastName');
- }.property('firstName', 'lastName')
- ```
- When you call `get` on a computed property, the function will be
- called and the return value will be returned instead of the function
- itself.
- ### Unknown Properties
- Likewise, if you try to call `get` on a property whose value is
- `undefined`, the `unknownProperty()` method will be called on the object.
- If this method returns any value other than `undefined`, it will be returned
- instead. This allows you to implement "virtual" properties that are
- not defined upfront.
- @method get
- @param {String} keyName The property to retrieve
- @return {Object} The property value or undefined.
- */
- get: function(keyName) {
- return get(this, keyName);
- },
- /**
- To get multiple properties at once, call `getProperties`
- with a list of strings or an array:
- ```javascript
- record.getProperties('firstName', 'lastName', 'zipCode'); // { firstName: 'John', lastName: 'Doe', zipCode: '10011' }
- ```
- is equivalent to:
- ```javascript
- record.getProperties(['firstName', 'lastName', 'zipCode']); // { firstName: 'John', lastName: 'Doe', zipCode: '10011' }
- ```
- @method getProperties
- @param {String...|Array} list of keys to get
- @return {Hash}
- */
- getProperties: function() {
- return getProperties.apply(null, [this].concat(slice.call(arguments)));
- },
- /**
- Sets the provided key or path to the value.
- This method is generally very similar to calling `object[key] = value` or
- `object.key = value`, except that it provides support for computed
- properties, the `setUnknownProperty()` method and property observers.
- ### Computed Properties
- If you try to set a value on a key that has a computed property handler
- defined (see the `get()` method for an example), then `set()` will call
- that method, passing both the value and key instead of simply changing
- the value itself. This is useful for those times when you need to
- implement a property that is composed of one or more member
- properties.
- ### Unknown Properties
- If you try to set a value on a key that is undefined in the target
- object, then the `setUnknownProperty()` handler will be called instead. This
- gives you an opportunity to implement complex "virtual" properties that
- are not predefined on the object. If `setUnknownProperty()` returns
- undefined, then `set()` will simply set the value on the object.
- ### Property Observers
- In addition to changing the property, `set()` will also register a property
- change with the object. Unless you have placed this call inside of a
- `beginPropertyChanges()` and `endPropertyChanges(),` any "local" observers
- (i.e. observer methods declared on the same object), will be called
- immediately. Any "remote" observers (i.e. observer methods declared on
- another object) will be placed in a queue and called at a later time in a
- coalesced manner.
- ### Chaining
- In addition to property changes, `set()` returns the value of the object
- itself so you can do chaining like this:
- ```javascript
- record.set('firstName', 'Charles').set('lastName', 'Jolley');
- ```
- @method set
- @param {String} keyName The property to set
- @param {Object} value The value to set or `null`.
- @return {Ember.Observable}
- */
- set: function(keyName, value) {
- set(this, keyName, value);
- return this;
- },
- /**
- Sets a list of properties at once. These properties are set inside
- a single `beginPropertyChanges` and `endPropertyChanges` batch, so
- observers will be buffered.
- ```javascript
- record.setProperties({ firstName: 'Charles', lastName: 'Jolley' });
- ```
- @method setProperties
- @param {Hash} hash the hash of keys and values to set
- @return {Ember.Observable}
- */
- setProperties: function(hash) {
- return Ember.setProperties(this, hash);
- },
- /**
- Begins a grouping of property changes.
- You can use this method to group property changes so that notifications
- will not be sent until the changes are finished. If you plan to make a
- large number of changes to an object at one time, you should call this
- method at the beginning of the changes to begin deferring change
- notifications. When you are done making changes, call
- `endPropertyChanges()` to deliver the deferred change notifications and end
- deferring.
- @method beginPropertyChanges
- @return {Ember.Observable}
- */
- beginPropertyChanges: function() {
- Ember.beginPropertyChanges();
- return this;
- },
- /**
- Ends a grouping of property changes.
- You can use this method to group property changes so that notifications
- will not be sent until the changes are finished. If you plan to make a
- large number of changes to an object at one time, you should call
- `beginPropertyChanges()` at the beginning of the changes to defer change
- notifications. When you are done making changes, call this method to
- deliver the deferred change notifications and end deferring.
- @method endPropertyChanges
- @return {Ember.Observable}
- */
- endPropertyChanges: function() {
- Ember.endPropertyChanges();
- return this;
- },
- /**
- Notify the observer system that a property is about to change.
- Sometimes you need to change a value directly or indirectly without
- actually calling `get()` or `set()` on it. In this case, you can use this
- method and `propertyDidChange()` instead. Calling these two methods
- together will notify all observers that the property has potentially
- changed value.
- Note that you must always call `propertyWillChange` and `propertyDidChange`
- as a pair. If you do not, it may get the property change groups out of
- order and cause notifications to be delivered more often than you would
- like.
- @method propertyWillChange
- @param {String} keyName The property key that is about to change.
- @return {Ember.Observable}
- */
- propertyWillChange: function(keyName) {
- Ember.propertyWillChange(this, keyName);
- return this;
- },
- /**
- Notify the observer system that a property has just changed.
- Sometimes you need to change a value directly or indirectly without
- actually calling `get()` or `set()` on it. In this case, you can use this
- method and `propertyWillChange()` instead. Calling these two methods
- together will notify all observers that the property has potentially
- changed value.
- Note that you must always call `propertyWillChange` and `propertyDidChange`
- as a pair. If you do not, it may get the property change groups out of
- order and cause notifications to be delivered more often than you would
- like.
- @method propertyDidChange
- @param {String} keyName The property key that has just changed.
- @return {Ember.Observable}
- */
- propertyDidChange: function(keyName) {
- Ember.propertyDidChange(this, keyName);
- return this;
- },
- /**
- Convenience method to call `propertyWillChange` and `propertyDidChange` in
- succession.
- @method notifyPropertyChange
- @param {String} keyName The property key to be notified about.
- @return {Ember.Observable}
- */
- notifyPropertyChange: function(keyName) {
- this.propertyWillChange(keyName);
- this.propertyDidChange(keyName);
- return this;
- },
- addBeforeObserver: function(key, target, method) {
- Ember.addBeforeObserver(this, key, target, method);
- },
- /**
- Adds an observer on a property.
- This is the core method used to register an observer for a property.
- Once you call this method, any time the key's value is set, your observer
- will be notified. Note that the observers are triggered any time the
- value is set, regardless of whether it has actually changed. Your
- observer should be prepared to handle that.
- You can also pass an optional context parameter to this method. The
- context will be passed to your observer method whenever it is triggered.
- Note that if you add the same target/method pair on a key multiple times
- with different context parameters, your observer will only be called once
- with the last context you passed.
- ### Observer Methods
- Observer methods you pass should generally have the following signature if
- you do not pass a `context` parameter:
- ```javascript
- fooDidChange: function(sender, key, value, rev) { };
- ```
- The sender is the object that changed. The key is the property that
- changes. The value property is currently reserved and unused. The rev
- is the last property revision of the object when it changed, which you can
- use to detect if the key value has really changed or not.
- If you pass a `context` parameter, the context will be passed before the
- revision like so:
- ```javascript
- fooDidChange: function(sender, key, value, context, rev) { };
- ```
- Usually you will not need the value, context or revision parameters at
- the end. In this case, it is common to write observer methods that take
- only a sender and key value as parameters or, if you aren't interested in
- any of these values, to write an observer that has no parameters at all.
- @method addObserver
- @param {String} key The key to observer
- @param {Object} target The target object to invoke
- @param {String|Function} method The method to invoke.
- @return {Ember.Object} self
- */
- addObserver: function(key, target, method) {
- Ember.addObserver(this, key, target, method);
- },
- /**
- Remove an observer you have previously registered on this object. Pass
- the same key, target, and method you passed to `addObserver()` and your
- target will no longer receive notifications.
- @method removeObserver
- @param {String} key The key to observer
- @param {Object} target The target object to invoke
- @param {String|Function} method The method to invoke.
- @return {Ember.Observable} receiver
- */
- removeObserver: function(key, target, method) {
- Ember.removeObserver(this, key, target, method);
- },
- /**
- Returns `true` if the object currently has observers registered for a
- particular key. You can use this method to potentially defer performing
- an expensive action until someone begins observing a particular property
- on the object.
- @method hasObserverFor
- @param {String} key Key to check
- @return {Boolean}
- */
- hasObserverFor: function(key) {
- return Ember.hasListeners(this, key+':change');
- },
- /**
- Retrieves the value of a property, or a default value in the case that the
- property returns `undefined`.
- ```javascript
- person.getWithDefault('lastName', 'Doe');
- ```
- @method getWithDefault
- @param {String} keyName The name of the property to retrieve
- @param {Object} defaultValue The value to return if the property value is undefined
- @return {Object} The property value or the defaultValue.
- */
- getWithDefault: function(keyName, defaultValue) {
- return Ember.getWithDefault(this, keyName, defaultValue);
- },
- /**
- Set the value of a property to the current value plus some amount.
- ```javascript
- person.incrementProperty('age');
- team.incrementProperty('score', 2);
- ```
- @method incrementProperty
- @param {String} keyName The name of the property to increment
- @param {Number} increment The amount to increment by. Defaults to 1
- @return {Number} The new property value
- */
- incrementProperty: function(keyName, increment) {
- if (Ember.isNone(increment)) { increment = 1; }
- Ember.assert("Must pass a numeric value to incrementProperty", (!isNaN(parseFloat(increment)) && isFinite(increment)));
- set(this, keyName, (get(this, keyName) || 0) + increment);
- return get(this, keyName);
- },
- /**
- Set the value of a property to the current value minus some amount.
- ```javascript
- player.decrementProperty('lives');
- orc.decrementProperty('health', 5);
- ```
- @method decrementProperty
- @param {String} keyName The name of the property to decrement
- @param {Number} decrement The amount to decrement by. Defaults to 1
- @return {Number} The new property value
- */
- decrementProperty: function(keyName, decrement) {
- if (Ember.isNone(decrement)) { decrement = 1; }
- Ember.assert("Must pass a numeric value to decrementProperty", (!isNaN(parseFloat(decrement)) && isFinite(decrement)));
- set(this, keyName, (get(this, keyName) || 0) - decrement);
- return get(this, keyName);
- },
- /**
- Set the value of a boolean property to the opposite of it's
- current value.
- ```javascript
- starship.toggleProperty('warpDriveEngaged');
- ```
- @method toggleProperty
- @param {String} keyName The name of the property to toggle
- @return {Object} The new property value
- */
- toggleProperty: function(keyName) {
- set(this, keyName, !get(this, keyName));
- return get(this, keyName);
- },
- /**
- Returns the cached value of a computed property, if it exists.
- This allows you to inspect the value of a computed property
- without accidentally invoking it if it is intended to be
- generated lazily.
- @method cacheFor
- @param {String} keyName
- @return {Object} The cached value of the computed property, if any
- */
- cacheFor: function(keyName) {
- return Ember.cacheFor(this, keyName);
- },
- // intended for debugging purposes
- observersForKey: function(keyName) {
- return Ember.observersFor(this, keyName);
- }
- });
- })();
- (function() {
- /**
- @module ember
- @submodule ember-runtime
- */
- // NOTE: this object should never be included directly. Instead use `Ember.Object`.
- // We only define this separately so that `Ember.Set` can depend on it.
- var set = Ember.set, get = Ember.get,
- o_create = Ember.create,
- o_defineProperty = Ember.platform.defineProperty,
- GUID_KEY = Ember.GUID_KEY,
- guidFor = Ember.guidFor,
- generateGuid = Ember.generateGuid,
- meta = Ember.meta,
- META_KEY = Ember.META_KEY,
- rewatch = Ember.rewatch,
- finishChains = Ember.finishChains,
- sendEvent = Ember.sendEvent,
- destroy = Ember.destroy,
- schedule = Ember.run.schedule,
- Mixin = Ember.Mixin,
- applyMixin = Mixin._apply,
- finishPartial = Mixin.finishPartial,
- reopen = Mixin.prototype.reopen,
- MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER,
- indexOf = Ember.EnumerableUtils.indexOf;
- var undefinedDescriptor = {
- configurable: true,
- writable: true,
- enumerable: false,
- value: undefined
- };
- function makeCtor() {
- // Note: avoid accessing any properties on the object since it makes the
- // method a lot faster. This is glue code so we want it to be as fast as
- // possible.
- var wasApplied = false, initMixins, initProperties;
- var Class = function() {
- if (!wasApplied) {
- Class.proto(); // prepare prototype...
- }
- o_defineProperty(this, GUID_KEY, undefinedDescriptor);
- o_defineProperty(this, '_super', undefinedDescriptor);
- var m = meta(this), proto = m.proto;
- m.proto = this;
- if (initMixins) {
- // capture locally so we can clear the closed over variable
- var mixins = initMixins;
- initMixins = null;
- this.reopen.apply(this, mixins);
- }
- if (initProperties) {
- // capture locally so we can clear the closed over variable
- var props = initProperties;
- initProperties = null;
- var concatenatedProperties = this.concatenatedProperties;
- for (var i = 0, l = props.length; i < l; i++) {
- var properties = props[i];
- Ember.assert("Ember.Object.create no longer supports mixing in other definitions, use createWithMixins instead.", !(properties instanceof Ember.Mixin));
- if (typeof properties !== 'object' && properties !== undefined) {
- throw new Ember.Error("Ember.Object.create only accepts objects.");
- }
- if (!properties) { continue; }
- var keyNames = Ember.keys(properties);
- for (var j = 0, ll = keyNames.length; j < ll; j++) {
- var keyName = keyNames[j];
- if (!properties.hasOwnProperty(keyName)) { continue; }
- var value = properties[keyName],
- IS_BINDING = Ember.IS_BINDING;
- if (IS_BINDING.test(keyName)) {
- var bindings = m.bindings;
- if (!bindings) {
- bindings = m.bindings = {};
- } else if (!m.hasOwnProperty('bindings')) {
- bindings = m.bindings = o_create(m.bindings);
- }
- bindings[keyName] = value;
- }
- var desc = m.descs[keyName];
- Ember.assert("Ember.Object.create no longer supports defining computed properties.", !(value instanceof Ember.ComputedProperty));
- Ember.assert("Ember.Object.create no longer supports defining methods that call _super.", !(typeof value === 'function' && value.toString().indexOf('._super') !== -1));
- Ember.assert("`actions` must be provided at extend time, not at create " +
- "time, when Ember.ActionHandler is used (i.e. views, " +
- "controllers & routes).", !((keyName === 'actions') && Ember.ActionHandler.detect(this)));
- if (concatenatedProperties && indexOf(concatenatedProperties, keyName) >= 0) {
- var baseValue = this[keyName];
- if (baseValue) {
- if ('function' === typeof baseValue.concat) {
- value = baseValue.concat(value);
- } else {
- value = Ember.makeArray(baseValue).concat(value);
- }
- } else {
- value = Ember.makeArray(value);
- }
- }
- if (desc) {
- desc.set(this, keyName, value);
- } else {
- if (typeof this.setUnknownProperty === 'function' && !(keyName in this)) {
- this.setUnknownProperty(keyName, value);
- } else if (MANDATORY_SETTER) {
- Ember.defineProperty(this, keyName, null, value); // setup mandatory setter
- } else {
- this[keyName] = value;
- }
- }
- }
- }
- }
- finishPartial(this, m);
- this.init.apply(this, arguments);
- m.proto = proto;
- finishChains(this);
- sendEvent(this, "init");
- };
- Class.toString = Mixin.prototype.toString;
- Class.willReopen = function() {
- if (wasApplied) {
- Class.PrototypeMixin = Mixin.create(Class.PrototypeMixin);
- }
- wasApplied = false;
- };
- Class._initMixins = function(args) { initMixins = args; };
- Class._initProperties = function(args) { initProperties = args; };
- Class.proto = function() {
- var superclass = Class.superclass;
- if (superclass) { superclass.proto(); }
- if (!wasApplied) {
- wasApplied = true;
- Class.PrototypeMixin.applyPartial(Class.prototype);
- rewatch(Class.prototype);
- }
- return this.prototype;
- };
- return Class;
- }
- /**
- @class CoreObject
- @namespace Ember
- */
- var CoreObject = makeCtor();
- CoreObject.toString = function() { return "Ember.CoreObject"; };
- CoreObject.PrototypeMixin = Mixin.create({
- reopen: function() {
- applyMixin(this, arguments, true);
- return this;
- },
- /**
- An overridable method called when objects are instantiated. By default,
- does nothing unless it is overridden during class definition.
- Example:
- ```javascript
- App.Person = Ember.Object.extend({
- init: function() {
- alert('Name is ' + this.get('name'));
- }
- });
- var steve = App.Person.create({
- name: "Steve"
- });
- // alerts 'Name is Steve'.
- ```
- NOTE: If you do override `init` for a framework class like `Ember.View` or
- `Ember.ArrayController`, be sure to call `this._super()` in your
- `init` declaration! If you don't, Ember may not have an opportunity to
- do important setup work, and you'll see strange behavior in your
- application.
- @method init
- */
- init: function() {},
- /**
- Defines the properties that will be concatenated from the superclass
- (instead of overridden).
- By default, when you extend an Ember class a property defined in
- the subclass overrides a property with the same name that is defined
- in the superclass. However, there are some cases where it is preferable
- to build up a property's value by combining the superclass' property
- value with the subclass' value. An example of this in use within Ember
- is the `classNames` property of `Ember.View`.
- Here is some sample code showing the difference between a concatenated
- property and a normal one:
- ```javascript
- App.BarView = Ember.View.extend({
- someNonConcatenatedProperty: ['bar'],
- classNames: ['bar']
- });
- App.FooBarView = App.BarView.extend({
- someNonConcatenatedProperty: ['foo'],
- classNames: ['foo'],
- });
- var fooBarView = App.FooBarView.create();
- fooBarView.get('someNonConcatenatedProperty'); // ['foo']
- fooBarView.get('classNames'); // ['ember-view', 'bar', 'foo']
- ```
- This behavior extends to object creation as well. Continuing the
- above example:
- ```javascript
- var view = App.FooBarView.create({
- someNonConcatenatedProperty: ['baz'],
- classNames: ['baz']
- })
- view.get('someNonConcatenatedProperty'); // ['baz']
- view.get('classNames'); // ['ember-view', 'bar', 'foo', 'baz']
- ```
- Adding a single property that is not an array will just add it in the array:
- ```javascript
- var view = App.FooBarView.create({
- classNames: 'baz'
- })
- view.get('classNames'); // ['ember-view', 'bar', 'foo', 'baz']
- ```
- Using the `concatenatedProperties` property, we can tell to Ember that mix
- the content of the properties.
- In `Ember.View` the `classNameBindings` and `attributeBindings` properties
- are also concatenated, in addition to `classNames`.
- This feature is available for you to use throughout the Ember object model,
- although typical app developers are likely to use it infrequently. Since
- it changes expectations about behavior of properties, you should properly
- document its usage in each individual concatenated property (to not
- mislead your users to think they can override the property in a subclass).
- @property concatenatedProperties
- @type Array
- @default null
- */
- concatenatedProperties: null,
- /**
- Destroyed object property flag.
- if this property is `true` the observers and bindings were already
- removed by the effect of calling the `destroy()` method.
- @property isDestroyed
- @default false
- */
- isDestroyed: false,
- /**
- Destruction scheduled flag. The `destroy()` method has been called.
- The object stays intact until the end of the run loop at which point
- the `isDestroyed` flag is set.
- @property isDestroying
- @default false
- */
- isDestroying: false,
- /**
- Destroys an object by setting the `isDestroyed` flag and removing its
- metadata, which effectively destroys observers and bindings.
- If you try to set a property on a destroyed object, an exception will be
- raised.
- Note that destruction is scheduled for the end of the run loop and does not
- happen immediately. It will set an isDestroying flag immediately.
- @method destroy
- @return {Ember.Object} receiver
- */
- destroy: function() {
- if (this.isDestroying) { return; }
- this.isDestroying = true;
- schedule('actions', this, this.willDestroy);
- schedule('destroy', this, this._scheduledDestroy);
- return this;
- },
- /**
- Override to implement teardown.
- @method willDestroy
- */
- willDestroy: Ember.K,
- /**
- Invoked by the run loop to actually destroy the object. This is
- scheduled for execution by the `destroy` method.
- @private
- @method _scheduledDestroy
- */
- _scheduledDestroy: function() {
- if (this.isDestroyed) { return; }
- destroy(this);
- this.isDestroyed = true;
- },
- bind: function(to, from) {
- if (!(from instanceof Ember.Binding)) { from = Ember.Binding.from(from); }
- from.to(to).connect(this);
- return from;
- },
- /**
- Returns a string representation which attempts to provide more information
- than Javascript's `toString` typically does, in a generic way for all Ember
- objects.
- ```javascript
- App.Person = Em.Object.extend()
- person = App.Person.create()
- person.toString() //=> "<App.Person:ember1024>"
- ```
- If the object's class is not defined on an Ember namespace, it will
- indicate it is a subclass of the registered superclass:
- ```javascript
- Student = App.Person.extend()
- student = Student.create()
- student.toString() //=> "<(subclass of App.Person):ember1025>"
- ```
- If the method `toStringExtension` is defined, its return value will be
- included in the output.
- ```javascript
- App.Teacher = App.Person.extend({
- toStringExtension: function() {
- return this.get('fullName');
- }
- });
- teacher = App.Teacher.create()
- teacher.toString(); //=> "<App.Teacher:ember1026:Tom Dale>"
- ```
- @method toString
- @return {String} string representation
- */
- toString: function toString() {
- var hasToStringExtension = typeof this.toStringExtension === 'function',
- extension = hasToStringExtension ? ":" + this.toStringExtension() : '';
- var ret = '<'+this.constructor.toString()+':'+guidFor(this)+extension+'>';
- this.toString = makeToString(ret);
- return ret;
- }
- });
- CoreObject.PrototypeMixin.ownerConstructor = CoreObject;
- function makeToString(ret) {
- return function() { return ret; };
- }
- if (Ember.config.overridePrototypeMixin) {
- Ember.config.overridePrototypeMixin(CoreObject.PrototypeMixin);
- }
- CoreObject.__super__ = null;
- var ClassMixin = Mixin.create({
- ClassMixin: Ember.required(),
- PrototypeMixin: Ember.required(),
- isClass: true,
- isMethod: false,
- /**
- Creates a new subclass.
- ```javascript
- App.Person = Ember.Object.extend({
- say: function(thing) {
- alert(thing);
- }
- });
- ```
- This defines a new subclass of Ember.Object: `App.Person`. It contains one method: `say()`.
- You can also create a subclass from any existing class by calling its `extend()` method. For example, you might want to create a subclass of Ember's built-in `Ember.View` class:
- ```javascript
- App.PersonView = Ember.View.extend({
- tagName: 'li',
- classNameBindings: ['isAdministrator']
- });
- ```
- When defining a subclass, you can override methods but still access the implementation of your parent class by calling the special `_super()` method:
- ```javascript
- App.Person = Ember.Object.extend({
- say: function(thing) {
- var name = this.get('name');
- alert(name + ' says: ' + thing);
- }
- });
- App.Soldier = App.Person.extend({
- say: function(thing) {
- this._super(thing + ", sir!");
- },
- march: function(numberOfHours) {
- alert(this.get('name') + ' marches for ' + numberOfHours + ' hours.')
- }
- });
- var yehuda = App.Soldier.create({
- name: "Yehuda Katz"
- });
- yehuda.say("Yes"); // alerts "Yehuda Katz says: Yes, sir!"
- ```
- The `create()` on line #17 creates an *instance* of the `App.Soldier` class. The `extend()` on line #8 creates a *subclass* of `App.Person`. Any instance of the `App.Person` class will *not* have the `march()` method.
- You can also pass `Ember.Mixin` classes to add additional properties to the subclass.
- ```javascript
- App.Person = Ember.Object.extend({
- say: function(thing) {
- alert(this.get('name') + ' says: ' + thing);
- }
- });
- App.SingingMixin = Ember.Mixin.create({
- sing: function(thing){
- alert(this.get('name') + ' sings: la la la ' + thing);
- }
- });
- App.BroadwayStar = App.Person.extend(App.SingingMixin, {
- dance: function() {
- alert(this.get('name') + ' dances: tap tap tap tap ');
- }
- });
- ```
- The `App.BroadwayStar` class contains three methods: `say()`, `sing()`, and `dance()`.
- @method extend
- @static
- @param {Ember.Mixin} [mixins]* One or more Ember.Mixin classes
- @param {Object} [arguments]* Object containing values to use within the new class
- */
- extend: function() {
- var Class = makeCtor(), proto;
- Class.ClassMixin = Mixin.create(this.ClassMixin);
- Class.PrototypeMixin = Mixin.create(this.PrototypeMixin);
- Class.ClassMixin.ownerConstructor = Class;
- Class.PrototypeMixin.ownerConstructor = Class;
- reopen.apply(Class.PrototypeMixin, arguments);
- Class.superclass = this;
- Class.__super__ = this.prototype;
- proto = Class.prototype = o_create(this.prototype);
- proto.constructor = Class;
- generateGuid(proto);
- meta(proto).proto = proto; // this will disable observers on prototype
- Class.ClassMixin.apply(Class);
- return Class;
- },
- /**
- Equivalent to doing `extend(arguments).create()`.
- If possible use the normal `create` method instead.
- @method createWithMixins
- @static
- @param [arguments]*
- */
- createWithMixins: function() {
- var C = this;
- if (arguments.length>0) { this._initMixins(arguments); }
- return new C();
- },
- /**
- Creates an instance of a class. Accepts either no arguments, or an object
- containing values to initialize the newly instantiated object with.
- ```javascript
- App.Person = Ember.Object.extend({
- helloWorld: function() {
- alert("Hi, my name is " + this.get('name'));
- }
- });
- var tom = App.Person.create({
- name: 'Tom Dale'
- });
- tom.helloWorld(); // alerts "Hi, my name is Tom Dale".
- ```
- `create` will call the `init` function if defined during
- `Ember.AnyObject.extend`
- If no arguments are passed to `create`, it will not set values to the new
- instance during initialization:
- ```javascript
- var noName = App.Person.create();
- noName.helloWorld(); // alerts undefined
- ```
- NOTE: For performance reasons, you cannot declare methods or computed
- properties during `create`. You should instead declare methods and computed
- properties when using `extend` or use the `createWithMixins` shorthand.
- @method create
- @static
- @param [arguments]*
- */
- create: function() {
- var C = this;
- if (arguments.length>0) { this._initProperties(arguments); }
- return new C();
- },
- /**
- Augments a constructor's prototype with additional
- properties and functions:
- ```javascript
- MyObject = Ember.Object.extend({
- name: 'an object'
- });
- o = MyObject.create();
- o.get('name'); // 'an object'
- MyObject.reopen({
- say: function(msg){
- console.log(msg);
- }
- })
- o2 = MyObject.create();
- o2.say("hello"); // logs "hello"
- o.say("goodbye"); // logs "goodbye"
- ```
- To add functions and properties to the constructor itself,
- see `reopenClass`
- @method reopen
- */
- reopen: function() {
- this.willReopen();
- reopen.apply(this.PrototypeMixin, arguments);
- return this;
- },
- /**
- Augments a constructor's own properties and functions:
- ```javascript
- MyObject = Ember.Object.extend({
- name: 'an object'
- });
- MyObject.reopenClass({
- canBuild: false
- });
- MyObject.canBuild; // false
- o = MyObject.create();
- ```
- In other words, this creates static properties and functions for the class. These are only available on the class
- and not on any instance of that class.
- ```javascript
- App.Person = Ember.Object.extend({
- name : "",
- sayHello : function(){
- alert("Hello. My name is " + this.get('name'));
- }
- });
- App.Person.reopenClass({
- species : "Homo sapiens",
- createPerson: function(newPersonsName){
- return App.Person.create({
- name:newPersonsName
- });
- }
- });
- var tom = App.Person.create({
- name : "Tom Dale"
- });
- var yehuda = App.Person.createPerson("Yehuda Katz");
- tom.sayHello(); // "Hello. My name is Tom Dale"
- yehuda.sayHello(); // "Hello. My name is Yehuda Katz"
- alert(App.Person.species); // "Homo sapiens"
- ```
- Note that `species` and `createPerson` are *not* valid on the `tom` and `yehuda`
- variables. They are only valid on `App.Person`.
- To add functions and properties to instances of
- a constructor by extending the constructor's prototype
- see `reopen`
- @method reopenClass
- */
- reopenClass: function() {
- reopen.apply(this.ClassMixin, arguments);
- applyMixin(this, arguments, false);
- return this;
- },
- detect: function(obj) {
- if ('function' !== typeof obj) { return false; }
- while(obj) {
- if (obj===this) { return true; }
- obj = obj.superclass;
- }
- return false;
- },
- detectInstance: function(obj) {
- return obj instanceof this;
- },
- /**
- In some cases, you may want to annotate computed properties with additional
- metadata about how they function or what values they operate on. For
- example, computed property functions may close over variables that are then
- no longer available for introspection.
- You can pass a hash of these values to a computed property like this:
- ```javascript
- person: function() {
- var personId = this.get('personId');
- return App.Person.create({ id: personId });
- }.property().meta({ type: App.Person })
- ```
- Once you've done this, you can retrieve the values saved to the computed
- property from your class like this:
- ```javascript
- MyClass.metaForProperty('person');
- ```
- This will return the original hash that was passed to `meta()`.
- @method metaForProperty
- @param key {String} property name
- */
- metaForProperty: function(key) {
- var meta = this.proto()[META_KEY],
- desc = meta && meta.descs[key];
- Ember.assert("metaForProperty() could not find a computed property with key '"+key+"'.", !!desc && desc instanceof Ember.ComputedProperty);
- return desc._meta || {};
- },
- /**
- Iterate over each computed property for the class, passing its name
- and any associated metadata (see `metaForProperty`) to the callback.
- @method eachComputedProperty
- @param {Function} callback
- @param {Object} binding
- */
- eachComputedProperty: function(callback, binding) {
- var proto = this.proto(),
- descs = meta(proto).descs,
- empty = {},
- property;
- for (var name in descs) {
- property = descs[name];
- if (property instanceof Ember.ComputedProperty) {
- callback.call(binding || this, name, property._meta || empty);
- }
- }
- }
- });
- ClassMixin.ownerConstructor = CoreObject;
- if (Ember.config.overrideClassMixin) {
- Ember.config.overrideClassMixin(ClassMixin);
- }
- CoreObject.ClassMixin = ClassMixin;
- ClassMixin.apply(CoreObject);
- Ember.CoreObject = CoreObject;
- })();
- (function() {
- /**
- @module ember
- @submodule ember-runtime
- */
- /**
- `Ember.Object` is the main base class for all Ember objects. It is a subclass
- of `Ember.CoreObject` with the `Ember.Observable` mixin applied. For details,
- see the documentation for each of these.
- @class Object
- @namespace Ember
- @extends Ember.CoreObject
- @uses Ember.Observable
- */
- Ember.Object = Ember.CoreObject.extend(Ember.Observable);
- Ember.Object.toString = function() { return "Ember.Object"; };
- })();
- (function() {
- /**
- @module ember
- @submodule ember-runtime
- */
- var get = Ember.get, indexOf = Ember.ArrayPolyfills.indexOf;
- /**
- A Namespace is an object usually used to contain other objects or methods
- such as an application or framework. Create a namespace anytime you want
- to define one of these new containers.
- # Example Usage
- ```javascript
- MyFramework = Ember.Namespace.create({
- VERSION: '1.0.0'
- });
- ```
- @class Namespace
- @namespace Ember
- @extends Ember.Object
- */
- var Namespace = Ember.Namespace = Ember.Object.extend({
- isNamespace: true,
- init: function() {
- Ember.Namespace.NAMESPACES.push(this);
- Ember.Namespace.PROCESSED = false;
- },
- toString: function() {
- var name = get(this, 'name');
- if (name) { return name; }
- findNamespaces();
- return this[Ember.GUID_KEY+'_name'];
- },
- nameClasses: function() {
- processNamespace([this.toString()], this, {});
- },
- destroy: function() {
- var namespaces = Ember.Namespace.NAMESPACES;
- Ember.lookup[this.toString()] = undefined;
- namespaces.splice(indexOf.call(namespaces, this), 1);
- this._super();
- }
- });
- Namespace.reopenClass({
- NAMESPACES: [Ember],
- NAMESPACES_BY_ID: {},
- PROCESSED: false,
- processAll: processAllNamespaces,
- byName: function(name) {
- if (!Ember.BOOTED) {
- processAllNamespaces();
- }
- return NAMESPACES_BY_ID[name];
- }
- });
- var NAMESPACES_BY_ID = Namespace.NAMESPACES_BY_ID;
- var hasOwnProp = ({}).hasOwnProperty,
- guidFor = Ember.guidFor;
- function processNamespace(paths, root, seen) {
- var idx = paths.length;
- NAMESPACES_BY_ID[paths.join('.')] = root;
- // Loop over all of the keys in the namespace, looking for classes
- for(var key in root) {
- if (!hasOwnProp.call(root, key)) { continue; }
- var obj = root[key];
- // If we are processing the `Ember` namespace, for example, the
- // `paths` will start with `["Ember"]`. Every iteration through
- // the loop will update the **second** element of this list with
- // the key, so processing `Ember.View` will make the Array
- // `['Ember', 'View']`.
- paths[idx] = key;
- // If we have found an unprocessed class
- if (obj && obj.toString === classToString) {
- // Replace the class' `toString` with the dot-separated path
- // and set its `NAME_KEY`
- obj.toString = makeToString(paths.join('.'));
- obj[NAME_KEY] = paths.join('.');
- // Support nested namespaces
- } else if (obj && obj.isNamespace) {
- // Skip aliased namespaces
- if (seen[guidFor(obj)]) { continue; }
- seen[guidFor(obj)] = true;
- // Process the child namespace
- processNamespace(paths, obj, seen);
- }
- }
- paths.length = idx; // cut out last item
- }
- function findNamespaces() {
- var Namespace = Ember.Namespace, lookup = Ember.lookup, obj, isNamespace;
- if (Namespace.PROCESSED) { return; }
- for (var prop in lookup) {
- // These don't raise exceptions but can cause warnings
- if (prop === "parent" || prop === "top" || prop === "frameElement" || prop === "webkitStorageInfo") { continue; }
- // get(window.globalStorage, 'isNamespace') would try to read the storage for domain isNamespace and cause exception in Firefox.
- // globalStorage is a storage obsoleted by the WhatWG storage specification. See https://developer.mozilla.org/en/DOM/Storage#globalStorage
- if (prop === "globalStorage" && lookup.StorageList && lookup.globalStorage instanceof lookup.StorageList) { continue; }
- // Unfortunately, some versions of IE don't support window.hasOwnProperty
- if (lookup.hasOwnProperty && !lookup.hasOwnProperty(prop)) { continue; }
- // At times we are not allowed to access certain properties for security reasons.
- // There are also times where even if we can access them, we are not allowed to access their properties.
- try {
- obj = Ember.lookup[prop];
- isNamespace = obj && obj.isNamespace;
- } catch (e) {
- continue;
- }
- if (isNamespace) {
- Ember.deprecate("Namespaces should not begin with lowercase.", /^[A-Z]/.test(prop));
- obj[NAME_KEY] = prop;
- }
- }
- }
- var NAME_KEY = Ember.NAME_KEY = Ember.GUID_KEY + '_name';
- function superClassString(mixin) {
- var superclass = mixin.superclass;
- if (superclass) {
- if (superclass[NAME_KEY]) { return superclass[NAME_KEY]; }
- else { return superClassString(superclass); }
- } else {
- return;
- }
- }
- function classToString() {
- if (!Ember.BOOTED && !this[NAME_KEY]) {
- processAllNamespaces();
- }
- var ret;
- if (this[NAME_KEY]) {
- ret = this[NAME_KEY];
- } else if (this._toString) {
- ret = this._toString;
- } else {
- var str = superClassString(this);
- if (str) {
- ret = "(subclass of " + str + ")";
- } else {
- ret = "(unknown mixin)";
- }
- this.toString = makeToString(ret);
- }
- return ret;
- }
- function processAllNamespaces() {
- var unprocessedNamespaces = !Namespace.PROCESSED,
- unprocessedMixins = Ember.anyUnprocessedMixins;
- if (unprocessedNamespaces) {
- findNamespaces();
- Namespace.PROCESSED = true;
- }
- if (unprocessedNamespaces || unprocessedMixins) {
- var namespaces = Namespace.NAMESPACES, namespace;
- for (var i=0, l=namespaces.length; i<l; i++) {
- namespace = namespaces[i];
- processNamespace([namespace.toString()], namespace, {});
- }
- Ember.anyUnprocessedMixins = false;
- }
- }
- function makeToString(ret) {
- return function() { return ret; };
- }
- Ember.Mixin.prototype.toString = classToString;
- })();
- (function() {
- /**
- @module ember
- @submodule ember-runtime
- */
- var get = Ember.get,
- set = Ember.set,
- fmt = Ember.String.fmt,
- addBeforeObserver = Ember.addBeforeObserver,
- addObserver = Ember.addObserver,
- removeBeforeObserver = Ember.removeBeforeObserver,
- removeObserver = Ember.removeObserver,
- propertyWillChange = Ember.propertyWillChange,
- propertyDidChange = Ember.propertyDidChange,
- meta = Ember.meta,
- defineProperty = Ember.defineProperty;
- function contentPropertyWillChange(content, contentKey) {
- var key = contentKey.slice(8); // remove "content."
- if (key in this) { return; } // if shadowed in proxy
- propertyWillChange(this, key);
- }
- function contentPropertyDidChange(content, contentKey) {
- var key = contentKey.slice(8); // remove "content."
- if (key in this) { return; } // if shadowed in proxy
- propertyDidChange(this, key);
- }
- /**
- `Ember.ObjectProxy` forwards all properties not defined by the proxy itself
- to a proxied `content` object.
- ```javascript
- object = Ember.Object.create({
- name: 'Foo'
- });
- proxy = Ember.ObjectProxy.create({
- content: object
- });
- // Access and change existing properties
- proxy.get('name') // 'Foo'
- proxy.set('name', 'Bar');
- object.get('name') // 'Bar'
- // Create new 'description' property on `object`
- proxy.set('description', 'Foo is a whizboo baz');
- object.get('description') // 'Foo is a whizboo baz'
- ```
- While `content` is unset, setting a property to be delegated will throw an
- Error.
- ```javascript
- proxy = Ember.ObjectProxy.create({
- content: null,
- flag: null
- });
- proxy.set('flag', true);
- proxy.get('flag'); // true
- proxy.get('foo'); // undefined
- proxy.set('foo', 'data'); // throws Error
- ```
- Delegated properties can be bound to and will change when content is updated.
- Computed properties on the proxy itself can depend on delegated properties.
- ```javascript
- ProxyWithComputedProperty = Ember.ObjectProxy.extend({
- fullName: function () {
- var firstName = this.get('firstName'),
- lastName = this.get('lastName');
- if (firstName && lastName) {
- return firstName + ' ' + lastName;
- }
- return firstName || lastName;
- }.property('firstName', 'lastName')
- });
- proxy = ProxyWithComputedProperty.create();
- proxy.get('fullName'); // undefined
- proxy.set('content', {
- firstName: 'Tom', lastName: 'Dale'
- }); // triggers property change for fullName on proxy
- proxy.get('fullName'); // 'Tom Dale'
- ```
- @class ObjectProxy
- @namespace Ember
- @extends Ember.Object
- */
- Ember.ObjectProxy = Ember.Object.extend({
- /**
- The object whose properties will be forwarded.
- @property content
- @type Ember.Object
- @default null
- */
- content: null,
- _contentDidChange: Ember.observer('content', function() {
- Ember.assert("Can't set ObjectProxy's content to itself", this.get('content') !== this);
- }),
- isTruthy: Ember.computed.bool('content'),
- _debugContainerKey: null,
- willWatchProperty: function (key) {
- var contentKey = 'content.' + key;
- addBeforeObserver(this, contentKey, null, contentPropertyWillChange);
- addObserver(this, contentKey, null, contentPropertyDidChange);
- },
- didUnwatchProperty: function (key) {
- var contentKey = 'content.' + key;
- removeBeforeObserver(this, contentKey, null, contentPropertyWillChange);
- removeObserver(this, contentKey, null, contentPropertyDidChange);
- },
- unknownProperty: function (key) {
- var content = get(this, 'content');
- if (content) {
- return get(content, key);
- }
- },
- setUnknownProperty: function (key, value) {
- var m = meta(this);
- if (m.proto === this) {
- // if marked as prototype then just defineProperty
- // rather than delegate
- defineProperty(this, key, null, value);
- return value;
- }
- var content = get(this, 'content');
- Ember.assert(fmt("Cannot delegate set('%@', %@) to the 'content' property of object proxy %@: its 'content' is undefined.", [key, value, this]), content);
- return set(content, key, value);
- }
- });
- })();
- (function() {
- /**
- @module ember
- @submodule ember-runtime
- */
- // ..........................................................
- // HELPERS
- //
- var get = Ember.get, set = Ember.set;
- var a_slice = Array.prototype.slice;
- var a_indexOf = Ember.EnumerableUtils.indexOf;
- var contexts = [];
- function popCtx() {
- return contexts.length===0 ? {} : contexts.pop();
- }
- function pushCtx(ctx) {
- contexts.push(ctx);
- return null;
- }
- function iter(key, value) {
- var valueProvided = arguments.length === 2;
- function i(item) {
- var cur = get(item, key);
- return valueProvided ? value===cur : !!cur;
- }
- return i ;
- }
- /**
- This mixin defines the common interface implemented by enumerable objects
- in Ember. Most of these methods follow the standard Array iteration
- API defined up to JavaScript 1.8 (excluding language-specific features that
- cannot be emulated in older versions of JavaScript).
- This mixin is applied automatically to the Array class on page load, so you
- can use any of these methods on simple arrays. If Array already implements
- one of these methods, the mixin will not override them.
- ## Writing Your Own Enumerable
- To make your own custom class enumerable, you need two items:
- 1. You must have a length property. This property should change whenever
- the number of items in your enumerable object changes. If you use this
- with an `Ember.Object` subclass, you should be sure to change the length
- property using `set().`
- 2. You must implement `nextObject().` See documentation.
- Once you have these two methods implemented, apply the `Ember.Enumerable` mixin
- to your class and you will be able to enumerate the contents of your object
- like any other collection.
- ## Using Ember Enumeration with Other Libraries
- Many other libraries provide some kind of iterator or enumeration like
- facility. This is often where the most common API conflicts occur.
- Ember's API is designed to be as friendly as possible with other
- libraries by implementing only methods that mostly correspond to the
- JavaScript 1.8 API.
- @class Enumerable
- @namespace Ember
- @since Ember 0.9
- */
- Ember.Enumerable = Ember.Mixin.create({
- /**
- Implement this method to make your class enumerable.
- This method will be call repeatedly during enumeration. The index value
- will always begin with 0 and increment monotonically. You don't have to
- rely on the index value to determine what object to return, but you should
- always check the value and start from the beginning when you see the
- requested index is 0.
- The `previousObject` is the object that was returned from the last call
- to `nextObject` for the current iteration. This is a useful way to
- manage iteration if you are tracing a linked list, for example.
- Finally the context parameter will always contain a hash you can use as
- a "scratchpad" to maintain any other state you need in order to iterate
- properly. The context object is reused and is not reset between
- iterations so make sure you setup the context with a fresh state whenever
- the index parameter is 0.
- Generally iterators will continue to call `nextObject` until the index
- reaches the your current length-1. If you run out of data before this
- time for some reason, you should simply return undefined.
- The default implementation of this method simply looks up the index.
- This works great on any Array-like objects.
- @method nextObject
- @param {Number} index the current index of the iteration
- @param {Object} previousObject the value returned by the last call to
- `nextObject`.
- @param {Object} context a context object you can use to maintain state.
- @return {Object} the next object in the iteration or undefined
- */
- nextObject: Ember.required(Function),
- /**
- Helper method returns the first object from a collection. This is usually
- used by bindings and other parts of the framework to extract a single
- object if the enumerable contains only one item.
- If you override this method, you should implement it so that it will
- always return the same value each time it is called. If your enumerable
- contains only one object, this method should always return that object.
- If your enumerable is empty, this method should return `undefined`.
- ```javascript
- var arr = ["a", "b", "c"];
- arr.get('firstObject'); // "a"
- var arr = [];
- arr.get('firstObject'); // undefined
- ```
- @property firstObject
- @return {Object} the object or undefined
- */
- firstObject: Ember.computed(function() {
- if (get(this, 'length')===0) return undefined ;
- // handle generic enumerables
- var context = popCtx(), ret;
- ret = this.nextObject(0, null, context);
- pushCtx(context);
- return ret ;
- }).property('[]'),
- /**
- Helper method returns the last object from a collection. If your enumerable
- contains only one object, this method should always return that object.
- If your enumerable is empty, this method should return `undefined`.
- ```javascript
- var arr = ["a", "b", "c"];
- arr.get('lastObject'); // "c"
- var arr = [];
- arr.get('lastObject'); // undefined
- ```
- @property lastObject
- @return {Object} the last object or undefined
- */
- lastObject: Ember.computed(function() {
- var len = get(this, 'length');
- if (len===0) return undefined ;
- var context = popCtx(), idx=0, cur, last = null;
- do {
- last = cur;
- cur = this.nextObject(idx++, last, context);
- } while (cur !== undefined);
- pushCtx(context);
- return last;
- }).property('[]'),
- /**
- Returns `true` if the passed object can be found in the receiver. The
- default version will iterate through the enumerable until the object
- is found. You may want to override this with a more efficient version.
- ```javascript
- var arr = ["a", "b", "c"];
- arr.contains("a"); // true
- arr.contains("z"); // false
- ```
- @method contains
- @param {Object} obj The object to search for.
- @return {Boolean} `true` if object is found in enumerable.
- */
- contains: function(obj) {
- return this.find(function(item) { return item===obj; }) !== undefined;
- },
- /**
- Iterates through the enumerable, calling the passed function on each
- item. This method corresponds to the `forEach()` method defined in
- JavaScript 1.6.
- The callback method you provide should have the following signature (all
- parameters are optional):
- ```javascript
- function(item, index, enumerable);
- ```
- - `item` is the current item in the iteration.
- - `index` is the current index in the iteration.
- - `enumerable` is the enumerable object itself.
- Note that in addition to a callback, you can also pass an optional target
- object that will be set as `this` on the context. This is a good way
- to give your iterator function access to the current object.
- @method forEach
- @param {Function} callback The callback to execute
- @param {Object} [target] The target object to use
- @return {Object} receiver
- */
- forEach: function(callback, target) {
- if (typeof callback !== "function") throw new TypeError() ;
- var len = get(this, 'length'), last = null, context = popCtx();
- if (target === undefined) target = null;
- for(var idx=0;idx<len;idx++) {
- var next = this.nextObject(idx, last, context) ;
- callback.call(target, next, idx, this);
- last = next ;
- }
- last = null ;
- context = pushCtx(context);
- return this ;
- },
- /**
- Alias for `mapBy`
- @method getEach
- @param {String} key name of the property
- @return {Array} The mapped array.
- */
- getEach: function(key) {
- return this.mapBy(key);
- },
- /**
- Sets the value on the named property for each member. This is more
- efficient than using other methods defined on this helper. If the object
- implements Ember.Observable, the value will be changed to `set(),` otherwise
- it will be set directly. `null` objects are skipped.
- @method setEach
- @param {String} key The key to set
- @param {Object} value The object to set
- @return {Object} receiver
- */
- setEach: function(key, value) {
- return this.forEach(function(item) {
- set(item, key, value);
- });
- },
- /**
- Maps all of the items in the enumeration to another value, returning
- a new array. This method corresponds to `map()` defined in JavaScript 1.6.
- The callback method you provide should have the following signature (all
- parameters are optional):
- ```javascript
- function(item, index, enumerable);
- ```
- - `item` is the current item in the iteration.
- - `index` is the current index in the iteration.
- - `enumerable` is the enumerable object itself.
- It should return the mapped value.
- Note that in addition to a callback, you can also pass an optional target
- object that will be set as `this` on the context. This is a good way
- to give your iterator function access to the current object.
- @method map
- @param {Function} callback The callback to execute
- @param {Object} [target] The target object to use
- @return {Array} The mapped array.
- */
- map: function(callback, target) {
- var ret = Ember.A();
- this.forEach(function(x, idx, i) {
- ret[idx] = callback.call(target, x, idx,i);
- });
- return ret ;
- },
- /**
- Similar to map, this specialized function returns the value of the named
- property on all items in the enumeration.
- @method mapBy
- @param {String} key name of the property
- @return {Array} The mapped array.
- */
- mapBy: function(key) {
- return this.map(function(next) {
- return get(next, key);
- });
- },
- /**
- Similar to map, this specialized function returns the value of the named
- property on all items in the enumeration.
- @method mapProperty
- @param {String} key name of the property
- @return {Array} The mapped array.
- @deprecated Use `mapBy` instead
- */
- mapProperty: Ember.aliasMethod('mapBy'),
- /**
- Returns an array with all of the items in the enumeration that the passed
- function returns true for. This method corresponds to `filter()` defined in
- JavaScript 1.6.
- The callback method you provide should have the following signature (all
- parameters are optional):
- ```javascript
- function(item, index, enumerable);
- ```
- - `item` is the current item in the iteration.
- - `index` is the current index in the iteration.
- - `enumerable` is the enumerable object itself.
- It should return the `true` to include the item in the results, `false`
- otherwise.
- Note that in addition to a callback, you can also pass an optional target
- object that will be set as `this` on the context. This is a good way
- to give your iterator function access to the current object.
- @method filter
- @param {Function} callback The callback to execute
- @param {Object} [target] The target object to use
- @return {Array} A filtered array.
- */
- filter: function(callback, target) {
- var ret = Ember.A();
- this.forEach(function(x, idx, i) {
- if (callback.call(target, x, idx, i)) ret.push(x);
- });
- return ret ;
- },
- /**
- Returns an array with all of the items in the enumeration where the passed
- function returns false for. This method is the inverse of filter().
- The callback method you provide should have the following signature (all
- parameters are optional):
- ```javascript
- function(item, index, enumerable);
- ```
- - *item* is the current item in the iteration.
- - *index* is the current index in the iteration
- - *enumerable* is the enumerable object itself.
- It should return the a falsey value to include the item in the results.
- Note that in addition to a callback, you can also pass an optional target
- object that will be set as "this" on the context. This is a good way
- to give your iterator function access to the current object.
- @method reject
- @param {Function} callback The callback to execute
- @param {Object} [target] The target object to use
- @return {Array} A rejected array.
- */
- reject: function(callback, target) {
- return this.filter(function() {
- return !(callback.apply(target, arguments));
- });
- },
- /**
- Returns an array with just the items with the matched property. You
- can pass an optional second argument with the target value. Otherwise
- this will match any property that evaluates to `true`.
- @method filterBy
- @param {String} key the property to test
- @param {String} [value] optional value to test against.
- @return {Array} filtered array
- */
- filterBy: function(key, value) {
- return this.filter(iter.apply(this, arguments));
- },
- /**
- Returns an array with just the items with the matched property. You
- can pass an optional second argument with the target value. Otherwise
- this will match any property that evaluates to `true`.
- @method filterProperty
- @param {String} key the property to test
- @param {String} [value] optional value to test against.
- @return {Array} filtered array
- @deprecated Use `filterBy` instead
- */
- filterProperty: Ember.aliasMethod('filterBy'),
- /**
- Returns an array with the items that do not have truthy values for
- key. You can pass an optional second argument with the target value. Otherwise
- this will match any property that evaluates to false.
- @method rejectBy
- @param {String} key the property to test
- @param {String} [value] optional value to test against.
- @return {Array} rejected array
- */
- rejectBy: function(key, value) {
- var exactValue = function(item) { return get(item, key) === value; },
- hasValue = function(item) { return !!get(item, key); },
- use = (arguments.length === 2 ? exactValue : hasValue);
- return this.reject(use);
- },
- /**
- Returns an array with the items that do not have truthy values for
- key. You can pass an optional second argument with the target value. Otherwise
- this will match any property that evaluates to false.
- @method rejectProperty
- @param {String} key the property to test
- @param {String} [value] optional value to test against.
- @return {Array} rejected array
- @deprecated Use `rejectBy` instead
- */
- rejectProperty: Ember.aliasMethod('rejectBy'),
- /**
- Returns the first item in the array for which the callback returns true.
- This method works similar to the `filter()` method defined in JavaScript 1.6
- except that it will stop working on the array once a match is found.
- The callback method you provide should have the following signature (all
- parameters are optional):
- ```javascript
- function(item, index, enumerable);
- ```
- - `item` is the current item in the iteration.
- - `index` is the current index in the iteration.
- - `enumerable` is the enumerable object itself.
- It should return the `true` to include the item in the results, `false`
- otherwise.
- Note that in addition to a callback, you can also pass an optional target
- object that will be set as `this` on the context. This is a good way
- to give your iterator function access to the current object.
- @method find
- @param {Function} callback The callback to execute
- @param {Object} [target] The target object to use
- @return {Object} Found item or `undefined`.
- */
- find: function(callback, target) {
- var len = get(this, 'length') ;
- if (target === undefined) target = null;
- var last = null, next, found = false, ret ;
- var context = popCtx();
- for(var idx=0;idx<len && !found;idx++) {
- next = this.nextObject(idx, last, context) ;
- if (found = callback.call(target, next, idx, this)) ret = next ;
- last = next ;
- }
- next = last = null ;
- context = pushCtx(context);
- return ret ;
- },
- /**
- Returns the first item with a property matching the passed value. You
- can pass an optional second argument with the target value. Otherwise
- this will match any property that evaluates to `true`.
- This method works much like the more generic `find()` method.
- @method findBy
- @param {String} key the property to test
- @param {String} [value] optional value to test against.
- @return {Object} found item or `undefined`
- */
- findBy: function(key, value) {
- return this.find(iter.apply(this, arguments));
- },
- /**
- Returns the first item with a property matching the passed value. You
- can pass an optional second argument with the target value. Otherwise
- this will match any property that evaluates to `true`.
- This method works much like the more generic `find()` method.
- @method findProperty
- @param {String} key the property to test
- @param {String} [value] optional value to test against.
- @return {Object} found item or `undefined`
- @deprecated Use `findBy` instead
- */
- findProperty: Ember.aliasMethod('findBy'),
- /**
- Returns `true` if the passed function returns true for every item in the
- enumeration. This corresponds with the `every()` method in JavaScript 1.6.
- The callback method you provide should have the following signature (all
- parameters are optional):
- ```javascript
- function(item, index, enumerable);
- ```
- - `item` is the current item in the iteration.
- - `index` is the current index in the iteration.
- - `enumerable` is the enumerable object itself.
- It should return the `true` or `false`.
- Note that in addition to a callback, you can also pass an optional target
- object that will be set as `this` on the context. This is a good way
- to give your iterator function access to the current object.
- Example Usage:
- ```javascript
- if (people.every(isEngineer)) { Paychecks.addBigBonus(); }
- ```
- @method every
- @param {Function} callback The callback to execute
- @param {Object} [target] The target object to use
- @return {Boolean}
- */
- every: function(callback, target) {
- return !this.find(function(x, idx, i) {
- return !callback.call(target, x, idx, i);
- });
- },
- /**
- @method everyBy
- @param {String} key the property to test
- @param {String} [value] optional value to test against.
- @deprecated Use `isEvery` instead
- @return {Boolean}
- */
- everyBy: Ember.aliasMethod('isEvery'),
- /**
- @method everyProperty
- @param {String} key the property to test
- @param {String} [value] optional value to test against.
- @deprecated Use `isEvery` instead
- @return {Boolean}
- */
- everyProperty: Ember.aliasMethod('isEvery'),
- /**
- Returns `true` if the passed property resolves to `true` for all items in
- the enumerable. This method is often simpler/faster than using a callback.
- @method isEvery
- @param {String} key the property to test
- @param {String} [value] optional value to test against.
- @return {Boolean}
- */
- isEvery: function(key, value) {
- return this.every(iter.apply(this, arguments));
- },
- /**
- Returns `true` if the passed function returns true for any item in the
- enumeration. This corresponds with the `some()` method in JavaScript 1.6.
- The callback method you provide should have the following signature (all
- parameters are optional):
- ```javascript
- function(item, index, enumerable);
- ```
- - `item` is the current item in the iteration.
- - `index` is the current index in the iteration.
- - `enumerable` is the enumerable object itself.
- It should return the `true` to include the item in the results, `false`
- otherwise.
- Note that in addition to a callback, you can also pass an optional target
- object that will be set as `this` on the context. This is a good way
- to give your iterator function access to the current object.
- Usage Example:
- ```javascript
- if (people.any(isManager)) { Paychecks.addBiggerBonus(); }
- ```
- @method any
- @param {Function} callback The callback to execute
- @param {Object} [target] The target object to use
- @return {Boolean} `true` if the passed function returns `true` for any item
- */
- any: function(callback, target) {
- var len = get(this, 'length'),
- context = popCtx(),
- found = false,
- last = null,
- next, idx;
- if (target === undefined) { target = null; }
- for (idx = 0; idx < len && !found; idx++) {
- next = this.nextObject(idx, last, context);
- found = callback.call(target, next, idx, this);
- last = next;
- }
- next = last = null;
- context = pushCtx(context);
- return found;
- },
- /**
- Returns `true` if the passed function returns true for any item in the
- enumeration. This corresponds with the `some()` method in JavaScript 1.6.
- The callback method you provide should have the following signature (all
- parameters are optional):
- ```javascript
- function(item, index, enumerable);
- ```
- - `item` is the current item in the iteration.
- - `index` is the current index in the iteration.
- - `enumerable` is the enumerable object itself.
- It should return the `true` to include the item in the results, `false`
- otherwise.
- Note that in addition to a callback, you can also pass an optional target
- object that will be set as `this` on the context. This is a good way
- to give your iterator function access to the current object.
- Usage Example:
- ```javascript
- if (people.some(isManager)) { Paychecks.addBiggerBonus(); }
- ```
- @method some
- @param {Function} callback The callback to execute
- @param {Object} [target] The target object to use
- @return {Boolean} `true` if the passed function returns `true` for any item
- @deprecated Use `any` instead
- */
- some: Ember.aliasMethod('any'),
- /**
- Returns `true` if the passed property resolves to `true` for any item in
- the enumerable. This method is often simpler/faster than using a callback.
- @method isAny
- @param {String} key the property to test
- @param {String} [value] optional value to test against.
- @return {Boolean} `true` if the passed function returns `true` for any item
- */
- isAny: function(key, value) {
- return this.any(iter.apply(this, arguments));
- },
- /**
- @method anyBy
- @param {String} key the property to test
- @param {String} [value] optional value to test against.
- @return {Boolean} `true` if the passed function returns `true` for any item
- @deprecated Use `isAny` instead
- */
- anyBy: Ember.aliasMethod('isAny'),
- /**
- @method someProperty
- @param {String} key the property to test
- @param {String} [value] optional value to test against.
- @return {Boolean} `true` if the passed function returns `true` for any item
- @deprecated Use `isAny` instead
- */
- someProperty: Ember.aliasMethod('isAny'),
- /**
- This will combine the values of the enumerator into a single value. It
- is a useful way to collect a summary value from an enumeration. This
- corresponds to the `reduce()` method defined in JavaScript 1.8.
- The callback method you provide should have the following signature (all
- parameters are optional):
- ```javascript
- function(previousValue, item, index, enumerable);
- ```
- - `previousValue` is the value returned by the last call to the iterator.
- - `item` is the current item in the iteration.
- - `index` is the current index in the iteration.
- - `enumerable` is the enumerable object itself.
- Return the new cumulative value.
- In addition to the callback you can also pass an `initialValue`. An error
- will be raised if you do not pass an initial value and the enumerator is
- empty.
- Note that unlike the other methods, this method does not allow you to
- pass a target object to set as this for the callback. It's part of the
- spec. Sorry.
- @method reduce
- @param {Function} callback The callback to execute
- @param {Object} initialValue Initial value for the reduce
- @param {String} reducerProperty internal use only.
- @return {Object} The reduced value.
- */
- reduce: function(callback, initialValue, reducerProperty) {
- if (typeof callback !== "function") { throw new TypeError(); }
- var ret = initialValue;
- this.forEach(function(item, i) {
- ret = callback(ret, item, i, this, reducerProperty);
- }, this);
- return ret;
- },
- /**
- Invokes the named method on every object in the receiver that
- implements it. This method corresponds to the implementation in
- Prototype 1.6.
- @method invoke
- @param {String} methodName the name of the method
- @param {Object...} args optional arguments to pass as well.
- @return {Array} return values from calling invoke.
- */
- invoke: function(methodName) {
- var args, ret = Ember.A();
- if (arguments.length>1) args = a_slice.call(arguments, 1);
- this.forEach(function(x, idx) {
- var method = x && x[methodName];
- if ('function' === typeof method) {
- ret[idx] = args ? method.apply(x, args) : x[methodName]();
- }
- }, this);
- return ret;
- },
- /**
- Simply converts the enumerable into a genuine array. The order is not
- guaranteed. Corresponds to the method implemented by Prototype.
- @method toArray
- @return {Array} the enumerable as an array.
- */
- toArray: function() {
- var ret = Ember.A();
- this.forEach(function(o, idx) { ret[idx] = o; });
- return ret ;
- },
- /**
- Returns a copy of the array with all null and undefined elements removed.
- ```javascript
- var arr = ["a", null, "c", undefined];
- arr.compact(); // ["a", "c"]
- ```
- @method compact
- @return {Array} the array without null and undefined elements.
- */
- compact: function() {
- return this.filter(function(value) { return value != null; });
- },
- /**
- Returns a new enumerable that excludes the passed value. The default
- implementation returns an array regardless of the receiver type unless
- the receiver does not contain the value.
- ```javascript
- var arr = ["a", "b", "a", "c"];
- arr.without("a"); // ["b", "c"]
- ```
- @method without
- @param {Object} value
- @return {Ember.Enumerable}
- */
- without: function(value) {
- if (!this.contains(value)) return this; // nothing to do
- var ret = Ember.A();
- this.forEach(function(k) {
- if (k !== value) ret[ret.length] = k;
- }) ;
- return ret ;
- },
- /**
- Returns a new enumerable that contains only unique values. The default
- implementation returns an array regardless of the receiver type.
- ```javascript
- var arr = ["a", "a", "b", "b"];
- arr.uniq(); // ["a", "b"]
- ```
- @method uniq
- @return {Ember.Enumerable}
- */
- uniq: function() {
- var ret = Ember.A();
- this.forEach(function(k) {
- if (a_indexOf(ret, k)<0) ret.push(k);
- });
- return ret;
- },
- /**
- This property will trigger anytime the enumerable's content changes.
- You can observe this property to be notified of changes to the enumerables
- content.
- For plain enumerables, this property is read only. `Ember.Array` overrides
- this method.
- @property []
- @type Ember.Array
- @return this
- */
- '[]': Ember.computed(function(key, value) {
- return this;
- }),
- // ..........................................................
- // ENUMERABLE OBSERVERS
- //
- /**
- Registers an enumerable observer. Must implement `Ember.EnumerableObserver`
- mixin.
- @method addEnumerableObserver
- @param {Object} target
- @param {Hash} [opts]
- @return this
- */
- addEnumerableObserver: function(target, opts) {
- var willChange = (opts && opts.willChange) || 'enumerableWillChange',
- didChange = (opts && opts.didChange) || 'enumerableDidChange';
- var hasObservers = get(this, 'hasEnumerableObservers');
- if (!hasObservers) Ember.propertyWillChange(this, 'hasEnumerableObservers');
- Ember.addListener(this, '@enumerable:before', target, willChange);
- Ember.addListener(this, '@enumerable:change', target, didChange);
- if (!hasObservers) Ember.propertyDidChange(this, 'hasEnumerableObservers');
- return this;
- },
- /**
- Removes a registered enumerable observer.
- @method removeEnumerableObserver
- @param {Object} target
- @param {Hash} [opts]
- @return this
- */
- removeEnumerableObserver: function(target, opts) {
- var willChange = (opts && opts.willChange) || 'enumerableWillChange',
- didChange = (opts && opts.didChange) || 'enumerableDidChange';
- var hasObservers = get(this, 'hasEnumerableObservers');
- if (hasObservers) Ember.propertyWillChange(this, 'hasEnumerableObservers');
- Ember.removeListener(this, '@enumerable:before', target, willChange);
- Ember.removeListener(this, '@enumerable:change', target, didChange);
- if (hasObservers) Ember.propertyDidChange(this, 'hasEnumerableObservers');
- return this;
- },
- /**
- Becomes true whenever the array currently has observers watching changes
- on the array.
- @property hasEnumerableObservers
- @type Boolean
- */
- hasEnumerableObservers: Ember.computed(function() {
- return Ember.hasListeners(this, '@enumerable:change') || Ember.hasListeners(this, '@enumerable:before');
- }),
- /**
- Invoke this method just before the contents of your enumerable will
- change. You can either omit the parameters completely or pass the objects
- to be removed or added if available or just a count.
- @method enumerableContentWillChange
- @param {Ember.Enumerable|Number} removing An enumerable of the objects to
- be removed or the number of items to be removed.
- @param {Ember.Enumerable|Number} adding An enumerable of the objects to be
- added or the number of items to be added.
- @chainable
- */
- enumerableContentWillChange: function(removing, adding) {
- var removeCnt, addCnt, hasDelta;
- if ('number' === typeof removing) removeCnt = removing;
- else if (removing) removeCnt = get(removing, 'length');
- else removeCnt = removing = -1;
- if ('number' === typeof adding) addCnt = adding;
- else if (adding) addCnt = get(adding,'length');
- else addCnt = adding = -1;
- hasDelta = addCnt<0 || removeCnt<0 || addCnt-removeCnt!==0;
- if (removing === -1) removing = null;
- if (adding === -1) adding = null;
- Ember.propertyWillChange(this, '[]');
- if (hasDelta) Ember.propertyWillChange(this, 'length');
- Ember.sendEvent(this, '@enumerable:before', [this, removing, adding]);
- return this;
- },
- /**
- Invoke this method when the contents of your enumerable has changed.
- This will notify any observers watching for content changes. If your are
- implementing an ordered enumerable (such as an array), also pass the
- start and end values where the content changed so that it can be used to
- notify range observers.
- @method enumerableContentDidChange
- @param {Ember.Enumerable|Number} removing An enumerable of the objects to
- be removed or the number of items to be removed.
- @param {Ember.Enumerable|Number} adding An enumerable of the objects to
- be added or the number of items to be added.
- @chainable
- */
- enumerableContentDidChange: function(removing, adding) {
- var removeCnt, addCnt, hasDelta;
- if ('number' === typeof removing) removeCnt = removing;
- else if (removing) removeCnt = get(removing, 'length');
- else removeCnt = removing = -1;
- if ('number' === typeof adding) addCnt = adding;
- else if (adding) addCnt = get(adding, 'length');
- else addCnt = adding = -1;
- hasDelta = addCnt<0 || removeCnt<0 || addCnt-removeCnt!==0;
- if (removing === -1) removing = null;
- if (adding === -1) adding = null;
- Ember.sendEvent(this, '@enumerable:change', [this, removing, adding]);
- if (hasDelta) Ember.propertyDidChange(this, 'length');
- Ember.propertyDidChange(this, '[]');
- return this ;
- },
- /**
- Converts the enumerable into an array and sorts by the keys
- specified in the argument.
- You may provide multiple arguments to sort by multiple properties.
- @method sortBy
- @param {String} property name(s) to sort on
- @return {Array} The sorted array.
- */
- sortBy: function() {
- var sortKeys = arguments;
- return this.toArray().sort(function(a, b){
- for(var i = 0; i < sortKeys.length; i++) {
- var key = sortKeys[i],
- propA = get(a, key),
- propB = get(b, key);
- // return 1 or -1 else continue to the next sortKey
- var compareValue = Ember.compare(propA, propB);
- if (compareValue) { return compareValue; }
- }
- return 0;
- });
- }
- });
- })();
- (function() {
- /**
- @module ember
- @submodule ember-runtime
- */
- // ..........................................................
- // HELPERS
- //
- var get = Ember.get, set = Ember.set, isNone = Ember.isNone, map = Ember.EnumerableUtils.map, cacheFor = Ember.cacheFor;
- // ..........................................................
- // ARRAY
- //
- /**
- This module implements Observer-friendly Array-like behavior. This mixin is
- picked up by the Array class as well as other controllers, etc. that want to
- appear to be arrays.
- Unlike `Ember.Enumerable,` this mixin defines methods specifically for
- collections that provide index-ordered access to their contents. When you
- are designing code that needs to accept any kind of Array-like object, you
- should use these methods instead of Array primitives because these will
- properly notify observers of changes to the array.
- Although these methods are efficient, they do add a layer of indirection to
- your application so it is a good idea to use them only when you need the
- flexibility of using both true JavaScript arrays and "virtual" arrays such
- as controllers and collections.
- You can use the methods defined in this module to access and modify array
- contents in a KVO-friendly way. You can also be notified whenever the
- membership of an array changes by changing the syntax of the property to
- `.observes('*myProperty.[]')`.
- To support `Ember.Array` in your own class, you must override two
- primitives to use it: `replace()` and `objectAt()`.
- Note that the Ember.Array mixin also incorporates the `Ember.Enumerable`
- mixin. All `Ember.Array`-like objects are also enumerable.
- @class Array
- @namespace Ember
- @uses Ember.Enumerable
- @since Ember 0.9.0
- */
- Ember.Array = Ember.Mixin.create(Ember.Enumerable, {
- /**
- Your array must support the `length` property. Your replace methods should
- set this property whenever it changes.
- @property {Number} length
- */
- length: Ember.required(),
- /**
- Returns the object at the given `index`. If the given `index` is negative
- or is greater or equal than the array length, returns `undefined`.
- This is one of the primitives you must implement to support `Ember.Array`.
- If your object supports retrieving the value of an array item using `get()`
- (i.e. `myArray.get(0)`), then you do not need to implement this method
- yourself.
- ```javascript
- var arr = ['a', 'b', 'c', 'd'];
- arr.objectAt(0); // "a"
- arr.objectAt(3); // "d"
- arr.objectAt(-1); // undefined
- arr.objectAt(4); // undefined
- arr.objectAt(5); // undefined
- ```
- @method objectAt
- @param {Number} idx The index of the item to return.
- @return {*} item at index or undefined
- */
- objectAt: function(idx) {
- if ((idx < 0) || (idx>=get(this, 'length'))) return undefined ;
- return get(this, idx);
- },
- /**
- This returns the objects at the specified indexes, using `objectAt`.
- ```javascript
- var arr = ['a', 'b', 'c', 'd'];
- arr.objectsAt([0, 1, 2]); // ["a", "b", "c"]
- arr.objectsAt([2, 3, 4]); // ["c", "d", undefined]
- ```
- @method objectsAt
- @param {Array} indexes An array of indexes of items to return.
- @return {Array}
- */
- objectsAt: function(indexes) {
- var self = this;
- return map(indexes, function(idx) { return self.objectAt(idx); });
- },
- // overrides Ember.Enumerable version
- nextObject: function(idx) {
- return this.objectAt(idx);
- },
- /**
- This is the handler for the special array content property. If you get
- this property, it will return this. If you set this property it a new
- array, it will replace the current content.
- This property overrides the default property defined in `Ember.Enumerable`.
- @property []
- @return this
- */
- '[]': Ember.computed(function(key, value) {
- if (value !== undefined) this.replace(0, get(this, 'length'), value) ;
- return this ;
- }),
- firstObject: Ember.computed(function() {
- return this.objectAt(0);
- }),
- lastObject: Ember.computed(function() {
- return this.objectAt(get(this, 'length')-1);
- }),
- // optimized version from Enumerable
- contains: function(obj) {
- return this.indexOf(obj) >= 0;
- },
- // Add any extra methods to Ember.Array that are native to the built-in Array.
- /**
- Returns a new array that is a slice of the receiver. This implementation
- uses the observable array methods to retrieve the objects for the new
- slice.
- ```javascript
- var arr = ['red', 'green', 'blue'];
- arr.slice(0); // ['red', 'green', 'blue']
- arr.slice(0, 2); // ['red', 'green']
- arr.slice(1, 100); // ['green', 'blue']
- ```
- @method slice
- @param {Integer} beginIndex (Optional) index to begin slicing from.
- @param {Integer} endIndex (Optional) index to end the slice at (but not included).
- @return {Array} New array with specified slice
- */
- slice: function(beginIndex, endIndex) {
- var ret = Ember.A();
- var length = get(this, 'length') ;
- if (isNone(beginIndex)) beginIndex = 0 ;
- if (isNone(endIndex) || (endIndex > length)) endIndex = length ;
- if (beginIndex < 0) beginIndex = length + beginIndex;
- if (endIndex < 0) endIndex = length + endIndex;
- while(beginIndex < endIndex) {
- ret[ret.length] = this.objectAt(beginIndex++) ;
- }
- return ret ;
- },
- /**
- Returns the index of the given object's first occurrence.
- If no `startAt` argument is given, the starting location to
- search is 0. If it's negative, will count backward from
- the end of the array. Returns -1 if no match is found.
- ```javascript
- var arr = ["a", "b", "c", "d", "a"];
- arr.indexOf("a"); // 0
- arr.indexOf("z"); // -1
- arr.indexOf("a", 2); // 4
- arr.indexOf("a", -1); // 4
- arr.indexOf("b", 3); // -1
- arr.indexOf("a", 100); // -1
- ```
- @method indexOf
- @param {Object} object the item to search for
- @param {Number} startAt optional starting location to search, default 0
- @return {Number} index or -1 if not found
- */
- indexOf: function(object, startAt) {
- var idx, len = get(this, 'length');
- if (startAt === undefined) startAt = 0;
- if (startAt < 0) startAt += len;
- for(idx=startAt;idx<len;idx++) {
- if (this.objectAt(idx) === object) return idx ;
- }
- return -1;
- },
- /**
- Returns the index of the given object's last occurrence.
- If no `startAt` argument is given, the search starts from
- the last position. If it's negative, will count backward
- from the end of the array. Returns -1 if no match is found.
- ```javascript
- var arr = ["a", "b", "c", "d", "a"];
- arr.lastIndexOf("a"); // 4
- arr.lastIndexOf("z"); // -1
- arr.lastIndexOf("a", 2); // 0
- arr.lastIndexOf("a", -1); // 4
- arr.lastIndexOf("b", 3); // 1
- arr.lastIndexOf("a", 100); // 4
- ```
- @method lastIndexOf
- @param {Object} object the item to search for
- @param {Number} startAt optional starting location to search, default 0
- @return {Number} index or -1 if not found
- */
- lastIndexOf: function(object, startAt) {
- var idx, len = get(this, 'length');
- if (startAt === undefined || startAt >= len) startAt = len-1;
- if (startAt < 0) startAt += len;
- for(idx=startAt;idx>=0;idx--) {
- if (this.objectAt(idx) === object) return idx ;
- }
- return -1;
- },
- // ..........................................................
- // ARRAY OBSERVERS
- //
- /**
- Adds an array observer to the receiving array. The array observer object
- normally must implement two methods:
- * `arrayWillChange(observedObj, start, removeCount, addCount)` - This method will be
- called just before the array is modified.
- * `arrayDidChange(observedObj, start, removeCount, addCount)` - This method will be
- called just after the array is modified.
- Both callbacks will be passed the observed object, starting index of the
- change as well a a count of the items to be removed and added. You can use
- these callbacks to optionally inspect the array during the change, clear
- caches, or do any other bookkeeping necessary.
- In addition to passing a target, you can also include an options hash
- which you can use to override the method names that will be invoked on the
- target.
- @method addArrayObserver
- @param {Object} target The observer object.
- @param {Hash} opts Optional hash of configuration options including
- `willChange` and `didChange` option.
- @return {Ember.Array} receiver
- */
- addArrayObserver: function(target, opts) {
- var willChange = (opts && opts.willChange) || 'arrayWillChange',
- didChange = (opts && opts.didChange) || 'arrayDidChange';
- var hasObservers = get(this, 'hasArrayObservers');
- if (!hasObservers) Ember.propertyWillChange(this, 'hasArrayObservers');
- Ember.addListener(this, '@array:before', target, willChange);
- Ember.addListener(this, '@array:change', target, didChange);
- if (!hasObservers) Ember.propertyDidChange(this, 'hasArrayObservers');
- return this;
- },
- /**
- Removes an array observer from the object if the observer is current
- registered. Calling this method multiple times with the same object will
- have no effect.
- @method removeArrayObserver
- @param {Object} target The object observing the array.
- @param {Hash} opts Optional hash of configuration options including
- `willChange` and `didChange` option.
- @return {Ember.Array} receiver
- */
- removeArrayObserver: function(target, opts) {
- var willChange = (opts && opts.willChange) || 'arrayWillChange',
- didChange = (opts && opts.didChange) || 'arrayDidChange';
- var hasObservers = get(this, 'hasArrayObservers');
- if (hasObservers) Ember.propertyWillChange(this, 'hasArrayObservers');
- Ember.removeListener(this, '@array:before', target, willChange);
- Ember.removeListener(this, '@array:change', target, didChange);
- if (hasObservers) Ember.propertyDidChange(this, 'hasArrayObservers');
- return this;
- },
- /**
- Becomes true whenever the array currently has observers watching changes
- on the array.
- @property {Boolean} hasArrayObservers
- */
- hasArrayObservers: Ember.computed(function() {
- return Ember.hasListeners(this, '@array:change') || Ember.hasListeners(this, '@array:before');
- }),
- /**
- If you are implementing an object that supports `Ember.Array`, call this
- method just before the array content changes to notify any observers and
- invalidate any related properties. Pass the starting index of the change
- as well as a delta of the amounts to change.
- @method arrayContentWillChange
- @param {Number} startIdx The starting index in the array that will change.
- @param {Number} removeAmt The number of items that will be removed. If you
- pass `null` assumes 0
- @param {Number} addAmt The number of items that will be added. If you
- pass `null` assumes 0.
- @return {Ember.Array} receiver
- */
- arrayContentWillChange: function(startIdx, removeAmt, addAmt) {
- // if no args are passed assume everything changes
- if (startIdx===undefined) {
- startIdx = 0;
- removeAmt = addAmt = -1;
- } else {
- if (removeAmt === undefined) removeAmt=-1;
- if (addAmt === undefined) addAmt=-1;
- }
- // Make sure the @each proxy is set up if anyone is observing @each
- if (Ember.isWatching(this, '@each')) { get(this, '@each'); }
- Ember.sendEvent(this, '@array:before', [this, startIdx, removeAmt, addAmt]);
- var removing, lim;
- if (startIdx>=0 && removeAmt>=0 && get(this, 'hasEnumerableObservers')) {
- removing = [];
- lim = startIdx+removeAmt;
- for(var idx=startIdx;idx<lim;idx++) removing.push(this.objectAt(idx));
- } else {
- removing = removeAmt;
- }
- this.enumerableContentWillChange(removing, addAmt);
- return this;
- },
- /**
- If you are implementing an object that supports `Ember.Array`, call this
- method just after the array content changes to notify any observers and
- invalidate any related properties. Pass the starting index of the change
- as well as a delta of the amounts to change.
- @method arrayContentDidChange
- @param {Number} startIdx The starting index in the array that did change.
- @param {Number} removeAmt The number of items that were removed. If you
- pass `null` assumes 0
- @param {Number} addAmt The number of items that were added. If you
- pass `null` assumes 0.
- @return {Ember.Array} receiver
- */
- arrayContentDidChange: function(startIdx, removeAmt, addAmt) {
- // if no args are passed assume everything changes
- if (startIdx===undefined) {
- startIdx = 0;
- removeAmt = addAmt = -1;
- } else {
- if (removeAmt === undefined) removeAmt=-1;
- if (addAmt === undefined) addAmt=-1;
- }
- var adding, lim;
- if (startIdx>=0 && addAmt>=0 && get(this, 'hasEnumerableObservers')) {
- adding = [];
- lim = startIdx+addAmt;
- for(var idx=startIdx;idx<lim;idx++) adding.push(this.objectAt(idx));
- } else {
- adding = addAmt;
- }
- this.enumerableContentDidChange(removeAmt, adding);
- Ember.sendEvent(this, '@array:change', [this, startIdx, removeAmt, addAmt]);
- var length = get(this, 'length'),
- cachedFirst = cacheFor(this, 'firstObject'),
- cachedLast = cacheFor(this, 'lastObject');
- if (this.objectAt(0) !== cachedFirst) {
- Ember.propertyWillChange(this, 'firstObject');
- Ember.propertyDidChange(this, 'firstObject');
- }
- if (this.objectAt(length-1) !== cachedLast) {
- Ember.propertyWillChange(this, 'lastObject');
- Ember.propertyDidChange(this, 'lastObject');
- }
- return this;
- },
- // ..........................................................
- // ENUMERATED PROPERTIES
- //
- /**
- Returns a special object that can be used to observe individual properties
- on the array. Just get an equivalent property on this object and it will
- return an enumerable that maps automatically to the named key on the
- member objects.
- If you merely want to watch for any items being added or removed to the array,
- use the `[]` property instead of `@each`.
- @property @each
- */
- '@each': Ember.computed(function() {
- if (!this.__each) this.__each = new Ember.EachProxy(this);
- return this.__each;
- })
- }) ;
- })();
- (function() {
- var e_get = Ember.get,
- set = Ember.set,
- guidFor = Ember.guidFor,
- metaFor = Ember.meta,
- propertyWillChange = Ember.propertyWillChange,
- propertyDidChange = Ember.propertyDidChange,
- addBeforeObserver = Ember.addBeforeObserver,
- removeBeforeObserver = Ember.removeBeforeObserver,
- addObserver = Ember.addObserver,
- removeObserver = Ember.removeObserver,
- ComputedProperty = Ember.ComputedProperty,
- a_slice = [].slice,
- o_create = Ember.create,
- forEach = Ember.EnumerableUtils.forEach,
- // Here we explicitly don't allow `@each.foo`; it would require some special
- // testing, but there's no particular reason why it should be disallowed.
- eachPropertyPattern = /^(.*)\.@each\.(.*)/,
- doubleEachPropertyPattern = /(.*\.@each){2,}/,
- arrayBracketPattern = /\.\[\]$/;
- var expandProperties = Ember.expandProperties;
- function get(obj, key) {
- if (key === '@this') {
- return obj;
- }
- return e_get(obj, key);
- }
- /*
- Tracks changes to dependent arrays, as well as to properties of items in
- dependent arrays.
- @class DependentArraysObserver
- */
- function DependentArraysObserver(callbacks, cp, instanceMeta, context, propertyName, sugarMeta) {
- // user specified callbacks for `addedItem` and `removedItem`
- this.callbacks = callbacks;
- // the computed property: remember these are shared across instances
- this.cp = cp;
- // the ReduceComputedPropertyInstanceMeta this DependentArraysObserver is
- // associated with
- this.instanceMeta = instanceMeta;
- // A map of array guids to dependentKeys, for the given context. We track
- // this because we want to set up the computed property potentially before the
- // dependent array even exists, but when the array observer fires, we lack
- // enough context to know what to update: we can recover that context by
- // getting the dependentKey.
- this.dependentKeysByGuid = {};
- // a map of dependent array guids -> Ember.TrackedArray instances. We use
- // this to lazily recompute indexes for item property observers.
- this.trackedArraysByGuid = {};
- // We suspend observers to ignore replacements from `reset` when totally
- // recomputing. Unfortunately we cannot properly suspend the observers
- // because we only have the key; instead we make the observers no-ops
- this.suspended = false;
- // This is used to coalesce item changes from property observers.
- this.changedItems = {};
- }
- function ItemPropertyObserverContext (dependentArray, index, trackedArray) {
- Ember.assert("Internal error: trackedArray is null or undefined", trackedArray);
- this.dependentArray = dependentArray;
- this.index = index;
- this.item = dependentArray.objectAt(index);
- this.trackedArray = trackedArray;
- this.beforeObserver = null;
- this.observer = null;
- this.destroyed = false;
- }
- DependentArraysObserver.prototype = {
- setValue: function (newValue) {
- this.instanceMeta.setValue(newValue, true);
- },
- getValue: function () {
- return this.instanceMeta.getValue();
- },
- setupObservers: function (dependentArray, dependentKey) {
- Ember.assert("dependent array must be an `Ember.Array`", Ember.Array.detect(dependentArray));
- this.dependentKeysByGuid[guidFor(dependentArray)] = dependentKey;
- dependentArray.addArrayObserver(this, {
- willChange: 'dependentArrayWillChange',
- didChange: 'dependentArrayDidChange'
- });
- if (this.cp._itemPropertyKeys[dependentKey]) {
- this.setupPropertyObservers(dependentKey, this.cp._itemPropertyKeys[dependentKey]);
- }
- },
- teardownObservers: function (dependentArray, dependentKey) {
- var itemPropertyKeys = this.cp._itemPropertyKeys[dependentKey] || [];
- delete this.dependentKeysByGuid[guidFor(dependentArray)];
- this.teardownPropertyObservers(dependentKey, itemPropertyKeys);
- dependentArray.removeArrayObserver(this, {
- willChange: 'dependentArrayWillChange',
- didChange: 'dependentArrayDidChange'
- });
- },
- suspendArrayObservers: function (callback, binding) {
- var oldSuspended = this.suspended;
- this.suspended = true;
- callback.call(binding);
- this.suspended = oldSuspended;
- },
- setupPropertyObservers: function (dependentKey, itemPropertyKeys) {
- var dependentArray = get(this.instanceMeta.context, dependentKey),
- length = get(dependentArray, 'length'),
- observerContexts = new Array(length);
- this.resetTransformations(dependentKey, observerContexts);
- forEach(dependentArray, function (item, index) {
- var observerContext = this.createPropertyObserverContext(dependentArray, index, this.trackedArraysByGuid[dependentKey]);
- observerContexts[index] = observerContext;
- forEach(itemPropertyKeys, function (propertyKey) {
- addBeforeObserver(item, propertyKey, this, observerContext.beforeObserver);
- addObserver(item, propertyKey, this, observerContext.observer);
- }, this);
- }, this);
- },
- teardownPropertyObservers: function (dependentKey, itemPropertyKeys) {
- var dependentArrayObserver = this,
- trackedArray = this.trackedArraysByGuid[dependentKey],
- beforeObserver,
- observer,
- item;
- if (!trackedArray) { return; }
- trackedArray.apply(function (observerContexts, offset, operation) {
- if (operation === Ember.TrackedArray.DELETE) { return; }
- forEach(observerContexts, function (observerContext) {
- observerContext.destroyed = true;
- beforeObserver = observerContext.beforeObserver;
- observer = observerContext.observer;
- item = observerContext.item;
- forEach(itemPropertyKeys, function (propertyKey) {
- removeBeforeObserver(item, propertyKey, dependentArrayObserver, beforeObserver);
- removeObserver(item, propertyKey, dependentArrayObserver, observer);
- });
- });
- });
- },
- createPropertyObserverContext: function (dependentArray, index, trackedArray) {
- var observerContext = new ItemPropertyObserverContext(dependentArray, index, trackedArray);
- this.createPropertyObserver(observerContext);
- return observerContext;
- },
- createPropertyObserver: function (observerContext) {
- var dependentArrayObserver = this;
- observerContext.beforeObserver = function (obj, keyName) {
- return dependentArrayObserver.itemPropertyWillChange(obj, keyName, observerContext.dependentArray, observerContext);
- };
- observerContext.observer = function (obj, keyName) {
- return dependentArrayObserver.itemPropertyDidChange(obj, keyName, observerContext.dependentArray, observerContext);
- };
- },
- resetTransformations: function (dependentKey, observerContexts) {
- this.trackedArraysByGuid[dependentKey] = new Ember.TrackedArray(observerContexts);
- },
- trackAdd: function (dependentKey, index, newItems) {
- var trackedArray = this.trackedArraysByGuid[dependentKey];
- if (trackedArray) {
- trackedArray.addItems(index, newItems);
- }
- },
- trackRemove: function (dependentKey, index, removedCount) {
- var trackedArray = this.trackedArraysByGuid[dependentKey];
- if (trackedArray) {
- return trackedArray.removeItems(index, removedCount);
- }
- return [];
- },
- updateIndexes: function (trackedArray, array) {
- var length = get(array, 'length');
- // OPTIMIZE: we could stop updating once we hit the object whose observer
- // fired; ie partially apply the transformations
- trackedArray.apply(function (observerContexts, offset, operation) {
- // we don't even have observer contexts for removed items, even if we did,
- // they no longer have any index in the array
- if (operation === Ember.TrackedArray.DELETE) { return; }
- if (operation === Ember.TrackedArray.RETAIN && observerContexts.length === length && offset === 0) {
- // If we update many items we don't want to walk the array each time: we
- // only need to update the indexes at most once per run loop.
- return;
- }
- forEach(observerContexts, function (context, index) {
- context.index = index + offset;
- });
- });
- },
- dependentArrayWillChange: function (dependentArray, index, removedCount, addedCount) {
- if (this.suspended) { return; }
- var removedItem = this.callbacks.removedItem,
- changeMeta,
- guid = guidFor(dependentArray),
- dependentKey = this.dependentKeysByGuid[guid],
- itemPropertyKeys = this.cp._itemPropertyKeys[dependentKey] || [],
- length = get(dependentArray, 'length'),
- normalizedIndex = normalizeIndex(index, length, 0),
- normalizedRemoveCount = normalizeRemoveCount(normalizedIndex, length, removedCount),
- item,
- itemIndex,
- sliceIndex,
- observerContexts;
- observerContexts = this.trackRemove(dependentKey, normalizedIndex, normalizedRemoveCount);
- function removeObservers(propertyKey) {
- observerContexts[sliceIndex].destroyed = true;
- removeBeforeObserver(item, propertyKey, this, observerContexts[sliceIndex].beforeObserver);
- removeObserver(item, propertyKey, this, observerContexts[sliceIndex].observer);
- }
- for (sliceIndex = normalizedRemoveCount - 1; sliceIndex >= 0; --sliceIndex) {
- itemIndex = normalizedIndex + sliceIndex;
- if (itemIndex >= length) { break; }
- item = dependentArray.objectAt(itemIndex);
- forEach(itemPropertyKeys, removeObservers, this);
- changeMeta = createChangeMeta(dependentArray, item, itemIndex, this.instanceMeta.propertyName, this.cp);
- this.setValue( removedItem.call(
- this.instanceMeta.context, this.getValue(), item, changeMeta, this.instanceMeta.sugarMeta));
- }
- },
- dependentArrayDidChange: function (dependentArray, index, removedCount, addedCount) {
- if (this.suspended) { return; }
- var addedItem = this.callbacks.addedItem,
- guid = guidFor(dependentArray),
- dependentKey = this.dependentKeysByGuid[guid],
- observerContexts = new Array(addedCount),
- itemPropertyKeys = this.cp._itemPropertyKeys[dependentKey],
- length = get(dependentArray, 'length'),
- normalizedIndex = normalizeIndex(index, length, addedCount),
- changeMeta,
- observerContext;
- forEach(dependentArray.slice(normalizedIndex, normalizedIndex + addedCount), function (item, sliceIndex) {
- if (itemPropertyKeys) {
- observerContext =
- observerContexts[sliceIndex] =
- this.createPropertyObserverContext(dependentArray, normalizedIndex + sliceIndex, this.trackedArraysByGuid[dependentKey]);
- forEach(itemPropertyKeys, function (propertyKey) {
- addBeforeObserver(item, propertyKey, this, observerContext.beforeObserver);
- addObserver(item, propertyKey, this, observerContext.observer);
- }, this);
- }
- changeMeta = createChangeMeta(dependentArray, item, normalizedIndex + sliceIndex, this.instanceMeta.propertyName, this.cp);
- this.setValue( addedItem.call(
- this.instanceMeta.context, this.getValue(), item, changeMeta, this.instanceMeta.sugarMeta));
- }, this);
- this.trackAdd(dependentKey, normalizedIndex, observerContexts);
- },
- itemPropertyWillChange: function (obj, keyName, array, observerContext) {
- var guid = guidFor(obj);
- if (!this.changedItems[guid]) {
- this.changedItems[guid] = {
- array: array,
- observerContext: observerContext,
- obj: obj,
- previousValues: {}
- };
- }
- this.changedItems[guid].previousValues[keyName] = get(obj, keyName);
- },
- itemPropertyDidChange: function(obj, keyName, array, observerContext) {
- this.flushChanges();
- },
- flushChanges: function() {
- var changedItems = this.changedItems, key, c, changeMeta;
- for (key in changedItems) {
- c = changedItems[key];
- if (c.observerContext.destroyed) { continue; }
- this.updateIndexes(c.observerContext.trackedArray, c.observerContext.dependentArray);
- changeMeta = createChangeMeta(c.array, c.obj, c.observerContext.index, this.instanceMeta.propertyName, this.cp, c.previousValues);
- this.setValue(
- this.callbacks.removedItem.call(this.instanceMeta.context, this.getValue(), c.obj, changeMeta, this.instanceMeta.sugarMeta));
- this.setValue(
- this.callbacks.addedItem.call(this.instanceMeta.context, this.getValue(), c.obj, changeMeta, this.instanceMeta.sugarMeta));
- }
- this.changedItems = {};
- }
- };
- function normalizeIndex(index, length, newItemsOffset) {
- if (index < 0) {
- return Math.max(0, length + index);
- } else if (index < length) {
- return index;
- } else /* index > length */ {
- return Math.min(length - newItemsOffset, index);
- }
- }
- function normalizeRemoveCount(index, length, removedCount) {
- return Math.min(removedCount, length - index);
- }
- function createChangeMeta(dependentArray, item, index, propertyName, property, previousValues) {
- var meta = {
- arrayChanged: dependentArray,
- index: index,
- item: item,
- propertyName: propertyName,
- property: property
- };
- if (previousValues) {
- // previous values only available for item property changes
- meta.previousValues = previousValues;
- }
- return meta;
- }
- function addItems (dependentArray, callbacks, cp, propertyName, meta) {
- forEach(dependentArray, function (item, index) {
- meta.setValue( callbacks.addedItem.call(
- this, meta.getValue(), item, createChangeMeta(dependentArray, item, index, propertyName, cp), meta.sugarMeta));
- }, this);
- }
- function reset(cp, propertyName) {
- var callbacks = cp._callbacks(),
- meta;
- if (cp._hasInstanceMeta(this, propertyName)) {
- meta = cp._instanceMeta(this, propertyName);
- meta.setValue(cp.resetValue(meta.getValue()));
- } else {
- meta = cp._instanceMeta(this, propertyName);
- }
- if (cp.options.initialize) {
- cp.options.initialize.call(this, meta.getValue(), { property: cp, propertyName: propertyName }, meta.sugarMeta);
- }
- }
- function partiallyRecomputeFor(obj, dependentKey) {
- if (arrayBracketPattern.test(dependentKey)) {
- return false;
- }
- var value = get(obj, dependentKey);
- return Ember.Array.detect(value);
- }
- function ReduceComputedPropertyInstanceMeta(context, propertyName, initialValue) {
- this.context = context;
- this.propertyName = propertyName;
- this.cache = metaFor(context).cache;
- this.dependentArrays = {};
- this.sugarMeta = {};
- this.initialValue = initialValue;
- }
- ReduceComputedPropertyInstanceMeta.prototype = {
- getValue: function () {
- if (this.propertyName in this.cache) {
- return this.cache[this.propertyName];
- } else {
- return this.initialValue;
- }
- },
- setValue: function(newValue, triggerObservers) {
- // This lets sugars force a recomputation, handy for very simple
- // implementations of eg max.
- if (newValue === this.cache[this.propertyName]) {
- return;
- }
- if (triggerObservers) {
- propertyWillChange(this.context, this.propertyName);
- }
- if (newValue === undefined) {
- delete this.cache[this.propertyName];
- } else {
- this.cache[this.propertyName] = newValue;
- }
- if (triggerObservers) {
- propertyDidChange(this.context, this.propertyName);
- }
- }
- };
- /**
- A computed property whose dependent keys are arrays and which is updated with
- "one at a time" semantics.
- @class ReduceComputedProperty
- @namespace Ember
- @extends Ember.ComputedProperty
- @constructor
- */
- function ReduceComputedProperty(options) {
- var cp = this;
- this.options = options;
- this._instanceMetas = {};
- this._dependentKeys = null;
- // A map of dependentKey -> [itemProperty, ...] that tracks what properties of
- // items in the array we must track to update this property.
- this._itemPropertyKeys = {};
- this._previousItemPropertyKeys = {};
- this.readOnly();
- this.cacheable();
- this.recomputeOnce = function(propertyName) {
- // What we really want to do is coalesce by <cp, propertyName>.
- // We need a form of `scheduleOnce` that accepts an arbitrary token to
- // coalesce by, in addition to the target and method.
- Ember.run.once(this, recompute, propertyName);
- };
- var recompute = function(propertyName) {
- var dependentKeys = cp._dependentKeys,
- meta = cp._instanceMeta(this, propertyName),
- callbacks = cp._callbacks();
- reset.call(this, cp, propertyName);
- meta.dependentArraysObserver.suspendArrayObservers(function () {
- forEach(cp._dependentKeys, function (dependentKey) {
- if (!partiallyRecomputeFor(this, dependentKey)) { return; }
- var dependentArray = get(this, dependentKey),
- previousDependentArray = meta.dependentArrays[dependentKey];
- if (dependentArray === previousDependentArray) {
- // The array may be the same, but our item property keys may have
- // changed, so we set them up again. We can't easily tell if they've
- // changed: the array may be the same object, but with different
- // contents.
- if (cp._previousItemPropertyKeys[dependentKey]) {
- delete cp._previousItemPropertyKeys[dependentKey];
- meta.dependentArraysObserver.setupPropertyObservers(dependentKey, cp._itemPropertyKeys[dependentKey]);
- }
- } else {
- meta.dependentArrays[dependentKey] = dependentArray;
- if (previousDependentArray) {
- meta.dependentArraysObserver.teardownObservers(previousDependentArray, dependentKey);
- }
- if (dependentArray) {
- meta.dependentArraysObserver.setupObservers(dependentArray, dependentKey);
- }
- }
- }, this);
- }, this);
- forEach(cp._dependentKeys, function(dependentKey) {
- if (!partiallyRecomputeFor(this, dependentKey)) { return; }
- var dependentArray = get(this, dependentKey);
- if (dependentArray) {
- addItems.call(this, dependentArray, callbacks, cp, propertyName, meta);
- }
- }, this);
- };
- this.func = function (propertyName) {
- Ember.assert("Computed reduce values require at least one dependent key", cp._dependentKeys);
- recompute.call(this, propertyName);
- return cp._instanceMeta(this, propertyName).getValue();
- };
- }
- Ember.ReduceComputedProperty = ReduceComputedProperty;
- ReduceComputedProperty.prototype = o_create(ComputedProperty.prototype);
- function defaultCallback(computedValue) {
- return computedValue;
- }
- ReduceComputedProperty.prototype._callbacks = function () {
- if (!this.callbacks) {
- var options = this.options;
- this.callbacks = {
- removedItem: options.removedItem || defaultCallback,
- addedItem: options.addedItem || defaultCallback
- };
- }
- return this.callbacks;
- };
- ReduceComputedProperty.prototype._hasInstanceMeta = function (context, propertyName) {
- var guid = guidFor(context),
- key = guid + ':' + propertyName;
- return !!this._instanceMetas[key];
- };
- ReduceComputedProperty.prototype._instanceMeta = function (context, propertyName) {
- var guid = guidFor(context),
- key = guid + ':' + propertyName,
- meta = this._instanceMetas[key];
- if (!meta) {
- meta = this._instanceMetas[key] = new ReduceComputedPropertyInstanceMeta(context, propertyName, this.initialValue());
- meta.dependentArraysObserver = new DependentArraysObserver(this._callbacks(), this, meta, context, propertyName, meta.sugarMeta);
- }
- return meta;
- };
- ReduceComputedProperty.prototype.initialValue = function () {
- if (typeof this.options.initialValue === 'function') {
- return this.options.initialValue();
- }
- else {
- return this.options.initialValue;
- }
- };
- ReduceComputedProperty.prototype.resetValue = function (value) {
- return this.initialValue();
- };
- ReduceComputedProperty.prototype.itemPropertyKey = function (dependentArrayKey, itemPropertyKey) {
- this._itemPropertyKeys[dependentArrayKey] = this._itemPropertyKeys[dependentArrayKey] || [];
- this._itemPropertyKeys[dependentArrayKey].push(itemPropertyKey);
- };
- ReduceComputedProperty.prototype.clearItemPropertyKeys = function (dependentArrayKey) {
- if (this._itemPropertyKeys[dependentArrayKey]) {
- this._previousItemPropertyKeys[dependentArrayKey] = this._itemPropertyKeys[dependentArrayKey];
- this._itemPropertyKeys[dependentArrayKey] = [];
- }
- };
- ReduceComputedProperty.prototype.property = function () {
- var cp = this,
- args = a_slice.call(arguments),
- propertyArgs = new Ember.Set(),
- match,
- dependentArrayKey,
- itemPropertyKey;
- forEach(a_slice.call(arguments), function (dependentKey) {
- if (doubleEachPropertyPattern.test(dependentKey)) {
- throw new Ember.Error("Nested @each properties not supported: " + dependentKey);
- } else if (match = eachPropertyPattern.exec(dependentKey)) {
- dependentArrayKey = match[1];
- var itemPropertyKeyPattern = match[2],
- addItemPropertyKey = function (itemPropertyKey) {
- cp.itemPropertyKey(dependentArrayKey, itemPropertyKey);
- };
- expandProperties(itemPropertyKeyPattern, addItemPropertyKey);
- propertyArgs.add(dependentArrayKey);
- } else {
- propertyArgs.add(dependentKey);
- }
- });
- return ComputedProperty.prototype.property.apply(this, propertyArgs.toArray());
- };
- /**
- Creates a computed property which operates on dependent arrays and
- is updated with "one at a time" semantics. When items are added or
- removed from the dependent array(s) a reduce computed only operates
- on the change instead of re-evaluating the entire array.
- If there are more than one arguments the first arguments are
- considered to be dependent property keys. The last argument is
- required to be an options object. The options object can have the
- following four properties:
- `initialValue` - A value or function that will be used as the initial
- value for the computed. If this property is a function the result of calling
- the function will be used as the initial value. This property is required.
- `initialize` - An optional initialize function. Typically this will be used
- to set up state on the instanceMeta object.
- `removedItem` - A function that is called each time an element is removed
- from the array.
- `addedItem` - A function that is called each time an element is added to
- the array.
- The `initialize` function has the following signature:
- ```javascript
- function (initialValue, changeMeta, instanceMeta)
- ```
- `initialValue` - The value of the `initialValue` property from the
- options object.
- `changeMeta` - An object which contains meta information about the
- computed. It contains the following properties:
- - `property` the computed property
- - `propertyName` the name of the property on the object
- `instanceMeta` - An object that can be used to store meta
- information needed for calculating your computed. For example a
- unique computed might use this to store the number of times a given
- element is found in the dependent array.
- The `removedItem` and `addedItem` functions both have the following signature:
- ```javascript
- function (accumulatedValue, item, changeMeta, instanceMeta)
- ```
- `accumulatedValue` - The value returned from the last time
- `removedItem` or `addedItem` was called or `initialValue`.
- `item` - the element added or removed from the array
- `changeMeta` - An object which contains meta information about the
- change. It contains the following properties:
- - `property` the computed property
- - `propertyName` the name of the property on the object
- - `index` the index of the added or removed item
- - `item` the added or removed item: this is exactly the same as
- the second arg
- - `arrayChanged` the array that triggered the change. Can be
- useful when depending on multiple arrays.
- For property changes triggered on an item property change (when
- depKey is something like `someArray.@each.someProperty`),
- `changeMeta` will also contain the following property:
- - `previousValues` an object whose keys are the properties that changed on
- the item, and whose values are the item's previous values.
- `previousValues` is important Ember coalesces item property changes via
- Ember.run.once. This means that by the time removedItem gets called, item has
- the new values, but you may need the previous value (eg for sorting &
- filtering).
- `instanceMeta` - An object that can be used to store meta
- information needed for calculating your computed. For example a
- unique computed might use this to store the number of times a given
- element is found in the dependent array.
- The `removedItem` and `addedItem` functions should return the accumulated
- value. It is acceptable to not return anything (ie return undefined)
- to invalidate the computation. This is generally not a good idea for
- arrayComputed but it's used in eg max and min.
- Note that observers will be fired if either of these functions return a value
- that differs from the accumulated value. When returning an object that
- mutates in response to array changes, for example an array that maps
- everything from some other array (see `Ember.computed.map`), it is usually
- important that the *same* array be returned to avoid accidentally triggering observers.
- Example
- ```javascript
- Ember.computed.max = function (dependentKey) {
- return Ember.reduceComputed(dependentKey, {
- initialValue: -Infinity,
- addedItem: function (accumulatedValue, item, changeMeta, instanceMeta) {
- return Math.max(accumulatedValue, item);
- },
- removedItem: function (accumulatedValue, item, changeMeta, instanceMeta) {
- if (item < accumulatedValue) {
- return accumulatedValue;
- }
- }
- });
- };
- ```
- Dependent keys may refer to `@this` to observe changes to the object itself,
- which must be array-like, rather than a property of the object. This is
- mostly useful for array proxies, to ensure objects are retrieved via
- `objectAtContent`. This is how you could sort items by properties defined on an item controller.
- Example
- ```javascript
- App.PeopleController = Ember.ArrayController.extend({
- itemController: 'person',
- sortedPeople: Ember.computed.sort('@this.@each.reversedName', function(personA, personB) {
- // `reversedName` isn't defined on Person, but we have access to it via
- // the item controller App.PersonController. If we'd used
- // `content.@each.reversedName` above, we would be getting the objects
- // directly and not have access to `reversedName`.
- //
- var reversedNameA = get(personA, 'reversedName'),
- reversedNameB = get(personB, 'reversedName');
- return Ember.compare(reversedNameA, reversedNameB);
- })
- });
- App.PersonController = Ember.ObjectController.extend({
- reversedName: function () {
- return reverse(get(this, 'name'));
- }.property('name')
- })
- ```
- Dependent keys whose values are not arrays are treated as regular
- dependencies: when they change, the computed property is completely
- recalculated. It is sometimes useful to have dependent arrays with similar
- semantics. Dependent keys which end in `.[]` do not use "one at a time"
- semantics. When an item is added or removed from such a dependency, the
- computed property is completely recomputed.
- Example
- ```javascript
- Ember.Object.extend({
- // When `string` is changed, `computed` is completely recomputed.
- string: 'a string',
- // When an item is added to `array`, `addedItem` is called.
- array: [],
- // When an item is added to `anotherArray`, `computed` is completely
- // recomputed.
- anotherArray: [],
- computed: Ember.reduceComputed('string', 'array', 'anotherArray.[]', {
- addedItem: addedItemCallback,
- removedItem: removedItemCallback
- })
- });
- ```
- @method reduceComputed
- @for Ember
- @param {String} [dependentKeys*]
- @param {Object} options
- @return {Ember.ComputedProperty}
- */
- Ember.reduceComputed = function (options) {
- var args;
- if (arguments.length > 1) {
- args = a_slice.call(arguments, 0, -1);
- options = a_slice.call(arguments, -1)[0];
- }
- if (typeof options !== "object") {
- throw new Ember.Error("Reduce Computed Property declared without an options hash");
- }
- if (!('initialValue' in options)) {
- throw new Ember.Error("Reduce Computed Property declared without an initial value");
- }
- var cp = new ReduceComputedProperty(options);
- if (args) {
- cp.property.apply(cp, args);
- }
- return cp;
- };
- })();
- (function() {
- var ReduceComputedProperty = Ember.ReduceComputedProperty,
- a_slice = [].slice,
- o_create = Ember.create,
- forEach = Ember.EnumerableUtils.forEach;
- function ArrayComputedProperty() {
- var cp = this;
- ReduceComputedProperty.apply(this, arguments);
- this.func = (function(reduceFunc) {
- return function (propertyName) {
- if (!cp._hasInstanceMeta(this, propertyName)) {
- // When we recompute an array computed property, we need already
- // retrieved arrays to be updated; we can't simply empty the cache and
- // hope the array is re-retrieved.
- forEach(cp._dependentKeys, function(dependentKey) {
- Ember.addObserver(this, dependentKey, function() {
- cp.recomputeOnce.call(this, propertyName);
- });
- }, this);
- }
- return reduceFunc.apply(this, arguments);
- };
- })(this.func);
- return this;
- }
- Ember.ArrayComputedProperty = ArrayComputedProperty;
- ArrayComputedProperty.prototype = o_create(ReduceComputedProperty.prototype);
- ArrayComputedProperty.prototype.initialValue = function () {
- return Ember.A();
- };
- ArrayComputedProperty.prototype.resetValue = function (array) {
- array.clear();
- return array;
- };
- // This is a stopgap to keep the reference counts correct with lazy CPs.
- ArrayComputedProperty.prototype.didChange = function (obj, keyName) {
- return;
- };
- /**
- Creates a computed property which operates on dependent arrays and
- is updated with "one at a time" semantics. When items are added or
- removed from the dependent array(s) an array computed only operates
- on the change instead of re-evaluating the entire array. This should
- return an array, if you'd like to use "one at a time" semantics and
- compute some value other then an array look at
- `Ember.reduceComputed`.
- If there are more than one arguments the first arguments are
- considered to be dependent property keys. The last argument is
- required to be an options object. The options object can have the
- following three properties.
- `initialize` - An optional initialize function. Typically this will be used
- to set up state on the instanceMeta object.
- `removedItem` - A function that is called each time an element is
- removed from the array.
- `addedItem` - A function that is called each time an element is
- added to the array.
- The `initialize` function has the following signature:
- ```javascript
- function (array, changeMeta, instanceMeta)
- ```
- `array` - The initial value of the arrayComputed, an empty array.
- `changeMeta` - An object which contains meta information about the
- computed. It contains the following properties:
- - `property` the computed property
- - `propertyName` the name of the property on the object
- `instanceMeta` - An object that can be used to store meta
- information needed for calculating your computed. For example a
- unique computed might use this to store the number of times a given
- element is found in the dependent array.
- The `removedItem` and `addedItem` functions both have the following signature:
- ```javascript
- function (accumulatedValue, item, changeMeta, instanceMeta)
- ```
- `accumulatedValue` - The value returned from the last time
- `removedItem` or `addedItem` was called or an empty array.
- `item` - the element added or removed from the array
- `changeMeta` - An object which contains meta information about the
- change. It contains the following properties:
- - `property` the computed property
- - `propertyName` the name of the property on the object
- - `index` the index of the added or removed item
- - `item` the added or removed item: this is exactly the same as
- the second arg
- - `arrayChanged` the array that triggered the change. Can be
- useful when depending on multiple arrays.
- For property changes triggered on an item property change (when
- depKey is something like `someArray.@each.someProperty`),
- `changeMeta` will also contain the following property:
- - `previousValues` an object whose keys are the properties that changed on
- the item, and whose values are the item's previous values.
- `previousValues` is important Ember coalesces item property changes via
- Ember.run.once. This means that by the time removedItem gets called, item has
- the new values, but you may need the previous value (eg for sorting &
- filtering).
- `instanceMeta` - An object that can be used to store meta
- information needed for calculating your computed. For example a
- unique computed might use this to store the number of times a given
- element is found in the dependent array.
- The `removedItem` and `addedItem` functions should return the accumulated
- value. It is acceptable to not return anything (ie return undefined)
- to invalidate the computation. This is generally not a good idea for
- arrayComputed but it's used in eg max and min.
- Example
- ```javascript
- Ember.computed.map = function(dependentKey, callback) {
- var options = {
- addedItem: function(array, item, changeMeta, instanceMeta) {
- var mapped = callback(item);
- array.insertAt(changeMeta.index, mapped);
- return array;
- },
- removedItem: function(array, item, changeMeta, instanceMeta) {
- array.removeAt(changeMeta.index, 1);
- return array;
- }
- };
- return Ember.arrayComputed(dependentKey, options);
- };
- ```
- @method arrayComputed
- @for Ember
- @param {String} [dependentKeys*]
- @param {Object} options
- @return {Ember.ComputedProperty}
- */
- Ember.arrayComputed = function (options) {
- var args;
- if (arguments.length > 1) {
- args = a_slice.call(arguments, 0, -1);
- options = a_slice.call(arguments, -1)[0];
- }
- if (typeof options !== "object") {
- throw new Ember.Error("Array Computed Property declared without an options hash");
- }
- var cp = new ArrayComputedProperty(options);
- if (args) {
- cp.property.apply(cp, args);
- }
- return cp;
- };
- })();
- (function() {
- /**
- @module ember
- @submodule ember-runtime
- */
- var get = Ember.get,
- set = Ember.set,
- guidFor = Ember.guidFor,
- merge = Ember.merge,
- a_slice = [].slice,
- forEach = Ember.EnumerableUtils.forEach,
- map = Ember.EnumerableUtils.map,
- SearchProxy;
- /**
- A computed property that returns the sum of the value
- in the dependent array.
- @method computed.sum
- @for Ember
- @param {String} dependentKey
- @return {Ember.ComputedProperty} computes the sum of all values in the dependentKey's array
- */
- Ember.computed.sum = function(dependentKey){
- return Ember.reduceComputed(dependentKey, {
- initialValue: 0,
- addedItem: function(accumulatedValue, item, changeMeta, instanceMeta){
- return accumulatedValue + item;
- },
- removedItem: function(accumulatedValue, item, changeMeta, instanceMeta){
- return accumulatedValue - item;
- }
- });
- };
- /**
- A computed property that calculates the maximum value in the
- dependent array. This will return `-Infinity` when the dependent
- array is empty.
- ```javascript
- App.Person = Ember.Object.extend({
- childAges: Ember.computed.mapBy('children', 'age'),
- maxChildAge: Ember.computed.max('childAges')
- });
- var lordByron = App.Person.create({children: []});
- lordByron.get('maxChildAge'); // -Infinity
- lordByron.get('children').pushObject({
- name: 'Augusta Ada Byron', age: 7
- });
- lordByron.get('maxChildAge'); // 7
- lordByron.get('children').pushObjects([{
- name: 'Allegra Byron',
- age: 5
- }, {
- name: 'Elizabeth Medora Leigh',
- age: 8
- }]);
- lordByron.get('maxChildAge'); // 8
- ```
- @method computed.max
- @for Ember
- @param {String} dependentKey
- @return {Ember.ComputedProperty} computes the largest value in the dependentKey's array
- */
- Ember.computed.max = function (dependentKey) {
- return Ember.reduceComputed(dependentKey, {
- initialValue: -Infinity,
- addedItem: function (accumulatedValue, item, changeMeta, instanceMeta) {
- return Math.max(accumulatedValue, item);
- },
- removedItem: function (accumulatedValue, item, changeMeta, instanceMeta) {
- if (item < accumulatedValue) {
- return accumulatedValue;
- }
- }
- });
- };
- /**
- A computed property that calculates the minimum value in the
- dependent array. This will return `Infinity` when the dependent
- array is empty.
- ```javascript
- App.Person = Ember.Object.extend({
- childAges: Ember.computed.mapBy('children', 'age'),
- minChildAge: Ember.computed.min('childAges')
- });
- var lordByron = App.Person.create({children: []});
- lordByron.get('minChildAge'); // Infinity
- lordByron.get('children').pushObject({
- name: 'Augusta Ada Byron', age: 7
- });
- lordByron.get('minChildAge'); // 7
- lordByron.get('children').pushObjects([{
- name: 'Allegra Byron',
- age: 5
- }, {
- name: 'Elizabeth Medora Leigh',
- age: 8
- }]);
- lordByron.get('minChildAge'); // 5
- ```
- @method computed.min
- @for Ember
- @param {String} dependentKey
- @return {Ember.ComputedProperty} computes the smallest value in the dependentKey's array
- */
- Ember.computed.min = function (dependentKey) {
- return Ember.reduceComputed(dependentKey, {
- initialValue: Infinity,
- addedItem: function (accumulatedValue, item, changeMeta, instanceMeta) {
- return Math.min(accumulatedValue, item);
- },
- removedItem: function (accumulatedValue, item, changeMeta, instanceMeta) {
- if (item > accumulatedValue) {
- return accumulatedValue;
- }
- }
- });
- };
- /**
- Returns an array mapped via the callback
- The callback method you provide should have the following signature.
- `item` is the current item in the iteration.
- ```javascript
- function(item);
- ```
- Example
- ```javascript
- App.Hamster = Ember.Object.extend({
- excitingChores: Ember.computed.map('chores', function(chore) {
- return chore.toUpperCase() + '!';
- })
- });
- var hamster = App.Hamster.create({
- chores: ['clean', 'write more unit tests']
- });
- hamster.get('excitingChores'); // ['CLEAN!', 'WRITE MORE UNIT TESTS!']
- ```
- @method computed.map
- @for Ember
- @param {String} dependentKey
- @param {Function} callback
- @return {Ember.ComputedProperty} an array mapped via the callback
- */
- Ember.computed.map = function(dependentKey, callback) {
- var options = {
- addedItem: function(array, item, changeMeta, instanceMeta) {
- var mapped = callback.call(this, item);
- array.insertAt(changeMeta.index, mapped);
- return array;
- },
- removedItem: function(array, item, changeMeta, instanceMeta) {
- array.removeAt(changeMeta.index, 1);
- return array;
- }
- };
- return Ember.arrayComputed(dependentKey, options);
- };
- /**
- Returns an array mapped to the specified key.
- ```javascript
- App.Person = Ember.Object.extend({
- childAges: Ember.computed.mapBy('children', 'age')
- });
- var lordByron = App.Person.create({children: []});
- lordByron.get('childAges'); // []
- lordByron.get('children').pushObject({name: 'Augusta Ada Byron', age: 7});
- lordByron.get('childAges'); // [7]
- lordByron.get('children').pushObjects([{
- name: 'Allegra Byron',
- age: 5
- }, {
- name: 'Elizabeth Medora Leigh',
- age: 8
- }]);
- lordByron.get('childAges'); // [7, 5, 8]
- ```
- @method computed.mapBy
- @for Ember
- @param {String} dependentKey
- @param {String} propertyKey
- @return {Ember.ComputedProperty} an array mapped to the specified key
- */
- Ember.computed.mapBy = function(dependentKey, propertyKey) {
- var callback = function(item) { return get(item, propertyKey); };
- return Ember.computed.map(dependentKey + '.@each.' + propertyKey, callback);
- };
- /**
- @method computed.mapProperty
- @for Ember
- @deprecated Use `Ember.computed.mapBy` instead
- @param dependentKey
- @param propertyKey
- */
- Ember.computed.mapProperty = Ember.computed.mapBy;
- /**
- Filters the array by the callback.
- The callback method you provide should have the following signature.
- `item` is the current item in the iteration.
- ```javascript
- function(item);
- ```
- ```javascript
- App.Hamster = Ember.Object.extend({
- remainingChores: Ember.computed.filter('chores', function(chore) {
- return !chore.done;
- })
- });
- var hamster = App.Hamster.create({chores: [
- {name: 'cook', done: true},
- {name: 'clean', done: true},
- {name: 'write more unit tests', done: false}
- ]});
- hamster.get('remainingChores'); // [{name: 'write more unit tests', done: false}]
- ```
- @method computed.filter
- @for Ember
- @param {String} dependentKey
- @param {Function} callback
- @return {Ember.ComputedProperty} the filtered array
- */
- Ember.computed.filter = function(dependentKey, callback) {
- var options = {
- initialize: function (array, changeMeta, instanceMeta) {
- instanceMeta.filteredArrayIndexes = new Ember.SubArray();
- },
- addedItem: function(array, item, changeMeta, instanceMeta) {
- var match = !!callback.call(this, item),
- filterIndex = instanceMeta.filteredArrayIndexes.addItem(changeMeta.index, match);
- if (match) {
- array.insertAt(filterIndex, item);
- }
- return array;
- },
- removedItem: function(array, item, changeMeta, instanceMeta) {
- var filterIndex = instanceMeta.filteredArrayIndexes.removeItem(changeMeta.index);
- if (filterIndex > -1) {
- array.removeAt(filterIndex);
- }
- return array;
- }
- };
- return Ember.arrayComputed(dependentKey, options);
- };
- /**
- Filters the array by the property and value
- ```javascript
- App.Hamster = Ember.Object.extend({
- remainingChores: Ember.computed.filterBy('chores', 'done', false)
- });
- var hamster = App.Hamster.create({chores: [
- {name: 'cook', done: true},
- {name: 'clean', done: true},
- {name: 'write more unit tests', done: false}
- ]});
- hamster.get('remainingChores'); // [{name: 'write more unit tests', done: false}]
- ```
- @method computed.filterBy
- @for Ember
- @param {String} dependentKey
- @param {String} propertyKey
- @param {String} value
- @return {Ember.ComputedProperty} the filtered array
- */
- Ember.computed.filterBy = function(dependentKey, propertyKey, value) {
- var callback;
- if (arguments.length === 2) {
- callback = function(item) {
- return get(item, propertyKey);
- };
- } else {
- callback = function(item) {
- return get(item, propertyKey) === value;
- };
- }
- return Ember.computed.filter(dependentKey + '.@each.' + propertyKey, callback);
- };
- /**
- @method computed.filterProperty
- @for Ember
- @param dependentKey
- @param propertyKey
- @param value
- @deprecated Use `Ember.computed.filterBy` instead
- */
- Ember.computed.filterProperty = Ember.computed.filterBy;
- /**
- A computed property which returns a new array with all the unique
- elements from one or more dependent arrays.
- Example
- ```javascript
- App.Hamster = Ember.Object.extend({
- uniqueFruits: Ember.computed.uniq('fruits')
- });
- var hamster = App.Hamster.create({fruits: [
- 'banana',
- 'grape',
- 'kale',
- 'banana'
- ]});
- hamster.get('uniqueFruits'); // ['banana', 'grape', 'kale']
- ```
- @method computed.uniq
- @for Ember
- @param {String} propertyKey*
- @return {Ember.ComputedProperty} computes a new array with all the
- unique elements from the dependent array
- */
- Ember.computed.uniq = function() {
- var args = a_slice.call(arguments);
- args.push({
- initialize: function(array, changeMeta, instanceMeta) {
- instanceMeta.itemCounts = {};
- },
- addedItem: function(array, item, changeMeta, instanceMeta) {
- var guid = guidFor(item);
- if (!instanceMeta.itemCounts[guid]) {
- instanceMeta.itemCounts[guid] = 1;
- } else {
- ++instanceMeta.itemCounts[guid];
- }
- array.addObject(item);
- return array;
- },
- removedItem: function(array, item, _, instanceMeta) {
- var guid = guidFor(item),
- itemCounts = instanceMeta.itemCounts;
- if (--itemCounts[guid] === 0) {
- array.removeObject(item);
- }
- return array;
- }
- });
- return Ember.arrayComputed.apply(null, args);
- };
- /**
- Alias for [Ember.computed.uniq](/api/#method_computed_uniq).
- @method computed.union
- @for Ember
- @param {String} propertyKey*
- @return {Ember.ComputedProperty} computes a new array with all the
- unique elements from the dependent array
- */
- Ember.computed.union = Ember.computed.uniq;
- /**
- A computed property which returns a new array with all the duplicated
- elements from two or more dependent arrays.
- Example
- ```javascript
- var obj = Ember.Object.createWithMixins({
- adaFriends: ['Charles Babbage', 'John Hobhouse', 'William King', 'Mary Somerville'],
- charlesFriends: ['William King', 'Mary Somerville', 'Ada Lovelace', 'George Peacock'],
- friendsInCommon: Ember.computed.intersect('adaFriends', 'charlesFriends')
- });
- obj.get('friendsInCommon'); // ['William King', 'Mary Somerville']
- ```
- @method computed.intersect
- @for Ember
- @param {String} propertyKey*
- @return {Ember.ComputedProperty} computes a new array with all the
- duplicated elements from the dependent arrays
- */
- Ember.computed.intersect = function () {
- var getDependentKeyGuids = function (changeMeta) {
- return map(changeMeta.property._dependentKeys, function (dependentKey) {
- return guidFor(dependentKey);
- });
- };
- var args = a_slice.call(arguments);
- args.push({
- initialize: function (array, changeMeta, instanceMeta) {
- instanceMeta.itemCounts = {};
- },
- addedItem: function(array, item, changeMeta, instanceMeta) {
- var itemGuid = guidFor(item),
- dependentGuids = getDependentKeyGuids(changeMeta),
- dependentGuid = guidFor(changeMeta.arrayChanged),
- numberOfDependentArrays = changeMeta.property._dependentKeys.length,
- itemCounts = instanceMeta.itemCounts;
- if (!itemCounts[itemGuid]) { itemCounts[itemGuid] = {}; }
- if (itemCounts[itemGuid][dependentGuid] === undefined) { itemCounts[itemGuid][dependentGuid] = 0; }
- if (++itemCounts[itemGuid][dependentGuid] === 1 &&
- numberOfDependentArrays === Ember.keys(itemCounts[itemGuid]).length) {
- array.addObject(item);
- }
- return array;
- },
- removedItem: function(array, item, changeMeta, instanceMeta) {
- var itemGuid = guidFor(item),
- dependentGuids = getDependentKeyGuids(changeMeta),
- dependentGuid = guidFor(changeMeta.arrayChanged),
- numberOfDependentArrays = changeMeta.property._dependentKeys.length,
- numberOfArraysItemAppearsIn,
- itemCounts = instanceMeta.itemCounts;
- if (itemCounts[itemGuid][dependentGuid] === undefined) { itemCounts[itemGuid][dependentGuid] = 0; }
- if (--itemCounts[itemGuid][dependentGuid] === 0) {
- delete itemCounts[itemGuid][dependentGuid];
- numberOfArraysItemAppearsIn = Ember.keys(itemCounts[itemGuid]).length;
- if (numberOfArraysItemAppearsIn === 0) {
- delete itemCounts[itemGuid];
- }
- array.removeObject(item);
- }
- return array;
- }
- });
- return Ember.arrayComputed.apply(null, args);
- };
- /**
- A computed property which returns a new array with all the
- properties from the first dependent array that are not in the second
- dependent array.
- Example
- ```javascript
- App.Hamster = Ember.Object.extend({
- likes: ['banana', 'grape', 'kale'],
- wants: Ember.computed.setDiff('likes', 'fruits')
- });
- var hamster = App.Hamster.create({fruits: [
- 'grape',
- 'kale',
- ]});
- hamster.get('wants'); // ['banana']
- ```
- @method computed.setDiff
- @for Ember
- @param {String} setAProperty
- @param {String} setBProperty
- @return {Ember.ComputedProperty} computes a new array with all the
- items from the first dependent array that are not in the second
- dependent array
- */
- Ember.computed.setDiff = function (setAProperty, setBProperty) {
- if (arguments.length !== 2) {
- throw new Ember.Error("setDiff requires exactly two dependent arrays.");
- }
- return Ember.arrayComputed(setAProperty, setBProperty, {
- addedItem: function (array, item, changeMeta, instanceMeta) {
- var setA = get(this, setAProperty),
- setB = get(this, setBProperty);
- if (changeMeta.arrayChanged === setA) {
- if (!setB.contains(item)) {
- array.addObject(item);
- }
- } else {
- array.removeObject(item);
- }
- return array;
- },
- removedItem: function (array, item, changeMeta, instanceMeta) {
- var setA = get(this, setAProperty),
- setB = get(this, setBProperty);
- if (changeMeta.arrayChanged === setB) {
- if (setA.contains(item)) {
- array.addObject(item);
- }
- } else {
- array.removeObject(item);
- }
- return array;
- }
- });
- };
- function binarySearch(array, item, low, high) {
- var mid, midItem, res, guidMid, guidItem;
- if (arguments.length < 4) { high = get(array, 'length'); }
- if (arguments.length < 3) { low = 0; }
- if (low === high) {
- return low;
- }
- mid = low + Math.floor((high - low) / 2);
- midItem = array.objectAt(mid);
- guidMid = _guidFor(midItem);
- guidItem = _guidFor(item);
- if (guidMid === guidItem) {
- return mid;
- }
- res = this.order(midItem, item);
- if (res === 0) {
- res = guidMid < guidItem ? -1 : 1;
- }
- if (res < 0) {
- return this.binarySearch(array, item, mid+1, high);
- } else if (res > 0) {
- return this.binarySearch(array, item, low, mid);
- }
- return mid;
- function _guidFor(item) {
- if (SearchProxy.detectInstance(item)) {
- return guidFor(get(item, 'content'));
- }
- return guidFor(item);
- }
- }
- SearchProxy = Ember.ObjectProxy.extend();
- /**
- A computed property which returns a new array with all the
- properties from the first dependent array sorted based on a property
- or sort function.
- The callback method you provide should have the following signature:
- ```javascript
- function(itemA, itemB);
- ```
- - `itemA` the first item to compare.
- - `itemB` the second item to compare.
- This function should return `-1` when `itemA` should come before
- `itemB`. It should return `1` when `itemA` should come after
- `itemB`. If the `itemA` and `itemB` are equal this function should return `0`.
- Example
- ```javascript
- var ToDoList = Ember.Object.extend({
- todosSorting: ['name'],
- sortedTodos: Ember.computed.sort('todos', 'todosSorting'),
- priorityTodos: Ember.computed.sort('todos', function(a, b){
- if (a.priority > b.priority) {
- return 1;
- } else if (a.priority < b.priority) {
- return -1;
- }
- return 0;
- }),
- });
- var todoList = ToDoList.create({todos: [
- {name: 'Unit Test', priority: 2},
- {name: 'Documentation', priority: 3},
- {name: 'Release', priority: 1}
- ]});
- todoList.get('sortedTodos'); // [{name:'Documentation', priority:3}, {name:'Release', priority:1}, {name:'Unit Test', priority:2}]
- todoList.get('priorityTodos'); // [{name:'Release', priority:1}, {name:'Unit Test', priority:2}, {name:'Documentation', priority:3}]
- ```
- @method computed.sort
- @for Ember
- @param {String} dependentKey
- @param {String or Function} sortDefinition a dependent key to an
- array of sort properties or a function to use when sorting
- @return {Ember.ComputedProperty} computes a new sorted array based
- on the sort property array or callback function
- */
- Ember.computed.sort = function (itemsKey, sortDefinition) {
- Ember.assert("Ember.computed.sort requires two arguments: an array key to sort and either a sort properties key or sort function", arguments.length === 2);
- var initFn, sortPropertiesKey;
- if (typeof sortDefinition === 'function') {
- initFn = function (array, changeMeta, instanceMeta) {
- instanceMeta.order = sortDefinition;
- instanceMeta.binarySearch = binarySearch;
- };
- } else {
- sortPropertiesKey = sortDefinition;
- initFn = function (array, changeMeta, instanceMeta) {
- function setupSortProperties() {
- var sortPropertyDefinitions = get(this, sortPropertiesKey),
- sortProperty,
- sortProperties = instanceMeta.sortProperties = [],
- sortPropertyAscending = instanceMeta.sortPropertyAscending = {},
- idx,
- asc;
- Ember.assert("Cannot sort: '" + sortPropertiesKey + "' is not an array.", Ember.isArray(sortPropertyDefinitions));
- changeMeta.property.clearItemPropertyKeys(itemsKey);
- forEach(sortPropertyDefinitions, function (sortPropertyDefinition) {
- if ((idx = sortPropertyDefinition.indexOf(':')) !== -1) {
- sortProperty = sortPropertyDefinition.substring(0, idx);
- asc = sortPropertyDefinition.substring(idx+1).toLowerCase() !== 'desc';
- } else {
- sortProperty = sortPropertyDefinition;
- asc = true;
- }
- sortProperties.push(sortProperty);
- sortPropertyAscending[sortProperty] = asc;
- changeMeta.property.itemPropertyKey(itemsKey, sortProperty);
- });
- sortPropertyDefinitions.addObserver('@each', this, updateSortPropertiesOnce);
- }
- function updateSortPropertiesOnce() {
- Ember.run.once(this, updateSortProperties, changeMeta.propertyName);
- }
- function updateSortProperties(propertyName) {
- setupSortProperties.call(this);
- changeMeta.property.recomputeOnce.call(this, propertyName);
- }
- Ember.addObserver(this, sortPropertiesKey, updateSortPropertiesOnce);
- setupSortProperties.call(this);
- instanceMeta.order = function (itemA, itemB) {
- var sortProperty, result, asc;
- for (var i = 0; i < this.sortProperties.length; ++i) {
- sortProperty = this.sortProperties[i];
- result = Ember.compare(get(itemA, sortProperty), get(itemB, sortProperty));
- if (result !== 0) {
- asc = this.sortPropertyAscending[sortProperty];
- return asc ? result : (-1 * result);
- }
- }
- return 0;
- };
- instanceMeta.binarySearch = binarySearch;
- };
- }
- return Ember.arrayComputed(itemsKey, {
- initialize: initFn,
- addedItem: function (array, item, changeMeta, instanceMeta) {
- var index = instanceMeta.binarySearch(array, item);
- array.insertAt(index, item);
- return array;
- },
- removedItem: function (array, item, changeMeta, instanceMeta) {
- var proxyProperties, index, searchItem;
- if (changeMeta.previousValues) {
- proxyProperties = merge({ content: item }, changeMeta.previousValues);
- searchItem = SearchProxy.create(proxyProperties);
- } else {
- searchItem = item;
- }
- index = instanceMeta.binarySearch(array, searchItem);
- array.removeAt(index);
- return array;
- }
- });
- };
- })();
- (function() {
- Ember.RSVP = requireModule('rsvp');
- Ember.RSVP.onerrorDefault = function(error) {
- if (error instanceof Error) {
- if (Ember.testing) {
- if (Ember.Test && Ember.Test.adapter) {
- Ember.Test.adapter.exception(error);
- } else {
- throw error;
- }
- } else {
- Ember.Logger.error(error.stack);
- Ember.assert(error, false);
- }
- }
- };
- Ember.RSVP.on('error', Ember.RSVP.onerrorDefault);
- })();
- (function() {
- /**
- @module ember
- @submodule ember-runtime
- */
- var a_slice = Array.prototype.slice;
- var expandProperties = Ember.expandProperties;
- if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.Function) {
- /**
- The `property` extension of Javascript's Function prototype is available
- when `Ember.EXTEND_PROTOTYPES` or `Ember.EXTEND_PROTOTYPES.Function` is
- `true`, which is the default.
- Computed properties allow you to treat a function like a property:
- ```javascript
- MyApp.President = Ember.Object.extend({
- firstName: '',
- lastName: '',
- fullName: function() {
- return this.get('firstName') + ' ' + this.get('lastName');
- // Call this flag to mark the function as a property
- }.property()
- });
- var president = MyApp.President.create({
- firstName: "Barack",
- lastName: "Obama"
- });
- president.get('fullName'); // "Barack Obama"
- ```
- Treating a function like a property is useful because they can work with
- bindings, just like any other property.
- Many computed properties have dependencies on other properties. For
- example, in the above example, the `fullName` property depends on
- `firstName` and `lastName` to determine its value. You can tell Ember
- about these dependencies like this:
- ```javascript
- MyApp.President = Ember.Object.extend({
- firstName: '',
- lastName: '',
- fullName: function() {
- return this.get('firstName') + ' ' + this.get('lastName');
- // Tell Ember.js that this computed property depends on firstName
- // and lastName
- }.property('firstName', 'lastName')
- });
- ```
- Make sure you list these dependencies so Ember knows when to update
- bindings that connect to a computed property. Changing a dependency
- will not immediately trigger an update of the computed property, but
- will instead clear the cache so that it is updated when the next `get`
- is called on the property.
- See [Ember.ComputedProperty](/api/classes/Ember.ComputedProperty.html), [Ember.computed](/api/#method_computed).
- @method property
- @for Function
- */
- Function.prototype.property = function() {
- var ret = Ember.computed(this);
- // ComputedProperty.prototype.property expands properties; no need for us to
- // do so here.
- return ret.property.apply(ret, arguments);
- };
- /**
- The `observes` extension of Javascript's Function prototype is available
- when `Ember.EXTEND_PROTOTYPES` or `Ember.EXTEND_PROTOTYPES.Function` is
- true, which is the default.
- You can observe property changes simply by adding the `observes`
- call to the end of your method declarations in classes that you write.
- For example:
- ```javascript
- Ember.Object.extend({
- valueObserver: function() {
- // Executes whenever the "value" property changes
- }.observes('value')
- });
- ```
- In the future this method may become asynchronous. If you want to ensure
- synchronous behavior, use `observesImmediately`.
- See `Ember.observer`.
- @method observes
- @for Function
- */
- Function.prototype.observes = function() {
- var addWatchedProperty = function (obs) { watched.push(obs); };
- var watched = [];
- for (var i=0; i<arguments.length; ++i) {
- expandProperties(arguments[i], addWatchedProperty);
- }
- this.__ember_observes__ = watched;
- return this;
- };
- /**
- The `observesImmediately` extension of Javascript's Function prototype is
- available when `Ember.EXTEND_PROTOTYPES` or
- `Ember.EXTEND_PROTOTYPES.Function` is true, which is the default.
- You can observe property changes simply by adding the `observesImmediately`
- call to the end of your method declarations in classes that you write.
- For example:
- ```javascript
- Ember.Object.extend({
- valueObserver: function() {
- // Executes immediately after the "value" property changes
- }.observesImmediately('value')
- });
- ```
- In the future, `observes` may become asynchronous. In this event,
- `observesImmediately` will maintain the synchronous behavior.
- See `Ember.immediateObserver`.
- @method observesImmediately
- @for Function
- */
- Function.prototype.observesImmediately = function() {
- for (var i=0, l=arguments.length; i<l; i++) {
- var arg = arguments[i];
- Ember.assert("Immediate observers must observe internal properties only, not properties on other objects.", arg.indexOf('.') === -1);
- }
- // observes handles property expansion
- return this.observes.apply(this, arguments);
- };
- /**
- The `observesBefore` extension of Javascript's Function prototype is
- available when `Ember.EXTEND_PROTOTYPES` or
- `Ember.EXTEND_PROTOTYPES.Function` is true, which is the default.
- You can get notified when a property change is about to happen by
- by adding the `observesBefore` call to the end of your method
- declarations in classes that you write. For example:
- ```javascript
- Ember.Object.extend({
- valueObserver: function() {
- // Executes whenever the "value" property is about to change
- }.observesBefore('value')
- });
- ```
- See `Ember.beforeObserver`.
- @method observesBefore
- @for Function
- */
- Function.prototype.observesBefore = function() {
- var addWatchedProperty = function (obs) { watched.push(obs); };
- var watched = [];
- for (var i=0; i<arguments.length; ++i) {
- expandProperties(arguments[i], addWatchedProperty);
- }
- this.__ember_observesBefore__ = watched;
- return this;
- };
- /**
- The `on` extension of Javascript's Function prototype is available
- when `Ember.EXTEND_PROTOTYPES` or `Ember.EXTEND_PROTOTYPES.Function` is
- true, which is the default.
- You can listen for events simply by adding the `on` call to the end of
- your method declarations in classes or mixins that you write. For example:
- ```javascript
- Ember.Mixin.create({
- doSomethingWithElement: function() {
- // Executes whenever the "didInsertElement" event fires
- }.on('didInsertElement')
- });
- ```
- See `Ember.on`.
- @method on
- @for Function
- */
- Function.prototype.on = function() {
- var events = a_slice.call(arguments);
- this.__ember_listens__ = events;
- return this;
- };
- }
- })();
- (function() {
- })();
- (function() {
- /**
- @module ember
- @submodule ember-runtime
- */
- /**
- Implements some standard methods for comparing objects. Add this mixin to
- any class you create that can compare its instances.
- You should implement the `compare()` method.
- @class Comparable
- @namespace Ember
- @since Ember 0.9
- */
- Ember.Comparable = Ember.Mixin.create({
- /**
- Override to return the result of the comparison of the two parameters. The
- compare method should return:
- - `-1` if `a < b`
- - `0` if `a == b`
- - `1` if `a > b`
- Default implementation raises an exception.
- @method compare
- @param a {Object} the first object to compare
- @param b {Object} the second object to compare
- @return {Integer} the result of the comparison
- */
- compare: Ember.required(Function)
- });
- })();
- (function() {
- /**
- @module ember
- @submodule ember-runtime
- */
- var get = Ember.get, set = Ember.set;
- /**
- Implements some standard methods for copying an object. Add this mixin to
- any object you create that can create a copy of itself. This mixin is
- added automatically to the built-in array.
- You should generally implement the `copy()` method to return a copy of the
- receiver.
- Note that `frozenCopy()` will only work if you also implement
- `Ember.Freezable`.
- @class Copyable
- @namespace Ember
- @since Ember 0.9
- */
- Ember.Copyable = Ember.Mixin.create({
- /**
- Override to return a copy of the receiver. Default implementation raises
- an exception.
- @method copy
- @param {Boolean} deep if `true`, a deep copy of the object should be made
- @return {Object} copy of receiver
- */
- copy: Ember.required(Function),
- /**
- If the object implements `Ember.Freezable`, then this will return a new
- copy if the object is not frozen and the receiver if the object is frozen.
- Raises an exception if you try to call this method on a object that does
- not support freezing.
- You should use this method whenever you want a copy of a freezable object
- since a freezable object can simply return itself without actually
- consuming more memory.
- @method frozenCopy
- @return {Object} copy of receiver or receiver
- */
- frozenCopy: function() {
- if (Ember.Freezable && Ember.Freezable.detect(this)) {
- return get(this, 'isFrozen') ? this : this.copy().freeze();
- } else {
- throw new Ember.Error(Ember.String.fmt("%@ does not support freezing", [this]));
- }
- }
- });
- })();
- (function() {
- /**
- @module ember
- @submodule ember-runtime
- */
- var get = Ember.get, set = Ember.set;
- /**
- The `Ember.Freezable` mixin implements some basic methods for marking an
- object as frozen. Once an object is frozen it should be read only. No changes
- may be made the internal state of the object.
- ## Enforcement
- To fully support freezing in your subclass, you must include this mixin and
- override any method that might alter any property on the object to instead
- raise an exception. You can check the state of an object by checking the
- `isFrozen` property.
- Although future versions of JavaScript may support language-level freezing
- object objects, that is not the case today. Even if an object is freezable,
- it is still technically possible to modify the object, even though it could
- break other parts of your application that do not expect a frozen object to
- change. It is, therefore, very important that you always respect the
- `isFrozen` property on all freezable objects.
- ## Example Usage
- The example below shows a simple object that implement the `Ember.Freezable`
- protocol.
- ```javascript
- Contact = Ember.Object.extend(Ember.Freezable, {
- firstName: null,
- lastName: null,
- // swaps the names
- swapNames: function() {
- if (this.get('isFrozen')) throw Ember.FROZEN_ERROR;
- var tmp = this.get('firstName');
- this.set('firstName', this.get('lastName'));
- this.set('lastName', tmp);
- return this;
- }
- });
- c = Contact.create({ firstName: "John", lastName: "Doe" });
- c.swapNames(); // returns c
- c.freeze();
- c.swapNames(); // EXCEPTION
- ```
- ## Copying
- Usually the `Ember.Freezable` protocol is implemented in cooperation with the
- `Ember.Copyable` protocol, which defines a `frozenCopy()` method that will
- return a frozen object, if the object implements this method as well.
- @class Freezable
- @namespace Ember
- @since Ember 0.9
- */
- Ember.Freezable = Ember.Mixin.create({
- /**
- Set to `true` when the object is frozen. Use this property to detect
- whether your object is frozen or not.
- @property isFrozen
- @type Boolean
- */
- isFrozen: false,
- /**
- Freezes the object. Once this method has been called the object should
- no longer allow any properties to be edited.
- @method freeze
- @return {Object} receiver
- */
- freeze: function() {
- if (get(this, 'isFrozen')) return this;
- set(this, 'isFrozen', true);
- return this;
- }
- });
- Ember.FROZEN_ERROR = "Frozen object cannot be modified.";
- })();
- (function() {
- /**
- @module ember
- @submodule ember-runtime
- */
- var forEach = Ember.EnumerableUtils.forEach;
- /**
- This mixin defines the API for modifying generic enumerables. These methods
- can be applied to an object regardless of whether it is ordered or
- unordered.
- Note that an Enumerable can change even if it does not implement this mixin.
- For example, a MappedEnumerable cannot be directly modified but if its
- underlying enumerable changes, it will change also.
- ## Adding Objects
- To add an object to an enumerable, use the `addObject()` method. This
- method will only add the object to the enumerable if the object is not
- already present and is of a type supported by the enumerable.
- ```javascript
- set.addObject(contact);
- ```
- ## Removing Objects
- To remove an object from an enumerable, use the `removeObject()` method. This
- will only remove the object if it is present in the enumerable, otherwise
- this method has no effect.
- ```javascript
- set.removeObject(contact);
- ```
- ## Implementing In Your Own Code
- If you are implementing an object and want to support this API, just include
- this mixin in your class and implement the required methods. In your unit
- tests, be sure to apply the Ember.MutableEnumerableTests to your object.
- @class MutableEnumerable
- @namespace Ember
- @uses Ember.Enumerable
- */
- Ember.MutableEnumerable = Ember.Mixin.create(Ember.Enumerable, {
- /**
- __Required.__ You must implement this method to apply this mixin.
- Attempts to add the passed object to the receiver if the object is not
- already present in the collection. If the object is present, this method
- has no effect.
- If the passed object is of a type not supported by the receiver,
- then this method should raise an exception.
- @method addObject
- @param {Object} object The object to add to the enumerable.
- @return {Object} the passed object
- */
- addObject: Ember.required(Function),
- /**
- Adds each object in the passed enumerable to the receiver.
- @method addObjects
- @param {Ember.Enumerable} objects the objects to add.
- @return {Object} receiver
- */
- addObjects: function(objects) {
- Ember.beginPropertyChanges(this);
- forEach(objects, function(obj) { this.addObject(obj); }, this);
- Ember.endPropertyChanges(this);
- return this;
- },
- /**
- __Required.__ You must implement this method to apply this mixin.
- Attempts to remove the passed object from the receiver collection if the
- object is present in the collection. If the object is not present,
- this method has no effect.
- If the passed object is of a type not supported by the receiver,
- then this method should raise an exception.
- @method removeObject
- @param {Object} object The object to remove from the enumerable.
- @return {Object} the passed object
- */
- removeObject: Ember.required(Function),
- /**
- Removes each object in the passed enumerable from the receiver.
- @method removeObjects
- @param {Ember.Enumerable} objects the objects to remove
- @return {Object} receiver
- */
- removeObjects: function(objects) {
- Ember.beginPropertyChanges(this);
- forEach(objects, function(obj) { this.removeObject(obj); }, this);
- Ember.endPropertyChanges(this);
- return this;
- }
- });
- })();
- (function() {
- /**
- @module ember
- @submodule ember-runtime
- */
- // ..........................................................
- // CONSTANTS
- //
- var OUT_OF_RANGE_EXCEPTION = "Index out of range" ;
- var EMPTY = [];
- // ..........................................................
- // HELPERS
- //
- var get = Ember.get, set = Ember.set;
- /**
- This mixin defines the API for modifying array-like objects. These methods
- can be applied only to a collection that keeps its items in an ordered set.
- Note that an Array can change even if it does not implement this mixin.
- For example, one might implement a SparseArray that cannot be directly
- modified, but if its underlying enumerable changes, it will change also.
- @class MutableArray
- @namespace Ember
- @uses Ember.Array
- @uses Ember.MutableEnumerable
- */
- Ember.MutableArray = Ember.Mixin.create(Ember.Array, Ember.MutableEnumerable, {
- /**
- __Required.__ You must implement this method to apply this mixin.
- This is one of the primitives you must implement to support `Ember.Array`.
- You should replace amt objects started at idx with the objects in the
- passed array. You should also call `this.enumerableContentDidChange()`
- @method replace
- @param {Number} idx Starting index in the array to replace. If
- idx >= length, then append to the end of the array.
- @param {Number} amt Number of elements that should be removed from
- the array, starting at *idx*.
- @param {Array} objects An array of zero or more objects that should be
- inserted into the array at *idx*
- */
- replace: Ember.required(),
- /**
- Remove all elements from self. This is useful if you
- want to reuse an existing array without having to recreate it.
- ```javascript
- var colors = ["red", "green", "blue"];
- color.length(); // 3
- colors.clear(); // []
- colors.length(); // 0
- ```
- @method clear
- @return {Ember.Array} An empty Array.
- */
- clear: function () {
- var len = get(this, 'length');
- if (len === 0) return this;
- this.replace(0, len, EMPTY);
- return this;
- },
- /**
- This will use the primitive `replace()` method to insert an object at the
- specified index.
- ```javascript
- var colors = ["red", "green", "blue"];
- colors.insertAt(2, "yellow"); // ["red", "green", "yellow", "blue"]
- colors.insertAt(5, "orange"); // Error: Index out of range
- ```
- @method insertAt
- @param {Number} idx index of insert the object at.
- @param {Object} object object to insert
- @return this
- */
- insertAt: function(idx, object) {
- if (idx > get(this, 'length')) throw new Ember.Error(OUT_OF_RANGE_EXCEPTION) ;
- this.replace(idx, 0, [object]) ;
- return this ;
- },
- /**
- Remove an object at the specified index using the `replace()` primitive
- method. You can pass either a single index, or a start and a length.
- If you pass a start and length that is beyond the
- length this method will throw an `OUT_OF_RANGE_EXCEPTION`.
- ```javascript
- var colors = ["red", "green", "blue", "yellow", "orange"];
- colors.removeAt(0); // ["green", "blue", "yellow", "orange"]
- colors.removeAt(2, 2); // ["green", "blue"]
- colors.removeAt(4, 2); // Error: Index out of range
- ```
- @method removeAt
- @param {Number} start index, start of range
- @param {Number} len length of passing range
- @return {Object} receiver
- */
- removeAt: function(start, len) {
- if ('number' === typeof start) {
- if ((start < 0) || (start >= get(this, 'length'))) {
- throw new Ember.Error(OUT_OF_RANGE_EXCEPTION);
- }
- // fast case
- if (len === undefined) len = 1;
- this.replace(start, len, EMPTY);
- }
- return this ;
- },
- /**
- Push the object onto the end of the array. Works just like `push()` but it
- is KVO-compliant.
- ```javascript
- var colors = ["red", "green"];
- colors.pushObject("black"); // ["red", "green", "black"]
- colors.pushObject(["yellow"]); // ["red", "green", ["yellow"]]
- ```
- @method pushObject
- @param {*} obj object to push
- @return The same obj passed as param
- */
- pushObject: function(obj) {
- this.insertAt(get(this, 'length'), obj) ;
- return obj;
- },
- /**
- Add the objects in the passed numerable to the end of the array. Defers
- notifying observers of the change until all objects are added.
- ```javascript
- var colors = ["red"];
- colors.pushObjects(["yellow", "orange"]); // ["red", "yellow", "orange"]
- ```
- @method pushObjects
- @param {Ember.Enumerable} objects the objects to add
- @return {Ember.Array} receiver
- */
- pushObjects: function(objects) {
- if (!(Ember.Enumerable.detect(objects) || Ember.isArray(objects))) {
- throw new TypeError("Must pass Ember.Enumerable to Ember.MutableArray#pushObjects");
- }
- this.replace(get(this, 'length'), 0, objects);
- return this;
- },
- /**
- Pop object from array or nil if none are left. Works just like `pop()` but
- it is KVO-compliant.
- ```javascript
- var colors = ["red", "green", "blue"];
- colors.popObject(); // "blue"
- console.log(colors); // ["red", "green"]
- ```
- @method popObject
- @return object
- */
- popObject: function() {
- var len = get(this, 'length') ;
- if (len === 0) return null ;
- var ret = this.objectAt(len-1) ;
- this.removeAt(len-1, 1) ;
- return ret ;
- },
- /**
- Shift an object from start of array or nil if none are left. Works just
- like `shift()` but it is KVO-compliant.
- ```javascript
- var colors = ["red", "green", "blue"];
- colors.shiftObject(); // "red"
- console.log(colors); // ["green", "blue"]
- ```
- @method shiftObject
- @return object
- */
- shiftObject: function() {
- if (get(this, 'length') === 0) return null ;
- var ret = this.objectAt(0) ;
- this.removeAt(0) ;
- return ret ;
- },
- /**
- Unshift an object to start of array. Works just like `unshift()` but it is
- KVO-compliant.
- ```javascript
- var colors = ["red"];
- colors.unshiftObject("yellow"); // ["yellow", "red"]
- colors.unshiftObject(["black"]); // [["black"], "yellow", "red"]
- ```
- @method unshiftObject
- @param {*} obj object to unshift
- @return The same obj passed as param
- */
- unshiftObject: function(obj) {
- this.insertAt(0, obj) ;
- return obj ;
- },
- /**
- Adds the named objects to the beginning of the array. Defers notifying
- observers until all objects have been added.
- ```javascript
- var colors = ["red"];
- colors.unshiftObjects(["black", "white"]); // ["black", "white", "red"]
- colors.unshiftObjects("yellow"); // Type Error: 'undefined' is not a function
- ```
- @method unshiftObjects
- @param {Ember.Enumerable} objects the objects to add
- @return {Ember.Array} receiver
- */
- unshiftObjects: function(objects) {
- this.replace(0, 0, objects);
- return this;
- },
- /**
- Reverse objects in the array. Works just like `reverse()` but it is
- KVO-compliant.
- @method reverseObjects
- @return {Ember.Array} receiver
- */
- reverseObjects: function() {
- var len = get(this, 'length');
- if (len === 0) return this;
- var objects = this.toArray().reverse();
- this.replace(0, len, objects);
- return this;
- },
- /**
- Replace all the the receiver's content with content of the argument.
- If argument is an empty array receiver will be cleared.
- ```javascript
- var colors = ["red", "green", "blue"];
- colors.setObjects(["black", "white"]); // ["black", "white"]
- colors.setObjects([]); // []
- ```
- @method setObjects
- @param {Ember.Array} objects array whose content will be used for replacing
- the content of the receiver
- @return {Ember.Array} receiver with the new content
- */
- setObjects: function(objects) {
- if (objects.length === 0) return this.clear();
- var len = get(this, 'length');
- this.replace(0, len, objects);
- return this;
- },
- // ..........................................................
- // IMPLEMENT Ember.MutableEnumerable
- //
- removeObject: function(obj) {
- var loc = get(this, 'length') || 0;
- while(--loc >= 0) {
- var curObject = this.objectAt(loc) ;
- if (curObject === obj) this.removeAt(loc) ;
- }
- return this ;
- },
- addObject: function(obj) {
- if (!this.contains(obj)) this.pushObject(obj);
- return this ;
- }
- });
- })();
- (function() {
- /**
- @module ember
- @submodule ember-runtime
- */
- var get = Ember.get, set = Ember.set;
- /**
- `Ember.TargetActionSupport` is a mixin that can be included in a class
- to add a `triggerAction` method with semantics similar to the Handlebars
- `{{action}}` helper. In normal Ember usage, the `{{action}}` helper is
- usually the best choice. This mixin is most often useful when you are
- doing more complex event handling in View objects.
- See also `Ember.ViewTargetActionSupport`, which has
- view-aware defaults for target and actionContext.
- @class TargetActionSupport
- @namespace Ember
- @extends Ember.Mixin
- */
- Ember.TargetActionSupport = Ember.Mixin.create({
- target: null,
- action: null,
- actionContext: null,
- targetObject: Ember.computed(function() {
- var target = get(this, 'target');
- if (Ember.typeOf(target) === "string") {
- var value = get(this, target);
- if (value === undefined) { value = get(Ember.lookup, target); }
- return value;
- } else {
- return target;
- }
- }).property('target'),
- actionContextObject: Ember.computed(function() {
- var actionContext = get(this, 'actionContext');
- if (Ember.typeOf(actionContext) === "string") {
- var value = get(this, actionContext);
- if (value === undefined) { value = get(Ember.lookup, actionContext); }
- return value;
- } else {
- return actionContext;
- }
- }).property('actionContext'),
- /**
- Send an `action` with an `actionContext` to a `target`. The action, actionContext
- and target will be retrieved from properties of the object. For example:
- ```javascript
- App.SaveButtonView = Ember.View.extend(Ember.TargetActionSupport, {
- target: Ember.computed.alias('controller'),
- action: 'save',
- actionContext: Ember.computed.alias('context'),
- click: function() {
- this.triggerAction(); // Sends the `save` action, along with the current context
- // to the current controller
- }
- });
- ```
- The `target`, `action`, and `actionContext` can be provided as properties of
- an optional object argument to `triggerAction` as well.
- ```javascript
- App.SaveButtonView = Ember.View.extend(Ember.TargetActionSupport, {
- click: function() {
- this.triggerAction({
- action: 'save',
- target: this.get('controller'),
- actionContext: this.get('context'),
- }); // Sends the `save` action, along with the current context
- // to the current controller
- }
- });
- ```
- The `actionContext` defaults to the object you mixing `TargetActionSupport` into.
- But `target` and `action` must be specified either as properties or with the argument
- to `triggerAction`, or a combination:
- ```javascript
- App.SaveButtonView = Ember.View.extend(Ember.TargetActionSupport, {
- target: Ember.computed.alias('controller'),
- click: function() {
- this.triggerAction({
- action: 'save'
- }); // Sends the `save` action, along with a reference to `this`,
- // to the current controller
- }
- });
- ```
- @method triggerAction
- @param opts {Hash} (optional, with the optional keys action, target and/or actionContext)
- @return {Boolean} true if the action was sent successfully and did not return false
- */
- triggerAction: function(opts) {
- opts = opts || {};
- var action = opts.action || get(this, 'action'),
- target = opts.target || get(this, 'targetObject'),
- actionContext = opts.actionContext;
- function args(options, actionName) {
- var ret = [];
- if (actionName) { ret.push(actionName); }
- return ret.concat(options);
- }
- if (typeof actionContext === 'undefined') {
- actionContext = get(this, 'actionContextObject') || this;
- }
- if (target && action) {
- var ret;
- if (target.send) {
- ret = target.send.apply(target, args(actionContext, action));
- } else {
- Ember.assert("The action '" + action + "' did not exist on " + target, typeof target[action] === 'function');
- ret = target[action].apply(target, args(actionContext));
- }
- if (ret !== false) ret = true;
- return ret;
- } else {
- return false;
- }
- }
- });
- })();
- (function() {
- /**
- @module ember
- @submodule ember-runtime
- */
- /**
- This mixin allows for Ember objects to subscribe to and emit events.
- ```javascript
- App.Person = Ember.Object.extend(Ember.Evented, {
- greet: function() {
- // ...
- this.trigger('greet');
- }
- });
- var person = App.Person.create();
- person.on('greet', function() {
- console.log('Our person has greeted');
- });
- person.greet();
- // outputs: 'Our person has greeted'
- ```
- You can also chain multiple event subscriptions:
- ```javascript
- person.on('greet', function() {
- console.log('Our person has greeted');
- }).one('greet', function() {
- console.log('Offer one-time special');
- }).off('event', this, forgetThis);
- ```
- @class Evented
- @namespace Ember
- */
- Ember.Evented = Ember.Mixin.create({
- /**
- Subscribes to a named event with given function.
- ```javascript
- person.on('didLoad', function() {
- // fired once the person has loaded
- });
- ```
- An optional target can be passed in as the 2nd argument that will
- be set as the "this" for the callback. This is a good way to give your
- function access to the object triggering the event. When the target
- parameter is used the callback becomes the third argument.
- @method on
- @param {String} name The name of the event
- @param {Object} [target] The "this" binding for the callback
- @param {Function} method The callback to execute
- @return this
- */
- on: function(name, target, method) {
- Ember.addListener(this, name, target, method);
- return this;
- },
- /**
- Subscribes a function to a named event and then cancels the subscription
- after the first time the event is triggered. It is good to use ``one`` when
- you only care about the first time an event has taken place.
- This function takes an optional 2nd argument that will become the "this"
- value for the callback. If this argument is passed then the 3rd argument
- becomes the function.
- @method one
- @param {String} name The name of the event
- @param {Object} [target] The "this" binding for the callback
- @param {Function} method The callback to execute
- @return this
- */
- one: function(name, target, method) {
- if (!method) {
- method = target;
- target = null;
- }
- Ember.addListener(this, name, target, method, true);
- return this;
- },
- /**
- Triggers a named event for the object. Any additional arguments
- will be passed as parameters to the functions that are subscribed to the
- event.
- ```javascript
- person.on('didEat', function(food) {
- console.log('person ate some ' + food);
- });
- person.trigger('didEat', 'broccoli');
- // outputs: person ate some broccoli
- ```
- @method trigger
- @param {String} name The name of the event
- @param {Object...} args Optional arguments to pass on
- */
- trigger: function(name) {
- var args = [], i, l;
- for (i = 1, l = arguments.length; i < l; i++) {
- args.push(arguments[i]);
- }
- Ember.sendEvent(this, name, args);
- },
- /**
- Cancels subscription for given name, target, and method.
- @method off
- @param {String} name The name of the event
- @param {Object} target The target of the subscription
- @param {Function} method The function of the subscription
- @return this
- */
- off: function(name, target, method) {
- Ember.removeListener(this, name, target, method);
- return this;
- },
- /**
- Checks to see if object has any subscriptions for named event.
- @method has
- @param {String} name The name of the event
- @return {Boolean} does the object have a subscription for event
- */
- has: function(name) {
- return Ember.hasListeners(this, name);
- }
- });
- })();
- (function() {
- var RSVP = requireModule("rsvp");
- RSVP.configure('async', function(callback, promise) {
- Ember.run.schedule('actions', promise, callback, promise);
- });
- RSVP.Promise.prototype.fail = function(callback, label){
- Ember.deprecate('RSVP.Promise.fail has been renamed as RSVP.Promise.catch');
- return this['catch'](callback, label);
- };
- /**
- @module ember
- @submodule ember-runtime
- */
- var get = Ember.get;
- /**
- @class Deferred
- @namespace Ember
- */
- Ember.DeferredMixin = Ember.Mixin.create({
- /**
- Add handlers to be called when the Deferred object is resolved or rejected.
- @method then
- @param {Function} resolve a callback function to be called when done
- @param {Function} reject a callback function to be called when failed
- */
- then: function(resolve, reject, label) {
- var deferred, promise, entity;
- entity = this;
- deferred = get(this, '_deferred');
- promise = deferred.promise;
- function fulfillmentHandler(fulfillment) {
- if (fulfillment === promise) {
- return resolve(entity);
- } else {
- return resolve(fulfillment);
- }
- }
- return promise.then(resolve && fulfillmentHandler, reject, label);
- },
- /**
- Resolve a Deferred object and call any `doneCallbacks` with the given args.
- @method resolve
- */
- resolve: function(value) {
- var deferred, promise;
- deferred = get(this, '_deferred');
- promise = deferred.promise;
- if (value === this) {
- deferred.resolve(promise);
- } else {
- deferred.resolve(value);
- }
- },
- /**
- Reject a Deferred object and call any `failCallbacks` with the given args.
- @method reject
- */
- reject: function(value) {
- get(this, '_deferred').reject(value);
- },
- _deferred: Ember.computed(function() {
- return RSVP.defer('Ember: DeferredMixin - ' + this);
- })
- });
- })();
- (function() {
- /**
- @module ember
- @submodule ember-runtime
- */
- var get = Ember.get, typeOf = Ember.typeOf;
- /**
- The `Ember.ActionHandler` mixin implements support for moving an `actions`
- property to an `_actions` property at extend time, and adding `_actions`
- to the object's mergedProperties list.
- `Ember.ActionHandler` is used internally by Ember in `Ember.View`,
- `Ember.Controller`, and `Ember.Route`.
- @class ActionHandler
- @namespace Ember
- */
- Ember.ActionHandler = Ember.Mixin.create({
- mergedProperties: ['_actions'],
- /**
- The collection of functions, keyed by name, available on this
- `ActionHandler` as action targets.
- These functions will be invoked when a matching `{{action}}` is triggered
- from within a template and the application's current route is this route.
- Actions can also be invoked from other parts of your application
- via `ActionHandler#send`.
- The `actions` hash will inherit action handlers from
- the `actions` hash defined on extended parent classes
- or mixins rather than just replace the entire hash, e.g.:
- ```js
- App.CanDisplayBanner = Ember.Mixin.create({
- actions: {
- displayBanner: function(msg) {
- // ...
- }
- }
- });
- App.WelcomeRoute = Ember.Route.extend(App.CanDisplayBanner, {
- actions: {
- playMusic: function() {
- // ...
- }
- }
- });
- // `WelcomeRoute`, when active, will be able to respond
- // to both actions, since the actions hash is merged rather
- // then replaced when extending mixins / parent classes.
- this.send('displayBanner');
- this.send('playMusic');
- ```
- Within a Controller, Route, View or Component's action handler,
- the value of the `this` context is the Controller, Route, View or
- Component object:
- ```js
- App.SongRoute = Ember.Route.extend({
- actions: {
- myAction: function() {
- this.controllerFor("song");
- this.transitionTo("other.route");
- ...
- }
- }
- });
- ```
- It is also possible to call `this._super()` from within an
- action handler if it overrides a handler defined on a parent
- class or mixin:
- Take for example the following routes:
- ```js
- App.DebugRoute = Ember.Mixin.create({
- actions: {
- debugRouteInformation: function() {
- console.debug("trololo");
- }
- }
- });
- App.AnnoyingDebugRoute = Ember.Route.extend(App.DebugRoute, {
- actions: {
- debugRouteInformation: function() {
- // also call the debugRouteInformation of mixed in App.DebugRoute
- this._super();
- // show additional annoyance
- window.alert(...);
- }
- }
- });
- ```
- ## Bubbling
- By default, an action will stop bubbling once a handler defined
- on the `actions` hash handles it. To continue bubbling the action,
- you must return `true` from the handler:
- ```js
- App.Router.map(function() {
- this.resource("album", function() {
- this.route("song");
- });
- });
- App.AlbumRoute = Ember.Route.extend({
- actions: {
- startPlaying: function() {
- }
- }
- });
- App.AlbumSongRoute = Ember.Route.extend({
- actions: {
- startPlaying: function() {
- // ...
- if (actionShouldAlsoBeTriggeredOnParentRoute) {
- return true;
- }
- }
- }
- });
- ```
- @property actions
- @type Hash
- @default null
- */
- /**
- Moves `actions` to `_actions` at extend time. Note that this currently
- modifies the mixin themselves, which is technically dubious but
- is practically of little consequence. This may change in the future.
- @private
- @method willMergeMixin
- */
- willMergeMixin: function(props) {
- var hashName;
- if (!props._actions) {
- Ember.assert("'actions' should not be a function", typeof(props.actions) !== 'function');
- if (typeOf(props.actions) === 'object') {
- hashName = 'actions';
- } else if (typeOf(props.events) === 'object') {
- Ember.deprecate('Action handlers contained in an `events` object are deprecated in favor of putting them in an `actions` object', false);
- hashName = 'events';
- }
- if (hashName) {
- props._actions = Ember.merge(props._actions || {}, props[hashName]);
- }
- delete props[hashName];
- }
- },
- /**
- Triggers a named action on the `ActionHandler`. Any parameters
- supplied after the `actionName` string will be passed as arguments
- to the action target function.
- If the `ActionHandler` has its `target` property set, actions may
- bubble to the `target`. Bubbling happens when an `actionName` can
- not be found in the `ActionHandler`'s `actions` hash or if the
- action target function returns `true`.
- Example
- ```js
- App.WelcomeRoute = Ember.Route.extend({
- actions: {
- playTheme: function() {
- this.send('playMusic', 'theme.mp3');
- },
- playMusic: function(track) {
- // ...
- }
- }
- });
- ```
- @method send
- @param {String} actionName The action to trigger
- @param {*} context a context to send with the action
- */
- send: function(actionName) {
- var args = [].slice.call(arguments, 1), target;
- if (this._actions && this._actions[actionName]) {
- if (this._actions[actionName].apply(this, args) === true) {
- // handler returned true, so this action will bubble
- } else {
- return;
- }
- } else if (this.deprecatedSend && this.deprecatedSendHandles && this.deprecatedSendHandles(actionName)) {
- if (this.deprecatedSend.apply(this, [].slice.call(arguments)) === true) {
- // handler return true, so this action will bubble
- } else {
- return;
- }
- }
- if (target = get(this, 'target')) {
- Ember.assert("The `target` for " + this + " (" + target + ") does not have a `send` method", typeof target.send === 'function');
- target.send.apply(target, arguments);
- }
- }
- });
- })();
- (function() {
- var set = Ember.set, get = Ember.get,
- not = Ember.computed.not,
- or = Ember.computed.or;
- /**
- @module ember
- @submodule ember-runtime
- */
- function tap(proxy, promise) {
- return promise.then(function(value) {
- set(proxy, 'isFulfilled', true);
- set(proxy, 'content', value);
- return value;
- }, function(reason) {
- set(proxy, 'isRejected', true);
- set(proxy, 'reason', reason);
- throw reason;
- }, "Ember: PromiseProxy");
- }
- /**
- A low level mixin making ObjectProxy, ObjectController or ArrayController's promise aware.
- ```javascript
- var ObjectPromiseController = Ember.ObjectController.extend(Ember.PromiseProxyMixin);
- var controller = ObjectPromiseController.create({
- promise: $.getJSON('/some/remote/data.json')
- });
- controller.then(function(json){
- // the json
- }, function(reason) {
- // the reason why you have no json
- });
- ```
- the controller has bindable attributes which
- track the promises life cycle
- ```javascript
- controller.get('isPending') //=> true
- controller.get('isSettled') //=> false
- controller.get('isRejected') //=> false
- controller.get('isFulfilled') //=> false
- ```
- When the the $.getJSON completes, and the promise is fulfilled
- with json, the life cycle attributes will update accordingly.
- ```javascript
- controller.get('isPending') //=> false
- controller.get('isSettled') //=> true
- controller.get('isRejected') //=> false
- controller.get('isFulfilled') //=> true
- ```
- As the controller is an ObjectController, and the json now its content,
- all the json properties will be available directly from the controller.
- ```javascript
- // Assuming the following json:
- {
- firstName: 'Stefan',
- lastName: 'Penner'
- }
- // both properties will accessible on the controller
- controller.get('firstName') //=> 'Stefan'
- controller.get('lastName') //=> 'Penner'
- ```
- If the controller is backing a template, the attributes are
- bindable from within that template
- ```handlebars
- {{#if isPending}}
- loading...
- {{else}}
- firstName: {{firstName}}
- lastName: {{lastName}}
- {{/if}}
- ```
- @class Ember.PromiseProxyMixin
- */
- Ember.PromiseProxyMixin = Ember.Mixin.create({
- /**
- If the proxied promise is rejected this will contain the reason
- provided.
- @property reason
- @default null
- */
- reason: null,
- /**
- Once the proxied promise has settled this will become `false`.
- @property isPending
- @default true
- */
- isPending: not('isSettled').readOnly(),
- /**
- Once the proxied promise has settled this will become `true`.
- @property isSettled
- @default false
- */
- isSettled: or('isRejected', 'isFulfilled').readOnly(),
- /**
- Will become `true` if the proxied promise is rejected.
- @property isRejected
- @default false
- */
- isRejected: false,
- /**
- Will become `true` if the proxied promise is fulfilled.
- @property isFullfilled
- @default false
- */
- isFulfilled: false,
- /**
- The promise whose fulfillment value is being proxied by this object.
- This property must be specified upon creation, and should not be
- changed once created.
- Example:
- ```javascript
- Ember.ObjectController.extend(Ember.PromiseProxyMixin).create({
- promise: <thenable>
- });
- ```
- @property promise
- */
- promise: Ember.computed(function(key, promise) {
- if (arguments.length === 2) {
- return tap(this, promise);
- } else {
- throw new Ember.Error("PromiseProxy's promise must be set");
- }
- }),
- /**
- An alias to the proxied promise's `then`.
- See RSVP.Promise.then.
- @method then
- @param {Function} callback
- @return {RSVP.Promise}
- */
- then: promiseAlias('then'),
- /**
- An alias to the proxied promise's `catch`.
- See RSVP.Promise.catch.
- @method catch
- @param {Function} callback
- @return {RSVP.Promise}
- */
- 'catch': promiseAlias('catch'),
- /**
- An alias to the proxied promise's `finally`.
- See RSVP.Promise.finally.
- @method finally
- @param {Function} callback
- @return {RSVP.Promise}
- */
- 'finally': promiseAlias('finally')
- });
- function promiseAlias(name) {
- return function () {
- var promise = get(this, 'promise');
- return promise[name].apply(promise, arguments);
- };
- }
- })();
- (function() {
- })();
- (function() {
- var get = Ember.get,
- forEach = Ember.EnumerableUtils.forEach,
- RETAIN = 'r',
- INSERT = 'i',
- DELETE = 'd';
- /**
- An `Ember.TrackedArray` tracks array operations. It's useful when you want to
- lazily compute the indexes of items in an array after they've been shifted by
- subsequent operations.
- @class TrackedArray
- @namespace Ember
- @param {array} [items=[]] The array to be tracked. This is used just to get
- the initial items for the starting state of retain:n.
- */
- Ember.TrackedArray = function (items) {
- if (arguments.length < 1) { items = []; }
- var length = get(items, 'length');
- if (length) {
- this._operations = [new ArrayOperation(RETAIN, length, items)];
- } else {
- this._operations = [];
- }
- };
- Ember.TrackedArray.RETAIN = RETAIN;
- Ember.TrackedArray.INSERT = INSERT;
- Ember.TrackedArray.DELETE = DELETE;
- Ember.TrackedArray.prototype = {
- /**
- Track that `newItems` were added to the tracked array at `index`.
- @method addItems
- @param index
- @param newItems
- */
- addItems: function (index, newItems) {
- var count = get(newItems, 'length');
- if (count < 1) { return; }
- var match = this._findArrayOperation(index),
- arrayOperation = match.operation,
- arrayOperationIndex = match.index,
- arrayOperationRangeStart = match.rangeStart,
- composeIndex,
- splitIndex,
- splitItems,
- splitArrayOperation,
- newArrayOperation;
- newArrayOperation = new ArrayOperation(INSERT, count, newItems);
- if (arrayOperation) {
- if (!match.split) {
- // insert left of arrayOperation
- this._operations.splice(arrayOperationIndex, 0, newArrayOperation);
- composeIndex = arrayOperationIndex;
- } else {
- this._split(arrayOperationIndex, index - arrayOperationRangeStart, newArrayOperation);
- composeIndex = arrayOperationIndex + 1;
- }
- } else {
- // insert at end
- this._operations.push(newArrayOperation);
- composeIndex = arrayOperationIndex;
- }
- this._composeInsert(composeIndex);
- },
- /**
- Track that `count` items were removed at `index`.
- @method removeItems
- @param index
- @param count
- */
- removeItems: function (index, count) {
- if (count < 1) { return; }
- var match = this._findArrayOperation(index),
- arrayOperation = match.operation,
- arrayOperationIndex = match.index,
- arrayOperationRangeStart = match.rangeStart,
- newArrayOperation,
- composeIndex;
- newArrayOperation = new ArrayOperation(DELETE, count);
- if (!match.split) {
- // insert left of arrayOperation
- this._operations.splice(arrayOperationIndex, 0, newArrayOperation);
- composeIndex = arrayOperationIndex;
- } else {
- this._split(arrayOperationIndex, index - arrayOperationRangeStart, newArrayOperation);
- composeIndex = arrayOperationIndex + 1;
- }
- return this._composeDelete(composeIndex);
- },
- /**
- Apply all operations, reducing them to retain:n, for `n`, the number of
- items in the array.
- `callback` will be called for each operation and will be passed the following arguments:
- * {array} items The items for the given operation
- * {number} offset The computed offset of the items, ie the index in the
- array of the first item for this operation.
- * {string} operation The type of the operation. One of
- `Ember.TrackedArray.{RETAIN, DELETE, INSERT}`
- @method apply
- @param {function} callback
- */
- apply: function (callback) {
- var items = [],
- offset = 0;
- forEach(this._operations, function (arrayOperation) {
- callback(arrayOperation.items, offset, arrayOperation.type);
- if (arrayOperation.type !== DELETE) {
- offset += arrayOperation.count;
- items = items.concat(arrayOperation.items);
- }
- });
- this._operations = [new ArrayOperation(RETAIN, items.length, items)];
- },
- /**
- Return an `ArrayOperationMatch` for the operation that contains the item at `index`.
- @method _findArrayOperation
- @param {number} index the index of the item whose operation information
- should be returned.
- @private
- */
- _findArrayOperation: function (index) {
- var arrayOperationIndex,
- len,
- split = false,
- arrayOperation,
- arrayOperationRangeStart,
- arrayOperationRangeEnd;
- // OPTIMIZE: we could search these faster if we kept a balanced tree.
- // find leftmost arrayOperation to the right of `index`
- for (arrayOperationIndex = arrayOperationRangeStart = 0, len = this._operations.length; arrayOperationIndex < len; ++arrayOperationIndex) {
- arrayOperation = this._operations[arrayOperationIndex];
- if (arrayOperation.type === DELETE) { continue; }
- arrayOperationRangeEnd = arrayOperationRangeStart + arrayOperation.count - 1;
- if (index === arrayOperationRangeStart) {
- break;
- } else if (index > arrayOperationRangeStart && index <= arrayOperationRangeEnd) {
- split = true;
- break;
- } else {
- arrayOperationRangeStart = arrayOperationRangeEnd + 1;
- }
- }
- return new ArrayOperationMatch(arrayOperation, arrayOperationIndex, split, arrayOperationRangeStart);
- },
- _split: function (arrayOperationIndex, splitIndex, newArrayOperation) {
- var arrayOperation = this._operations[arrayOperationIndex],
- splitItems = arrayOperation.items.slice(splitIndex),
- splitArrayOperation = new ArrayOperation(arrayOperation.type, splitItems.length, splitItems);
- // truncate LHS
- arrayOperation.count = splitIndex;
- arrayOperation.items = arrayOperation.items.slice(0, splitIndex);
- this._operations.splice(arrayOperationIndex + 1, 0, newArrayOperation, splitArrayOperation);
- },
- // see SubArray for a better implementation.
- _composeInsert: function (index) {
- var newArrayOperation = this._operations[index],
- leftArrayOperation = this._operations[index-1], // may be undefined
- rightArrayOperation = this._operations[index+1], // may be undefined
- leftOp = leftArrayOperation && leftArrayOperation.type,
- rightOp = rightArrayOperation && rightArrayOperation.type;
- if (leftOp === INSERT) {
- // merge left
- leftArrayOperation.count += newArrayOperation.count;
- leftArrayOperation.items = leftArrayOperation.items.concat(newArrayOperation.items);
- if (rightOp === INSERT) {
- // also merge right (we have split an insert with an insert)
- leftArrayOperation.count += rightArrayOperation.count;
- leftArrayOperation.items = leftArrayOperation.items.concat(rightArrayOperation.items);
- this._operations.splice(index, 2);
- } else {
- // only merge left
- this._operations.splice(index, 1);
- }
- } else if (rightOp === INSERT) {
- // merge right
- newArrayOperation.count += rightArrayOperation.count;
- newArrayOperation.items = newArrayOperation.items.concat(rightArrayOperation.items);
- this._operations.splice(index + 1, 1);
- }
- },
- _composeDelete: function (index) {
- var arrayOperation = this._operations[index],
- deletesToGo = arrayOperation.count,
- leftArrayOperation = this._operations[index-1], // may be undefined
- leftOp = leftArrayOperation && leftArrayOperation.type,
- nextArrayOperation,
- nextOp,
- nextCount,
- removeNewAndNextOp = false,
- removedItems = [];
- if (leftOp === DELETE) {
- arrayOperation = leftArrayOperation;
- index -= 1;
- }
- for (var i = index + 1; deletesToGo > 0; ++i) {
- nextArrayOperation = this._operations[i];
- nextOp = nextArrayOperation.type;
- nextCount = nextArrayOperation.count;
- if (nextOp === DELETE) {
- arrayOperation.count += nextCount;
- continue;
- }
- if (nextCount > deletesToGo) {
- // d:2 {r,i}:5 we reduce the retain or insert, but it stays
- removedItems = removedItems.concat(nextArrayOperation.items.splice(0, deletesToGo));
- nextArrayOperation.count -= deletesToGo;
- // In the case where we truncate the last arrayOperation, we don't need to
- // remove it; also the deletesToGo reduction is not the entirety of
- // nextCount
- i -= 1;
- nextCount = deletesToGo;
- deletesToGo = 0;
- } else {
- if (nextCount === deletesToGo) {
- // Handle edge case of d:2 i:2 in which case both operations go away
- // during composition.
- removeNewAndNextOp = true;
- }
- removedItems = removedItems.concat(nextArrayOperation.items);
- deletesToGo -= nextCount;
- }
- if (nextOp === INSERT) {
- // d:2 i:3 will result in delete going away
- arrayOperation.count -= nextCount;
- }
- }
- if (arrayOperation.count > 0) {
- // compose our new delete with possibly several operations to the right of
- // disparate types
- this._operations.splice(index+1, i-1-index);
- } else {
- // The delete operation can go away; it has merely reduced some other
- // operation, as in d:3 i:4; it may also have eliminated that operation,
- // as in d:3 i:3.
- this._operations.splice(index, removeNewAndNextOp ? 2 : 1);
- }
- return removedItems;
- },
- toString: function () {
- var str = "";
- forEach(this._operations, function (operation) {
- str += " " + operation.type + ":" + operation.count;
- });
- return str.substring(1);
- }
- };
- /**
- Internal data structure to represent an array operation.
- @method ArrayOperation
- @private
- @param {string} type The type of the operation. One of
- `Ember.TrackedArray.{RETAIN, INSERT, DELETE}`
- @param {number} count The number of items in this operation.
- @param {array} items The items of the operation, if included. RETAIN and
- INSERT include their items, DELETE does not.
- */
- function ArrayOperation (operation, count, items) {
- this.type = operation; // RETAIN | INSERT | DELETE
- this.count = count;
- this.items = items;
- }
- /**
- Internal data structure used to include information when looking up operations
- by item index.
- @method ArrayOperationMatch
- @private
- @param {ArrayOperation} operation
- @param {number} index The index of `operation` in the array of operations.
- @param {boolean} split Whether or not the item index searched for would
- require a split for a new operation type.
- @param {number} rangeStart The index of the first item in the operation,
- with respect to the tracked array. The index of the last item can be computed
- from `rangeStart` and `operation.count`.
- */
- function ArrayOperationMatch(operation, index, split, rangeStart) {
- this.operation = operation;
- this.index = index;
- this.split = split;
- this.rangeStart = rangeStart;
- }
- })();
- (function() {
- var get = Ember.get,
- forEach = Ember.EnumerableUtils.forEach,
- RETAIN = 'r',
- FILTER = 'f';
- function Operation (type, count) {
- this.type = type;
- this.count = count;
- }
- /**
- An `Ember.SubArray` tracks an array in a way similar to, but more specialized
- than, `Ember.TrackedArray`. It is useful for keeping track of the indexes of
- items within a filtered array.
- @class SubArray
- @namespace Ember
- */
- Ember.SubArray = function (length) {
- if (arguments.length < 1) { length = 0; }
- if (length > 0) {
- this._operations = [new Operation(RETAIN, length)];
- } else {
- this._operations = [];
- }
- };
- Ember.SubArray.prototype = {
- /**
- Track that an item was added to the tracked array.
- @method addItem
- @param {number} index The index of the item in the tracked array.
- @param {boolean} match `true` iff the item is included in the subarray.
- @return {number} The index of the item in the subarray.
- */
- addItem: function(index, match) {
- var returnValue = -1,
- itemType = match ? RETAIN : FILTER,
- self = this;
- this._findOperation(index, function(operation, operationIndex, rangeStart, rangeEnd, seenInSubArray) {
- var newOperation, splitOperation;
- if (itemType === operation.type) {
- ++operation.count;
- } else if (index === rangeStart) {
- // insert to the left of `operation`
- self._operations.splice(operationIndex, 0, new Operation(itemType, 1));
- } else {
- newOperation = new Operation(itemType, 1);
- splitOperation = new Operation(operation.type, rangeEnd - index + 1);
- operation.count = index - rangeStart;
- self._operations.splice(operationIndex + 1, 0, newOperation, splitOperation);
- }
- if (match) {
- if (operation.type === RETAIN) {
- returnValue = seenInSubArray + (index - rangeStart);
- } else {
- returnValue = seenInSubArray;
- }
- }
- self._composeAt(operationIndex);
- }, function(seenInSubArray) {
- self._operations.push(new Operation(itemType, 1));
- if (match) {
- returnValue = seenInSubArray;
- }
- self._composeAt(self._operations.length-1);
- });
- return returnValue;
- },
- /**
- Track that an item was removed from the tracked array.
- @method removeItem
- @param {number} index The index of the item in the tracked array.
- @return {number} The index of the item in the subarray, or `-1` if the item
- was not in the subarray.
- */
- removeItem: function(index) {
- var returnValue = -1,
- self = this;
- this._findOperation(index, function (operation, operationIndex, rangeStart, rangeEnd, seenInSubArray) {
- if (operation.type === RETAIN) {
- returnValue = seenInSubArray + (index - rangeStart);
- }
- if (operation.count > 1) {
- --operation.count;
- } else {
- self._operations.splice(operationIndex, 1);
- self._composeAt(operationIndex);
- }
- }, function() {
- throw new Ember.Error("Can't remove an item that has never been added.");
- });
- return returnValue;
- },
- _findOperation: function (index, foundCallback, notFoundCallback) {
- var operationIndex,
- len,
- operation,
- rangeStart,
- rangeEnd,
- seenInSubArray = 0;
- // OPTIMIZE: change to balanced tree
- // find leftmost operation to the right of `index`
- for (operationIndex = rangeStart = 0, len = this._operations.length; operationIndex < len; rangeStart = rangeEnd + 1, ++operationIndex) {
- operation = this._operations[operationIndex];
- rangeEnd = rangeStart + operation.count - 1;
- if (index >= rangeStart && index <= rangeEnd) {
- foundCallback(operation, operationIndex, rangeStart, rangeEnd, seenInSubArray);
- return;
- } else if (operation.type === RETAIN) {
- seenInSubArray += operation.count;
- }
- }
- notFoundCallback(seenInSubArray);
- },
- _composeAt: function(index) {
- var op = this._operations[index],
- otherOp;
- if (!op) {
- // Composing out of bounds is a no-op, as when removing the last operation
- // in the list.
- return;
- }
- if (index > 0) {
- otherOp = this._operations[index-1];
- if (otherOp.type === op.type) {
- op.count += otherOp.count;
- this._operations.splice(index-1, 1);
- --index;
- }
- }
- if (index < this._operations.length-1) {
- otherOp = this._operations[index+1];
- if (otherOp.type === op.type) {
- op.count += otherOp.count;
- this._operations.splice(index+1, 1);
- }
- }
- },
- toString: function () {
- var str = "";
- forEach(this._operations, function (operation) {
- str += " " + operation.type + ":" + operation.count;
- });
- return str.substring(1);
- }
- };
- })();
- (function() {
- Ember.Container = requireModule('container');
- Ember.Container.set = Ember.set;
- })();
- (function() {
- Ember.Application = Ember.Namespace.extend();
- })();
- (function() {
- /**
- @module ember
- @submodule ember-runtime
- */
- var OUT_OF_RANGE_EXCEPTION = "Index out of range";
- var EMPTY = [];
- var get = Ember.get, set = Ember.set;
- /**
- An ArrayProxy wraps any other object that implements `Ember.Array` and/or
- `Ember.MutableArray,` forwarding all requests. This makes it very useful for
- a number of binding use cases or other cases where being able to swap
- out the underlying array is useful.
- A simple example of usage:
- ```javascript
- var pets = ['dog', 'cat', 'fish'];
- var ap = Ember.ArrayProxy.create({ content: Ember.A(pets) });
- ap.get('firstObject'); // 'dog'
- ap.set('content', ['amoeba', 'paramecium']);
- ap.get('firstObject'); // 'amoeba'
- ```
- This class can also be useful as a layer to transform the contents of
- an array, as they are accessed. This can be done by overriding
- `objectAtContent`:
- ```javascript
- var pets = ['dog', 'cat', 'fish'];
- var ap = Ember.ArrayProxy.create({
- content: Ember.A(pets),
- objectAtContent: function(idx) {
- return this.get('content').objectAt(idx).toUpperCase();
- }
- });
- ap.get('firstObject'); // . 'DOG'
- ```
- @class ArrayProxy
- @namespace Ember
- @extends Ember.Object
- @uses Ember.MutableArray
- */
- Ember.ArrayProxy = Ember.Object.extend(Ember.MutableArray, {
- /**
- The content array. Must be an object that implements `Ember.Array` and/or
- `Ember.MutableArray.`
- @property content
- @type Ember.Array
- */
- content: null,
- /**
- The array that the proxy pretends to be. In the default `ArrayProxy`
- implementation, this and `content` are the same. Subclasses of `ArrayProxy`
- can override this property to provide things like sorting and filtering.
- @property arrangedContent
- */
- arrangedContent: Ember.computed.alias('content'),
- /**
- Should actually retrieve the object at the specified index from the
- content. You can override this method in subclasses to transform the
- content item to something new.
- This method will only be called if content is non-`null`.
- @method objectAtContent
- @param {Number} idx The index to retrieve.
- @return {Object} the value or undefined if none found
- */
- objectAtContent: function(idx) {
- return get(this, 'arrangedContent').objectAt(idx);
- },
- /**
- Should actually replace the specified objects on the content array.
- You can override this method in subclasses to transform the content item
- into something new.
- This method will only be called if content is non-`null`.
- @method replaceContent
- @param {Number} idx The starting index
- @param {Number} amt The number of items to remove from the content.
- @param {Array} objects Optional array of objects to insert or null if no
- objects.
- @return {void}
- */
- replaceContent: function(idx, amt, objects) {
- get(this, 'content').replace(idx, amt, objects);
- },
- /**
- Invoked when the content property is about to change. Notifies observers that the
- entire array content will change.
- @private
- @method _contentWillChange
- */
- _contentWillChange: Ember.beforeObserver('content', function() {
- this._teardownContent();
- }),
- _teardownContent: function() {
- var content = get(this, 'content');
- if (content) {
- content.removeArrayObserver(this, {
- willChange: 'contentArrayWillChange',
- didChange: 'contentArrayDidChange'
- });
- }
- },
- contentArrayWillChange: Ember.K,
- contentArrayDidChange: Ember.K,
- /**
- Invoked when the content property changes. Notifies observers that the
- entire array content has changed.
- @private
- @method _contentDidChange
- */
- _contentDidChange: Ember.observer('content', function() {
- var content = get(this, 'content');
- Ember.assert("Can't set ArrayProxy's content to itself", content !== this);
- this._setupContent();
- }),
- _setupContent: function() {
- var content = get(this, 'content');
- if (content) {
- content.addArrayObserver(this, {
- willChange: 'contentArrayWillChange',
- didChange: 'contentArrayDidChange'
- });
- }
- },
- _arrangedContentWillChange: Ember.beforeObserver('arrangedContent', function() {
- var arrangedContent = get(this, 'arrangedContent'),
- len = arrangedContent ? get(arrangedContent, 'length') : 0;
- this.arrangedContentArrayWillChange(this, 0, len, undefined);
- this.arrangedContentWillChange(this);
- this._teardownArrangedContent(arrangedContent);
- }),
- _arrangedContentDidChange: Ember.observer('arrangedContent', function() {
- var arrangedContent = get(this, 'arrangedContent'),
- len = arrangedContent ? get(arrangedContent, 'length') : 0;
- Ember.assert("Can't set ArrayProxy's content to itself", arrangedContent !== this);
- this._setupArrangedContent();
- this.arrangedContentDidChange(this);
- this.arrangedContentArrayDidChange(this, 0, undefined, len);
- }),
- _setupArrangedContent: function() {
- var arrangedContent = get(this, 'arrangedContent');
- if (arrangedContent) {
- arrangedContent.addArrayObserver(this, {
- willChange: 'arrangedContentArrayWillChange',
- didChange: 'arrangedContentArrayDidChange'
- });
- }
- },
- _teardownArrangedContent: function() {
- var arrangedContent = get(this, 'arrangedContent');
- if (arrangedContent) {
- arrangedContent.removeArrayObserver(this, {
- willChange: 'arrangedContentArrayWillChange',
- didChange: 'arrangedContentArrayDidChange'
- });
- }
- },
- arrangedContentWillChange: Ember.K,
- arrangedContentDidChange: Ember.K,
- objectAt: function(idx) {
- return get(this, 'content') && this.objectAtContent(idx);
- },
- length: Ember.computed(function() {
- var arrangedContent = get(this, 'arrangedContent');
- return arrangedContent ? get(arrangedContent, 'length') : 0;
- // No dependencies since Enumerable notifies length of change
- }),
- _replace: function(idx, amt, objects) {
- var content = get(this, 'content');
- Ember.assert('The content property of '+ this.constructor + ' should be set before modifying it', content);
- if (content) this.replaceContent(idx, amt, objects);
- return this;
- },
- replace: function() {
- if (get(this, 'arrangedContent') === get(this, 'content')) {
- this._replace.apply(this, arguments);
- } else {
- throw new Ember.Error("Using replace on an arranged ArrayProxy is not allowed.");
- }
- },
- _insertAt: function(idx, object) {
- if (idx > get(this, 'content.length')) throw new Ember.Error(OUT_OF_RANGE_EXCEPTION);
- this._replace(idx, 0, [object]);
- return this;
- },
- insertAt: function(idx, object) {
- if (get(this, 'arrangedContent') === get(this, 'content')) {
- return this._insertAt(idx, object);
- } else {
- throw new Ember.Error("Using insertAt on an arranged ArrayProxy is not allowed.");
- }
- },
- removeAt: function(start, len) {
- if ('number' === typeof start) {
- var content = get(this, 'content'),
- arrangedContent = get(this, 'arrangedContent'),
- indices = [], i;
- if ((start < 0) || (start >= get(this, 'length'))) {
- throw new Ember.Error(OUT_OF_RANGE_EXCEPTION);
- }
- if (len === undefined) len = 1;
- // Get a list of indices in original content to remove
- for (i=start; i<start+len; i++) {
- // Use arrangedContent here so we avoid confusion with objects transformed by objectAtContent
- indices.push(content.indexOf(arrangedContent.objectAt(i)));
- }
- // Replace in reverse order since indices will change
- indices.sort(function(a,b) { return b - a; });
- Ember.beginPropertyChanges();
- for (i=0; i<indices.length; i++) {
- this._replace(indices[i], 1, EMPTY);
- }
- Ember.endPropertyChanges();
- }
- return this ;
- },
- pushObject: function(obj) {
- this._insertAt(get(this, 'content.length'), obj) ;
- return obj ;
- },
- pushObjects: function(objects) {
- if (!(Ember.Enumerable.detect(objects) || Ember.isArray(objects))) {
- throw new TypeError("Must pass Ember.Enumerable to Ember.MutableArray#pushObjects");
- }
- this._replace(get(this, 'length'), 0, objects);
- return this;
- },
- setObjects: function(objects) {
- if (objects.length === 0) return this.clear();
- var len = get(this, 'length');
- this._replace(0, len, objects);
- return this;
- },
- unshiftObject: function(obj) {
- this._insertAt(0, obj) ;
- return obj ;
- },
- unshiftObjects: function(objects) {
- this._replace(0, 0, objects);
- return this;
- },
- slice: function() {
- var arr = this.toArray();
- return arr.slice.apply(arr, arguments);
- },
- arrangedContentArrayWillChange: function(item, idx, removedCnt, addedCnt) {
- this.arrayContentWillChange(idx, removedCnt, addedCnt);
- },
- arrangedContentArrayDidChange: function(item, idx, removedCnt, addedCnt) {
- this.arrayContentDidChange(idx, removedCnt, addedCnt);
- },
- init: function() {
- this._super();
- this._setupContent();
- this._setupArrangedContent();
- },
- willDestroy: function() {
- this._teardownArrangedContent();
- this._teardownContent();
- }
- });
- })();
- (function() {
- /**
- @module ember
- @submodule ember-runtime
- */
- var set = Ember.set, get = Ember.get, guidFor = Ember.guidFor;
- var forEach = Ember.EnumerableUtils.forEach,
- indexOf = Ember.ArrayPolyfills.indexOf;
- var EachArray = Ember.Object.extend(Ember.Array, {
- init: function(content, keyName, owner) {
- this._super();
- this._keyName = keyName;
- this._owner = owner;
- this._content = content;
- },
- objectAt: function(idx) {
- var item = this._content.objectAt(idx);
- return item && get(item, this._keyName);
- },
- length: Ember.computed(function() {
- var content = this._content;
- return content ? get(content, 'length') : 0;
- })
- });
- var IS_OBSERVER = /^.+:(before|change)$/;
- function addObserverForContentKey(content, keyName, proxy, idx, loc) {
- var objects = proxy._objects, guid;
- if (!objects) objects = proxy._objects = {};
- while(--loc>=idx) {
- var item = content.objectAt(loc);
- if (item) {
- Ember.assert('When using @each to observe the array ' + content + ', the array must return an object', Ember.typeOf(item) === 'instance' || Ember.typeOf(item) === 'object');
- Ember.addBeforeObserver(item, keyName, proxy, 'contentKeyWillChange');
- Ember.addObserver(item, keyName, proxy, 'contentKeyDidChange');
- // keep track of the index each item was found at so we can map
- // it back when the obj changes.
- guid = guidFor(item);
- if (!objects[guid]) objects[guid] = [];
- objects[guid].push(loc);
- }
- }
- }
- function removeObserverForContentKey(content, keyName, proxy, idx, loc) {
- var objects = proxy._objects;
- if (!objects) objects = proxy._objects = {};
- var indicies, guid;
- while(--loc>=idx) {
- var item = content.objectAt(loc);
- if (item) {
- Ember.removeBeforeObserver(item, keyName, proxy, 'contentKeyWillChange');
- Ember.removeObserver(item, keyName, proxy, 'contentKeyDidChange');
- guid = guidFor(item);
- indicies = objects[guid];
- indicies[indexOf.call(indicies, loc)] = null;
- }
- }
- }
- /**
- This is the object instance returned when you get the `@each` property on an
- array. It uses the unknownProperty handler to automatically create
- EachArray instances for property names.
- @private
- @class EachProxy
- @namespace Ember
- @extends Ember.Object
- */
- Ember.EachProxy = Ember.Object.extend({
- init: function(content) {
- this._super();
- this._content = content;
- content.addArrayObserver(this);
- // in case someone is already observing some keys make sure they are
- // added
- forEach(Ember.watchedEvents(this), function(eventName) {
- this.didAddListener(eventName);
- }, this);
- },
- /**
- You can directly access mapped properties by simply requesting them.
- The `unknownProperty` handler will generate an EachArray of each item.
- @method unknownProperty
- @param keyName {String}
- @param value {*}
- */
- unknownProperty: function(keyName, value) {
- var ret;
- ret = new EachArray(this._content, keyName, this);
- Ember.defineProperty(this, keyName, null, ret);
- this.beginObservingContentKey(keyName);
- return ret;
- },
- // ..........................................................
- // ARRAY CHANGES
- // Invokes whenever the content array itself changes.
- arrayWillChange: function(content, idx, removedCnt, addedCnt) {
- var keys = this._keys, key, lim;
- lim = removedCnt>0 ? idx+removedCnt : -1;
- Ember.beginPropertyChanges(this);
- for(key in keys) {
- if (!keys.hasOwnProperty(key)) { continue; }
- if (lim>0) { removeObserverForContentKey(content, key, this, idx, lim); }
- Ember.propertyWillChange(this, key);
- }
- Ember.propertyWillChange(this._content, '@each');
- Ember.endPropertyChanges(this);
- },
- arrayDidChange: function(content, idx, removedCnt, addedCnt) {
- var keys = this._keys, lim;
- lim = addedCnt>0 ? idx+addedCnt : -1;
- Ember.changeProperties(function() {
- for(var key in keys) {
- if (!keys.hasOwnProperty(key)) { continue; }
- if (lim>0) { addObserverForContentKey(content, key, this, idx, lim); }
- Ember.propertyDidChange(this, key);
- }
- Ember.propertyDidChange(this._content, '@each');
- }, this);
- },
- // ..........................................................
- // LISTEN FOR NEW OBSERVERS AND OTHER EVENT LISTENERS
- // Start monitoring keys based on who is listening...
- didAddListener: function(eventName) {
- if (IS_OBSERVER.test(eventName)) {
- this.beginObservingContentKey(eventName.slice(0, -7));
- }
- },
- didRemoveListener: function(eventName) {
- if (IS_OBSERVER.test(eventName)) {
- this.stopObservingContentKey(eventName.slice(0, -7));
- }
- },
- // ..........................................................
- // CONTENT KEY OBSERVING
- // Actual watch keys on the source content.
- beginObservingContentKey: function(keyName) {
- var keys = this._keys;
- if (!keys) keys = this._keys = {};
- if (!keys[keyName]) {
- keys[keyName] = 1;
- var content = this._content,
- len = get(content, 'length');
- addObserverForContentKey(content, keyName, this, 0, len);
- } else {
- keys[keyName]++;
- }
- },
- stopObservingContentKey: function(keyName) {
- var keys = this._keys;
- if (keys && (keys[keyName]>0) && (--keys[keyName]<=0)) {
- var content = this._content,
- len = get(content, 'length');
- removeObserverForContentKey(content, keyName, this, 0, len);
- }
- },
- contentKeyWillChange: function(obj, keyName) {
- Ember.propertyWillChange(this, keyName);
- },
- contentKeyDidChange: function(obj, keyName) {
- Ember.propertyDidChange(this, keyName);
- }
- });
- })();
- (function() {
- /**
- @module ember
- @submodule ember-runtime
- */
- var get = Ember.get, set = Ember.set, replace = Ember.EnumerableUtils._replace;
- // Add Ember.Array to Array.prototype. Remove methods with native
- // implementations and supply some more optimized versions of generic methods
- // because they are so common.
- var NativeArray = Ember.Mixin.create(Ember.MutableArray, Ember.Observable, Ember.Copyable, {
- // because length is a built-in property we need to know to just get the
- // original property.
- get: function(key) {
- if (key==='length') return this.length;
- else if ('number' === typeof key) return this[key];
- else return this._super(key);
- },
- objectAt: function(idx) {
- return this[idx];
- },
- // primitive for array support.
- replace: function(idx, amt, objects) {
- if (this.isFrozen) throw Ember.FROZEN_ERROR;
- // if we replaced exactly the same number of items, then pass only the
- // replaced range. Otherwise, pass the full remaining array length
- // since everything has shifted
- var len = objects ? get(objects, 'length') : 0;
- this.arrayContentWillChange(idx, amt, len);
- if (len === 0) {
- this.splice(idx, amt);
- } else {
- replace(this, idx, amt, objects);
- }
- this.arrayContentDidChange(idx, amt, len);
- return this;
- },
- // If you ask for an unknown property, then try to collect the value
- // from member items.
- unknownProperty: function(key, value) {
- var ret;// = this.reducedProperty(key, value) ;
- if ((value !== undefined) && ret === undefined) {
- ret = this[key] = value;
- }
- return ret ;
- },
- // If browser did not implement indexOf natively, then override with
- // specialized version
- indexOf: function(object, startAt) {
- var idx, len = this.length;
- if (startAt === undefined) startAt = 0;
- else startAt = (startAt < 0) ? Math.ceil(startAt) : Math.floor(startAt);
- if (startAt < 0) startAt += len;
- for(idx=startAt;idx<len;idx++) {
- if (this[idx] === object) return idx ;
- }
- return -1;
- },
- lastIndexOf: function(object, startAt) {
- var idx, len = this.length;
- if (startAt === undefined) startAt = len-1;
- else startAt = (startAt < 0) ? Math.ceil(startAt) : Math.floor(startAt);
- if (startAt < 0) startAt += len;
- for(idx=startAt;idx>=0;idx--) {
- if (this[idx] === object) return idx ;
- }
- return -1;
- },
- copy: function(deep) {
- if (deep) {
- return this.map(function(item) { return Ember.copy(item, true); });
- }
- return this.slice();
- }
- });
- // Remove any methods implemented natively so we don't override them
- var ignore = ['length'];
- Ember.EnumerableUtils.forEach(NativeArray.keys(), function(methodName) {
- if (Array.prototype[methodName]) ignore.push(methodName);
- });
- if (ignore.length>0) {
- NativeArray = NativeArray.without.apply(NativeArray, ignore);
- }
- /**
- The NativeArray mixin contains the properties needed to to make the native
- Array support Ember.MutableArray and all of its dependent APIs. Unless you
- have `Ember.EXTEND_PROTOTYPES` or `Ember.EXTEND_PROTOTYPES.Array` set to
- false, this will be applied automatically. Otherwise you can apply the mixin
- at anytime by calling `Ember.NativeArray.activate`.
- @class NativeArray
- @namespace Ember
- @uses Ember.MutableArray
- @uses Ember.Observable
- @uses Ember.Copyable
- */
- Ember.NativeArray = NativeArray;
- /**
- Creates an `Ember.NativeArray` from an Array like object.
- Does not modify the original object. Ember.A is not needed if
- `Ember.EXTEND_PROTOTYPES` is `true` (the default value). However,
- it is recommended that you use Ember.A when creating addons for
- ember or when you can not guarantee that `Ember.EXTEND_PROTOTYPES`
- will be `true`.
- Example
- ```js
- var Pagination = Ember.CollectionView.extend({
- tagName: 'ul',
- classNames: ['pagination'],
- init: function() {
- this._super();
- if (!this.get('content')) {
- this.set('content', Ember.A([]));
- }
- }
- });
- ```
- @method A
- @for Ember
- @return {Ember.NativeArray}
- */
- Ember.A = function(arr) {
- if (arr === undefined) { arr = []; }
- return Ember.Array.detect(arr) ? arr : Ember.NativeArray.apply(arr);
- };
- /**
- Activates the mixin on the Array.prototype if not already applied. Calling
- this method more than once is safe. This will be called when ember is loaded
- unless you have `Ember.EXTEND_PROTOTYPES` or `Ember.EXTEND_PROTOTYPES.Array`
- set to `false`.
- Example
- ```js
- if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.Array) {
- Ember.NativeArray.activate();
- }
- ```
- @method activate
- @for Ember.NativeArray
- @static
- @return {void}
- */
- Ember.NativeArray.activate = function() {
- NativeArray.apply(Array.prototype);
- Ember.A = function(arr) { return arr || []; };
- };
- if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.Array) {
- Ember.NativeArray.activate();
- }
- })();
- (function() {
- /**
- @module ember
- @submodule ember-runtime
- */
- var get = Ember.get, set = Ember.set, guidFor = Ember.guidFor, isNone = Ember.isNone, fmt = Ember.String.fmt;
- /**
- An unordered collection of objects.
- A Set works a bit like an array except that its items are not ordered. You
- can create a set to efficiently test for membership for an object. You can
- also iterate through a set just like an array, even accessing objects by
- index, however there is no guarantee as to their order.
- All Sets are observable via the Enumerable Observer API - which works
- on any enumerable object including both Sets and Arrays.
- ## Creating a Set
- You can create a set like you would most objects using
- `new Ember.Set()`. Most new sets you create will be empty, but you can
- also initialize the set with some content by passing an array or other
- enumerable of objects to the constructor.
- Finally, you can pass in an existing set and the set will be copied. You
- can also create a copy of a set by calling `Ember.Set#copy()`.
- ```javascript
- // creates a new empty set
- var foundNames = new Ember.Set();
- // creates a set with four names in it.
- var names = new Ember.Set(["Charles", "Tom", "Juan", "Alex"]); // :P
- // creates a copy of the names set.
- var namesCopy = new Ember.Set(names);
- // same as above.
- var anotherNamesCopy = names.copy();
- ```
- ## Adding/Removing Objects
- You generally add or remove objects from a set using `add()` or
- `remove()`. You can add any type of object including primitives such as
- numbers, strings, and booleans.
- Unlike arrays, objects can only exist one time in a set. If you call `add()`
- on a set with the same object multiple times, the object will only be added
- once. Likewise, calling `remove()` with the same object multiple times will
- remove the object the first time and have no effect on future calls until
- you add the object to the set again.
- NOTE: You cannot add/remove `null` or `undefined` to a set. Any attempt to do
- so will be ignored.
- In addition to add/remove you can also call `push()`/`pop()`. Push behaves
- just like `add()` but `pop()`, unlike `remove()` will pick an arbitrary
- object, remove it and return it. This is a good way to use a set as a job
- queue when you don't care which order the jobs are executed in.
- ## Testing for an Object
- To test for an object's presence in a set you simply call
- `Ember.Set#contains()`.
- ## Observing changes
- When using `Ember.Set`, you can observe the `"[]"` property to be
- alerted whenever the content changes. You can also add an enumerable
- observer to the set to be notified of specific objects that are added and
- removed from the set. See [Ember.Enumerable](/api/classes/Ember.Enumerable.html)
- for more information on enumerables.
- This is often unhelpful. If you are filtering sets of objects, for instance,
- it is very inefficient to re-filter all of the items each time the set
- changes. It would be better if you could just adjust the filtered set based
- on what was changed on the original set. The same issue applies to merging
- sets, as well.
- ## Other Methods
- `Ember.Set` primary implements other mixin APIs. For a complete reference
- on the methods you will use with `Ember.Set`, please consult these mixins.
- The most useful ones will be `Ember.Enumerable` and
- `Ember.MutableEnumerable` which implement most of the common iterator
- methods you are used to on Array.
- Note that you can also use the `Ember.Copyable` and `Ember.Freezable`
- APIs on `Ember.Set` as well. Once a set is frozen it can no longer be
- modified. The benefit of this is that when you call `frozenCopy()` on it,
- Ember will avoid making copies of the set. This allows you to write
- code that can know with certainty when the underlying set data will or
- will not be modified.
- @class Set
- @namespace Ember
- @extends Ember.CoreObject
- @uses Ember.MutableEnumerable
- @uses Ember.Copyable
- @uses Ember.Freezable
- @since Ember 0.9
- */
- Ember.Set = Ember.CoreObject.extend(Ember.MutableEnumerable, Ember.Copyable, Ember.Freezable,
- {
- // ..........................................................
- // IMPLEMENT ENUMERABLE APIS
- //
- /**
- This property will change as the number of objects in the set changes.
- @property length
- @type number
- @default 0
- */
- length: 0,
- /**
- Clears the set. This is useful if you want to reuse an existing set
- without having to recreate it.
- ```javascript
- var colors = new Ember.Set(["red", "green", "blue"]);
- colors.length; // 3
- colors.clear();
- colors.length; // 0
- ```
- @method clear
- @return {Ember.Set} An empty Set
- */
- clear: function() {
- if (this.isFrozen) { throw new Ember.Error(Ember.FROZEN_ERROR); }
- var len = get(this, 'length');
- if (len === 0) { return this; }
- var guid;
- this.enumerableContentWillChange(len, 0);
- Ember.propertyWillChange(this, 'firstObject');
- Ember.propertyWillChange(this, 'lastObject');
- for (var i=0; i < len; i++) {
- guid = guidFor(this[i]);
- delete this[guid];
- delete this[i];
- }
- set(this, 'length', 0);
- Ember.propertyDidChange(this, 'firstObject');
- Ember.propertyDidChange(this, 'lastObject');
- this.enumerableContentDidChange(len, 0);
- return this;
- },
- /**
- Returns true if the passed object is also an enumerable that contains the
- same objects as the receiver.
- ```javascript
- var colors = ["red", "green", "blue"],
- same_colors = new Ember.Set(colors);
- same_colors.isEqual(colors); // true
- same_colors.isEqual(["purple", "brown"]); // false
- ```
- @method isEqual
- @param {Ember.Set} obj the other object.
- @return {Boolean}
- */
- isEqual: function(obj) {
- // fail fast
- if (!Ember.Enumerable.detect(obj)) return false;
- var loc = get(this, 'length');
- if (get(obj, 'length') !== loc) return false;
- while(--loc >= 0) {
- if (!obj.contains(this[loc])) return false;
- }
- return true;
- },
- /**
- Adds an object to the set. Only non-`null` objects can be added to a set
- and those can only be added once. If the object is already in the set or
- the passed value is null this method will have no effect.
- This is an alias for `Ember.MutableEnumerable.addObject()`.
- ```javascript
- var colors = new Ember.Set();
- colors.add("blue"); // ["blue"]
- colors.add("blue"); // ["blue"]
- colors.add("red"); // ["blue", "red"]
- colors.add(null); // ["blue", "red"]
- colors.add(undefined); // ["blue", "red"]
- ```
- @method add
- @param {Object} obj The object to add.
- @return {Ember.Set} The set itself.
- */
- add: Ember.aliasMethod('addObject'),
- /**
- Removes the object from the set if it is found. If you pass a `null` value
- or an object that is already not in the set, this method will have no
- effect. This is an alias for `Ember.MutableEnumerable.removeObject()`.
- ```javascript
- var colors = new Ember.Set(["red", "green", "blue"]);
- colors.remove("red"); // ["blue", "green"]
- colors.remove("purple"); // ["blue", "green"]
- colors.remove(null); // ["blue", "green"]
- ```
- @method remove
- @param {Object} obj The object to remove
- @return {Ember.Set} The set itself.
- */
- remove: Ember.aliasMethod('removeObject'),
- /**
- Removes the last element from the set and returns it, or `null` if it's empty.
- ```javascript
- var colors = new Ember.Set(["green", "blue"]);
- colors.pop(); // "blue"
- colors.pop(); // "green"
- colors.pop(); // null
- ```
- @method pop
- @return {Object} The removed object from the set or null.
- */
- pop: function() {
- if (get(this, 'isFrozen')) throw new Ember.Error(Ember.FROZEN_ERROR);
- var obj = this.length > 0 ? this[this.length-1] : null;
- this.remove(obj);
- return obj;
- },
- /**
- Inserts the given object on to the end of the set. It returns
- the set itself.
- This is an alias for `Ember.MutableEnumerable.addObject()`.
- ```javascript
- var colors = new Ember.Set();
- colors.push("red"); // ["red"]
- colors.push("green"); // ["red", "green"]
- colors.push("blue"); // ["red", "green", "blue"]
- ```
- @method push
- @return {Ember.Set} The set itself.
- */
- push: Ember.aliasMethod('addObject'),
- /**
- Removes the last element from the set and returns it, or `null` if it's empty.
- This is an alias for `Ember.Set.pop()`.
- ```javascript
- var colors = new Ember.Set(["green", "blue"]);
- colors.shift(); // "blue"
- colors.shift(); // "green"
- colors.shift(); // null
- ```
- @method shift
- @return {Object} The removed object from the set or null.
- */
- shift: Ember.aliasMethod('pop'),
- /**
- Inserts the given object on to the end of the set. It returns
- the set itself.
- This is an alias of `Ember.Set.push()`
- ```javascript
- var colors = new Ember.Set();
- colors.unshift("red"); // ["red"]
- colors.unshift("green"); // ["red", "green"]
- colors.unshift("blue"); // ["red", "green", "blue"]
- ```
- @method unshift
- @return {Ember.Set} The set itself.
- */
- unshift: Ember.aliasMethod('push'),
- /**
- Adds each object in the passed enumerable to the set.
- This is an alias of `Ember.MutableEnumerable.addObjects()`
- ```javascript
- var colors = new Ember.Set();
- colors.addEach(["red", "green", "blue"]); // ["red", "green", "blue"]
- ```
- @method addEach
- @param {Ember.Enumerable} objects the objects to add.
- @return {Ember.Set} The set itself.
- */
- addEach: Ember.aliasMethod('addObjects'),
- /**
- Removes each object in the passed enumerable to the set.
- This is an alias of `Ember.MutableEnumerable.removeObjects()`
- ```javascript
- var colors = new Ember.Set(["red", "green", "blue"]);
- colors.removeEach(["red", "blue"]); // ["green"]
- ```
- @method removeEach
- @param {Ember.Enumerable} objects the objects to remove.
- @return {Ember.Set} The set itself.
- */
- removeEach: Ember.aliasMethod('removeObjects'),
- // ..........................................................
- // PRIVATE ENUMERABLE SUPPORT
- //
- init: function(items) {
- this._super();
- if (items) this.addObjects(items);
- },
- // implement Ember.Enumerable
- nextObject: function(idx) {
- return this[idx];
- },
- // more optimized version
- firstObject: Ember.computed(function() {
- return this.length > 0 ? this[0] : undefined;
- }),
- // more optimized version
- lastObject: Ember.computed(function() {
- return this.length > 0 ? this[this.length-1] : undefined;
- }),
- // implements Ember.MutableEnumerable
- addObject: function(obj) {
- if (get(this, 'isFrozen')) throw new Ember.Error(Ember.FROZEN_ERROR);
- if (isNone(obj)) return this; // nothing to do
- var guid = guidFor(obj),
- idx = this[guid],
- len = get(this, 'length'),
- added ;
- if (idx>=0 && idx<len && (this[idx] === obj)) return this; // added
- added = [obj];
- this.enumerableContentWillChange(null, added);
- Ember.propertyWillChange(this, 'lastObject');
- len = get(this, 'length');
- this[guid] = len;
- this[len] = obj;
- set(this, 'length', len+1);
- Ember.propertyDidChange(this, 'lastObject');
- this.enumerableContentDidChange(null, added);
- return this;
- },
- // implements Ember.MutableEnumerable
- removeObject: function(obj) {
- if (get(this, 'isFrozen')) throw new Ember.Error(Ember.FROZEN_ERROR);
- if (isNone(obj)) return this; // nothing to do
- var guid = guidFor(obj),
- idx = this[guid],
- len = get(this, 'length'),
- isFirst = idx === 0,
- isLast = idx === len-1,
- last, removed;
- if (idx>=0 && idx<len && (this[idx] === obj)) {
- removed = [obj];
- this.enumerableContentWillChange(removed, null);
- if (isFirst) { Ember.propertyWillChange(this, 'firstObject'); }
- if (isLast) { Ember.propertyWillChange(this, 'lastObject'); }
- // swap items - basically move the item to the end so it can be removed
- if (idx < len-1) {
- last = this[len-1];
- this[idx] = last;
- this[guidFor(last)] = idx;
- }
- delete this[guid];
- delete this[len-1];
- set(this, 'length', len-1);
- if (isFirst) { Ember.propertyDidChange(this, 'firstObject'); }
- if (isLast) { Ember.propertyDidChange(this, 'lastObject'); }
- this.enumerableContentDidChange(removed, null);
- }
- return this;
- },
- // optimized version
- contains: function(obj) {
- return this[guidFor(obj)]>=0;
- },
- copy: function() {
- var C = this.constructor, ret = new C(), loc = get(this, 'length');
- set(ret, 'length', loc);
- while(--loc>=0) {
- ret[loc] = this[loc];
- ret[guidFor(this[loc])] = loc;
- }
- return ret;
- },
- toString: function() {
- var len = this.length, idx, array = [];
- for(idx = 0; idx < len; idx++) {
- array[idx] = this[idx];
- }
- return fmt("Ember.Set<%@>", [array.join(',')]);
- }
- });
- })();
- (function() {
- var DeferredMixin = Ember.DeferredMixin, // mixins/deferred
- get = Ember.get;
- var Deferred = Ember.Object.extend(DeferredMixin);
- Deferred.reopenClass({
- promise: function(callback, binding) {
- var deferred = Deferred.create();
- callback.call(binding, deferred);
- return deferred;
- }
- });
- Ember.Deferred = Deferred;
- })();
- (function() {
- var forEach = Ember.ArrayPolyfills.forEach;
- /**
- @module ember
- @submodule ember-runtime
- */
- var loadHooks = Ember.ENV.EMBER_LOAD_HOOKS || {};
- var loaded = {};
- /**
- Detects when a specific package of Ember (e.g. 'Ember.Handlebars')
- has fully loaded and is available for extension.
- The provided `callback` will be called with the `name` passed
- resolved from a string into the object:
- ``` javascript
- Ember.onLoad('Ember.Handlebars' function(hbars){
- hbars.registerHelper(...);
- });
- ```
- @method onLoad
- @for Ember
- @param name {String} name of hook
- @param callback {Function} callback to be called
- */
- Ember.onLoad = function(name, callback) {
- var object;
- loadHooks[name] = loadHooks[name] || Ember.A();
- loadHooks[name].pushObject(callback);
- if (object = loaded[name]) {
- callback(object);
- }
- };
- /**
- Called when an Ember.js package (e.g Ember.Handlebars) has finished
- loading. Triggers any callbacks registered for this event.
- @method runLoadHooks
- @for Ember
- @param name {String} name of hook
- @param object {Object} object to pass to callbacks
- */
- Ember.runLoadHooks = function(name, object) {
- loaded[name] = object;
- if (loadHooks[name]) {
- forEach.call(loadHooks[name], function(callback) {
- callback(object);
- });
- }
- };
- })();
- (function() {
- })();
- (function() {
- var get = Ember.get;
- /**
- @module ember
- @submodule ember-runtime
- */
- /**
- `Ember.ControllerMixin` provides a standard interface for all classes that
- compose Ember's controller layer: `Ember.Controller`,
- `Ember.ArrayController`, and `Ember.ObjectController`.
- @class ControllerMixin
- @namespace Ember
- @uses Ember.ActionHandler
- */
- Ember.ControllerMixin = Ember.Mixin.create(Ember.ActionHandler, {
- /* ducktype as a controller */
- isController: true,
- /**
- The object to which actions from the view should be sent.
- For example, when a Handlebars template uses the `{{action}}` helper,
- it will attempt to send the action to the view's controller's `target`.
- By default, a controller's `target` is set to the router after it is
- instantiated by `Ember.Application#initialize`.
- @property target
- @default null
- */
- target: null,
- container: null,
- parentController: null,
- store: null,
- model: Ember.computed.alias('content'),
- deprecatedSendHandles: function(actionName) {
- return !!this[actionName];
- },
- deprecatedSend: function(actionName) {
- var args = [].slice.call(arguments, 1);
- Ember.assert('' + this + " has the action " + actionName + " but it is not a function", typeof this[actionName] === 'function');
- Ember.deprecate('Action handlers implemented directly on controllers are deprecated in favor of action handlers on an `actions` object ( action: `' + actionName + '` on ' + this + ')', false);
- this[actionName].apply(this, args);
- return;
- }
- });
- /**
- @class Controller
- @namespace Ember
- @extends Ember.Object
- @uses Ember.ControllerMixin
- */
- Ember.Controller = Ember.Object.extend(Ember.ControllerMixin);
- })();
- (function() {
- /**
- @module ember
- @submodule ember-runtime
- */
- var get = Ember.get, set = Ember.set, forEach = Ember.EnumerableUtils.forEach;
- /**
- `Ember.SortableMixin` provides a standard interface for array proxies
- to specify a sort order and maintain this sorting when objects are added,
- removed, or updated without changing the implicit order of their underlying
- content array:
- ```javascript
- songs = [
- {trackNumber: 4, title: 'Ob-La-Di, Ob-La-Da'},
- {trackNumber: 2, title: 'Back in the U.S.S.R.'},
- {trackNumber: 3, title: 'Glass Onion'},
- ];
- songsController = Ember.ArrayController.create({
- content: songs,
- sortProperties: ['trackNumber'],
- sortAscending: true
- });
- songsController.get('firstObject'); // {trackNumber: 2, title: 'Back in the U.S.S.R.'}
- songsController.addObject({trackNumber: 1, title: 'Dear Prudence'});
- songsController.get('firstObject'); // {trackNumber: 1, title: 'Dear Prudence'}
- ```
- If you add or remove the properties to sort by or change the sort direction the content
- sort order will be automatically updated.
- ```javascript
- songsController.set('sortProperties', ['title']);
- songsController.get('firstObject'); // {trackNumber: 2, title: 'Back in the U.S.S.R.'}
- songsController.toggleProperty('sortAscending');
- songsController.get('firstObject'); // {trackNumber: 4, title: 'Ob-La-Di, Ob-La-Da'}
- ```
- SortableMixin works by sorting the arrangedContent array, which is the array that
- arrayProxy displays. Due to the fact that the underlying 'content' array is not changed, that
- array will not display the sorted list:
- ```javascript
- songsController.get('content').get('firstObject'); // Returns the unsorted original content
- songsController.get('firstObject'); // Returns the sorted content.
- ```
-
- Although the sorted content can also be accessed through the arrangedContent property,
- it is preferable to use the proxied class and not the arrangedContent array directly.
- @class SortableMixin
- @namespace Ember
- @uses Ember.MutableEnumerable
- */
- Ember.SortableMixin = Ember.Mixin.create(Ember.MutableEnumerable, {
- /**
- Specifies which properties dictate the arrangedContent's sort order.
- When specifying multiple properties the sorting will use properties
- from the `sortProperties` array prioritized from first to last.
- @property {Array} sortProperties
- */
- sortProperties: null,
- /**
- Specifies the arrangedContent's sort direction
- @property {Boolean} sortAscending
- */
- sortAscending: true,
- /**
- The function used to compare two values. You can override this if you
- want to do custom comparisons. Functions must be of the type expected by
- Array#sort, i.e.
- return 0 if the two parameters are equal,
- return a negative value if the first parameter is smaller than the second or
- return a positive value otherwise:
- ```javascript
- function(x,y) { // These are assumed to be integers
- if (x === y)
- return 0;
- return x < y ? -1 : 1;
- }
- ```
- @property sortFunction
- @type {Function}
- @default Ember.compare
- */
- sortFunction: Ember.compare,
- orderBy: function(item1, item2) {
- var result = 0,
- sortProperties = get(this, 'sortProperties'),
- sortAscending = get(this, 'sortAscending'),
- sortFunction = get(this, 'sortFunction');
- Ember.assert("you need to define `sortProperties`", !!sortProperties);
- forEach(sortProperties, function(propertyName) {
- if (result === 0) {
- result = sortFunction(get(item1, propertyName), get(item2, propertyName));
- if ((result !== 0) && !sortAscending) {
- result = (-1) * result;
- }
- }
- });
- return result;
- },
- destroy: function() {
- var content = get(this, 'content'),
- sortProperties = get(this, 'sortProperties');
- if (content && sortProperties) {
- forEach(content, function(item) {
- forEach(sortProperties, function(sortProperty) {
- Ember.removeObserver(item, sortProperty, this, 'contentItemSortPropertyDidChange');
- }, this);
- }, this);
- }
- return this._super();
- },
- isSorted: Ember.computed.bool('sortProperties'),
- /**
- Overrides the default arrangedContent from arrayProxy in order to sort by sortFunction.
- Also sets up observers for each sortProperty on each item in the content Array.
-
- @property arrangedContent
- */
- arrangedContent: Ember.computed('content', 'sortProperties.@each', function(key, value) {
- var content = get(this, 'content'),
- isSorted = get(this, 'isSorted'),
- sortProperties = get(this, 'sortProperties'),
- self = this;
- if (content && isSorted) {
- content = content.slice();
- content.sort(function(item1, item2) {
- return self.orderBy(item1, item2);
- });
- forEach(content, function(item) {
- forEach(sortProperties, function(sortProperty) {
- Ember.addObserver(item, sortProperty, this, 'contentItemSortPropertyDidChange');
- }, this);
- }, this);
- return Ember.A(content);
- }
- return content;
- }),
- _contentWillChange: Ember.beforeObserver('content', function() {
- var content = get(this, 'content'),
- sortProperties = get(this, 'sortProperties');
- if (content && sortProperties) {
- forEach(content, function(item) {
- forEach(sortProperties, function(sortProperty) {
- Ember.removeObserver(item, sortProperty, this, 'contentItemSortPropertyDidChange');
- }, this);
- }, this);
- }
- this._super();
- }),
- sortAscendingWillChange: Ember.beforeObserver('sortAscending', function() {
- this._lastSortAscending = get(this, 'sortAscending');
- }),
- sortAscendingDidChange: Ember.observer('sortAscending', function() {
- if (get(this, 'sortAscending') !== this._lastSortAscending) {
- var arrangedContent = get(this, 'arrangedContent');
- arrangedContent.reverseObjects();
- }
- }),
- contentArrayWillChange: function(array, idx, removedCount, addedCount) {
- var isSorted = get(this, 'isSorted');
- if (isSorted) {
- var arrangedContent = get(this, 'arrangedContent');
- var removedObjects = array.slice(idx, idx+removedCount);
- var sortProperties = get(this, 'sortProperties');
- forEach(removedObjects, function(item) {
- arrangedContent.removeObject(item);
- forEach(sortProperties, function(sortProperty) {
- Ember.removeObserver(item, sortProperty, this, 'contentItemSortPropertyDidChange');
- }, this);
- }, this);
- }
- return this._super(array, idx, removedCount, addedCount);
- },
- contentArrayDidChange: function(array, idx, removedCount, addedCount) {
- var isSorted = get(this, 'isSorted'),
- sortProperties = get(this, 'sortProperties');
- if (isSorted) {
- var addedObjects = array.slice(idx, idx+addedCount);
- forEach(addedObjects, function(item) {
- this.insertItemSorted(item);
- forEach(sortProperties, function(sortProperty) {
- Ember.addObserver(item, sortProperty, this, 'contentItemSortPropertyDidChange');
- }, this);
- }, this);
- }
- return this._super(array, idx, removedCount, addedCount);
- },
- insertItemSorted: function(item) {
- var arrangedContent = get(this, 'arrangedContent');
- var length = get(arrangedContent, 'length');
- var idx = this._binarySearch(item, 0, length);
- arrangedContent.insertAt(idx, item);
- },
- contentItemSortPropertyDidChange: function(item) {
- var arrangedContent = get(this, 'arrangedContent'),
- oldIndex = arrangedContent.indexOf(item),
- leftItem = arrangedContent.objectAt(oldIndex - 1),
- rightItem = arrangedContent.objectAt(oldIndex + 1),
- leftResult = leftItem && this.orderBy(item, leftItem),
- rightResult = rightItem && this.orderBy(item, rightItem);
- if (leftResult < 0 || rightResult > 0) {
- arrangedContent.removeObject(item);
- this.insertItemSorted(item);
- }
- },
- _binarySearch: function(item, low, high) {
- var mid, midItem, res, arrangedContent;
- if (low === high) {
- return low;
- }
- arrangedContent = get(this, 'arrangedContent');
- mid = low + Math.floor((high - low) / 2);
- midItem = arrangedContent.objectAt(mid);
- res = this.orderBy(midItem, item);
- if (res < 0) {
- return this._binarySearch(item, mid+1, high);
- } else if (res > 0) {
- return this._binarySearch(item, low, mid);
- }
- return mid;
- }
- });
- })();
- (function() {
- /**
- @module ember
- @submodule ember-runtime
- */
- var get = Ember.get, set = Ember.set, forEach = Ember.EnumerableUtils.forEach,
- replace = Ember.EnumerableUtils.replace;
- /**
- `Ember.ArrayController` provides a way for you to publish a collection of
- objects so that you can easily bind to the collection from a Handlebars
- `#each` helper, an `Ember.CollectionView`, or other controllers.
- The advantage of using an `ArrayController` is that you only have to set up
- your view bindings once; to change what's displayed, simply swap out the
- `content` property on the controller.
- For example, imagine you wanted to display a list of items fetched via an XHR
- request. Create an `Ember.ArrayController` and set its `content` property:
- ```javascript
- MyApp.listController = Ember.ArrayController.create();
- $.get('people.json', function(data) {
- MyApp.listController.set('content', data);
- });
- ```
- Then, create a view that binds to your new controller:
- ```handlebars
- {{#each MyApp.listController}}
- {{firstName}} {{lastName}}
- {{/each}}
- ```
- Although you are binding to the controller, the behavior of this controller
- is to pass through any methods or properties to the underlying array. This
- capability comes from `Ember.ArrayProxy`, which this class inherits from.
- Sometimes you want to display computed properties within the body of an
- `#each` helper that depend on the underlying items in `content`, but are not
- present on those items. To do this, set `itemController` to the name of a
- controller (probably an `ObjectController`) that will wrap each individual item.
- For example:
- ```handlebars
- {{#each post in controller}}
- <li>{{title}} ({{titleLength}} characters)</li>
- {{/each}}
- ```
- ```javascript
- App.PostsController = Ember.ArrayController.extend({
- itemController: 'post'
- });
- App.PostController = Ember.ObjectController.extend({
- // the `title` property will be proxied to the underlying post.
- titleLength: function() {
- return this.get('title').length;
- }.property('title')
- });
- ```
- In some cases it is helpful to return a different `itemController` depending
- on the particular item. Subclasses can do this by overriding
- `lookupItemController`.
- For example:
- ```javascript
- App.MyArrayController = Ember.ArrayController.extend({
- lookupItemController: function( object ) {
- if (object.get('isSpecial')) {
- return "special"; // use App.SpecialController
- } else {
- return "regular"; // use App.RegularController
- }
- }
- });
- ```
- The itemController instances will have a `parentController` property set to
- the `ArrayController` instance.
- @class ArrayController
- @namespace Ember
- @extends Ember.ArrayProxy
- @uses Ember.SortableMixin
- @uses Ember.ControllerMixin
- */
- Ember.ArrayController = Ember.ArrayProxy.extend(Ember.ControllerMixin,
- Ember.SortableMixin, {
- /**
- The controller used to wrap items, if any.
- @property itemController
- @type String
- @default null
- */
- itemController: null,
- /**
- Return the name of the controller to wrap items, or `null` if items should
- be returned directly. The default implementation simply returns the
- `itemController` property, but subclasses can override this method to return
- different controllers for different objects.
- For example:
- ```javascript
- App.MyArrayController = Ember.ArrayController.extend({
- lookupItemController: function( object ) {
- if (object.get('isSpecial')) {
- return "special"; // use App.SpecialController
- } else {
- return "regular"; // use App.RegularController
- }
- }
- });
- ```
- @method lookupItemController
- @param {Object} object
- @return {String}
- */
- lookupItemController: function(object) {
- return get(this, 'itemController');
- },
- objectAtContent: function(idx) {
- var length = get(this, 'length'),
- arrangedContent = get(this,'arrangedContent'),
- object = arrangedContent && arrangedContent.objectAt(idx);
- if (idx >= 0 && idx < length) {
- var controllerClass = this.lookupItemController(object);
- if (controllerClass) {
- return this.controllerAt(idx, object, controllerClass);
- }
- }
- // When `controllerClass` is falsy, we have not opted in to using item
- // controllers, so return the object directly.
- // When the index is out of range, we want to return the "out of range"
- // value, whatever that might be. Rather than make assumptions
- // (e.g. guessing `null` or `undefined`) we defer this to `arrangedContent`.
- return object;
- },
- arrangedContentDidChange: function() {
- this._super();
- this._resetSubControllers();
- },
- arrayContentDidChange: function(idx, removedCnt, addedCnt) {
- var subControllers = get(this, '_subControllers'),
- subControllersToRemove = subControllers.slice(idx, idx+removedCnt);
- forEach(subControllersToRemove, function(subController) {
- if (subController) { subController.destroy(); }
- });
- replace(subControllers, idx, removedCnt, new Array(addedCnt));
- // The shadow array of subcontrollers must be updated before we trigger
- // observers, otherwise observers will get the wrong subcontainer when
- // calling `objectAt`
- this._super(idx, removedCnt, addedCnt);
- },
- init: function() {
- this._super();
- this.set('_subControllers', Ember.A());
- },
- content: Ember.computed(function () {
- return Ember.A();
- }),
- /**
- * Flag to mark as being "virtual". Used to keep this instance
- * from participating in the parentController hierarchy.
- *
- * @private
- * @type Boolean
- */
- _isVirtual: false,
- controllerAt: function(idx, object, controllerClass) {
- var container = get(this, 'container'),
- subControllers = get(this, '_subControllers'),
- subController = subControllers[idx],
- factory, fullName;
- if (subController) { return subController; }
- fullName = "controller:" + controllerClass;
- if (!container.has(fullName)) {
- throw new Ember.Error('Could not resolve itemController: "' + controllerClass + '"');
- }
- var parentController;
- if (this._isVirtual) {
- parentController = get(this, 'parentController');
- }
- parentController = parentController || this;
- subController = container.lookupFactory(fullName).create({
- target: this,
- parentController: parentController,
- content: object
- });
- subControllers[idx] = subController;
- return subController;
- },
- _subControllers: null,
- _resetSubControllers: function() {
- var subControllers = get(this, '_subControllers');
- if (subControllers) {
- forEach(subControllers, function(subController) {
- if (subController) { subController.destroy(); }
- });
- }
- this.set('_subControllers', Ember.A());
- }
- });
- })();
- (function() {
- /**
- @module ember
- @submodule ember-runtime
- */
- /**
- `Ember.ObjectController` is part of Ember's Controller layer. It is intended
- to wrap a single object, proxying unhandled attempts to `get` and `set` to the underlying
- content object, and to forward unhandled action attempts to its `target`.
- `Ember.ObjectController` derives this functionality from its superclass
- `Ember.ObjectProxy` and the `Ember.ControllerMixin` mixin.
- @class ObjectController
- @namespace Ember
- @extends Ember.ObjectProxy
- @uses Ember.ControllerMixin
- **/
- Ember.ObjectController = Ember.ObjectProxy.extend(Ember.ControllerMixin);
- })();
- (function() {
- })();
- (function() {
- /**
- Ember Runtime
- @module ember
- @submodule ember-runtime
- @requires ember-metal
- */
- })();
- (function() {
- /**
- @module ember
- @submodule ember-views
- */
- var jQuery = (this && this.jQuery) || (Ember.imports && Ember.imports.jQuery);
- if (!jQuery && typeof require === 'function') {
- jQuery = require('jquery');
- }
- Ember.assert("Ember Views require jQuery between 1.7 and 2.1", jQuery && (jQuery().jquery.match(/^((1\.(7|8|9|10|11))|(2\.(0|1)))(\.\d+)?(pre|rc\d?)?/) || Ember.ENV.FORCE_JQUERY));
- /**
- Alias for jQuery
- @method $
- @for Ember
- */
- Ember.$ = jQuery;
- })();
- (function() {
- /**
- @module ember
- @submodule ember-views
- */
- if (Ember.$) {
- // http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#dndevents
- var dragEvents = Ember.String.w('dragstart drag dragenter dragleave dragover drop dragend');
- // Copies the `dataTransfer` property from a browser event object onto the
- // jQuery event object for the specified events
- Ember.EnumerableUtils.forEach(dragEvents, function(eventName) {
- Ember.$.event.fixHooks[eventName] = { props: ['dataTransfer'] };
- });
- }
- })();
- (function() {
- /**
- @module ember
- @submodule ember-views
- */
- /* BEGIN METAMORPH HELPERS */
- // Internet Explorer prior to 9 does not allow setting innerHTML if the first element
- // is a "zero-scope" element. This problem can be worked around by making
- // the first node an invisible text node. We, like Modernizr, use ­
- var needsShy = typeof document !== 'undefined' && (function() {
- var testEl = document.createElement('div');
- testEl.innerHTML = "<div></div>";
- testEl.firstChild.innerHTML = "<script></script>";
- return testEl.firstChild.innerHTML === '';
- })();
- // IE 8 (and likely earlier) likes to move whitespace preceeding
- // a script tag to appear after it. This means that we can
- // accidentally remove whitespace when updating a morph.
- var movesWhitespace = typeof document !== 'undefined' && (function() {
- var testEl = document.createElement('div');
- testEl.innerHTML = "Test: <script type='text/x-placeholder'></script>Value";
- return testEl.childNodes[0].nodeValue === 'Test:' &&
- testEl.childNodes[2].nodeValue === ' Value';
- })();
- // Use this to find children by ID instead of using jQuery
- var findChildById = function(element, id) {
- if (element.getAttribute('id') === id) { return element; }
- var len = element.childNodes.length, idx, node, found;
- for (idx=0; idx<len; idx++) {
- node = element.childNodes[idx];
- found = node.nodeType === 1 && findChildById(node, id);
- if (found) { return found; }
- }
- };
- var setInnerHTMLWithoutFix = function(element, html) {
- if (needsShy) {
- html = '­' + html;
- }
- var matches = [];
- if (movesWhitespace) {
- // Right now we only check for script tags with ids with the
- // goal of targeting morphs.
- html = html.replace(/(\s+)(<script id='([^']+)')/g, function(match, spaces, tag, id) {
- matches.push([id, spaces]);
- return tag;
- });
- }
- element.innerHTML = html;
- // If we have to do any whitespace adjustments do them now
- if (matches.length > 0) {
- var len = matches.length, idx;
- for (idx=0; idx<len; idx++) {
- var script = findChildById(element, matches[idx][0]),
- node = document.createTextNode(matches[idx][1]);
- script.parentNode.insertBefore(node, script);
- }
- }
- if (needsShy) {
- var shyElement = element.firstChild;
- while (shyElement.nodeType === 1 && !shyElement.nodeName) {
- shyElement = shyElement.firstChild;
- }
- if (shyElement.nodeType === 3 && shyElement.nodeValue.charAt(0) === "\u00AD") {
- shyElement.nodeValue = shyElement.nodeValue.slice(1);
- }
- }
- };
- /* END METAMORPH HELPERS */
- var innerHTMLTags = {};
- var canSetInnerHTML = function(tagName) {
- if (innerHTMLTags[tagName] !== undefined) {
- return innerHTMLTags[tagName];
- }
- var canSet = true;
- // IE 8 and earlier don't allow us to do innerHTML on select
- if (tagName.toLowerCase() === 'select') {
- var el = document.createElement('select');
- setInnerHTMLWithoutFix(el, '<option value="test">Test</option>');
- canSet = el.options.length === 1;
- }
- innerHTMLTags[tagName] = canSet;
- return canSet;
- };
- var setInnerHTML = function(element, html) {
- var tagName = element.tagName;
- if (canSetInnerHTML(tagName)) {
- setInnerHTMLWithoutFix(element, html);
- } else {
- // Firefox versions < 11 do not have support for element.outerHTML.
- var outerHTML = element.outerHTML || new XMLSerializer().serializeToString(element);
- Ember.assert("Can't set innerHTML on "+element.tagName+" in this browser", outerHTML);
- var startTag = outerHTML.match(new RegExp("<"+tagName+"([^>]*)>", 'i'))[0],
- endTag = '</'+tagName+'>';
- var wrapper = document.createElement('div');
- setInnerHTMLWithoutFix(wrapper, startTag + html + endTag);
- element = wrapper.firstChild;
- while (element.tagName !== tagName) {
- element = element.nextSibling;
- }
- }
- return element;
- };
- function isSimpleClick(event) {
- var modifier = event.shiftKey || event.metaKey || event.altKey || event.ctrlKey,
- secondaryClick = event.which > 1; // IE9 may return undefined
- return !modifier && !secondaryClick;
- }
- Ember.ViewUtils = {
- setInnerHTML: setInnerHTML,
- isSimpleClick: isSimpleClick
- };
- })();
- (function() {
- /**
- @module ember
- @submodule ember-views
- */
- var get = Ember.get, set = Ember.set;
- var ClassSet = function() {
- this.seen = {};
- this.list = [];
- };
- ClassSet.prototype = {
- add: function(string) {
- if (string in this.seen) { return; }
- this.seen[string] = true;
- this.list.push(string);
- },
- toDOM: function() {
- return this.list.join(" ");
- }
- };
- var BAD_TAG_NAME_TEST_REGEXP = /[^a-zA-Z0-9\-]/;
- var BAD_TAG_NAME_REPLACE_REGEXP = /[^a-zA-Z0-9\-]/g;
- function stripTagName(tagName) {
- if (!tagName) {
- return tagName;
- }
- if (!BAD_TAG_NAME_TEST_REGEXP.test(tagName)) {
- return tagName;
- }
- return tagName.replace(BAD_TAG_NAME_REPLACE_REGEXP, '');
- }
- var BAD_CHARS_REGEXP = /&(?!\w+;)|[<>"'`]/g;
- var POSSIBLE_CHARS_REGEXP = /[&<>"'`]/;
- function escapeAttribute(value) {
- // Stolen shamelessly from Handlebars
- var escape = {
- "<": "<",
- ">": ">",
- '"': """,
- "'": "'",
- "`": "`"
- };
- var escapeChar = function(chr) {
- return escape[chr] || "&";
- };
- var string = value.toString();
- if(!POSSIBLE_CHARS_REGEXP.test(string)) { return string; }
- return string.replace(BAD_CHARS_REGEXP, escapeChar);
- }
- // IE 6/7 have bugs around setting names on inputs during creation.
- // From http://msdn.microsoft.com/en-us/library/ie/ms536389(v=vs.85).aspx:
- // "To include the NAME attribute at run time on objects created with the createElement method, use the eTag."
- var canSetNameOnInputs = (function() {
- var div = document.createElement('div'),
- el = document.createElement('input');
- el.setAttribute('name', 'foo');
- div.appendChild(el);
- return !!div.innerHTML.match('foo');
- })();
- /**
- `Ember.RenderBuffer` gathers information regarding the a view and generates the
- final representation. `Ember.RenderBuffer` will generate HTML which can be pushed
- to the DOM.
- ```javascript
- var buffer = Ember.RenderBuffer('div');
- ```
- @class RenderBuffer
- @namespace Ember
- @constructor
- @param {String} tagName tag name (such as 'div' or 'p') used for the buffer
- */
- Ember.RenderBuffer = function(tagName) {
- return new Ember._RenderBuffer(tagName);
- };
- Ember._RenderBuffer = function(tagName) {
- this.tagNames = [tagName || null];
- this.buffer = "";
- };
- Ember._RenderBuffer.prototype = {
- // The root view's element
- _element: null,
- _hasElement: true,
- /**
- An internal set used to de-dupe class names when `addClass()` is
- used. After each call to `addClass()`, the `classes` property
- will be updated.
- @private
- @property elementClasses
- @type Array
- @default []
- */
- elementClasses: null,
- /**
- Array of class names which will be applied in the class attribute.
- You can use `setClasses()` to set this property directly. If you
- use `addClass()`, it will be maintained for you.
- @property classes
- @type Array
- @default []
- */
- classes: null,
- /**
- The id in of the element, to be applied in the id attribute.
- You should not set this property yourself, rather, you should use
- the `id()` method of `Ember.RenderBuffer`.
- @property elementId
- @type String
- @default null
- */
- elementId: null,
- /**
- A hash keyed on the name of the attribute and whose value will be
- applied to that attribute. For example, if you wanted to apply a
- `data-view="Foo.bar"` property to an element, you would set the
- elementAttributes hash to `{'data-view':'Foo.bar'}`.
- You should not maintain this hash yourself, rather, you should use
- the `attr()` method of `Ember.RenderBuffer`.
- @property elementAttributes
- @type Hash
- @default {}
- */
- elementAttributes: null,
- /**
- A hash keyed on the name of the properties and whose value will be
- applied to that property. For example, if you wanted to apply a
- `checked=true` property to an element, you would set the
- elementProperties hash to `{'checked':true}`.
- You should not maintain this hash yourself, rather, you should use
- the `prop()` method of `Ember.RenderBuffer`.
- @property elementProperties
- @type Hash
- @default {}
- */
- elementProperties: null,
- /**
- The tagname of the element an instance of `Ember.RenderBuffer` represents.
- Usually, this gets set as the first parameter to `Ember.RenderBuffer`. For
- example, if you wanted to create a `p` tag, then you would call
- ```javascript
- Ember.RenderBuffer('p')
- ```
- @property elementTag
- @type String
- @default null
- */
- elementTag: null,
- /**
- A hash keyed on the name of the style attribute and whose value will
- be applied to that attribute. For example, if you wanted to apply a
- `background-color:black;` style to an element, you would set the
- elementStyle hash to `{'background-color':'black'}`.
- You should not maintain this hash yourself, rather, you should use
- the `style()` method of `Ember.RenderBuffer`.
- @property elementStyle
- @type Hash
- @default {}
- */
- elementStyle: null,
- /**
- Nested `RenderBuffers` will set this to their parent `RenderBuffer`
- instance.
- @property parentBuffer
- @type Ember._RenderBuffer
- */
- parentBuffer: null,
- /**
- Adds a string of HTML to the `RenderBuffer`.
- @method push
- @param {String} string HTML to push into the buffer
- @chainable
- */
- push: function(string) {
- this.buffer += string;
- return this;
- },
- /**
- Adds a class to the buffer, which will be rendered to the class attribute.
- @method addClass
- @param {String} className Class name to add to the buffer
- @chainable
- */
- addClass: function(className) {
- // lazily create elementClasses
- this.elementClasses = (this.elementClasses || new ClassSet());
- this.elementClasses.add(className);
- this.classes = this.elementClasses.list;
- return this;
- },
- setClasses: function(classNames) {
- this.elementClasses = null;
- var len = classNames.length, i;
- for (i = 0; i < len; i++) {
- this.addClass(classNames[i]);
- }
- },
- /**
- Sets the elementID to be used for the element.
- @method id
- @param {String} id
- @chainable
- */
- id: function(id) {
- this.elementId = id;
- return this;
- },
- // duck type attribute functionality like jQuery so a render buffer
- // can be used like a jQuery object in attribute binding scenarios.
- /**
- Adds an attribute which will be rendered to the element.
- @method attr
- @param {String} name The name of the attribute
- @param {String} value The value to add to the attribute
- @chainable
- @return {Ember.RenderBuffer|String} this or the current attribute value
- */
- attr: function(name, value) {
- var attributes = this.elementAttributes = (this.elementAttributes || {});
- if (arguments.length === 1) {
- return attributes[name];
- } else {
- attributes[name] = value;
- }
- return this;
- },
- /**
- Remove an attribute from the list of attributes to render.
- @method removeAttr
- @param {String} name The name of the attribute
- @chainable
- */
- removeAttr: function(name) {
- var attributes = this.elementAttributes;
- if (attributes) { delete attributes[name]; }
- return this;
- },
- /**
- Adds a property which will be rendered to the element.
- @method prop
- @param {String} name The name of the property
- @param {String} value The value to add to the property
- @chainable
- @return {Ember.RenderBuffer|String} this or the current property value
- */
- prop: function(name, value) {
- var properties = this.elementProperties = (this.elementProperties || {});
- if (arguments.length === 1) {
- return properties[name];
- } else {
- properties[name] = value;
- }
- return this;
- },
- /**
- Remove an property from the list of properties to render.
- @method removeProp
- @param {String} name The name of the property
- @chainable
- */
- removeProp: function(name) {
- var properties = this.elementProperties;
- if (properties) { delete properties[name]; }
- return this;
- },
- /**
- Adds a style to the style attribute which will be rendered to the element.
- @method style
- @param {String} name Name of the style
- @param {String} value
- @chainable
- */
- style: function(name, value) {
- this.elementStyle = (this.elementStyle || {});
- this.elementStyle[name] = value;
- return this;
- },
- begin: function(tagName) {
- this.tagNames.push(tagName || null);
- return this;
- },
- pushOpeningTag: function() {
- var tagName = this.currentTagName();
- if (!tagName) { return; }
- if (this._hasElement && !this._element && this.buffer.length === 0) {
- this._element = this.generateElement();
- return;
- }
- var buffer = this.buffer,
- id = this.elementId,
- classes = this.classes,
- attrs = this.elementAttributes,
- props = this.elementProperties,
- style = this.elementStyle,
- attr, prop;
- buffer += '<' + stripTagName(tagName);
- if (id) {
- buffer += ' id="' + escapeAttribute(id) + '"';
- this.elementId = null;
- }
- if (classes) {
- buffer += ' class="' + escapeAttribute(classes.join(' ')) + '"';
- this.classes = null;
- this.elementClasses = null;
- }
- if (style) {
- buffer += ' style="';
- for (prop in style) {
- if (style.hasOwnProperty(prop)) {
- buffer += prop + ':' + escapeAttribute(style[prop]) + ';';
- }
- }
- buffer += '"';
- this.elementStyle = null;
- }
- if (attrs) {
- for (attr in attrs) {
- if (attrs.hasOwnProperty(attr)) {
- buffer += ' ' + attr + '="' + escapeAttribute(attrs[attr]) + '"';
- }
- }
- this.elementAttributes = null;
- }
- if (props) {
- for (prop in props) {
- if (props.hasOwnProperty(prop)) {
- var value = props[prop];
- if (value || typeof(value) === 'number') {
- if (value === true) {
- buffer += ' ' + prop + '="' + prop + '"';
- } else {
- buffer += ' ' + prop + '="' + escapeAttribute(props[prop]) + '"';
- }
- }
- }
- }
- this.elementProperties = null;
- }
- buffer += '>';
- this.buffer = buffer;
- },
- pushClosingTag: function() {
- var tagName = this.tagNames.pop();
- if (tagName) { this.buffer += '</' + stripTagName(tagName) + '>'; }
- },
- currentTagName: function() {
- return this.tagNames[this.tagNames.length-1];
- },
- generateElement: function() {
- var tagName = this.tagNames.pop(), // pop since we don't need to close
- id = this.elementId,
- classes = this.classes,
- attrs = this.elementAttributes,
- props = this.elementProperties,
- style = this.elementStyle,
- styleBuffer = '', attr, prop, tagString;
- if (attrs && attrs.name && !canSetNameOnInputs) {
- // IE allows passing a tag to createElement. See note on `canSetNameOnInputs` above as well.
- tagString = '<'+stripTagName(tagName)+' name="'+escapeAttribute(attrs.name)+'">';
- } else {
- tagString = tagName;
- }
- var element = document.createElement(tagString),
- $element = Ember.$(element);
- if (id) {
- $element.attr('id', id);
- this.elementId = null;
- }
- if (classes) {
- $element.attr('class', classes.join(' '));
- this.classes = null;
- this.elementClasses = null;
- }
- if (style) {
- for (prop in style) {
- if (style.hasOwnProperty(prop)) {
- styleBuffer += (prop + ':' + style[prop] + ';');
- }
- }
- $element.attr('style', styleBuffer);
- this.elementStyle = null;
- }
- if (attrs) {
- for (attr in attrs) {
- if (attrs.hasOwnProperty(attr)) {
- $element.attr(attr, attrs[attr]);
- }
- }
- this.elementAttributes = null;
- }
- if (props) {
- for (prop in props) {
- if (props.hasOwnProperty(prop)) {
- $element.prop(prop, props[prop]);
- }
- }
- this.elementProperties = null;
- }
- return element;
- },
- /**
- @method element
- @return {DOMElement} The element corresponding to the generated HTML
- of this buffer
- */
- element: function() {
- var html = this.innerString();
- if (html) {
- this._element = Ember.ViewUtils.setInnerHTML(this._element, html);
- }
- return this._element;
- },
- /**
- Generates the HTML content for this buffer.
- @method string
- @return {String} The generated HTML
- */
- string: function() {
- if (this._hasElement && this._element) {
- // Firefox versions < 11 do not have support for element.outerHTML.
- var thisElement = this.element(), outerHTML = thisElement.outerHTML;
- if (typeof outerHTML === 'undefined') {
- return Ember.$('<div/>').append(thisElement).html();
- }
- return outerHTML;
- } else {
- return this.innerString();
- }
- },
- innerString: function() {
- return this.buffer;
- }
- };
- })();
- (function() {
- /**
- @module ember
- @submodule ember-views
- */
- var get = Ember.get, set = Ember.set, fmt = Ember.String.fmt;
- /**
- `Ember.EventDispatcher` handles delegating browser events to their
- corresponding `Ember.Views.` For example, when you click on a view,
- `Ember.EventDispatcher` ensures that that view's `mouseDown` method gets
- called.
- @class EventDispatcher
- @namespace Ember
- @private
- @extends Ember.Object
- */
- Ember.EventDispatcher = Ember.Object.extend({
- /**
- The set of events names (and associated handler function names) to be setup
- and dispatched by the `EventDispatcher`. Custom events can added to this list at setup
- time, generally via the `Ember.Application.customEvents` hash. Only override this
- default set to prevent the EventDispatcher from listening on some events all together.
- This set will be modified by `setup` to also include any events added at that time.
- @property events
- @type Object
- */
- events: {
- touchstart : 'touchStart',
- touchmove : 'touchMove',
- touchend : 'touchEnd',
- touchcancel : 'touchCancel',
- keydown : 'keyDown',
- keyup : 'keyUp',
- keypress : 'keyPress',
- mousedown : 'mouseDown',
- mouseup : 'mouseUp',
- contextmenu : 'contextMenu',
- click : 'click',
- dblclick : 'doubleClick',
- mousemove : 'mouseMove',
- focusin : 'focusIn',
- focusout : 'focusOut',
- mouseenter : 'mouseEnter',
- mouseleave : 'mouseLeave',
- submit : 'submit',
- input : 'input',
- change : 'change',
- dragstart : 'dragStart',
- drag : 'drag',
- dragenter : 'dragEnter',
- dragleave : 'dragLeave',
- dragover : 'dragOver',
- drop : 'drop',
- dragend : 'dragEnd'
- },
- /**
- The root DOM element to which event listeners should be attached. Event
- listeners will be attached to the document unless this is overridden.
- Can be specified as a DOMElement or a selector string.
- The default body is a string since this may be evaluated before document.body
- exists in the DOM.
- @private
- @property rootElement
- @type DOMElement
- @default 'body'
- */
- rootElement: 'body',
- /**
- Sets up event listeners for standard browser events.
- This will be called after the browser sends a `DOMContentReady` event. By
- default, it will set up all of the listeners on the document body. If you
- would like to register the listeners on a different element, set the event
- dispatcher's `root` property.
- @private
- @method setup
- @param addedEvents {Hash}
- */
- setup: function(addedEvents, rootElement) {
- var event, events = get(this, 'events');
- Ember.$.extend(events, addedEvents || {});
- if (!Ember.isNone(rootElement)) {
- set(this, 'rootElement', rootElement);
- }
- rootElement = Ember.$(get(this, 'rootElement'));
- Ember.assert(fmt('You cannot use the same root element (%@) multiple times in an Ember.Application', [rootElement.selector || rootElement[0].tagName]), !rootElement.is('.ember-application'));
- Ember.assert('You cannot make a new Ember.Application using a root element that is a descendent of an existing Ember.Application', !rootElement.closest('.ember-application').length);
- Ember.assert('You cannot make a new Ember.Application using a root element that is an ancestor of an existing Ember.Application', !rootElement.find('.ember-application').length);
- rootElement.addClass('ember-application');
- Ember.assert('Unable to add "ember-application" class to rootElement. Make sure you set rootElement to the body or an element in the body.', rootElement.is('.ember-application'));
- for (event in events) {
- if (events.hasOwnProperty(event)) {
- this.setupHandler(rootElement, event, events[event]);
- }
- }
- },
- /**
- Registers an event listener on the document. If the given event is
- triggered, the provided event handler will be triggered on the target view.
- If the target view does not implement the event handler, or if the handler
- returns `false`, the parent view will be called. The event will continue to
- bubble to each successive parent view until it reaches the top.
- For example, to have the `mouseDown` method called on the target view when
- a `mousedown` event is received from the browser, do the following:
- ```javascript
- setupHandler('mousedown', 'mouseDown');
- ```
- @private
- @method setupHandler
- @param {Element} rootElement
- @param {String} event the browser-originated event to listen to
- @param {String} eventName the name of the method to call on the view
- */
- setupHandler: function(rootElement, event, eventName) {
- var self = this;
- rootElement.on(event + '.ember', '.ember-view', function(evt, triggeringManager) {
- return Ember.handleErrors(function handleViewEvent() {
- var view = Ember.View.views[this.id],
- result = true, manager = null;
- manager = self._findNearestEventManager(view,eventName);
- if (manager && manager !== triggeringManager) {
- result = self._dispatchEvent(manager, evt, eventName, view);
- } else if (view) {
- result = self._bubbleEvent(view,evt,eventName);
- } else {
- evt.stopPropagation();
- }
- return result;
- }, this);
- });
- rootElement.on(event + '.ember', '[data-ember-action]', function(evt) {
- return Ember.handleErrors(function handleActionEvent() {
- var actionId = Ember.$(evt.currentTarget).attr('data-ember-action'),
- action = Ember.Handlebars.ActionHelper.registeredActions[actionId];
- // We have to check for action here since in some cases, jQuery will trigger
- // an event on `removeChild` (i.e. focusout) after we've already torn down the
- // action handlers for the view.
- if (action && action.eventName === eventName) {
- return action.handler(evt);
- }
- }, this);
- });
- },
- _findNearestEventManager: function(view, eventName) {
- var manager = null;
- while (view) {
- manager = get(view, 'eventManager');
- if (manager && manager[eventName]) { break; }
- view = get(view, 'parentView');
- }
- return manager;
- },
- _dispatchEvent: function(object, evt, eventName, view) {
- var result = true;
- var handler = object[eventName];
- if (Ember.typeOf(handler) === 'function') {
- result = Ember.run(function() {
- return handler.call(object, evt, view);
- });
- // Do not preventDefault in eventManagers.
- evt.stopPropagation();
- }
- else {
- result = this._bubbleEvent(view, evt, eventName);
- }
- return result;
- },
- _bubbleEvent: function(view, evt, eventName) {
- return Ember.run(function bubbleEvent() {
- return view.handleEvent(eventName, evt);
- });
- },
- destroy: function() {
- var rootElement = get(this, 'rootElement');
- Ember.$(rootElement).off('.ember', '**').removeClass('ember-application');
- return this._super();
- }
- });
- })();
- (function() {
- /**
- @module ember
- @submodule ember-views
- */
- // Add a new named queue for rendering views that happens
- // after bindings have synced, and a queue for scheduling actions
- // that that should occur after view rendering.
- var queues = Ember.run.queues,
- indexOf = Ember.ArrayPolyfills.indexOf;
- queues.splice(indexOf.call(queues, 'actions')+1, 0, 'render', 'afterRender');
- })();
- (function() {
- /**
- @module ember
- @submodule ember-views
- */
- var get = Ember.get, set = Ember.set;
- // Original class declaration and documentation in runtime/lib/controllers/controller.js
- // NOTE: It may be possible with YUIDoc to combine docs in two locations
- /**
- Additional methods for the ControllerMixin
- @class ControllerMixin
- @namespace Ember
- */
- Ember.ControllerMixin.reopen({
- target: null,
- namespace: null,
- view: null,
- container: null,
- _childContainers: null,
- init: function() {
- this._super();
- set(this, '_childContainers', {});
- },
- _modelDidChange: Ember.observer('model', function() {
- var containers = get(this, '_childContainers');
- for (var prop in containers) {
- if (!containers.hasOwnProperty(prop)) { continue; }
- containers[prop].destroy();
- }
- set(this, '_childContainers', {});
- })
- });
- })();
- (function() {
- })();
- (function() {
- var states = {};
- /**
- @module ember
- @submodule ember-views
- */
- var get = Ember.get, set = Ember.set;
- var guidFor = Ember.guidFor;
- var a_forEach = Ember.EnumerableUtils.forEach;
- var a_addObject = Ember.EnumerableUtils.addObject;
- var meta = Ember.meta;
- var childViewsProperty = Ember.computed(function() {
- var childViews = this._childViews, ret = Ember.A(), view = this;
- a_forEach(childViews, function(view) {
- var currentChildViews;
- if (view.isVirtual) {
- if (currentChildViews = get(view, 'childViews')) {
- ret.pushObjects(currentChildViews);
- }
- } else {
- ret.push(view);
- }
- });
- ret.replace = function (idx, removedCount, addedViews) {
- if (view instanceof Ember.ContainerView) {
- Ember.deprecate("Manipulating an Ember.ContainerView through its childViews property is deprecated. Please use the ContainerView instance itself as an Ember.MutableArray.");
- return view.replace(idx, removedCount, addedViews);
- }
- throw new Ember.Error("childViews is immutable");
- };
- return ret;
- });
- Ember.warn("The VIEW_PRESERVES_CONTEXT flag has been removed and the functionality can no longer be disabled.", Ember.ENV.VIEW_PRESERVES_CONTEXT !== false);
- /**
- Global hash of shared templates. This will automatically be populated
- by the build tools so that you can store your Handlebars templates in
- separate files that get loaded into JavaScript at buildtime.
- @property TEMPLATES
- @for Ember
- @type Hash
- */
- Ember.TEMPLATES = {};
- /**
- `Ember.CoreView` is an abstract class that exists to give view-like behavior
- to both Ember's main view class `Ember.View` and other classes like
- `Ember._SimpleMetamorphView` that don't need the fully functionaltiy of
- `Ember.View`.
- Unless you have specific needs for `CoreView`, you will use `Ember.View`
- in your applications.
- @class CoreView
- @namespace Ember
- @extends Ember.Object
- @uses Ember.Evented
- @uses Ember.ActionHandler
- */
- Ember.CoreView = Ember.Object.extend(Ember.Evented, Ember.ActionHandler, {
- isView: true,
- states: states,
- init: function() {
- this._super();
- this.transitionTo('preRender');
- },
- /**
- If the view is currently inserted into the DOM of a parent view, this
- property will point to the parent of the view.
- @property parentView
- @type Ember.View
- @default null
- */
- parentView: Ember.computed(function() {
- var parent = this._parentView;
- if (parent && parent.isVirtual) {
- return get(parent, 'parentView');
- } else {
- return parent;
- }
- }).property('_parentView'),
- state: null,
- _parentView: null,
- // return the current view, not including virtual views
- concreteView: Ember.computed(function() {
- if (!this.isVirtual) { return this; }
- else { return get(this, 'parentView'); }
- }).property('parentView'),
- instrumentName: 'core_view',
- instrumentDetails: function(hash) {
- hash.object = this.toString();
- },
- /**
- Invoked by the view system when this view needs to produce an HTML
- representation. This method will create a new render buffer, if needed,
- then apply any default attributes, such as class names and visibility.
- Finally, the `render()` method is invoked, which is responsible for
- doing the bulk of the rendering.
- You should not need to override this method; instead, implement the
- `template` property, or if you need more control, override the `render`
- method.
- @method renderToBuffer
- @param {Ember.RenderBuffer} buffer the render buffer. If no buffer is
- passed, a default buffer, using the current view's `tagName`, will
- be used.
- @private
- */
- renderToBuffer: function(parentBuffer, bufferOperation) {
- var name = 'render.' + this.instrumentName,
- details = {};
- this.instrumentDetails(details);
- return Ember.instrument(name, details, function instrumentRenderToBuffer() {
- return this._renderToBuffer(parentBuffer, bufferOperation);
- }, this);
- },
- _renderToBuffer: function(parentBuffer, bufferOperation) {
- // If this is the top-most view, start a new buffer. Otherwise,
- // create a new buffer relative to the original using the
- // provided buffer operation (for example, `insertAfter` will
- // insert a new buffer after the "parent buffer").
- var tagName = this.tagName;
- if (tagName === null || tagName === undefined) {
- tagName = 'div';
- }
- var buffer = this.buffer = parentBuffer && parentBuffer.begin(tagName) || Ember.RenderBuffer(tagName);
- this.transitionTo('inBuffer', false);
- this.beforeRender(buffer);
- this.render(buffer);
- this.afterRender(buffer);
- return buffer;
- },
- /**
- Override the default event firing from `Ember.Evented` to
- also call methods with the given name.
- @method trigger
- @param name {String}
- @private
- */
- trigger: function(name) {
- this._super.apply(this, arguments);
- var method = this[name];
- if (method) {
- var args = [], i, l;
- for (i = 1, l = arguments.length; i < l; i++) {
- args.push(arguments[i]);
- }
- return method.apply(this, args);
- }
- },
- deprecatedSendHandles: function(actionName) {
- return !!this[actionName];
- },
- deprecatedSend: function(actionName) {
- var args = [].slice.call(arguments, 1);
- Ember.assert('' + this + " has the action " + actionName + " but it is not a function", typeof this[actionName] === 'function');
- Ember.deprecate('Action handlers implemented directly on views are deprecated in favor of action handlers on an `actions` object ( action: `' + actionName + '` on ' + this + ')', false);
- this[actionName].apply(this, args);
- return;
- },
- has: function(name) {
- return Ember.typeOf(this[name]) === 'function' || this._super(name);
- },
- destroy: function() {
- var parent = this._parentView;
- if (!this._super()) { return; }
- // destroy the element -- this will avoid each child view destroying
- // the element over and over again...
- if (!this.removedFromDOM) { this.destroyElement(); }
- // remove from parent if found. Don't call removeFromParent,
- // as removeFromParent will try to remove the element from
- // the DOM again.
- if (parent) { parent.removeChild(this); }
- this.transitionTo('destroying', false);
- return this;
- },
- clearRenderedChildren: Ember.K,
- triggerRecursively: Ember.K,
- invokeRecursively: Ember.K,
- transitionTo: Ember.K,
- destroyElement: Ember.K
- });
- var ViewCollection = Ember._ViewCollection = function(initialViews) {
- var views = this.views = initialViews || [];
- this.length = views.length;
- };
- ViewCollection.prototype = {
- length: 0,
- trigger: function(eventName) {
- var views = this.views, view;
- for (var i = 0, l = views.length; i < l; i++) {
- view = views[i];
- if (view.trigger) { view.trigger(eventName); }
- }
- },
- triggerRecursively: function(eventName) {
- var views = this.views;
- for (var i = 0, l = views.length; i < l; i++) {
- views[i].triggerRecursively(eventName);
- }
- },
- invokeRecursively: function(fn) {
- var views = this.views, view;
- for (var i = 0, l = views.length; i < l; i++) {
- view = views[i];
- fn(view);
- }
- },
- transitionTo: function(state, children) {
- var views = this.views;
- for (var i = 0, l = views.length; i < l; i++) {
- views[i].transitionTo(state, children);
- }
- },
- push: function() {
- this.length += arguments.length;
- var views = this.views;
- return views.push.apply(views, arguments);
- },
- objectAt: function(idx) {
- return this.views[idx];
- },
- forEach: function(callback) {
- var views = this.views;
- return a_forEach(views, callback);
- },
- clear: function() {
- this.length = 0;
- this.views.length = 0;
- }
- };
- var EMPTY_ARRAY = [];
- /**
- `Ember.View` is the class in Ember responsible for encapsulating templates of
- HTML content, combining templates with data to render as sections of a page's
- DOM, and registering and responding to user-initiated events.
- ## HTML Tag
- The default HTML tag name used for a view's DOM representation is `div`. This
- can be customized by setting the `tagName` property. The following view
- class:
- ```javascript
- ParagraphView = Ember.View.extend({
- tagName: 'em'
- });
- ```
- Would result in instances with the following HTML:
- ```html
- <em id="ember1" class="ember-view"></em>
- ```
- ## HTML `class` Attribute
- The HTML `class` attribute of a view's tag can be set by providing a
- `classNames` property that is set to an array of strings:
- ```javascript
- MyView = Ember.View.extend({
- classNames: ['my-class', 'my-other-class']
- });
- ```
- Will result in view instances with an HTML representation of:
- ```html
- <div id="ember1" class="ember-view my-class my-other-class"></div>
- ```
- `class` attribute values can also be set by providing a `classNameBindings`
- property set to an array of properties names for the view. The return value
- of these properties will be added as part of the value for the view's `class`
- attribute. These properties can be computed properties:
- ```javascript
- MyView = Ember.View.extend({
- classNameBindings: ['propertyA', 'propertyB'],
- propertyA: 'from-a',
- propertyB: function() {
- if (someLogic) { return 'from-b'; }
- }.property()
- });
- ```
- Will result in view instances with an HTML representation of:
- ```html
- <div id="ember1" class="ember-view from-a from-b"></div>
- ```
- If the value of a class name binding returns a boolean the property name
- itself will be used as the class name if the property is true. The class name
- will not be added if the value is `false` or `undefined`.
- ```javascript
- MyView = Ember.View.extend({
- classNameBindings: ['hovered'],
- hovered: true
- });
- ```
- Will result in view instances with an HTML representation of:
- ```html
- <div id="ember1" class="ember-view hovered"></div>
- ```
- When using boolean class name bindings you can supply a string value other
- than the property name for use as the `class` HTML attribute by appending the
- preferred value after a ":" character when defining the binding:
- ```javascript
- MyView = Ember.View.extend({
- classNameBindings: ['awesome:so-very-cool'],
- awesome: true
- });
- ```
- Will result in view instances with an HTML representation of:
- ```html
- <div id="ember1" class="ember-view so-very-cool"></div>
- ```
- Boolean value class name bindings whose property names are in a
- camelCase-style format will be converted to a dasherized format:
- ```javascript
- MyView = Ember.View.extend({
- classNameBindings: ['isUrgent'],
- isUrgent: true
- });
- ```
- Will result in view instances with an HTML representation of:
- ```html
- <div id="ember1" class="ember-view is-urgent"></div>
- ```
- Class name bindings can also refer to object values that are found by
- traversing a path relative to the view itself:
- ```javascript
- MyView = Ember.View.extend({
- classNameBindings: ['messages.empty']
- messages: Ember.Object.create({
- empty: true
- })
- });
- ```
- Will result in view instances with an HTML representation of:
- ```html
- <div id="ember1" class="ember-view empty"></div>
- ```
- If you want to add a class name for a property which evaluates to true and
- and a different class name if it evaluates to false, you can pass a binding
- like this:
- ```javascript
- // Applies 'enabled' class when isEnabled is true and 'disabled' when isEnabled is false
- Ember.View.extend({
- classNameBindings: ['isEnabled:enabled:disabled']
- isEnabled: true
- });
- ```
- Will result in view instances with an HTML representation of:
- ```html
- <div id="ember1" class="ember-view enabled"></div>
- ```
- When isEnabled is `false`, the resulting HTML reprensentation looks like
- this:
- ```html
- <div id="ember1" class="ember-view disabled"></div>
- ```
- This syntax offers the convenience to add a class if a property is `false`:
- ```javascript
- // Applies no class when isEnabled is true and class 'disabled' when isEnabled is false
- Ember.View.extend({
- classNameBindings: ['isEnabled::disabled']
- isEnabled: true
- });
- ```
- Will result in view instances with an HTML representation of:
- ```html
- <div id="ember1" class="ember-view"></div>
- ```
- When the `isEnabled` property on the view is set to `false`, it will result
- in view instances with an HTML representation of:
- ```html
- <div id="ember1" class="ember-view disabled"></div>
- ```
- Updates to the the value of a class name binding will result in automatic
- update of the HTML `class` attribute in the view's rendered HTML
- representation. If the value becomes `false` or `undefined` the class name
- will be removed.
- Both `classNames` and `classNameBindings` are concatenated properties. See
- [Ember.Object](/api/classes/Ember.Object.html) documentation for more
- information about concatenated properties.
- ## HTML Attributes
- The HTML attribute section of a view's tag can be set by providing an
- `attributeBindings` property set to an array of property names on the view.
- The return value of these properties will be used as the value of the view's
- HTML associated attribute:
- ```javascript
- AnchorView = Ember.View.extend({
- tagName: 'a',
- attributeBindings: ['href'],
- href: 'http://google.com'
- });
- ```
- Will result in view instances with an HTML representation of:
- ```html
- <a id="ember1" class="ember-view" href="http://google.com"></a>
- ```
- If the return value of an `attributeBindings` monitored property is a boolean
- the property will follow HTML's pattern of repeating the attribute's name as
- its value:
- ```javascript
- MyTextInput = Ember.View.extend({
- tagName: 'input',
- attributeBindings: ['disabled'],
- disabled: true
- });
- ```
- Will result in view instances with an HTML representation of:
- ```html
- <input id="ember1" class="ember-view" disabled="disabled" />
- ```
- `attributeBindings` can refer to computed properties:
- ```javascript
- MyTextInput = Ember.View.extend({
- tagName: 'input',
- attributeBindings: ['disabled'],
- disabled: function() {
- if (someLogic) {
- return true;
- } else {
- return false;
- }
- }.property()
- });
- ```
- Updates to the the property of an attribute binding will result in automatic
- update of the HTML attribute in the view's rendered HTML representation.
- `attributeBindings` is a concatenated property. See [Ember.Object](/api/classes/Ember.Object.html)
- documentation for more information about concatenated properties.
- ## Templates
- The HTML contents of a view's rendered representation are determined by its
- template. Templates can be any function that accepts an optional context
- parameter and returns a string of HTML that will be inserted within the
- view's tag. Most typically in Ember this function will be a compiled
- `Ember.Handlebars` template.
- ```javascript
- AView = Ember.View.extend({
- template: Ember.Handlebars.compile('I am the template')
- });
- ```
- Will result in view instances with an HTML representation of:
- ```html
- <div id="ember1" class="ember-view">I am the template</div>
- ```
- Within an Ember application is more common to define a Handlebars templates as
- part of a page:
- ```html
- <script type='text/x-handlebars' data-template-name='some-template'>
- Hello
- </script>
- ```
- And associate it by name using a view's `templateName` property:
- ```javascript
- AView = Ember.View.extend({
- templateName: 'some-template'
- });
- ```
- If you have nested resources, your Handlebars template will look like this:
- ```html
- <script type='text/x-handlebars' data-template-name='posts/new'>
- <h1>New Post</h1>
- </script>
- ```
- And `templateName` property:
- ```javascript
- AView = Ember.View.extend({
- templateName: 'posts/new'
- });
- ```
- Using a value for `templateName` that does not have a Handlebars template
- with a matching `data-template-name` attribute will throw an error.
- For views classes that may have a template later defined (e.g. as the block
- portion of a `{{view}}` Handlebars helper call in another template or in
- a subclass), you can provide a `defaultTemplate` property set to compiled
- template function. If a template is not later provided for the view instance
- the `defaultTemplate` value will be used:
- ```javascript
- AView = Ember.View.extend({
- defaultTemplate: Ember.Handlebars.compile('I was the default'),
- template: null,
- templateName: null
- });
- ```
- Will result in instances with an HTML representation of:
- ```html
- <div id="ember1" class="ember-view">I was the default</div>
- ```
- If a `template` or `templateName` is provided it will take precedence over
- `defaultTemplate`:
- ```javascript
- AView = Ember.View.extend({
- defaultTemplate: Ember.Handlebars.compile('I was the default')
- });
- aView = AView.create({
- template: Ember.Handlebars.compile('I was the template, not default')
- });
- ```
- Will result in the following HTML representation when rendered:
- ```html
- <div id="ember1" class="ember-view">I was the template, not default</div>
- ```
- ## View Context
- The default context of the compiled template is the view's controller:
- ```javascript
- AView = Ember.View.extend({
- template: Ember.Handlebars.compile('Hello {{excitedGreeting}}')
- });
- aController = Ember.Object.create({
- firstName: 'Barry',
- excitedGreeting: function() {
- return this.get("content.firstName") + "!!!"
- }.property()
- });
- aView = AView.create({
- controller: aController,
- });
- ```
- Will result in an HTML representation of:
- ```html
- <div id="ember1" class="ember-view">Hello Barry!!!</div>
- ```
- A context can also be explicitly supplied through the view's `context`
- property. If the view has neither `context` nor `controller` properties, the
- `parentView`'s context will be used.
- ## Layouts
- Views can have a secondary template that wraps their main template. Like
- primary templates, layouts can be any function that accepts an optional
- context parameter and returns a string of HTML that will be inserted inside
- view's tag. Views whose HTML element is self closing (e.g. `<input />`)
- cannot have a layout and this property will be ignored.
- Most typically in Ember a layout will be a compiled `Ember.Handlebars`
- template.
- A view's layout can be set directly with the `layout` property or reference
- an existing Handlebars template by name with the `layoutName` property.
- A template used as a layout must contain a single use of the Handlebars
- `{{yield}}` helper. The HTML contents of a view's rendered `template` will be
- inserted at this location:
- ```javascript
- AViewWithLayout = Ember.View.extend({
- layout: Ember.Handlebars.compile("<div class='my-decorative-class'>{{yield}}</div>")
- template: Ember.Handlebars.compile("I got wrapped"),
- });
- ```
- Will result in view instances with an HTML representation of:
- ```html
- <div id="ember1" class="ember-view">
- <div class="my-decorative-class">
- I got wrapped
- </div>
- </div>
- ```
- See [Ember.Handlebars.helpers.yield](/api/classes/Ember.Handlebars.helpers.html#method_yield)
- for more information.
- ## Responding to Browser Events
- Views can respond to user-initiated events in one of three ways: method
- implementation, through an event manager, and through `{{action}}` helper use
- in their template or layout.
- ### Method Implementation
- Views can respond to user-initiated events by implementing a method that
- matches the event name. A `jQuery.Event` object will be passed as the
- argument to this method.
- ```javascript
- AView = Ember.View.extend({
- click: function(event) {
- // will be called when when an instance's
- // rendered element is clicked
- }
- });
- ```
- ### Event Managers
- Views can define an object as their `eventManager` property. This object can
- then implement methods that match the desired event names. Matching events
- that occur on the view's rendered HTML or the rendered HTML of any of its DOM
- descendants will trigger this method. A `jQuery.Event` object will be passed
- as the first argument to the method and an `Ember.View` object as the
- second. The `Ember.View` will be the view whose rendered HTML was interacted
- with. This may be the view with the `eventManager` property or one of its
- descendent views.
- ```javascript
- AView = Ember.View.extend({
- eventManager: Ember.Object.create({
- doubleClick: function(event, view) {
- // will be called when when an instance's
- // rendered element or any rendering
- // of this views's descendent
- // elements is clicked
- }
- })
- });
- ```
- An event defined for an event manager takes precedence over events of the
- same name handled through methods on the view.
- ```javascript
- AView = Ember.View.extend({
- mouseEnter: function(event) {
- // will never trigger.
- },
- eventManager: Ember.Object.create({
- mouseEnter: function(event, view) {
- // takes precedence over AView#mouseEnter
- }
- })
- });
- ```
- Similarly a view's event manager will take precedence for events of any views
- rendered as a descendent. A method name that matches an event name will not
- be called if the view instance was rendered inside the HTML representation of
- a view that has an `eventManager` property defined that handles events of the
- name. Events not handled by the event manager will still trigger method calls
- on the descendent.
- ```javascript
- OuterView = Ember.View.extend({
- template: Ember.Handlebars.compile("outer {{#view InnerView}}inner{{/view}} outer"),
- eventManager: Ember.Object.create({
- mouseEnter: function(event, view) {
- // view might be instance of either
- // OuterView or InnerView depending on
- // where on the page the user interaction occured
- }
- })
- });
- InnerView = Ember.View.extend({
- click: function(event) {
- // will be called if rendered inside
- // an OuterView because OuterView's
- // eventManager doesn't handle click events
- },
- mouseEnter: function(event) {
- // will never be called if rendered inside
- // an OuterView.
- }
- });
- ```
- ### Handlebars `{{action}}` Helper
- See [Handlebars.helpers.action](/api/classes/Ember.Handlebars.helpers.html#method_action).
- ### Event Names
- All of the event handling approaches described above respond to the same set
- of events. The names of the built-in events are listed below. (The hash of
- built-in events exists in `Ember.EventDispatcher`.) Additional, custom events
- can be registered by using `Ember.Application.customEvents`.
- Touch events:
- * `touchStart`
- * `touchMove`
- * `touchEnd`
- * `touchCancel`
- Keyboard events
- * `keyDown`
- * `keyUp`
- * `keyPress`
- Mouse events
- * `mouseDown`
- * `mouseUp`
- * `contextMenu`
- * `click`
- * `doubleClick`
- * `mouseMove`
- * `focusIn`
- * `focusOut`
- * `mouseEnter`
- * `mouseLeave`
- Form events:
- * `submit`
- * `change`
- * `focusIn`
- * `focusOut`
- * `input`
- HTML5 drag and drop events:
- * `dragStart`
- * `drag`
- * `dragEnter`
- * `dragLeave`
- * `drop`
- * `dragEnd`
- ## Handlebars `{{view}}` Helper
- Other `Ember.View` instances can be included as part of a view's template by
- using the `{{view}}` Handlebars helper. See [Ember.Handlebars.helpers.view](/api/classes/Ember.Handlebars.helpers.html#method_view)
- for additional information.
- @class View
- @namespace Ember
- @extends Ember.CoreView
- */
- Ember.View = Ember.CoreView.extend({
- concatenatedProperties: ['classNames', 'classNameBindings', 'attributeBindings'],
- /**
- @property isView
- @type Boolean
- @default true
- @static
- */
- isView: true,
- // ..........................................................
- // TEMPLATE SUPPORT
- //
- /**
- The name of the template to lookup if no template is provided.
- By default `Ember.View` will lookup a template with this name in
- `Ember.TEMPLATES` (a shared global object).
- @property templateName
- @type String
- @default null
- */
- templateName: null,
- /**
- The name of the layout to lookup if no layout is provided.
- By default `Ember.View` will lookup a template with this name in
- `Ember.TEMPLATES` (a shared global object).
- @property layoutName
- @type String
- @default null
- */
- layoutName: null,
- /**
- The template used to render the view. This should be a function that
- accepts an optional context parameter and returns a string of HTML that
- will be inserted into the DOM relative to its parent view.
- In general, you should set the `templateName` property instead of setting
- the template yourself.
- @property template
- @type Function
- */
- template: Ember.computed(function(key, value) {
- if (value !== undefined) { return value; }
- var templateName = get(this, 'templateName'),
- template = this.templateForName(templateName, 'template');
- Ember.assert("You specified the templateName " + templateName + " for " + this + ", but it did not exist.", !templateName || template);
- return template || get(this, 'defaultTemplate');
- }).property('templateName'),
- /**
- The controller managing this view. If this property is set, it will be
- made available for use by the template.
- @property controller
- @type Object
- */
- controller: Ember.computed(function(key) {
- var parentView = get(this, '_parentView');
- return parentView ? get(parentView, 'controller') : null;
- }).property('_parentView'),
- /**
- A view may contain a layout. A layout is a regular template but
- supersedes the `template` property during rendering. It is the
- responsibility of the layout template to retrieve the `template`
- property from the view (or alternatively, call `Handlebars.helpers.yield`,
- `{{yield}}`) to render it in the correct location.
- This is useful for a view that has a shared wrapper, but which delegates
- the rendering of the contents of the wrapper to the `template` property
- on a subclass.
- @property layout
- @type Function
- */
- layout: Ember.computed(function(key) {
- var layoutName = get(this, 'layoutName'),
- layout = this.templateForName(layoutName, 'layout');
- Ember.assert("You specified the layoutName " + layoutName + " for " + this + ", but it did not exist.", !layoutName || layout);
- return layout || get(this, 'defaultLayout');
- }).property('layoutName'),
- _yield: function(context, options) {
- var template = get(this, 'template');
- if (template) { template(context, options); }
- },
- templateForName: function(name, type) {
- if (!name) { return; }
- Ember.assert("templateNames are not allowed to contain periods: "+name, name.indexOf('.') === -1);
- // the defaultContainer is deprecated
- var container = this.container || (Ember.Container && Ember.Container.defaultContainer);
- return container && container.lookup('template:' + name);
- },
- /**
- The object from which templates should access properties.
- This object will be passed to the template function each time the render
- method is called, but it is up to the individual function to decide what
- to do with it.
- By default, this will be the view's controller.
- @property context
- @type Object
- */
- context: Ember.computed(function(key, value) {
- if (arguments.length === 2) {
- set(this, '_context', value);
- return value;
- } else {
- return get(this, '_context');
- }
- }).volatile(),
- /**
- Private copy of the view's template context. This can be set directly
- by Handlebars without triggering the observer that causes the view
- to be re-rendered.
- The context of a view is looked up as follows:
- 1. Supplied context (usually by Handlebars)
- 2. Specified controller
- 3. `parentView`'s context (for a child of a ContainerView)
- The code in Handlebars that overrides the `_context` property first
- checks to see whether the view has a specified controller. This is
- something of a hack and should be revisited.
- @property _context
- @private
- */
- _context: Ember.computed(function(key) {
- var parentView, controller;
- if (controller = get(this, 'controller')) {
- return controller;
- }
- parentView = this._parentView;
- if (parentView) {
- return get(parentView, '_context');
- }
- return null;
- }),
- /**
- If a value that affects template rendering changes, the view should be
- re-rendered to reflect the new value.
- @method _contextDidChange
- @private
- */
- _contextDidChange: Ember.observer('context', function() {
- this.rerender();
- }),
- /**
- If `false`, the view will appear hidden in DOM.
- @property isVisible
- @type Boolean
- @default null
- */
- isVisible: true,
- /**
- Array of child views. You should never edit this array directly.
- Instead, use `appendChild` and `removeFromParent`.
- @property childViews
- @type Array
- @default []
- @private
- */
- childViews: childViewsProperty,
- _childViews: EMPTY_ARRAY,
- // When it's a virtual view, we need to notify the parent that their
- // childViews will change.
- _childViewsWillChange: Ember.beforeObserver('childViews', function() {
- if (this.isVirtual) {
- var parentView = get(this, 'parentView');
- if (parentView) { Ember.propertyWillChange(parentView, 'childViews'); }
- }
- }),
- // When it's a virtual view, we need to notify the parent that their
- // childViews did change.
- _childViewsDidChange: Ember.observer('childViews', function() {
- if (this.isVirtual) {
- var parentView = get(this, 'parentView');
- if (parentView) { Ember.propertyDidChange(parentView, 'childViews'); }
- }
- }),
- /**
- Return the nearest ancestor that is an instance of the provided
- class.
- @property nearestInstanceOf
- @param {Class} klass Subclass of Ember.View (or Ember.View itself)
- @return Ember.View
- @deprecated
- */
- nearestInstanceOf: function(klass) {
- Ember.deprecate("nearestInstanceOf is deprecated and will be removed from future releases. Use nearestOfType.");
- var view = get(this, 'parentView');
- while (view) {
- if (view instanceof klass) { return view; }
- view = get(view, 'parentView');
- }
- },
- /**
- Return the nearest ancestor that is an instance of the provided
- class or mixin.
- @property nearestOfType
- @param {Class,Mixin} klass Subclass of Ember.View (or Ember.View itself),
- or an instance of Ember.Mixin.
- @return Ember.View
- */
- nearestOfType: function(klass) {
- var view = get(this, 'parentView'),
- isOfType = klass instanceof Ember.Mixin ?
- function(view) { return klass.detect(view); } :
- function(view) { return klass.detect(view.constructor); };
- while (view) {
- if (isOfType(view)) { return view; }
- view = get(view, 'parentView');
- }
- },
- /**
- Return the nearest ancestor that has a given property.
- @function nearestWithProperty
- @param {String} property A property name
- @return Ember.View
- */
- nearestWithProperty: function(property) {
- var view = get(this, 'parentView');
- while (view) {
- if (property in view) { return view; }
- view = get(view, 'parentView');
- }
- },
- /**
- Return the nearest ancestor whose parent is an instance of
- `klass`.
- @method nearestChildOf
- @param {Class} klass Subclass of Ember.View (or Ember.View itself)
- @return Ember.View
- */
- nearestChildOf: function(klass) {
- var view = get(this, 'parentView');
- while (view) {
- if (get(view, 'parentView') instanceof klass) { return view; }
- view = get(view, 'parentView');
- }
- },
- /**
- When the parent view changes, recursively invalidate `controller`
- @method _parentViewDidChange
- @private
- */
- _parentViewDidChange: Ember.observer('_parentView', function() {
- if (this.isDestroying) { return; }
- this.trigger('parentViewDidChange');
- if (get(this, 'parentView.controller') && !get(this, 'controller')) {
- this.notifyPropertyChange('controller');
- }
- }),
- _controllerDidChange: Ember.observer('controller', function() {
- if (this.isDestroying) { return; }
- this.rerender();
- this.forEachChildView(function(view) {
- view.propertyDidChange('controller');
- });
- }),
- cloneKeywords: function() {
- var templateData = get(this, 'templateData');
- var keywords = templateData ? Ember.copy(templateData.keywords) : {};
- set(keywords, 'view', get(this, 'concreteView'));
- set(keywords, '_view', this);
- set(keywords, 'controller', get(this, 'controller'));
- return keywords;
- },
- /**
- Called on your view when it should push strings of HTML into a
- `Ember.RenderBuffer`. Most users will want to override the `template`
- or `templateName` properties instead of this method.
- By default, `Ember.View` will look for a function in the `template`
- property and invoke it with the value of `context`. The value of
- `context` will be the view's controller unless you override it.
- @method render
- @param {Ember.RenderBuffer} buffer The render buffer
- */
- render: function(buffer) {
- // If this view has a layout, it is the responsibility of the
- // the layout to render the view's template. Otherwise, render the template
- // directly.
- var template = get(this, 'layout') || get(this, 'template');
- if (template) {
- var context = get(this, 'context');
- var keywords = this.cloneKeywords();
- var output;
- var data = {
- view: this,
- buffer: buffer,
- isRenderData: true,
- keywords: keywords,
- insideGroup: get(this, 'templateData.insideGroup')
- };
- // Invoke the template with the provided template context, which
- // is the view's controller by default. A hash of data is also passed that provides
- // the template with access to the view and render buffer.
- Ember.assert('template must be a function. Did you mean to call Ember.Handlebars.compile("...") or specify templateName instead?', typeof template === 'function');
- // The template should write directly to the render buffer instead
- // of returning a string.
- output = template(context, { data: data });
- // If the template returned a string instead of writing to the buffer,
- // push the string onto the buffer.
- if (output !== undefined) { buffer.push(output); }
- }
- },
- /**
- Renders the view again. This will work regardless of whether the
- view is already in the DOM or not. If the view is in the DOM, the
- rendering process will be deferred to give bindings a chance
- to synchronize.
- If children were added during the rendering process using `appendChild`,
- `rerender` will remove them, because they will be added again
- if needed by the next `render`.
- In general, if the display of your view changes, you should modify
- the DOM element directly instead of manually calling `rerender`, which can
- be slow.
- @method rerender
- */
- rerender: function() {
- return this.currentState.rerender(this);
- },
- clearRenderedChildren: function() {
- var lengthBefore = this.lengthBeforeRender,
- lengthAfter = this.lengthAfterRender;
- // If there were child views created during the last call to render(),
- // remove them under the assumption that they will be re-created when
- // we re-render.
- // VIEW-TODO: Unit test this path.
- var childViews = this._childViews;
- for (var i=lengthAfter-1; i>=lengthBefore; i--) {
- if (childViews[i]) { childViews[i].destroy(); }
- }
- },
- /**
- Iterates over the view's `classNameBindings` array, inserts the value
- of the specified property into the `classNames` array, then creates an
- observer to update the view's element if the bound property ever changes
- in the future.
- @method _applyClassNameBindings
- @private
- */
- _applyClassNameBindings: function(classBindings) {
- var classNames = this.classNames,
- elem, newClass, dasherizedClass;
- // Loop through all of the configured bindings. These will be either
- // property names ('isUrgent') or property paths relative to the view
- // ('content.isUrgent')
- a_forEach(classBindings, function(binding) {
- Ember.assert("classNameBindings must not have spaces in them. Multiple class name bindings can be provided as elements of an array, e.g. ['foo', ':bar']", binding.indexOf(' ') === -1);
- // Variable in which the old class value is saved. The observer function
- // closes over this variable, so it knows which string to remove when
- // the property changes.
- var oldClass;
- // Extract just the property name from bindings like 'foo:bar'
- var parsedPath = Ember.View._parsePropertyPath(binding);
- // Set up an observer on the context. If the property changes, toggle the
- // class name.
- var observer = function() {
- // Get the current value of the property
- newClass = this._classStringForProperty(binding);
- elem = this.$();
- // If we had previously added a class to the element, remove it.
- if (oldClass) {
- elem.removeClass(oldClass);
- // Also remove from classNames so that if the view gets rerendered,
- // the class doesn't get added back to the DOM.
- classNames.removeObject(oldClass);
- }
- // If necessary, add a new class. Make sure we keep track of it so
- // it can be removed in the future.
- if (newClass) {
- elem.addClass(newClass);
- oldClass = newClass;
- } else {
- oldClass = null;
- }
- };
- // Get the class name for the property at its current value
- dasherizedClass = this._classStringForProperty(binding);
- if (dasherizedClass) {
- // Ensure that it gets into the classNames array
- // so it is displayed when we render.
- a_addObject(classNames, dasherizedClass);
- // Save a reference to the class name so we can remove it
- // if the observer fires. Remember that this variable has
- // been closed over by the observer.
- oldClass = dasherizedClass;
- }
- this.registerObserver(this, parsedPath.path, observer);
- // Remove className so when the view is rerendered,
- // the className is added based on binding reevaluation
- this.one('willClearRender', function() {
- if (oldClass) {
- classNames.removeObject(oldClass);
- oldClass = null;
- }
- });
- }, this);
- },
- /**
- Iterates through the view's attribute bindings, sets up observers for each,
- then applies the current value of the attributes to the passed render buffer.
- @method _applyAttributeBindings
- @param {Ember.RenderBuffer} buffer
- @private
- */
- _applyAttributeBindings: function(buffer, attributeBindings) {
- var attributeValue, elem;
- a_forEach(attributeBindings, function(binding) {
- var split = binding.split(':'),
- property = split[0],
- attributeName = split[1] || property;
- // Create an observer to add/remove/change the attribute if the
- // JavaScript property changes.
- var observer = function() {
- elem = this.$();
- attributeValue = get(this, property);
- Ember.View.applyAttributeBindings(elem, attributeName, attributeValue);
- };
- this.registerObserver(this, property, observer);
- // Determine the current value and add it to the render buffer
- // if necessary.
- attributeValue = get(this, property);
- Ember.View.applyAttributeBindings(buffer, attributeName, attributeValue);
- }, this);
- },
- /**
- Given a property name, returns a dasherized version of that
- property name if the property evaluates to a non-falsy value.
- For example, if the view has property `isUrgent` that evaluates to true,
- passing `isUrgent` to this method will return `"is-urgent"`.
- @method _classStringForProperty
- @param property
- @private
- */
- _classStringForProperty: function(property) {
- var parsedPath = Ember.View._parsePropertyPath(property);
- var path = parsedPath.path;
- var val = get(this, path);
- if (val === undefined && Ember.isGlobalPath(path)) {
- val = get(Ember.lookup, path);
- }
- return Ember.View._classStringForValue(path, val, parsedPath.className, parsedPath.falsyClassName);
- },
- // ..........................................................
- // ELEMENT SUPPORT
- //
- /**
- Returns the current DOM element for the view.
- @property element
- @type DOMElement
- */
- element: Ember.computed(function(key, value) {
- if (value !== undefined) {
- return this.currentState.setElement(this, value);
- } else {
- return this.currentState.getElement(this);
- }
- }).property('_parentView'),
- /**
- Returns a jQuery object for this view's element. If you pass in a selector
- string, this method will return a jQuery object, using the current element
- as its buffer.
- For example, calling `view.$('li')` will return a jQuery object containing
- all of the `li` elements inside the DOM element of this view.
- @method $
- @param {String} [selector] a jQuery-compatible selector string
- @return {jQuery} the jQuery object for the DOM node
- */
- $: function(sel) {
- return this.currentState.$(this, sel);
- },
- mutateChildViews: function(callback) {
- var childViews = this._childViews,
- idx = childViews.length,
- view;
- while(--idx >= 0) {
- view = childViews[idx];
- callback(this, view, idx);
- }
- return this;
- },
- forEachChildView: function(callback) {
- var childViews = this._childViews;
- if (!childViews) { return this; }
- var len = childViews.length,
- view, idx;
- for (idx = 0; idx < len; idx++) {
- view = childViews[idx];
- callback(view);
- }
- return this;
- },
- /**
- Appends the view's element to the specified parent element.
- If the view does not have an HTML representation yet, `createElement()`
- will be called automatically.
- Note that this method just schedules the view to be appended; the DOM
- element will not be appended to the given element until all bindings have
- finished synchronizing.
- This is not typically a function that you will need to call directly when
- building your application. You might consider using `Ember.ContainerView`
- instead. If you do need to use `appendTo`, be sure that the target element
- you are providing is associated with an `Ember.Application` and does not
- have an ancestor element that is associated with an Ember view.
- @method appendTo
- @param {String|DOMElement|jQuery} A selector, element, HTML string, or jQuery object
- @return {Ember.View} receiver
- */
- appendTo: function(target) {
- // Schedule the DOM element to be created and appended to the given
- // element after bindings have synchronized.
- this._insertElementLater(function() {
- Ember.assert("You tried to append to (" + target + ") but that isn't in the DOM", Ember.$(target).length > 0);
- Ember.assert("You cannot append to an existing Ember.View. Consider using Ember.ContainerView instead.", !Ember.$(target).is('.ember-view') && !Ember.$(target).parents().is('.ember-view'));
- this.$().appendTo(target);
- });
- return this;
- },
- /**
- Replaces the content of the specified parent element with this view's
- element. If the view does not have an HTML representation yet,
- `createElement()` will be called automatically.
- Note that this method just schedules the view to be appended; the DOM
- element will not be appended to the given element until all bindings have
- finished synchronizing
- @method replaceIn
- @param {String|DOMElement|jQuery} target A selector, element, HTML string, or jQuery object
- @return {Ember.View} received
- */
- replaceIn: function(target) {
- Ember.assert("You tried to replace in (" + target + ") but that isn't in the DOM", Ember.$(target).length > 0);
- Ember.assert("You cannot replace an existing Ember.View. Consider using Ember.ContainerView instead.", !Ember.$(target).is('.ember-view') && !Ember.$(target).parents().is('.ember-view'));
- this._insertElementLater(function() {
- Ember.$(target).empty();
- this.$().appendTo(target);
- });
- return this;
- },
- /**
- Schedules a DOM operation to occur during the next render phase. This
- ensures that all bindings have finished synchronizing before the view is
- rendered.
- To use, pass a function that performs a DOM operation.
- Before your function is called, this view and all child views will receive
- the `willInsertElement` event. After your function is invoked, this view
- and all of its child views will receive the `didInsertElement` event.
- ```javascript
- view._insertElementLater(function() {
- this.createElement();
- this.$().appendTo('body');
- });
- ```
- @method _insertElementLater
- @param {Function} fn the function that inserts the element into the DOM
- @private
- */
- _insertElementLater: function(fn) {
- this._scheduledInsert = Ember.run.scheduleOnce('render', this, '_insertElement', fn);
- },
- _insertElement: function (fn) {
- this._scheduledInsert = null;
- this.currentState.insertElement(this, fn);
- },
- /**
- Appends the view's element to the document body. If the view does
- not have an HTML representation yet, `createElement()` will be called
- automatically.
- If your application uses the `rootElement` property, you must append
- the view within that element. Rendering views outside of the `rootElement`
- is not supported.
- Note that this method just schedules the view to be appended; the DOM
- element will not be appended to the document body until all bindings have
- finished synchronizing.
- @method append
- @return {Ember.View} receiver
- */
- append: function() {
- return this.appendTo(document.body);
- },
- /**
- Removes the view's element from the element to which it is attached.
- @method remove
- @return {Ember.View} receiver
- */
- remove: function() {
- // What we should really do here is wait until the end of the run loop
- // to determine if the element has been re-appended to a different
- // element.
- // In the interim, we will just re-render if that happens. It is more
- // important than elements get garbage collected.
- if (!this.removedFromDOM) { this.destroyElement(); }
- this.invokeRecursively(function(view) {
- if (view.clearRenderedChildren) { view.clearRenderedChildren(); }
- });
- },
- elementId: null,
- /**
- Attempts to discover the element in the parent element. The default
- implementation looks for an element with an ID of `elementId` (or the
- view's guid if `elementId` is null). You can override this method to
- provide your own form of lookup. For example, if you want to discover your
- element using a CSS class name instead of an ID.
- @method findElementInParentElement
- @param {DOMElement} parentElement The parent's DOM element
- @return {DOMElement} The discovered element
- */
- findElementInParentElement: function(parentElem) {
- var id = "#" + this.elementId;
- return Ember.$(id)[0] || Ember.$(id, parentElem)[0];
- },
- /**
- Creates a DOM representation of the view and all of its
- child views by recursively calling the `render()` method.
- After the element has been created, `didInsertElement` will
- be called on this view and all of its child views.
- @method createElement
- @return {Ember.View} receiver
- */
- createElement: function() {
- if (get(this, 'element')) { return this; }
- var buffer = this.renderToBuffer();
- set(this, 'element', buffer.element());
- return this;
- },
- /**
- Called when a view is going to insert an element into the DOM.
- @event willInsertElement
- */
- willInsertElement: Ember.K,
- /**
- Called when the element of the view has been inserted into the DOM
- or after the view was re-rendered. Override this function to do any
- set up that requires an element in the document body.
- @event didInsertElement
- */
- didInsertElement: Ember.K,
- /**
- Called when the view is about to rerender, but before anything has
- been torn down. This is a good opportunity to tear down any manual
- observers you have installed based on the DOM state
- @event willClearRender
- */
- willClearRender: Ember.K,
- /**
- Run this callback on the current view (unless includeSelf is false) and recursively on child views.
- @method invokeRecursively
- @param fn {Function}
- @param includeSelf {Boolean} Includes itself if true.
- @private
- */
- invokeRecursively: function(fn, includeSelf) {
- var childViews = (includeSelf === false) ? this._childViews : [this];
- var currentViews, view, currentChildViews;
- while (childViews.length) {
- currentViews = childViews.slice();
- childViews = [];
- for (var i=0, l=currentViews.length; i<l; i++) {
- view = currentViews[i];
- currentChildViews = view._childViews ? view._childViews.slice(0) : null;
- fn(view);
- if (currentChildViews) {
- childViews.push.apply(childViews, currentChildViews);
- }
- }
- }
- },
- triggerRecursively: function(eventName) {
- var childViews = [this], currentViews, view, currentChildViews;
- while (childViews.length) {
- currentViews = childViews.slice();
- childViews = [];
- for (var i=0, l=currentViews.length; i<l; i++) {
- view = currentViews[i];
- currentChildViews = view._childViews ? view._childViews.slice(0) : null;
- if (view.trigger) { view.trigger(eventName); }
- if (currentChildViews) {
- childViews.push.apply(childViews, currentChildViews);
- }
- }
- }
- },
- viewHierarchyCollection: function() {
- var currentView, viewCollection = new ViewCollection([this]);
- for (var i = 0; i < viewCollection.length; i++) {
- currentView = viewCollection.objectAt(i);
- if (currentView._childViews) {
- viewCollection.push.apply(viewCollection, currentView._childViews);
- }
- }
- return viewCollection;
- },
- /**
- Destroys any existing element along with the element for any child views
- as well. If the view does not currently have a element, then this method
- will do nothing.
- If you implement `willDestroyElement()` on your view, then this method will
- be invoked on your view before your element is destroyed to give you a
- chance to clean up any event handlers, etc.
- If you write a `willDestroyElement()` handler, you can assume that your
- `didInsertElement()` handler was called earlier for the same element.
- You should not call or override this method yourself, but you may
- want to implement the above callbacks.
- @method destroyElement
- @return {Ember.View} receiver
- */
- destroyElement: function() {
- return this.currentState.destroyElement(this);
- },
- /**
- Called when the element of the view is going to be destroyed. Override
- this function to do any teardown that requires an element, like removing
- event listeners.
- @event willDestroyElement
- */
- willDestroyElement: Ember.K,
- /**
- Triggers the `willDestroyElement` event (which invokes the
- `willDestroyElement()` method if it exists) on this view and all child
- views.
- Before triggering `willDestroyElement`, it first triggers the
- `willClearRender` event recursively.
- @method _notifyWillDestroyElement
- @private
- */
- _notifyWillDestroyElement: function() {
- var viewCollection = this.viewHierarchyCollection();
- viewCollection.trigger('willClearRender');
- viewCollection.trigger('willDestroyElement');
- return viewCollection;
- },
- /**
- If this view's element changes, we need to invalidate the caches of our
- child views so that we do not retain references to DOM elements that are
- no longer needed.
- @method _elementDidChange
- @private
- */
- _elementDidChange: Ember.observer('element', function() {
- this.forEachChildView(function(view) {
- delete meta(view).cache.element;
- });
- }),
- /**
- Called when the parentView property has changed.
- @event parentViewDidChange
- */
- parentViewDidChange: Ember.K,
- instrumentName: 'view',
- instrumentDetails: function(hash) {
- hash.template = get(this, 'templateName');
- this._super(hash);
- },
- _renderToBuffer: function(parentBuffer, bufferOperation) {
- this.lengthBeforeRender = this._childViews.length;
- var buffer = this._super(parentBuffer, bufferOperation);
- this.lengthAfterRender = this._childViews.length;
- return buffer;
- },
- renderToBufferIfNeeded: function (buffer) {
- return this.currentState.renderToBufferIfNeeded(this, buffer);
- },
- beforeRender: function(buffer) {
- this.applyAttributesToBuffer(buffer);
- buffer.pushOpeningTag();
- },
- afterRender: function(buffer) {
- buffer.pushClosingTag();
- },
- applyAttributesToBuffer: function(buffer) {
- // Creates observers for all registered class name and attribute bindings,
- // then adds them to the element.
- var classNameBindings = get(this, 'classNameBindings');
- if (classNameBindings.length) {
- this._applyClassNameBindings(classNameBindings);
- }
- // Pass the render buffer so the method can apply attributes directly.
- // This isn't needed for class name bindings because they use the
- // existing classNames infrastructure.
- var attributeBindings = get(this, 'attributeBindings');
- if (attributeBindings.length) {
- this._applyAttributeBindings(buffer, attributeBindings);
- }
- buffer.setClasses(this.classNames);
- buffer.id(this.elementId);
- var role = get(this, 'ariaRole');
- if (role) {
- buffer.attr('role', role);
- }
- if (get(this, 'isVisible') === false) {
- buffer.style('display', 'none');
- }
- },
- // ..........................................................
- // STANDARD RENDER PROPERTIES
- //
- /**
- Tag name for the view's outer element. The tag name is only used when an
- element is first created. If you change the `tagName` for an element, you
- must destroy and recreate the view element.
- By default, the render buffer will use a `<div>` tag for views.
- @property tagName
- @type String
- @default null
- */
- // We leave this null by default so we can tell the difference between
- // the default case and a user-specified tag.
- tagName: null,
- /**
- The WAI-ARIA role of the control represented by this view. For example, a
- button may have a role of type 'button', or a pane may have a role of
- type 'alertdialog'. This property is used by assistive software to help
- visually challenged users navigate rich web applications.
- The full list of valid WAI-ARIA roles is available at:
- [http://www.w3.org/TR/wai-aria/roles#roles_categorization](http://www.w3.org/TR/wai-aria/roles#roles_categorization)
- @property ariaRole
- @type String
- @default null
- */
- ariaRole: null,
- /**
- Standard CSS class names to apply to the view's outer element. This
- property automatically inherits any class names defined by the view's
- superclasses as well.
- @property classNames
- @type Array
- @default ['ember-view']
- */
- classNames: ['ember-view'],
- /**
- A list of properties of the view to apply as class names. If the property
- is a string value, the value of that string will be applied as a class
- name.
- ```javascript
- // Applies the 'high' class to the view element
- Ember.View.extend({
- classNameBindings: ['priority']
- priority: 'high'
- });
- ```
- If the value of the property is a Boolean, the name of that property is
- added as a dasherized class name.
- ```javascript
- // Applies the 'is-urgent' class to the view element
- Ember.View.extend({
- classNameBindings: ['isUrgent']
- isUrgent: true
- });
- ```
- If you would prefer to use a custom value instead of the dasherized
- property name, you can pass a binding like this:
- ```javascript
- // Applies the 'urgent' class to the view element
- Ember.View.extend({
- classNameBindings: ['isUrgent:urgent']
- isUrgent: true
- });
- ```
- This list of properties is inherited from the view's superclasses as well.
- @property classNameBindings
- @type Array
- @default []
- */
- classNameBindings: EMPTY_ARRAY,
- /**
- A list of properties of the view to apply as attributes. If the property is
- a string value, the value of that string will be applied as the attribute.
- ```javascript
- // Applies the type attribute to the element
- // with the value "button", like <div type="button">
- Ember.View.extend({
- attributeBindings: ['type'],
- type: 'button'
- });
- ```
- If the value of the property is a Boolean, the name of that property is
- added as an attribute.
- ```javascript
- // Renders something like <div enabled="enabled">
- Ember.View.extend({
- attributeBindings: ['enabled'],
- enabled: true
- });
- ```
- @property attributeBindings
- */
- attributeBindings: EMPTY_ARRAY,
- // .......................................................
- // CORE DISPLAY METHODS
- //
- /**
- Setup a view, but do not finish waking it up.
- * configure `childViews`
- * register the view with the global views hash, which is used for event
- dispatch
- @method init
- @private
- */
- init: function() {
- this.elementId = this.elementId || guidFor(this);
- this._super();
- // setup child views. be sure to clone the child views array first
- this._childViews = this._childViews.slice();
- Ember.assert("Only arrays are allowed for 'classNameBindings'", Ember.typeOf(this.classNameBindings) === 'array');
- this.classNameBindings = Ember.A(this.classNameBindings.slice());
- Ember.assert("Only arrays are allowed for 'classNames'", Ember.typeOf(this.classNames) === 'array');
- this.classNames = Ember.A(this.classNames.slice());
- },
- appendChild: function(view, options) {
- return this.currentState.appendChild(this, view, options);
- },
- /**
- Removes the child view from the parent view.
- @method removeChild
- @param {Ember.View} view
- @return {Ember.View} receiver
- */
- removeChild: function(view) {
- // If we're destroying, the entire subtree will be
- // freed, and the DOM will be handled separately,
- // so no need to mess with childViews.
- if (this.isDestroying) { return; }
- // update parent node
- set(view, '_parentView', null);
- // remove view from childViews array.
- var childViews = this._childViews;
- Ember.EnumerableUtils.removeObject(childViews, view);
- this.propertyDidChange('childViews'); // HUH?! what happened to will change?
- return this;
- },
- /**
- Removes all children from the `parentView`.
- @method removeAllChildren
- @return {Ember.View} receiver
- */
- removeAllChildren: function() {
- return this.mutateChildViews(function(parentView, view) {
- parentView.removeChild(view);
- });
- },
- destroyAllChildren: function() {
- return this.mutateChildViews(function(parentView, view) {
- view.destroy();
- });
- },
- /**
- Removes the view from its `parentView`, if one is found. Otherwise
- does nothing.
- @method removeFromParent
- @return {Ember.View} receiver
- */
- removeFromParent: function() {
- var parent = this._parentView;
- // Remove DOM element from parent
- this.remove();
- if (parent) { parent.removeChild(this); }
- return this;
- },
- /**
- You must call `destroy` on a view to destroy the view (and all of its
- child views). This will remove the view from any parent node, then make
- sure that the DOM element managed by the view can be released by the
- memory manager.
- @method destroy
- */
- destroy: function() {
- var childViews = this._childViews,
- // get parentView before calling super because it'll be destroyed
- nonVirtualParentView = get(this, 'parentView'),
- viewName = this.viewName,
- childLen, i;
- if (!this._super()) { return; }
- childLen = childViews.length;
- for (i=childLen-1; i>=0; i--) {
- childViews[i].removedFromDOM = true;
- }
- // remove from non-virtual parent view if viewName was specified
- if (viewName && nonVirtualParentView) {
- nonVirtualParentView.set(viewName, null);
- }
- childLen = childViews.length;
- for (i=childLen-1; i>=0; i--) {
- childViews[i].destroy();
- }
- return this;
- },
- /**
- Instantiates a view to be added to the childViews array during view
- initialization. You generally will not call this method directly unless
- you are overriding `createChildViews()`. Note that this method will
- automatically configure the correct settings on the new view instance to
- act as a child of the parent.
- @method createChildView
- @param {Class|String} viewClass
- @param {Hash} [attrs] Attributes to add
- @return {Ember.View} new instance
- */
- createChildView: function(view, attrs) {
- if (!view) {
- throw new TypeError("createChildViews first argument must exist");
- }
- if (view.isView && view._parentView === this && view.container === this.container) {
- return view;
- }
- attrs = attrs || {};
- attrs._parentView = this;
- if (Ember.CoreView.detect(view)) {
- attrs.templateData = attrs.templateData || get(this, 'templateData');
- attrs.container = this.container;
- view = view.create(attrs);
- // don't set the property on a virtual view, as they are invisible to
- // consumers of the view API
- if (view.viewName) {
- set(get(this, 'concreteView'), view.viewName, view);
- }
- } else if ('string' === typeof view) {
- var fullName = 'view:' + view;
- var View = this.container.lookupFactory(fullName);
- Ember.assert("Could not find view: '" + fullName + "'", !!View);
- attrs.templateData = get(this, 'templateData');
- view = View.create(attrs);
- } else {
- Ember.assert('You must pass instance or subclass of View', view.isView);
- attrs.container = this.container;
- if (!get(view, 'templateData')) {
- attrs.templateData = get(this, 'templateData');
- }
- Ember.setProperties(view, attrs);
- }
- return view;
- },
- becameVisible: Ember.K,
- becameHidden: Ember.K,
- /**
- When the view's `isVisible` property changes, toggle the visibility
- element of the actual DOM element.
- @method _isVisibleDidChange
- @private
- */
- _isVisibleDidChange: Ember.observer('isVisible', function() {
- var $el = this.$();
- if (!$el) { return; }
- var isVisible = get(this, 'isVisible');
- $el.toggle(isVisible);
- if (this._isAncestorHidden()) { return; }
- if (isVisible) {
- this._notifyBecameVisible();
- } else {
- this._notifyBecameHidden();
- }
- }),
- _notifyBecameVisible: function() {
- this.trigger('becameVisible');
- this.forEachChildView(function(view) {
- var isVisible = get(view, 'isVisible');
- if (isVisible || isVisible === null) {
- view._notifyBecameVisible();
- }
- });
- },
- _notifyBecameHidden: function() {
- this.trigger('becameHidden');
- this.forEachChildView(function(view) {
- var isVisible = get(view, 'isVisible');
- if (isVisible || isVisible === null) {
- view._notifyBecameHidden();
- }
- });
- },
- _isAncestorHidden: function() {
- var parent = get(this, 'parentView');
- while (parent) {
- if (get(parent, 'isVisible') === false) { return true; }
- parent = get(parent, 'parentView');
- }
- return false;
- },
- clearBuffer: function() {
- this.invokeRecursively(function(view) {
- view.buffer = null;
- });
- },
- transitionTo: function(state, children) {
- var priorState = this.currentState,
- currentState = this.currentState = this.states[state];
- this.state = state;
- if (priorState && priorState.exit) { priorState.exit(this); }
- if (currentState.enter) { currentState.enter(this); }
- if (state === 'inDOM') { delete Ember.meta(this).cache.element; }
- if (children !== false) {
- this.forEachChildView(function(view) {
- view.transitionTo(state);
- });
- }
- },
- // .......................................................
- // EVENT HANDLING
- //
- /**
- Handle events from `Ember.EventDispatcher`
- @method handleEvent
- @param eventName {String}
- @param evt {Event}
- @private
- */
- handleEvent: function(eventName, evt) {
- return this.currentState.handleEvent(this, eventName, evt);
- },
- registerObserver: function(root, path, target, observer) {
- if (!observer && 'function' === typeof target) {
- observer = target;
- target = null;
- }
- if (!root || typeof root !== 'object') {
- return;
- }
- var view = this,
- stateCheckedObserver = function() {
- view.currentState.invokeObserver(this, observer);
- },
- scheduledObserver = function() {
- Ember.run.scheduleOnce('render', this, stateCheckedObserver);
- };
- Ember.addObserver(root, path, target, scheduledObserver);
- this.one('willClearRender', function() {
- Ember.removeObserver(root, path, target, scheduledObserver);
- });
- }
- });
- /*
- Describe how the specified actions should behave in the various
- states that a view can exist in. Possible states:
- * preRender: when a view is first instantiated, and after its
- element was destroyed, it is in the preRender state
- * inBuffer: once a view has been rendered, but before it has
- been inserted into the DOM, it is in the inBuffer state
- * hasElement: the DOM representation of the view is created,
- and is ready to be inserted
- * inDOM: once a view has been inserted into the DOM it is in
- the inDOM state. A view spends the vast majority of its
- existence in this state.
- * destroyed: once a view has been destroyed (using the destroy
- method), it is in this state. No further actions can be invoked
- on a destroyed view.
- */
- // in the destroyed state, everything is illegal
- // before rendering has begun, all legal manipulations are noops.
- // inside the buffer, legal manipulations are done on the buffer
- // once the view has been inserted into the DOM, legal manipulations
- // are done on the DOM element.
- function notifyMutationListeners() {
- Ember.run.once(Ember.View, 'notifyMutationListeners');
- }
- var DOMManager = {
- prepend: function(view, html) {
- view.$().prepend(html);
- notifyMutationListeners();
- },
- after: function(view, html) {
- view.$().after(html);
- notifyMutationListeners();
- },
- html: function(view, html) {
- view.$().html(html);
- notifyMutationListeners();
- },
- replace: function(view) {
- var element = get(view, 'element');
- set(view, 'element', null);
- view._insertElementLater(function() {
- Ember.$(element).replaceWith(get(view, 'element'));
- notifyMutationListeners();
- });
- },
- remove: function(view) {
- view.$().remove();
- notifyMutationListeners();
- },
- empty: function(view) {
- view.$().empty();
- notifyMutationListeners();
- }
- };
- Ember.View.reopen({
- domManager: DOMManager
- });
- Ember.View.reopenClass({
- /**
- Parse a path and return an object which holds the parsed properties.
- For example a path like "content.isEnabled:enabled:disabled" will return the
- following object:
- ```javascript
- {
- path: "content.isEnabled",
- className: "enabled",
- falsyClassName: "disabled",
- classNames: ":enabled:disabled"
- }
- ```
- @method _parsePropertyPath
- @static
- @private
- */
- _parsePropertyPath: function(path) {
- var split = path.split(':'),
- propertyPath = split[0],
- classNames = "",
- className,
- falsyClassName;
- // check if the property is defined as prop:class or prop:trueClass:falseClass
- if (split.length > 1) {
- className = split[1];
- if (split.length === 3) { falsyClassName = split[2]; }
- classNames = ':' + className;
- if (falsyClassName) { classNames += ":" + falsyClassName; }
- }
- return {
- path: propertyPath,
- classNames: classNames,
- className: (className === '') ? undefined : className,
- falsyClassName: falsyClassName
- };
- },
- /**
- Get the class name for a given value, based on the path, optional
- `className` and optional `falsyClassName`.
- - if a `className` or `falsyClassName` has been specified:
- - if the value is truthy and `className` has been specified,
- `className` is returned
- - if the value is falsy and `falsyClassName` has been specified,
- `falsyClassName` is returned
- - otherwise `null` is returned
- - if the value is `true`, the dasherized last part of the supplied path
- is returned
- - if the value is not `false`, `undefined` or `null`, the `value`
- is returned
- - if none of the above rules apply, `null` is returned
- @method _classStringForValue
- @param path
- @param val
- @param className
- @param falsyClassName
- @static
- @private
- */
- _classStringForValue: function(path, val, className, falsyClassName) {
- // When using the colon syntax, evaluate the truthiness or falsiness
- // of the value to determine which className to return
- if (className || falsyClassName) {
- if (className && !!val) {
- return className;
- } else if (falsyClassName && !val) {
- return falsyClassName;
- } else {
- return null;
- }
- // If value is a Boolean and true, return the dasherized property
- // name.
- } else if (val === true) {
- // Normalize property path to be suitable for use
- // as a class name. For exaple, content.foo.barBaz
- // becomes bar-baz.
- var parts = path.split('.');
- return Ember.String.dasherize(parts[parts.length-1]);
- // If the value is not false, undefined, or null, return the current
- // value of the property.
- } else if (val !== false && val != null) {
- return val;
- // Nothing to display. Return null so that the old class is removed
- // but no new class is added.
- } else {
- return null;
- }
- }
- });
- var mutation = Ember.Object.extend(Ember.Evented).create();
- Ember.View.addMutationListener = function(callback) {
- mutation.on('change', callback);
- };
- Ember.View.removeMutationListener = function(callback) {
- mutation.off('change', callback);
- };
- Ember.View.notifyMutationListeners = function() {
- mutation.trigger('change');
- };
- /**
- Global views hash
- @property views
- @static
- @type Hash
- */
- Ember.View.views = {};
- // If someone overrides the child views computed property when
- // defining their class, we want to be able to process the user's
- // supplied childViews and then restore the original computed property
- // at view initialization time. This happens in Ember.ContainerView's init
- // method.
- Ember.View.childViewsProperty = childViewsProperty;
- Ember.View.applyAttributeBindings = function(elem, name, value) {
- var type = Ember.typeOf(value);
- // if this changes, also change the logic in ember-handlebars/lib/helpers/binding.js
- if (name !== 'value' && (type === 'string' || (type === 'number' && !isNaN(value)))) {
- if (value !== elem.attr(name)) {
- elem.attr(name, value);
- }
- } else if (name === 'value' || type === 'boolean') {
- if (Ember.isNone(value) || value === false) {
- // `null`, `undefined` or `false` should remove attribute
- elem.removeAttr(name);
- elem.prop(name, '');
- } else if (value !== elem.prop(name)) {
- // value should always be properties
- elem.prop(name, value);
- }
- } else if (!value) {
- elem.removeAttr(name);
- }
- };
- Ember.View.states = states;
- })();
- (function() {
- /**
- @module ember
- @submodule ember-views
- */
- var get = Ember.get, set = Ember.set;
- Ember.View.states._default = {
- // appendChild is only legal while rendering the buffer.
- appendChild: function() {
- throw "You can't use appendChild outside of the rendering process";
- },
- $: function() {
- return undefined;
- },
- getElement: function() {
- return null;
- },
- // Handle events from `Ember.EventDispatcher`
- handleEvent: function() {
- return true; // continue event propagation
- },
- destroyElement: function(view) {
- set(view, 'element', null);
- if (view._scheduledInsert) {
- Ember.run.cancel(view._scheduledInsert);
- view._scheduledInsert = null;
- }
- return view;
- },
- renderToBufferIfNeeded: function () {
- return false;
- },
- rerender: Ember.K,
- invokeObserver: Ember.K
- };
- })();
- (function() {
- /**
- @module ember
- @submodule ember-views
- */
- var preRender = Ember.View.states.preRender = Ember.create(Ember.View.states._default);
- Ember.merge(preRender, {
- // a view leaves the preRender state once its element has been
- // created (createElement).
- insertElement: function(view, fn) {
- view.createElement();
- var viewCollection = view.viewHierarchyCollection();
- viewCollection.trigger('willInsertElement');
- fn.call(view);
- // We transition to `inDOM` if the element exists in the DOM
- var element = view.get('element');
- while (element = element.parentNode) {
- if (element === document) {
- viewCollection.transitionTo('inDOM', false);
- viewCollection.trigger('didInsertElement');
- }
- }
- },
- renderToBufferIfNeeded: function(view, buffer) {
- view.renderToBuffer(buffer);
- return true;
- },
- empty: Ember.K,
- setElement: function(view, value) {
- if (value !== null) {
- view.transitionTo('hasElement');
- }
- return value;
- }
- });
- })();
- (function() {
- /**
- @module ember
- @submodule ember-views
- */
- var get = Ember.get, set = Ember.set;
- var inBuffer = Ember.View.states.inBuffer = Ember.create(Ember.View.states._default);
- Ember.merge(inBuffer, {
- $: function(view, sel) {
- // if we don't have an element yet, someone calling this.$() is
- // trying to update an element that isn't in the DOM. Instead,
- // rerender the view to allow the render method to reflect the
- // changes.
- view.rerender();
- return Ember.$();
- },
- // when a view is rendered in a buffer, rerendering it simply
- // replaces the existing buffer with a new one
- rerender: function(view) {
- throw new Ember.Error("Something you did caused a view to re-render after it rendered but before it was inserted into the DOM.");
- },
- // when a view is rendered in a buffer, appending a child
- // view will render that view and append the resulting
- // buffer into its buffer.
- appendChild: function(view, childView, options) {
- var buffer = view.buffer, _childViews = view._childViews;
- childView = view.createChildView(childView, options);
- if (!_childViews.length) { _childViews = view._childViews = _childViews.slice(); }
- _childViews.push(childView);
- childView.renderToBuffer(buffer);
- view.propertyDidChange('childViews');
- return childView;
- },
- // when a view is rendered in a buffer, destroying the
- // element will simply destroy the buffer and put the
- // state back into the preRender state.
- destroyElement: function(view) {
- view.clearBuffer();
- var viewCollection = view._notifyWillDestroyElement();
- viewCollection.transitionTo('preRender', false);
- return view;
- },
- empty: function() {
- Ember.assert("Emptying a view in the inBuffer state is not allowed and " +
- "should not happen under normal circumstances. Most likely " +
- "there is a bug in your application. This may be due to " +
- "excessive property change notifications.");
- },
- renderToBufferIfNeeded: function (view, buffer) {
- return false;
- },
- // It should be impossible for a rendered view to be scheduled for
- // insertion.
- insertElement: function() {
- throw "You can't insert an element that has already been rendered";
- },
- setElement: function(view, value) {
- if (value === null) {
- view.transitionTo('preRender');
- } else {
- view.clearBuffer();
- view.transitionTo('hasElement');
- }
- return value;
- },
- invokeObserver: function(target, observer) {
- observer.call(target);
- }
- });
- })();
- (function() {
- /**
- @module ember
- @submodule ember-views
- */
- var get = Ember.get, set = Ember.set;
- var hasElement = Ember.View.states.hasElement = Ember.create(Ember.View.states._default);
- Ember.merge(hasElement, {
- $: function(view, sel) {
- var elem = get(view, 'element');
- return sel ? Ember.$(sel, elem) : Ember.$(elem);
- },
- getElement: function(view) {
- var parent = get(view, 'parentView');
- if (parent) { parent = get(parent, 'element'); }
- if (parent) { return view.findElementInParentElement(parent); }
- return Ember.$("#" + get(view, 'elementId'))[0];
- },
- setElement: function(view, value) {
- if (value === null) {
- view.transitionTo('preRender');
- } else {
- throw "You cannot set an element to a non-null value when the element is already in the DOM.";
- }
- return value;
- },
- // once the view has been inserted into the DOM, rerendering is
- // deferred to allow bindings to synchronize.
- rerender: function(view) {
- view.triggerRecursively('willClearRender');
- view.clearRenderedChildren();
- view.domManager.replace(view);
- return view;
- },
- // once the view is already in the DOM, destroying it removes it
- // from the DOM, nukes its element, and puts it back into the
- // preRender state if inDOM.
- destroyElement: function(view) {
- view._notifyWillDestroyElement();
- view.domManager.remove(view);
- set(view, 'element', null);
- if (view._scheduledInsert) {
- Ember.run.cancel(view._scheduledInsert);
- view._scheduledInsert = null;
- }
- return view;
- },
- empty: function(view) {
- var _childViews = view._childViews, len, idx;
- if (_childViews) {
- len = _childViews.length;
- for (idx = 0; idx < len; idx++) {
- _childViews[idx]._notifyWillDestroyElement();
- }
- }
- view.domManager.empty(view);
- },
- // Handle events from `Ember.EventDispatcher`
- handleEvent: function(view, eventName, evt) {
- if (view.has(eventName)) {
- // Handler should be able to re-dispatch events, so we don't
- // preventDefault or stopPropagation.
- return view.trigger(eventName, evt);
- } else {
- return true; // continue event propagation
- }
- },
- invokeObserver: function(target, observer) {
- observer.call(target);
- }
- });
- })();
- (function() {
- /**
- @module ember
- @submodule ember-views
- */
- var hasElement = Ember.View.states.hasElement;
- var inDOM = Ember.View.states.inDOM = Ember.create(hasElement);
- Ember.merge(inDOM, {
- enter: function(view) {
- // Register the view for event handling. This hash is used by
- // Ember.EventDispatcher to dispatch incoming events.
- if (!view.isVirtual) {
- Ember.assert("Attempted to register a view with an id already in use: "+view.elementId, !Ember.View.views[view.elementId]);
- Ember.View.views[view.elementId] = view;
- }
- view.addBeforeObserver('elementId', function() {
- throw new Ember.Error("Changing a view's elementId after creation is not allowed");
- });
- },
- exit: function(view) {
- if (!this.isVirtual) delete Ember.View.views[view.elementId];
- },
- insertElement: function(view, fn) {
- throw "You can't insert an element into the DOM that has already been inserted";
- }
- });
- })();
- (function() {
- /**
- @module ember
- @submodule ember-views
- */
- var destroyingError = "You can't call %@ on a view being destroyed", fmt = Ember.String.fmt;
- var destroying = Ember.View.states.destroying = Ember.create(Ember.View.states._default);
- Ember.merge(destroying, {
- appendChild: function() {
- throw fmt(destroyingError, ['appendChild']);
- },
- rerender: function() {
- throw fmt(destroyingError, ['rerender']);
- },
- destroyElement: function() {
- throw fmt(destroyingError, ['destroyElement']);
- },
- empty: function() {
- throw fmt(destroyingError, ['empty']);
- },
- setElement: function() {
- throw fmt(destroyingError, ["set('element', ...)"]);
- },
- renderToBufferIfNeeded: function() {
- return false;
- },
- // Since element insertion is scheduled, don't do anything if
- // the view has been destroyed between scheduling and execution
- insertElement: Ember.K
- });
- })();
- (function() {
- Ember.View.cloneStates = function(from) {
- var into = {};
- into._default = {};
- into.preRender = Ember.create(into._default);
- into.destroying = Ember.create(into._default);
- into.inBuffer = Ember.create(into._default);
- into.hasElement = Ember.create(into._default);
- into.inDOM = Ember.create(into.hasElement);
- for (var stateName in from) {
- if (!from.hasOwnProperty(stateName)) { continue; }
- Ember.merge(into[stateName], from[stateName]);
- }
- return into;
- };
- })();
- (function() {
- var states = Ember.View.cloneStates(Ember.View.states);
- /**
- @module ember
- @submodule ember-views
- */
- var get = Ember.get, set = Ember.set;
- var forEach = Ember.EnumerableUtils.forEach;
- var ViewCollection = Ember._ViewCollection;
- /**
- A `ContainerView` is an `Ember.View` subclass that implements `Ember.MutableArray`
- allowing programmatic management of its child views.
- ## Setting Initial Child Views
- The initial array of child views can be set in one of two ways. You can
- provide a `childViews` property at creation time that contains instance of
- `Ember.View`:
- ```javascript
- aContainer = Ember.ContainerView.create({
- childViews: [Ember.View.create(), Ember.View.create()]
- });
- ```
- You can also provide a list of property names whose values are instances of
- `Ember.View`:
- ```javascript
- aContainer = Ember.ContainerView.create({
- childViews: ['aView', 'bView', 'cView'],
- aView: Ember.View.create(),
- bView: Ember.View.create(),
- cView: Ember.View.create()
- });
- ```
- The two strategies can be combined:
- ```javascript
- aContainer = Ember.ContainerView.create({
- childViews: ['aView', Ember.View.create()],
- aView: Ember.View.create()
- });
- ```
- Each child view's rendering will be inserted into the container's rendered
- HTML in the same order as its position in the `childViews` property.
- ## Adding and Removing Child Views
- The container view implements `Ember.MutableArray` allowing programmatic management of its child views.
- To remove a view, pass that view into a `removeObject` call on the container view.
- Given an empty `<body>` the following code
- ```javascript
- aContainer = Ember.ContainerView.create({
- classNames: ['the-container'],
- childViews: ['aView', 'bView'],
- aView: Ember.View.create({
- template: Ember.Handlebars.compile("A")
- }),
- bView: Ember.View.create({
- template: Ember.Handlebars.compile("B")
- })
- });
- aContainer.appendTo('body');
- ```
- Results in the HTML
- ```html
- <div class="ember-view the-container">
- <div class="ember-view">A</div>
- <div class="ember-view">B</div>
- </div>
- ```
- Removing a view
- ```javascript
- aContainer.toArray(); // [aContainer.aView, aContainer.bView]
- aContainer.removeObject(aContainer.get('bView'));
- aContainer.toArray(); // [aContainer.aView]
- ```
- Will result in the following HTML
- ```html
- <div class="ember-view the-container">
- <div class="ember-view">A</div>
- </div>
- ```
- Similarly, adding a child view is accomplished by adding `Ember.View` instances to the
- container view.
- Given an empty `<body>` the following code
- ```javascript
- aContainer = Ember.ContainerView.create({
- classNames: ['the-container'],
- childViews: ['aView', 'bView'],
- aView: Ember.View.create({
- template: Ember.Handlebars.compile("A")
- }),
- bView: Ember.View.create({
- template: Ember.Handlebars.compile("B")
- })
- });
- aContainer.appendTo('body');
- ```
- Results in the HTML
- ```html
- <div class="ember-view the-container">
- <div class="ember-view">A</div>
- <div class="ember-view">B</div>
- </div>
- ```
- Adding a view
- ```javascript
- AnotherViewClass = Ember.View.extend({
- template: Ember.Handlebars.compile("Another view")
- });
- aContainer.toArray(); // [aContainer.aView, aContainer.bView]
- aContainer.pushObject(AnotherViewClass.create());
- aContainer.toArray(); // [aContainer.aView, aContainer.bView, <AnotherViewClass instance>]
- ```
- Will result in the following HTML
- ```html
- <div class="ember-view the-container">
- <div class="ember-view">A</div>
- <div class="ember-view">B</div>
- <div class="ember-view">Another view</div>
- </div>
- ```
- ## Templates and Layout
- A `template`, `templateName`, `defaultTemplate`, `layout`, `layoutName` or
- `defaultLayout` property on a container view will not result in the template
- or layout being rendered. The HTML contents of a `Ember.ContainerView`'s DOM
- representation will only be the rendered HTML of its child views.
- @class ContainerView
- @namespace Ember
- @extends Ember.View
- */
- Ember.ContainerView = Ember.View.extend(Ember.MutableArray, {
- states: states,
- init: function() {
- this._super();
- var childViews = get(this, 'childViews');
- // redefine view's childViews property that was obliterated
- Ember.defineProperty(this, 'childViews', Ember.View.childViewsProperty);
- var _childViews = this._childViews;
- forEach(childViews, function(viewName, idx) {
- var view;
- if ('string' === typeof viewName) {
- view = get(this, viewName);
- view = this.createChildView(view);
- set(this, viewName, view);
- } else {
- view = this.createChildView(viewName);
- }
- _childViews[idx] = view;
- }, this);
- var currentView = get(this, 'currentView');
- if (currentView) {
- if (!_childViews.length) { _childViews = this._childViews = this._childViews.slice(); }
- _childViews.push(this.createChildView(currentView));
- }
- },
- replace: function(idx, removedCount, addedViews) {
- var addedCount = addedViews ? get(addedViews, 'length') : 0;
- var self = this;
- Ember.assert("You can't add a child to a container that is already a child of another view", Ember.A(addedViews).every(function(item) { return !get(item, '_parentView') || get(item, '_parentView') === self; }));
- this.arrayContentWillChange(idx, removedCount, addedCount);
- this.childViewsWillChange(this._childViews, idx, removedCount);
- if (addedCount === 0) {
- this._childViews.splice(idx, removedCount) ;
- } else {
- var args = [idx, removedCount].concat(addedViews);
- if (addedViews.length && !this._childViews.length) { this._childViews = this._childViews.slice(); }
- this._childViews.splice.apply(this._childViews, args);
- }
- this.arrayContentDidChange(idx, removedCount, addedCount);
- this.childViewsDidChange(this._childViews, idx, removedCount, addedCount);
- return this;
- },
- objectAt: function(idx) {
- return this._childViews[idx];
- },
- length: Ember.computed(function () {
- return this._childViews.length;
- }).volatile(),
- /**
- Instructs each child view to render to the passed render buffer.
- @private
- @method render
- @param {Ember.RenderBuffer} buffer the buffer to render to
- */
- render: function(buffer) {
- this.forEachChildView(function(view) {
- view.renderToBuffer(buffer);
- });
- },
- instrumentName: 'container',
- /**
- When a child view is removed, destroy its element so that
- it is removed from the DOM.
- The array observer that triggers this action is set up in the
- `renderToBuffer` method.
- @private
- @method childViewsWillChange
- @param {Ember.Array} views the child views array before mutation
- @param {Number} start the start position of the mutation
- @param {Number} removed the number of child views removed
- **/
- childViewsWillChange: function(views, start, removed) {
- this.propertyWillChange('childViews');
- if (removed > 0) {
- var changedViews = views.slice(start, start+removed);
- // transition to preRender before clearing parentView
- this.currentState.childViewsWillChange(this, views, start, removed);
- this.initializeViews(changedViews, null, null);
- }
- },
- removeChild: function(child) {
- this.removeObject(child);
- return this;
- },
- /**
- When a child view is added, make sure the DOM gets updated appropriately.
- If the view has already rendered an element, we tell the child view to
- create an element and insert it into the DOM. If the enclosing container
- view has already written to a buffer, but not yet converted that buffer
- into an element, we insert the string representation of the child into the
- appropriate place in the buffer.
- @private
- @method childViewsDidChange
- @param {Ember.Array} views the array of child views afte the mutation has occurred
- @param {Number} start the start position of the mutation
- @param {Number} removed the number of child views removed
- @param {Number} the number of child views added
- */
- childViewsDidChange: function(views, start, removed, added) {
- if (added > 0) {
- var changedViews = views.slice(start, start+added);
- this.initializeViews(changedViews, this, get(this, 'templateData'));
- this.currentState.childViewsDidChange(this, views, start, added);
- }
- this.propertyDidChange('childViews');
- },
- initializeViews: function(views, parentView, templateData) {
- forEach(views, function(view) {
- set(view, '_parentView', parentView);
- if (!view.container && parentView) {
- set(view, 'container', parentView.container);
- }
- if (!get(view, 'templateData')) {
- set(view, 'templateData', templateData);
- }
- });
- },
- currentView: null,
- _currentViewWillChange: Ember.beforeObserver('currentView', function() {
- var currentView = get(this, 'currentView');
- if (currentView) {
- currentView.destroy();
- }
- }),
- _currentViewDidChange: Ember.observer('currentView', function() {
- var currentView = get(this, 'currentView');
- if (currentView) {
- Ember.assert("You tried to set a current view that already has a parent. Make sure you don't have multiple outlets in the same view.", !get(currentView, '_parentView'));
- this.pushObject(currentView);
- }
- }),
- _ensureChildrenAreInDOM: function () {
- this.currentState.ensureChildrenAreInDOM(this);
- }
- });
- Ember.merge(states._default, {
- childViewsWillChange: Ember.K,
- childViewsDidChange: Ember.K,
- ensureChildrenAreInDOM: Ember.K
- });
- Ember.merge(states.inBuffer, {
- childViewsDidChange: function(parentView, views, start, added) {
- throw new Ember.Error('You cannot modify child views while in the inBuffer state');
- }
- });
- Ember.merge(states.hasElement, {
- childViewsWillChange: function(view, views, start, removed) {
- for (var i=start; i<start+removed; i++) {
- views[i].remove();
- }
- },
- childViewsDidChange: function(view, views, start, added) {
- Ember.run.scheduleOnce('render', view, '_ensureChildrenAreInDOM');
- },
- ensureChildrenAreInDOM: function(view) {
- var childViews = view._childViews, i, len, childView, previous, buffer, viewCollection = new ViewCollection();
- for (i = 0, len = childViews.length; i < len; i++) {
- childView = childViews[i];
- if (!buffer) { buffer = Ember.RenderBuffer(); buffer._hasElement = false; }
- if (childView.renderToBufferIfNeeded(buffer)) {
- viewCollection.push(childView);
- } else if (viewCollection.length) {
- insertViewCollection(view, viewCollection, previous, buffer);
- buffer = null;
- previous = childView;
- viewCollection.clear();
- } else {
- previous = childView;
- }
- }
- if (viewCollection.length) {
- insertViewCollection(view, viewCollection, previous, buffer);
- }
- }
- });
- function insertViewCollection(view, viewCollection, previous, buffer) {
- viewCollection.triggerRecursively('willInsertElement');
- if (previous) {
- previous.domManager.after(previous, buffer.string());
- } else {
- view.domManager.prepend(view, buffer.string());
- }
- viewCollection.forEach(function(v) {
- v.transitionTo('inDOM');
- v.propertyDidChange('element');
- v.triggerRecursively('didInsertElement');
- });
- }
- })();
- (function() {
- /**
- @module ember
- @submodule ember-views
- */
- var get = Ember.get, set = Ember.set, fmt = Ember.String.fmt;
- /**
- `Ember.CollectionView` is an `Ember.View` descendent responsible for managing
- a collection (an array or array-like object) by maintaining a child view object
- and associated DOM representation for each item in the array and ensuring
- that child views and their associated rendered HTML are updated when items in
- the array are added, removed, or replaced.
- ## Setting content
- The managed collection of objects is referenced as the `Ember.CollectionView`
- instance's `content` property.
- ```javascript
- someItemsView = Ember.CollectionView.create({
- content: ['A', 'B','C']
- })
- ```
- The view for each item in the collection will have its `content` property set
- to the item.
- ## Specifying itemViewClass
- By default the view class for each item in the managed collection will be an
- instance of `Ember.View`. You can supply a different class by setting the
- `CollectionView`'s `itemViewClass` property.
- Given an empty `<body>` and the following code:
- ```javascript
- someItemsView = Ember.CollectionView.create({
- classNames: ['a-collection'],
- content: ['A','B','C'],
- itemViewClass: Ember.View.extend({
- template: Ember.Handlebars.compile("the letter: {{view.content}}")
- })
- });
- someItemsView.appendTo('body');
- ```
- Will result in the following HTML structure
- ```html
- <div class="ember-view a-collection">
- <div class="ember-view">the letter: A</div>
- <div class="ember-view">the letter: B</div>
- <div class="ember-view">the letter: C</div>
- </div>
- ```
- ## Automatic matching of parent/child tagNames
- Setting the `tagName` property of a `CollectionView` to any of
- "ul", "ol", "table", "thead", "tbody", "tfoot", "tr", or "select" will result
- in the item views receiving an appropriately matched `tagName` property.
- Given an empty `<body>` and the following code:
- ```javascript
- anUnorderedListView = Ember.CollectionView.create({
- tagName: 'ul',
- content: ['A','B','C'],
- itemViewClass: Ember.View.extend({
- template: Ember.Handlebars.compile("the letter: {{view.content}}")
- })
- });
- anUnorderedListView.appendTo('body');
- ```
- Will result in the following HTML structure
- ```html
- <ul class="ember-view a-collection">
- <li class="ember-view">the letter: A</li>
- <li class="ember-view">the letter: B</li>
- <li class="ember-view">the letter: C</li>
- </ul>
- ```
- Additional `tagName` pairs can be provided by adding to
- `Ember.CollectionView.CONTAINER_MAP `
- ```javascript
- Ember.CollectionView.CONTAINER_MAP['article'] = 'section'
- ```
- ## Programmatic creation of child views
- For cases where additional customization beyond the use of a single
- `itemViewClass` or `tagName` matching is required CollectionView's
- `createChildView` method can be overidden:
- ```javascript
- CustomCollectionView = Ember.CollectionView.extend({
- createChildView: function(viewClass, attrs) {
- if (attrs.content.kind == 'album') {
- viewClass = App.AlbumView;
- } else {
- viewClass = App.SongView;
- }
- return this._super(viewClass, attrs);
- }
- });
- ```
- ## Empty View
- You can provide an `Ember.View` subclass to the `Ember.CollectionView`
- instance as its `emptyView` property. If the `content` property of a
- `CollectionView` is set to `null` or an empty array, an instance of this view
- will be the `CollectionView`s only child.
- ```javascript
- aListWithNothing = Ember.CollectionView.create({
- classNames: ['nothing']
- content: null,
- emptyView: Ember.View.extend({
- template: Ember.Handlebars.compile("The collection is empty")
- })
- });
- aListWithNothing.appendTo('body');
- ```
- Will result in the following HTML structure
- ```html
- <div class="ember-view nothing">
- <div class="ember-view">
- The collection is empty
- </div>
- </div>
- ```
- ## Adding and Removing items
- The `childViews` property of a `CollectionView` should not be directly
- manipulated. Instead, add, remove, replace items from its `content` property.
- This will trigger appropriate changes to its rendered HTML.
- @class CollectionView
- @namespace Ember
- @extends Ember.ContainerView
- @since Ember 0.9
- */
- Ember.CollectionView = Ember.ContainerView.extend({
- /**
- A list of items to be displayed by the `Ember.CollectionView`.
- @property content
- @type Ember.Array
- @default null
- */
- content: null,
- /**
- This provides metadata about what kind of empty view class this
- collection would like if it is being instantiated from another
- system (like Handlebars)
- @private
- @property emptyViewClass
- */
- emptyViewClass: Ember.View,
- /**
- An optional view to display if content is set to an empty array.
- @property emptyView
- @type Ember.View
- @default null
- */
- emptyView: null,
- /**
- @property itemViewClass
- @type Ember.View
- @default Ember.View
- */
- itemViewClass: Ember.View,
- /**
- Setup a CollectionView
- @method init
- */
- init: function() {
- var ret = this._super();
- this._contentDidChange();
- return ret;
- },
- /**
- Invoked when the content property is about to change. Notifies observers that the
- entire array content will change.
- @private
- @method _contentWillChange
- */
- _contentWillChange: Ember.beforeObserver('content', function() {
- var content = this.get('content');
- if (content) { content.removeArrayObserver(this); }
- var len = content ? get(content, 'length') : 0;
- this.arrayWillChange(content, 0, len);
- }),
- /**
- Check to make sure that the content has changed, and if so,
- update the children directly. This is always scheduled
- asynchronously, to allow the element to be created before
- bindings have synchronized and vice versa.
- @private
- @method _contentDidChange
- */
- _contentDidChange: Ember.observer('content', function() {
- var content = get(this, 'content');
- if (content) {
- this._assertArrayLike(content);
- content.addArrayObserver(this);
- }
- var len = content ? get(content, 'length') : 0;
- this.arrayDidChange(content, 0, null, len);
- }),
- /**
- Ensure that the content implements Ember.Array
- @private
- @method _assertArrayLike
- */
- _assertArrayLike: function(content) {
- Ember.assert(fmt("an Ember.CollectionView's content must implement Ember.Array. You passed %@", [content]), Ember.Array.detect(content));
- },
- /**
- Removes the content and content observers.
- @method destroy
- */
- destroy: function() {
- if (!this._super()) { return; }
- var content = get(this, 'content');
- if (content) { content.removeArrayObserver(this); }
- if (this._createdEmptyView) {
- this._createdEmptyView.destroy();
- }
- return this;
- },
- /**
- Called when a mutation to the underlying content array will occur.
- This method will remove any views that are no longer in the underlying
- content array.
- Invokes whenever the content array itself will change.
- @method arrayWillChange
- @param {Array} content the managed collection of objects
- @param {Number} start the index at which the changes will occurr
- @param {Number} removed number of object to be removed from content
- */
- arrayWillChange: function(content, start, removedCount) {
- // If the contents were empty before and this template collection has an
- // empty view remove it now.
- var emptyView = get(this, 'emptyView');
- if (emptyView && emptyView instanceof Ember.View) {
- emptyView.removeFromParent();
- }
- // Loop through child views that correspond with the removed items.
- // Note that we loop from the end of the array to the beginning because
- // we are mutating it as we go.
- var childViews = this._childViews, childView, idx, len;
- len = this._childViews.length;
- var removingAll = removedCount === len;
- if (removingAll) {
- this.currentState.empty(this);
- this.invokeRecursively(function(view) {
- view.removedFromDOM = true;
- }, false);
- }
- for (idx = start + removedCount - 1; idx >= start; idx--) {
- childView = childViews[idx];
- childView.destroy();
- }
- },
- /**
- Called when a mutation to the underlying content array occurs.
- This method will replay that mutation against the views that compose the
- `Ember.CollectionView`, ensuring that the view reflects the model.
- This array observer is added in `contentDidChange`.
- @method arrayDidChange
- @param {Array} content the managed collection of objects
- @param {Number} start the index at which the changes occurred
- @param {Number} removed number of object removed from content
- @param {Number} added number of object added to content
- */
- arrayDidChange: function(content, start, removed, added) {
- var addedViews = [], view, item, idx, len, itemViewClass,
- emptyView;
- len = content ? get(content, 'length') : 0;
- if (len) {
- itemViewClass = get(this, 'itemViewClass');
- if ('string' === typeof itemViewClass) {
- itemViewClass = get(itemViewClass) || itemViewClass;
- }
- Ember.assert(fmt("itemViewClass must be a subclass of Ember.View, not %@", [itemViewClass]), 'string' === typeof itemViewClass || Ember.View.detect(itemViewClass));
- for (idx = start; idx < start+added; idx++) {
- item = content.objectAt(idx);
- view = this.createChildView(itemViewClass, {
- content: item,
- contentIndex: idx
- });
- addedViews.push(view);
- }
- } else {
- emptyView = get(this, 'emptyView');
- if (!emptyView) { return; }
- if ('string' === typeof emptyView) {
- emptyView = get(emptyView) || emptyView;
- }
- emptyView = this.createChildView(emptyView);
- addedViews.push(emptyView);
- set(this, 'emptyView', emptyView);
- if (Ember.CoreView.detect(emptyView)) {
- this._createdEmptyView = emptyView;
- }
- }
- this.replace(start, 0, addedViews);
- },
- /**
- Instantiates a view to be added to the childViews array during view
- initialization. You generally will not call this method directly unless
- you are overriding `createChildViews()`. Note that this method will
- automatically configure the correct settings on the new view instance to
- act as a child of the parent.
- The tag name for the view will be set to the tagName of the viewClass
- passed in.
- @method createChildView
- @param {Class} viewClass
- @param {Hash} [attrs] Attributes to add
- @return {Ember.View} new instance
- */
- createChildView: function(view, attrs) {
- view = this._super(view, attrs);
- var itemTagName = get(view, 'tagName');
- if (itemTagName === null || itemTagName === undefined) {
- itemTagName = Ember.CollectionView.CONTAINER_MAP[get(this, 'tagName')];
- set(view, 'tagName', itemTagName);
- }
- return view;
- }
- });
- /**
- A map of parent tags to their default child tags. You can add
- additional parent tags if you want collection views that use
- a particular parent tag to default to a child tag.
- @property CONTAINER_MAP
- @type Hash
- @static
- @final
- */
- Ember.CollectionView.CONTAINER_MAP = {
- ul: 'li',
- ol: 'li',
- table: 'tr',
- thead: 'tr',
- tbody: 'tr',
- tfoot: 'tr',
- tr: 'td',
- select: 'option'
- };
- })();
- (function() {
- /**
- The ComponentTemplateDeprecation mixin is used to provide a useful
- deprecation warning when using either `template` or `templateName` with
- a component. The `template` and `templateName` properties specified at
- extend time are moved to `layout` and `layoutName` respectively.
- `Ember.ComponentTemplateDeprecation` is used internally by Ember in
- `Ember.Component`.
- @class ComponentTemplateDeprecation
- @namespace Ember
- */
- Ember.ComponentTemplateDeprecation = Ember.Mixin.create({
- /**
- @private
- Moves `templateName` to `layoutName` and `template` to `layout` at extend
- time if a layout is not also specified.
- Note that this currently modifies the mixin themselves, which is technically
- dubious but is practically of little consequence. This may change in the
- future.
- @method willMergeMixin
- */
- willMergeMixin: function(props) {
- // must call _super here to ensure that the ActionHandler
- // mixin is setup properly (moves actions -> _actions)
- //
- // Calling super is only OK here since we KNOW that
- // there is another Mixin loaded first.
- this._super.apply(this, arguments);
- var deprecatedProperty, replacementProperty,
- layoutSpecified = (props.layoutName || props.layout);
- if (props.templateName && !layoutSpecified) {
- deprecatedProperty = 'templateName';
- replacementProperty = 'layoutName';
- props.layoutName = props.templateName;
- delete props['templateName'];
- }
- if (props.template && !layoutSpecified) {
- deprecatedProperty = 'template';
- replacementProperty = 'layout';
- props.layout = props.template;
- delete props['template'];
- }
- if (deprecatedProperty) {
- Ember.deprecate('Do not specify ' + deprecatedProperty + ' on a Component, use ' + replacementProperty + ' instead.', false);
- }
- }
- });
- })();
- (function() {
- var get = Ember.get, set = Ember.set, isNone = Ember.isNone,
- a_slice = Array.prototype.slice;
- /**
- @module ember
- @submodule ember-views
- */
- /**
- An `Ember.Component` is a view that is completely
- isolated. Property access in its templates go
- to the view object and actions are targeted at
- the view object. There is no access to the
- surrounding context or outer controller; all
- contextual information must be passed in.
- The easiest way to create an `Ember.Component` is via
- a template. If you name a template
- `components/my-foo`, you will be able to use
- `{{my-foo}}` in other templates, which will make
- an instance of the isolated component.
- ```handlebars
- {{app-profile person=currentUser}}
- ```
- ```handlebars
- <!-- app-profile template -->
- <h1>{{person.title}}</h1>
- <img {{bind-attr src=person.avatar}}>
- <p class='signature'>{{person.signature}}</p>
- ```
- You can use `yield` inside a template to
- include the **contents** of any block attached to
- the component. The block will be executed in the
- context of the surrounding context or outer controller:
- ```handlebars
- {{#app-profile person=currentUser}}
- <p>Admin mode</p>
- {{! Executed in the controller's context. }}
- {{/app-profile}}
- ```
- ```handlebars
- <!-- app-profile template -->
- <h1>{{person.title}}</h1>
- {{! Executed in the components context. }}
- {{yield}} {{! block contents }}
- ```
- If you want to customize the component, in order to
- handle events or actions, you implement a subclass
- of `Ember.Component` named after the name of the
- component. Note that `Component` needs to be appended to the name of
- your subclass like `AppProfileComponent`.
- For example, you could implement the action
- `hello` for the `app-profile` component:
- ```javascript
- App.AppProfileComponent = Ember.Component.extend({
- actions: {
- hello: function(name) {
- console.log("Hello", name);
- }
- }
- });
- ```
- And then use it in the component's template:
- ```handlebars
- <!-- app-profile template -->
- <h1>{{person.title}}</h1>
- {{yield}} <!-- block contents -->
- <button {{action 'hello' person.name}}>
- Say Hello to {{person.name}}
- </button>
- ```
- Components must have a `-` in their name to avoid
- conflicts with built-in controls that wrap HTML
- elements. This is consistent with the same
- requirement in web components.
- @class Component
- @namespace Ember
- @extends Ember.View
- */
- Ember.Component = Ember.View.extend(Ember.TargetActionSupport, Ember.ComponentTemplateDeprecation, {
- init: function() {
- this._super();
- set(this, 'context', this);
- set(this, 'controller', this);
- },
- defaultLayout: function(context, options){
- Ember.Handlebars.helpers['yield'].call(context, options);
- },
- /**
- A components template property is set by passing a block
- during its invocation. It is executed within the parent context.
- Example:
- ```handlebars
- {{#my-component}}
- // something that is run in the context
- // of the parent context
- {{/my-component}}
- ```
- Specifying a template directly to a component is deprecated without
- also specifying the layout property.
- @deprecated
- @property template
- */
- template: Ember.computed(function(key, value) {
- if (value !== undefined) { return value; }
- var templateName = get(this, 'templateName'),
- template = this.templateForName(templateName, 'template');
- Ember.assert("You specified the templateName " + templateName + " for " + this + ", but it did not exist.", !templateName || template);
- return template || get(this, 'defaultTemplate');
- }).property('templateName'),
- /**
- Specifying a components `templateName` is deprecated without also
- providing the `layout` or `layoutName` properties.
- @deprecated
- @property templateName
- */
- templateName: null,
- // during render, isolate keywords
- cloneKeywords: function() {
- return {
- view: this,
- controller: this
- };
- },
- _yield: function(context, options) {
- var view = options.data.view,
- parentView = this._parentView,
- template = get(this, 'template');
- if (template) {
- Ember.assert("A Component must have a parent view in order to yield.", parentView);
- view.appendChild(Ember.View, {
- isVirtual: true,
- tagName: '',
- _contextView: parentView,
- template: template,
- context: get(parentView, 'context'),
- controller: get(parentView, 'controller'),
- templateData: { keywords: parentView.cloneKeywords() }
- });
- }
- },
- /**
- If the component is currently inserted into the DOM of a parent view, this
- property will point to the controller of the parent view.
- @property targetObject
- @type Ember.Controller
- @default null
- */
- targetObject: Ember.computed(function(key) {
- var parentView = get(this, '_parentView');
- return parentView ? get(parentView, 'controller') : null;
- }).property('_parentView'),
- /**
- Triggers a named action on the controller context where the component is used if
- this controller has registered for notifications of the action.
- For example a component for playing or pausing music may translate click events
- into action notifications of "play" or "stop" depending on some internal state
- of the component:
- ```javascript
- App.PlayButtonComponent = Ember.Component.extend({
- click: function(){
- if (this.get('isPlaying')) {
- this.sendAction('play');
- } else {
- this.sendAction('stop');
- }
- }
- });
- ```
- When used inside a template these component actions are configured to
- trigger actions in the outer application context:
- ```handlebars
- {{! application.hbs }}
- {{play-button play="musicStarted" stop="musicStopped"}}
- ```
- When the component receives a browser `click` event it translate this
- interaction into application-specific semantics ("play" or "stop") and
- triggers the specified action name on the controller for the template
- where the component is used:
- ```javascript
- App.ApplicationController = Ember.Controller.extend({
- actions: {
- musicStarted: function(){
- // called when the play button is clicked
- // and the music started playing
- },
- musicStopped: function(){
- // called when the play button is clicked
- // and the music stopped playing
- }
- }
- });
- ```
- If no action name is passed to `sendAction` a default name of "action"
- is assumed.
- ```javascript
- App.NextButtonComponent = Ember.Component.extend({
- click: function(){
- this.sendAction();
- }
- });
- ```
- ```handlebars
- {{! application.hbs }}
- {{next-button action="playNextSongInAlbum"}}
- ```
- ```javascript
- App.ApplicationController = Ember.Controller.extend({
- actions: {
- playNextSongInAlbum: function(){
- ...
- }
- }
- });
- ```
- @method sendAction
- @param [action] {String} the action to trigger
- @param [context] {*} a context to send with the action
- */
- sendAction: function(action) {
- var actionName,
- contexts = a_slice.call(arguments, 1);
- // Send the default action
- if (action === undefined) {
- actionName = get(this, 'action');
- Ember.assert("The default action was triggered on the component " + this.toString() +
- ", but the action name (" + actionName + ") was not a string.",
- isNone(actionName) || typeof actionName === 'string');
- } else {
- actionName = get(this, action);
- Ember.assert("The " + action + " action was triggered on the component " +
- this.toString() + ", but the action name (" + actionName +
- ") was not a string.",
- isNone(actionName) || typeof actionName === 'string');
- }
- // If no action name for that action could be found, just abort.
- if (actionName === undefined) { return; }
- this.triggerAction({
- action: actionName,
- actionContext: contexts
- });
- }
- });
- })();
- (function() {
- })();
- (function() {
- /**
- `Ember.ViewTargetActionSupport` is a mixin that can be included in a
- view class to add a `triggerAction` method with semantics similar to
- the Handlebars `{{action}}` helper. It provides intelligent defaults
- for the action's target: the view's controller; and the context that is
- sent with the action: the view's context.
- Note: In normal Ember usage, the `{{action}}` helper is usually the best
- choice. This mixin is most often useful when you are doing more complex
- event handling in custom View subclasses.
- For example:
- ```javascript
- App.SaveButtonView = Ember.View.extend(Ember.ViewTargetActionSupport, {
- action: 'save',
- click: function() {
- this.triggerAction(); // Sends the `save` action, along with the current context
- // to the current controller
- }
- });
- ```
- The `action` can be provided as properties of an optional object argument
- to `triggerAction` as well.
- ```javascript
- App.SaveButtonView = Ember.View.extend(Ember.ViewTargetActionSupport, {
- click: function() {
- this.triggerAction({
- action: 'save'
- }); // Sends the `save` action, along with the current context
- // to the current controller
- }
- });
- ```
- @class ViewTargetActionSupport
- @namespace Ember
- @extends Ember.TargetActionSupport
- */
- Ember.ViewTargetActionSupport = Ember.Mixin.create(Ember.TargetActionSupport, {
- /**
- @property target
- */
- target: Ember.computed.alias('controller'),
- /**
- @property actionContext
- */
- actionContext: Ember.computed.alias('context')
- });
- })();
- (function() {
- })();
- (function() {
- /**
- Ember Views
- @module ember
- @submodule ember-views
- @requires ember-runtime
- @main ember-views
- */
- })();
- (function() {
- define("metamorph",
- [],
- function() {
- "use strict";
- // ==========================================================================
- // Project: metamorph
- // Copyright: ©2014 Tilde, Inc. All rights reserved.
- // ==========================================================================
- var K = function() {},
- guid = 0,
- disableRange = (function(){
- if ('undefined' !== typeof MetamorphENV) {
- return MetamorphENV.DISABLE_RANGE_API;
- } else if ('undefined' !== ENV) {
- return ENV.DISABLE_RANGE_API;
- } else {
- return false;
- }
- })(),
- // Feature-detect the W3C range API, the extended check is for IE9 which only partially supports ranges
- supportsRange = (!disableRange) && typeof document !== 'undefined' && ('createRange' in document) && (typeof Range !== 'undefined') && Range.prototype.createContextualFragment,
- // Internet Explorer prior to 9 does not allow setting innerHTML if the first element
- // is a "zero-scope" element. This problem can be worked around by making
- // the first node an invisible text node. We, like Modernizr, use ­
- needsShy = typeof document !== 'undefined' && (function() {
- var testEl = document.createElement('div');
- testEl.innerHTML = "<div></div>";
- testEl.firstChild.innerHTML = "<script></script>";
- return testEl.firstChild.innerHTML === '';
- })(),
- // IE 8 (and likely earlier) likes to move whitespace preceeding
- // a script tag to appear after it. This means that we can
- // accidentally remove whitespace when updating a morph.
- movesWhitespace = document && (function() {
- var testEl = document.createElement('div');
- testEl.innerHTML = "Test: <script type='text/x-placeholder'></script>Value";
- return testEl.childNodes[0].nodeValue === 'Test:' &&
- testEl.childNodes[2].nodeValue === ' Value';
- })();
- // Constructor that supports either Metamorph('foo') or new
- // Metamorph('foo');
- //
- // Takes a string of HTML as the argument.
- var Metamorph = function(html) {
- var self;
- if (this instanceof Metamorph) {
- self = this;
- } else {
- self = new K();
- }
- self.innerHTML = html;
- var myGuid = 'metamorph-'+(guid++);
- self.start = myGuid + '-start';
- self.end = myGuid + '-end';
- return self;
- };
- K.prototype = Metamorph.prototype;
- var rangeFor, htmlFunc, removeFunc, outerHTMLFunc, appendToFunc, afterFunc, prependFunc, startTagFunc, endTagFunc;
- outerHTMLFunc = function() {
- return this.startTag() + this.innerHTML + this.endTag();
- };
- startTagFunc = function() {
- /*
- * We replace chevron by its hex code in order to prevent escaping problems.
- * Check this thread for more explaination:
- * http://stackoverflow.com/questions/8231048/why-use-x3c-instead-of-when-generating-html-from-javascript
- */
- return "<script id='" + this.start + "' type='text/x-placeholder'>\x3C/script>";
- };
- endTagFunc = function() {
- /*
- * We replace chevron by its hex code in order to prevent escaping problems.
- * Check this thread for more explaination:
- * http://stackoverflow.com/questions/8231048/why-use-x3c-instead-of-when-generating-html-from-javascript
- */
- return "<script id='" + this.end + "' type='text/x-placeholder'>\x3C/script>";
- };
- // If we have the W3C range API, this process is relatively straight forward.
- if (supportsRange) {
- // Get a range for the current morph. Optionally include the starting and
- // ending placeholders.
- rangeFor = function(morph, outerToo) {
- var range = document.createRange();
- var before = document.getElementById(morph.start);
- var after = document.getElementById(morph.end);
- if (outerToo) {
- range.setStartBefore(before);
- range.setEndAfter(after);
- } else {
- range.setStartAfter(before);
- range.setEndBefore(after);
- }
- return range;
- };
- htmlFunc = function(html, outerToo) {
- // get a range for the current metamorph object
- var range = rangeFor(this, outerToo);
- // delete the contents of the range, which will be the
- // nodes between the starting and ending placeholder.
- range.deleteContents();
- // create a new document fragment for the HTML
- var fragment = range.createContextualFragment(html);
- // insert the fragment into the range
- range.insertNode(fragment);
- };
- /**
- * @public
- *
- * Remove this object (including starting and ending
- * placeholders).
- *
- * @method remove
- */
- removeFunc = function() {
- // get a range for the current metamorph object including
- // the starting and ending placeholders.
- var range = rangeFor(this, true);
- // delete the entire range.
- range.deleteContents();
- };
- appendToFunc = function(node) {
- var range = document.createRange();
- range.setStart(node);
- range.collapse(false);
- var frag = range.createContextualFragment(this.outerHTML());
- node.appendChild(frag);
- };
- afterFunc = function(html) {
- var range = document.createRange();
- var after = document.getElementById(this.end);
- range.setStartAfter(after);
- range.setEndAfter(after);
- var fragment = range.createContextualFragment(html);
- range.insertNode(fragment);
- };
- prependFunc = function(html) {
- var range = document.createRange();
- var start = document.getElementById(this.start);
- range.setStartAfter(start);
- range.setEndAfter(start);
- var fragment = range.createContextualFragment(html);
- range.insertNode(fragment);
- };
- } else {
- /*
- * This code is mostly taken from jQuery, with one exception. In jQuery's case, we
- * have some HTML and we need to figure out how to convert it into some nodes.
- *
- * In this case, jQuery needs to scan the HTML looking for an opening tag and use
- * that as the key for the wrap map. In our case, we know the parent node, and
- * can use its type as the key for the wrap map.
- **/
- var wrapMap = {
- select: [ 1, "<select multiple='multiple'>", "</select>" ],
- fieldset: [ 1, "<fieldset>", "</fieldset>" ],
- table: [ 1, "<table>", "</table>" ],
- tbody: [ 2, "<table><tbody>", "</tbody></table>" ],
- tr: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],
- colgroup: [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ],
- map: [ 1, "<map>", "</map>" ],
- _default: [ 0, "", "" ]
- };
- var findChildById = function(element, id) {
- if (element.getAttribute('id') === id) { return element; }
- var len = element.childNodes.length, idx, node, found;
- for (idx=0; idx<len; idx++) {
- node = element.childNodes[idx];
- found = node.nodeType === 1 && findChildById(node, id);
- if (found) { return found; }
- }
- };
- var setInnerHTML = function(element, html) {
- var matches = [];
- if (movesWhitespace) {
- // Right now we only check for script tags with ids with the
- // goal of targeting morphs.
- html = html.replace(/(\s+)(<script id='([^']+)')/g, function(match, spaces, tag, id) {
- matches.push([id, spaces]);
- return tag;
- });
- }
- element.innerHTML = html;
- // If we have to do any whitespace adjustments do them now
- if (matches.length > 0) {
- var len = matches.length, idx;
- for (idx=0; idx<len; idx++) {
- var script = findChildById(element, matches[idx][0]),
- node = document.createTextNode(matches[idx][1]);
- script.parentNode.insertBefore(node, script);
- }
- }
- };
- /*
- * Given a parent node and some HTML, generate a set of nodes. Return the first
- * node, which will allow us to traverse the rest using nextSibling.
- *
- * We need to do this because innerHTML in IE does not really parse the nodes.
- */
- var firstNodeFor = function(parentNode, html) {
- var arr = wrapMap[parentNode.tagName.toLowerCase()] || wrapMap._default;
- var depth = arr[0], start = arr[1], end = arr[2];
- if (needsShy) { html = '­'+html; }
- var element = document.createElement('div');
- setInnerHTML(element, start + html + end);
- for (var i=0; i<=depth; i++) {
- element = element.firstChild;
- }
- // Look for ­ to remove it.
- if (needsShy) {
- var shyElement = element;
- // Sometimes we get nameless elements with the shy inside
- while (shyElement.nodeType === 1 && !shyElement.nodeName) {
- shyElement = shyElement.firstChild;
- }
- // At this point it's the actual unicode character.
- if (shyElement.nodeType === 3 && shyElement.nodeValue.charAt(0) === "\u00AD") {
- shyElement.nodeValue = shyElement.nodeValue.slice(1);
- }
- }
- return element;
- };
- /*
- * In some cases, Internet Explorer can create an anonymous node in
- * the hierarchy with no tagName. You can create this scenario via:
- *
- * div = document.createElement("div");
- * div.innerHTML = "<table>­<script></script><tr><td>hi</td></tr></table>";
- * div.firstChild.firstChild.tagName //=> ""
- *
- * If our script markers are inside such a node, we need to find that
- * node and use *it* as the marker.
- */
- var realNode = function(start) {
- while (start.parentNode.tagName === "") {
- start = start.parentNode;
- }
- return start;
- };
- /*
- * When automatically adding a tbody, Internet Explorer inserts the
- * tbody immediately before the first <tr>. Other browsers create it
- * before the first node, no matter what.
- *
- * This means the the following code:
- *
- * div = document.createElement("div");
- * div.innerHTML = "<table><script id='first'></script><tr><td>hi</td></tr><script id='last'></script></table>
- *
- * Generates the following DOM in IE:
- *
- * + div
- * + table
- * - script id='first'
- * + tbody
- * + tr
- * + td
- * - "hi"
- * - script id='last'
- *
- * Which means that the two script tags, even though they were
- * inserted at the same point in the hierarchy in the original
- * HTML, now have different parents.
- *
- * This code reparents the first script tag by making it the tbody's
- * first child.
- *
- */
- var fixParentage = function(start, end) {
- if (start.parentNode !== end.parentNode) {
- end.parentNode.insertBefore(start, end.parentNode.firstChild);
- }
- };
- htmlFunc = function(html, outerToo) {
- // get the real starting node. see realNode for details.
- var start = realNode(document.getElementById(this.start));
- var end = document.getElementById(this.end);
- var parentNode = end.parentNode;
- var node, nextSibling, last;
- // make sure that the start and end nodes share the same
- // parent. If not, fix it.
- fixParentage(start, end);
- // remove all of the nodes after the starting placeholder and
- // before the ending placeholder.
- node = start.nextSibling;
- while (node) {
- nextSibling = node.nextSibling;
- last = node === end;
- // if this is the last node, and we want to remove it as well,
- // set the `end` node to the next sibling. This is because
- // for the rest of the function, we insert the new nodes
- // before the end (note that insertBefore(node, null) is
- // the same as appendChild(node)).
- //
- // if we do not want to remove it, just break.
- if (last) {
- if (outerToo) { end = node.nextSibling; } else { break; }
- }
- node.parentNode.removeChild(node);
- // if this is the last node and we didn't break before
- // (because we wanted to remove the outer nodes), break
- // now.
- if (last) { break; }
- node = nextSibling;
- }
- // get the first node for the HTML string, even in cases like
- // tables and lists where a simple innerHTML on a div would
- // swallow some of the content.
- node = firstNodeFor(start.parentNode, html);
- if (outerToo) {
- start.parentNode.removeChild(start);
- }
- // copy the nodes for the HTML between the starting and ending
- // placeholder.
- while (node) {
- nextSibling = node.nextSibling;
- parentNode.insertBefore(node, end);
- node = nextSibling;
- }
- };
- // remove the nodes in the DOM representing this metamorph.
- //
- // this includes the starting and ending placeholders.
- removeFunc = function() {
- var start = realNode(document.getElementById(this.start));
- var end = document.getElementById(this.end);
- this.html('');
- start.parentNode.removeChild(start);
- end.parentNode.removeChild(end);
- };
- appendToFunc = function(parentNode) {
- var node = firstNodeFor(parentNode, this.outerHTML());
- var nextSibling;
- while (node) {
- nextSibling = node.nextSibling;
- parentNode.appendChild(node);
- node = nextSibling;
- }
- };
- afterFunc = function(html) {
- // get the real starting node. see realNode for details.
- var end = document.getElementById(this.end);
- var insertBefore = end.nextSibling;
- var parentNode = end.parentNode;
- var nextSibling;
- var node;
- // get the first node for the HTML string, even in cases like
- // tables and lists where a simple innerHTML on a div would
- // swallow some of the content.
- node = firstNodeFor(parentNode, html);
- // copy the nodes for the HTML between the starting and ending
- // placeholder.
- while (node) {
- nextSibling = node.nextSibling;
- parentNode.insertBefore(node, insertBefore);
- node = nextSibling;
- }
- };
- prependFunc = function(html) {
- var start = document.getElementById(this.start);
- var parentNode = start.parentNode;
- var nextSibling;
- var node;
- node = firstNodeFor(parentNode, html);
- var insertBefore = start.nextSibling;
- while (node) {
- nextSibling = node.nextSibling;
- parentNode.insertBefore(node, insertBefore);
- node = nextSibling;
- }
- };
- }
- Metamorph.prototype.html = function(html) {
- this.checkRemoved();
- if (html === undefined) { return this.innerHTML; }
- htmlFunc.call(this, html);
- this.innerHTML = html;
- };
- Metamorph.prototype.replaceWith = function(html) {
- this.checkRemoved();
- htmlFunc.call(this, html, true);
- };
- Metamorph.prototype.remove = removeFunc;
- Metamorph.prototype.outerHTML = outerHTMLFunc;
- Metamorph.prototype.appendTo = appendToFunc;
- Metamorph.prototype.after = afterFunc;
- Metamorph.prototype.prepend = prependFunc;
- Metamorph.prototype.startTag = startTagFunc;
- Metamorph.prototype.endTag = endTagFunc;
- Metamorph.prototype.isRemoved = function() {
- var before = document.getElementById(this.start);
- var after = document.getElementById(this.end);
- return !before || !after;
- };
- Metamorph.prototype.checkRemoved = function() {
- if (this.isRemoved()) {
- throw new Error("Cannot perform operations on a Metamorph that is not in the DOM.");
- }
- };
- return Metamorph;
- });
- })();
- (function() {
- /**
- @module ember
- @submodule ember-handlebars-compiler
- */
- // Eliminate dependency on any Ember to simplify precompilation workflow
- var objectCreate = Object.create || function(parent) {
- function F() {}
- F.prototype = parent;
- return new F();
- };
- var Handlebars = (Ember.imports && Ember.imports.Handlebars) || (this && this.Handlebars);
- if (!Handlebars && typeof require === 'function') {
- Handlebars = require('handlebars');
- }
- Ember.assert("Ember Handlebars requires Handlebars version 1.0 or 1.1. Include " +
- "a SCRIPT tag in the HTML HEAD linking to the Handlebars file " +
- "before you link to Ember.", Handlebars);
- Ember.assert("Ember Handlebars requires Handlebars version 1.0 or 1.1, " +
- "COMPILER_REVISION expected: 4, got: " + Handlebars.COMPILER_REVISION +
- " - Please note: Builds of master may have other COMPILER_REVISION values.",
- Handlebars.COMPILER_REVISION === 4);
- /**
- Prepares the Handlebars templating library for use inside Ember's view
- system.
- The `Ember.Handlebars` object is the standard Handlebars library, extended to
- use Ember's `get()` method instead of direct property access, which allows
- computed properties to be used inside templates.
- To create an `Ember.Handlebars` template, call `Ember.Handlebars.compile()`.
- This will return a function that can be used by `Ember.View` for rendering.
- @class Handlebars
- @namespace Ember
- */
- Ember.Handlebars = objectCreate(Handlebars);
- /**
- Register a bound helper or custom view helper.
- ## Simple bound helper example
- ```javascript
- Ember.Handlebars.helper('capitalize', function(value) {
- return value.toUpperCase();
- });
- ```
- The above bound helper can be used inside of templates as follows:
- ```handlebars
- {{capitalize name}}
- ```
- In this case, when the `name` property of the template's context changes,
- the rendered value of the helper will update to reflect this change.
- For more examples of bound helpers, see documentation for
- `Ember.Handlebars.registerBoundHelper`.
- ## Custom view helper example
- Assuming a view subclass named `App.CalendarView` were defined, a helper
- for rendering instances of this view could be registered as follows:
- ```javascript
- Ember.Handlebars.helper('calendar', App.CalendarView):
- ```
- The above bound helper can be used inside of templates as follows:
- ```handlebars
- {{calendar}}
- ```
- Which is functionally equivalent to:
- ```handlebars
- {{view App.CalendarView}}
- ```
- Options in the helper will be passed to the view in exactly the same
- manner as with the `view` helper.
- @method helper
- @for Ember.Handlebars
- @param {String} name
- @param {Function|Ember.View} function or view class constructor
- @param {String} dependentKeys*
- */
- Ember.Handlebars.helper = function(name, value) {
- Ember.assert("You tried to register a component named '" + name + "', but component names must include a '-'", !Ember.Component.detect(value) || name.match(/-/));
- if (Ember.View.detect(value)) {
- Ember.Handlebars.registerHelper(name, Ember.Handlebars.makeViewHelper(value));
- } else {
- Ember.Handlebars.registerBoundHelper.apply(null, arguments);
- }
- };
- /**
- Returns a helper function that renders the provided ViewClass.
- Used internally by Ember.Handlebars.helper and other methods
- involving helper/component registration.
- @private
- @method helper
- @for Ember.Handlebars
- @param {Function} ViewClass view class constructor
- */
- Ember.Handlebars.makeViewHelper = function(ViewClass) {
- return function(options) {
- Ember.assert("You can only pass attributes (such as name=value) not bare values to a helper for a View found in '" + ViewClass.toString() + "'", arguments.length < 2);
- return Ember.Handlebars.helpers.view.call(this, ViewClass, options);
- };
- };
- /**
- @class helpers
- @namespace Ember.Handlebars
- */
- Ember.Handlebars.helpers = objectCreate(Handlebars.helpers);
- /**
- Override the the opcode compiler and JavaScript compiler for Handlebars.
- @class Compiler
- @namespace Ember.Handlebars
- @private
- @constructor
- */
- Ember.Handlebars.Compiler = function() {};
- // Handlebars.Compiler doesn't exist in runtime-only
- if (Handlebars.Compiler) {
- Ember.Handlebars.Compiler.prototype = objectCreate(Handlebars.Compiler.prototype);
- }
- Ember.Handlebars.Compiler.prototype.compiler = Ember.Handlebars.Compiler;
- /**
- @class JavaScriptCompiler
- @namespace Ember.Handlebars
- @private
- @constructor
- */
- Ember.Handlebars.JavaScriptCompiler = function() {};
- // Handlebars.JavaScriptCompiler doesn't exist in runtime-only
- if (Handlebars.JavaScriptCompiler) {
- Ember.Handlebars.JavaScriptCompiler.prototype = objectCreate(Handlebars.JavaScriptCompiler.prototype);
- Ember.Handlebars.JavaScriptCompiler.prototype.compiler = Ember.Handlebars.JavaScriptCompiler;
- }
- Ember.Handlebars.JavaScriptCompiler.prototype.namespace = "Ember.Handlebars";
- Ember.Handlebars.JavaScriptCompiler.prototype.initializeBuffer = function() {
- return "''";
- };
- /**
- Override the default buffer for Ember Handlebars. By default, Handlebars
- creates an empty String at the beginning of each invocation and appends to
- it. Ember's Handlebars overrides this to append to a single shared buffer.
- @private
- @method appendToBuffer
- @param string {String}
- */
- Ember.Handlebars.JavaScriptCompiler.prototype.appendToBuffer = function(string) {
- return "data.buffer.push("+string+");";
- };
- // Hacks ahead:
- // Handlebars presently has a bug where the `blockHelperMissing` hook
- // doesn't get passed the name of the missing helper name, but rather
- // gets passed the value of that missing helper evaluated on the current
- // context, which is most likely `undefined` and totally useless.
- //
- // So we alter the compiled template function to pass the name of the helper
- // instead, as expected.
- //
- // This can go away once the following is closed:
- // https://github.com/wycats/handlebars.js/issues/634
- var DOT_LOOKUP_REGEX = /helpers\.(.*?)\)/,
- BRACKET_STRING_LOOKUP_REGEX = /helpers\['(.*?)'/,
- INVOCATION_SPLITTING_REGEX = /(.*blockHelperMissing\.call\(.*)(stack[0-9]+)(,.*)/;
- Ember.Handlebars.JavaScriptCompiler.stringifyLastBlockHelperMissingInvocation = function(source) {
- var helperInvocation = source[source.length - 1],
- helperName = (DOT_LOOKUP_REGEX.exec(helperInvocation) || BRACKET_STRING_LOOKUP_REGEX.exec(helperInvocation))[1],
- matches = INVOCATION_SPLITTING_REGEX.exec(helperInvocation);
- source[source.length - 1] = matches[1] + "'" + helperName + "'" + matches[3];
- }
- var stringifyBlockHelperMissing = Ember.Handlebars.JavaScriptCompiler.stringifyLastBlockHelperMissingInvocation;
- var originalBlockValue = Ember.Handlebars.JavaScriptCompiler.prototype.blockValue;
- Ember.Handlebars.JavaScriptCompiler.prototype.blockValue = function() {
- originalBlockValue.apply(this, arguments);
- stringifyBlockHelperMissing(this.source);
- };
- var originalAmbiguousBlockValue = Ember.Handlebars.JavaScriptCompiler.prototype.ambiguousBlockValue;
- Ember.Handlebars.JavaScriptCompiler.prototype.ambiguousBlockValue = function() {
- originalAmbiguousBlockValue.apply(this, arguments);
- stringifyBlockHelperMissing(this.source);
- };
- var prefix = "ember" + (+new Date()), incr = 1;
- /**
- Rewrite simple mustaches from `{{foo}}` to `{{bind "foo"}}`. This means that
- all simple mustaches in Ember's Handlebars will also set up an observer to
- keep the DOM up to date when the underlying property changes.
- @private
- @method mustache
- @for Ember.Handlebars.Compiler
- @param mustache
- */
- Ember.Handlebars.Compiler.prototype.mustache = function(mustache) {
- if (mustache.isHelper && mustache.id.string === 'control') {
- mustache.hash = mustache.hash || new Handlebars.AST.HashNode([]);
- mustache.hash.pairs.push(["controlID", new Handlebars.AST.StringNode(prefix + incr++)]);
- } else if (mustache.params.length || mustache.hash) {
- // no changes required
- } else {
- var id = new Handlebars.AST.IdNode([{ part: '_triageMustache' }]);
- // Update the mustache node to include a hash value indicating whether the original node
- // was escaped. This will allow us to properly escape values when the underlying value
- // changes and we need to re-render the value.
- if (!mustache.escaped) {
- mustache.hash = mustache.hash || new Handlebars.AST.HashNode([]);
- mustache.hash.pairs.push(["unescaped", new Handlebars.AST.StringNode("true")]);
- }
- mustache = new Handlebars.AST.MustacheNode([id].concat([mustache.id]), mustache.hash, !mustache.escaped);
- }
- return Handlebars.Compiler.prototype.mustache.call(this, mustache);
- };
- /**
- Used for precompilation of Ember Handlebars templates. This will not be used
- during normal app execution.
- @method precompile
- @for Ember.Handlebars
- @static
- @param {String} string The template to precompile
- */
- Ember.Handlebars.precompile = function(string) {
- var ast = Handlebars.parse(string);
- var options = {
- knownHelpers: {
- action: true,
- unbound: true,
- 'bind-attr': true,
- template: true,
- view: true,
- _triageMustache: true
- },
- data: true,
- stringParams: true
- };
- var environment = new Ember.Handlebars.Compiler().compile(ast, options);
- return new Ember.Handlebars.JavaScriptCompiler().compile(environment, options, undefined, true);
- };
- // We don't support this for Handlebars runtime-only
- if (Handlebars.compile) {
- /**
- The entry point for Ember Handlebars. This replaces the default
- `Handlebars.compile` and turns on template-local data and String
- parameters.
- @method compile
- @for Ember.Handlebars
- @static
- @param {String} string The template to compile
- @return {Function}
- */
- Ember.Handlebars.compile = function(string) {
- var ast = Handlebars.parse(string);
- var options = { data: true, stringParams: true };
- var environment = new Ember.Handlebars.Compiler().compile(ast, options);
- var templateSpec = new Ember.Handlebars.JavaScriptCompiler().compile(environment, options, undefined, true);
- var template = Ember.Handlebars.template(templateSpec);
- template.isMethod = false; //Make sure we don't wrap templates with ._super
- return template;
- };
- }
- })();
- (function() {
- var slice = Array.prototype.slice,
- originalTemplate = Ember.Handlebars.template;
- /**
- If a path starts with a reserved keyword, returns the root
- that should be used.
- @private
- @method normalizePath
- @for Ember
- @param root {Object}
- @param path {String}
- @param data {Hash}
- */
- var normalizePath = Ember.Handlebars.normalizePath = function(root, path, data) {
- var keywords = (data && data.keywords) || {},
- keyword, isKeyword;
- // Get the first segment of the path. For example, if the
- // path is "foo.bar.baz", returns "foo".
- keyword = path.split('.', 1)[0];
- // Test to see if the first path is a keyword that has been
- // passed along in the view's data hash. If so, we will treat
- // that object as the new root.
- if (keywords.hasOwnProperty(keyword)) {
- // Look up the value in the template's data hash.
- root = keywords[keyword];
- isKeyword = true;
- // Handle cases where the entire path is the reserved
- // word. In that case, return the object itself.
- if (path === keyword) {
- path = '';
- } else {
- // Strip the keyword from the path and look up
- // the remainder from the newly found root.
- path = path.substr(keyword.length+1);
- }
- }
- return { root: root, path: path, isKeyword: isKeyword };
- };
- /**
- Lookup both on root and on window. If the path starts with
- a keyword, the corresponding object will be looked up in the
- template's data hash and used to resolve the path.
- @method get
- @for Ember.Handlebars
- @param {Object} root The object to look up the property on
- @param {String} path The path to be lookedup
- @param {Object} options The template's option hash
- */
- var handlebarsGet = Ember.Handlebars.get = function(root, path, options) {
- var data = options && options.data,
- normalizedPath = normalizePath(root, path, data),
- value;
-
- root = normalizedPath.root;
- path = normalizedPath.path;
- value = Ember.get(root, path);
- if (value === undefined && root !== Ember.lookup && Ember.isGlobalPath(path)) {
- value = Ember.get(Ember.lookup, path);
- }
-
- return value;
- };
- /**
- This method uses `Ember.Handlebars.get` to lookup a value, then ensures
- that the value is escaped properly.
- If `unescaped` is a truthy value then the escaping will not be performed.
- @method getEscaped
- @for Ember.Handlebars
- @param {Object} root The object to look up the property on
- @param {String} path The path to be lookedup
- @param {Object} options The template's option hash
- */
- Ember.Handlebars.getEscaped = function(root, path, options) {
- var result = handlebarsGet(root, path, options);
- if (result === null || result === undefined) {
- result = "";
- } else if (!(result instanceof Handlebars.SafeString)) {
- result = String(result);
- }
- if (!options.hash.unescaped){
- result = Handlebars.Utils.escapeExpression(result);
- }
- return result;
- };
- Ember.Handlebars.resolveParams = function(context, params, options) {
- var resolvedParams = [], types = options.types, param, type;
- for (var i=0, l=params.length; i<l; i++) {
- param = params[i];
- type = types[i];
- if (type === 'ID') {
- resolvedParams.push(handlebarsGet(context, param, options));
- } else {
- resolvedParams.push(param);
- }
- }
- return resolvedParams;
- };
- Ember.Handlebars.resolveHash = function(context, hash, options) {
- var resolvedHash = {}, types = options.hashTypes, type;
- for (var key in hash) {
- if (!hash.hasOwnProperty(key)) { continue; }
- type = types[key];
- if (type === 'ID') {
- resolvedHash[key] = handlebarsGet(context, hash[key], options);
- } else {
- resolvedHash[key] = hash[key];
- }
- }
- return resolvedHash;
- };
- /**
- Registers a helper in Handlebars that will be called if no property with the
- given name can be found on the current context object, and no helper with
- that name is registered.
- This throws an exception with a more helpful error message so the user can
- track down where the problem is happening.
- @private
- @method helperMissing
- @for Ember.Handlebars.helpers
- @param {String} path
- @param {Hash} options
- */
- Ember.Handlebars.registerHelper('helperMissing', function(path) {
- var error, view = "";
- var options = arguments[arguments.length - 1];
- var helper = Ember.Handlebars.resolveHelper(options.data.view.container, path);
- if (helper) {
- return helper.apply(this, slice.call(arguments, 1));
- }
- error = "%@ Handlebars error: Could not find property '%@' on object %@.";
- if (options.data) {
- view = options.data.view;
- }
- throw new Ember.Error(Ember.String.fmt(error, [view, path, this]));
- });
- /**
- Registers a helper in Handlebars that will be called if no property with the
- given name can be found on the current context object, and no helper with
- that name is registered.
- This throws an exception with a more helpful error message so the user can
- track down where the problem is happening.
- @private
- @method helperMissing
- @for Ember.Handlebars.helpers
- @param {String} path
- @param {Hash} options
- */
- Ember.Handlebars.registerHelper('blockHelperMissing', function(path) {
- var options = arguments[arguments.length - 1];
- Ember.assert("`blockHelperMissing` was invoked without a helper name, which " +
- "is most likely due to a mismatch between the version of " +
- "Ember.js you're running now and the one used to precompile your " +
- "templates. Please make sure the version of " +
- "`ember-handlebars-compiler` you're using is up to date.", path);
- var helper = Ember.Handlebars.resolveHelper(options.data.view.container, path);
- if (helper) {
- return helper.apply(this, slice.call(arguments, 1));
- } else {
- return Handlebars.helpers.helperMissing.call(this, path);
- }
- return Handlebars.helpers.blockHelperMissing.apply(this, arguments);
- });
- /**
- Register a bound handlebars helper. Bound helpers behave similarly to regular
- handlebars helpers, with the added ability to re-render when the underlying data
- changes.
- ## Simple example
- ```javascript
- Ember.Handlebars.registerBoundHelper('capitalize', function(value) {
- return value.toUpperCase();
- });
- ```
- The above bound helper can be used inside of templates as follows:
- ```handlebars
- {{capitalize name}}
- ```
- In this case, when the `name` property of the template's context changes,
- the rendered value of the helper will update to reflect this change.
- ## Example with options
- Like normal handlebars helpers, bound helpers have access to the options
- passed into the helper call.
- ```javascript
- Ember.Handlebars.registerBoundHelper('repeat', function(value, options) {
- var count = options.hash.count;
- var a = [];
- while(a.length < count) {
- a.push(value);
- }
- return a.join('');
- });
- ```
- This helper could be used in a template as follows:
- ```handlebars
- {{repeat text count=3}}
- ```
- ## Example with bound options
- Bound hash options are also supported. Example:
- ```handlebars
- {{repeat text countBinding="numRepeats"}}
- ```
- In this example, count will be bound to the value of
- the `numRepeats` property on the context. If that property
- changes, the helper will be re-rendered.
- ## Example with extra dependencies
- The `Ember.Handlebars.registerBoundHelper` method takes a variable length
- third parameter which indicates extra dependencies on the passed in value.
- This allows the handlebars helper to update when these dependencies change.
- ```javascript
- Ember.Handlebars.registerBoundHelper('capitalizeName', function(value) {
- return value.get('name').toUpperCase();
- }, 'name');
- ```
- ## Example with multiple bound properties
- `Ember.Handlebars.registerBoundHelper` supports binding to
- multiple properties, e.g.:
- ```javascript
- Ember.Handlebars.registerBoundHelper('concatenate', function() {
- var values = Array.prototype.slice.call(arguments, 0, -1);
- return values.join('||');
- });
- ```
- Which allows for template syntax such as `{{concatenate prop1 prop2}}` or
- `{{concatenate prop1 prop2 prop3}}`. If any of the properties change,
- the helpr will re-render. Note that dependency keys cannot be
- using in conjunction with multi-property helpers, since it is ambiguous
- which property the dependent keys would belong to.
- ## Use with unbound helper
- The `{{unbound}}` helper can be used with bound helper invocations
- to render them in their unbound form, e.g.
- ```handlebars
- {{unbound capitalize name}}
- ```
- In this example, if the name property changes, the helper
- will not re-render.
- ## Use with blocks not supported
- Bound helpers do not support use with Handlebars blocks or
- the addition of child views of any kind.
- @method registerBoundHelper
- @for Ember.Handlebars
- @param {String} name
- @param {Function} function
- @param {String} dependentKeys*
- */
- Ember.Handlebars.registerBoundHelper = function(name, fn) {
- var boundHelperArgs = slice.call(arguments, 1),
- boundFn = Ember.Handlebars.makeBoundHelper.apply(this, boundHelperArgs);
- Ember.Handlebars.registerHelper(name, boundFn);
- };
- /**
- A (mostly) private helper function to `registerBoundHelper`. Takes the
- provided Handlebars helper function fn and returns it in wrapped
- bound helper form.
- The main use case for using this outside of `registerBoundHelper`
- is for registering helpers on the container:
- ```js
- var boundHelperFn = Ember.Handlebars.makeBoundHelper(function(word) {
- return word.toUpperCase();
- });
- container.register('helper:my-bound-helper', boundHelperFn);
- ```
- In the above example, if the helper function hadn't been wrapped in
- `makeBoundHelper`, the registered helper would be unbound.
- @private
- @method makeBoundHelper
- @for Ember.Handlebars
- @param {Function} function
- @param {String} dependentKeys*
- */
- Ember.Handlebars.makeBoundHelper = function(fn) {
- var dependentKeys = slice.call(arguments, 1);
- function helper() {
- var properties = slice.call(arguments, 0, -1),
- numProperties = properties.length,
- options = arguments[arguments.length - 1],
- normalizedProperties = [],
- data = options.data,
- types = data.isUnbound ? slice.call(options.types, 1) : options.types,
- hash = options.hash,
- view = data.view,
- contexts = options.contexts,
- currentContext = (contexts && contexts.length) ? contexts[0] : this,
- prefixPathForDependentKeys = '',
- loc, len, hashOption,
- boundOption, property,
- normalizedValue = Ember._SimpleHandlebarsView.prototype.normalizedValue;
- Ember.assert("registerBoundHelper-generated helpers do not support use with Handlebars blocks.", !options.fn);
- // Detect bound options (e.g. countBinding="otherCount")
- var boundOptions = hash.boundOptions = {};
- for (hashOption in hash) {
- if (Ember.IS_BINDING.test(hashOption)) {
- // Lop off 'Binding' suffix.
- boundOptions[hashOption.slice(0, -7)] = hash[hashOption];
- }
- }
- // Expose property names on data.properties object.
- var watchedProperties = [];
- data.properties = [];
- for (loc = 0; loc < numProperties; ++loc) {
- data.properties.push(properties[loc]);
- if (types[loc] === 'ID') {
- var normalizedProp = normalizePath(currentContext, properties[loc], data);
- normalizedProperties.push(normalizedProp);
- watchedProperties.push(normalizedProp);
- } else {
- if(data.isUnbound) {
- normalizedProperties.push({path: properties[loc]});
- }else {
- normalizedProperties.push(null);
- }
- }
- }
- // Handle case when helper invocation is preceded by `unbound`, e.g.
- // {{unbound myHelper foo}}
- if (data.isUnbound) {
- return evaluateUnboundHelper(this, fn, normalizedProperties, options);
- }
- var bindView = new Ember._SimpleHandlebarsView(null, null, !options.hash.unescaped, options.data);
- // Override SimpleHandlebarsView's method for generating the view's content.
- bindView.normalizedValue = function() {
- var args = [], boundOption;
- // Copy over bound hash options.
- for (boundOption in boundOptions) {
- if (!boundOptions.hasOwnProperty(boundOption)) { continue; }
- property = normalizePath(currentContext, boundOptions[boundOption], data);
- bindView.path = property.path;
- bindView.pathRoot = property.root;
- hash[boundOption] = normalizedValue.call(bindView);
- }
- for (loc = 0; loc < numProperties; ++loc) {
- property = normalizedProperties[loc];
- if (property) {
- bindView.path = property.path;
- bindView.pathRoot = property.root;
- args.push(normalizedValue.call(bindView));
- } else {
- args.push(properties[loc]);
- }
- }
- args.push(options);
- // Run the supplied helper function.
- return fn.apply(currentContext, args);
- };
- view.appendChild(bindView);
- // Assemble list of watched properties that'll re-render this helper.
- for (boundOption in boundOptions) {
- if (boundOptions.hasOwnProperty(boundOption)) {
- watchedProperties.push(normalizePath(currentContext, boundOptions[boundOption], data));
- }
- }
- // Observe each property.
- for (loc = 0, len = watchedProperties.length; loc < len; ++loc) {
- property = watchedProperties[loc];
- view.registerObserver(property.root, property.path, bindView, bindView.rerender);
- }
- if (types[0] !== 'ID' || normalizedProperties.length === 0) {
- return;
- }
- // Add dependent key observers to the first param
- var normalized = normalizedProperties[0],
- pathRoot = normalized.root,
- path = normalized.path;
- if(!Ember.isEmpty(path)) {
- prefixPathForDependentKeys = path + '.';
- }
- for (var i=0, l=dependentKeys.length; i<l; i++) {
- view.registerObserver(pathRoot, prefixPathForDependentKeys + dependentKeys[i], bindView, bindView.rerender);
- }
- }
- helper._rawFunction = fn;
- return helper;
- };
- /**
- Renders the unbound form of an otherwise bound helper function.
- @private
- @method evaluateUnboundHelper
- @param {Function} fn
- @param {Object} context
- @param {Array} normalizedProperties
- @param {String} options
- */
- function evaluateUnboundHelper(context, fn, normalizedProperties, options) {
- var args = [],
- hash = options.hash,
- boundOptions = hash.boundOptions,
- types = slice.call(options.types, 1),
- loc,
- len,
- property,
- propertyType,
- boundOption;
- for (boundOption in boundOptions) {
- if (!boundOptions.hasOwnProperty(boundOption)) { continue; }
- hash[boundOption] = Ember.Handlebars.get(context, boundOptions[boundOption], options);
- }
- for(loc = 0, len = normalizedProperties.length; loc < len; ++loc) {
- property = normalizedProperties[loc];
- propertyType = types[loc];
- if(propertyType === "ID") {
- args.push(Ember.Handlebars.get(property.root, property.path, options));
- } else {
- args.push(property.path);
- }
- }
- args.push(options);
- return fn.apply(context, args);
- }
- /**
- Overrides Handlebars.template so that we can distinguish
- user-created, top-level templates from inner contexts.
- @private
- @method template
- @for Ember.Handlebars
- @param {String} spec
- */
- Ember.Handlebars.template = function(spec) {
- var t = originalTemplate(spec);
- t.isTop = true;
- return t;
- };
- })();
- (function() {
- /**
- Mark a string as safe for unescaped output with Handlebars. If you
- return HTML from a Handlebars helper, use this function to
- ensure Handlebars does not escape the HTML.
- ```javascript
- Ember.String.htmlSafe('<div>someString</div>')
- ```
- @method htmlSafe
- @for Ember.String
- @static
- @return {Handlebars.SafeString} a string that will not be html escaped by Handlebars
- */
- Ember.String.htmlSafe = function(str) {
- return new Handlebars.SafeString(str);
- };
- var htmlSafe = Ember.String.htmlSafe;
- if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.String) {
- /**
- Mark a string as being safe for unescaped output with Handlebars.
- ```javascript
- '<div>someString</div>'.htmlSafe()
- ```
- See [Ember.String.htmlSafe](/api/classes/Ember.String.html#method_htmlSafe).
- @method htmlSafe
- @for String
- @return {Handlebars.SafeString} a string that will not be html escaped by Handlebars
- */
- String.prototype.htmlSafe = function() {
- return htmlSafe(this);
- };
- }
- })();
- (function() {
- Ember.Handlebars.resolvePaths = function(options) {
- var ret = [],
- contexts = options.contexts,
- roots = options.roots,
- data = options.data;
- for (var i=0, l=contexts.length; i<l; i++) {
- ret.push( Ember.Handlebars.get(roots[i], contexts[i], { data: data }) );
- }
- return ret;
- };
- })();
- (function() {
- /*jshint newcap:false*/
- /**
- @module ember
- @submodule ember-handlebars
- */
- var set = Ember.set, get = Ember.get;
- var Metamorph = requireModule('metamorph');
- function notifyMutationListeners() {
- Ember.run.once(Ember.View, 'notifyMutationListeners');
- }
- // DOMManager should just abstract dom manipulation between jquery and metamorph
- var DOMManager = {
- remove: function(view) {
- view.morph.remove();
- notifyMutationListeners();
- },
- prepend: function(view, html) {
- view.morph.prepend(html);
- notifyMutationListeners();
- },
- after: function(view, html) {
- view.morph.after(html);
- notifyMutationListeners();
- },
- html: function(view, html) {
- view.morph.html(html);
- notifyMutationListeners();
- },
- // This is messed up.
- replace: function(view) {
- var morph = view.morph;
- view.transitionTo('preRender');
- Ember.run.schedule('render', this, function renderMetamorphView() {
- if (view.isDestroying) { return; }
- view.clearRenderedChildren();
- var buffer = view.renderToBuffer();
- view.invokeRecursively(function(view) {
- view.propertyWillChange('element');
- });
- view.triggerRecursively('willInsertElement');
- morph.replaceWith(buffer.string());
- view.transitionTo('inDOM');
- view.invokeRecursively(function(view) {
- view.propertyDidChange('element');
- });
- view.triggerRecursively('didInsertElement');
- notifyMutationListeners();
- });
- },
- empty: function(view) {
- view.morph.html("");
- notifyMutationListeners();
- }
- };
- // The `morph` and `outerHTML` properties are internal only
- // and not observable.
- /**
- @class _Metamorph
- @namespace Ember
- @private
- */
- Ember._Metamorph = Ember.Mixin.create({
- isVirtual: true,
- tagName: '',
- instrumentName: 'metamorph',
- init: function() {
- this._super();
- this.morph = Metamorph();
- Ember.deprecate('Supplying a tagName to Metamorph views is unreliable and is deprecated. You may be setting the tagName on a Handlebars helper that creates a Metamorph.', !this.tagName);
- },
- beforeRender: function(buffer) {
- buffer.push(this.morph.startTag());
- buffer.pushOpeningTag();
- },
- afterRender: function(buffer) {
- buffer.pushClosingTag();
- buffer.push(this.morph.endTag());
- },
- createElement: function() {
- var buffer = this.renderToBuffer();
- this.outerHTML = buffer.string();
- this.clearBuffer();
- },
- domManager: DOMManager
- });
- /**
- @class _MetamorphView
- @namespace Ember
- @extends Ember.View
- @uses Ember._Metamorph
- @private
- */
- Ember._MetamorphView = Ember.View.extend(Ember._Metamorph);
- /**
- @class _SimpleMetamorphView
- @namespace Ember
- @extends Ember.CoreView
- @uses Ember._Metamorph
- @private
- */
- Ember._SimpleMetamorphView = Ember.CoreView.extend(Ember._Metamorph);
- })();
- (function() {
- /*globals Handlebars */
- /*jshint newcap:false*/
- /**
- @module ember
- @submodule ember-handlebars
- */
- var get = Ember.get, set = Ember.set, handlebarsGet = Ember.Handlebars.get;
- var Metamorph = requireModule('metamorph');
- function SimpleHandlebarsView(path, pathRoot, isEscaped, templateData) {
- this.path = path;
- this.pathRoot = pathRoot;
- this.isEscaped = isEscaped;
- this.templateData = templateData;
- this.morph = Metamorph();
- this.state = 'preRender';
- this.updateId = null;
- this._parentView = null;
- this.buffer = null;
- }
- Ember._SimpleHandlebarsView = SimpleHandlebarsView;
- SimpleHandlebarsView.prototype = {
- isVirtual: true,
- isView: true,
- destroy: function () {
- if (this.updateId) {
- Ember.run.cancel(this.updateId);
- this.updateId = null;
- }
- if (this._parentView) {
- this._parentView.removeChild(this);
- }
- this.morph = null;
- this.state = 'destroyed';
- },
- propertyWillChange: Ember.K,
- propertyDidChange: Ember.K,
- normalizedValue: function() {
- var path = this.path,
- pathRoot = this.pathRoot,
- result, templateData;
- // Use the pathRoot as the result if no path is provided. This
- // happens if the path is `this`, which gets normalized into
- // a `pathRoot` of the current Handlebars context and a path
- // of `''`.
- if (path === '') {
- result = pathRoot;
- } else {
- templateData = this.templateData;
- result = handlebarsGet(pathRoot, path, { data: templateData });
- }
- return result;
- },
- renderToBuffer: function(buffer) {
- var string = '';
- string += this.morph.startTag();
- string += this.render();
- string += this.morph.endTag();
- buffer.push(string);
- },
- render: function() {
- // If not invoked via a triple-mustache ({{{foo}}}), escape
- // the content of the template.
- var escape = this.isEscaped;
- var result = this.normalizedValue();
- if (result === null || result === undefined) {
- result = "";
- } else if (!(result instanceof Handlebars.SafeString)) {
- result = String(result);
- }
- if (escape) { result = Handlebars.Utils.escapeExpression(result); }
- return result;
- },
- rerender: function() {
- switch(this.state) {
- case 'preRender':
- case 'destroyed':
- break;
- case 'inBuffer':
- throw new Ember.Error("Something you did tried to replace an {{expression}} before it was inserted into the DOM.");
- case 'hasElement':
- case 'inDOM':
- this.updateId = Ember.run.scheduleOnce('render', this, 'update');
- break;
- }
- return this;
- },
- update: function () {
- this.updateId = null;
- this.morph.html(this.render());
- },
- transitionTo: function(state) {
- this.state = state;
- }
- };
- var states = Ember.View.cloneStates(Ember.View.states), merge = Ember.merge;
- merge(states._default, {
- rerenderIfNeeded: Ember.K
- });
- merge(states.inDOM, {
- rerenderIfNeeded: function(view) {
- if (view.normalizedValue() !== view._lastNormalizedValue) {
- view.rerender();
- }
- }
- });
- /**
- `Ember._HandlebarsBoundView` is a private view created by the Handlebars
- `{{bind}}` helpers that is used to keep track of bound properties.
- Every time a property is bound using a `{{mustache}}`, an anonymous subclass
- of `Ember._HandlebarsBoundView` is created with the appropriate sub-template
- and context set up. When the associated property changes, just the template
- for this view will re-render.
- @class _HandlebarsBoundView
- @namespace Ember
- @extends Ember._MetamorphView
- @private
- */
- Ember._HandlebarsBoundView = Ember._MetamorphView.extend({
- instrumentName: 'boundHandlebars',
- states: states,
- /**
- The function used to determine if the `displayTemplate` or
- `inverseTemplate` should be rendered. This should be a function that takes
- a value and returns a Boolean.
- @property shouldDisplayFunc
- @type Function
- @default null
- */
- shouldDisplayFunc: null,
- /**
- Whether the template rendered by this view gets passed the context object
- of its parent template, or gets passed the value of retrieving `path`
- from the `pathRoot`.
- For example, this is true when using the `{{#if}}` helper, because the
- template inside the helper should look up properties relative to the same
- object as outside the block. This would be `false` when used with `{{#with
- foo}}` because the template should receive the object found by evaluating
- `foo`.
- @property preserveContext
- @type Boolean
- @default false
- */
- preserveContext: false,
- /**
- If `preserveContext` is true, this is the object that will be used
- to render the template.
- @property previousContext
- @type Object
- */
- previousContext: null,
- /**
- The template to render when `shouldDisplayFunc` evaluates to `true`.
- @property displayTemplate
- @type Function
- @default null
- */
- displayTemplate: null,
- /**
- The template to render when `shouldDisplayFunc` evaluates to `false`.
- @property inverseTemplate
- @type Function
- @default null
- */
- inverseTemplate: null,
- /**
- The path to look up on `pathRoot` that is passed to
- `shouldDisplayFunc` to determine which template to render.
- In addition, if `preserveContext` is `false,` the object at this path will
- be passed to the template when rendering.
- @property path
- @type String
- @default null
- */
- path: null,
- /**
- The object from which the `path` will be looked up. Sometimes this is the
- same as the `previousContext`, but in cases where this view has been
- generated for paths that start with a keyword such as `view` or
- `controller`, the path root will be that resolved object.
- @property pathRoot
- @type Object
- */
- pathRoot: null,
- normalizedValue: function() {
- var path = get(this, 'path'),
- pathRoot = get(this, 'pathRoot'),
- valueNormalizer = get(this, 'valueNormalizerFunc'),
- result, templateData;
- // Use the pathRoot as the result if no path is provided. This
- // happens if the path is `this`, which gets normalized into
- // a `pathRoot` of the current Handlebars context and a path
- // of `''`.
- if (path === '') {
- result = pathRoot;
- } else {
- templateData = get(this, 'templateData');
- result = handlebarsGet(pathRoot, path, { data: templateData });
- }
- return valueNormalizer ? valueNormalizer(result) : result;
- },
- rerenderIfNeeded: function() {
- this.currentState.rerenderIfNeeded(this);
- },
- /**
- Determines which template to invoke, sets up the correct state based on
- that logic, then invokes the default `Ember.View` `render` implementation.
- This method will first look up the `path` key on `pathRoot`,
- then pass that value to the `shouldDisplayFunc` function. If that returns
- `true,` the `displayTemplate` function will be rendered to DOM. Otherwise,
- `inverseTemplate`, if specified, will be rendered.
- For example, if this `Ember._HandlebarsBoundView` represented the `{{#with
- foo}}` helper, it would look up the `foo` property of its context, and
- `shouldDisplayFunc` would always return true. The object found by looking
- up `foo` would be passed to `displayTemplate`.
- @method render
- @param {Ember.RenderBuffer} buffer
- */
- render: function(buffer) {
- // If not invoked via a triple-mustache ({{{foo}}}), escape
- // the content of the template.
- var escape = get(this, 'isEscaped');
- var shouldDisplay = get(this, 'shouldDisplayFunc'),
- preserveContext = get(this, 'preserveContext'),
- context = get(this, 'previousContext');
- var _contextController = get(this, '_contextController');
- var inverseTemplate = get(this, 'inverseTemplate'),
- displayTemplate = get(this, 'displayTemplate');
- var result = this.normalizedValue();
- this._lastNormalizedValue = result;
- // First, test the conditional to see if we should
- // render the template or not.
- if (shouldDisplay(result)) {
- set(this, 'template', displayTemplate);
- // If we are preserving the context (for example, if this
- // is an #if block, call the template with the same object.
- if (preserveContext) {
- set(this, '_context', context);
- } else {
- // Otherwise, determine if this is a block bind or not.
- // If so, pass the specified object to the template
- if (displayTemplate) {
- if (_contextController) {
- set(_contextController, 'content', result);
- result = _contextController;
- }
- set(this, '_context', result);
- } else {
- // This is not a bind block, just push the result of the
- // expression to the render context and return.
- if (result === null || result === undefined) {
- result = "";
- } else if (!(result instanceof Handlebars.SafeString)) {
- result = String(result);
- }
- if (escape) { result = Handlebars.Utils.escapeExpression(result); }
- buffer.push(result);
- return;
- }
- }
- } else if (inverseTemplate) {
- set(this, 'template', inverseTemplate);
- if (preserveContext) {
- set(this, '_context', context);
- } else {
- set(this, '_context', result);
- }
- } else {
- set(this, 'template', function() { return ''; });
- }
- return this._super(buffer);
- }
- });
- })();
- (function() {
- /**
- @module ember
- @submodule ember-handlebars
- */
- var get = Ember.get, set = Ember.set, fmt = Ember.String.fmt;
- var handlebarsGet = Ember.Handlebars.get, normalizePath = Ember.Handlebars.normalizePath;
- var handlebarsGetEscaped = Ember.Handlebars.getEscaped;
- var forEach = Ember.ArrayPolyfills.forEach;
- var o_create = Ember.create;
- var EmberHandlebars = Ember.Handlebars, helpers = EmberHandlebars.helpers;
- function exists(value) {
- return !Ember.isNone(value);
- }
- // Binds a property into the DOM. This will create a hook in DOM that the
- // KVO system will look for and update if the property changes.
- function bind(property, options, preserveContext, shouldDisplay, valueNormalizer, childProperties) {
- var data = options.data,
- fn = options.fn,
- inverse = options.inverse,
- view = data.view,
- currentContext = this,
- normalized, observer, i;
- normalized = normalizePath(currentContext, property, data);
- // Set up observers for observable objects
- if ('object' === typeof this) {
- if (data.insideGroup) {
- observer = function() {
- Ember.run.once(view, 'rerender');
- };
- var template, context, result = handlebarsGet(currentContext, property, options);
- result = valueNormalizer ? valueNormalizer(result) : result;
- context = preserveContext ? currentContext : result;
- if (shouldDisplay(result)) {
- template = fn;
- } else if (inverse) {
- template = inverse;
- }
- template(context, { data: options.data });
- } else {
- // Create the view that will wrap the output of this template/property
- // and add it to the nearest view's childViews array.
- // See the documentation of Ember._HandlebarsBoundView for more.
- var bindView = view.createChildView(Ember._HandlebarsBoundView, {
- preserveContext: preserveContext,
- shouldDisplayFunc: shouldDisplay,
- valueNormalizerFunc: valueNormalizer,
- displayTemplate: fn,
- inverseTemplate: inverse,
- path: property,
- pathRoot: currentContext,
- previousContext: currentContext,
- isEscaped: !options.hash.unescaped,
- templateData: options.data
- });
- if (options.hash.controller) {
- bindView.set('_contextController', this.container.lookupFactory('controller:'+options.hash.controller).create({
- container: currentContext.container,
- parentController: currentContext,
- target: currentContext
- }));
- }
- view.appendChild(bindView);
- observer = function() {
- Ember.run.scheduleOnce('render', bindView, 'rerenderIfNeeded');
- };
- }
- // Observes the given property on the context and
- // tells the Ember._HandlebarsBoundView to re-render. If property
- // is an empty string, we are printing the current context
- // object ({{this}}) so updating it is not our responsibility.
- if (normalized.path !== '') {
- view.registerObserver(normalized.root, normalized.path, observer);
- if (childProperties) {
- for (i=0; i<childProperties.length; i++) {
- view.registerObserver(normalized.root, normalized.path+'.'+childProperties[i], observer);
- }
- }
- }
- } else {
- // The object is not observable, so just render it out and
- // be done with it.
- data.buffer.push(handlebarsGetEscaped(currentContext, property, options));
- }
- }
- EmberHandlebars.bind = bind;
- function simpleBind(currentContext, property, options) {
- var data = options.data,
- view = data.view,
- normalized, observer, pathRoot, output;
- normalized = normalizePath(currentContext, property, data);
- pathRoot = normalized.root;
- // Set up observers for observable objects
- if (pathRoot && ('object' === typeof pathRoot)) {
- if (data.insideGroup) {
- observer = function() {
- Ember.run.once(view, 'rerender');
- };
- output = handlebarsGetEscaped(currentContext, property, options);
- data.buffer.push(output);
- } else {
- var bindView = new Ember._SimpleHandlebarsView(
- property, currentContext, !options.hash.unescaped, options.data
- );
- bindView._parentView = view;
- view.appendChild(bindView);
- observer = function() {
- Ember.run.scheduleOnce('render', bindView, 'rerender');
- };
- }
- // Observes the given property on the context and
- // tells the Ember._HandlebarsBoundView to re-render. If property
- // is an empty string, we are printing the current context
- // object ({{this}}) so updating it is not our responsibility.
- if (normalized.path !== '') {
- view.registerObserver(normalized.root, normalized.path, observer);
- }
- } else {
- // The object is not observable, so just render it out and
- // be done with it.
- output = handlebarsGetEscaped(currentContext, property, options);
- data.buffer.push(output);
- }
- }
- function shouldDisplayIfHelperContent(result) {
- var truthy = result && get(result, 'isTruthy');
- if (typeof truthy === 'boolean') { return truthy; }
- if (Ember.isArray(result)) {
- return get(result, 'length') !== 0;
- } else {
- return !!result;
- }
- }
- /**
- '_triageMustache' is used internally select between a binding, helper, or component for
- the given context. Until this point, it would be hard to determine if the
- mustache is a property reference or a regular helper reference. This triage
- helper resolves that.
- This would not be typically invoked by directly.
- @private
- @method _triageMustache
- @for Ember.Handlebars.helpers
- @param {String} property Property/helperID to triage
- @param {Object} options hash of template/rendering options
- @return {String} HTML string
- */
- EmberHandlebars.registerHelper('_triageMustache', function(property, options) {
- Ember.assert("You cannot pass more than one argument to the _triageMustache helper", arguments.length <= 2);
- if (helpers[property]) {
- return helpers[property].call(this, options);
- }
- var helper = Ember.Handlebars.resolveHelper(options.data.view.container, property);
- if (helper) {
- return helper.call(this, options);
- }
- return helpers.bind.call(this, property, options);
- });
- Ember.Handlebars.resolveHelper = function(container, name) {
- if (!container || name.indexOf('-') === -1) {
- return;
- }
- var helper = container.lookup('helper:' + name);
- if (!helper) {
- var componentLookup = container.lookup('component-lookup:main');
- Ember.assert("Could not find 'component-lookup:main' on the provided container, which is necessary for performing component lookups", componentLookup);
- var Component = componentLookup.lookupFactory(name, container);
- if (Component) {
- helper = EmberHandlebars.makeViewHelper(Component);
- container.register('helper:' + name, helper);
- }
- }
- return helper;
- };
- /**
- `bind` can be used to display a value, then update that value if it
- changes. For example, if you wanted to print the `title` property of
- `content`:
- ```handlebars
- {{bind "content.title"}}
- ```
- This will return the `title` property as a string, then create a new observer
- at the specified path. If it changes, it will update the value in DOM. Note
- that if you need to support IE7 and IE8 you must modify the model objects
- properties using `Ember.get()` and `Ember.set()` for this to work as it
- relies on Ember's KVO system. For all other browsers this will be handled for
- you automatically.
- @private
- @method bind
- @for Ember.Handlebars.helpers
- @param {String} property Property to bind
- @param {Function} fn Context to provide for rendering
- @return {String} HTML string
- */
- EmberHandlebars.registerHelper('bind', function bindHelper(property, options) {
- Ember.assert("You cannot pass more than one argument to the bind helper", arguments.length <= 2);
- var context = (options.contexts && options.contexts.length) ? options.contexts[0] : this;
- if (!options.fn) {
- return simpleBind(context, property, options);
- }
- return bind.call(context, property, options, false, exists);
- });
- /**
- Use the `boundIf` helper to create a conditional that re-evaluates
- whenever the truthiness of the bound value changes.
- ```handlebars
- {{#boundIf "content.shouldDisplayTitle"}}
- {{content.title}}
- {{/boundIf}}
- ```
- @private
- @method boundIf
- @for Ember.Handlebars.helpers
- @param {String} property Property to bind
- @param {Function} fn Context to provide for rendering
- @return {String} HTML string
- */
- EmberHandlebars.registerHelper('boundIf', function boundIfHelper(property, fn) {
- var context = (fn.contexts && fn.contexts.length) ? fn.contexts[0] : this;
- return bind.call(context, property, fn, true, shouldDisplayIfHelperContent, shouldDisplayIfHelperContent, ['isTruthy', 'length']);
- });
- /**
- @private
- Use the `unboundIf` helper to create a conditional that evaluates once.
- ```handlebars
- {{#unboundIf "content.shouldDisplayTitle"}}
- {{content.title}}
- {{/unboundIf}}
- ```
- @method unboundIf
- @for Ember.Handlebars.helpers
- @param {String} property Property to bind
- @param {Function} fn Context to provide for rendering
- @return {String} HTML string
- */
- EmberHandlebars.registerHelper('unboundIf', function unboundIfHelper(property, fn) {
- var context = (fn.contexts && fn.contexts.length) ? fn.contexts[0] : this,
- data = fn.data,
- template = fn.fn,
- inverse = fn.inverse,
- normalized, propertyValue, result;
- normalized = normalizePath(context, property, data);
- propertyValue = handlebarsGet(context, property, fn);
- if (!shouldDisplayIfHelperContent(propertyValue)) {
- template = inverse;
- }
- template(context, { data: data });
- });
- /**
- Use the `{{with}}` helper when you want to scope context. Take the following code as an example:
- ```handlebars
- <h5>{{user.name}}</h5>
- <div class="role">
- <h6>{{user.role.label}}</h6>
- <span class="role-id">{{user.role.id}}</span>
- <p class="role-desc">{{user.role.description}}</p>
- </div>
- ```
- `{{with}}` can be our best friend in these cases,
- instead of writing `user.role.*` over and over, we use `{{#with user.role}}`.
- Now the context within the `{{#with}} .. {{/with}}` block is `user.role` so you can do the following:
- ```handlebars
- <h5>{{user.name}}</h5>
- <div class="role">
- {{#with user.role}}
- <h6>{{label}}</h6>
- <span class="role-id">{{id}}</span>
- <p class="role-desc">{{description}}</p>
- {{/with}}
- </div>
- ```
- ### `as` operator
- This operator aliases the scope to a new name. It's helpful for semantic clarity and to retain
- default scope or to reference from another `{{with}}` block.
- ```handlebars
- // posts might not be
- {{#with user.posts as blogPosts}}
- <div class="notice">
- There are {{blogPosts.length}} blog posts written by {{user.name}}.
- </div>
- {{#each post in blogPosts}}
- <li>{{post.title}}</li>
- {{/each}}
- {{/with}}
- ```
- Without the `as` operator, it would be impossible to reference `user.name` in the example above.
- ### `controller` option
- Adding `controller='something'` instructs the `{{with}}` helper to create and use an instance of
- the specified controller with the new context as its content.
- This is very similar to using an `itemController` option with the `{{each}}` helper.
- ```handlebars
- {{#with users.posts controller='userBlogPosts'}}
- {{!- The current context is wrapped in our controller instance }}
- {{/with}}
- ```
- In the above example, the template provided to the `{{with}}` block is now wrapped in the
- `userBlogPost` controller, which provides a very elegant way to decorate the context with custom
- functions/properties.
- @method with
- @for Ember.Handlebars.helpers
- @param {Function} context
- @param {Hash} options
- @return {String} HTML string
- */
- EmberHandlebars.registerHelper('with', function withHelper(context, options) {
- if (arguments.length === 4) {
- var keywordName, path, rootPath, normalized, contextPath;
- Ember.assert("If you pass more than one argument to the with helper, it must be in the form #with foo as bar", arguments[1] === "as");
- options = arguments[3];
- keywordName = arguments[2];
- path = arguments[0];
- Ember.assert("You must pass a block to the with helper", options.fn && options.fn !== Handlebars.VM.noop);
- var localizedOptions = o_create(options);
- localizedOptions.data = o_create(options.data);
- localizedOptions.data.keywords = o_create(options.data.keywords || {});
- if (Ember.isGlobalPath(path)) {
- contextPath = path;
- } else {
- normalized = normalizePath(this, path, options.data);
- path = normalized.path;
- rootPath = normalized.root;
- // This is a workaround for the fact that you cannot bind separate objects
- // together. When we implement that functionality, we should use it here.
- var contextKey = Ember.$.expando + Ember.guidFor(rootPath);
- localizedOptions.data.keywords[contextKey] = rootPath;
- // if the path is '' ("this"), just bind directly to the current context
- contextPath = path ? contextKey + '.' + path : contextKey;
- }
- Ember.bind(localizedOptions.data.keywords, keywordName, contextPath);
- return bind.call(this, path, localizedOptions, true, exists);
- } else {
- Ember.assert("You must pass exactly one argument to the with helper", arguments.length === 2);
- Ember.assert("You must pass a block to the with helper", options.fn && options.fn !== Handlebars.VM.noop);
- return helpers.bind.call(options.contexts[0], context, options);
- }
- });
- /**
- See [boundIf](/api/classes/Ember.Handlebars.helpers.html#method_boundIf)
- and [unboundIf](/api/classes/Ember.Handlebars.helpers.html#method_unboundIf)
- @method if
- @for Ember.Handlebars.helpers
- @param {Function} context
- @param {Hash} options
- @return {String} HTML string
- */
- EmberHandlebars.registerHelper('if', function ifHelper(context, options) {
- Ember.assert("You must pass exactly one argument to the if helper", arguments.length === 2);
- Ember.assert("You must pass a block to the if helper", options.fn && options.fn !== Handlebars.VM.noop);
- if (options.data.isUnbound) {
- return helpers.unboundIf.call(options.contexts[0], context, options);
- } else {
- return helpers.boundIf.call(options.contexts[0], context, options);
- }
- });
- /**
- @method unless
- @for Ember.Handlebars.helpers
- @param {Function} context
- @param {Hash} options
- @return {String} HTML string
- */
- EmberHandlebars.registerHelper('unless', function unlessHelper(context, options) {
- Ember.assert("You must pass exactly one argument to the unless helper", arguments.length === 2);
- Ember.assert("You must pass a block to the unless helper", options.fn && options.fn !== Handlebars.VM.noop);
- var fn = options.fn, inverse = options.inverse;
- options.fn = inverse;
- options.inverse = fn;
- if (options.data.isUnbound) {
- return helpers.unboundIf.call(options.contexts[0], context, options);
- } else {
- return helpers.boundIf.call(options.contexts[0], context, options);
- }
- });
- /**
- `bind-attr` allows you to create a binding between DOM element attributes and
- Ember objects. For example:
- ```handlebars
- <img {{bind-attr src="imageUrl" alt="imageTitle"}}>
- ```
- The above handlebars template will fill the `<img>`'s `src` attribute will
- the value of the property referenced with `"imageUrl"` and its `alt`
- attribute with the value of the property referenced with `"imageTitle"`.
- If the rendering context of this template is the following object:
- ```javascript
- {
- imageUrl: 'http://lolcats.info/haz-a-funny',
- imageTitle: 'A humorous image of a cat'
- }
- ```
- The resulting HTML output will be:
- ```html
- <img src="http://lolcats.info/haz-a-funny" alt="A humorous image of a cat">
- ```
- `bind-attr` cannot redeclare existing DOM element attributes. The use of `src`
- in the following `bind-attr` example will be ignored and the hard coded value
- of `src="/failwhale.gif"` will take precedence:
- ```handlebars
- <img src="/failwhale.gif" {{bind-attr src="imageUrl" alt="imageTitle"}}>
- ```
- ### `bind-attr` and the `class` attribute
- `bind-attr` supports a special syntax for handling a number of cases unique
- to the `class` DOM element attribute. The `class` attribute combines
- multiple discrete values into a single attribute as a space-delimited
- list of strings. Each string can be:
- * a string return value of an object's property.
- * a boolean return value of an object's property
- * a hard-coded value
- A string return value works identically to other uses of `bind-attr`. The
- return value of the property will become the value of the attribute. For
- example, the following view and template:
- ```javascript
- AView = Ember.View.extend({
- someProperty: function() {
- return "aValue";
- }.property()
- })
- ```
- ```handlebars
- <img {{bind-attr class="view.someProperty}}>
- ```
- Result in the following rendered output:
- ```html
- <img class="aValue">
- ```
- A boolean return value will insert a specified class name if the property
- returns `true` and remove the class name if the property returns `false`.
- A class name is provided via the syntax
- `somePropertyName:class-name-if-true`.
- ```javascript
- AView = Ember.View.extend({
- someBool: true
- })
- ```
- ```handlebars
- <img {{bind-attr class="view.someBool:class-name-if-true"}}>
- ```
- Result in the following rendered output:
- ```html
- <img class="class-name-if-true">
- ```
- An additional section of the binding can be provided if you want to
- replace the existing class instead of removing it when the boolean
- value changes:
- ```handlebars
- <img {{bind-attr class="view.someBool:class-name-if-true:class-name-if-false"}}>
- ```
- A hard-coded value can be used by prepending `:` to the desired
- class name: `:class-name-to-always-apply`.
- ```handlebars
- <img {{bind-attr class=":class-name-to-always-apply"}}>
- ```
- Results in the following rendered output:
- ```html
- <img class="class-name-to-always-apply">
- ```
- All three strategies - string return value, boolean return value, and
- hard-coded value – can be combined in a single declaration:
- ```handlebars
- <img {{bind-attr class=":class-name-to-always-apply view.someBool:class-name-if-true view.someProperty"}}>
- ```
- @method bind-attr
- @for Ember.Handlebars.helpers
- @param {Hash} options
- @return {String} HTML string
- */
- EmberHandlebars.registerHelper('bind-attr', function bindAttrHelper(options) {
- var attrs = options.hash;
- Ember.assert("You must specify at least one hash argument to bind-attr", !!Ember.keys(attrs).length);
- var view = options.data.view;
- var ret = [];
- var ctx = this;
- // Generate a unique id for this element. This will be added as a
- // data attribute to the element so it can be looked up when
- // the bound property changes.
- var dataId = ++Ember.uuid;
- // Handle classes differently, as we can bind multiple classes
- var classBindings = attrs['class'];
- if (classBindings != null) {
- var classResults = EmberHandlebars.bindClasses(this, classBindings, view, dataId, options);
- ret.push('class="' + Handlebars.Utils.escapeExpression(classResults.join(' ')) + '"');
- delete attrs['class'];
- }
- var attrKeys = Ember.keys(attrs);
- // For each attribute passed, create an observer and emit the
- // current value of the property as an attribute.
- forEach.call(attrKeys, function(attr) {
- var path = attrs[attr],
- normalized;
- Ember.assert(fmt("You must provide an expression as the value of bound attribute. You specified: %@=%@", [attr, path]), typeof path === 'string');
- normalized = normalizePath(ctx, path, options.data);
- var value = (path === 'this') ? normalized.root : handlebarsGet(ctx, path, options),
- type = Ember.typeOf(value);
- Ember.assert(fmt("Attributes must be numbers, strings or booleans, not %@", [value]), value === null || value === undefined || type === 'number' || type === 'string' || type === 'boolean');
- var observer, invoker;
- observer = function observer() {
- var result = handlebarsGet(ctx, path, options);
- Ember.assert(fmt("Attributes must be numbers, strings or booleans, not %@", [result]),
- result === null || result === undefined || typeof result === 'number' ||
- typeof result === 'string' || typeof result === 'boolean');
- var elem = view.$("[data-bindattr-" + dataId + "='" + dataId + "']");
- // If we aren't able to find the element, it means the element
- // to which we were bound has been removed from the view.
- // In that case, we can assume the template has been re-rendered
- // and we need to clean up the observer.
- if (!elem || elem.length === 0) {
- Ember.removeObserver(normalized.root, normalized.path, invoker);
- return;
- }
- Ember.View.applyAttributeBindings(elem, attr, result);
- };
- // Add an observer to the view for when the property changes.
- // When the observer fires, find the element using the
- // unique data id and update the attribute to the new value.
- // Note: don't add observer when path is 'this' or path
- // is whole keyword e.g. {{#each x in list}} ... {{bind-attr attr="x"}}
- if (path !== 'this' && !(normalized.isKeyword && normalized.path === '' )) {
- view.registerObserver(normalized.root, normalized.path, observer);
- }
- // if this changes, also change the logic in ember-views/lib/views/view.js
- if ((type === 'string' || (type === 'number' && !isNaN(value)))) {
- ret.push(attr + '="' + Handlebars.Utils.escapeExpression(value) + '"');
- } else if (value && type === 'boolean') {
- // The developer controls the attr name, so it should always be safe
- ret.push(attr + '="' + attr + '"');
- }
- }, this);
- // Add the unique identifier
- // NOTE: We use all lower-case since Firefox has problems with mixed case in SVG
- ret.push('data-bindattr-' + dataId + '="' + dataId + '"');
- return new EmberHandlebars.SafeString(ret.join(' '));
- });
- /**
- See `bind-attr`
- @method bindAttr
- @for Ember.Handlebars.helpers
- @deprecated
- @param {Function} context
- @param {Hash} options
- @return {String} HTML string
- */
- EmberHandlebars.registerHelper('bindAttr', function bindAttrHelper() {
- Ember.warn("The 'bindAttr' view helper is deprecated in favor of 'bind-attr'");
- return EmberHandlebars.helpers['bind-attr'].apply(this, arguments);
- });
- /**
- Helper that, given a space-separated string of property paths and a context,
- returns an array of class names. Calling this method also has the side
- effect of setting up observers at those property paths, such that if they
- change, the correct class name will be reapplied to the DOM element.
- For example, if you pass the string "fooBar", it will first look up the
- "fooBar" value of the context. If that value is true, it will add the
- "foo-bar" class to the current element (i.e., the dasherized form of
- "fooBar"). If the value is a string, it will add that string as the class.
- Otherwise, it will not add any new class name.
- @private
- @method bindClasses
- @for Ember.Handlebars
- @param {Ember.Object} context The context from which to lookup properties
- @param {String} classBindings A string, space-separated, of class bindings
- to use
- @param {Ember.View} view The view in which observers should look for the
- element to update
- @param {Srting} bindAttrId Optional bindAttr id used to lookup elements
- @return {Array} An array of class names to add
- */
- EmberHandlebars.bindClasses = function(context, classBindings, view, bindAttrId, options) {
- var ret = [], newClass, value, elem;
- // Helper method to retrieve the property from the context and
- // determine which class string to return, based on whether it is
- // a Boolean or not.
- var classStringForPath = function(root, parsedPath, options) {
- var val,
- path = parsedPath.path;
- if (path === 'this') {
- val = root;
- } else if (path === '') {
- val = true;
- } else {
- val = handlebarsGet(root, path, options);
- }
- return Ember.View._classStringForValue(path, val, parsedPath.className, parsedPath.falsyClassName);
- };
- // For each property passed, loop through and setup
- // an observer.
- forEach.call(classBindings.split(' '), function(binding) {
- // Variable in which the old class value is saved. The observer function
- // closes over this variable, so it knows which string to remove when
- // the property changes.
- var oldClass;
- var observer, invoker;
- var parsedPath = Ember.View._parsePropertyPath(binding),
- path = parsedPath.path,
- pathRoot = context,
- normalized;
- if (path !== '' && path !== 'this') {
- normalized = normalizePath(context, path, options.data);
- pathRoot = normalized.root;
- path = normalized.path;
- }
- // Set up an observer on the context. If the property changes, toggle the
- // class name.
- observer = function() {
- // Get the current value of the property
- newClass = classStringForPath(context, parsedPath, options);
- elem = bindAttrId ? view.$("[data-bindattr-" + bindAttrId + "='" + bindAttrId + "']") : view.$();
- // If we can't find the element anymore, a parent template has been
- // re-rendered and we've been nuked. Remove the observer.
- if (!elem || elem.length === 0) {
- Ember.removeObserver(pathRoot, path, invoker);
- } else {
- // If we had previously added a class to the element, remove it.
- if (oldClass) {
- elem.removeClass(oldClass);
- }
- // If necessary, add a new class. Make sure we keep track of it so
- // it can be removed in the future.
- if (newClass) {
- elem.addClass(newClass);
- oldClass = newClass;
- } else {
- oldClass = null;
- }
- }
- };
- if (path !== '' && path !== 'this') {
- view.registerObserver(pathRoot, path, observer);
- }
- // We've already setup the observer; now we just need to figure out the
- // correct behavior right now on the first pass through.
- value = classStringForPath(context, parsedPath, options);
- if (value) {
- ret.push(value);
- // Make sure we save the current value so that it can be removed if the
- // observer fires.
- oldClass = value;
- }
- });
- return ret;
- };
- })();
- (function() {
- /*globals Handlebars */
- // TODO: Don't require the entire module
- /**
- @module ember
- @submodule ember-handlebars
- */
- var get = Ember.get, set = Ember.set;
- var EmberHandlebars = Ember.Handlebars;
- var LOWERCASE_A_Z = /^[a-z]/;
- var VIEW_PREFIX = /^view\./;
- function makeBindings(thisContext, options) {
- var hash = options.hash,
- hashType = options.hashTypes;
- for (var prop in hash) {
- if (hashType[prop] === 'ID') {
- var value = hash[prop];
- if (Ember.IS_BINDING.test(prop)) {
- Ember.warn("You're attempting to render a view by passing " + prop + "=" + value + " to a view helper, but this syntax is ambiguous. You should either surround " + value + " in quotes or remove `Binding` from " + prop + ".");
- } else {
- hash[prop + 'Binding'] = value;
- hashType[prop + 'Binding'] = 'STRING';
- delete hash[prop];
- delete hashType[prop];
- }
- }
- }
- if (hash.hasOwnProperty('idBinding')) {
- // id can't be bound, so just perform one-time lookup.
- hash.id = EmberHandlebars.get(thisContext, hash.idBinding, options);
- hashType.id = 'STRING';
- delete hash.idBinding;
- delete hashType.idBinding;
- }
- }
- EmberHandlebars.ViewHelper = Ember.Object.create({
- propertiesFromHTMLOptions: function(options) {
- var hash = options.hash, data = options.data;
- var extensions = {},
- classes = hash['class'],
- dup = false;
- if (hash.id) {
- extensions.elementId = hash.id;
- dup = true;
- }
- if (hash.tag) {
- extensions.tagName = hash.tag;
- dup = true;
- }
- if (classes) {
- classes = classes.split(' ');
- extensions.classNames = classes;
- dup = true;
- }
- if (hash.classBinding) {
- extensions.classNameBindings = hash.classBinding.split(' ');
- dup = true;
- }
- if (hash.classNameBindings) {
- if (extensions.classNameBindings === undefined) extensions.classNameBindings = [];
- extensions.classNameBindings = extensions.classNameBindings.concat(hash.classNameBindings.split(' '));
- dup = true;
- }
- if (hash.attributeBindings) {
- Ember.assert("Setting 'attributeBindings' via Handlebars is not allowed. Please subclass Ember.View and set it there instead.");
- extensions.attributeBindings = null;
- dup = true;
- }
- if (dup) {
- hash = Ember.$.extend({}, hash);
- delete hash.id;
- delete hash.tag;
- delete hash['class'];
- delete hash.classBinding;
- }
- // Set the proper context for all bindings passed to the helper. This applies to regular attribute bindings
- // as well as class name bindings. If the bindings are local, make them relative to the current context
- // instead of the view.
- var path;
- // Evaluate the context of regular attribute bindings:
- for (var prop in hash) {
- if (!hash.hasOwnProperty(prop)) { continue; }
- // Test if the property ends in "Binding"
- if (Ember.IS_BINDING.test(prop) && typeof hash[prop] === 'string') {
- path = this.contextualizeBindingPath(hash[prop], data);
- if (path) { hash[prop] = path; }
- }
- }
- // Evaluate the context of class name bindings:
- if (extensions.classNameBindings) {
- for (var b in extensions.classNameBindings) {
- var full = extensions.classNameBindings[b];
- if (typeof full === 'string') {
- // Contextualize the path of classNameBinding so this:
- //
- // classNameBinding="isGreen:green"
- //
- // is converted to this:
- //
- // classNameBinding="_parentView.context.isGreen:green"
- var parsedPath = Ember.View._parsePropertyPath(full);
- path = this.contextualizeBindingPath(parsedPath.path, data);
- if (path) { extensions.classNameBindings[b] = path + parsedPath.classNames; }
- }
- }
- }
- return Ember.$.extend(hash, extensions);
- },
- // Transform bindings from the current context to a context that can be evaluated within the view.
- // Returns null if the path shouldn't be changed.
- //
- // TODO: consider the addition of a prefix that would allow this method to return `path`.
- contextualizeBindingPath: function(path, data) {
- var normalized = Ember.Handlebars.normalizePath(null, path, data);
- if (normalized.isKeyword) {
- return 'templateData.keywords.' + path;
- } else if (Ember.isGlobalPath(path)) {
- return null;
- } else if (path === 'this' || path === '') {
- return '_parentView.context';
- } else {
- return '_parentView.context.' + path;
- }
- },
- helper: function(thisContext, path, options) {
- var data = options.data,
- fn = options.fn,
- newView;
- makeBindings(thisContext, options);
- if ('string' === typeof path) {
- // TODO: this is a lame conditional, this should likely change
- // but something along these lines will likely need to be added
- // as deprecation warnings
- //
- if (options.types[0] === 'STRING' && LOWERCASE_A_Z.test(path) && !VIEW_PREFIX.test(path)) {
- Ember.assert("View requires a container", !!data.view.container);
- newView = data.view.container.lookupFactory('view:' + path);
- } else {
- newView = EmberHandlebars.get(thisContext, path, options);
- }
- Ember.assert("Unable to find view at path '" + path + "'", !!newView);
- } else {
- newView = path;
- }
- Ember.assert(Ember.String.fmt('You must pass a view to the #view helper, not %@ (%@)', [path, newView]), Ember.View.detect(newView) || Ember.View.detectInstance(newView));
- var viewOptions = this.propertiesFromHTMLOptions(options, thisContext);
- var currentView = data.view;
- viewOptions.templateData = data;
- var newViewProto = newView.proto ? newView.proto() : newView;
- if (fn) {
- Ember.assert("You cannot provide a template block if you also specified a templateName", !get(viewOptions, 'templateName') && !get(newViewProto, 'templateName'));
- viewOptions.template = fn;
- }
- // We only want to override the `_context` computed property if there is
- // no specified controller. See View#_context for more information.
- if (!newViewProto.controller && !newViewProto.controllerBinding && !viewOptions.controller && !viewOptions.controllerBinding) {
- viewOptions._context = thisContext;
- }
- currentView.appendChild(newView, viewOptions);
- }
- });
- /**
- `{{view}}` inserts a new instance of `Ember.View` into a template passing its
- options to the `Ember.View`'s `create` method and using the supplied block as
- the view's own template.
- An empty `<body>` and the following template:
- ```handlebars
- A span:
- {{#view tagName="span"}}
- hello.
- {{/view}}
- ```
- Will result in HTML structure:
- ```html
- <body>
- <!-- Note: the handlebars template script
- also results in a rendered Ember.View
- which is the outer <div> here -->
- <div class="ember-view">
- A span:
- <span id="ember1" class="ember-view">
- Hello.
- </span>
- </div>
- </body>
- ```
- ### `parentView` setting
- The `parentView` property of the new `Ember.View` instance created through
- `{{view}}` will be set to the `Ember.View` instance of the template where
- `{{view}}` was called.
- ```javascript
- aView = Ember.View.create({
- template: Ember.Handlebars.compile("{{#view}} my parent: {{parentView.elementId}} {{/view}}")
- });
- aView.appendTo('body');
- ```
- Will result in HTML structure:
- ```html
- <div id="ember1" class="ember-view">
- <div id="ember2" class="ember-view">
- my parent: ember1
- </div>
- </div>
- ```
- ### Setting CSS id and class attributes
- The HTML `id` attribute can be set on the `{{view}}`'s resulting element with
- the `id` option. This option will _not_ be passed to `Ember.View.create`.
- ```handlebars
- {{#view tagName="span" id="a-custom-id"}}
- hello.
- {{/view}}
- ```
- Results in the following HTML structure:
- ```html
- <div class="ember-view">
- <span id="a-custom-id" class="ember-view">
- hello.
- </span>
- </div>
- ```
- The HTML `class` attribute can be set on the `{{view}}`'s resulting element
- with the `class` or `classNameBindings` options. The `class` option will
- directly set the CSS `class` attribute and will not be passed to
- `Ember.View.create`. `classNameBindings` will be passed to `create` and use
- `Ember.View`'s class name binding functionality:
- ```handlebars
- {{#view tagName="span" class="a-custom-class"}}
- hello.
- {{/view}}
- ```
- Results in the following HTML structure:
- ```html
- <div class="ember-view">
- <span id="ember2" class="ember-view a-custom-class">
- hello.
- </span>
- </div>
- ```
- ### Supplying a different view class
- `{{view}}` can take an optional first argument before its supplied options to
- specify a path to a custom view class.
- ```handlebars
- {{#view "MyApp.CustomView"}}
- hello.
- {{/view}}
- ```
- The first argument can also be a relative path accessible from the current
- context.
- ```javascript
- MyApp = Ember.Application.create({});
- MyApp.OuterView = Ember.View.extend({
- innerViewClass: Ember.View.extend({
- classNames: ['a-custom-view-class-as-property']
- }),
- template: Ember.Handlebars.compile('{{#view "view.innerViewClass"}} hi {{/view}}')
- });
- MyApp.OuterView.create().appendTo('body');
- ```
- Will result in the following HTML:
- ```html
- <div id="ember1" class="ember-view">
- <div id="ember2" class="ember-view a-custom-view-class-as-property">
- hi
- </div>
- </div>
- ```
- ### Blockless use
- If you supply a custom `Ember.View` subclass that specifies its own template
- or provide a `templateName` option to `{{view}}` it can be used without
- supplying a block. Attempts to use both a `templateName` option and supply a
- block will throw an error.
- ```handlebars
- {{view "MyApp.ViewWithATemplateDefined"}}
- ```
- ### `viewName` property
- You can supply a `viewName` option to `{{view}}`. The `Ember.View` instance
- will be referenced as a property of its parent view by this name.
- ```javascript
- aView = Ember.View.create({
- template: Ember.Handlebars.compile('{{#view viewName="aChildByName"}} hi {{/view}}')
- });
- aView.appendTo('body');
- aView.get('aChildByName') // the instance of Ember.View created by {{view}} helper
- ```
- @method view
- @for Ember.Handlebars.helpers
- @param {String} path
- @param {Hash} options
- @return {String} HTML string
- */
- EmberHandlebars.registerHelper('view', function viewHelper(path, options) {
- Ember.assert("The view helper only takes a single argument", arguments.length <= 2);
- // If no path is provided, treat path param as options.
- if (path && path.data && path.data.isRenderData) {
- options = path;
- path = "Ember.View";
- }
- return EmberHandlebars.ViewHelper.helper(this, path, options);
- });
- })();
- (function() {
- // TODO: Don't require all of this module
- /**
- @module ember
- @submodule ember-handlebars
- */
- var get = Ember.get, handlebarsGet = Ember.Handlebars.get, fmt = Ember.String.fmt;
- /**
- `{{collection}}` is a `Ember.Handlebars` helper for adding instances of
- `Ember.CollectionView` to a template. See [Ember.CollectionView](/api/classes/Ember.CollectionView.html)
- for additional information on how a `CollectionView` functions.
- `{{collection}}`'s primary use is as a block helper with a `contentBinding`
- option pointing towards an `Ember.Array`-compatible object. An `Ember.View`
- instance will be created for each item in its `content` property. Each view
- will have its own `content` property set to the appropriate item in the
- collection.
- The provided block will be applied as the template for each item's view.
- Given an empty `<body>` the following template:
- ```handlebars
- {{#collection contentBinding="App.items"}}
- Hi {{view.content.name}}
- {{/collection}}
- ```
- And the following application code
- ```javascript
- App = Ember.Application.create()
- App.items = [
- Ember.Object.create({name: 'Dave'}),
- Ember.Object.create({name: 'Mary'}),
- Ember.Object.create({name: 'Sara'})
- ]
- ```
- Will result in the HTML structure below
- ```html
- <div class="ember-view">
- <div class="ember-view">Hi Dave</div>
- <div class="ember-view">Hi Mary</div>
- <div class="ember-view">Hi Sara</div>
- </div>
- ```
- ### Blockless use in a collection
- If you provide an `itemViewClass` option that has its own `template` you can
- omit the block.
- The following template:
- ```handlebars
- {{collection contentBinding="App.items" itemViewClass="App.AnItemView"}}
- ```
- And application code
- ```javascript
- App = Ember.Application.create();
- App.items = [
- Ember.Object.create({name: 'Dave'}),
- Ember.Object.create({name: 'Mary'}),
- Ember.Object.create({name: 'Sara'})
- ];
- App.AnItemView = Ember.View.extend({
- template: Ember.Handlebars.compile("Greetings {{view.content.name}}")
- });
- ```
- Will result in the HTML structure below
- ```html
- <div class="ember-view">
- <div class="ember-view">Greetings Dave</div>
- <div class="ember-view">Greetings Mary</div>
- <div class="ember-view">Greetings Sara</div>
- </div>
- ```
- ### Specifying a CollectionView subclass
- By default the `{{collection}}` helper will create an instance of
- `Ember.CollectionView`. You can supply a `Ember.CollectionView` subclass to
- the helper by passing it as the first argument:
- ```handlebars
- {{#collection App.MyCustomCollectionClass contentBinding="App.items"}}
- Hi {{view.content.name}}
- {{/collection}}
- ```
- ### Forwarded `item.*`-named Options
- As with the `{{view}}`, helper options passed to the `{{collection}}` will be
- set on the resulting `Ember.CollectionView` as properties. Additionally,
- options prefixed with `item` will be applied to the views rendered for each
- item (note the camelcasing):
- ```handlebars
- {{#collection contentBinding="App.items"
- itemTagName="p"
- itemClassNames="greeting"}}
- Howdy {{view.content.name}}
- {{/collection}}
- ```
- Will result in the following HTML structure:
- ```html
- <div class="ember-view">
- <p class="ember-view greeting">Howdy Dave</p>
- <p class="ember-view greeting">Howdy Mary</p>
- <p class="ember-view greeting">Howdy Sara</p>
- </div>
- ```
- @method collection
- @for Ember.Handlebars.helpers
- @param {String} path
- @param {Hash} options
- @return {String} HTML string
- @deprecated Use `{{each}}` helper instead.
- */
- Ember.Handlebars.registerHelper('collection', function collectionHelper(path, options) {
- Ember.deprecate("Using the {{collection}} helper without specifying a class has been deprecated as the {{each}} helper now supports the same functionality.", path !== 'collection');
- // If no path is provided, treat path param as options.
- if (path && path.data && path.data.isRenderData) {
- options = path;
- path = undefined;
- Ember.assert("You cannot pass more than one argument to the collection helper", arguments.length === 1);
- } else {
- Ember.assert("You cannot pass more than one argument to the collection helper", arguments.length === 2);
- }
- var fn = options.fn;
- var data = options.data;
- var inverse = options.inverse;
- var view = options.data.view;
- var controller, container;
- // If passed a path string, convert that into an object.
- // Otherwise, just default to the standard class.
- var collectionClass;
- if (path) {
- controller = data.keywords.controller;
- container = controller && controller.container;
- collectionClass = handlebarsGet(this, path, options) || container.lookupFactory('view:' + path);
- Ember.assert(fmt("%@ #collection: Could not find collection class %@", [data.view, path]), !!collectionClass);
- }
- else {
- collectionClass = Ember.CollectionView;
- }
- var hash = options.hash, itemHash = {}, match;
- // Extract item view class if provided else default to the standard class
- var collectionPrototype = collectionClass.proto(),
- itemViewClass;
- if (hash.itemView) {
- controller = data.keywords.controller;
- Ember.assert('You specified an itemView, but the current context has no ' +
- 'container to look the itemView up in. This probably means ' +
- 'that you created a view manually, instead of through the ' +
- 'container. Instead, use container.lookup("view:viewName"), ' +
- 'which will properly instantiate your view.',
- controller && controller.container);
- container = controller.container;
- itemViewClass = container.lookupFactory('view:' + hash.itemView);
- Ember.assert('You specified the itemView ' + hash.itemView + ", but it was " +
- "not found at " + container.describe("view:" + hash.itemView) +
- " (and it was not registered in the container)", !!itemViewClass);
- } else if (hash.itemViewClass) {
- itemViewClass = handlebarsGet(collectionPrototype, hash.itemViewClass, options);
- } else {
- itemViewClass = collectionPrototype.itemViewClass;
- }
- Ember.assert(fmt("%@ #collection: Could not find itemViewClass %@", [data.view, itemViewClass]), !!itemViewClass);
- delete hash.itemViewClass;
- delete hash.itemView;
- // Go through options passed to the {{collection}} helper and extract options
- // that configure item views instead of the collection itself.
- for (var prop in hash) {
- if (hash.hasOwnProperty(prop)) {
- match = prop.match(/^item(.)(.*)$/);
- if (match && prop !== 'itemController') {
- // Convert itemShouldFoo -> shouldFoo
- itemHash[match[1].toLowerCase() + match[2]] = hash[prop];
- // Delete from hash as this will end up getting passed to the
- // {{view}} helper method.
- delete hash[prop];
- }
- }
- }
- if (fn) {
- itemHash.template = fn;
- delete options.fn;
- }
- var emptyViewClass;
- if (inverse && inverse !== Ember.Handlebars.VM.noop) {
- emptyViewClass = get(collectionPrototype, 'emptyViewClass');
- emptyViewClass = emptyViewClass.extend({
- template: inverse,
- tagName: itemHash.tagName
- });
- } else if (hash.emptyViewClass) {
- emptyViewClass = handlebarsGet(this, hash.emptyViewClass, options);
- }
- if (emptyViewClass) { hash.emptyView = emptyViewClass; }
- if (!hash.keyword) {
- itemHash._context = Ember.computed.alias('content');
- }
- var viewOptions = Ember.Handlebars.ViewHelper.propertiesFromHTMLOptions({ data: data, hash: itemHash }, this);
- hash.itemViewClass = itemViewClass.extend(viewOptions);
- return Ember.Handlebars.helpers.view.call(this, collectionClass, options);
- });
- })();
- (function() {
- /*globals Handlebars */
- /**
- @module ember
- @submodule ember-handlebars
- */
- var handlebarsGet = Ember.Handlebars.get;
- /**
- `unbound` allows you to output a property without binding. *Important:* The
- output will not be updated if the property changes. Use with caution.
- ```handlebars
- <div>{{unbound somePropertyThatDoesntChange}}</div>
- ```
- `unbound` can also be used in conjunction with a bound helper to
- render it in its unbound form:
- ```handlebars
- <div>{{unbound helperName somePropertyThatDoesntChange}}</div>
- ```
- @method unbound
- @for Ember.Handlebars.helpers
- @param {String} property
- @return {String} HTML string
- */
- Ember.Handlebars.registerHelper('unbound', function unboundHelper(property, fn) {
- var options = arguments[arguments.length - 1], helper, context, out;
- if (arguments.length > 2) {
- // Unbound helper call.
- options.data.isUnbound = true;
- helper = Ember.Handlebars.helpers[arguments[0]] || Ember.Handlebars.helpers.helperMissing;
- out = helper.apply(this, Array.prototype.slice.call(arguments, 1));
- delete options.data.isUnbound;
- return out;
- }
- context = (fn.contexts && fn.contexts.length) ? fn.contexts[0] : this;
- return handlebarsGet(context, property, fn);
- });
- })();
- (function() {
- /*jshint debug:true*/
- /**
- @module ember
- @submodule ember-handlebars
- */
- var get = Ember.get, handlebarsGet = Ember.Handlebars.get, normalizePath = Ember.Handlebars.normalizePath;
- /**
- `log` allows you to output the value of a variable in the current rendering
- context.
- ```handlebars
- {{log myVariable}}
- ```
- @method log
- @for Ember.Handlebars.helpers
- @param {String} property
- */
- Ember.Handlebars.registerHelper('log', function logHelper(property, options) {
- var context = (options.contexts && options.contexts.length) ? options.contexts[0] : this,
- normalized = normalizePath(context, property, options.data),
- pathRoot = normalized.root,
- path = normalized.path,
- value = (path === 'this') ? pathRoot : handlebarsGet(pathRoot, path, options);
- Ember.Logger.log(value);
- });
- /**
- Execute the `debugger` statement in the current context.
- ```handlebars
- {{debugger}}
- ```
- Before invoking the `debugger` statement, there
- are a few helpful variables defined in the
- body of this helper that you can inspect while
- debugging that describe how and where this
- helper was invoked:
- - templateContext: this is most likely a controller
- from which this template looks up / displays properties
- - typeOfTemplateContext: a string description of
- what the templateContext is
- For example, if you're wondering why a value `{{foo}}`
- isn't rendering as expected within a template, you
- could place a `{{debugger}}` statement, and when
- the `debugger;` breakpoint is hit, you can inspect
- `templateContext`, determine if it's the object you
- expect, and/or evaluate expressions in the console
- to perform property lookups on the `templateContext`:
- ```
- > templateContext.get('foo') // -> "<value of {{foo}}>"
- ```
- @method debugger
- @for Ember.Handlebars.helpers
- @param {String} property
- */
- Ember.Handlebars.registerHelper('debugger', function debuggerHelper(options) {
- // These are helpful values you can inspect while debugging.
- var templateContext = this;
- var typeOfTemplateContext = Ember.inspect(templateContext);
- debugger;
- });
- })();
- (function() {
- /**
- @module ember
- @submodule ember-handlebars
- */
- var get = Ember.get, set = Ember.set;
- var fmt = Ember.String.fmt;
- Ember.Handlebars.EachView = Ember.CollectionView.extend(Ember._Metamorph, {
- init: function() {
- var itemController = get(this, 'itemController');
- var binding;
- if (itemController) {
- var controller = get(this, 'controller.container').lookupFactory('controller:array').create({
- _isVirtual: true,
- parentController: get(this, 'controller'),
- itemController: itemController,
- target: get(this, 'controller'),
- _eachView: this
- });
- this.disableContentObservers(function() {
- set(this, 'content', controller);
- binding = new Ember.Binding('content', '_eachView.dataSource').oneWay();
- binding.connect(controller);
- });
- set(this, '_arrayController', controller);
- } else {
- this.disableContentObservers(function() {
- binding = new Ember.Binding('content', 'dataSource').oneWay();
- binding.connect(this);
- });
- }
- return this._super();
- },
- _assertArrayLike: function(content) {
- Ember.assert(fmt("The value that #each loops over must be an Array. You " +
- "passed %@, but it should have been an ArrayController",
- [content.constructor]),
- !Ember.ControllerMixin.detect(content) ||
- (content && content.isGenerated) ||
- content instanceof Ember.ArrayController);
- Ember.assert(fmt("The value that #each loops over must be an Array. You passed %@", [(Ember.ControllerMixin.detect(content) && content.get('model') !== undefined) ? fmt("'%@' (wrapped in %@)", [content.get('model'), content]) : content]), Ember.Array.detect(content));
- },
- disableContentObservers: function(callback) {
- Ember.removeBeforeObserver(this, 'content', null, '_contentWillChange');
- Ember.removeObserver(this, 'content', null, '_contentDidChange');
- callback.call(this);
- Ember.addBeforeObserver(this, 'content', null, '_contentWillChange');
- Ember.addObserver(this, 'content', null, '_contentDidChange');
- },
- itemViewClass: Ember._MetamorphView,
- emptyViewClass: Ember._MetamorphView,
- createChildView: function(view, attrs) {
- view = this._super(view, attrs);
- // At the moment, if a container view subclass wants
- // to insert keywords, it is responsible for cloning
- // the keywords hash. This will be fixed momentarily.
- var keyword = get(this, 'keyword');
- var content = get(view, 'content');
- if (keyword) {
- var data = get(view, 'templateData');
- data = Ember.copy(data);
- data.keywords = view.cloneKeywords();
- set(view, 'templateData', data);
- // In this case, we do not bind, because the `content` of
- // a #each item cannot change.
- data.keywords[keyword] = content;
- }
- // If {{#each}} is looping over an array of controllers,
- // point each child view at their respective controller.
- if (content && get(content, 'isController')) {
- set(view, 'controller', content);
- }
- return view;
- },
- destroy: function() {
- if (!this._super()) { return; }
- var arrayController = get(this, '_arrayController');
- if (arrayController) {
- arrayController.destroy();
- }
- return this;
- }
- });
- var GroupedEach = Ember.Handlebars.GroupedEach = function(context, path, options) {
- var self = this,
- normalized = Ember.Handlebars.normalizePath(context, path, options.data);
- this.context = context;
- this.path = path;
- this.options = options;
- this.template = options.fn;
- this.containingView = options.data.view;
- this.normalizedRoot = normalized.root;
- this.normalizedPath = normalized.path;
- this.content = this.lookupContent();
- this.addContentObservers();
- this.addArrayObservers();
- this.containingView.on('willClearRender', function() {
- self.destroy();
- });
- };
- GroupedEach.prototype = {
- contentWillChange: function() {
- this.removeArrayObservers();
- },
- contentDidChange: function() {
- this.content = this.lookupContent();
- this.addArrayObservers();
- this.rerenderContainingView();
- },
- contentArrayWillChange: Ember.K,
- contentArrayDidChange: function() {
- this.rerenderContainingView();
- },
- lookupContent: function() {
- return Ember.Handlebars.get(this.normalizedRoot, this.normalizedPath, this.options);
- },
- addArrayObservers: function() {
- if (!this.content) { return; }
- this.content.addArrayObserver(this, {
- willChange: 'contentArrayWillChange',
- didChange: 'contentArrayDidChange'
- });
- },
- removeArrayObservers: function() {
- if (!this.content) { return; }
- this.content.removeArrayObserver(this, {
- willChange: 'contentArrayWillChange',
- didChange: 'contentArrayDidChange'
- });
- },
- addContentObservers: function() {
- Ember.addBeforeObserver(this.normalizedRoot, this.normalizedPath, this, this.contentWillChange);
- Ember.addObserver(this.normalizedRoot, this.normalizedPath, this, this.contentDidChange);
- },
- removeContentObservers: function() {
- Ember.removeBeforeObserver(this.normalizedRoot, this.normalizedPath, this.contentWillChange);
- Ember.removeObserver(this.normalizedRoot, this.normalizedPath, this.contentDidChange);
- },
- render: function() {
- if (!this.content) { return; }
- var content = this.content,
- contentLength = get(content, 'length'),
- data = this.options.data,
- template = this.template;
- data.insideEach = true;
- for (var i = 0; i < contentLength; i++) {
- template(content.objectAt(i), { data: data });
- }
- },
- rerenderContainingView: function() {
- var self = this;
- Ember.run.scheduleOnce('render', this, function() {
- // It's possible it's been destroyed after we enqueued a re-render call.
- if (!self.destroyed) {
- self.containingView.rerender();
- }
- });
- },
- destroy: function() {
- this.removeContentObservers();
- if (this.content) {
- this.removeArrayObservers();
- }
- this.destroyed = true;
- }
- };
- /**
- The `{{#each}}` helper loops over elements in a collection, rendering its
- block once for each item. It is an extension of the base Handlebars `{{#each}}`
- helper:
- ```javascript
- Developers = [{name: 'Yehuda'},{name: 'Tom'}, {name: 'Paul'}];
- ```
- ```handlebars
- {{#each Developers}}
- {{name}}
- {{/each}}
- ```
- `{{each}}` supports an alternative syntax with element naming:
- ```handlebars
- {{#each person in Developers}}
- {{person.name}}
- {{/each}}
- ```
- When looping over objects that do not have properties, `{{this}}` can be used
- to render the object:
- ```javascript
- DeveloperNames = ['Yehuda', 'Tom', 'Paul']
- ```
- ```handlebars
- {{#each DeveloperNames}}
- {{this}}
- {{/each}}
- ```
- ### {{else}} condition
- `{{#each}}` can have a matching `{{else}}`. The contents of this block will render
- if the collection is empty.
- ```
- {{#each person in Developers}}
- {{person.name}}
- {{else}}
- <p>Sorry, nobody is available for this task.</p>
- {{/each}}
- ```
- ### Specifying a View class for items
- If you provide an `itemViewClass` option that references a view class
- with its own `template` you can omit the block.
- The following template:
- ```handlebars
- {{#view App.MyView }}
- {{each view.items itemViewClass="App.AnItemView"}}
- {{/view}}
- ```
- And application code
- ```javascript
- App = Ember.Application.create({
- MyView: Ember.View.extend({
- items: [
- Ember.Object.create({name: 'Dave'}),
- Ember.Object.create({name: 'Mary'}),
- Ember.Object.create({name: 'Sara'})
- ]
- })
- });
- App.AnItemView = Ember.View.extend({
- template: Ember.Handlebars.compile("Greetings {{name}}")
- });
- ```
- Will result in the HTML structure below
- ```html
- <div class="ember-view">
- <div class="ember-view">Greetings Dave</div>
- <div class="ember-view">Greetings Mary</div>
- <div class="ember-view">Greetings Sara</div>
- </div>
- ```
- If an `itemViewClass` is defined on the helper, and therefore the helper is not
- being used as a block, an `emptyViewClass` can also be provided optionally.
- The `emptyViewClass` will match the behavior of the `{{else}}` condition
- described above. That is, the `emptyViewClass` will render if the collection
- is empty.
- ### Representing each item with a Controller.
- By default the controller lookup within an `{{#each}}` block will be
- the controller of the template where the `{{#each}}` was used. If each
- item needs to be presented by a custom controller you can provide a
- `itemController` option which references a controller by lookup name.
- Each item in the loop will be wrapped in an instance of this controller
- and the item itself will be set to the `content` property of that controller.
- This is useful in cases where properties of model objects need transformation
- or synthesis for display:
- ```javascript
- App.DeveloperController = Ember.ObjectController.extend({
- isAvailableForHire: function() {
- return !this.get('content.isEmployed') && this.get('content.isSeekingWork');
- }.property('isEmployed', 'isSeekingWork')
- })
- ```
- ```handlebars
- {{#each person in developers itemController="developer"}}
- {{person.name}} {{#if person.isAvailableForHire}}Hire me!{{/if}}
- {{/each}}
- ```
- Each itemController will receive a reference to the current controller as
- a `parentController` property.
- ### (Experimental) Grouped Each
- When used in conjunction with the experimental [group helper](https://github.com/emberjs/group-helper),
- you can inform Handlebars to re-render an entire group of items instead of
- re-rendering them one at a time (in the event that they are changed en masse
- or an item is added/removed).
- ```handlebars
- {{#group}}
- {{#each people}}
- {{firstName}} {{lastName}}
- {{/each}}
- {{/group}}
- ```
- This can be faster than the normal way that Handlebars re-renders items
- in some cases.
- If for some reason you have a group with more than one `#each`, you can make
- one of the collections be updated in normal (non-grouped) fashion by setting
- the option `groupedRows=true` (counter-intuitive, I know).
- For example,
- ```handlebars
- {{dealershipName}}
- {{#group}}
- {{#each dealers}}
- {{firstName}} {{lastName}}
- {{/each}}
- {{#each car in cars groupedRows=true}}
- {{car.make}} {{car.model}} {{car.color}}
- {{/each}}
- {{/group}}
- ```
- Any change to `dealershipName` or the `dealers` collection will cause the
- entire group to be re-rendered. However, changes to the `cars` collection
- will be re-rendered individually (as normal).
- Note that `group` behavior is also disabled by specifying an `itemViewClass`.
- @method each
- @for Ember.Handlebars.helpers
- @param [name] {String} name for item (used with `in`)
- @param [path] {String} path
- @param [options] {Object} Handlebars key/value pairs of options
- @param [options.itemViewClass] {String} a path to a view class used for each item
- @param [options.itemController] {String} name of a controller to be created for each item
- @param [options.groupedRows] {boolean} enable normal item-by-item rendering when inside a `#group` helper
- */
- Ember.Handlebars.registerHelper('each', function eachHelper(path, options) {
- if (arguments.length === 4) {
- Ember.assert("If you pass more than one argument to the each helper, it must be in the form #each foo in bar", arguments[1] === "in");
- var keywordName = arguments[0];
- options = arguments[3];
- path = arguments[2];
- if (path === '') { path = "this"; }
- options.hash.keyword = keywordName;
- }
- if (arguments.length === 1) {
- options = path;
- path = 'this';
- }
- options.hash.dataSourceBinding = path;
- // Set up emptyView as a metamorph with no tag
- //options.hash.emptyViewClass = Ember._MetamorphView;
- if (options.data.insideGroup && !options.hash.groupedRows && !options.hash.itemViewClass) {
- new Ember.Handlebars.GroupedEach(this, path, options).render();
- } else {
- return Ember.Handlebars.helpers.collection.call(this, 'Ember.Handlebars.EachView', options);
- }
- });
- })();
- (function() {
- /**
- @module ember
- @submodule ember-handlebars
- */
- /**
- `template` allows you to render a template from inside another template.
- This allows you to re-use the same template in multiple places. For example:
- ```html
- <script type="text/x-handlebars" data-template-name="logged_in_user">
- {{#with loggedInUser}}
- Last Login: {{lastLogin}}
- User Info: {{template "user_info"}}
- {{/with}}
- </script>
- ```
- ```html
- <script type="text/x-handlebars" data-template-name="user_info">
- Name: <em>{{name}}</em>
- Karma: <em>{{karma}}</em>
- </script>
- ```
- ```handlebars
- {{#if isUser}}
- {{template "user_info"}}
- {{else}}
- {{template "unlogged_user_info"}}
- {{/if}}
- ```
- This helper looks for templates in the global `Ember.TEMPLATES` hash. If you
- add `<script>` tags to your page with the `data-template-name` attribute set,
- they will be compiled and placed in this hash automatically.
- You can also manually register templates by adding them to the hash:
- ```javascript
- Ember.TEMPLATES["my_cool_template"] = Ember.Handlebars.compile('<b>{{user}}</b>');
- ```
- @deprecated
- @method template
- @for Ember.Handlebars.helpers
- @param {String} templateName the template to render
- */
- Ember.Handlebars.registerHelper('template', function(name, options) {
- Ember.deprecate("The `template` helper has been deprecated in favor of the `partial` helper. Please use `partial` instead, which will work the same way.");
- return Ember.Handlebars.helpers.partial.apply(this, arguments);
- });
- })();
- (function() {
- /**
- @module ember
- @submodule ember-handlebars
- */
- /**
- The `partial` helper renders another template without
- changing the template context:
- ```handlebars
- {{foo}}
- {{partial "nav"}}
- ```
- The above example template will render a template named
- "_nav", which has the same context as the parent template
- it's rendered into, so if the "_nav" template also referenced
- `{{foo}}`, it would print the same thing as the `{{foo}}`
- in the above example.
- If a "_nav" template isn't found, the `partial` helper will
- fall back to a template named "nav".
- ## Bound template names
- The parameter supplied to `partial` can also be a path
- to a property containing a template name, e.g.:
- ```handlebars
- {{partial someTemplateName}}
- ```
- The above example will look up the value of `someTemplateName`
- on the template context (e.g. a controller) and use that
- value as the name of the template to render. If the resolved
- value is falsy, nothing will be rendered. If `someTemplateName`
- changes, the partial will be re-rendered using the new template
- name.
- ## Setting the partial's context with `with`
- The `partial` helper can be used in conjunction with the `with`
- helper to set a context that will be used by the partial:
- ```handlebars
- {{#with currentUser}}
- {{partial "user_info"}}
- {{/with}}
- ```
- @method partial
- @for Ember.Handlebars.helpers
- @param {String} partialName the name of the template to render minus the leading underscore
- */
- Ember.Handlebars.registerHelper('partial', function partialHelper(name, options) {
- var context = (options.contexts && options.contexts.length) ? options.contexts[0] : this;
- if (options.types[0] === "ID") {
- // Helper was passed a property path; we need to
- // create a binding that will re-render whenever
- // this property changes.
- options.fn = function(context, fnOptions) {
- var partialName = Ember.Handlebars.get(context, name, fnOptions);
- renderPartial(context, partialName, fnOptions);
- };
- return Ember.Handlebars.bind.call(context, name, options, true, exists);
- } else {
- // Render the partial right into parent template.
- renderPartial(context, name, options);
- }
- });
- function exists(value) {
- return !Ember.isNone(value);
- }
- function renderPartial(context, name, options) {
- var nameParts = name.split("/"),
- lastPart = nameParts[nameParts.length - 1];
- nameParts[nameParts.length - 1] = "_" + lastPart;
- var view = options.data.view,
- underscoredName = nameParts.join("/"),
- template = view.templateForName(underscoredName),
- deprecatedTemplate = !template && view.templateForName(name);
- Ember.assert("Unable to find partial with name '"+name+"'.", template || deprecatedTemplate);
- template = template || deprecatedTemplate;
- template(context, { data: options.data });
- }
- })();
- (function() {
- /**
- @module ember
- @submodule ember-handlebars
- */
- var get = Ember.get, set = Ember.set;
- /**
- `{{yield}}` denotes an area of a template that will be rendered inside
- of another template. It has two main uses:
- ### Use with `layout`
- When used in a Handlebars template that is assigned to an `Ember.View`
- instance's `layout` property Ember will render the layout template first,
- inserting the view's own rendered output at the `{{yield}}` location.
- An empty `<body>` and the following application code:
- ```javascript
- AView = Ember.View.extend({
- classNames: ['a-view-with-layout'],
- layout: Ember.Handlebars.compile('<div class="wrapper">{{yield}}</div>'),
- template: Ember.Handlebars.compile('<span>I am wrapped</span>')
- });
- aView = AView.create();
- aView.appendTo('body');
- ```
- Will result in the following HTML output:
- ```html
- <body>
- <div class='ember-view a-view-with-layout'>
- <div class="wrapper">
- <span>I am wrapped</span>
- </div>
- </div>
- </body>
- ```
- The `yield` helper cannot be used outside of a template assigned to an
- `Ember.View`'s `layout` property and will throw an error if attempted.
- ```javascript
- BView = Ember.View.extend({
- classNames: ['a-view-with-layout'],
- template: Ember.Handlebars.compile('{{yield}}')
- });
- bView = BView.create();
- bView.appendTo('body');
- // throws
- // Uncaught Error: assertion failed:
- // You called yield in a template that was not a layout
- ```
- ### Use with Ember.Component
- When designing components `{{yield}}` is used to denote where, inside the component's
- template, an optional block passed to the component should render:
- ```handlebars
- <!-- application.hbs -->
- {{#labeled-textfield value=someProperty}}
- First name:
- {{/labeled-textfield}}
- ```
- ```handlebars
- <!-- components/labeled-textfield.hbs -->
- <label>
- {{yield}} {{input value=value}}
- </label>
- ```
- Result:
- ```html
- <label>
- First name: <input type="text" />
- <label>
- ```
- @method yield
- @for Ember.Handlebars.helpers
- @param {Hash} options
- @return {String} HTML string
- */
- Ember.Handlebars.registerHelper('yield', function yieldHelper(options) {
- var view = options.data.view;
- while (view && !get(view, 'layout')) {
- if (view._contextView) {
- view = view._contextView;
- } else {
- view = get(view, 'parentView');
- }
- }
- Ember.assert("You called yield in a template that was not a layout", !!view);
- view._yield(this, options);
- });
- })();
- (function() {
- /**
- @module ember
- @submodule ember-handlebars
- */
- /**
- `loc` looks up the string in the localized strings hash.
- This is a convenient way to localize text. For example:
- ```html
- <script type="text/x-handlebars" data-template-name="home">
- {{loc "welcome"}}
- </script>
- ```
- Take note that `"welcome"` is a string and not an object
- reference.
- @method loc
- @for Ember.Handlebars.helpers
- @param {String} str The string to format
- */
- Ember.Handlebars.registerHelper('loc', function locHelper(str) {
- return Ember.String.loc(str);
- });
- })();
- (function() {
- })();
- (function() {
- })();
- (function() {
- /**
- @module ember
- @submodule ember-handlebars
- */
- var set = Ember.set, get = Ember.get;
- /**
- The internal class used to create text inputs when the `{{input}}`
- helper is used with `type` of `checkbox`.
- See [handlebars.helpers.input](/api/classes/Ember.Handlebars.helpers.html#method_input) for usage details.
- ## Direct manipulation of `checked`
- The `checked` attribute of an `Ember.Checkbox` object should always be set
- through the Ember object or by interacting with its rendered element
- representation via the mouse, keyboard, or touch. Updating the value of the
- checkbox via jQuery will result in the checked value of the object and its
- element losing synchronization.
- ## Layout and LayoutName properties
- Because HTML `input` elements are self closing `layout` and `layoutName`
- properties will not be applied. See [Ember.View](/api/classes/Ember.View.html)'s
- layout section for more information.
- @class Checkbox
- @namespace Ember
- @extends Ember.View
- */
- Ember.Checkbox = Ember.View.extend({
- classNames: ['ember-checkbox'],
- tagName: 'input',
- attributeBindings: ['type', 'checked', 'indeterminate', 'disabled', 'tabindex', 'name'],
- type: "checkbox",
- checked: false,
- disabled: false,
- indeterminate: false,
- init: function() {
- this._super();
- this.on("change", this, this._updateElementValue);
- },
- didInsertElement: function() {
- this._super();
- this.get('element').indeterminate = !!this.get('indeterminate');
- },
- _updateElementValue: function() {
- set(this, 'checked', this.$().prop('checked'));
- }
- });
- })();
- (function() {
- /**
- @module ember
- @submodule ember-handlebars
- */
- var get = Ember.get, set = Ember.set;
- /**
- Shared mixin used by `Ember.TextField` and `Ember.TextArea`.
- @class TextSupport
- @namespace Ember
- @private
- */
- Ember.TextSupport = Ember.Mixin.create({
- value: "",
- attributeBindings: ['placeholder', 'disabled', 'maxlength', 'tabindex', 'readonly'],
- placeholder: null,
- disabled: false,
- maxlength: null,
- init: function() {
- this._super();
- this.on("focusOut", this, this._elementValueDidChange);
- this.on("change", this, this._elementValueDidChange);
- this.on("paste", this, this._elementValueDidChange);
- this.on("cut", this, this._elementValueDidChange);
- this.on("input", this, this._elementValueDidChange);
- this.on("keyUp", this, this.interpretKeyEvents);
- },
- /**
- The action to be sent when the user presses the return key.
- This is similar to the `{{action}}` helper, but is fired when
- the user presses the return key when editing a text field, and sends
- the value of the field as the context.
- @property action
- @type String
- @default null
- */
- action: null,
- /**
- The event that should send the action.
- Options are:
- * `enter`: the user pressed enter
- * `keyPress`: the user pressed a key
- @property onEvent
- @type String
- @default enter
- */
- onEvent: 'enter',
- /**
- Whether they `keyUp` event that triggers an `action` to be sent continues
- propagating to other views.
- By default, when the user presses the return key on their keyboard and
- the text field has an `action` set, the action will be sent to the view's
- controller and the key event will stop propagating.
- If you would like parent views to receive the `keyUp` event even after an
- action has been dispatched, set `bubbles` to true.
- @property bubbles
- @type Boolean
- @default false
- */
- bubbles: false,
- interpretKeyEvents: function(event) {
- var map = Ember.TextSupport.KEY_EVENTS;
- var method = map[event.keyCode];
- this._elementValueDidChange();
- if (method) { return this[method](event); }
- },
- _elementValueDidChange: function() {
- set(this, 'value', this.$().val());
- },
- /**
- The action to be sent when the user inserts a new line.
- Called by the `Ember.TextSupport` mixin on keyUp if keycode matches 13.
- Uses sendAction to send the `enter` action to the controller.
- @method insertNewline
- @param {Event} event
- */
- insertNewline: function(event) {
- sendAction('enter', this, event);
- sendAction('insert-newline', this, event);
- },
- /**
- Called when the user hits escape.
- Called by the `Ember.TextSupport` mixin on keyUp if keycode matches 27.
- Uses sendAction to send the `escape-press` action to the controller.
- @method cancel
- @param {Event} event
- */
- cancel: function(event) {
- sendAction('escape-press', this, event);
- },
- /**
- Called when the text area is focused.
- @method focusIn
- @param {Event} event
- */
- focusIn: function(event) {
- sendAction('focus-in', this, event);
- },
- /**
- Called when the text area is blurred.
- @method focusOut
- @param {Event} event
- */
- focusOut: function(event) {
- sendAction('focus-out', this, event);
- },
- /**
- The action to be sent when the user presses a key. Enabled by setting
- the `onEvent` property to `keyPress`.
- Uses sendAction to send the `keyPress` action to the controller.
- @method keyPress
- @param {Event} event
- */
- keyPress: function(event) {
- sendAction('key-press', this, event);
- }
- });
- Ember.TextSupport.KEY_EVENTS = {
- 13: 'insertNewline',
- 27: 'cancel'
- };
- // In principle, this shouldn't be necessary, but the legacy
- // sectionAction semantics for TextField are different from
- // the component semantics so this method normalizes them.
- function sendAction(eventName, view, event) {
- var action = get(view, eventName),
- on = get(view, 'onEvent'),
- value = get(view, 'value');
- // back-compat support for keyPress as an event name even though
- // it's also a method name that consumes the event (and therefore
- // incompatible with sendAction semantics).
- if (on === eventName || (on === 'keyPress' && eventName === 'key-press')) {
- view.sendAction('action', value);
- }
- view.sendAction(eventName, value);
- if (action || on === eventName) {
- if(!get(view, 'bubbles')) {
- event.stopPropagation();
- }
- }
- }
- })();
- (function() {
- /**
- @module ember
- @submodule ember-handlebars
- */
- var get = Ember.get, set = Ember.set;
- /**
- The internal class used to create text inputs when the `{{input}}`
- helper is used with `type` of `text`.
- See [Handlebars.helpers.input](/api/classes/Ember.Handlebars.helpers.html#method_input) for usage details.
- ## Layout and LayoutName properties
- Because HTML `input` elements are self closing `layout` and `layoutName`
- properties will not be applied. See [Ember.View](/api/classes/Ember.View.html)'s
- layout section for more information.
- @class TextField
- @namespace Ember
- @extends Ember.Component
- @uses Ember.TextSupport
- */
- Ember.TextField = Ember.Component.extend(Ember.TextSupport, {
- classNames: ['ember-text-field'],
- tagName: "input",
- attributeBindings: ['type', 'value', 'size', 'pattern', 'name', 'min', 'max'],
- /**
- The `value` attribute of the input element. As the user inputs text, this
- property is updated live.
- @property value
- @type String
- @default ""
- */
- value: "",
- /**
- The `type` attribute of the input element.
- @property type
- @type String
- @default "text"
- */
- type: "text",
- /**
- The `size` of the text field in characters.
- @property size
- @type String
- @default null
- */
- size: null,
- /**
- The `pattern` attribute of input element.
- @property pattern
- @type String
- @default null
- */
- pattern: null,
- /**
- The `min` attribute of input element used with `type="number"` or `type="range"`.
- @property min
- @type String
- @default null
- */
- min: null,
- /**
- The `max` attribute of input element used with `type="number"` or `type="range"`.
- @property max
- @type String
- @default null
- */
- max: null
- });
- })();
- (function() {
- /**
- @module ember
- @submodule ember-handlebars
- */
- var get = Ember.get, set = Ember.set;
- /**
- The internal class used to create textarea element when the `{{textarea}}`
- helper is used.
- See [handlebars.helpers.textarea](/api/classes/Ember.Handlebars.helpers.html#method_textarea) for usage details.
- ## Layout and LayoutName properties
- Because HTML `textarea` elements do not contain inner HTML the `layout` and
- `layoutName` properties will not be applied. See [Ember.View](/api/classes/Ember.View.html)'s
- layout section for more information.
- @class TextArea
- @namespace Ember
- @extends Ember.Component
- @uses Ember.TextSupport
- */
- Ember.TextArea = Ember.Component.extend(Ember.TextSupport, {
- classNames: ['ember-text-area'],
- tagName: "textarea",
- attributeBindings: ['rows', 'cols', 'name'],
- rows: null,
- cols: null,
- _updateElementValue: Ember.observer('value', function() {
- // We do this check so cursor position doesn't get affected in IE
- var value = get(this, 'value'),
- $el = this.$();
- if ($el && value !== $el.val()) {
- $el.val(value);
- }
- }),
- init: function() {
- this._super();
- this.on("didInsertElement", this, this._updateElementValue);
- }
- });
- })();
- (function() {
- /*jshint eqeqeq:false */
- /**
- @module ember
- @submodule ember-handlebars
- */
- var set = Ember.set,
- get = Ember.get,
- indexOf = Ember.EnumerableUtils.indexOf,
- indexesOf = Ember.EnumerableUtils.indexesOf,
- forEach = Ember.EnumerableUtils.forEach,
- replace = Ember.EnumerableUtils.replace,
- isArray = Ember.isArray,
- precompileTemplate = Ember.Handlebars.compile;
- Ember.SelectOption = Ember.View.extend({
- tagName: 'option',
- attributeBindings: ['value', 'selected'],
- defaultTemplate: function(context, options) {
- options = { data: options.data, hash: {} };
- Ember.Handlebars.helpers.bind.call(context, "view.label", options);
- },
- init: function() {
- this.labelPathDidChange();
- this.valuePathDidChange();
- this._super();
- },
- selected: Ember.computed(function() {
- var content = get(this, 'content'),
- selection = get(this, 'parentView.selection');
- if (get(this, 'parentView.multiple')) {
- return selection && indexOf(selection, content.valueOf()) > -1;
- } else {
- // Primitives get passed through bindings as objects... since
- // `new Number(4) !== 4`, we use `==` below
- return content == selection;
- }
- }).property('content', 'parentView.selection'),
- labelPathDidChange: Ember.observer('parentView.optionLabelPath', function() {
- var labelPath = get(this, 'parentView.optionLabelPath');
- if (!labelPath) { return; }
- Ember.defineProperty(this, 'label', Ember.computed(function() {
- return get(this, labelPath);
- }).property(labelPath));
- }),
- valuePathDidChange: Ember.observer('parentView.optionValuePath', function() {
- var valuePath = get(this, 'parentView.optionValuePath');
- if (!valuePath) { return; }
- Ember.defineProperty(this, 'value', Ember.computed(function() {
- return get(this, valuePath);
- }).property(valuePath));
- })
- });
- Ember.SelectOptgroup = Ember.CollectionView.extend({
- tagName: 'optgroup',
- attributeBindings: ['label'],
- selectionBinding: 'parentView.selection',
- multipleBinding: 'parentView.multiple',
- optionLabelPathBinding: 'parentView.optionLabelPath',
- optionValuePathBinding: 'parentView.optionValuePath',
- itemViewClassBinding: 'parentView.optionView'
- });
- /**
- The `Ember.Select` view class renders a
- [select](https://developer.mozilla.org/en/HTML/Element/select) HTML element,
- allowing the user to choose from a list of options.
- The text and `value` property of each `<option>` element within the
- `<select>` element are populated from the objects in the `Element.Select`'s
- `content` property. The underlying data object of the selected `<option>` is
- stored in the `Element.Select`'s `value` property.
- ## The Content Property (array of strings)
- The simplest version of an `Ember.Select` takes an array of strings as its
- `content` property. The string will be used as both the `value` property and
- the inner text of each `<option>` element inside the rendered `<select>`.
- Example:
- ```javascript
- App.ApplicationController = Ember.Controller.extend({
- names: ["Yehuda", "Tom"]
- });
- ```
- ```handlebars
- {{view Ember.Select content=names}}
- ```
- Would result in the following HTML:
- ```html
- <select class="ember-select">
- <option value="Yehuda">Yehuda</option>
- <option value="Tom">Tom</option>
- </select>
- ```
- You can control which `<option>` is selected through the `Ember.Select`'s
- `value` property:
- ```javascript
- App.ApplicationController = Ember.Controller.extend({
- selectedName: 'Tom',
- names: ["Yehuda", "Tom"]
- });
- ```
- ```handlebars
- {{view Ember.Select
- content=names
- value=selectedName
- }}
- ```
- Would result in the following HTML with the `<option>` for 'Tom' selected:
- ```html
- <select class="ember-select">
- <option value="Yehuda">Yehuda</option>
- <option value="Tom" selected="selected">Tom</option>
- </select>
- ```
- A user interacting with the rendered `<select>` to choose "Yehuda" would
- update the value of `selectedName` to "Yehuda".
- ## The Content Property (array of Objects)
- An `Ember.Select` can also take an array of JavaScript or Ember objects as
- its `content` property.
- When using objects you need to tell the `Ember.Select` which property should
- be accessed on each object to supply the `value` attribute of the `<option>`
- and which property should be used to supply the element text.
- The `optionValuePath` option is used to specify the path on each object to
- the desired property for the `value` attribute. The `optionLabelPath`
- specifies the path on each object to the desired property for the
- element's text. Both paths must reference each object itself as `content`:
- ```javascript
- App.ApplicationController = Ember.Controller.extend({
- programmers: [
- {firstName: "Yehuda", id: 1},
- {firstName: "Tom", id: 2}
- ]
- });
- ```
- ```handlebars
- {{view Ember.Select
- content=programmers
- optionValuePath="content.id"
- optionLabelPath="content.firstName"}}
- ```
- Would result in the following HTML:
- ```html
- <select class="ember-select">
- <option value="1">Yehuda</option>
- <option value="2">Tom</option>
- </select>
- ```
- The `value` attribute of the selected `<option>` within an `Ember.Select`
- can be bound to a property on another object:
- ```javascript
- App.ApplicationController = Ember.Controller.extend({
- programmers: [
- {firstName: "Yehuda", id: 1},
- {firstName: "Tom", id: 2}
- ],
- currentProgrammer: {
- id: 2
- }
- });
- ```
- ```handlebars
- {{view Ember.Select
- content=programmers
- optionValuePath="content.id"
- optionLabelPath="content.firstName"
- value=currentProgrammer.id}}
- ```
- Would result in the following HTML with a selected option:
- ```html
- <select class="ember-select">
- <option value="1">Yehuda</option>
- <option value="2" selected="selected">Tom</option>
- </select>
- ```
- Interacting with the rendered element by selecting the first option
- ('Yehuda') will update the `id` of `currentProgrammer`
- to match the `value` property of the newly selected `<option>`.
- Alternatively, you can control selection through the underlying objects
- used to render each object by binding the `selection` option. When the selected
- `<option>` is changed, the property path provided to `selection`
- will be updated to match the content object of the rendered `<option>`
- element:
- ```javascript
- App.ApplicationController = Ember.Controller.extend({
- selectedPerson: null,
- programmers: [
- {firstName: "Yehuda", id: 1},
- {firstName: "Tom", id: 2}
- ]
- });
- ```
- ```handlebars
- {{view Ember.Select
- content=programmers
- optionValuePath="content.id"
- optionLabelPath="content.firstName"
- selection=selectedPerson}}
- ```
- Would result in the following HTML with a selected option:
- ```html
- <select class="ember-select">
- <option value="1">Yehuda</option>
- <option value="2" selected="selected">Tom</option>
- </select>
- ```
- Interacting with the rendered element by selecting the first option
- ('Yehuda') will update the `selectedPerson` to match the object of
- the newly selected `<option>`. In this case it is the first object
- in the `programmers`
- ## Supplying a Prompt
- A `null` value for the `Ember.Select`'s `value` or `selection` property
- results in there being no `<option>` with a `selected` attribute:
- ```javascript
- App.ApplicationController = Ember.Controller.extend({
- selectedProgrammer: null,
- programmers: [
- "Yehuda",
- "Tom"
- ]
- });
- ```
- ``` handlebars
- {{view Ember.Select
- content=programmers
- value=selectedProgrammer
- }}
- ```
- Would result in the following HTML:
- ```html
- <select class="ember-select">
- <option value="Yehuda">Yehuda</option>
- <option value="Tom">Tom</option>
- </select>
- ```
- Although `selectedProgrammer` is `null` and no `<option>`
- has a `selected` attribute the rendered HTML will display the
- first item as though it were selected. You can supply a string
- value for the `Ember.Select` to display when there is no selection
- with the `prompt` option:
- ```javascript
- App.ApplicationController = Ember.Controller.extend({
- selectedProgrammer: null,
- programmers: [
- "Yehuda",
- "Tom"
- ]
- });
- ```
- ```handlebars
- {{view Ember.Select
- content=programmers
- value=selectedProgrammer
- prompt="Please select a name"
- }}
- ```
- Would result in the following HTML:
- ```html
- <select class="ember-select">
- <option>Please select a name</option>
- <option value="Yehuda">Yehuda</option>
- <option value="Tom">Tom</option>
- </select>
- ```
- @class Select
- @namespace Ember
- @extends Ember.View
- */
- Ember.Select = Ember.View.extend({
- tagName: 'select',
- classNames: ['ember-select'],
- defaultTemplate: Ember.Handlebars.template(function anonymous(Handlebars,depth0,helpers,partials,data) {
- this.compilerInfo = [4,'>= 1.0.0'];
- helpers = this.merge(helpers, Ember.Handlebars.helpers); data = data || {};
- var buffer = '', stack1, escapeExpression=this.escapeExpression, self=this;
- function program1(depth0,data) {
-
- var buffer = '', stack1;
- data.buffer.push("<option value=\"\">");
- stack1 = helpers._triageMustache.call(depth0, "view.prompt", {hash:{},hashTypes:{},hashContexts:{},contexts:[depth0],types:["ID"],data:data});
- if(stack1 || stack1 === 0) { data.buffer.push(stack1); }
- data.buffer.push("</option>");
- return buffer;
- }
- function program3(depth0,data) {
-
- var stack1;
- stack1 = helpers.each.call(depth0, "view.groupedContent", {hash:{},hashTypes:{},hashContexts:{},inverse:self.noop,fn:self.program(4, program4, data),contexts:[depth0],types:["ID"],data:data});
- if(stack1 || stack1 === 0) { data.buffer.push(stack1); }
- else { data.buffer.push(''); }
- }
- function program4(depth0,data) {
-
-
- data.buffer.push(escapeExpression(helpers.view.call(depth0, "view.groupView", {hash:{
- 'content': ("content"),
- 'label': ("label")
- },hashTypes:{'content': "ID",'label': "ID"},hashContexts:{'content': depth0,'label': depth0},contexts:[depth0],types:["ID"],data:data})));
- }
- function program6(depth0,data) {
-
- var stack1;
- stack1 = helpers.each.call(depth0, "view.content", {hash:{},hashTypes:{},hashContexts:{},inverse:self.noop,fn:self.program(7, program7, data),contexts:[depth0],types:["ID"],data:data});
- if(stack1 || stack1 === 0) { data.buffer.push(stack1); }
- else { data.buffer.push(''); }
- }
- function program7(depth0,data) {
-
-
- data.buffer.push(escapeExpression(helpers.view.call(depth0, "view.optionView", {hash:{
- 'content': ("")
- },hashTypes:{'content': "ID"},hashContexts:{'content': depth0},contexts:[depth0],types:["ID"],data:data})));
- }
- stack1 = helpers['if'].call(depth0, "view.prompt", {hash:{},hashTypes:{},hashContexts:{},inverse:self.noop,fn:self.program(1, program1, data),contexts:[depth0],types:["ID"],data:data});
- if(stack1 || stack1 === 0) { data.buffer.push(stack1); }
- stack1 = helpers['if'].call(depth0, "view.optionGroupPath", {hash:{},hashTypes:{},hashContexts:{},inverse:self.program(6, program6, data),fn:self.program(3, program3, data),contexts:[depth0],types:["ID"],data:data});
- if(stack1 || stack1 === 0) { data.buffer.push(stack1); }
- return buffer;
-
- }),
- attributeBindings: ['multiple', 'disabled', 'tabindex', 'name'],
- /**
- The `multiple` attribute of the select element. Indicates whether multiple
- options can be selected.
- @property multiple
- @type Boolean
- @default false
- */
- multiple: false,
- /**
- The `disabled` attribute of the select element. Indicates whether
- the element is disabled from interactions.
- @property disabled
- @type Boolean
- @default false
- */
- disabled: false,
- /**
- The list of options.
- If `optionLabelPath` and `optionValuePath` are not overridden, this should
- be a list of strings, which will serve simultaneously as labels and values.
- Otherwise, this should be a list of objects. For instance:
- ```javascript
- Ember.Select.create({
- content: Ember.A([
- { id: 1, firstName: 'Yehuda' },
- { id: 2, firstName: 'Tom' }
- ]),
- optionLabelPath: 'content.firstName',
- optionValuePath: 'content.id'
- });
- ```
- @property content
- @type Array
- @default null
- */
- content: null,
- /**
- When `multiple` is `false`, the element of `content` that is currently
- selected, if any.
- When `multiple` is `true`, an array of such elements.
- @property selection
- @type Object or Array
- @default null
- */
- selection: null,
- /**
- In single selection mode (when `multiple` is `false`), value can be used to
- get the current selection's value or set the selection by it's value.
- It is not currently supported in multiple selection mode.
- @property value
- @type String
- @default null
- */
- value: Ember.computed(function(key, value) {
- if (arguments.length === 2) { return value; }
- var valuePath = get(this, 'optionValuePath').replace(/^content\.?/, '');
- return valuePath ? get(this, 'selection.' + valuePath) : get(this, 'selection');
- }).property('selection'),
- /**
- If given, a top-most dummy option will be rendered to serve as a user
- prompt.
- @property prompt
- @type String
- @default null
- */
- prompt: null,
- /**
- The path of the option labels. See [content](/api/classes/Ember.Select.html#property_content).
- @property optionLabelPath
- @type String
- @default 'content'
- */
- optionLabelPath: 'content',
- /**
- The path of the option values. See [content](/api/classes/Ember.Select.html#property_content).
- @property optionValuePath
- @type String
- @default 'content'
- */
- optionValuePath: 'content',
- /**
- The path of the option group.
- When this property is used, `content` should be sorted by `optionGroupPath`.
- @property optionGroupPath
- @type String
- @default null
- */
- optionGroupPath: null,
- /**
- The view class for optgroup.
- @property groupView
- @type Ember.View
- @default Ember.SelectOptgroup
- */
- groupView: Ember.SelectOptgroup,
- groupedContent: Ember.computed(function() {
- var groupPath = get(this, 'optionGroupPath');
- var groupedContent = Ember.A();
- var content = get(this, 'content') || [];
- forEach(content, function(item) {
- var label = get(item, groupPath);
- if (get(groupedContent, 'lastObject.label') !== label) {
- groupedContent.pushObject({
- label: label,
- content: Ember.A()
- });
- }
- get(groupedContent, 'lastObject.content').push(item);
- });
- return groupedContent;
- }).property('optionGroupPath', 'content.@each'),
- /**
- The view class for option.
- @property optionView
- @type Ember.View
- @default Ember.SelectOption
- */
- optionView: Ember.SelectOption,
- _change: function() {
- if (get(this, 'multiple')) {
- this._changeMultiple();
- } else {
- this._changeSingle();
- }
- },
- selectionDidChange: Ember.observer('selection.@each', function() {
- var selection = get(this, 'selection');
- if (get(this, 'multiple')) {
- if (!isArray(selection)) {
- set(this, 'selection', Ember.A([selection]));
- return;
- }
- this._selectionDidChangeMultiple();
- } else {
- this._selectionDidChangeSingle();
- }
- }),
- valueDidChange: Ember.observer('value', function() {
- var content = get(this, 'content'),
- value = get(this, 'value'),
- valuePath = get(this, 'optionValuePath').replace(/^content\.?/, ''),
- selectedValue = (valuePath ? get(this, 'selection.' + valuePath) : get(this, 'selection')),
- selection;
- if (value !== selectedValue) {
- selection = content ? content.find(function(obj) {
- return value === (valuePath ? get(obj, valuePath) : obj);
- }) : null;
- this.set('selection', selection);
- }
- }),
- _triggerChange: function() {
- var selection = get(this, 'selection');
- var value = get(this, 'value');
- if (!Ember.isNone(selection)) { this.selectionDidChange(); }
- if (!Ember.isNone(value)) { this.valueDidChange(); }
- this._change();
- },
- _changeSingle: function() {
- var selectedIndex = this.$()[0].selectedIndex,
- content = get(this, 'content'),
- prompt = get(this, 'prompt');
- if (!content || !get(content, 'length')) { return; }
- if (prompt && selectedIndex === 0) { set(this, 'selection', null); return; }
- if (prompt) { selectedIndex -= 1; }
- set(this, 'selection', content.objectAt(selectedIndex));
- },
- _changeMultiple: function() {
- var options = this.$('option:selected'),
- prompt = get(this, 'prompt'),
- offset = prompt ? 1 : 0,
- content = get(this, 'content'),
- selection = get(this, 'selection');
- if (!content) { return; }
- if (options) {
- var selectedIndexes = options.map(function() {
- return this.index - offset;
- }).toArray();
- var newSelection = content.objectsAt(selectedIndexes);
- if (isArray(selection)) {
- replace(selection, 0, get(selection, 'length'), newSelection);
- } else {
- set(this, 'selection', newSelection);
- }
- }
- },
- _selectionDidChangeSingle: function() {
- var el = this.get('element');
- if (!el) { return; }
- var content = get(this, 'content'),
- selection = get(this, 'selection'),
- selectionIndex = content ? indexOf(content, selection) : -1,
- prompt = get(this, 'prompt');
- if (prompt) { selectionIndex += 1; }
- if (el) { el.selectedIndex = selectionIndex; }
- },
- _selectionDidChangeMultiple: function() {
- var content = get(this, 'content'),
- selection = get(this, 'selection'),
- selectedIndexes = content ? indexesOf(content, selection) : [-1],
- prompt = get(this, 'prompt'),
- offset = prompt ? 1 : 0,
- options = this.$('option'),
- adjusted;
- if (options) {
- options.each(function() {
- adjusted = this.index > -1 ? this.index - offset : -1;
- this.selected = indexOf(selectedIndexes, adjusted) > -1;
- });
- }
- },
- init: function() {
- this._super();
- this.on("didInsertElement", this, this._triggerChange);
- this.on("change", this, this._change);
- }
- });
- })();
- (function() {
- /**
- @module ember
- @submodule ember-handlebars-compiler
- */
- /**
- The `{{input}}` helper inserts an HTML `<input>` tag into the template,
- with a `type` value of either `text` or `checkbox`. If no `type` is provided,
- `text` will be the default value applied. The attributes of `{{input}}`
- match those of the native HTML tag as closely as possible for these two types.
- ## Use as text field
- An `{{input}}` with no `type` or a `type` of `text` will render an HTML text input.
- The following HTML attributes can be set via the helper:
- * `value`
- * `size`
- * `name`
- * `pattern`
- * `placeholder`
- * `disabled`
- * `maxlength`
- * `tabindex`
- When set to a quoted string, these values will be directly applied to the HTML
- element. When left unquoted, these values will be bound to a property on the
- template's current rendering context (most typically a controller instance).
- ## Unbound:
- ```handlebars
- {{input value="http://www.facebook.com"}}
- ```
- ```html
- <input type="text" value="http://www.facebook.com"/>
- ```
- ## Bound:
- ```javascript
- App.ApplicationController = Ember.Controller.extend({
- firstName: "Stanley",
- entryNotAllowed: true
- });
- ```
- ```handlebars
- {{input type="text" value=firstName disabled=entryNotAllowed size="50"}}
- ```
- ```html
- <input type="text" value="Stanley" disabled="disabled" size="50"/>
- ```
- ## Extension
- Internally, `{{input type="text"}}` creates an instance of `Ember.TextField`, passing
- arguments from the helper to `Ember.TextField`'s `create` method. You can extend the
- capablilties of text inputs in your applications by reopening this class. For example,
- if you are deploying to browsers where the `required` attribute is used, you
- can add this to the `TextField`'s `attributeBindings` property:
- ```javascript
- Ember.TextField.reopen({
- attributeBindings: ['required']
- });
- ```
- Keep in mind when writing `Ember.TextField` subclasses that `Ember.TextField`
- itself extends `Ember.Component`, meaning that it does NOT inherit
- the `controller` of the parent view.
- See more about [Ember components](api/classes/Ember.Component.html)
- ## Use as checkbox
- An `{{input}}` with a `type` of `checkbox` will render an HTML checkbox input.
- The following HTML attributes can be set via the helper:
- * `checked`
- * `disabled`
- * `tabindex`
- * `indeterminate`
- * `name`
- When set to a quoted string, these values will be directly applied to the HTML
- element. When left unquoted, these values will be bound to a property on the
- template's current rendering context (most typically a controller instance).
- ## Unbound:
- ```handlebars
- {{input type="checkbox" name="isAdmin"}}
- ```
- ```html
- <input type="checkbox" name="isAdmin" />
- ```
- ## Bound:
- ```javascript
- App.ApplicationController = Ember.Controller.extend({
- isAdmin: true
- });
- ```
- ```handlebars
- {{input type="checkbox" checked=isAdmin }}
- ```
- ```html
- <input type="checkbox" checked="checked" />
- ```
- ## Extension
- Internally, `{{input type="checkbox"}}` creates an instance of `Ember.Checkbox`, passing
- arguments from the helper to `Ember.Checkbox`'s `create` method. You can extend the
- capablilties of checkbox inputs in your applications by reopening this class. For example,
- if you wanted to add a css class to all checkboxes in your application:
- ```javascript
- Ember.Checkbox.reopen({
- classNames: ['my-app-checkbox']
- });
- ```
- @method input
- @for Ember.Handlebars.helpers
- @param {Hash} options
- */
- Ember.Handlebars.registerHelper('input', function(options) {
- Ember.assert('You can only pass attributes to the `input` helper, not arguments', arguments.length < 2);
- var hash = options.hash,
- types = options.hashTypes,
- inputType = hash.type,
- onEvent = hash.on;
- delete hash.type;
- delete hash.on;
- if (inputType === 'checkbox') {
- Ember.assert("{{input type='checkbox'}} does not support setting `value=someBooleanValue`; you must use `checked=someBooleanValue` instead.", options.hashTypes.value !== 'ID');
- return Ember.Handlebars.helpers.view.call(this, Ember.Checkbox, options);
- } else {
- if (inputType) { hash.type = inputType; }
- hash.onEvent = onEvent || 'enter';
- return Ember.Handlebars.helpers.view.call(this, Ember.TextField, options);
- }
- });
- /**
- `{{textarea}}` inserts a new instance of `<textarea>` tag into the template.
- The attributes of `{{textarea}}` match those of the native HTML tags as
- closely as possible.
- The following HTML attributes can be set:
- * `value`
- * `name`
- * `rows`
- * `cols`
- * `placeholder`
- * `disabled`
- * `maxlength`
- * `tabindex`
- When set to a quoted string, these value will be directly applied to the HTML
- element. When left unquoted, these values will be bound to a property on the
- template's current rendering context (most typically a controller instance).
- Unbound:
- ```handlebars
- {{textarea value="Lots of static text that ISN'T bound"}}
- ```
- Would result in the following HTML:
- ```html
- <textarea class="ember-text-area">
- Lots of static text that ISN'T bound
- </textarea>
- ```
- Bound:
- In the following example, the `writtenWords` property on `App.ApplicationController`
- will be updated live as the user types 'Lots of text that IS bound' into
- the text area of their browser's window.
- ```javascript
- App.ApplicationController = Ember.Controller.extend({
- writtenWords: "Lots of text that IS bound"
- });
- ```
- ```handlebars
- {{textarea value=writtenWords}}
- ```
- Would result in the following HTML:
- ```html
- <textarea class="ember-text-area">
- Lots of text that IS bound
- </textarea>
- ```
- If you wanted a one way binding between the text area and a div tag
- somewhere else on your screen, you could use `Ember.computed.oneWay`:
- ```javascript
- App.ApplicationController = Ember.Controller.extend({
- writtenWords: "Lots of text that IS bound",
- outputWrittenWords: Ember.computed.oneWay("writtenWords")
- });
- ```
- ```handlebars
- {{textarea value=writtenWords}}
- <div>
- {{outputWrittenWords}}
- </div>
- ```
- Would result in the following HTML:
- ```html
- <textarea class="ember-text-area">
- Lots of text that IS bound
- </textarea>
- <-- the following div will be updated in real time as you type -->
- <div>
- Lots of text that IS bound
- </div>
- ```
- Finally, this example really shows the power and ease of Ember when two
- properties are bound to eachother via `Ember.computed.alias`. Type into
- either text area box and they'll both stay in sync. Note that
- `Ember.computed.alias` costs more in terms of performance, so only use it when
- your really binding in both directions:
- ```javascript
- App.ApplicationController = Ember.Controller.extend({
- writtenWords: "Lots of text that IS bound",
- twoWayWrittenWords: Ember.computed.alias("writtenWords")
- });
- ```
- ```handlebars
- {{textarea value=writtenWords}}
- {{textarea value=twoWayWrittenWords}}
- ```
- ```html
- <textarea id="ember1" class="ember-text-area">
- Lots of text that IS bound
- </textarea>
- <-- both updated in real time -->
- <textarea id="ember2" class="ember-text-area">
- Lots of text that IS bound
- </textarea>
- ```
- ## Extension
- Internally, `{{textarea}}` creates an instance of `Ember.TextArea`, passing
- arguments from the helper to `Ember.TextArea`'s `create` method. You can
- extend the capabilities of text areas in your application by reopening this
- class. For example, if you are deploying to browsers where the `required`
- attribute is used, you can globally add support for the `required` attribute
- on all `{{textarea}}`s' in your app by reopening `Ember.TextArea` or
- `Ember.TextSupport` and adding it to the `attributeBindings` concatenated
- property:
- ```javascript
- Ember.TextArea.reopen({
- attributeBindings: ['required']
- });
- ```
- Keep in mind when writing `Ember.TextArea` subclasses that `Ember.TextArea`
- itself extends `Ember.Component`, meaning that it does NOT inherit
- the `controller` of the parent view.
- See more about [Ember components](api/classes/Ember.Component.html)
- @method textarea
- @for Ember.Handlebars.helpers
- @param {Hash} options
- */
- Ember.Handlebars.registerHelper('textarea', function(options) {
- Ember.assert('You can only pass attributes to the `textarea` helper, not arguments', arguments.length < 2);
- var hash = options.hash,
- types = options.hashTypes;
- return Ember.Handlebars.helpers.view.call(this, Ember.TextArea, options);
- });
- })();
- (function() {
- Ember.ComponentLookup = Ember.Object.extend({
- lookupFactory: function(name, container) {
- container = container || this.container;
- var fullName = 'component:' + name,
- templateFullName = 'template:components/' + name,
- templateRegistered = container && container.has(templateFullName);
- if (templateRegistered) {
- container.injection(fullName, 'layout', templateFullName);
- }
- var Component = container.lookupFactory(fullName);
- // Only treat as a component if either the component
- // or a template has been registered.
- if (templateRegistered || Component) {
- if (!Component) {
- container.register(fullName, Ember.Component);
- Component = container.lookupFactory(fullName);
- }
- return Component;
- }
- }
- });
- })();
- (function() {
- /*globals Handlebars */
- /**
- @module ember
- @submodule ember-handlebars
- */
- /**
- Find templates stored in the head tag as script tags and make them available
- to `Ember.CoreView` in the global `Ember.TEMPLATES` object. This will be run
- as as jQuery DOM-ready callback.
- Script tags with `text/x-handlebars` will be compiled
- with Ember's Handlebars and are suitable for use as a view's template.
- Those with type `text/x-raw-handlebars` will be compiled with regular
- Handlebars and are suitable for use in views' computed properties.
- @private
- @method bootstrap
- @for Ember.Handlebars
- @static
- @param ctx
- */
- Ember.Handlebars.bootstrap = function(ctx) {
- var selectors = 'script[type="text/x-handlebars"], script[type="text/x-raw-handlebars"]';
- Ember.$(selectors, ctx)
- .each(function() {
- // Get a reference to the script tag
- var script = Ember.$(this);
- var compile = (script.attr('type') === 'text/x-raw-handlebars') ?
- Ember.$.proxy(Handlebars.compile, Handlebars) :
- Ember.$.proxy(Ember.Handlebars.compile, Ember.Handlebars),
- // Get the name of the script, used by Ember.View's templateName property.
- // First look for data-template-name attribute, then fall back to its
- // id if no name is found.
- templateName = script.attr('data-template-name') || script.attr('id') || 'application',
- template = compile(script.html());
- // Check if template of same name already exists
- if (Ember.TEMPLATES[templateName] !== undefined) {
- throw new Ember.Error('Template named "' + templateName + '" already exists.');
- }
- // For templates which have a name, we save them and then remove them from the DOM
- Ember.TEMPLATES[templateName] = template;
- // Remove script tag from DOM
- script.remove();
- });
- };
- function bootstrap() {
- Ember.Handlebars.bootstrap( Ember.$(document) );
- }
- function registerComponentLookup(container) {
- container.register('component-lookup:main', Ember.ComponentLookup);
- }
- /*
- We tie this to application.load to ensure that we've at least
- attempted to bootstrap at the point that the application is loaded.
- We also tie this to document ready since we're guaranteed that all
- the inline templates are present at this point.
- There's no harm to running this twice, since we remove the templates
- from the DOM after processing.
- */
- Ember.onLoad('Ember.Application', function(Application) {
- Application.initializer({
- name: 'domTemplates',
- initialize: bootstrap
- });
- Application.initializer({
- name: 'registerComponentLookup',
- after: 'domTemplates',
- initialize: registerComponentLookup
- });
- });
- })();
- (function() {
- /**
- Ember Handlebars
- @module ember
- @submodule ember-handlebars
- @requires ember-views
- */
- Ember.runLoadHooks('Ember.Handlebars', Ember.Handlebars);
- })();
- (function() {
- define("route-recognizer",
- ["exports"],
- function(__exports__) {
- "use strict";
- var specials = [
- '/', '.', '*', '+', '?', '|',
- '(', ')', '[', ']', '{', '}', '\\'
- ];
- var escapeRegex = new RegExp('(\\' + specials.join('|\\') + ')', 'g');
- // A Segment represents a segment in the original route description.
- // Each Segment type provides an `eachChar` and `regex` method.
- //
- // The `eachChar` method invokes the callback with one or more character
- // specifications. A character specification consumes one or more input
- // characters.
- //
- // The `regex` method returns a regex fragment for the segment. If the
- // segment is a dynamic of star segment, the regex fragment also includes
- // a capture.
- //
- // A character specification contains:
- //
- // * `validChars`: a String with a list of all valid characters, or
- // * `invalidChars`: a String with a list of all invalid characters
- // * `repeat`: true if the character specification can repeat
- function StaticSegment(string) { this.string = string; }
- StaticSegment.prototype = {
- eachChar: function(callback) {
- var string = this.string, ch;
- for (var i=0, l=string.length; i<l; i++) {
- ch = string.charAt(i);
- callback({ validChars: ch });
- }
- },
- regex: function() {
- return this.string.replace(escapeRegex, '\\$1');
- },
- generate: function() {
- return this.string;
- }
- };
- function DynamicSegment(name) { this.name = name; }
- DynamicSegment.prototype = {
- eachChar: function(callback) {
- callback({ invalidChars: "/", repeat: true });
- },
- regex: function() {
- return "([^/]+)";
- },
- generate: function(params) {
- return params[this.name];
- }
- };
- function StarSegment(name) { this.name = name; }
- StarSegment.prototype = {
- eachChar: function(callback) {
- callback({ invalidChars: "", repeat: true });
- },
- regex: function() {
- return "(.+)";
- },
- generate: function(params) {
- return params[this.name];
- }
- };
- function EpsilonSegment() {}
- EpsilonSegment.prototype = {
- eachChar: function() {},
- regex: function() { return ""; },
- generate: function() { return ""; }
- };
- function parse(route, names, types) {
- // normalize route as not starting with a "/". Recognition will
- // also normalize.
- if (route.charAt(0) === "/") { route = route.substr(1); }
- var segments = route.split("/"), results = [];
- for (var i=0, l=segments.length; i<l; i++) {
- var segment = segments[i], match;
- if (match = segment.match(/^:([^\/]+)$/)) {
- results.push(new DynamicSegment(match[1]));
- names.push(match[1]);
- types.dynamics++;
- } else if (match = segment.match(/^\*([^\/]+)$/)) {
- results.push(new StarSegment(match[1]));
- names.push(match[1]);
- types.stars++;
- } else if(segment === "") {
- results.push(new EpsilonSegment());
- } else {
- results.push(new StaticSegment(segment));
- types.statics++;
- }
- }
- return results;
- }
- // A State has a character specification and (`charSpec`) and a list of possible
- // subsequent states (`nextStates`).
- //
- // If a State is an accepting state, it will also have several additional
- // properties:
- //
- // * `regex`: A regular expression that is used to extract parameters from paths
- // that reached this accepting state.
- // * `handlers`: Information on how to convert the list of captures into calls
- // to registered handlers with the specified parameters
- // * `types`: How many static, dynamic or star segments in this route. Used to
- // decide which route to use if multiple registered routes match a path.
- //
- // Currently, State is implemented naively by looping over `nextStates` and
- // comparing a character specification against a character. A more efficient
- // implementation would use a hash of keys pointing at one or more next states.
- function State(charSpec) {
- this.charSpec = charSpec;
- this.nextStates = [];
- }
- State.prototype = {
- get: function(charSpec) {
- var nextStates = this.nextStates;
- for (var i=0, l=nextStates.length; i<l; i++) {
- var child = nextStates[i];
- var isEqual = child.charSpec.validChars === charSpec.validChars;
- isEqual = isEqual && child.charSpec.invalidChars === charSpec.invalidChars;
- if (isEqual) { return child; }
- }
- },
- put: function(charSpec) {
- var state;
- // If the character specification already exists in a child of the current
- // state, just return that state.
- if (state = this.get(charSpec)) { return state; }
- // Make a new state for the character spec
- state = new State(charSpec);
- // Insert the new state as a child of the current state
- this.nextStates.push(state);
- // If this character specification repeats, insert the new state as a child
- // of itself. Note that this will not trigger an infinite loop because each
- // transition during recognition consumes a character.
- if (charSpec.repeat) {
- state.nextStates.push(state);
- }
- // Return the new state
- return state;
- },
- // Find a list of child states matching the next character
- match: function(ch) {
- // DEBUG "Processing `" + ch + "`:"
- var nextStates = this.nextStates,
- child, charSpec, chars;
- // DEBUG " " + debugState(this)
- var returned = [];
- for (var i=0, l=nextStates.length; i<l; i++) {
- child = nextStates[i];
- charSpec = child.charSpec;
- if (typeof (chars = charSpec.validChars) !== 'undefined') {
- if (chars.indexOf(ch) !== -1) { returned.push(child); }
- } else if (typeof (chars = charSpec.invalidChars) !== 'undefined') {
- if (chars.indexOf(ch) === -1) { returned.push(child); }
- }
- }
- return returned;
- }
- /** IF DEBUG
- , debug: function() {
- var charSpec = this.charSpec,
- debug = "[",
- chars = charSpec.validChars || charSpec.invalidChars;
- if (charSpec.invalidChars) { debug += "^"; }
- debug += chars;
- debug += "]";
- if (charSpec.repeat) { debug += "+"; }
- return debug;
- }
- END IF **/
- };
- /** IF DEBUG
- function debug(log) {
- console.log(log);
- }
- function debugState(state) {
- return state.nextStates.map(function(n) {
- if (n.nextStates.length === 0) { return "( " + n.debug() + " [accepting] )"; }
- return "( " + n.debug() + " <then> " + n.nextStates.map(function(s) { return s.debug() }).join(" or ") + " )";
- }).join(", ")
- }
- END IF **/
- // This is a somewhat naive strategy, but should work in a lot of cases
- // A better strategy would properly resolve /posts/:id/new and /posts/edit/:id
- function sortSolutions(states) {
- return states.sort(function(a, b) {
- if (a.types.stars !== b.types.stars) { return a.types.stars - b.types.stars; }
- if (a.types.dynamics !== b.types.dynamics) { return a.types.dynamics - b.types.dynamics; }
- if (a.types.statics !== b.types.statics) { return b.types.statics - a.types.statics; }
- return 0;
- });
- }
- function recognizeChar(states, ch) {
- var nextStates = [];
- for (var i=0, l=states.length; i<l; i++) {
- var state = states[i];
- nextStates = nextStates.concat(state.match(ch));
- }
- return nextStates;
- }
- var oCreate = Object.create || function(proto) {
- function F() {}
- F.prototype = proto;
- return new F();
- };
- function RecognizeResults(queryParams) {
- this.queryParams = queryParams || {};
- }
- RecognizeResults.prototype = oCreate({
- splice: Array.prototype.splice,
- slice: Array.prototype.slice,
- push: Array.prototype.push,
- length: 0,
- queryParams: null
- });
- function findHandler(state, path, queryParams) {
- var handlers = state.handlers, regex = state.regex;
- var captures = path.match(regex), currentCapture = 1;
- var result = new RecognizeResults(queryParams);
- for (var i=0, l=handlers.length; i<l; i++) {
- var handler = handlers[i], names = handler.names, params = {};
- for (var j=0, m=names.length; j<m; j++) {
- params[names[j]] = captures[currentCapture++];
- }
- result.push({ handler: handler.handler, params: params, isDynamic: !!names.length });
- }
- return result;
- }
- function addSegment(currentState, segment) {
- segment.eachChar(function(ch) {
- var state;
- currentState = currentState.put(ch);
- });
- return currentState;
- }
- // The main interface
- var RouteRecognizer = function() {
- this.rootState = new State();
- this.names = {};
- };
- RouteRecognizer.prototype = {
- add: function(routes, options) {
- var currentState = this.rootState, regex = "^",
- types = { statics: 0, dynamics: 0, stars: 0 },
- handlers = [], allSegments = [], name;
- var isEmpty = true;
- for (var i=0, l=routes.length; i<l; i++) {
- var route = routes[i], names = [];
- var segments = parse(route.path, names, types);
- allSegments = allSegments.concat(segments);
- for (var j=0, m=segments.length; j<m; j++) {
- var segment = segments[j];
- if (segment instanceof EpsilonSegment) { continue; }
- isEmpty = false;
- // Add a "/" for the new segment
- currentState = currentState.put({ validChars: "/" });
- regex += "/";
- // Add a representation of the segment to the NFA and regex
- currentState = addSegment(currentState, segment);
- regex += segment.regex();
- }
- var handler = { handler: route.handler, names: names };
- handlers.push(handler);
- }
- if (isEmpty) {
- currentState = currentState.put({ validChars: "/" });
- regex += "/";
- }
- currentState.handlers = handlers;
- currentState.regex = new RegExp(regex + "$");
- currentState.types = types;
- if (name = options && options.as) {
- this.names[name] = {
- segments: allSegments,
- handlers: handlers
- };
- }
- },
- handlersFor: function(name) {
- var route = this.names[name], result = [];
- if (!route) { throw new Error("There is no route named " + name); }
- for (var i=0, l=route.handlers.length; i<l; i++) {
- result.push(route.handlers[i]);
- }
- return result;
- },
- hasRoute: function(name) {
- return !!this.names[name];
- },
- generate: function(name, params) {
- var route = this.names[name], output = "";
- if (!route) { throw new Error("There is no route named " + name); }
- var segments = route.segments;
- for (var i=0, l=segments.length; i<l; i++) {
- var segment = segments[i];
- if (segment instanceof EpsilonSegment) { continue; }
- output += "/";
- output += segment.generate(params);
- }
- if (output.charAt(0) !== '/') { output = '/' + output; }
- if (params && params.queryParams) {
- output += this.generateQueryString(params.queryParams, route.handlers);
- }
- return output;
- },
- generateQueryString: function(params, handlers) {
- var pairs = [];
- for(var key in params) {
- if (params.hasOwnProperty(key)) {
- var value = params[key];
- if (value === false || value == null) {
- continue;
- }
- var pair = key;
- if (Array.isArray(value)) {
- for (var i = 0, l = value.length; i < l; i++) {
- var arrayPair = key + '[]' + '=' + encodeURIComponent(value[i]);
- pairs.push(arrayPair);
- }
- }
- else if (value !== true) {
- pair += "=" + encodeURIComponent(value);
- pairs.push(pair);
- } else {
- pairs.push(pair);
- }
- }
- }
- if (pairs.length === 0) { return ''; }
- return "?" + pairs.join("&");
- },
- parseQueryString: function(queryString) {
- var pairs = queryString.split("&"), queryParams = {};
- for(var i=0; i < pairs.length; i++) {
- var pair = pairs[i].split('='),
- key = decodeURIComponent(pair[0]),
- keyLength = key.length,
- isArray = false,
- value;
- if (pair.length === 1) {
- value = true;
- } else {
- //Handle arrays
- if (keyLength > 2 && key.slice(keyLength -2) === '[]') {
- isArray = true;
- key = key.slice(0, keyLength - 2);
- if(!queryParams[key]) {
- queryParams[key] = [];
- }
- }
- value = pair[1] ? decodeURIComponent(pair[1]) : '';
- }
- if (isArray) {
- queryParams[key].push(value);
- } else {
- queryParams[key] = value;
- }
-
- }
- return queryParams;
- },
- recognize: function(path) {
- var states = [ this.rootState ],
- pathLen, i, l, queryStart, queryParams = {},
- isSlashDropped = false;
- queryStart = path.indexOf('?');
- if (queryStart !== -1) {
- var queryString = path.substr(queryStart + 1, path.length);
- path = path.substr(0, queryStart);
- queryParams = this.parseQueryString(queryString);
- }
- // DEBUG GROUP path
- if (path.charAt(0) !== "/") { path = "/" + path; }
- pathLen = path.length;
- if (pathLen > 1 && path.charAt(pathLen - 1) === "/") {
- path = path.substr(0, pathLen - 1);
- isSlashDropped = true;
- }
- for (i=0, l=path.length; i<l; i++) {
- states = recognizeChar(states, path.charAt(i));
- if (!states.length) { break; }
- }
- // END DEBUG GROUP
- var solutions = [];
- for (i=0, l=states.length; i<l; i++) {
- if (states[i].handlers) { solutions.push(states[i]); }
- }
- states = sortSolutions(solutions);
- var state = solutions[0];
- if (state && state.handlers) {
- // if a trailing slash was dropped and a star segment is the last segment
- // specified, put the trailing slash back
- if (isSlashDropped && state.regex.source.slice(-5) === "(.+)$") {
- path = path + "/";
- }
- return findHandler(state, path, queryParams);
- }
- }
- };
- __exports__["default"] = RouteRecognizer;
- function Target(path, matcher, delegate) {
- this.path = path;
- this.matcher = matcher;
- this.delegate = delegate;
- }
- Target.prototype = {
- to: function(target, callback) {
- var delegate = this.delegate;
- if (delegate && delegate.willAddRoute) {
- target = delegate.willAddRoute(this.matcher.target, target);
- }
- this.matcher.add(this.path, target);
- if (callback) {
- if (callback.length === 0) { throw new Error("You must have an argument in the function passed to `to`"); }
- this.matcher.addChild(this.path, target, callback, this.delegate);
- }
- return this;
- }
- };
- function Matcher(target) {
- this.routes = {};
- this.children = {};
- this.target = target;
- }
- Matcher.prototype = {
- add: function(path, handler) {
- this.routes[path] = handler;
- },
- addChild: function(path, target, callback, delegate) {
- var matcher = new Matcher(target);
- this.children[path] = matcher;
- var match = generateMatch(path, matcher, delegate);
- if (delegate && delegate.contextEntered) {
- delegate.contextEntered(target, match);
- }
- callback(match);
- }
- };
- function generateMatch(startingPath, matcher, delegate) {
- return function(path, nestedCallback) {
- var fullPath = startingPath + path;
- if (nestedCallback) {
- nestedCallback(generateMatch(fullPath, matcher, delegate));
- } else {
- return new Target(startingPath + path, matcher, delegate);
- }
- };
- }
- function addRoute(routeArray, path, handler) {
- var len = 0;
- for (var i=0, l=routeArray.length; i<l; i++) {
- len += routeArray[i].path.length;
- }
- path = path.substr(len);
- var route = { path: path, handler: handler };
- routeArray.push(route);
- }
- function eachRoute(baseRoute, matcher, callback, binding) {
- var routes = matcher.routes;
- for (var path in routes) {
- if (routes.hasOwnProperty(path)) {
- var routeArray = baseRoute.slice();
- addRoute(routeArray, path, routes[path]);
- if (matcher.children[path]) {
- eachRoute(routeArray, matcher.children[path], callback, binding);
- } else {
- callback.call(binding, routeArray);
- }
- }
- }
- }
- RouteRecognizer.prototype.map = function(callback, addRouteCallback) {
- var matcher = new Matcher();
- callback(generateMatch("", matcher, this.delegate));
- eachRoute([], matcher, function(route) {
- if (addRouteCallback) { addRouteCallback(this, route); }
- else { this.add(route); }
- }, this);
- };
- });
- })();
- (function() {
- define("router/handler-info",
- ["./utils","rsvp","exports"],
- function(__dependency1__, __dependency2__, __exports__) {
- "use strict";
- var bind = __dependency1__.bind;
- var merge = __dependency1__.merge;
- var oCreate = __dependency1__.oCreate;
- var serialize = __dependency1__.serialize;
- var resolve = __dependency2__.resolve;
- function HandlerInfo(props) {
- if (props) {
- merge(this, props);
- }
- }
- HandlerInfo.prototype = {
- name: null,
- handler: null,
- params: null,
- context: null,
- log: function(payload, message) {
- if (payload.log) {
- payload.log(this.name + ': ' + message);
- }
- },
- resolve: function(async, shouldContinue, payload) {
- var checkForAbort = bind(this.checkForAbort, this, shouldContinue),
- beforeModel = bind(this.runBeforeModelHook, this, async, payload),
- model = bind(this.getModel, this, async, payload),
- afterModel = bind(this.runAfterModelHook, this, async, payload),
- becomeResolved = bind(this.becomeResolved, this, payload);
- return resolve().then(checkForAbort)
- .then(beforeModel)
- .then(checkForAbort)
- .then(model)
- .then(checkForAbort)
- .then(afterModel)
- .then(checkForAbort)
- .then(becomeResolved);
- },
- runBeforeModelHook: function(async, payload) {
- if (payload.trigger) {
- payload.trigger(true, 'willResolveModel', payload, this.handler);
- }
- return this.runSharedModelHook(async, payload, 'beforeModel', []);
- },
- runAfterModelHook: function(async, payload, resolvedModel) {
- // Stash the resolved model on the payload.
- // This makes it possible for users to swap out
- // the resolved model in afterModel.
- var name = this.name;
- this.stashResolvedModel(payload, resolvedModel);
- return this.runSharedModelHook(async, payload, 'afterModel', [resolvedModel])
- .then(function() {
- // Ignore the fulfilled value returned from afterModel.
- // Return the value stashed in resolvedModels, which
- // might have been swapped out in afterModel.
- return payload.resolvedModels[name];
- });
- },
- runSharedModelHook: function(async, payload, hookName, args) {
- this.log(payload, "calling " + hookName + " hook");
- if (this.queryParams) {
- args.push(this.queryParams);
- }
- args.push(payload);
- var handler = this.handler;
- return async(function() {
- return handler[hookName] && handler[hookName].apply(handler, args);
- });
- },
- getModel: function(payload) {
- throw new Error("This should be overridden by a subclass of HandlerInfo");
- },
- checkForAbort: function(shouldContinue, promiseValue) {
- return resolve(shouldContinue()).then(function() {
- // We don't care about shouldContinue's resolve value;
- // pass along the original value passed to this fn.
- return promiseValue;
- });
- },
- stashResolvedModel: function(payload, resolvedModel) {
- payload.resolvedModels = payload.resolvedModels || {};
- payload.resolvedModels[this.name] = resolvedModel;
- },
- becomeResolved: function(payload, resolvedContext) {
- var params = this.params || serialize(this.handler, resolvedContext, this.names);
- if (payload) {
- this.stashResolvedModel(payload, resolvedContext);
- payload.params = payload.params || {};
- payload.params[this.name] = params;
- }
- return new ResolvedHandlerInfo({
- context: resolvedContext,
- name: this.name,
- handler: this.handler,
- params: params
- });
- },
- shouldSupercede: function(other) {
- // Prefer this newer handlerInfo over `other` if:
- // 1) The other one doesn't exist
- // 2) The names don't match
- // 3) This handler has a context that doesn't match
- // the other one (or the other one doesn't have one).
- // 4) This handler has parameters that don't match the other.
- if (!other) { return true; }
- var contextsMatch = (other.context === this.context);
- return other.name !== this.name ||
- (this.hasOwnProperty('context') && !contextsMatch) ||
- (this.hasOwnProperty('params') && !paramsMatch(this.params, other.params));
- }
- };
- function ResolvedHandlerInfo(props) {
- HandlerInfo.call(this, props);
- }
- ResolvedHandlerInfo.prototype = oCreate(HandlerInfo.prototype);
- ResolvedHandlerInfo.prototype.resolve = function(async, shouldContinue, payload) {
- // A ResolvedHandlerInfo just resolved with itself.
- if (payload && payload.resolvedModels) {
- payload.resolvedModels[this.name] = this.context;
- }
- return resolve(this);
- };
- // These are generated by URL transitions and
- // named transitions for non-dynamic route segments.
- function UnresolvedHandlerInfoByParam(props) {
- HandlerInfo.call(this, props);
- this.params = this.params || {};
- }
- UnresolvedHandlerInfoByParam.prototype = oCreate(HandlerInfo.prototype);
- UnresolvedHandlerInfoByParam.prototype.getModel = function(async, payload) {
- var fullParams = this.params;
- if (payload && payload.queryParams) {
- fullParams = {};
- merge(fullParams, this.params);
- fullParams.queryParams = payload.queryParams;
- }
- var hookName = typeof this.handler.deserialize === 'function' ?
- 'deserialize' : 'model';
- return this.runSharedModelHook(async, payload, hookName, [fullParams]);
- };
- // These are generated only for named transitions
- // with dynamic route segments.
- function UnresolvedHandlerInfoByObject(props) {
- HandlerInfo.call(this, props);
- }
- UnresolvedHandlerInfoByObject.prototype = oCreate(HandlerInfo.prototype);
- UnresolvedHandlerInfoByObject.prototype.getModel = function(async, payload) {
- this.log(payload, this.name + ": resolving provided model");
- return resolve(this.context);
- };
- function paramsMatch(a, b) {
- if ((!a) ^ (!b)) {
- // Only one is null.
- return false;
- }
- if (!a) {
- // Both must be null.
- return true;
- }
- // Note: this assumes that both params have the same
- // number of keys, but since we're comparing the
- // same handlers, they should.
- for (var k in a) {
- if (a.hasOwnProperty(k) && a[k] !== b[k]) {
- return false;
- }
- }
- return true;
- }
- __exports__.HandlerInfo = HandlerInfo;
- __exports__.ResolvedHandlerInfo = ResolvedHandlerInfo;
- __exports__.UnresolvedHandlerInfoByParam = UnresolvedHandlerInfoByParam;
- __exports__.UnresolvedHandlerInfoByObject = UnresolvedHandlerInfoByObject;
- });
- define("router/router",
- ["route-recognizer","rsvp","./utils","./transition-state","./transition","./transition-intent/named-transition-intent","./transition-intent/url-transition-intent","exports"],
- function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __exports__) {
- "use strict";
- var RouteRecognizer = __dependency1__["default"];
- var resolve = __dependency2__.resolve;
- var reject = __dependency2__.reject;
- var async = __dependency2__.async;
- var Promise = __dependency2__.Promise;
- var trigger = __dependency3__.trigger;
- var log = __dependency3__.log;
- var slice = __dependency3__.slice;
- var forEach = __dependency3__.forEach;
- var merge = __dependency3__.merge;
- var serialize = __dependency3__.serialize;
- var extractQueryParams = __dependency3__.extractQueryParams;
- var getChangelist = __dependency3__.getChangelist;
- var TransitionState = __dependency4__.TransitionState;
- var logAbort = __dependency5__.logAbort;
- var Transition = __dependency5__.Transition;
- var TransitionAborted = __dependency5__.TransitionAborted;
- var NamedTransitionIntent = __dependency6__.NamedTransitionIntent;
- var URLTransitionIntent = __dependency7__.URLTransitionIntent;
- var pop = Array.prototype.pop;
- function Router() {
- this.recognizer = new RouteRecognizer();
- this.reset();
- }
- Router.prototype = {
- /**
- The main entry point into the router. The API is essentially
- the same as the `map` method in `route-recognizer`.
- This method extracts the String handler at the last `.to()`
- call and uses it as the name of the whole route.
- @param {Function} callback
- */
- map: function(callback) {
- this.recognizer.delegate = this.delegate;
- this.recognizer.map(callback, function(recognizer, route) {
- var lastHandler = route[route.length - 1].handler;
- var args = [route, { as: lastHandler }];
- recognizer.add.apply(recognizer, args);
- });
- },
- hasRoute: function(route) {
- return this.recognizer.hasRoute(route);
- },
- // NOTE: this doesn't really belong here, but here
- // it shall remain until our ES6 transpiler can
- // handle cyclical deps.
- transitionByIntent: function(intent, isIntermediate) {
- var wasTransitioning = !!this.activeTransition;
- var oldState = wasTransitioning ? this.activeTransition.state : this.state;
- var newTransition;
- var router = this;
- try {
- var newState = intent.applyToState(oldState, this.recognizer, this.getHandler, isIntermediate);
- if (handlerInfosEqual(newState.handlerInfos, oldState.handlerInfos)) {
- // This is a no-op transition. See if query params changed.
- var queryParamChangelist = getChangelist(oldState.queryParams, newState.queryParams);
- if (queryParamChangelist) {
- // This is a little hacky but we need some way of storing
- // changed query params given that no activeTransition
- // is guaranteed to have occurred.
- this._changedQueryParams = queryParamChangelist.changed;
- trigger(this, newState.handlerInfos, true, ['queryParamsDidChange', queryParamChangelist.changed, queryParamChangelist.all, queryParamChangelist.removed]);
- this._changedQueryParams = null;
- if (!wasTransitioning && this.activeTransition) {
- // One of the handlers in queryParamsDidChange
- // caused a transition. Just return that transition.
- return this.activeTransition;
- } else {
- // Running queryParamsDidChange didn't change anything.
- // Just update query params and be on our way.
- oldState.queryParams = finalizeQueryParamChange(this, newState.handlerInfos, newState.queryParams);
- // We have to return a noop transition that will
- // perform a URL update at the end. This gives
- // the user the ability to set the url update
- // method (default is replaceState).
- newTransition = new Transition(this);
- newTransition.urlMethod = 'replace';
- newTransition.promise = newTransition.promise.then(function(result) {
- updateURL(newTransition, oldState, true);
- if (router.didTransition) {
- router.didTransition(router.currentHandlerInfos);
- }
- return result;
- });
- return newTransition;
- }
- }
- // No-op. No need to create a new transition.
- return new Transition(this);
- }
- if (isIntermediate) {
- setupContexts(this, newState);
- return;
- }
- // Create a new transition to the destination route.
- newTransition = new Transition(this, intent, newState);
- // Abort and usurp any previously active transition.
- if (this.activeTransition) {
- this.activeTransition.abort();
- }
- this.activeTransition = newTransition;
- // Transition promises by default resolve with resolved state.
- // For our purposes, swap out the promise to resolve
- // after the transition has been finalized.
- newTransition.promise = newTransition.promise.then(function(result) {
- return router.async(function() {
- return finalizeTransition(newTransition, result.state);
- });
- });
- if (!wasTransitioning) {
- trigger(this, this.state.handlerInfos, true, ['willTransition', newTransition]);
- }
- return newTransition;
- } catch(e) {
- return new Transition(this, intent, null, e);
- }
- },
- /**
- Clears the current and target route handlers and triggers exit
- on each of them starting at the leaf and traversing up through
- its ancestors.
- */
- reset: function() {
- if (this.state) {
- forEach(this.state.handlerInfos, function(handlerInfo) {
- var handler = handlerInfo.handler;
- if (handler.exit) {
- handler.exit();
- }
- });
- }
- this.state = new TransitionState();
- this.currentHandlerInfos = null;
- },
- activeTransition: null,
- /**
- var handler = handlerInfo.handler;
- The entry point for handling a change to the URL (usually
- via the back and forward button).
- Returns an Array of handlers and the parameters associated
- with those parameters.
- @param {String} url a URL to process
- @return {Array} an Array of `[handler, parameter]` tuples
- */
- handleURL: function(url) {
- // Perform a URL-based transition, but don't change
- // the URL afterward, since it already happened.
- var args = slice.call(arguments);
- if (url.charAt(0) !== '/') { args[0] = '/' + url; }
- return doTransition(this, args).method('replaceQuery');
- },
- /**
- Hook point for updating the URL.
- @param {String} url a URL to update to
- */
- updateURL: function() {
- throw new Error("updateURL is not implemented");
- },
- /**
- Hook point for replacing the current URL, i.e. with replaceState
- By default this behaves the same as `updateURL`
- @param {String} url a URL to update to
- */
- replaceURL: function(url) {
- this.updateURL(url);
- },
- /**
- Transition into the specified named route.
- If necessary, trigger the exit callback on any handlers
- that are no longer represented by the target route.
- @param {String} name the name of the route
- */
- transitionTo: function(name) {
- return doTransition(this, arguments);
- },
- intermediateTransitionTo: function(name) {
- doTransition(this, arguments, true);
- },
- refresh: function(pivotHandler) {
- var state = this.activeTransition ? this.activeTransition.state : this.state;
- var handlerInfos = state.handlerInfos;
- var params = {};
- for (var i = 0, len = handlerInfos.length; i < len; ++i) {
- var handlerInfo = handlerInfos[i];
- params[handlerInfo.name] = handlerInfo.params || {};
- }
- log(this, "Starting a refresh transition");
- var intent = new NamedTransitionIntent({
- name: handlerInfos[handlerInfos.length - 1].name,
- pivotHandler: pivotHandler || handlerInfos[0].handler,
- contexts: [], // TODO collect contexts...?
- queryParams: this._changedQueryParams || state.queryParams || {}
- });
- return this.transitionByIntent(intent, false);
- },
- /**
- Identical to `transitionTo` except that the current URL will be replaced
- if possible.
- This method is intended primarily for use with `replaceState`.
- @param {String} name the name of the route
- */
- replaceWith: function(name) {
- return doTransition(this, arguments).method('replace');
- },
- /**
- Take a named route and context objects and generate a
- URL.
- @param {String} name the name of the route to generate
- a URL for
- @param {...Object} objects a list of objects to serialize
- @return {String} a URL
- */
- generate: function(handlerName) {
- var partitionedArgs = extractQueryParams(slice.call(arguments, 1)),
- suppliedParams = partitionedArgs[0],
- queryParams = partitionedArgs[1];
- // Construct a TransitionIntent with the provided params
- // and apply it to the present state of the router.
- var intent = new NamedTransitionIntent({ name: handlerName, contexts: suppliedParams });
- var state = intent.applyToState(this.state, this.recognizer, this.getHandler);
- var params = {};
- for (var i = 0, len = state.handlerInfos.length; i < len; ++i) {
- var handlerInfo = state.handlerInfos[i];
- var handlerParams = handlerInfo.params ||
- serialize(handlerInfo.handler, handlerInfo.context, handlerInfo.names);
- merge(params, handlerParams);
- }
- params.queryParams = queryParams;
- return this.recognizer.generate(handlerName, params);
- },
- isActive: function(handlerName) {
- var partitionedArgs = extractQueryParams(slice.call(arguments, 1)),
- contexts = partitionedArgs[0],
- queryParams = partitionedArgs[1],
- activeQueryParams = this.state.queryParams;
- var targetHandlerInfos = this.state.handlerInfos,
- found = false, names, object, handlerInfo, handlerObj, i, len;
- if (!targetHandlerInfos.length) { return false; }
- var targetHandler = targetHandlerInfos[targetHandlerInfos.length - 1].name;
- var recogHandlers = this.recognizer.handlersFor(targetHandler);
- var index = 0;
- for (len = recogHandlers.length; index < len; ++index) {
- handlerInfo = targetHandlerInfos[index];
- if (handlerInfo.name === handlerName) { break; }
- }
- if (index === recogHandlers.length) {
- // The provided route name isn't even in the route hierarchy.
- return false;
- }
- var state = new TransitionState();
- state.handlerInfos = targetHandlerInfos.slice(0, index + 1);
- recogHandlers = recogHandlers.slice(0, index + 1);
- var intent = new NamedTransitionIntent({
- name: targetHandler,
- contexts: contexts
- });
- var newState = intent.applyToHandlers(state, recogHandlers, this.getHandler, targetHandler, true, true);
- return handlerInfosEqual(newState.handlerInfos, state.handlerInfos) &&
- !getChangelist(activeQueryParams, queryParams);
- },
- trigger: function(name) {
- var args = slice.call(arguments);
- trigger(this, this.currentHandlerInfos, false, args);
- },
- /**
- @private
- Pluggable hook for possibly running route hooks
- in a try-catch escaping manner.
- @param {Function} callback the callback that will
- be asynchronously called
- @return {Promise} a promise that fulfills with the
- value returned from the callback
- */
- async: function(callback) {
- return new Promise(function(resolve) {
- resolve(callback());
- });
- },
- /**
- Hook point for logging transition status updates.
- @param {String} message The message to log.
- */
- log: null
- };
- /**
- @private
- Takes an Array of `HandlerInfo`s, figures out which ones are
- exiting, entering, or changing contexts, and calls the
- proper handler hooks.
- For example, consider the following tree of handlers. Each handler is
- followed by the URL segment it handles.
- ```
- |~index ("/")
- | |~posts ("/posts")
- | | |-showPost ("/:id")
- | | |-newPost ("/new")
- | | |-editPost ("/edit")
- | |~about ("/about/:id")
- ```
- Consider the following transitions:
- 1. A URL transition to `/posts/1`.
- 1. Triggers the `*model` callbacks on the
- `index`, `posts`, and `showPost` handlers
- 2. Triggers the `enter` callback on the same
- 3. Triggers the `setup` callback on the same
- 2. A direct transition to `newPost`
- 1. Triggers the `exit` callback on `showPost`
- 2. Triggers the `enter` callback on `newPost`
- 3. Triggers the `setup` callback on `newPost`
- 3. A direct transition to `about` with a specified
- context object
- 1. Triggers the `exit` callback on `newPost`
- and `posts`
- 2. Triggers the `serialize` callback on `about`
- 3. Triggers the `enter` callback on `about`
- 4. Triggers the `setup` callback on `about`
- @param {Router} transition
- @param {TransitionState} newState
- */
- function setupContexts(router, newState, transition) {
- var partition = partitionHandlers(router.state, newState);
- forEach(partition.exited, function(handlerInfo) {
- var handler = handlerInfo.handler;
- delete handler.context;
- if (handler.exit) { handler.exit(); }
- });
- var oldState = router.oldState = router.state;
- router.state = newState;
- var currentHandlerInfos = router.currentHandlerInfos = partition.unchanged.slice();
- try {
- forEach(partition.updatedContext, function(handlerInfo) {
- return handlerEnteredOrUpdated(currentHandlerInfos, handlerInfo, false, transition);
- });
- forEach(partition.entered, function(handlerInfo) {
- return handlerEnteredOrUpdated(currentHandlerInfos, handlerInfo, true, transition);
- });
- } catch(e) {
- router.state = oldState;
- router.currentHandlerInfos = oldState.handlerInfos;
- throw e;
- }
- router.state.queryParams = finalizeQueryParamChange(router, currentHandlerInfos, newState.queryParams);
- }
- /**
- @private
- Helper method used by setupContexts. Handles errors or redirects
- that may happen in enter/setup.
- */
- function handlerEnteredOrUpdated(currentHandlerInfos, handlerInfo, enter, transition) {
- var handler = handlerInfo.handler,
- context = handlerInfo.context;
- if (enter && handler.enter) { handler.enter(transition); }
- if (transition && transition.isAborted) {
- throw new TransitionAborted();
- }
- handler.context = context;
- if (handler.contextDidChange) { handler.contextDidChange(); }
- if (handler.setup) { handler.setup(context, transition); }
- if (transition && transition.isAborted) {
- throw new TransitionAborted();
- }
- currentHandlerInfos.push(handlerInfo);
- return true;
- }
- /**
- @private
- This function is called when transitioning from one URL to
- another to determine which handlers are no longer active,
- which handlers are newly active, and which handlers remain
- active but have their context changed.
- Take a list of old handlers and new handlers and partition
- them into four buckets:
- * unchanged: the handler was active in both the old and
- new URL, and its context remains the same
- * updated context: the handler was active in both the
- old and new URL, but its context changed. The handler's
- `setup` method, if any, will be called with the new
- context.
- * exited: the handler was active in the old URL, but is
- no longer active.
- * entered: the handler was not active in the old URL, but
- is now active.
- The PartitionedHandlers structure has four fields:
- * `updatedContext`: a list of `HandlerInfo` objects that
- represent handlers that remain active but have a changed
- context
- * `entered`: a list of `HandlerInfo` objects that represent
- handlers that are newly active
- * `exited`: a list of `HandlerInfo` objects that are no
- longer active.
- * `unchanged`: a list of `HanderInfo` objects that remain active.
- @param {Array[HandlerInfo]} oldHandlers a list of the handler
- information for the previous URL (or `[]` if this is the
- first handled transition)
- @param {Array[HandlerInfo]} newHandlers a list of the handler
- information for the new URL
- @return {Partition}
- */
- function partitionHandlers(oldState, newState) {
- var oldHandlers = oldState.handlerInfos;
- var newHandlers = newState.handlerInfos;
- var handlers = {
- updatedContext: [],
- exited: [],
- entered: [],
- unchanged: []
- };
- var handlerChanged, contextChanged, queryParamsChanged, i, l;
- for (i=0, l=newHandlers.length; i<l; i++) {
- var oldHandler = oldHandlers[i], newHandler = newHandlers[i];
- if (!oldHandler || oldHandler.handler !== newHandler.handler) {
- handlerChanged = true;
- }
- if (handlerChanged) {
- handlers.entered.push(newHandler);
- if (oldHandler) { handlers.exited.unshift(oldHandler); }
- } else if (contextChanged || oldHandler.context !== newHandler.context || queryParamsChanged) {
- contextChanged = true;
- handlers.updatedContext.push(newHandler);
- } else {
- handlers.unchanged.push(oldHandler);
- }
- }
- for (i=newHandlers.length, l=oldHandlers.length; i<l; i++) {
- handlers.exited.unshift(oldHandlers[i]);
- }
- return handlers;
- }
- function updateURL(transition, state, inputUrl) {
- var urlMethod = transition.urlMethod;
- if (!urlMethod) {
- return;
- }
- var router = transition.router,
- handlerInfos = state.handlerInfos,
- handlerName = handlerInfos[handlerInfos.length - 1].name,
- params = {};
- for (var i = handlerInfos.length - 1; i >= 0; --i) {
- var handlerInfo = handlerInfos[i];
- merge(params, handlerInfo.params);
- if (handlerInfo.handler.inaccessibleByURL) {
- urlMethod = null;
- }
- }
- if (urlMethod) {
- params.queryParams = state.queryParams;
- var url = router.recognizer.generate(handlerName, params);
- if (urlMethod === 'replaceQuery') {
- if (url !== inputUrl) {
- router.replaceURL(url);
- }
- } else if (urlMethod === 'replace') {
- router.replaceURL(url);
- } else {
- router.updateURL(url);
- }
- }
- }
- /**
- @private
- Updates the URL (if necessary) and calls `setupContexts`
- to update the router's array of `currentHandlerInfos`.
- */
- function finalizeTransition(transition, newState) {
- try {
- log(transition.router, transition.sequence, "Resolved all models on destination route; finalizing transition.");
- var router = transition.router,
- handlerInfos = newState.handlerInfos,
- seq = transition.sequence;
- // Run all the necessary enter/setup/exit hooks
- setupContexts(router, newState, transition);
- // Check if a redirect occurred in enter/setup
- if (transition.isAborted) {
- // TODO: cleaner way? distinguish b/w targetHandlerInfos?
- router.state.handlerInfos = router.currentHandlerInfos;
- return reject(logAbort(transition));
- }
- updateURL(transition, newState, transition.intent.url);
- transition.isActive = false;
- router.activeTransition = null;
- trigger(router, router.currentHandlerInfos, true, ['didTransition']);
- if (router.didTransition) {
- router.didTransition(router.currentHandlerInfos);
- }
- log(router, transition.sequence, "TRANSITION COMPLETE.");
- // Resolve with the final handler.
- return handlerInfos[handlerInfos.length - 1].handler;
- } catch(e) {
- if (!(e instanceof TransitionAborted)) {
- //var erroneousHandler = handlerInfos.pop();
- var infos = transition.state.handlerInfos;
- transition.trigger(true, 'error', e, transition, infos[infos.length-1]);
- transition.abort();
- }
- throw e;
- }
- }
- /**
- @private
- Begins and returns a Transition based on the provided
- arguments. Accepts arguments in the form of both URL
- transitions and named transitions.
- @param {Router} router
- @param {Array[Object]} args arguments passed to transitionTo,
- replaceWith, or handleURL
- */
- function doTransition(router, args, isIntermediate) {
- // Normalize blank transitions to root URL transitions.
- var name = args[0] || '/';
- var lastArg = args[args.length-1];
- var queryParams = {};
- if (lastArg && lastArg.hasOwnProperty('queryParams')) {
- queryParams = pop.call(args).queryParams;
- }
- var intent;
- if (args.length === 0) {
- log(router, "Updating query params");
- // A query param update is really just a transition
- // into the route you're already on.
- var handlerInfos = router.state.handlerInfos;
- intent = new NamedTransitionIntent({
- name: handlerInfos[handlerInfos.length - 1].name,
- contexts: [],
- queryParams: queryParams
- });
- } else if (name.charAt(0) === '/') {
- log(router, "Attempting URL transition to " + name);
- intent = new URLTransitionIntent({ url: name });
- } else {
- log(router, "Attempting transition to " + name);
- intent = new NamedTransitionIntent({
- name: args[0],
- contexts: slice.call(args, 1),
- queryParams: queryParams
- });
- }
- return router.transitionByIntent(intent, isIntermediate);
- }
- function handlerInfosEqual(handlerInfos, otherHandlerInfos) {
- if (handlerInfos.length !== otherHandlerInfos.length) {
- return false;
- }
- for (var i = 0, len = handlerInfos.length; i < len; ++i) {
- if (handlerInfos[i] !== otherHandlerInfos[i]) {
- return false;
- }
- }
- return true;
- }
- function finalizeQueryParamChange(router, resolvedHandlers, newQueryParams) {
- // We fire a finalizeQueryParamChange event which
- // gives the new route hierarchy a chance to tell
- // us which query params it's consuming and what
- // their final values are. If a query param is
- // no longer consumed in the final route hierarchy,
- // its serialized segment will be removed
- // from the URL.
- var finalQueryParamsArray = [];
- trigger(router, resolvedHandlers, true, ['finalizeQueryParamChange', newQueryParams, finalQueryParamsArray]);
- var finalQueryParams = {};
- for (var i = 0, len = finalQueryParamsArray.length; i < len; ++i) {
- var qp = finalQueryParamsArray[i];
- finalQueryParams[qp.key] = qp.value;
- }
- return finalQueryParams;
- }
- __exports__.Router = Router;
- });
- define("router/transition-intent",
- ["./utils","exports"],
- function(__dependency1__, __exports__) {
- "use strict";
- var merge = __dependency1__.merge;
- function TransitionIntent(props) {
- if (props) {
- merge(this, props);
- }
- this.data = this.data || {};
- }
- TransitionIntent.prototype.applyToState = function(oldState) {
- // Default TransitionIntent is a no-op.
- return oldState;
- };
- __exports__.TransitionIntent = TransitionIntent;
- });
- define("router/transition-intent/named-transition-intent",
- ["../transition-intent","../transition-state","../handler-info","../utils","exports"],
- function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) {
- "use strict";
- var TransitionIntent = __dependency1__.TransitionIntent;
- var TransitionState = __dependency2__.TransitionState;
- var UnresolvedHandlerInfoByParam = __dependency3__.UnresolvedHandlerInfoByParam;
- var UnresolvedHandlerInfoByObject = __dependency3__.UnresolvedHandlerInfoByObject;
- var isParam = __dependency4__.isParam;
- var forEach = __dependency4__.forEach;
- var extractQueryParams = __dependency4__.extractQueryParams;
- var oCreate = __dependency4__.oCreate;
- var merge = __dependency4__.merge;
- function NamedTransitionIntent(props) {
- TransitionIntent.call(this, props);
- }
- NamedTransitionIntent.prototype = oCreate(TransitionIntent.prototype);
- NamedTransitionIntent.prototype.applyToState = function(oldState, recognizer, getHandler, isIntermediate) {
- var partitionedArgs = extractQueryParams([this.name].concat(this.contexts)),
- pureArgs = partitionedArgs[0],
- queryParams = partitionedArgs[1],
- handlers = recognizer.handlersFor(pureArgs[0]);
- var targetRouteName = handlers[handlers.length-1].handler;
- return this.applyToHandlers(oldState, handlers, getHandler, targetRouteName, isIntermediate);
- };
- NamedTransitionIntent.prototype.applyToHandlers = function(oldState, handlers, getHandler, targetRouteName, isIntermediate, checkingIfActive) {
- var i;
- var newState = new TransitionState();
- var objects = this.contexts.slice(0);
- var invalidateIndex = handlers.length;
- var nonDynamicIndexes = [];
- // Pivot handlers are provided for refresh transitions
- if (this.pivotHandler) {
- for (i = 0; i < handlers.length; ++i) {
- if (getHandler(handlers[i].handler) === this.pivotHandler) {
- invalidateIndex = i;
- break;
- }
- }
- }
- var pivotHandlerFound = !this.pivotHandler;
- for (i = handlers.length - 1; i >= 0; --i) {
- var result = handlers[i];
- var name = result.handler;
- var handler = getHandler(name);
- var oldHandlerInfo = oldState.handlerInfos[i];
- var newHandlerInfo = null;
- if (result.names.length > 0) {
- if (i >= invalidateIndex) {
- newHandlerInfo = this.createParamHandlerInfo(name, handler, result.names, objects, oldHandlerInfo);
- } else {
- newHandlerInfo = this.getHandlerInfoForDynamicSegment(name, handler, result.names, objects, oldHandlerInfo, targetRouteName);
- }
- } else {
- // This route has no dynamic segment.
- // Therefore treat as a param-based handlerInfo
- // with empty params. This will cause the `model`
- // hook to be called with empty params, which is desirable.
- newHandlerInfo = this.createParamHandlerInfo(name, handler, result.names, objects, oldHandlerInfo);
- nonDynamicIndexes.unshift(i);
- }
- if (checkingIfActive) {
- // If we're performing an isActive check, we want to
- // serialize URL params with the provided context, but
- // ignore mismatches between old and new context.
- newHandlerInfo = newHandlerInfo.becomeResolved(null, newHandlerInfo.context);
- var oldContext = oldHandlerInfo && oldHandlerInfo.context;
- if (result.names.length > 0 && newHandlerInfo.context === oldContext) {
- // If contexts match in isActive test, assume params also match.
- // This allows for flexibility in not requiring that every last
- // handler provide a `serialize` method
- newHandlerInfo.params = oldHandlerInfo && oldHandlerInfo.params;
- }
- newHandlerInfo.context = oldContext;
- }
- var handlerToUse = oldHandlerInfo;
- if (i >= invalidateIndex || newHandlerInfo.shouldSupercede(oldHandlerInfo)) {
- invalidateIndex = Math.min(i, invalidateIndex);
- handlerToUse = newHandlerInfo;
- }
- if (isIntermediate && !checkingIfActive) {
- handlerToUse = handlerToUse.becomeResolved(null, handlerToUse.context);
- }
- newState.handlerInfos.unshift(handlerToUse);
- }
- if (objects.length > 0) {
- throw new Error("More context objects were passed than there are dynamic segments for the route: " + targetRouteName);
- }
- if (!isIntermediate) {
- this.invalidateNonDynamicHandlers(newState.handlerInfos, nonDynamicIndexes, invalidateIndex);
- }
- merge(newState.queryParams, oldState.queryParams);
- merge(newState.queryParams, this.queryParams || {});
- return newState;
- };
- NamedTransitionIntent.prototype.invalidateNonDynamicHandlers = function(handlerInfos, indexes, invalidateIndex) {
- forEach(indexes, function(i) {
- if (i >= invalidateIndex) {
- var handlerInfo = handlerInfos[i];
- handlerInfos[i] = new UnresolvedHandlerInfoByParam({
- name: handlerInfo.name,
- handler: handlerInfo.handler,
- params: {}
- });
- }
- });
- };
- NamedTransitionIntent.prototype.getHandlerInfoForDynamicSegment = function(name, handler, names, objects, oldHandlerInfo, targetRouteName) {
- var numNames = names.length;
- var objectToUse;
- if (objects.length > 0) {
- // Use the objects provided for this transition.
- objectToUse = objects[objects.length - 1];
- if (isParam(objectToUse)) {
- return this.createParamHandlerInfo(name, handler, names, objects, oldHandlerInfo);
- } else {
- objects.pop();
- }
- } else if (oldHandlerInfo && oldHandlerInfo.name === name) {
- // Reuse the matching oldHandlerInfo
- return oldHandlerInfo;
- } else {
- // Ideally we should throw this error to provide maximal
- // information to the user that not enough context objects
- // were provided, but this proves too cumbersome in Ember
- // in cases where inner template helpers are evaluated
- // before parent helpers un-render, in which cases this
- // error somewhat prematurely fires.
- //throw new Error("Not enough context objects were provided to complete a transition to " + targetRouteName + ". Specifically, the " + name + " route needs an object that can be serialized into its dynamic URL segments [" + names.join(', ') + "]");
- return oldHandlerInfo;
- }
- return new UnresolvedHandlerInfoByObject({
- name: name,
- handler: handler,
- context: objectToUse,
- names: names
- });
- };
- NamedTransitionIntent.prototype.createParamHandlerInfo = function(name, handler, names, objects, oldHandlerInfo) {
- var params = {};
- // Soak up all the provided string/numbers
- var numNames = names.length;
- while (numNames--) {
- // Only use old params if the names match with the new handler
- var oldParams = (oldHandlerInfo && name === oldHandlerInfo.name && oldHandlerInfo.params) || {};
- var peek = objects[objects.length - 1];
- var paramName = names[numNames];
- if (isParam(peek)) {
- params[paramName] = "" + objects.pop();
- } else {
- // If we're here, this means only some of the params
- // were string/number params, so try and use a param
- // value from a previous handler.
- if (oldParams.hasOwnProperty(paramName)) {
- params[paramName] = oldParams[paramName];
- } else {
- throw new Error("You didn't provide enough string/numeric parameters to satisfy all of the dynamic segments for route " + name);
- }
- }
- }
- return new UnresolvedHandlerInfoByParam({
- name: name,
- handler: handler,
- params: params
- });
- };
- __exports__.NamedTransitionIntent = NamedTransitionIntent;
- });
- define("router/transition-intent/url-transition-intent",
- ["../transition-intent","../transition-state","../handler-info","../utils","exports"],
- function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) {
- "use strict";
- var TransitionIntent = __dependency1__.TransitionIntent;
- var TransitionState = __dependency2__.TransitionState;
- var UnresolvedHandlerInfoByParam = __dependency3__.UnresolvedHandlerInfoByParam;
- var oCreate = __dependency4__.oCreate;
- var merge = __dependency4__.merge;
- function URLTransitionIntent(props) {
- TransitionIntent.call(this, props);
- }
- URLTransitionIntent.prototype = oCreate(TransitionIntent.prototype);
- URLTransitionIntent.prototype.applyToState = function(oldState, recognizer, getHandler) {
- var newState = new TransitionState();
- var results = recognizer.recognize(this.url),
- queryParams = {},
- i, len;
- if (!results) {
- throw new UnrecognizedURLError(this.url);
- }
- var statesDiffer = false;
- for (i = 0, len = results.length; i < len; ++i) {
- var result = results[i];
- var name = result.handler;
- var handler = getHandler(name);
- if (handler.inaccessibleByURL) {
- throw new UnrecognizedURLError(this.url);
- }
- var newHandlerInfo = new UnresolvedHandlerInfoByParam({
- name: name,
- handler: handler,
- params: result.params
- });
- var oldHandlerInfo = oldState.handlerInfos[i];
- if (statesDiffer || newHandlerInfo.shouldSupercede(oldHandlerInfo)) {
- statesDiffer = true;
- newState.handlerInfos[i] = newHandlerInfo;
- } else {
- newState.handlerInfos[i] = oldHandlerInfo;
- }
- }
- merge(newState.queryParams, results.queryParams);
- return newState;
- };
- /**
- Promise reject reasons passed to promise rejection
- handlers for failed transitions.
- */
- function UnrecognizedURLError(message) {
- this.message = (message || "UnrecognizedURLError");
- this.name = "UnrecognizedURLError";
- }
- __exports__.URLTransitionIntent = URLTransitionIntent;
- });
- define("router/transition-state",
- ["./handler-info","./utils","rsvp","exports"],
- function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
- "use strict";
- var ResolvedHandlerInfo = __dependency1__.ResolvedHandlerInfo;
- var forEach = __dependency2__.forEach;
- var resolve = __dependency3__.resolve;
- function TransitionState(other) {
- this.handlerInfos = [];
- this.queryParams = {};
- this.params = {};
- }
- TransitionState.prototype = {
- handlerInfos: null,
- queryParams: null,
- params: null,
- resolve: function(async, shouldContinue, payload) {
- // First, calculate params for this state. This is useful
- // information to provide to the various route hooks.
- var params = this.params;
- forEach(this.handlerInfos, function(handlerInfo) {
- params[handlerInfo.name] = handlerInfo.params || {};
- });
- payload = payload || {};
- payload.resolveIndex = 0;
- var currentState = this;
- var wasAborted = false;
- // The prelude RSVP.resolve() asyncs us into the promise land.
- return resolve().then(resolveOneHandlerInfo)['catch'](handleError);
- function innerShouldContinue() {
- return resolve(shouldContinue())['catch'](function(reason) {
- // We distinguish between errors that occurred
- // during resolution (e.g. beforeModel/model/afterModel),
- // and aborts due to a rejecting promise from shouldContinue().
- wasAborted = true;
- throw reason;
- });
- }
- function handleError(error) {
- // This is the only possible
- // reject value of TransitionState#resolve
- throw {
- error: error,
- handlerWithError: currentState.handlerInfos[payload.resolveIndex].handler,
- wasAborted: wasAborted,
- state: currentState
- };
- }
- function proceed(resolvedHandlerInfo) {
- // Swap the previously unresolved handlerInfo with
- // the resolved handlerInfo
- currentState.handlerInfos[payload.resolveIndex++] = resolvedHandlerInfo;
- // Call the redirect hook. The reason we call it here
- // vs. afterModel is so that redirects into child
- // routes don't re-run the model hooks for this
- // already-resolved route.
- var handler = resolvedHandlerInfo.handler;
- if (handler && handler.redirect) {
- handler.redirect(resolvedHandlerInfo.context, payload);
- }
- // Proceed after ensuring that the redirect hook
- // didn't abort this transition by transitioning elsewhere.
- return innerShouldContinue().then(resolveOneHandlerInfo);
- }
- function resolveOneHandlerInfo() {
- if (payload.resolveIndex === currentState.handlerInfos.length) {
- // This is is the only possible
- // fulfill value of TransitionState#resolve
- return {
- error: null,
- state: currentState
- };
- }
- var handlerInfo = currentState.handlerInfos[payload.resolveIndex];
- return handlerInfo.resolve(async, innerShouldContinue, payload)
- .then(proceed);
- }
- }
- };
- __exports__.TransitionState = TransitionState;
- });
- define("router/transition",
- ["rsvp","./handler-info","./utils","exports"],
- function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
- "use strict";
- var reject = __dependency1__.reject;
- var resolve = __dependency1__.resolve;
- var ResolvedHandlerInfo = __dependency2__.ResolvedHandlerInfo;
- var trigger = __dependency3__.trigger;
- var slice = __dependency3__.slice;
- var log = __dependency3__.log;
- /**
- @private
- A Transition is a thennable (a promise-like object) that represents
- an attempt to transition to another route. It can be aborted, either
- explicitly via `abort` or by attempting another transition while a
- previous one is still underway. An aborted transition can also
- be `retry()`d later.
- */
- function Transition(router, intent, state, error) {
- var transition = this;
- this.state = state || router.state;
- this.intent = intent;
- this.router = router;
- this.data = this.intent && this.intent.data || {};
- this.resolvedModels = {};
- this.queryParams = {};
- if (error) {
- this.promise = reject(error);
- return;
- }
- if (state) {
- this.params = state.params;
- this.queryParams = state.queryParams;
- var len = state.handlerInfos.length;
- if (len) {
- this.targetName = state.handlerInfos[state.handlerInfos.length-1].name;
- }
- for (var i = 0; i < len; ++i) {
- var handlerInfo = state.handlerInfos[i];
- if (!(handlerInfo instanceof ResolvedHandlerInfo)) {
- break;
- }
- this.pivotHandler = handlerInfo.handler;
- }
- this.sequence = Transition.currentSequence++;
- this.promise = state.resolve(router.async, checkForAbort, this)['catch'](function(result) {
- if (result.wasAborted) {
- throw logAbort(transition);
- } else {
- transition.trigger('error', result.error, transition, result.handlerWithError);
- transition.abort();
- throw result.error;
- }
- });
- } else {
- this.promise = resolve(this.state);
- this.params = {};
- }
- function checkForAbort() {
- if (transition.isAborted) {
- return reject();
- }
- }
- }
- Transition.currentSequence = 0;
- Transition.prototype = {
- targetName: null,
- urlMethod: 'update',
- intent: null,
- params: null,
- pivotHandler: null,
- resolveIndex: 0,
- handlerInfos: null,
- resolvedModels: null,
- isActive: true,
- state: null,
- /**
- @public
- The Transition's internal promise. Calling `.then` on this property
- is that same as calling `.then` on the Transition object itself, but
- this property is exposed for when you want to pass around a
- Transition's promise, but not the Transition object itself, since
- Transition object can be externally `abort`ed, while the promise
- cannot.
- */
- promise: null,
- /**
- @public
- Custom state can be stored on a Transition's `data` object.
- This can be useful for decorating a Transition within an earlier
- hook and shared with a later hook. Properties set on `data` will
- be copied to new transitions generated by calling `retry` on this
- transition.
- */
- data: null,
- /**
- @public
- A standard promise hook that resolves if the transition
- succeeds and rejects if it fails/redirects/aborts.
- Forwards to the internal `promise` property which you can
- use in situations where you want to pass around a thennable,
- but not the Transition itself.
- @param {Function} success
- @param {Function} failure
- */
- then: function(success, failure) {
- return this.promise.then(success, failure);
- },
- /**
- @public
- Aborts the Transition. Note you can also implicitly abort a transition
- by initiating another transition while a previous one is underway.
- */
- abort: function() {
- if (this.isAborted) { return this; }
- log(this.router, this.sequence, this.targetName + ": transition was aborted");
- this.isAborted = true;
- this.isActive = false;
- this.router.activeTransition = null;
- return this;
- },
- /**
- @public
- Retries a previously-aborted transition (making sure to abort the
- transition if it's still active). Returns a new transition that
- represents the new attempt to transition.
- */
- retry: function() {
- // TODO: add tests for merged state retry()s
- this.abort();
- return this.router.transitionByIntent(this.intent, false);
- },
- /**
- @public
- Sets the URL-changing method to be employed at the end of a
- successful transition. By default, a new Transition will just
- use `updateURL`, but passing 'replace' to this method will
- cause the URL to update using 'replaceWith' instead. Omitting
- a parameter will disable the URL change, allowing for transitions
- that don't update the URL at completion (this is also used for
- handleURL, since the URL has already changed before the
- transition took place).
- @param {String} method the type of URL-changing method to use
- at the end of a transition. Accepted values are 'replace',
- falsy values, or any other non-falsy value (which is
- interpreted as an updateURL transition).
- @return {Transition} this transition
- */
- method: function(method) {
- this.urlMethod = method;
- return this;
- },
- /**
- @public
- Fires an event on the current list of resolved/resolving
- handlers within this transition. Useful for firing events
- on route hierarchies that haven't fully been entered yet.
- Note: This method is also aliased as `send`
- @param {Boolean} ignoreFailure the name of the event to fire
- @param {String} name the name of the event to fire
- */
- trigger: function (ignoreFailure) {
- var args = slice.call(arguments);
- if (typeof ignoreFailure === 'boolean') {
- args.shift();
- } else {
- // Throw errors on unhandled trigger events by default
- ignoreFailure = false;
- }
- trigger(this.router, this.state.handlerInfos.slice(0, this.resolveIndex + 1), ignoreFailure, args);
- },
- /**
- @public
- Transitions are aborted and their promises rejected
- when redirects occur; this method returns a promise
- that will follow any redirects that occur and fulfill
- with the value fulfilled by any redirecting transitions
- that occur.
- @return {Promise} a promise that fulfills with the same
- value that the final redirecting transition fulfills with
- */
- followRedirects: function() {
- var router = this.router;
- return this.promise['catch'](function(reason) {
- if (router.activeTransition) {
- return router.activeTransition.followRedirects();
- }
- throw reason;
- });
- },
- toString: function() {
- return "Transition (sequence " + this.sequence + ")";
- },
- /**
- @private
- */
- log: function(message) {
- log(this.router, this.sequence, message);
- }
- };
- // Alias 'trigger' as 'send'
- Transition.prototype.send = Transition.prototype.trigger;
- /**
- @private
- Logs and returns a TransitionAborted error.
- */
- function logAbort(transition) {
- log(transition.router, transition.sequence, "detected abort.");
- return new TransitionAborted();
- }
- function TransitionAborted(message) {
- this.message = (message || "TransitionAborted");
- this.name = "TransitionAborted";
- }
- __exports__.Transition = Transition;
- __exports__.logAbort = logAbort;
- __exports__.TransitionAborted = TransitionAborted;
- });
- define("router/utils",
- ["exports"],
- function(__exports__) {
- "use strict";
- var slice = Array.prototype.slice;
- function merge(hash, other) {
- for (var prop in other) {
- if (other.hasOwnProperty(prop)) { hash[prop] = other[prop]; }
- }
- }
- var oCreate = Object.create || function(proto) {
- function F() {}
- F.prototype = proto;
- return new F();
- };
- /**
- @private
- Extracts query params from the end of an array
- **/
- function extractQueryParams(array) {
- var len = (array && array.length), head, queryParams;
- if(len && len > 0 && array[len - 1] && array[len - 1].hasOwnProperty('queryParams')) {
- queryParams = array[len - 1].queryParams;
- head = slice.call(array, 0, len - 1);
- return [head, queryParams];
- } else {
- return [array, null];
- }
- }
- /**
- @private
- */
- function log(router, sequence, msg) {
- if (!router.log) { return; }
- if (arguments.length === 3) {
- router.log("Transition #" + sequence + ": " + msg);
- } else {
- msg = sequence;
- router.log(msg);
- }
- }
- function bind(fn, context) {
- var boundArgs = arguments;
- return function(value) {
- var args = slice.call(boundArgs, 2);
- args.push(value);
- return fn.apply(context, args);
- };
- }
- function isParam(object) {
- return (typeof object === "string" || object instanceof String || typeof object === "number" || object instanceof Number);
- }
- function forEach(array, callback) {
- for (var i=0, l=array.length; i<l && false !== callback(array[i]); i++) { }
- }
- /**
- @private
- Serializes a handler using its custom `serialize` method or
- by a default that looks up the expected property name from
- the dynamic segment.
- @param {Object} handler a router handler
- @param {Object} model the model to be serialized for this handler
- @param {Array[Object]} names the names array attached to an
- handler object returned from router.recognizer.handlersFor()
- */
- function serialize(handler, model, names) {
- var object = {};
- if (isParam(model)) {
- object[names[0]] = model;
- return object;
- }
- // Use custom serialize if it exists.
- if (handler.serialize) {
- return handler.serialize(model, names);
- }
- if (names.length !== 1) { return; }
- var name = names[0];
- if (/_id$/.test(name)) {
- object[name] = model.id;
- } else {
- object[name] = model;
- }
- return object;
- }
- function trigger(router, handlerInfos, ignoreFailure, args) {
- if (router.triggerEvent) {
- router.triggerEvent(handlerInfos, ignoreFailure, args);
- return;
- }
- var name = args.shift();
- if (!handlerInfos) {
- if (ignoreFailure) { return; }
- throw new Error("Could not trigger event '" + name + "'. There are no active handlers");
- }
- var eventWasHandled = false;
- for (var i=handlerInfos.length-1; i>=0; i--) {
- var handlerInfo = handlerInfos[i],
- handler = handlerInfo.handler;
- if (handler.events && handler.events[name]) {
- if (handler.events[name].apply(handler, args) === true) {
- eventWasHandled = true;
- } else {
- return;
- }
- }
- }
- if (!eventWasHandled && !ignoreFailure) {
- throw new Error("Nothing handled the event '" + name + "'.");
- }
- }
- function getChangelist(oldObject, newObject) {
- var key;
- var results = {
- all: {},
- changed: {},
- removed: {}
- };
- merge(results.all, newObject);
- var didChange = false;
- // Calculate removals
- for (key in oldObject) {
- if (oldObject.hasOwnProperty(key)) {
- if (!newObject.hasOwnProperty(key)) {
- didChange = true;
- results.removed[key] = oldObject[key];
- }
- }
- }
- // Calculate changes
- for (key in newObject) {
- if (newObject.hasOwnProperty(key)) {
- if (oldObject[key] !== newObject[key]) {
- results.changed[key] = newObject[key];
- didChange = true;
- }
- }
- }
- return didChange && results;
- }
- __exports__.trigger = trigger;
- __exports__.log = log;
- __exports__.oCreate = oCreate;
- __exports__.merge = merge;
- __exports__.extractQueryParams = extractQueryParams;
- __exports__.bind = bind;
- __exports__.isParam = isParam;
- __exports__.forEach = forEach;
- __exports__.slice = slice;
- __exports__.serialize = serialize;
- __exports__.getChangelist = getChangelist;
- });
- define("router",
- ["./router/router","exports"],
- function(__dependency1__, __exports__) {
- "use strict";
- var Router = __dependency1__.Router;
- __exports__.Router = Router;
- });
- })();
- (function() {
- /**
- @module ember
- @submodule ember-routing
- */
- function DSL(name) {
- this.parent = name;
- this.matches = [];
- }
- DSL.prototype = {
- resource: function(name, options, callback) {
- if (arguments.length === 2 && typeof options === 'function') {
- callback = options;
- options = {};
- }
- if (arguments.length === 1) {
- options = {};
- }
- if (typeof options.path !== 'string') {
- options.path = "/" + name;
- }
- if (callback) {
- var dsl = new DSL(name);
- route(dsl, 'loading');
- route(dsl, 'error', { path: "/_unused_dummy_error_path_route_" + name + "/:error" });
- callback.call(dsl);
- this.push(options.path, name, dsl.generate(), options.queryParams);
- } else {
- this.push(options.path, name, null, options.queryParams);
- }
- },
- push: function(url, name, callback, queryParams) {
- var parts = name.split('.');
- if (url === "" || url === "/" || parts[parts.length-1] === "index") { this.explicitIndex = true; }
- this.matches.push([url, name, callback, queryParams]);
- },
- route: function(name, options) {
- route(this, name, options);
- },
- generate: function() {
- var dslMatches = this.matches;
- if (!this.explicitIndex) {
- this.route("index", { path: "/" });
- }
- return function(match) {
- for (var i=0, l=dslMatches.length; i<l; i++) {
- var dslMatch = dslMatches[i];
- var matchObj = match(dslMatch[0]).to(dslMatch[1], dslMatch[2]);
- }
- };
- }
- };
- function route(dsl, name, options) {
- Ember.assert("You must use `this.resource` to nest", typeof options !== 'function');
- options = options || {};
- if (typeof options.path !== 'string') {
- options.path = "/" + name;
- }
- if (dsl.parent && dsl.parent !== 'application') {
- name = dsl.parent + "." + name;
- }
- dsl.push(options.path, name, null, options.queryParams);
- }
- DSL.map = function(callback) {
- var dsl = new DSL();
- callback.call(dsl);
- return dsl;
- };
- Ember.RouterDSL = DSL;
- })();
- (function() {
- var get = Ember.get;
- /**
- @module ember
- @submodule ember-routing
- */
- /**
- Finds a controller instance.
- @for Ember
- @method controllerFor
- @private
- */
- Ember.controllerFor = function(container, controllerName, lookupOptions) {
- return container.lookup('controller:' + controllerName, lookupOptions);
- };
- /**
- Generates a controller factory
- The type of the generated controller factory is derived
- from the context. If the context is an array an array controller
- is generated, if an object, an object controller otherwise, a basic
- controller is generated.
- You can customize your generated controllers by defining
- `App.ObjectController` or `App.ArrayController`.
- @for Ember
- @method generateControllerFactory
- @private
- */
- Ember.generateControllerFactory = function(container, controllerName, context) {
- var Factory, fullName, instance, name, factoryName, controllerType;
- if (context && Ember.isArray(context)) {
- controllerType = 'array';
- } else if (context) {
- controllerType = 'object';
- } else {
- controllerType = 'basic';
- }
- factoryName = 'controller:' + controllerType;
- Factory = container.lookupFactory(factoryName).extend({
- isGenerated: true,
- toString: function() {
- return "(generated " + controllerName + " controller)";
- }
- });
- fullName = 'controller:' + controllerName;
- container.register(fullName, Factory);
- return Factory;
- };
- /**
- Generates and instantiates a controller.
- The type of the generated controller factory is derived
- from the context. If the context is an array an array controller
- is generated, if an object, an object controller otherwise, a basic
- controller is generated.
- @for Ember
- @method generateController
- @private
- */
- Ember.generateController = function(container, controllerName, context) {
- Ember.generateControllerFactory(container, controllerName, context);
- var fullName = 'controller:' + controllerName;
- var instance = container.lookup(fullName);
- if (get(instance, 'namespace.LOG_ACTIVE_GENERATION')) {
- Ember.Logger.info("generated -> " + fullName, { fullName: fullName });
- }
- return instance;
- };
- })();
- (function() {
- /**
- @module ember
- @submodule ember-routing
- */
- var routerJsModule = requireModule("router");
- var Router = routerJsModule.Router;
- var Transition = routerJsModule.Transition;
- var get = Ember.get, set = Ember.set, fmt = Ember.String.fmt;
- var defineProperty = Ember.defineProperty;
- var slice = Array.prototype.slice;
- var forEach = Ember.EnumerableUtils.forEach;
- var DefaultView = Ember._MetamorphView;
- /**
- The `Ember.Router` class manages the application state and URLs. Refer to
- the [routing guide](http://emberjs.com/guides/routing/) for documentation.
- @class Router
- @namespace Ember
- @extends Ember.Object
- */
- Ember.Router = Ember.Object.extend(Ember.Evented, {
- /**
- The `location` property determines the type of URL's that your
- application will use.
- The following location types are currently available:
- * `hash`
- * `history`
- * `none`
- @property location
- @default 'hash'
- @see {Ember.Location}
- */
- location: 'hash',
- init: function() {
- this.router = this.constructor.router || this.constructor.map(Ember.K);
- this._activeViews = {};
- this._setupLocation();
- if (get(this, 'namespace.LOG_TRANSITIONS_INTERNAL')) {
- this.router.log = Ember.Logger.debug;
- }
- },
- /**
- Represents the current URL.
- @method url
- @returns {String} The current URL.
- */
- url: Ember.computed(function() {
- return get(this, 'location').getURL();
- }),
- /**
- Initializes the current router instance and sets up the change handling
- event listeners used by the instances `location` implementation.
- A property named `initialURL` will be used to determine the initial URL.
- If no value is found `/` will be used.
- @method startRouting
- @private
- */
- startRouting: function() {
- this.router = this.router || this.constructor.map(Ember.K);
- var router = this.router,
- location = get(this, 'location'),
- container = this.container,
- self = this,
- initialURL = get(this, 'initialURL');
- this._setupRouter(router, location);
- container.register('view:default', DefaultView);
- container.register('view:toplevel', Ember.View.extend());
- location.onUpdateURL(function(url) {
- self.handleURL(url);
- });
- if (typeof initialURL === "undefined") {
- initialURL = location.getURL();
- }
- this.handleURL(initialURL);
- },
- /**
- Handles updating the paths and notifying any listeners of the URL
- change.
- Triggers the router level `didTransition` hook.
- @method didTransition
- @private
- */
- didTransition: function(infos) {
- updatePaths(this);
- this._cancelLoadingEvent();
- this.notifyPropertyChange('url');
- // Put this in the runloop so url will be accurate. Seems
- // less surprising than didTransition being out of sync.
- Ember.run.once(this, this.trigger, 'didTransition');
- if (get(this, 'namespace').LOG_TRANSITIONS) {
- Ember.Logger.log("Transitioned into '" + Ember.Router._routePath(infos) + "'");
- }
- },
- handleURL: function(url) {
- return this._doTransition('handleURL', [url]);
- },
- transitionTo: function() {
- return this._doTransition('transitionTo', arguments);
- },
- intermediateTransitionTo: function() {
- this.router.intermediateTransitionTo.apply(this.router, arguments);
- updatePaths(this);
- var infos = this.router.currentHandlerInfos;
- if (get(this, 'namespace').LOG_TRANSITIONS) {
- Ember.Logger.log("Intermediate-transitioned into '" + Ember.Router._routePath(infos) + "'");
- }
- },
- replaceWith: function() {
- return this._doTransition('replaceWith', arguments);
- },
- generate: function() {
- var url = this.router.generate.apply(this.router, arguments);
- return this.location.formatURL(url);
- },
- /**
- Determines if the supplied route is currently active.
- @method isActive
- @param routeName
- @returns {Boolean}
- @private
- */
- isActive: function(routeName) {
- var router = this.router;
- return router.isActive.apply(router, arguments);
- },
- send: function(name, context) {
- this.router.trigger.apply(this.router, arguments);
- },
- /**
- Does this router instance have the given route.
- @method hasRoute
- @returns {Boolean}
- @private
- */
- hasRoute: function(route) {
- return this.router.hasRoute(route);
- },
- /**
- Resets the state of the router by clearing the current route
- handlers and deactivating them.
- @private
- @method reset
- */
- reset: function() {
- this.router.reset();
- },
- _lookupActiveView: function(templateName) {
- var active = this._activeViews[templateName];
- return active && active[0];
- },
- _connectActiveView: function(templateName, view) {
- var existing = this._activeViews[templateName];
- if (existing) {
- existing[0].off('willDestroyElement', this, existing[1]);
- }
- var disconnect = function() {
- delete this._activeViews[templateName];
- };
- this._activeViews[templateName] = [view, disconnect];
- view.one('willDestroyElement', this, disconnect);
- },
- _setupLocation: function() {
- var location = get(this, 'location'),
- rootURL = get(this, 'rootURL');
- if ('string' === typeof location && this.container) {
- var resolvedLocation = this.container.lookup('location:' + location);
- if ('undefined' !== typeof resolvedLocation) {
- location = set(this, 'location', resolvedLocation);
- } else {
- // Allow for deprecated registration of custom location API's
- var options = {implementation: location};
- location = set(this, 'location', Ember.Location.create(options));
- }
- }
- if (typeof rootURL === 'string') {
- location.rootURL = rootURL;
- }
- // ensure that initState is called AFTER the rootURL is set on
- // the location instance
- if (typeof location.initState === 'function') { location.initState(); }
- },
- _getHandlerFunction: function() {
- var seen = {}, container = this.container,
- DefaultRoute = container.lookupFactory('route:basic'),
- self = this;
- return function(name) {
- var routeName = 'route:' + name,
- handler = container.lookup(routeName);
- if (seen[name]) { return handler; }
- seen[name] = true;
- if (!handler) {
- container.register(routeName, DefaultRoute.extend());
- handler = container.lookup(routeName);
- if (get(self, 'namespace.LOG_ACTIVE_GENERATION')) {
- Ember.Logger.info("generated -> " + routeName, { fullName: routeName });
- }
- }
- handler.routeName = name;
- return handler;
- };
- },
- _setupRouter: function(router, location) {
- var lastURL, emberRouter = this;
- router.getHandler = this._getHandlerFunction();
- var doUpdateURL = function() {
- location.setURL(lastURL);
- };
- router.updateURL = function(path) {
- lastURL = path;
- Ember.run.once(doUpdateURL);
- };
- if (location.replaceURL) {
- var doReplaceURL = function() {
- location.replaceURL(lastURL);
- };
- router.replaceURL = function(path) {
- lastURL = path;
- Ember.run.once(doReplaceURL);
- };
- }
- router.didTransition = function(infos) {
- emberRouter.didTransition(infos);
- };
- },
- _doTransition: function(method, args) {
- // Normalize blank route to root URL.
- args = slice.call(args);
- args[0] = args[0] || '/';
- var passedName = args[0], name, self = this,
- isQueryParamsOnly = false, queryParams;
-
- if (!isQueryParamsOnly && passedName.charAt(0) !== '/') {
- if (!this.router.hasRoute(passedName)) {
- name = args[0] = passedName + '.index';
- } else {
- name = passedName;
- }
- Ember.assert("The route " + passedName + " was not found", this.router.hasRoute(name));
- }
- if (queryParams) {
- // router.js expects queryParams to be passed in in
- // their final serialized form, so we need to translate.
- if (!name) {
- // Need to determine destination route name.
- var handlerInfos = this.router.activeTransition ?
- this.router.activeTransition.state.handlerInfos :
- this.router.state.handlerInfos;
- name = handlerInfos[handlerInfos.length - 1].name;
- args.unshift(name);
- }
- var qpMappings = this._queryParamNamesFor(name);
- Ember.Router._translateQueryParams(queryParams, qpMappings.translations, name);
- for (var key in queryParams) {
- if (key in qpMappings.queryParams) {
- var value = queryParams[key];
- delete queryParams[key];
- queryParams[qpMappings.queryParams[key]] = value;
- }
- }
- }
- var transitionPromise = this.router[method].apply(this.router, args);
- transitionPromise.then(null, function(error) {
- if (error.name === "UnrecognizedURLError") {
- Ember.assert("The URL '" + error.message + "' did not match any routes in your application");
- }
- }, 'Ember: Check for Router unrecognized URL error');
- // We want to return the configurable promise object
- // so that callers of this function can use `.method()` on it,
- // which obviously doesn't exist for normal RSVP promises.
- return transitionPromise;
- },
- _scheduleLoadingEvent: function(transition, originRoute) {
- this._cancelLoadingEvent();
- this._loadingStateTimer = Ember.run.scheduleOnce('routerTransitions', this, '_fireLoadingEvent', transition, originRoute);
- },
- _fireLoadingEvent: function(transition, originRoute) {
- if (!this.router.activeTransition) {
- // Don't fire an event if we've since moved on from
- // the transition that put us in a loading state.
- return;
- }
- transition.trigger(true, 'loading', transition, originRoute);
- },
- _cancelLoadingEvent: function () {
- if (this._loadingStateTimer) {
- Ember.run.cancel(this._loadingStateTimer);
- }
- this._loadingStateTimer = null;
- },
- _queryParamNamesFor: function(routeName) {
- // TODO: add caching
- routeName = this.router.hasRoute(routeName) ? routeName : routeName + '.index';
- var handlerInfos = this.router.recognizer.handlersFor(routeName);
- var result = { queryParams: Ember.create(null), translations: Ember.create(null) };
- var routerjs = this.router;
- forEach(handlerInfos, function(recogHandler) {
- var route = routerjs.getHandler(recogHandler.handler);
- getQueryParamsForRoute(route, result);
- });
- return result;
- },
- _queryParamNamesForSingle: function(routeName) {
- // TODO: add caching
- var result = { queryParams: Ember.create(null), translations: Ember.create(null) };
- var route = this.router.getHandler(routeName);
- getQueryParamsForRoute(route, result);
- return result;
- },
- /**
- @private
- Utility function for fetching all the current query params
- values from a controller.
- */
- _queryParamOverrides: function(results, queryParams, callback) {
- for (var name in queryParams) {
- var parts = name.split(':');
- var controller = this.container.lookup('controller:' + parts[0]);
- Ember.assert(fmt("Could not lookup controller '%@' while setting up query params", [controller]), controller);
- // Now assign the final URL-serialized key-value pair,
- // e.g. "foo[propName]": "value"
- results[queryParams[name]] = get(controller, parts[1]);
- if (callback) {
- // Give callback a chance to override.
- callback(name, queryParams[name], name);
- }
- }
- }
- });
- /**
- @private
- */
- function getQueryParamsForRoute(route, result) {
- var controllerName = route.controllerName || route.routeName,
- controller = route.controllerFor(controllerName, true);
- if (controller && controller.queryParams) {
- forEach(controller.queryParams, function(propName) {
- var parts = propName.split(':');
- var urlKeyName;
- if (parts.length > 1) {
- urlKeyName = parts[1];
- } else {
- // TODO: use _queryParamScope here?
- if (controllerName !== 'application') {
- urlKeyName = controllerName + '[' + propName + ']';
- } else {
- urlKeyName = propName;
- }
- }
- var controllerFullname = controllerName + ':' + propName;
- result.queryParams[controllerFullname] = urlKeyName;
- result.translations[parts[0]] = controllerFullname;
- });
- }
- }
- /**
- Helper function for iterating root-ward, starting
- from (but not including) the provided `originRoute`.
- Returns true if the last callback fired requested
- to bubble upward.
- @private
- */
- function forEachRouteAbove(originRoute, transition, callback) {
- var handlerInfos = transition.state.handlerInfos,
- originRouteFound = false;
- for (var i = handlerInfos.length - 1; i >= 0; --i) {
- var handlerInfo = handlerInfos[i],
- route = handlerInfo.handler;
- if (!originRouteFound) {
- if (originRoute === route) {
- originRouteFound = true;
- }
- continue;
- }
- if (callback(route, handlerInfos[i + 1].handler) !== true) {
- return false;
- }
- }
- return true;
- }
- // These get invoked when an action bubbles above ApplicationRoute
- // and are not meant to be overridable.
- var defaultActionHandlers = {
- willResolveModel: function(transition, originRoute) {
- originRoute.router._scheduleLoadingEvent(transition, originRoute);
- },
- error: function(error, transition, originRoute) {
- // Attempt to find an appropriate error substate to enter.
- var router = originRoute.router;
- var tryTopLevel = forEachRouteAbove(originRoute, transition, function(route, childRoute) {
- var childErrorRouteName = findChildRouteName(route, childRoute, 'error');
- if (childErrorRouteName) {
- router.intermediateTransitionTo(childErrorRouteName, error);
- return;
- }
- return true;
- });
- if (tryTopLevel) {
- // Check for top-level error state to enter.
- if (routeHasBeenDefined(originRoute.router, 'application_error')) {
- router.intermediateTransitionTo('application_error', error);
- return;
- }
- } else {
- // Don't fire an assertion if we found an error substate.
- return;
- }
- Ember.Logger.error('Error while loading route: ' + error.stack);
- },
- loading: function(transition, originRoute) {
- // Attempt to find an appropriate loading substate to enter.
- var router = originRoute.router;
- var tryTopLevel = forEachRouteAbove(originRoute, transition, function(route, childRoute) {
- var childLoadingRouteName = findChildRouteName(route, childRoute, 'loading');
- if (childLoadingRouteName) {
- router.intermediateTransitionTo(childLoadingRouteName);
- return;
- }
- // Don't bubble above pivot route.
- if (transition.pivotHandler !== route) {
- return true;
- }
- });
- if (tryTopLevel) {
- // Check for top-level loading state to enter.
- if (routeHasBeenDefined(originRoute.router, 'application_loading')) {
- router.intermediateTransitionTo('application_loading');
- return;
- }
- }
- }
- };
- function findChildRouteName(parentRoute, originatingChildRoute, name) {
- var router = parentRoute.router,
- childName,
- targetChildRouteName = originatingChildRoute.routeName.split('.').pop(),
- namespace = parentRoute.routeName === 'application' ? '' : parentRoute.routeName + '.';
-
- // Second, try general loading state, e.g. 'loading'
- childName = namespace + name;
- if (routeHasBeenDefined(router, childName)) {
- return childName;
- }
- }
- function routeHasBeenDefined(router, name) {
- var container = router.container;
- return router.hasRoute(name) &&
- (container.has('template:' + name) || container.has('route:' + name));
- }
- function triggerEvent(handlerInfos, ignoreFailure, args) {
- var name = args.shift();
- if (!handlerInfos) {
- if (ignoreFailure) { return; }
- throw new Ember.Error("Can't trigger action '" + name + "' because your app hasn't finished transitioning into its first route. To trigger an action on destination routes during a transition, you can call `.send()` on the `Transition` object passed to the `model/beforeModel/afterModel` hooks.");
- }
- var eventWasHandled = false;
- for (var i = handlerInfos.length - 1; i >= 0; i--) {
- var handlerInfo = handlerInfos[i],
- handler = handlerInfo.handler;
- if (handler._actions && handler._actions[name]) {
- if (handler._actions[name].apply(handler, args) === true) {
- eventWasHandled = true;
- } else {
- return;
- }
- }
- }
- if (defaultActionHandlers[name]) {
- defaultActionHandlers[name].apply(null, args);
- return;
- }
- if (!eventWasHandled && !ignoreFailure) {
- throw new Ember.Error("Nothing handled the action '" + name + "'. If you did handle the action, this error can be caused by returning true from an action handler in a controller, causing the action to bubble.");
- }
- }
- function updatePaths(router) {
- var appController = router.container.lookup('controller:application');
- if (!appController) {
- // appController might not exist when top-level loading/error
- // substates have been entered since ApplicationRoute hasn't
- // actually been entered at that point.
- return;
- }
- var infos = router.router.currentHandlerInfos,
- path = Ember.Router._routePath(infos);
- if (!('currentPath' in appController)) {
- defineProperty(appController, 'currentPath');
- }
- set(appController, 'currentPath', path);
- if (!('currentRouteName' in appController)) {
- defineProperty(appController, 'currentRouteName');
- }
- set(appController, 'currentRouteName', infos[infos.length - 1].name);
- }
- Ember.Router.reopenClass({
- router: null,
- map: function(callback) {
- var router = this.router;
- if (!router) {
- router = new Router();
- router.callbacks = [];
- router.triggerEvent = triggerEvent;
- this.reopenClass({ router: router });
- }
- var dsl = Ember.RouterDSL.map(function() {
- this.resource('application', { path: "/" }, function() {
- for (var i=0; i < router.callbacks.length; i++) {
- router.callbacks[i].call(this);
- }
- callback.call(this);
- });
- });
- router.callbacks.push(callback);
- router.map(dsl.generate());
- return router;
- },
- _routePath: function(handlerInfos) {
- var path = [];
- // We have to handle coalescing resource names that
- // are prefixed with their parent's names, e.g.
- // ['foo', 'foo.bar.baz'] => 'foo.bar.baz', not 'foo.foo.bar.baz'
- function intersectionMatches(a1, a2) {
- for (var i = 0, len = a1.length; i < len; ++i) {
- if (a1[i] !== a2[i]) {
- return false;
- }
- }
- return true;
- }
- for (var i=1, l=handlerInfos.length; i<l; i++) {
- var name = handlerInfos[i].name,
- nameParts = name.split("."),
- oldNameParts = slice.call(path);
- while (oldNameParts.length) {
- if (intersectionMatches(oldNameParts, nameParts)) {
- break;
- }
- oldNameParts.shift();
- }
- path.push.apply(path, nameParts.slice(oldNameParts.length));
- }
- return path.join(".");
- },
- _translateQueryParams: function(queryParams, translations, routeName) {
- for (var name in queryParams) {
- if (!queryParams.hasOwnProperty(name)) { continue; }
- if (name in translations) {
- queryParams[translations[name]] = queryParams[name];
- delete queryParams[name];
- } else {
- Ember.assert(fmt("You supplied an unknown query param controller property '%@' for route '%@'. Only the following query param properties can be set for this route: %@", [name, routeName, Ember.keys(translations)]), name in queryParams);
- }
- }
- }
- });
- })();
- (function() {
- /**
- @module ember
- @submodule ember-routing
- */
- var get = Ember.get, set = Ember.set,
- getProperties = Ember.getProperties,
- classify = Ember.String.classify,
- fmt = Ember.String.fmt,
- a_forEach = Ember.EnumerableUtils.forEach,
- a_replace = Ember.EnumerableUtils.replace;
- /**
- The `Ember.Route` class is used to define individual routes. Refer to
- the [routing guide](http://emberjs.com/guides/routing/) for documentation.
- @class Route
- @namespace Ember
- @extends Ember.Object
- @uses Ember.ActionHandler
- */
- Ember.Route = Ember.Object.extend(Ember.ActionHandler, {
- /**
- @private
- @method exit
- */
- exit: function() {
- this.deactivate();
- this.teardownViews();
- },
- /**
- @private
- @method enter
- */
- enter: function() {
- this.activate();
- },
- /**
- The name of the view to use by default when rendering this routes template.
- When rendering a template, the route will, by default, determine the
- template and view to use from the name of the route itself. If you need to
- define a specific view, set this property.
- This is useful when multiple routes would benefit from using the same view
- because it doesn't require a custom `renderTemplate` method. For example,
- the following routes will all render using the `App.PostsListView` view:
- ```js
- var PostsList = Ember.Route.extend({
- viewName: 'postsList',
- });
- App.PostsIndexRoute = PostsList.extend();
- App.PostsArchivedRoute = PostsList.extend();
- ```
- @property viewName
- @type String
- @default null
- */
- viewName: null,
- /**
- The name of the template to use by default when rendering this routes
- template.
- This is similar with `viewName`, but is useful when you just want a custom
- template without a view.
- ```js
- var PostsList = Ember.Route.extend({
- templateName: 'posts/list'
- });
- App.PostsIndexRoute = PostsList.extend();
- App.PostsArchivedRoute = PostsList.extend();
- ```
- @property templateName
- @type String
- @default null
- */
- templateName: null,
- /**
- The name of the controller to associate with this route.
- By default, Ember will lookup a route's controller that matches the name
- of the route (i.e. `App.PostController` for `App.PostRoute`). However,
- if you would like to define a specific controller to use, you can do so
- using this property.
- This is useful in many ways, as the controller specified will be:
- * passed to the `setupController` method.
- * used as the controller for the view being rendered by the route.
- * returned from a call to `controllerFor` for the route.
- @property controllerName
- @type String
- @default null
- */
- controllerName: null,
- /**
- The collection of functions, keyed by name, available on this route as
- action targets.
- These functions will be invoked when a matching `{{action}}` is triggered
- from within a template and the application's current route is this route.
- Actions can also be invoked from other parts of your application via `Route#send`
- or `Controller#send`.
- The `actions` hash will inherit action handlers from
- the `actions` hash defined on extended Route parent classes
- or mixins rather than just replace the entire hash, e.g.:
- ```js
- App.CanDisplayBanner = Ember.Mixin.create({
- actions: {
- displayBanner: function(msg) {
- // ...
- }
- }
- });
- App.WelcomeRoute = Ember.Route.extend(App.CanDisplayBanner, {
- actions: {
- playMusic: function() {
- // ...
- }
- }
- });
- // `WelcomeRoute`, when active, will be able to respond
- // to both actions, since the actions hash is merged rather
- // then replaced when extending mixins / parent classes.
- this.send('displayBanner');
- this.send('playMusic');
- ```
- Within a route's action handler, the value of the `this` context
- is the Route object:
- ```js
- App.SongRoute = Ember.Route.extend({
- actions: {
- myAction: function() {
- this.controllerFor("song");
- this.transitionTo("other.route");
- ...
- }
- }
- });
- ```
- It is also possible to call `this._super()` from within an
- action handler if it overrides a handler defined on a parent
- class or mixin:
- Take for example the following routes:
- ```js
- App.DebugRoute = Ember.Mixin.create({
- actions: {
- debugRouteInformation: function() {
- console.debug("trololo");
- }
- }
- });
- App.AnnoyingDebugRoute = Ember.Route.extend(App.DebugRoute, {
- actions: {
- debugRouteInformation: function() {
- // also call the debugRouteInformation of mixed in App.DebugRoute
- this._super();
- // show additional annoyance
- window.alert(...);
- }
- }
- });
- ```
- ## Bubbling
- By default, an action will stop bubbling once a handler defined
- on the `actions` hash handles it. To continue bubbling the action,
- you must return `true` from the handler:
- ```js
- App.Router.map(function() {
- this.resource("album", function() {
- this.route("song");
- });
- });
- App.AlbumRoute = Ember.Route.extend({
- actions: {
- startPlaying: function() {
- }
- }
- });
- App.AlbumSongRoute = Ember.Route.extend({
- actions: {
- startPlaying: function() {
- // ...
- if (actionShouldAlsoBeTriggeredOnParentRoute) {
- return true;
- }
- }
- }
- });
- ```
- ## Built-in actions
- There are a few built-in actions pertaining to transitions that you
- can use to customize transition behavior: `willTransition` and
- `error`.
- ### `willTransition`
- The `willTransition` action is fired at the beginning of any
- attempted transition with a `Transition` object as the sole
- argument. This action can be used for aborting, redirecting,
- or decorating the transition from the currently active routes.
- A good example is preventing navigation when a form is
- half-filled out:
- ```js
- App.ContactFormRoute = Ember.Route.extend({
- actions: {
- willTransition: function(transition) {
- if (this.controller.get('userHasEnteredData')) {
- this.controller.displayNavigationConfirm();
- transition.abort();
- }
- }
- }
- });
- ```
- You can also redirect elsewhere by calling
- `this.transitionTo('elsewhere')` from within `willTransition`.
- Note that `willTransition` will not be fired for the
- redirecting `transitionTo`, since `willTransition` doesn't
- fire when there is already a transition underway. If you want
- subsequent `willTransition` actions to fire for the redirecting
- transition, you must first explicitly call
- `transition.abort()`.
- ### `error`
- When attempting to transition into a route, any of the hooks
- may return a promise that rejects, at which point an `error`
- action will be fired on the partially-entered routes, allowing
- for per-route error handling logic, or shared error handling
- logic defined on a parent route.
- Here is an example of an error handler that will be invoked
- for rejected promises from the various hooks on the route,
- as well as any unhandled errors from child routes:
- ```js
- App.AdminRoute = Ember.Route.extend({
- beforeModel: function() {
- return Ember.RSVP.reject("bad things!");
- },
- actions: {
- error: function(error, transition) {
- // Assuming we got here due to the error in `beforeModel`,
- // we can expect that error === "bad things!",
- // but a promise model rejecting would also
- // call this hook, as would any errors encountered
- // in `afterModel`.
- // The `error` hook is also provided the failed
- // `transition`, which can be stored and later
- // `.retry()`d if desired.
- this.transitionTo('login');
- }
- }
- });
- ```
- `error` actions that bubble up all the way to `ApplicationRoute`
- will fire a default error handler that logs the error. You can
- specify your own global default error handler by overriding the
- `error` handler on `ApplicationRoute`:
- ```js
- App.ApplicationRoute = Ember.Route.extend({
- actions: {
- error: function(error, transition) {
- this.controllerFor('banner').displayError(error.message);
- }
- }
- });
- ```
- @property actions
- @type Hash
- @default null
- */
- _actions: {
- finalizeQueryParamChange: function(params, finalParams) {
- }
- },
- /**
- @deprecated
- Please use `actions` instead.
- @method events
- */
- events: null,
- mergedProperties: ['events'],
- /**
- This hook is executed when the router completely exits this route. It is
- not executed when the model for the route changes.
- @method deactivate
- */
- deactivate: Ember.K,
- /**
- This hook is executed when the router enters the route. It is not executed
- when the model for the route changes.
- @method activate
- */
- activate: Ember.K,
- /**
- Transition into another route. Optionally supply model(s) for the
- route in question. If multiple models are supplied they will be applied
- last to first recursively up the resource tree (see Multiple Models Example
- below). The model(s) will be serialized into the URL using the appropriate
- route's `serialize` hook. See also 'replaceWith'.
- Simple Transition Example
- ```javascript
- App.Router.map(function() {
- this.route("index");
- this.route("secret");
- this.route("fourOhFour", { path: "*:"});
- });
- App.IndexRoute = Ember.Route.extend({
- actions: {
- moveToSecret: function(context){
- if (authorized()){
- this.transitionTo('secret', context);
- }
- this.transitionTo('fourOhFour');
- }
- }
- });
- ```
- Transition to a nested route
- ```javascript
- App.Router.map(function() {
- this.resource('articles', { path: '/articles' }, function() {
- this.route('new');
- });
- });
- App.IndexRoute = Ember.Route.extend({
- actions: {
- transitionToNewArticle: function() {
- this.transitionTo('articles.new');
- }
- }
- });
- ```
- Multiple Models Example
- ```javascript
- App.Router.map(function() {
- this.route("index");
- this.resource('breakfast', {path:':breakfastId'}, function(){
- this.resource('cereal', {path: ':cerealId'});
- });
- });
- App.IndexRoute = Ember.Route.extend({
- actions: {
- moveToChocolateCereal: function(){
- var cereal = { cerealId: "ChocolateYumminess"},
- breakfast = {breakfastId: "CerealAndMilk"};
- this.transitionTo('cereal', breakfast, cereal);
- }
- }
- });
- @method transitionTo
- @param {String} name the name of the route
- @param {...Object} models the model(s) to be used while transitioning
- to the route.
- @return {Transition} the transition object associated with this
- attempted transition
- */
- transitionTo: function(name, context) {
- var router = this.router;
- return router.transitionTo.apply(router, arguments);
- },
- /**
- Perform a synchronous transition into another route without attempting
- to resolve promises, update the URL, or abort any currently active
- asynchronous transitions (i.e. regular transitions caused by
- `transitionTo` or URL changes).
- This method is handy for performing intermediate transitions on the
- way to a final destination route, and is called internally by the
- default implementations of the `error` and `loading` handlers.
- @method intermediateTransitionTo
- @param {String} name the name of the route
- @param {...Object} models the model(s) to be used while transitioning
- to the route.
- */
- intermediateTransitionTo: function() {
- var router = this.router;
- router.intermediateTransitionTo.apply(router, arguments);
- },
- /**
- Refresh the model on this route and any child routes, firing the
- `beforeModel`, `model`, and `afterModel` hooks in a similar fashion
- to how routes are entered when transitioning in from other route.
- The current route params (e.g. `article_id`) will be passed in
- to the respective model hooks, and if a different model is returned,
- `setupController` and associated route hooks will re-fire as well.
- An example usage of this method is re-querying the server for the
- latest information using the same parameters as when the route
- was first entered.
- Note that this will cause `model` hooks to fire even on routes
- that were provided a model object when the route was initially
- entered.
- @method refresh
- @return {Transition} the transition object associated with this
- attempted transition
- */
- refresh: function() {
- return this.router.router.refresh(this).method('replace');
- },
- /**
- Transition into another route while replacing the current URL, if possible.
- This will replace the current history entry instead of adding a new one.
- Beside that, it is identical to `transitionTo` in all other respects. See
- 'transitionTo' for additional information regarding multiple models.
- Example
- ```javascript
- App.Router.map(function() {
- this.route("index");
- this.route("secret");
- });
- App.SecretRoute = Ember.Route.extend({
- afterModel: function() {
- if (!authorized()){
- this.replaceWith('index');
- }
- }
- });
- ```
- @method replaceWith
- @param {String} name the name of the route
- @param {...Object} models the model(s) to be used while transitioning
- to the route.
- @return {Transition} the transition object associated with this
- attempted transition
- */
- replaceWith: function() {
- var router = this.router;
- return router.replaceWith.apply(router, arguments);
- },
- /**
- Sends an action to the router, which will delegate it to the currently
- active route hierarchy per the bubbling rules explained under `actions`.
- Example
- ```javascript
- App.Router.map(function() {
- this.route("index");
- });
- App.ApplicationRoute = Ember.Route.extend({
- actions: {
- track: function(arg) {
- console.log(arg, 'was clicked');
- }
- }
- });
- App.IndexRoute = Ember.Route.extend({
- actions: {
- trackIfDebug: function(arg) {
- if (debug) {
- this.send('track', arg);
- }
- }
- }
- });
- ```
- @method send
- @param {String} name the name of the action to trigger
- @param {...*} args
- */
- send: function() {
- return this.router.send.apply(this.router, arguments);
- },
- /**
- This hook is the entry point for router.js
- @private
- @method setup
- */
- setup: function(context, transition) {
- var controllerName = this.controllerName || this.routeName,
- controller = this.controllerFor(controllerName, true);
- if (!controller) {
- controller = this.generateController(controllerName, context);
- }
- // Assign the route's controller so that it can more easily be
- // referenced in action handlers
- this.controller = controller;
-
- if (this.setupControllers) {
- Ember.deprecate("Ember.Route.setupControllers is deprecated. Please use Ember.Route.setupController(controller, model) instead.");
- this.setupControllers(controller, context);
- } else {
-
- this.setupController(controller, context);
-
- }
- if (this.renderTemplates) {
- Ember.deprecate("Ember.Route.renderTemplates is deprecated. Please use Ember.Route.renderTemplate(controller, model) instead.");
- this.renderTemplates(context);
- } else {
- this.renderTemplate(controller, context);
- }
- },
- /**
- This hook is the first of the route entry validation hooks
- called when an attempt is made to transition into a route
- or one of its children. It is called before `model` and
- `afterModel`, and is appropriate for cases when:
- 1) A decision can be made to redirect elsewhere without
- needing to resolve the model first.
- 2) Any async operations need to occur first before the
- model is attempted to be resolved.
- This hook is provided the current `transition` attempt
- as a parameter, which can be used to `.abort()` the transition,
- save it for a later `.retry()`, or retrieve values set
- on it from a previous hook. You can also just call
- `this.transitionTo` to another route to implicitly
- abort the `transition`.
- You can return a promise from this hook to pause the
- transition until the promise resolves (or rejects). This could
- be useful, for instance, for retrieving async code from
- the server that is required to enter a route.
- ```js
- App.PostRoute = Ember.Route.extend({
- beforeModel: function(transition) {
- if (!App.Post) {
- return Ember.$.getScript('/models/post.js');
- }
- }
- });
- ```
- If `App.Post` doesn't exist in the above example,
- `beforeModel` will use jQuery's `getScript`, which
- returns a promise that resolves after the server has
- successfully retrieved and executed the code from the
- server. Note that if an error were to occur, it would
- be passed to the `error` hook on `Ember.Route`, but
- it's also possible to handle errors specific to
- `beforeModel` right from within the hook (to distinguish
- from the shared error handling behavior of the `error`
- hook):
- ```js
- App.PostRoute = Ember.Route.extend({
- beforeModel: function(transition) {
- if (!App.Post) {
- var self = this;
- return Ember.$.getScript('post.js').then(null, function(e) {
- self.transitionTo('help');
- // Note that the above transitionTo will implicitly
- // halt the transition. If you were to return
- // nothing from this promise reject handler,
- // according to promise semantics, that would
- // convert the reject into a resolve and the
- // transition would continue. To propagate the
- // error so that it'd be handled by the `error`
- // hook, you would have to either
- return Ember.RSVP.reject(e);
- });
- }
- }
- });
- ```
- @method beforeModel
- @param {Transition} transition
- @param {Object} queryParams the active query params for this route
- @return {Promise} if the value returned from this hook is
- a promise, the transition will pause until the transition
- resolves. Otherwise, non-promise return values are not
- utilized in any way.
- */
- beforeModel: Ember.K,
- /**
- This hook is called after this route's model has resolved.
- It follows identical async/promise semantics to `beforeModel`
- but is provided the route's resolved model in addition to
- the `transition`, and is therefore suited to performing
- logic that can only take place after the model has already
- resolved.
- ```js
- App.PostsRoute = Ember.Route.extend({
- afterModel: function(posts, transition) {
- if (posts.length === 1) {
- this.transitionTo('post.show', posts[0]);
- }
- }
- });
- ```
- Refer to documentation for `beforeModel` for a description
- of transition-pausing semantics when a promise is returned
- from this hook.
- @method afterModel
- @param {Object} resolvedModel the value returned from `model`,
- or its resolved value if it was a promise
- @param {Transition} transition
- @param {Object} queryParams the active query params for this handler
- @return {Promise} if the value returned from this hook is
- a promise, the transition will pause until the transition
- resolves. Otherwise, non-promise return values are not
- utilized in any way.
- */
- afterModel: Ember.K,
- /**
- A hook you can implement to optionally redirect to another route.
- If you call `this.transitionTo` from inside of this hook, this route
- will not be entered in favor of the other hook.
- `redirect` and `afterModel` behave very similarly and are
- called almost at the same time, but they have an important
- distinction in the case that, from one of these hooks, a
- redirect into a child route of this route occurs: redirects
- from `afterModel` essentially invalidate the current attempt
- to enter this route, and will result in this route's `beforeModel`,
- `model`, and `afterModel` hooks being fired again within
- the new, redirecting transition. Redirects that occur within
- the `redirect` hook, on the other hand, will _not_ cause
- these hooks to be fired again the second time around; in
- other words, by the time the `redirect` hook has been called,
- both the resolved model and attempted entry into this route
- are considered to be fully validated.
- @method redirect
- @param {Object} model the model for this route
- */
- redirect: Ember.K,
- /**
- Called when the context is changed by router.js.
- @private
- @method contextDidChange
- */
- contextDidChange: function() {
- this.currentModel = this.context;
- },
- /**
- A hook you can implement to convert the URL into the model for
- this route.
- ```js
- App.Router.map(function() {
- this.resource('post', {path: '/posts/:post_id'});
- });
- ```
- The model for the `post` route is `App.Post.find(params.post_id)`.
- By default, if your route has a dynamic segment ending in `_id`:
- * The model class is determined from the segment (`post_id`'s
- class is `App.Post`)
- * The find method is called on the model class with the value of
- the dynamic segment.
- Note that for routes with dynamic segments, this hook is only
- executed when entered via the URL. If the route is entered
- through a transition (e.g. when using the `link-to` Handlebars
- helper), then a model context is already provided and this hook
- is not called. Routes without dynamic segments will always
- execute the model hook.
- This hook follows the asynchronous/promise semantics
- described in the documentation for `beforeModel`. In particular,
- if a promise returned from `model` fails, the error will be
- handled by the `error` hook on `Ember.Route`.
- Example
- ```js
- App.PostRoute = Ember.Route.extend({
- model: function(params) {
- return App.Post.find(params.post_id);
- }
- });
- ```
- @method model
- @param {Object} params the parameters extracted from the URL
- @param {Transition} transition
- @param {Object} queryParams the query params for this route
- @return {Object|Promise} the model for this route. If
- a promise is returned, the transition will pause until
- the promise resolves, and the resolved value of the promise
- will be used as the model for this route.
- */
- model: function(params, transition) {
- var match, name, sawParams, value;
- for (var prop in params) {
- if (prop === 'queryParams') { continue; }
- if (match = prop.match(/^(.*)_id$/)) {
- name = match[1];
- value = params[prop];
- }
- sawParams = true;
- }
- if (!name && sawParams) { return params; }
- else if (!name) { return; }
- return this.findModel(name, value);
- },
- /**
- @private
- Router.js hook.
- */
- deserialize: function(params, transition) {
-
- return this.model(params, transition);
-
- },
- /**
- @method findModel
- @param {String} type the model type
- @param {Object} value the value passed to find
- */
- findModel: function(){
- var store = get(this, 'store');
- return store.find.apply(store, arguments);
- },
- /**
- Store property provides a hook for data persistence libraries to inject themselves.
- By default, this store property provides the exact same functionality previously
- in the model hook.
- Currently, the required interface is:
- `store.find(modelName, findArguments)`
- @method store
- @param {Object} store
- */
- store: Ember.computed(function(){
- var container = this.container;
- var routeName = this.routeName;
- var namespace = get(this, 'router.namespace');
- return {
- find: function(name, value) {
- var modelClass = container.lookupFactory('model:' + name);
- Ember.assert("You used the dynamic segment " + name + "_id in your route " +
- routeName + ", but " + namespace + "." + classify(name) +
- " did not exist and you did not override your route's `model` " +
- "hook.", modelClass);
- if (!modelClass) { return; }
- return modelClass.find(value);
- }
- };
- }),
- /**
- A hook you can implement to convert the route's model into parameters
- for the URL.
- ```js
- App.Router.map(function() {
- this.resource('post', {path: '/posts/:post_id'});
- });
- App.PostRoute = Ember.Route.extend({
- model: function(params) {
- // the server returns `{ id: 12 }`
- return jQuery.getJSON("/posts/" + params.post_id);
- },
- serialize: function(model) {
- // this will make the URL `/posts/12`
- return { post_id: model.id };
- }
- });
- ```
- The default `serialize` method will insert the model's `id` into the
- route's dynamic segment (in this case, `:post_id`) if the segment contains '_id'.
- If the route has multiple dynamic segments or does not contain '_id', `serialize`
- will return `Ember.getProperties(model, params)`
- This method is called when `transitionTo` is called with a context
- in order to populate the URL.
- @method serialize
- @param {Object} model the route's model
- @param {Array} params an Array of parameter names for the current
- route (in the example, `['post_id']`.
- @return {Object} the serialized parameters
- */
- serialize: function(model, params) {
- if (params.length < 1) { return; }
- if (!model) { return; }
- var name = params[0], object = {};
- if (/_id$/.test(name) && params.length === 1) {
- object[name] = get(model, "id");
- } else {
- object = getProperties(model, params);
- }
- return object;
- },
- /**
- A hook you can use to setup the controller for the current route.
- This method is called with the controller for the current route and the
- model supplied by the `model` hook.
- By default, the `setupController` hook sets the `content` property of
- the controller to the `model`.
- This means that your template will get a proxy for the model as its
- context, and you can act as though the model itself was the context.
- The provided controller will be one resolved based on the name
- of this route.
- If no explicit controller is defined, Ember will automatically create
- an appropriate controller for the model.
- * if the model is an `Ember.Array` (including record arrays from Ember
- Data), the controller is an `Ember.ArrayController`.
- * otherwise, the controller is an `Ember.ObjectController`.
- As an example, consider the router:
- ```js
- App.Router.map(function() {
- this.resource('post', {path: '/posts/:post_id'});
- });
- ```
- For the `post` route, a controller named `App.PostController` would
- be used if it is defined. If it is not defined, an `Ember.ObjectController`
- instance would be used.
- Example
- ```js
- App.PostRoute = Ember.Route.extend({
- setupController: function(controller, model) {
- controller.set('model', model);
- }
- });
- ```
- @method setupController
- @param {Controller} controller instance
- @param {Object} model
- */
- setupController: function(controller, context, transition) {
- if (controller && (context !== undefined)) {
- set(controller, 'model', context);
- }
- },
- /**
- Returns the controller for a particular route or name.
- The controller instance must already have been created, either through entering the
- associated route or using `generateController`.
- ```js
- App.PostRoute = Ember.Route.extend({
- setupController: function(controller, post) {
- this._super(controller, post);
- this.controllerFor('posts').set('currentPost', post);
- }
- });
- ```
- @method controllerFor
- @param {String} name the name of the route or controller
- @return {Ember.Controller}
- */
- controllerFor: function(name, _skipAssert) {
- var container = this.container,
- route = container.lookup('route:'+name),
- controller;
- if (route && route.controllerName) {
- name = route.controllerName;
- }
- controller = container.lookup('controller:' + name);
- // NOTE: We're specifically checking that skipAssert is true, because according
- // to the old API the second parameter was model. We do not want people who
- // passed a model to skip the assertion.
- Ember.assert("The controller named '"+name+"' could not be found. Make sure " +
- "that this route exists and has already been entered at least " +
- "once. If you are accessing a controller not associated with a " +
- "route, make sure the controller class is explicitly defined.",
- controller || _skipAssert === true);
- return controller;
- },
- /**
- Generates a controller for a route.
- If the optional model is passed then the controller type is determined automatically,
- e.g., an ArrayController for arrays.
- Example
- ```js
- App.PostRoute = Ember.Route.extend({
- setupController: function(controller, post) {
- this._super(controller, post);
- this.generateController('posts', post);
- }
- });
- ```
- @method generateController
- @param {String} name the name of the controller
- @param {Object} model the model to infer the type of the controller (optional)
- */
- generateController: function(name, model) {
- var container = this.container;
- model = model || this.modelFor(name);
- return Ember.generateController(container, name, model);
- },
- /**
- Returns the model of a parent (or any ancestor) route
- in a route hierarchy. During a transition, all routes
- must resolve a model object, and if a route
- needs access to a parent route's model in order to
- resolve a model (or just reuse the model from a parent),
- it can call `this.modelFor(theNameOfParentRoute)` to
- retrieve it.
- Example
- ```js
- App.Router.map(function() {
- this.resource('post', { path: '/post/:post_id' }, function() {
- this.resource('comments');
- });
- });
- App.CommentsRoute = Ember.Route.extend({
- afterModel: function() {
- this.set('post', this.modelFor('post'));
- }
- });
- ```
- @method modelFor
- @param {String} name the name of the route
- @return {Object} the model object
- */
- modelFor: function(name) {
- var route = this.container.lookup('route:' + name),
- transition = this.router.router.activeTransition;
- // If we are mid-transition, we want to try and look up
- // resolved parent contexts on the current transitionEvent.
- if (transition) {
- var modelLookupName = (route && route.routeName) || name;
- if (transition.resolvedModels.hasOwnProperty(modelLookupName)) {
- return transition.resolvedModels[modelLookupName];
- }
- }
- return route && route.currentModel;
- },
- /**
- A hook you can use to render the template for the current route.
- This method is called with the controller for the current route and the
- model supplied by the `model` hook. By default, it renders the route's
- template, configured with the controller for the route.
- This method can be overridden to set up and render additional or
- alternative templates.
- ```js
- App.PostsRoute = Ember.Route.extend({
- renderTemplate: function(controller, model) {
- var favController = this.controllerFor('favoritePost');
- // Render the `favoritePost` template into
- // the outlet `posts`, and display the `favoritePost`
- // controller.
- this.render('favoritePost', {
- outlet: 'posts',
- controller: favController
- });
- }
- });
- ```
- @method renderTemplate
- @param {Object} controller the route's controller
- @param {Object} model the route's model
- */
- renderTemplate: function(controller, model) {
- this.render();
- },
- /**
- Renders a template into an outlet.
- This method has a number of defaults, based on the name of the
- route specified in the router.
- For example:
- ```js
- App.Router.map(function() {
- this.route('index');
- this.resource('post', {path: '/posts/:post_id'});
- });
- App.PostRoute = App.Route.extend({
- renderTemplate: function() {
- this.render();
- }
- });
- ```
- The name of the `PostRoute`, as defined by the router, is `post`.
- By default, render will:
- * render the `post` template
- * with the `post` view (`PostView`) for event handling, if one exists
- * and the `post` controller (`PostController`), if one exists
- * into the `main` outlet of the `application` template
- You can override this behavior:
- ```js
- App.PostRoute = App.Route.extend({
- renderTemplate: function() {
- this.render('myPost', { // the template to render
- into: 'index', // the template to render into
- outlet: 'detail', // the name of the outlet in that template
- controller: 'blogPost' // the controller to use for the template
- });
- }
- });
- ```
- Remember that the controller's `content` will be the route's model. In
- this case, the default model will be `App.Post.find(params.post_id)`.
- @method render
- @param {String} name the name of the template to render
- @param {Object} options the options
- */
- render: function(name, options) {
- Ember.assert("The name in the given arguments is undefined", arguments.length > 0 ? !Ember.isNone(arguments[0]) : true);
- var namePassed = !!name;
- if (typeof name === 'object' && !options) {
- options = name;
- name = this.routeName;
- }
- options = options || {};
- var templateName;
- if (name) {
- name = name.replace(/\//g, '.');
- templateName = name;
- } else {
- name = this.routeName;
- templateName = this.templateName || name;
- }
- var viewName = options.view || this.viewName || name;
- var container = this.container,
- view = container.lookup('view:' + viewName),
- template = view ? view.get('template') : null;
- if (!template) {
- template = container.lookup('template:' + templateName);
- }
- if (!view && !template) {
- Ember.assert("Could not find \"" + name + "\" template or view.", !namePassed);
- if (get(this.router, 'namespace.LOG_VIEW_LOOKUPS')) {
- Ember.Logger.info("Could not find \"" + name + "\" template or view. Nothing will be rendered", { fullName: 'template:' + name });
- }
- return;
- }
- options = normalizeOptions(this, name, template, options);
- view = setupView(view, container, options);
- if (options.outlet === 'main') { this.lastRenderedTemplate = name; }
- appendView(this, view, options);
- },
- /**
- Disconnects a view that has been rendered into an outlet.
- You may pass any or all of the following options to `disconnectOutlet`:
- * `outlet`: the name of the outlet to clear (default: 'main')
- * `parentView`: the name of the view containing the outlet to clear
- (default: the view rendered by the parent route)
- Example:
- ```js
- App.ApplicationRoute = App.Route.extend({
- actions: {
- showModal: function(evt) {
- this.render(evt.modalName, {
- outlet: 'modal',
- into: 'application'
- });
- },
- hideModal: function(evt) {
- this.disconnectOutlet({
- outlet: 'modal',
- parentView: 'application'
- });
- }
- }
- });
- ```
- @method disconnectOutlet
- @param {Object} options the options
- */
- disconnectOutlet: function(options) {
- options = options || {};
- options.parentView = options.parentView ? options.parentView.replace(/\//g, '.') : parentTemplate(this);
- options.outlet = options.outlet || 'main';
- var parentView = this.router._lookupActiveView(options.parentView);
- if (parentView) { parentView.disconnectOutlet(options.outlet); }
- },
- willDestroy: function() {
- this.teardownViews();
- },
- /**
- @private
- @method teardownViews
- */
- teardownViews: function() {
- // Tear down the top level view
- if (this.teardownTopLevelView) { this.teardownTopLevelView(); }
- // Tear down any outlets rendered with 'into'
- var teardownOutletViews = this.teardownOutletViews || [];
- a_forEach(teardownOutletViews, function(teardownOutletView) {
- teardownOutletView();
- });
- delete this.teardownTopLevelView;
- delete this.teardownOutletViews;
- delete this.lastRenderedTemplate;
- }
- });
- function parentRoute(route) {
- var handlerInfos = route.router.router.state.handlerInfos;
- if (!handlerInfos) { return; }
- var parent, current;
- for (var i=0, l=handlerInfos.length; i<l; i++) {
- current = handlerInfos[i].handler;
- if (current === route) { return parent; }
- parent = current;
- }
- }
- function parentTemplate(route) {
- var parent = parentRoute(route), template;
- if (!parent) { return; }
- if (template = parent.lastRenderedTemplate) {
- return template;
- } else {
- return parentTemplate(parent);
- }
- }
- function normalizeOptions(route, name, template, options) {
- options = options || {};
- options.into = options.into ? options.into.replace(/\//g, '.') : parentTemplate(route);
- options.outlet = options.outlet || 'main';
- options.name = name;
- options.template = template;
- options.LOG_VIEW_LOOKUPS = get(route.router, 'namespace.LOG_VIEW_LOOKUPS');
- Ember.assert("An outlet ("+options.outlet+") was specified but was not found.", options.outlet === 'main' || options.into);
- var controller = options.controller, namedController;
- if (options.controller) {
- controller = options.controller;
- } else if (namedController = route.container.lookup('controller:' + name)) {
- controller = namedController;
- } else {
- controller = route.controllerName || route.routeName;
- }
- if (typeof controller === 'string') {
- var controllerName = controller;
- controller = route.container.lookup('controller:' + controllerName);
- if (!controller) {
- throw new Ember.Error("You passed `controller: '" + controllerName + "'` into the `render` method, but no such controller could be found.");
- }
- }
- options.controller = controller;
- return options;
- }
- function setupView(view, container, options) {
- if (view) {
- if (options.LOG_VIEW_LOOKUPS) {
- Ember.Logger.info("Rendering " + options.name + " with " + view, { fullName: 'view:' + options.name });
- }
- } else {
- var defaultView = options.into ? 'view:default' : 'view:toplevel';
- view = container.lookup(defaultView);
- if (options.LOG_VIEW_LOOKUPS) {
- Ember.Logger.info("Rendering " + options.name + " with default view " + view, { fullName: 'view:' + options.name });
- }
- }
- if (!get(view, 'templateName')) {
- set(view, 'template', options.template);
- set(view, '_debugTemplateName', options.name);
- }
- set(view, 'renderedName', options.name);
- set(view, 'controller', options.controller);
- return view;
- }
- function appendView(route, view, options) {
- if (options.into) {
- var parentView = route.router._lookupActiveView(options.into);
- var teardownOutletView = generateOutletTeardown(parentView, options.outlet);
- if (!route.teardownOutletViews) { route.teardownOutletViews = []; }
- a_replace(route.teardownOutletViews, 0, 0, [teardownOutletView]);
- parentView.connectOutlet(options.outlet, view);
- } else {
- var rootElement = get(route, 'router.namespace.rootElement');
- // tear down view if one is already rendered
- if (route.teardownTopLevelView) {
- route.teardownTopLevelView();
- }
- route.router._connectActiveView(options.name, view);
- route.teardownTopLevelView = generateTopLevelTeardown(view);
- view.appendTo(rootElement);
- }
- }
- function generateTopLevelTeardown(view) {
- return function() { view.destroy(); };
- }
- function generateOutletTeardown(parentView, outlet) {
- return function() { parentView.disconnectOutlet(outlet); };
- }
- })();
- (function() {
- })();
- (function() {
- Ember.onLoad('Ember.Handlebars', function() {
- var handlebarsResolve = Ember.Handlebars.resolveParams,
- map = Ember.ArrayPolyfills.map,
- get = Ember.get,
- handlebarsGet = Ember.Handlebars.get;
- function resolveParams(context, params, options) {
- return map.call(resolvePaths(context, params, options), function(path, i) {
- if (null === path) {
- // Param was string/number, not a path, so just return raw string/number.
- return params[i];
- } else {
- return handlebarsGet(context, path, options);
- }
- });
- }
- function resolvePaths(context, params, options) {
- var resolved = handlebarsResolve(context, params, options),
- types = options.types;
- return map.call(resolved, function(object, i) {
- if (types[i] === 'ID') {
- return unwrap(object, params[i]);
- } else {
- return null;
- }
- });
- function unwrap(object, path) {
- if (path === 'controller') { return path; }
- if (Ember.ControllerMixin.detect(object)) {
- return unwrap(get(object, 'model'), path ? path + '.model' : 'model');
- } else {
- return path;
- }
- }
- }
- Ember.Router.resolveParams = resolveParams;
- Ember.Router.resolvePaths = resolvePaths;
- });
- })();
- (function() {
- /**
- @module ember
- @submodule ember-routing
- */
- var get = Ember.get, set = Ember.set, fmt = Ember.String.fmt;
- var slice = Array.prototype.slice;
- Ember.onLoad('Ember.Handlebars', function(Handlebars) {
- var QueryParams = Ember.Object.extend({
- values: null
- });
- var resolveParams = Ember.Router.resolveParams,
- translateQueryParams = Ember.Router._translateQueryParams,
- resolvePaths = Ember.Router.resolvePaths,
- isSimpleClick = Ember.ViewUtils.isSimpleClick;
- function fullRouteName(router, name) {
- var nameWithIndex;
- if (!router.hasRoute(name)) {
- nameWithIndex = name + '.index';
- Ember.assert(fmt("The attempt to link-to route '%@' failed (also tried '%@'). " +
- "The router did not find '%@' in its possible routes: '%@'",
- [name, nameWithIndex, name, Ember.keys(router.router.recognizer.names).join("', '")]),
- router.hasRoute(nameWithIndex));
- name = nameWithIndex;
- }
- return name;
- }
- function getResolvedPaths(options) {
- var types = options.options.types,
- data = options.options.data;
- return resolvePaths(options.context, options.params, { types: types, data: data });
- }
- /**
- `Ember.LinkView` renders an element whose `click` event triggers a
- transition of the application's instance of `Ember.Router` to
- a supplied route by name.
- Instances of `LinkView` will most likely be created through
- the `link-to` Handlebars helper, but properties of this class
- can be overridden to customize application-wide behavior.
- @class LinkView
- @namespace Ember
- @extends Ember.View
- @see {Handlebars.helpers.link-to}
- **/
- var LinkView = Ember.LinkView = Ember.View.extend({
- tagName: 'a',
- currentWhen: null,
- /**
- Sets the `title` attribute of the `LinkView`'s HTML element.
- @property title
- @default null
- **/
- title: null,
- /**
- Sets the `rel` attribute of the `LinkView`'s HTML element.
- @property rel
- @default null
- **/
- rel: null,
- /**
- The CSS class to apply to `LinkView`'s element when its `active`
- property is `true`.
- @property activeClass
- @type String
- @default active
- **/
- activeClass: 'active',
- /**
- The CSS class to apply to `LinkView`'s element when its `loading`
- property is `true`.
- @property loadingClass
- @type String
- @default loading
- **/
- loadingClass: 'loading',
- /**
- The CSS class to apply to a `LinkView`'s element when its `disabled`
- property is `true`.
- @property disabledClass
- @type String
- @default disabled
- **/
- disabledClass: 'disabled',
- _isDisabled: false,
- /**
- Determines whether the `LinkView` will trigger routing via
- the `replaceWith` routing strategy.
- @property replace
- @type Boolean
- @default false
- **/
- replace: false,
- /**
- By default the `{{link-to}}` helper will bind to the `href` and
- `title` attributes. It's discourage that you override these defaults,
- however you can push onto the array if needed.
- @property attributeBindings
- @type Array | String
- @default ['href', 'title', 'rel']
- **/
- attributeBindings: ['href', 'title', 'rel'],
- /**
- By default the `{{link-to}}` helper will bind to the `active`, `loading`, and
- `disabled` classes. It is discouraged to override these directly.
- @property classNameBindings
- @type Array
- @default ['active', 'loading', 'disabled']
- **/
- classNameBindings: ['active', 'loading', 'disabled'],
- /**
- By default the `{{link-to}}` helper responds to the `click` event. You
- can override this globally by setting this property to your custom
- event name.
- This is particularly useful on mobile when one wants to avoid the 300ms
- click delay using some sort of custom `tap` event.
- @property eventName
- @type String
- @default click
- */
- eventName: 'click',
- // this is doc'ed here so it shows up in the events
- // section of the API documentation, which is where
- // people will likely go looking for it.
- /**
- Triggers the `LinkView`'s routing behavior. If
- `eventName` is changed to a value other than `click`
- the routing behavior will trigger on that custom event
- instead.
- @event click
- **/
- /**
- An overridable method called when LinkView objects are instantiated.
- Example:
- ```javascript
- App.MyLinkView = Ember.LinkView.extend({
- init: function() {
- this._super();
- Ember.Logger.log('Event is ' + this.get('eventName'));
- }
- });
- ```
- NOTE: If you do override `init` for a framework class like `Ember.View` or
- `Ember.ArrayController`, be sure to call `this._super()` in your
- `init` declaration! If you don't, Ember may not have an opportunity to
- do important setup work, and you'll see strange behavior in your
- application.
- @method init
- */
- init: function() {
- this._super.apply(this, arguments);
- // Map desired event name to invoke function
- var eventName = get(this, 'eventName'), i;
- this.on(eventName, this, this._invoke);
- },
- /**
- This method is invoked by observers installed during `init` that fire
- whenever the params change
- @private
- @method _paramsChanged
- */
- _paramsChanged: function() {
- this.notifyPropertyChange('resolvedParams');
- },
- /**
- This is called to setup observers that will trigger a rerender.
- @private
- @method _setupPathObservers
- **/
- _setupPathObservers: function(){
- var helperParameters = this.parameters,
- linkTextPath = helperParameters.options.linkTextPath,
- paths = getResolvedPaths(helperParameters),
- length = paths.length,
- path, i, normalizedPath;
- if (linkTextPath) {
- normalizedPath = Ember.Handlebars.normalizePath(helperParameters.context, linkTextPath, helperParameters.options.data);
- this.registerObserver(normalizedPath.root, normalizedPath.path, this, this.rerender);
- }
- for(i=0; i < length; i++) {
- path = paths[i];
- if (null === path) {
- // A literal value was provided, not a path, so nothing to observe.
- continue;
- }
- normalizedPath = Ember.Handlebars.normalizePath(helperParameters.context, path, helperParameters.options.data);
- this.registerObserver(normalizedPath.root, normalizedPath.path, this, this._paramsChanged);
- }
- var queryParamsObject = this.queryParamsObject;
- if (queryParamsObject) {
- var values = queryParamsObject.values;
- // Install observers for all of the hash options
- // provided in the (query-params) subexpression.
- for (var k in values) {
- if (!values.hasOwnProperty(k)) { continue; }
- if (queryParamsObject.types[k] === 'ID') {
- normalizedPath = Ember.Handlebars.normalizePath(helperParameters.context, values[k], helperParameters.options.data);
- this.registerObserver(normalizedPath.root, normalizedPath.path, this, this._paramsChanged);
- }
- }
- }
- },
- afterRender: function(){
- this._super.apply(this, arguments);
- this._setupPathObservers();
- },
- /**
- Even though this isn't a virtual view, we want to treat it as if it is
- so that you can access the parent with {{view.prop}}
- @private
- @method concreteView
- **/
- concreteView: Ember.computed(function() {
- return get(this, 'parentView');
- }).property('parentView'),
- /**
- Accessed as a classname binding to apply the `LinkView`'s `disabledClass`
- CSS `class` to the element when the link is disabled.
- When `true` interactions with the element will not trigger route changes.
- @property disabled
- */
- disabled: Ember.computed(function computeLinkViewDisabled(key, value) {
- if (value !== undefined) { this.set('_isDisabled', value); }
- return value ? get(this, 'disabledClass') : false;
- }),
- /**
- Accessed as a classname binding to apply the `LinkView`'s `activeClass`
- CSS `class` to the element when the link is active.
- A `LinkView` is considered active when its `currentWhen` property is `true`
- or the application's current route is the route the `LinkView` would trigger
- transitions into.
- @property active
- **/
- active: Ember.computed(function computeLinkViewActive() {
- if (get(this, 'loading')) { return false; }
- var router = get(this, 'router'),
- routeArgs = get(this, 'routeArgs'),
- contexts = routeArgs.slice(1),
- resolvedParams = get(this, 'resolvedParams'),
- currentWhen = this.currentWhen || resolvedParams[0],
- currentWithIndex = currentWhen + '.index',
- isActive = router.isActive.apply(router, [currentWhen].concat(contexts)) ||
- router.isActive.apply(router, [currentWithIndex].concat(contexts));
- if (isActive) { return get(this, 'activeClass'); }
- }).property('resolvedParams', 'routeArgs'),
- /**
- Accessed as a classname binding to apply the `LinkView`'s `loadingClass`
- CSS `class` to the element when the link is loading.
- A `LinkView` is considered loading when it has at least one
- parameter whose value is currently null or undefined. During
- this time, clicking the link will perform no transition and
- emit a warning that the link is still in a loading state.
- @property loading
- **/
- loading: Ember.computed(function computeLinkViewLoading() {
- if (!get(this, 'routeArgs')) { return get(this, 'loadingClass'); }
- }).property('routeArgs'),
- /**
- Returns the application's main router from the container.
- @private
- @property router
- **/
- router: Ember.computed(function() {
- return get(this, 'controller').container.lookup('router:main');
- }),
- /**
- Event handler that invokes the link, activating the associated route.
- @private
- @method _invoke
- @param {Event} event
- */
- _invoke: function(event) {
- if (!isSimpleClick(event)) { return true; }
- if (this.preventDefault !== false) { event.preventDefault(); }
- if (this.bubbles === false) { event.stopPropagation(); }
- if (get(this, '_isDisabled')) { return false; }
- if (get(this, 'loading')) {
- Ember.Logger.warn("This link-to is in an inactive loading state because at least one of its parameters presently has a null/undefined value, or the provided route name is invalid.");
- return false;
- }
- var router = get(this, 'router'),
- routeArgs = get(this, 'routeArgs');
- if (get(this, 'replace')) {
- router.replaceWith.apply(router, routeArgs);
- } else {
- router.transitionTo.apply(router, routeArgs);
- }
- },
- /**
- Computed property that returns an array of the
- resolved parameters passed to the `link-to` helper,
- e.g.:
- ```hbs
- {{link-to a b '123' c}}
- ```
- will generate a `resolvedParams` of:
- ```js
- [aObject, bObject, '123', cObject]
- ```
- @private
- @property
- @return {Array}
- */
- resolvedParams: Ember.computed(function() {
- var parameters = this.parameters,
- options = parameters.options,
- types = options.types,
- data = options.data;
- if (parameters.params.length === 0) {
- var appController = this.container.lookup('controller:application');
- return [get(appController, 'currentRouteName')];
- } else {
- return resolveParams(parameters.context, parameters.params, { types: types, data: data });
- }
- // Original implementation if query params not enabled
- return resolveParams(parameters.context, parameters.params, { types: types, data: data });
- }).property('router.url'),
- /**
- Computed property that returns the current route name and
- any dynamic segments.
- @private
- @property
- @return {Array} An array with the route name and any dynamic segments
- */
- routeArgs: Ember.computed(function computeLinkViewRouteArgs() {
- var resolvedParams = get(this, 'resolvedParams').slice(0),
- router = get(this, 'router'),
- namedRoute = resolvedParams[0];
- if (!namedRoute) { return; }
- namedRoute = fullRouteName(router, namedRoute);
- resolvedParams[0] = namedRoute;
- for (var i = 1, len = resolvedParams.length; i < len; ++i) {
- var param = resolvedParams[i];
- if (param === null || typeof param === 'undefined') {
- // If contexts aren't present, consider the linkView unloaded.
- return;
- }
- }
-
- return resolvedParams;
- }).property('resolvedParams', 'queryParams'),
- queryParamsObject: null,
- queryParams: Ember.computed(function computeLinkViewQueryParams() {
- var queryParamsObject = get(this, 'queryParamsObject'),
- suppliedParams = {};
- if (queryParamsObject) {
- Ember.merge(suppliedParams, queryParamsObject.values);
- }
- var resolvedParams = get(this, 'resolvedParams'),
- router = get(this, 'router'),
- routeName = resolvedParams[0],
- paramsForRoute = router._queryParamNamesFor(routeName),
- queryParams = paramsForRoute.queryParams,
- translations = paramsForRoute.translations,
- paramsForRecognizer = {};
- // Normalize supplied params into their long-form name
- // e.g. 'foo' -> 'controllername:foo'
- translateQueryParams(suppliedParams, translations, routeName);
- var helperParameters = this.parameters;
- router._queryParamOverrides(paramsForRecognizer, queryParams, function(name, resultsName) {
- if (!(name in suppliedParams)) { return; }
- var parts = name.split(':');
- var type = queryParamsObject.types[parts[1]];
- var value;
- if (type === 'ID') {
- var normalizedPath = Ember.Handlebars.normalizePath(helperParameters.context, suppliedParams[name], helperParameters.options.data);
- value = Ember.Handlebars.get(normalizedPath.root, normalizedPath.path, helperParameters.options);
- } else {
- value = suppliedParams[name];
- }
- delete suppliedParams[name];
- paramsForRecognizer[resultsName] = value;
- });
- return paramsForRecognizer;
- }).property('resolvedParams.[]'),
- /**
- Sets the element's `href` attribute to the url for
- the `LinkView`'s targeted route.
- If the `LinkView`'s `tagName` is changed to a value other
- than `a`, this property will be ignored.
- @property href
- **/
- href: Ember.computed(function computeLinkViewHref() {
- if (get(this, 'tagName') !== 'a') { return; }
- var router = get(this, 'router'),
- routeArgs = get(this, 'routeArgs');
- return routeArgs ? router.generate.apply(router, routeArgs) : get(this, 'loadingHref');
- }).property('routeArgs'),
- /**
- The default href value to use while a link-to is loading.
- Only applies when tagName is 'a'
- @property loadingHref
- @type String
- @default #
- */
- loadingHref: '#'
- });
- LinkView.toString = function() { return "LinkView"; };
- /**
- The `{{link-to}}` helper renders a link to the supplied
- `routeName` passing an optionally supplied model to the
- route as its `model` context of the route. The block
- for `{{link-to}}` becomes the innerHTML of the rendered
- element:
- ```handlebars
- {{#link-to 'photoGallery'}}
- Great Hamster Photos
- {{/link-to}}
- ```
- ```html
- <a href="/hamster-photos">
- Great Hamster Photos
- </a>
- ```
- ### Supplying a tagName
- By default `{{link-to}}` renders an `<a>` element. This can
- be overridden for a single use of `{{link-to}}` by supplying
- a `tagName` option:
- ```handlebars
- {{#link-to 'photoGallery' tagName="li"}}
- Great Hamster Photos
- {{/link-to}}
- ```
- ```html
- <li>
- Great Hamster Photos
- </li>
- ```
- To override this option for your entire application, see
- "Overriding Application-wide Defaults".
- ### Disabling the `link-to` helper
- By default `{{link-to}}` is enabled.
- any passed value to `disabled` helper property will disable the `link-to` helper.
- static use: the `disabled` option:
- ```handlebars
- {{#link-to 'photoGallery' disabled=true}}
- Great Hamster Photos
- {{/link-to}}
- ```
- dynamic use: the `disabledWhen` option:
- ```handlebars
- {{#link-to 'photoGallery' disabledWhen=controller.someProperty}}
- Great Hamster Photos
- {{/link-to}}
- ```
- any passed value to `disabled` will disable it except `undefined`.
- to ensure that only `true` disable the `link-to` helper you can
- override the global behaviour of `Ember.LinkView`.
- ```javascript
- Ember.LinkView.reopen({
- disabled: Ember.computed(function(key, value) {
- if (value !== undefined) {
- this.set('_isDisabled', value === true);
- }
- return value === true ? get(this, 'disabledClass') : false;
- })
- });
- ```
- see "Overriding Application-wide Defaults" for more.
- ### Handling `href`
- `{{link-to}}` will use your application's Router to
- fill the element's `href` property with a url that
- matches the path to the supplied `routeName` for your
- routers's configured `Location` scheme, which defaults
- to Ember.HashLocation.
- ### Handling current route
- `{{link-to}}` will apply a CSS class name of 'active'
- when the application's current route matches
- the supplied routeName. For example, if the application's
- current route is 'photoGallery.recent' the following
- use of `{{link-to}}`:
- ```handlebars
- {{#link-to 'photoGallery.recent'}}
- Great Hamster Photos from the last week
- {{/link-to}}
- ```
- will result in
- ```html
- <a href="/hamster-photos/this-week" class="active">
- Great Hamster Photos
- </a>
- ```
- The CSS class name used for active classes can be customized
- for a single use of `{{link-to}}` by passing an `activeClass`
- option:
- ```handlebars
- {{#link-to 'photoGallery.recent' activeClass="current-url"}}
- Great Hamster Photos from the last week
- {{/link-to}}
- ```
- ```html
- <a href="/hamster-photos/this-week" class="current-url">
- Great Hamster Photos
- </a>
- ```
- To override this option for your entire application, see
- "Overriding Application-wide Defaults".
- ### Supplying a model
- An optional model argument can be used for routes whose
- paths contain dynamic segments. This argument will become
- the model context of the linked route:
- ```javascript
- App.Router.map(function() {
- this.resource("photoGallery", {path: "hamster-photos/:photo_id"});
- });
- ```
- ```handlebars
- {{#link-to 'photoGallery' aPhoto}}
- {{aPhoto.title}}
- {{/link-to}}
- ```
- ```html
- <a href="/hamster-photos/42">
- Tomster
- </a>
- ```
- ### Supplying multiple models
- For deep-linking to route paths that contain multiple
- dynamic segments, multiple model arguments can be used.
- As the router transitions through the route path, each
- supplied model argument will become the context for the
- route with the dynamic segments:
- ```javascript
- App.Router.map(function() {
- this.resource("photoGallery", {path: "hamster-photos/:photo_id"}, function() {
- this.route("comment", {path: "comments/:comment_id"});
- });
- });
- ```
- This argument will become the model context of the linked route:
- ```handlebars
- {{#link-to 'photoGallery.comment' aPhoto comment}}
- {{comment.body}}
- {{/link-to}}
- ```
- ```html
- <a href="/hamster-photos/42/comment/718">
- A+++ would snuggle again.
- </a>
- ```
- ### Supplying an explicit dynamic segment value
- If you don't have a model object available to pass to `{{link-to}}`,
- an optional string or integer argument can be passed for routes whose
- paths contain dynamic segments. This argument will become the value
- of the dynamic segment:
- ```javascript
- App.Router.map(function() {
- this.resource("photoGallery", {path: "hamster-photos/:photo_id"});
- });
- ```
- ```handlebars
- {{#link-to 'photoGallery' aPhotoId}}
- {{aPhoto.title}}
- {{/link-to}}
- ```
- ```html
- <a href="/hamster-photos/42">
- Tomster
- </a>
- ```
- When transitioning into the linked route, the `model` hook will
- be triggered with parameters including this passed identifier.
- ### Allowing Default Action
- By default the `{{link-to}}` helper prevents the default browser action
- by calling `preventDefault()` as this sort of action bubbling is normally
- handled internally and we do not want to take the browser to a new URL (for
- example).
- If you need to override this behavior specify `preventDefault=false` in
- your template:
- ```handlebars
- {{#link-to 'photoGallery' aPhotoId preventDefault=false}}
- {{aPhotoId.title}}
- {{/link-to}}
- ```
- ### Overriding attributes
- You can override any given property of the Ember.LinkView
- that is generated by the `{{link-to}}` helper by passing
- key/value pairs, like so:
- ```handlebars
- {{#link-to aPhoto tagName='li' title='Following this link will change your life' classNames='pic sweet'}}
- Uh-mazing!
- {{/link-to}}
- ```
- See [Ember.LinkView](/api/classes/Ember.LinkView.html) for a
- complete list of overrideable properties. Be sure to also
- check out inherited properties of `LinkView`.
- ### Overriding Application-wide Defaults
- ``{{link-to}}`` creates an instance of Ember.LinkView
- for rendering. To override options for your entire
- application, reopen Ember.LinkView and supply the
- desired values:
- ``` javascript
- Ember.LinkView.reopen({
- activeClass: "is-active",
- tagName: 'li'
- })
- ```
- It is also possible to override the default event in
- this manner:
- ``` javascript
- Ember.LinkView.reopen({
- eventName: 'customEventName'
- });
- ```
- @method link-to
- @for Ember.Handlebars.helpers
- @param {String} routeName
- @param {Object} [context]*
- @param [options] {Object} Handlebars key/value pairs of options, you can override any property of Ember.LinkView
- @return {String} HTML string
- @see {Ember.LinkView}
- */
- Ember.Handlebars.registerHelper('link-to', function linkToHelper(name) {
- var options = slice.call(arguments, -1)[0],
- params = slice.call(arguments, 0, -1),
- hash = options.hash;
- if (params[params.length - 1] instanceof QueryParams) {
- hash.queryParamsObject = params.pop();
- }
- hash.disabledBinding = hash.disabledWhen;
- if (!options.fn) {
- var linkTitle = params.shift();
- var linkType = options.types.shift();
- var context = this;
- if (linkType === 'ID') {
- options.linkTextPath = linkTitle;
- options.fn = function() {
- return Ember.Handlebars.getEscaped(context, linkTitle, options);
- };
- } else {
- options.fn = function() {
- return linkTitle;
- };
- }
- }
- hash.parameters = {
- context: this,
- options: options,
- params: params
- };
- return Ember.Handlebars.helpers.view.call(this, LinkView, options);
- });
-
- /**
- See [link-to](/api/classes/Ember.Handlebars.helpers.html#method_link-to)
- @method linkTo
- @for Ember.Handlebars.helpers
- @deprecated
- @param {String} routeName
- @param {Object} [context]*
- @return {String} HTML string
- */
- Ember.Handlebars.registerHelper('linkTo', function linkToHelper() {
- Ember.warn("The 'linkTo' view helper is deprecated in favor of 'link-to'");
- return Ember.Handlebars.helpers['link-to'].apply(this, arguments);
- });
- });
- })();
- (function() {
- /**
- @module ember
- @submodule ember-routing
- */
- var get = Ember.get, set = Ember.set;
- Ember.onLoad('Ember.Handlebars', function(Handlebars) {
- /**
- @module ember
- @submodule ember-routing
- */
- Handlebars.OutletView = Ember.ContainerView.extend(Ember._Metamorph);
- /**
- The `outlet` helper is a placeholder that the router will fill in with
- the appropriate template based on the current state of the application.
- ``` handlebars
- {{outlet}}
- ```
- By default, a template based on Ember's naming conventions will be rendered
- into the `outlet` (e.g. `App.PostsRoute` will render the `posts` template).
- You can render a different template by using the `render()` method in the
- route's `renderTemplate` hook. The following will render the `favoritePost`
- template into the `outlet`.
- ``` javascript
- App.PostsRoute = Ember.Route.extend({
- renderTemplate: function() {
- this.render('favoritePost');
- }
- });
- ```
- You can create custom named outlets for more control.
- ``` handlebars
- {{outlet 'favoritePost'}}
- {{outlet 'posts'}}
- ```
- Then you can define what template is rendered into each outlet in your
- route.
- ``` javascript
- App.PostsRoute = Ember.Route.extend({
- renderTemplate: function() {
- this.render('favoritePost', { outlet: 'favoritePost' });
- this.render('posts', { outlet: 'posts' });
- }
- });
- ```
- You can specify the view that the outlet uses to contain and manage the
- templates rendered into it.
- ``` handlebars
- {{outlet view='sectionContainer'}}
- ```
- ``` javascript
- App.SectionContainer = Ember.ContainerView.extend({
- tagName: 'section',
- classNames: ['special']
- });
- ```
- @method outlet
- @for Ember.Handlebars.helpers
- @param {String} property the property on the controller
- that holds the view for this outlet
- @return {String} HTML string
- */
- Handlebars.registerHelper('outlet', function outletHelper(property, options) {
-
- var outletSource,
- container,
- viewName,
- viewClass,
- viewFullName;
- if (property && property.data && property.data.isRenderData) {
- options = property;
- property = 'main';
- }
- container = options.data.view.container;
- outletSource = options.data.view;
- while (!outletSource.get('template.isTop')) {
- outletSource = outletSource.get('_parentView');
- }
- // provide controller override
- viewName = options.hash.view;
- if (viewName) {
- viewFullName = 'view:' + viewName;
- Ember.assert("Using a quoteless view parameter with {{outlet}} is not supported. Please update to quoted usage '{{outlet \"" + viewName + "\"}}.", options.hashTypes.view !== 'ID');
- Ember.assert("The view name you supplied '" + viewName + "' did not resolve to a view.", container.has(viewFullName));
- }
- viewClass = viewName ? container.lookupFactory(viewFullName) : options.hash.viewClass || Handlebars.OutletView;
- options.data.view.set('outletSource', outletSource);
- options.hash.currentViewBinding = '_view.outletSource._outlets.' + property;
- return Handlebars.helpers.view.call(this, viewClass, options);
- });
- });
- })();
- (function() {
- /**
- @module ember
- @submodule ember-routing
- */
- var get = Ember.get, set = Ember.set;
- Ember.onLoad('Ember.Handlebars', function(Handlebars) {
- /**
- Calling ``{{render}}`` from within a template will insert another
- template that matches the provided name. The inserted template will
- access its properties on its own controller (rather than the controller
- of the parent template).
- If a view class with the same name exists, the view class also will be used.
- Note: A given controller may only be used *once* in your app in this manner.
- A singleton instance of the controller will be created for you.
- Example:
- ```javascript
- App.NavigationController = Ember.Controller.extend({
- who: "world"
- });
- ```
- ```handlebars
- <!-- navigation.hbs -->
- Hello, {{who}}.
- ```
- ```handelbars
- <!-- application.hbs -->
- <h1>My great app</h1>
- {{render "navigation"}}
- ```
- ```html
- <h1>My great app</h1>
- <div class='ember-view'>
- Hello, world.
- </div>
- ```
- Optionally you may provide a second argument: a property path
- that will be bound to the `model` property of the controller.
- If a `model` property path is specified, then a new instance of the
- controller will be created and `{{render}}` can be used multiple times
- with the same name.
- For example if you had this `author` template.
- ```handlebars
- <div class="author">
- Written by {{firstName}} {{lastName}}.
- Total Posts: {{postCount}}
- </div>
- ```
- You could render it inside the `post` template using the `render` helper.
- ```handlebars
- <div class="post">
- <h1>{{title}}</h1>
- <div>{{body}}</div>
- {{render "author" author}}
- </div>
- ```
- @method render
- @for Ember.Handlebars.helpers
- @param {String} name
- @param {Object?} contextString
- @param {Hash} options
- @return {String} HTML string
- */
- Ember.Handlebars.registerHelper('render', function renderHelper(name, contextString, options) {
- var length = arguments.length;
- var contextProvided = length === 3,
- container, router, controller, view, context, lookupOptions;
- container = (options || contextString).data.keywords.controller.container;
- router = container.lookup('router:main');
- if (length === 2) {
- // use the singleton controller
- options = contextString;
- contextString = undefined;
- Ember.assert("You can only use the {{render}} helper once without a model object as its second argument, as in {{render \"post\" post}}.", !router || !router._lookupActiveView(name));
- } else if (length === 3) {
- // create a new controller
- context = Ember.Handlebars.get(options.contexts[1], contextString, options);
- } else {
- throw Ember.Error("You must pass a templateName to render");
- }
- Ember.deprecate("Using a quoteless parameter with {{render}} is deprecated. Please update to quoted usage '{{render \"" + name + "\"}}.", options.types[0] !== 'ID');
- // # legacy namespace
- name = name.replace(/\//g, '.');
- // \ legacy slash as namespace support
- view = container.lookup('view:' + name) || container.lookup('view:default');
- // provide controller override
- var controllerName = options.hash.controller || name;
- var controllerFullName = 'controller:' + controllerName;
- if (options.hash.controller) {
- Ember.assert("The controller name you supplied '" + controllerName + "' did not resolve to a controller.", container.has(controllerFullName));
- }
- var parentController = options.data.keywords.controller;
- // choose name
- if (length > 2) {
- var factory = container.lookupFactory(controllerFullName) ||
- Ember.generateControllerFactory(container, controllerName, context);
- controller = factory.create({
- model: context,
- parentController: parentController,
- target: parentController
- });
- } else {
- controller = container.lookup(controllerFullName) ||
- Ember.generateController(container, controllerName);
- controller.setProperties({
- target: parentController,
- parentController: parentController
- });
- }
- var root = options.contexts[1];
- if (root) {
- view.registerObserver(root, contextString, function() {
- controller.set('model', Ember.Handlebars.get(root, contextString, options));
- });
- }
- options.hash.viewName = Ember.String.camelize(name);
- var templateName = 'template:' + name;
- Ember.assert("You used `{{render '" + name + "'}}`, but '" + name + "' can not be found as either a template or a view.", container.has("view:" + name) || container.has(templateName) || options.fn);
- options.hash.template = container.lookup(templateName);
- options.hash.controller = controller;
- if (router && !context) {
- router._connectActiveView(name, view);
- }
- Ember.Handlebars.helpers.view.call(this, view, options);
- });
- });
- })();
- (function() {
- /**
- @module ember
- @submodule ember-routing
- */
- Ember.onLoad('Ember.Handlebars', function(Handlebars) {
- var resolveParams = Ember.Router.resolveParams,
- isSimpleClick = Ember.ViewUtils.isSimpleClick;
- var EmberHandlebars = Ember.Handlebars,
- handlebarsGet = EmberHandlebars.get,
- SafeString = EmberHandlebars.SafeString,
- forEach = Ember.ArrayPolyfills.forEach,
- get = Ember.get,
- a_slice = Array.prototype.slice;
- function args(options, actionName) {
- var ret = [];
- if (actionName) { ret.push(actionName); }
- var types = options.options.types.slice(1),
- data = options.options.data;
- return ret.concat(resolveParams(options.context, options.params, { types: types, data: data }));
- }
- var ActionHelper = EmberHandlebars.ActionHelper = {
- registeredActions: {}
- };
- var keys = ["alt", "shift", "meta", "ctrl"];
- var POINTER_EVENT_TYPE_REGEX = /^click|mouse|touch/;
- var isAllowedEvent = function(event, allowedKeys) {
- if (typeof allowedKeys === "undefined") {
- if (POINTER_EVENT_TYPE_REGEX.test(event.type)) {
- return isSimpleClick(event);
- } else {
- allowedKeys = '';
- }
- }
- if (allowedKeys.indexOf("any") >= 0) {
- return true;
- }
- var allowed = true;
- forEach.call(keys, function(key) {
- if (event[key + "Key"] && allowedKeys.indexOf(key) === -1) {
- allowed = false;
- }
- });
- return allowed;
- };
- ActionHelper.registerAction = function(actionName, options, allowedKeys) {
- var actionId = (++Ember.uuid).toString();
- ActionHelper.registeredActions[actionId] = {
- eventName: options.eventName,
- handler: function handleRegisteredAction(event) {
- if (!isAllowedEvent(event, allowedKeys)) { return true; }
- if (options.preventDefault !== false) {
- event.preventDefault();
- }
- if (options.bubbles === false) {
- event.stopPropagation();
- }
- var target = options.target;
- if (target.target) {
- target = handlebarsGet(target.root, target.target, target.options);
- } else {
- target = target.root;
- }
- if (options.boundProperty) {
- Ember.deprecate("Using a quoteless parameter with {{action}} is deprecated. Please update to quoted usage '{{action \"" + actionName + "\"}}.", false);
- }
- Ember.run(function runRegisteredAction() {
- if (target.send) {
- target.send.apply(target, args(options.parameters, actionName));
- } else {
- Ember.assert("The action '" + actionName + "' did not exist on " + target, typeof target[actionName] === 'function');
- target[actionName].apply(target, args(options.parameters));
- }
- });
- }
- };
- options.view.on('willClearRender', function() {
- delete ActionHelper.registeredActions[actionId];
- });
- return actionId;
- };
- /**
- The `{{action}}` helper registers an HTML element within a template for DOM
- event handling and forwards that interaction to the templates's controller
- or supplied `target` option (see 'Specifying a Target').
- If the controller does not implement the event, the event is sent
- to the current route, and it bubbles up the route hierarchy from there.
- User interaction with that element will invoke the supplied action name on
- the appropriate target.
- Given the following application Handlebars template on the page
- ```handlebars
- <div {{action 'anActionName'}}>
- click me
- </div>
- ```
- And application code
- ```javascript
- App.ApplicationController = Ember.Controller.extend({
- actions: {
- anActionName: function() {
- }
- }
- });
- ```
- Will result in the following rendered HTML
- ```html
- <div class="ember-view">
- <div data-ember-action="1">
- click me
- </div>
- </div>
- ```
- Clicking "click me" will trigger the `anActionName` action of the
- `App.ApplicationController`. In this case, no additional parameters will be passed.
- If you provide additional parameters to the helper:
- ```handlebars
- <button {{action 'edit' post}}>Edit</button>
- ```
- Those parameters will be passed along as arguments to the JavaScript
- function implementing the action.
- ### Event Propagation
- Events triggered through the action helper will automatically have
- `.preventDefault()` called on them. You do not need to do so in your event
- handlers. If you need to allow event propagation (to handle file inputs for
- example) you can supply the `preventDefault=false` option to the `{{action}}` helper:
- ```handlebars
- <div {{action "sayHello" preventDefault=false}}>
- <input type="file" />
- <input type="checkbox" />
- </div>
- ```
- To disable bubbling, pass `bubbles=false` to the helper:
- ```handlebars
- <button {{action 'edit' post bubbles=false}}>Edit</button>
- ```
- If you need the default handler to trigger you should either register your
- own event handler, or use event methods on your view class. See [Ember.View](/api/classes/Ember.View.html)
- 'Responding to Browser Events' for more information.
- ### Specifying DOM event type
- By default the `{{action}}` helper registers for DOM `click` events. You can
- supply an `on` option to the helper to specify a different DOM event name:
- ```handlebars
- <div {{action "anActionName" on="doubleClick"}}>
- click me
- </div>
- ```
- See `Ember.View` 'Responding to Browser Events' for a list of
- acceptable DOM event names.
- NOTE: Because `{{action}}` depends on Ember's event dispatch system it will
- only function if an `Ember.EventDispatcher` instance is available. An
- `Ember.EventDispatcher` instance will be created when a new `Ember.Application`
- is created. Having an instance of `Ember.Application` will satisfy this
- requirement.
- ### Specifying whitelisted modifier keys
- By default the `{{action}}` helper will ignore click event with pressed modifier
- keys. You can supply an `allowedKeys` option to specify which keys should not be ignored.
- ```handlebars
- <div {{action "anActionName" allowedKeys="alt"}}>
- click me
- </div>
- ```
- This way the `{{action}}` will fire when clicking with the alt key pressed down.
- Alternatively, supply "any" to the `allowedKeys` option to accept any combination of modifier keys.
- ```handlebars
- <div {{action "anActionName" allowedKeys="any"}}>
- click me with any key pressed
- </div>
- ```
- ### Specifying a Target
- There are several possible target objects for `{{action}}` helpers:
- In a typical Ember application, where views are managed through use of the
- `{{outlet}}` helper, actions will bubble to the current controller, then
- to the current route, and then up the route hierarchy.
- Alternatively, a `target` option can be provided to the helper to change
- which object will receive the method call. This option must be a path
- to an object, accessible in the current context:
- ```handlebars
- {{! the application template }}
- <div {{action "anActionName" target=view}}>
- click me
- </div>
- ```
- ```javascript
- App.ApplicationView = Ember.View.extend({
- actions: {
- anActionName: function(){}
- }
- });
- ```
- ### Additional Parameters
- You may specify additional parameters to the `{{action}}` helper. These
- parameters are passed along as the arguments to the JavaScript function
- implementing the action.
- ```handlebars
- {{#each person in people}}
- <div {{action "edit" person}}>
- click me
- </div>
- {{/each}}
- ```
- Clicking "click me" will trigger the `edit` method on the current controller
- with the value of `person` as a parameter.
- @method action
- @for Ember.Handlebars.helpers
- @param {String} actionName
- @param {Object} [context]*
- @param {Hash} options
- */
- EmberHandlebars.registerHelper('action', function actionHelper(actionName) {
- var options = arguments[arguments.length - 1],
- contexts = a_slice.call(arguments, 1, -1);
- var hash = options.hash,
- controller;
- // create a hash to pass along to registerAction
- var action = {
- eventName: hash.on || "click"
- };
- action.parameters = {
- context: this,
- options: options,
- params: contexts
- };
- action.view = options.data.view;
- var root, target;
- if (hash.target) {
- root = this;
- target = hash.target;
- } else if (controller = options.data.keywords.controller) {
- root = controller;
- }
- action.target = { root: root, target: target, options: options };
- action.bubbles = hash.bubbles;
- action.preventDefault = hash.preventDefault;
- action.boundProperty = options.types[0] === "ID";
- var actionId = ActionHelper.registerAction(actionName, action, hash.allowedKeys);
- return new SafeString('data-ember-action="' + actionId + '"');
- });
- });
- })();
- (function() {
- })();
- (function() {
- /**
- @module ember
- @submodule ember-routing
- */
- var get = Ember.get, set = Ember.set,
- map = Ember.EnumerableUtils.map;
- var queuedQueryParamChanges = {};
- Ember.ControllerMixin.reopen({
- /**
- Transition the application into another route. The route may
- be either a single route or route path:
- ```javascript
- aController.transitionToRoute('blogPosts');
- aController.transitionToRoute('blogPosts.recentEntries');
- ```
- Optionally supply a model for the route in question. The model
- will be serialized into the URL using the `serialize` hook of
- the route:
- ```javascript
- aController.transitionToRoute('blogPost', aPost);
- ```
- Multiple models will be applied last to first recursively up the
- resource tree.
- ```javascript
- this.resource('blogPost', {path:':blogPostId'}, function(){
- this.resource('blogComment', {path: ':blogCommentId'});
- });
- aController.transitionToRoute('blogComment', aPost, aComment);
- ```
- See also 'replaceRoute'.
- @param {String} name the name of the route
- @param {...Object} models the model(s) to be used while transitioning
- to the route.
- @for Ember.ControllerMixin
- @method transitionToRoute
- */
- transitionToRoute: function() {
- // target may be either another controller or a router
- var target = get(this, 'target'),
- method = target.transitionToRoute || target.transitionTo;
- return method.apply(target, arguments);
- },
- /**
- @deprecated
- @for Ember.ControllerMixin
- @method transitionTo
- */
- transitionTo: function() {
- Ember.deprecate("transitionTo is deprecated. Please use transitionToRoute.");
- return this.transitionToRoute.apply(this, arguments);
- },
- /**
- Transition into another route while replacing the current URL, if possible.
- This will replace the current history entry instead of adding a new one.
- Beside that, it is identical to `transitionToRoute` in all other respects.
- ```javascript
- aController.replaceRoute('blogPosts');
- aController.replaceRoute('blogPosts.recentEntries');
- ```
- Optionally supply a model for the route in question. The model
- will be serialized into the URL using the `serialize` hook of
- the route:
- ```javascript
- aController.replaceRoute('blogPost', aPost);
- ```
- Multiple models will be applied last to first recursively up the
- resource tree.
- ```javascript
- this.resource('blogPost', {path:':blogPostId'}, function(){
- this.resource('blogComment', {path: ':blogCommentId'});
- });
- aController.replaceRoute('blogComment', aPost, aComment);
- ```
- @param {String} name the name of the route
- @param {...Object} models the model(s) to be used while transitioning
- to the route.
- @for Ember.ControllerMixin
- @method replaceRoute
- */
- replaceRoute: function() {
- // target may be either another controller or a router
- var target = get(this, 'target'),
- method = target.replaceRoute || target.replaceWith;
- return method.apply(target, arguments);
- },
- /**
- @deprecated
- @for Ember.ControllerMixin
- @method replaceWith
- */
- replaceWith: function() {
- Ember.deprecate("replaceWith is deprecated. Please use replaceRoute.");
- return this.replaceRoute.apply(this, arguments);
- }
- });
- })();
- (function() {
- /**
- @module ember
- @submodule ember-routing
- */
- var get = Ember.get, set = Ember.set;
- Ember.View.reopen({
- /**
- Sets the private `_outlets` object on the view.
- @method init
- */
- init: function() {
- set(this, '_outlets', {});
- this._super();
- },
- /**
- Manually fill any of a view's `{{outlet}}` areas with the
- supplied view.
- Example
- ```javascript
- var MyView = Ember.View.extend({
- template: Ember.Handlebars.compile('Child view: {{outlet "main"}} ')
- });
- var myView = MyView.create();
- myView.appendTo('body');
- // The html for myView now looks like:
- // <div id="ember228" class="ember-view">Child view: </div>
- var FooView = Ember.View.extend({
- template: Ember.Handlebars.compile('<h1>Foo</h1> ')
- });
- var fooView = FooView.create();
- myView.connectOutlet('main', fooView);
- // The html for myView now looks like:
- // <div id="ember228" class="ember-view">Child view:
- // <div id="ember234" class="ember-view"><h1>Foo</h1> </div>
- // </div>
- ```
- @method connectOutlet
- @param {String} outletName A unique name for the outlet
- @param {Object} view An Ember.View
- */
- connectOutlet: function(outletName, view) {
- if (this._pendingDisconnections) {
- delete this._pendingDisconnections[outletName];
- }
- if (this._hasEquivalentView(outletName, view)) {
- view.destroy();
- return;
- }
- var outlets = get(this, '_outlets'),
- container = get(this, 'container'),
- router = container && container.lookup('router:main'),
- renderedName = get(view, 'renderedName');
- set(outlets, outletName, view);
- if (router && renderedName) {
- router._connectActiveView(renderedName, view);
- }
- },
- /**
- Determines if the view has already been created by checking if
- the view has the same constructor, template, and context as the
- view in the `_outlets` object.
- @private
- @method _hasEquivalentView
- @param {String} outletName The name of the outlet we are checking
- @param {Object} view An Ember.View
- @return {Boolean}
- */
- _hasEquivalentView: function(outletName, view) {
- var existingView = get(this, '_outlets.'+outletName);
- return existingView &&
- existingView.constructor === view.constructor &&
- existingView.get('template') === view.get('template') &&
- existingView.get('context') === view.get('context');
- },
- /**
- Removes an outlet from the view.
- Example
- ```javascript
- var MyView = Ember.View.extend({
- template: Ember.Handlebars.compile('Child view: {{outlet "main"}} ')
- });
- var myView = MyView.create();
- myView.appendTo('body');
- // myView's html:
- // <div id="ember228" class="ember-view">Child view: </div>
- var FooView = Ember.View.extend({
- template: Ember.Handlebars.compile('<h1>Foo</h1> ')
- });
- var fooView = FooView.create();
- myView.connectOutlet('main', fooView);
- // myView's html:
- // <div id="ember228" class="ember-view">Child view:
- // <div id="ember234" class="ember-view"><h1>Foo</h1> </div>
- // </div>
- myView.disconnectOutlet('main');
- // myView's html:
- // <div id="ember228" class="ember-view">Child view: </div>
- ```
- @method disconnectOutlet
- @param {String} outletName The name of the outlet to be removed
- */
- disconnectOutlet: function(outletName) {
- if (!this._pendingDisconnections) {
- this._pendingDisconnections = {};
- }
- this._pendingDisconnections[outletName] = true;
- Ember.run.once(this, '_finishDisconnections');
- },
- /**
- Gets an outlet that is pending disconnection and then
- nullifys the object on the `_outlet` object.
- @private
- @method _finishDisconnections
- */
- _finishDisconnections: function() {
- if (this.isDestroyed) return; // _outlets will be gone anyway
- var outlets = get(this, '_outlets');
- var pendingDisconnections = this._pendingDisconnections;
- this._pendingDisconnections = null;
- for (var outletName in pendingDisconnections) {
- set(outlets, outletName, null);
- }
- }
- });
- })();
- (function() {
- /**
- @module ember
- @submodule ember-views
- */
- // Add a new named queue after the 'actions' queue (where RSVP promises
- // resolve), which is used in router transitions to prevent unnecessary
- // loading state entry if all context promises resolve on the
- // 'actions' queue first.
- var queues = Ember.run.queues,
- indexOf = Ember.ArrayPolyfills.indexOf;
- queues.splice(indexOf.call(queues, 'actions') + 1, 0, 'routerTransitions');
- })();
- (function() {
- /**
- @module ember
- @submodule ember-routing
- */
- var get = Ember.get, set = Ember.set;
- /**
- Ember.Location returns an instance of the correct implementation of
- the `location` API.
- ## Implementations
- You can pass an implementation name (`hash`, `history`, `none`) to force a
- particular implementation to be used in your application.
- ### HashLocation
- Using `HashLocation` results in URLs with a `#` (hash sign) separating the
- server side URL portion of the URL from the portion that is used by Ember.
- This relies upon the `hashchange` event existing in the browser.
- Example:
- ```javascript
- App.Router.map(function() {
- this.resource('posts', function() {
- this.route('new');
- });
- });
- App.Router.reopen({
- location: 'hash'
- });
- ```
- This will result in a posts.new url of `/#/posts/new`.
- ### HistoryLocation
- Using `HistoryLocation` results in URLs that are indistinguishable from a
- standard URL. This relies upon the browser's `history` API.
- Example:
- ```javascript
- App.Router.map(function() {
- this.resource('posts', function() {
- this.route('new');
- });
- });
- App.Router.reopen({
- location: 'history'
- });
- ```
- This will result in a posts.new url of `/posts/new`.
- ### NoneLocation
- Using `NoneLocation` causes Ember to not store the applications URL state
- in the actual URL. This is generally used for testing purposes, and is one
- of the changes made when calling `App.setupForTesting()`.
- ## Location API
- Each location implementation must provide the following methods:
- * implementation: returns the string name used to reference the implementation.
- * getURL: returns the current URL.
- * setURL(path): sets the current URL.
- * replaceURL(path): replace the current URL (optional).
- * onUpdateURL(callback): triggers the callback when the URL changes.
- * formatURL(url): formats `url` to be placed into `href` attribute.
- Calling setURL or replaceURL will not trigger onUpdateURL callbacks.
- @class Location
- @namespace Ember
- @static
- */
- Ember.Location = {
- /**
- This is deprecated in favor of using the container to lookup the location
- implementation as desired.
- For example:
- ```javascript
- // Given a location registered as follows:
- container.register('location:history-test', HistoryTestLocation);
- // You could create a new instance via:
- container.lookup('location:history-test');
- ```
- @method create
- @param {Object} options
- @return {Object} an instance of an implementation of the `location` API
- @deprecated Use the container to lookup the location implementation that you
- need.
- */
- create: function(options) {
- var implementation = options && options.implementation;
- Ember.assert("Ember.Location.create: you must specify a 'implementation' option", !!implementation);
- var implementationClass = this.implementations[implementation];
- Ember.assert("Ember.Location.create: " + implementation + " is not a valid implementation", !!implementationClass);
- return implementationClass.create.apply(implementationClass, arguments);
- },
- /**
- This is deprecated in favor of using the container to register the
- location implementation as desired.
- Example:
- ```javascript
- Application.initializer({
- name: "history-test-location",
- initialize: function(container, application) {
- application.register('location:history-test', HistoryTestLocation);
- }
- });
- ```
- @method registerImplementation
- @param {String} name
- @param {Object} implementation of the `location` API
- @deprecated Register your custom location implementation with the
- container directly.
- */
- registerImplementation: function(name, implementation) {
- Ember.deprecate('Using the Ember.Location.registerImplementation is no longer supported. Register your custom location implementation with the container instead.', false);
- this.implementations[name] = implementation;
- },
- implementations: {},
- /**
- Returns the current `location.hash` by parsing location.href since browsers
- inconsistently URL-decode `location.hash`.
-
- https://bugzilla.mozilla.org/show_bug.cgi?id=483304
- @private
- @method getHash
- */
- getHash: function () {
- var href = window.location.href,
- hashIndex = href.indexOf('#');
- if (hashIndex === -1) {
- return '';
- } else {
- return href.substr(hashIndex);
- }
- }
- };
- })();
- (function() {
- /**
- @module ember
- @submodule ember-routing
- */
- var get = Ember.get, set = Ember.set;
- /**
- Ember.NoneLocation does not interact with the browser. It is useful for
- testing, or when you need to manage state with your Router, but temporarily
- don't want it to muck with the URL (for example when you embed your
- application in a larger page).
- @class NoneLocation
- @namespace Ember
- @extends Ember.Object
- */
- Ember.NoneLocation = Ember.Object.extend({
- implementation: 'none',
- path: '',
- /**
- Returns the current path.
- @private
- @method getURL
- @return {String} path
- */
- getURL: function() {
- return get(this, 'path');
- },
- /**
- Set the path and remembers what was set. Using this method
- to change the path will not invoke the `updateURL` callback.
- @private
- @method setURL
- @param path {String}
- */
- setURL: function(path) {
- set(this, 'path', path);
- },
- /**
- Register a callback to be invoked when the path changes. These
- callbacks will execute when the user presses the back or forward
- button, but not after `setURL` is invoked.
- @private
- @method onUpdateURL
- @param callback {Function}
- */
- onUpdateURL: function(callback) {
- this.updateCallback = callback;
- },
- /**
- Sets the path and calls the `updateURL` callback.
- @private
- @method handleURL
- @param callback {Function}
- */
- handleURL: function(url) {
- set(this, 'path', url);
- this.updateCallback(url);
- },
- /**
- Given a URL, formats it to be placed into the page as part
- of an element's `href` attribute.
- This is used, for example, when using the {{action}} helper
- to generate a URL based on an event.
- @private
- @method formatURL
- @param url {String}
- @return {String} url
- */
- formatURL: function(url) {
- // The return value is not overly meaningful, but we do not want to throw
- // errors when test code renders templates containing {{action href=true}}
- // helpers.
- return url;
- }
- });
- })();
- (function() {
- /**
- @module ember
- @submodule ember-routing
- */
- var get = Ember.get, set = Ember.set,
- getHash = Ember.Location.getHash;
- /**
- `Ember.HashLocation` implements the location API using the browser's
- hash. At present, it relies on a `hashchange` event existing in the
- browser.
- @class HashLocation
- @namespace Ember
- @extends Ember.Object
- */
- Ember.HashLocation = Ember.Object.extend({
- implementation: 'hash',
- init: function() {
- set(this, 'location', get(this, 'location') || window.location);
- },
- /**
- Returns the current `location.hash`, minus the '#' at the front.
- @private
- @method getURL
- */
- getURL: function() {
- return getHash().substr(1);
- },
- /**
- Set the `location.hash` and remembers what was set. This prevents
- `onUpdateURL` callbacks from triggering when the hash was set by
- `HashLocation`.
- @private
- @method setURL
- @param path {String}
- */
- setURL: function(path) {
- get(this, 'location').hash = path;
- set(this, 'lastSetURL', path);
- },
- /**
- Uses location.replace to update the url without a page reload
- or history modification.
- @private
- @method replaceURL
- @param path {String}
- */
- replaceURL: function(path) {
- get(this, 'location').replace('#' + path);
- set(this, 'lastSetURL', path);
- },
- /**
- Register a callback to be invoked when the hash changes. These
- callbacks will execute when the user presses the back or forward
- button, but not after `setURL` is invoked.
- @private
- @method onUpdateURL
- @param callback {Function}
- */
- onUpdateURL: function(callback) {
- var self = this;
- var guid = Ember.guidFor(this);
- Ember.$(window).on('hashchange.ember-location-'+guid, function() {
- Ember.run(function() {
- var path = self.getURL();
- if (get(self, 'lastSetURL') === path) { return; }
- set(self, 'lastSetURL', null);
- callback(path);
- });
- });
- },
- /**
- Given a URL, formats it to be placed into the page as part
- of an element's `href` attribute.
- This is used, for example, when using the {{action}} helper
- to generate a URL based on an event.
- @private
- @method formatURL
- @param url {String}
- */
- formatURL: function(url) {
- return '#'+url;
- },
- /**
- Cleans up the HashLocation event listener.
- @private
- @method willDestroy
- */
- willDestroy: function() {
- var guid = Ember.guidFor(this);
- Ember.$(window).off('hashchange.ember-location-'+guid);
- }
- });
- })();
- (function() {
- /**
- @module ember
- @submodule ember-routing
- */
- var get = Ember.get, set = Ember.set;
- var popstateFired = false;
- var supportsHistoryState = window.history && 'state' in window.history;
- /**
- Ember.HistoryLocation implements the location API using the browser's
- history.pushState API.
- @class HistoryLocation
- @namespace Ember
- @extends Ember.Object
- */
- Ember.HistoryLocation = Ember.Object.extend({
- implementation: 'history',
- init: function() {
- set(this, 'location', get(this, 'location') || window.location);
- set(this, 'baseURL', Ember.$('base').attr('href') || '');
- },
- /**
- Used to set state on first call to setURL
- @private
- @method initState
- */
- initState: function() {
- set(this, 'history', get(this, 'history') || window.history);
- this.replaceState(this.formatURL(this.getURL()));
- },
- /**
- Will be pre-pended to path upon state change
- @property rootURL
- @default '/'
- */
- rootURL: '/',
- /**
- Returns the current `location.pathname` without `rootURL`.
- @private
- @method getURL
- @return url {String}
- */
- getURL: function() {
- var rootURL = get(this, 'rootURL'),
- location = get(this, 'location'),
- path = location.pathname,
- baseURL = get(this, 'baseURL');
- rootURL = rootURL.replace(/\/$/, '');
- baseURL = baseURL.replace(/\/$/, '');
- var url = path.replace(baseURL, '').replace(rootURL, '');
-
- return url;
- },
- /**
- Uses `history.pushState` to update the url without a page reload.
- @private
- @method setURL
- @param path {String}
- */
- setURL: function(path) {
- var state = this.getState();
- path = this.formatURL(path);
- if (state && state.path !== path) {
- this.pushState(path);
- }
- },
- /**
- Uses `history.replaceState` to update the url without a page reload
- or history modification.
- @private
- @method replaceURL
- @param path {String}
- */
- replaceURL: function(path) {
- var state = this.getState();
- path = this.formatURL(path);
- if (state && state.path !== path) {
- this.replaceState(path);
- }
- },
- /**
- Get the current `history.state`
- Polyfill checks for native browser support and falls back to retrieving
- from a private _historyState variable
- @private
- @method getState
- @return state {Object}
- */
- getState: function() {
- return supportsHistoryState ? get(this, 'history').state : this._historyState;
- },
- /**
- Pushes a new state.
- @private
- @method pushState
- @param path {String}
- */
- pushState: function(path) {
- var state = { path: path };
- get(this, 'history').pushState(state, null, path);
- // store state if browser doesn't support `history.state`
- if (!supportsHistoryState) {
- this._historyState = state;
- }
- // used for webkit workaround
- this._previousURL = this.getURL();
- },
- /**
- Replaces the current state.
- @private
- @method replaceState
- @param path {String}
- */
- replaceState: function(path) {
- var state = { path: path };
- get(this, 'history').replaceState(state, null, path);
- // store state if browser doesn't support `history.state`
- if (!supportsHistoryState) {
- this._historyState = state;
- }
- // used for webkit workaround
- this._previousURL = this.getURL();
- },
- /**
- Register a callback to be invoked whenever the browser
- history changes, including using forward and back buttons.
- @private
- @method onUpdateURL
- @param callback {Function}
- */
- onUpdateURL: function(callback) {
- var guid = Ember.guidFor(this),
- self = this;
- Ember.$(window).on('popstate.ember-location-'+guid, function(e) {
- // Ignore initial page load popstate event in Chrome
- if (!popstateFired) {
- popstateFired = true;
- if (self.getURL() === self._previousURL) { return; }
- }
- callback(self.getURL());
- });
- },
- /**
- Used when using `{{action}}` helper. The url is always appended to the rootURL.
- @private
- @method formatURL
- @param url {String}
- @return formatted url {String}
- */
- formatURL: function(url) {
- var rootURL = get(this, 'rootURL'),
- baseURL = get(this, 'baseURL');
- if (url !== '') {
- rootURL = rootURL.replace(/\/$/, '');
- baseURL = baseURL.replace(/\/$/, '');
- } else if(baseURL.match(/^\//) && rootURL.match(/^\//)) {
- baseURL = baseURL.replace(/\/$/, '');
- }
- return baseURL + rootURL + url;
- },
- /**
- Cleans up the HistoryLocation event listener.
- @private
- @method willDestroy
- */
- willDestroy: function() {
- var guid = Ember.guidFor(this);
- Ember.$(window).off('popstate.ember-location-'+guid);
- }
- });
- })();
- (function() {
- })();
- (function() {
- /**
- Ember Routing
- @module ember
- @submodule ember-routing
- @requires ember-views
- */
- })();
- (function() {
- function visit(vertex, fn, visited, path) {
- var name = vertex.name,
- vertices = vertex.incoming,
- names = vertex.incomingNames,
- len = names.length,
- i;
- if (!visited) {
- visited = {};
- }
- if (!path) {
- path = [];
- }
- if (visited.hasOwnProperty(name)) {
- return;
- }
- path.push(name);
- visited[name] = true;
- for (i = 0; i < len; i++) {
- visit(vertices[names[i]], fn, visited, path);
- }
- fn(vertex, path);
- path.pop();
- }
- function DAG() {
- this.names = [];
- this.vertices = {};
- }
- DAG.prototype.add = function(name) {
- if (!name) { return; }
- if (this.vertices.hasOwnProperty(name)) {
- return this.vertices[name];
- }
- var vertex = {
- name: name, incoming: {}, incomingNames: [], hasOutgoing: false, value: null
- };
- this.vertices[name] = vertex;
- this.names.push(name);
- return vertex;
- };
- DAG.prototype.map = function(name, value) {
- this.add(name).value = value;
- };
- DAG.prototype.addEdge = function(fromName, toName) {
- if (!fromName || !toName || fromName === toName) {
- return;
- }
- var from = this.add(fromName), to = this.add(toName);
- if (to.incoming.hasOwnProperty(fromName)) {
- return;
- }
- function checkCycle(vertex, path) {
- if (vertex.name === toName) {
- throw new Ember.Error("cycle detected: " + toName + " <- " + path.join(" <- "));
- }
- }
- visit(from, checkCycle);
- from.hasOutgoing = true;
- to.incoming[fromName] = from;
- to.incomingNames.push(fromName);
- };
- DAG.prototype.topsort = function(fn) {
- var visited = {},
- vertices = this.vertices,
- names = this.names,
- len = names.length,
- i, vertex;
- for (i = 0; i < len; i++) {
- vertex = vertices[names[i]];
- if (!vertex.hasOutgoing) {
- visit(vertex, fn, visited);
- }
- }
- };
- DAG.prototype.addEdges = function(name, value, before, after) {
- var i;
- this.map(name, value);
- if (before) {
- if (typeof before === 'string') {
- this.addEdge(name, before);
- } else {
- for (i = 0; i < before.length; i++) {
- this.addEdge(name, before[i]);
- }
- }
- }
- if (after) {
- if (typeof after === 'string') {
- this.addEdge(after, name);
- } else {
- for (i = 0; i < after.length; i++) {
- this.addEdge(after[i], name);
- }
- }
- }
- };
- Ember.DAG = DAG;
- })();
- (function() {
- /**
- @module ember
- @submodule ember-application
- */
- var get = Ember.get,
- classify = Ember.String.classify,
- capitalize = Ember.String.capitalize,
- decamelize = Ember.String.decamelize;
- /**
- The DefaultResolver defines the default lookup rules to resolve
- container lookups before consulting the container for registered
- items:
- * templates are looked up on `Ember.TEMPLATES`
- * other names are looked up on the application after converting
- the name. For example, `controller:post` looks up
- `App.PostController` by default.
- * there are some nuances (see examples below)
- ### How Resolving Works
- The container calls this object's `resolve` method with the
- `fullName` argument.
- It first parses the fullName into an object using `parseName`.
- Then it checks for the presence of a type-specific instance
- method of the form `resolve[Type]` and calls it if it exists.
- For example if it was resolving 'template:post', it would call
- the `resolveTemplate` method.
- Its last resort is to call the `resolveOther` method.
- The methods of this object are designed to be easy to override
- in a subclass. For example, you could enhance how a template
- is resolved like so:
- ```javascript
- App = Ember.Application.create({
- Resolver: Ember.DefaultResolver.extend({
- resolveTemplate: function(parsedName) {
- var resolvedTemplate = this._super(parsedName);
- if (resolvedTemplate) { return resolvedTemplate; }
- return Ember.TEMPLATES['not_found'];
- }
- })
- });
- ```
- Some examples of how names are resolved:
- ```
- 'template:post' //=> Ember.TEMPLATES['post']
- 'template:posts/byline' //=> Ember.TEMPLATES['posts/byline']
- 'template:posts.byline' //=> Ember.TEMPLATES['posts/byline']
- 'template:blogPost' //=> Ember.TEMPLATES['blogPost']
- // OR
- // Ember.TEMPLATES['blog_post']
- 'controller:post' //=> App.PostController
- 'controller:posts.index' //=> App.PostsIndexController
- 'controller:blog/post' //=> Blog.PostController
- 'controller:basic' //=> Ember.Controller
- 'route:post' //=> App.PostRoute
- 'route:posts.index' //=> App.PostsIndexRoute
- 'route:blog/post' //=> Blog.PostRoute
- 'route:basic' //=> Ember.Route
- 'view:post' //=> App.PostView
- 'view:posts.index' //=> App.PostsIndexView
- 'view:blog/post' //=> Blog.PostView
- 'view:basic' //=> Ember.View
- 'foo:post' //=> App.PostFoo
- 'model:post' //=> App.Post
- ```
- @class DefaultResolver
- @namespace Ember
- @extends Ember.Object
- */
- Ember.DefaultResolver = Ember.Object.extend({
- /**
- This will be set to the Application instance when it is
- created.
- @property namespace
- */
- namespace: null,
- normalize: function(fullName) {
- var split = fullName.split(':', 2),
- type = split[0],
- name = split[1];
- Ember.assert("Tried to normalize a container name without a colon (:) in " +
- "it. You probably tried to lookup a name that did not contain " +
- "a type, a colon, and a name. A proper lookup name would be " +
- "`view:post`.", split.length === 2);
- if (type !== 'template') {
- var result = name;
- if (result.indexOf('.') > -1) {
- result = result.replace(/\.(.)/g, function(m) { return m.charAt(1).toUpperCase(); });
- }
- if (name.indexOf('_') > -1) {
- result = result.replace(/_(.)/g, function(m) { return m.charAt(1).toUpperCase(); });
- }
- return type + ':' + result;
- } else {
- return fullName;
- }
- },
- /**
- This method is called via the container's resolver method.
- It parses the provided `fullName` and then looks up and
- returns the appropriate template or class.
- @method resolve
- @param {String} fullName the lookup string
- @return {Object} the resolved factory
- */
- resolve: function(fullName) {
- var parsedName = this.parseName(fullName),
- typeSpecificResolveMethod = this[parsedName.resolveMethodName];
- if (!parsedName.name || !parsedName.type) {
- throw new TypeError("Invalid fullName: `" + fullName + "`, must be of the form `type:name` ");
- }
- if (typeSpecificResolveMethod) {
- var resolved = typeSpecificResolveMethod.call(this, parsedName);
- if (resolved) { return resolved; }
- }
- return this.resolveOther(parsedName);
- },
- /**
- Convert the string name of the form "type:name" to
- a Javascript object with the parsed aspects of the name
- broken out.
- @protected
- @param {String} fullName the lookup string
- @method parseName
- */
- parseName: function(fullName) {
- var nameParts = fullName.split(":"),
- type = nameParts[0], fullNameWithoutType = nameParts[1],
- name = fullNameWithoutType,
- namespace = get(this, 'namespace'),
- root = namespace;
- if (type !== 'template' && name.indexOf('/') !== -1) {
- var parts = name.split('/');
- name = parts[parts.length - 1];
- var namespaceName = capitalize(parts.slice(0, -1).join('.'));
- root = Ember.Namespace.byName(namespaceName);
- Ember.assert('You are looking for a ' + name + ' ' + type + ' in the ' + namespaceName + ' namespace, but the namespace could not be found', root);
- }
- return {
- fullName: fullName,
- type: type,
- fullNameWithoutType: fullNameWithoutType,
- name: name,
- root: root,
- resolveMethodName: "resolve" + classify(type)
- };
- },
- /**
- Look up the template in Ember.TEMPLATES
- @protected
- @param {Object} parsedName a parseName object with the parsed
- fullName lookup string
- @method resolveTemplate
- */
- resolveTemplate: function(parsedName) {
- var templateName = parsedName.fullNameWithoutType.replace(/\./g, '/');
- if (Ember.TEMPLATES[templateName]) {
- return Ember.TEMPLATES[templateName];
- }
- templateName = decamelize(templateName);
- if (Ember.TEMPLATES[templateName]) {
- return Ember.TEMPLATES[templateName];
- }
- },
- /**
- Given a parseName object (output from `parseName`), apply
- the conventions expected by `Ember.Router`
- @protected
- @param {Object} parsedName a parseName object with the parsed
- fullName lookup string
- @method useRouterNaming
- */
- useRouterNaming: function(parsedName) {
- parsedName.name = parsedName.name.replace(/\./g, '_');
- if (parsedName.name === 'basic') {
- parsedName.name = '';
- }
- },
- /**
- Lookup the controller using `resolveOther`
- @protected
- @param {Object} parsedName a parseName object with the parsed
- fullName lookup string
- @method resolveController
- */
- resolveController: function(parsedName) {
- this.useRouterNaming(parsedName);
- return this.resolveOther(parsedName);
- },
- /**
- Lookup the route using `resolveOther`
- @protected
- @param {Object} parsedName a parseName object with the parsed
- fullName lookup string
- @method resolveRoute
- */
- resolveRoute: function(parsedName) {
- this.useRouterNaming(parsedName);
- return this.resolveOther(parsedName);
- },
- /**
- Lookup the view using `resolveOther`
- @protected
- @param {Object} parsedName a parseName object with the parsed
- fullName lookup string
- @method resolveView
- */
- resolveView: function(parsedName) {
- this.useRouterNaming(parsedName);
- return this.resolveOther(parsedName);
- },
- resolveHelper: function(parsedName) {
- return this.resolveOther(parsedName) || Ember.Handlebars.helpers[parsedName.fullNameWithoutType];
- },
- /**
- Lookup the model on the Application namespace
- @protected
- @param {Object} parsedName a parseName object with the parsed
- fullName lookup string
- @method resolveModel
- */
- resolveModel: function(parsedName) {
- var className = classify(parsedName.name),
- factory = get(parsedName.root, className);
- if (factory) { return factory; }
- },
- /**
- Look up the specified object (from parsedName) on the appropriate
- namespace (usually on the Application)
- @protected
- @param {Object} parsedName a parseName object with the parsed
- fullName lookup string
- @method resolveOther
- */
- resolveOther: function(parsedName) {
- var className = classify(parsedName.name) + classify(parsedName.type),
- factory = get(parsedName.root, className);
- if (factory) { return factory; }
- },
- /**
- Returns a human-readable description for a fullName. Used by the
- Application namespace in assertions to describe the
- precise name of the class that Ember is looking for, rather than
- container keys.
- @protected
- @param {String} fullName the lookup string
- @method lookupDescription
- */
- lookupDescription: function(fullName) {
- var parsedName = this.parseName(fullName);
- if (parsedName.type === 'template') {
- return "template at " + parsedName.fullNameWithoutType.replace(/\./g, '/');
- }
- var description = parsedName.root + "." + classify(parsedName.name);
- if (parsedName.type !== 'model') { description += classify(parsedName.type); }
- return description;
- },
- makeToString: function(factory, fullName) {
- return factory.toString();
- }
- });
- })();
- (function() {
- /**
- @module ember
- @submodule ember-application
- */
- var get = Ember.get, set = Ember.set;
- function DeprecatedContainer(container) {
- this._container = container;
- }
- DeprecatedContainer.deprecate = function(method) {
- return function() {
- var container = this._container;
- Ember.deprecate('Using the defaultContainer is no longer supported. [defaultContainer#' + method + '] see: http://git.io/EKPpnA', false);
- return container[method].apply(container, arguments);
- };
- };
- DeprecatedContainer.prototype = {
- _container: null,
- lookup: DeprecatedContainer.deprecate('lookup'),
- resolve: DeprecatedContainer.deprecate('resolve'),
- register: DeprecatedContainer.deprecate('register')
- };
- /**
- An instance of `Ember.Application` is the starting point for every Ember
- application. It helps to instantiate, initialize and coordinate the many
- objects that make up your app.
- Each Ember app has one and only one `Ember.Application` object. In fact, the
- very first thing you should do in your application is create the instance:
- ```javascript
- window.App = Ember.Application.create();
- ```
- Typically, the application object is the only global variable. All other
- classes in your app should be properties on the `Ember.Application` instance,
- which highlights its first role: a global namespace.
- For example, if you define a view class, it might look like this:
- ```javascript
- App.MyView = Ember.View.extend();
- ```
- By default, calling `Ember.Application.create()` will automatically initialize
- your application by calling the `Ember.Application.initialize()` method. If
- you need to delay initialization, you can call your app's `deferReadiness()`
- method. When you are ready for your app to be initialized, call its
- `advanceReadiness()` method.
- You can define a `ready` method on the `Ember.Application` instance, which
- will be run by Ember when the application is initialized.
- Because `Ember.Application` inherits from `Ember.Namespace`, any classes
- you create will have useful string representations when calling `toString()`.
- See the `Ember.Namespace` documentation for more information.
- While you can think of your `Ember.Application` as a container that holds the
- other classes in your application, there are several other responsibilities
- going on under-the-hood that you may want to understand.
- ### Event Delegation
- Ember uses a technique called _event delegation_. This allows the framework
- to set up a global, shared event listener instead of requiring each view to
- do it manually. For example, instead of each view registering its own
- `mousedown` listener on its associated element, Ember sets up a `mousedown`
- listener on the `body`.
- If a `mousedown` event occurs, Ember will look at the target of the event and
- start walking up the DOM node tree, finding corresponding views and invoking
- their `mouseDown` method as it goes.
- `Ember.Application` has a number of default events that it listens for, as
- well as a mapping from lowercase events to camel-cased view method names. For
- example, the `keypress` event causes the `keyPress` method on the view to be
- called, the `dblclick` event causes `doubleClick` to be called, and so on.
- If there is a bubbling browser event that Ember does not listen for by
- default, you can specify custom events and their corresponding view method
- names by setting the application's `customEvents` property:
- ```javascript
- App = Ember.Application.create({
- customEvents: {
- // add support for the paste event
- paste: "paste"
- }
- });
- ```
- By default, the application sets up these event listeners on the document
- body. However, in cases where you are embedding an Ember application inside
- an existing page, you may want it to set up the listeners on an element
- inside the body.
- For example, if only events inside a DOM element with the ID of `ember-app`
- should be delegated, set your application's `rootElement` property:
- ```javascript
- window.App = Ember.Application.create({
- rootElement: '#ember-app'
- });
- ```
- The `rootElement` can be either a DOM element or a jQuery-compatible selector
- string. Note that *views appended to the DOM outside the root element will
- not receive events.* If you specify a custom root element, make sure you only
- append views inside it!
- To learn more about the advantages of event delegation and the Ember view
- layer, and a list of the event listeners that are setup by default, visit the
- [Ember View Layer guide](http://emberjs.com/guides/understanding-ember/the-view-layer/#toc_event-delegation).
- ### Initializers
- Libraries on top of Ember can register additional initializers, like so:
- ```javascript
- Ember.Application.initializer({
- name: "store",
- initialize: function(container, application) {
- container.register('store:main', application.Store);
- }
- });
- ```
- ### Routing
- In addition to creating your application's router, `Ember.Application` is
- also responsible for telling the router when to start routing. Transitions
- between routes can be logged with the `LOG_TRANSITIONS` flag, and more
- detailed intra-transition logging can be logged with
- the `LOG_TRANSITIONS_INTERNAL` flag:
- ```javascript
- window.App = Ember.Application.create({
- LOG_TRANSITIONS: true, // basic logging of successful transitions
- LOG_TRANSITIONS_INTERNAL: true // detailed logging of all routing steps
- });
- ```
- By default, the router will begin trying to translate the current URL into
- application state once the browser emits the `DOMContentReady` event. If you
- need to defer routing, you can call the application's `deferReadiness()`
- method. Once routing can begin, call the `advanceReadiness()` method.
- If there is any setup required before routing begins, you can implement a
- `ready()` method on your app that will be invoked immediately before routing
- begins.
- ```
- @class Application
- @namespace Ember
- @extends Ember.Namespace
- */
- var Application = Ember.Application = Ember.Namespace.extend(Ember.DeferredMixin, {
- /**
- The root DOM element of the Application. This can be specified as an
- element or a
- [jQuery-compatible selector string](http://api.jquery.com/category/selectors/).
- This is the element that will be passed to the Application's,
- `eventDispatcher`, which sets up the listeners for event delegation. Every
- view in your application should be a child of the element you specify here.
- @property rootElement
- @type DOMElement
- @default 'body'
- */
- rootElement: 'body',
- /**
- The `Ember.EventDispatcher` responsible for delegating events to this
- application's views.
- The event dispatcher is created by the application at initialization time
- and sets up event listeners on the DOM element described by the
- application's `rootElement` property.
- See the documentation for `Ember.EventDispatcher` for more information.
- @property eventDispatcher
- @type Ember.EventDispatcher
- @default null
- */
- eventDispatcher: null,
- /**
- The DOM events for which the event dispatcher should listen.
- By default, the application's `Ember.EventDispatcher` listens
- for a set of standard DOM events, such as `mousedown` and
- `keyup`, and delegates them to your application's `Ember.View`
- instances.
- If you would like additional bubbling events to be delegated to your
- views, set your `Ember.Application`'s `customEvents` property
- to a hash containing the DOM event name as the key and the
- corresponding view method name as the value. For example:
- ```javascript
- App = Ember.Application.create({
- customEvents: {
- // add support for the paste event
- paste: "paste"
- }
- });
- ```
- @property customEvents
- @type Object
- @default null
- */
- customEvents: null,
- // Start off the number of deferrals at 1. This will be
- // decremented by the Application's own `initialize` method.
- _readinessDeferrals: 1,
- init: function() {
- if (!this.$) { this.$ = Ember.$; }
- this.__container__ = this.buildContainer();
- this.Router = this.defaultRouter();
- this._super();
- this.scheduleInitialize();
- Ember.libraries.registerCoreLibrary('Handlebars', Ember.Handlebars.VERSION);
- Ember.libraries.registerCoreLibrary('jQuery', Ember.$().jquery);
- if ( Ember.LOG_VERSION ) {
- Ember.LOG_VERSION = false; // we only need to see this once per Application#init
- var maxNameLength = Math.max.apply(this, Ember.A(Ember.libraries).mapBy("name.length"));
- Ember.debug('-------------------------------');
- Ember.libraries.each(function(name, version) {
- var spaces = new Array(maxNameLength - name.length + 1).join(" ");
- Ember.debug([name, spaces, ' : ', version].join(""));
- });
- Ember.debug('-------------------------------');
- }
- },
- /**
- Build the container for the current application.
- Also register a default application view in case the application
- itself does not.
- @private
- @method buildContainer
- @return {Ember.Container} the configured container
- */
- buildContainer: function() {
- var container = this.__container__ = Application.buildContainer(this);
- return container;
- },
- /**
- If the application has not opted out of routing and has not explicitly
- defined a router, supply a default router for the application author
- to configure.
- This allows application developers to do:
- ```javascript
- var App = Ember.Application.create();
- App.Router.map(function() {
- this.resource('posts');
- });
- ```
- @private
- @method defaultRouter
- @return {Ember.Router} the default router
- */
- defaultRouter: function() {
- if (this.Router === false) { return; }
- var container = this.__container__;
- if (this.Router) {
- container.unregister('router:main');
- container.register('router:main', this.Router);
- }
- return container.lookupFactory('router:main');
- },
- /**
- Automatically initialize the application once the DOM has
- become ready.
- The initialization itself is scheduled on the actions queue
- which ensures that application loading finishes before
- booting.
- If you are asynchronously loading code, you should call
- `deferReadiness()` to defer booting, and then call
- `advanceReadiness()` once all of your code has finished
- loading.
- @private
- @method scheduleInitialize
- */
- scheduleInitialize: function() {
- var self = this;
- if (!this.$ || this.$.isReady) {
- Ember.run.schedule('actions', self, '_initialize');
- } else {
- this.$().ready(function runInitialize() {
- Ember.run(self, '_initialize');
- });
- }
- },
- /**
- Use this to defer readiness until some condition is true.
- Example:
- ```javascript
- App = Ember.Application.create();
- App.deferReadiness();
- jQuery.getJSON("/auth-token", function(token) {
- App.token = token;
- App.advanceReadiness();
- });
- ```
- This allows you to perform asynchronous setup logic and defer
- booting your application until the setup has finished.
- However, if the setup requires a loading UI, it might be better
- to use the router for this purpose.
- @method deferReadiness
- */
- deferReadiness: function() {
- Ember.assert("You must call deferReadiness on an instance of Ember.Application", this instanceof Ember.Application);
- Ember.assert("You cannot defer readiness since the `ready()` hook has already been called.", this._readinessDeferrals > 0);
- this._readinessDeferrals++;
- },
- /**
- Call `advanceReadiness` after any asynchronous setup logic has completed.
- Each call to `deferReadiness` must be matched by a call to `advanceReadiness`
- or the application will never become ready and routing will not begin.
- @method advanceReadiness
- @see {Ember.Application#deferReadiness}
- */
- advanceReadiness: function() {
- Ember.assert("You must call advanceReadiness on an instance of Ember.Application", this instanceof Ember.Application);
- this._readinessDeferrals--;
- if (this._readinessDeferrals === 0) {
- Ember.run.once(this, this.didBecomeReady);
- }
- },
- /**
- registers a factory for later injection
- Example:
- ```javascript
- App = Ember.Application.create();
- App.Person = Ember.Object.extend({});
- App.Orange = Ember.Object.extend({});
- App.Email = Ember.Object.extend({});
- App.session = Ember.Object.create({});
- App.register('model:user', App.Person, {singleton: false });
- App.register('fruit:favorite', App.Orange);
- App.register('communication:main', App.Email, {singleton: false});
- App.register('session', App.session, {instantiate: false});
- ```
- @method register
- @param fullName {String} type:name (e.g., 'model:user')
- @param factory {Function} (e.g., App.Person)
- @param options {String} (optional)
- **/
- register: function() {
- var container = this.__container__;
- container.register.apply(container, arguments);
- },
- /**
- defines an injection or typeInjection
- Example:
- ```javascript
- App.inject(<full_name or type>, <property name>, <full_name>)
- App.inject('controller:application', 'email', 'model:email')
- App.inject('controller', 'source', 'source:main')
- ```
- Please note that injections on models are currently disabled.
- This was done because ember-data was not ready for fully a container aware ecosystem.
-
- You can enable injections on models by setting `Ember.MODEL_FACTORY_INJECTIONS` flag to `true`
- If model factory injections are enabled, models should not be
- accessed globally (only through `container.lookupFactory('model:modelName'))`);
- @method inject
- @param factoryNameOrType {String}
- @param property {String}
- @param injectionName {String}
- **/
- inject: function() {
- var container = this.__container__;
- container.injection.apply(container, arguments);
- },
- /**
- Calling initialize manually is not supported.
- Please see Ember.Application#advanceReadiness and
- Ember.Application#deferReadiness.
- @private
- @deprecated
- @method initialize
- **/
- initialize: function() {
- Ember.deprecate('Calling initialize manually is not supported. Please see Ember.Application#advanceReadiness and Ember.Application#deferReadiness');
- },
- /**
- Initialize the application. This happens automatically.
- Run any initializers and run the application load hook. These hooks may
- choose to defer readiness. For example, an authentication hook might want
- to defer readiness until the auth token has been retrieved.
- @private
- @method _initialize
- */
- _initialize: function() {
- if (this.isDestroyed) { return; }
- // At this point, the App.Router must already be assigned
- if (this.Router) {
- var container = this.__container__;
- container.unregister('router:main');
- container.register('router:main', this.Router);
- }
- this.runInitializers();
- Ember.runLoadHooks('application', this);
- // At this point, any initializers or load hooks that would have wanted
- // to defer readiness have fired. In general, advancing readiness here
- // will proceed to didBecomeReady.
- this.advanceReadiness();
- return this;
- },
- /**
- Reset the application. This is typically used only in tests. It cleans up
- the application in the following order:
- 1. Deactivate existing routes
- 2. Destroy all objects in the container
- 3. Create a new application container
- 4. Re-route to the existing url
- Typical Example:
- ```javascript
- var App;
- Ember.run(function() {
- App = Ember.Application.create();
- });
- module("acceptance test", {
- setup: function() {
- App.reset();
- }
- });
- test("first test", function() {
- // App is freshly reset
- });
- test("first test", function() {
- // App is again freshly reset
- });
- ```
- Advanced Example:
- Occasionally you may want to prevent the app from initializing during
- setup. This could enable extra configuration, or enable asserting prior
- to the app becoming ready.
- ```javascript
- var App;
- Ember.run(function() {
- App = Ember.Application.create();
- });
- module("acceptance test", {
- setup: function() {
- Ember.run(function() {
- App.reset();
- App.deferReadiness();
- });
- }
- });
- test("first test", function() {
- ok(true, 'something before app is initialized');
- Ember.run(function() {
- App.advanceReadiness();
- });
- ok(true, 'something after app is initialized');
- });
- ```
- @method reset
- **/
- reset: function() {
- this._readinessDeferrals = 1;
- function handleReset() {
- var router = this.__container__.lookup('router:main');
- router.reset();
- Ember.run(this.__container__, 'destroy');
- this.buildContainer();
- Ember.run.schedule('actions', this, function() {
- this._initialize();
- });
- }
- Ember.run.join(this, handleReset);
- },
- /**
- @private
- @method runInitializers
- */
- runInitializers: function() {
- var initializers = get(this.constructor, 'initializers'),
- container = this.__container__,
- graph = new Ember.DAG(),
- namespace = this,
- name, initializer;
- for (name in initializers) {
- initializer = initializers[name];
- graph.addEdges(initializer.name, initializer.initialize, initializer.before, initializer.after);
- }
- graph.topsort(function (vertex) {
- var initializer = vertex.value;
- Ember.assert("No application initializer named '"+vertex.name+"'", initializer);
- initializer(container, namespace);
- });
- },
- /**
- @private
- @method didBecomeReady
- */
- didBecomeReady: function() {
- this.setupEventDispatcher();
- this.ready(); // user hook
- this.startRouting();
- if (!Ember.testing) {
- // Eagerly name all classes that are already loaded
- Ember.Namespace.processAll();
- Ember.BOOTED = true;
- }
- this.resolve(this);
- },
- /**
- Setup up the event dispatcher to receive events on the
- application's `rootElement` with any registered
- `customEvents`.
- @private
- @method setupEventDispatcher
- */
- setupEventDispatcher: function() {
- var customEvents = get(this, 'customEvents'),
- rootElement = get(this, 'rootElement'),
- dispatcher = this.__container__.lookup('event_dispatcher:main');
- set(this, 'eventDispatcher', dispatcher);
- dispatcher.setup(customEvents, rootElement);
- },
- /**
- trigger a new call to `route` whenever the URL changes.
- If the application has a router, use it to route to the current URL, and
- @private
- @method startRouting
- @property router {Ember.Router}
- */
- startRouting: function() {
- var router = this.__container__.lookup('router:main');
- if (!router) { return; }
- router.startRouting();
- },
- handleURL: function(url) {
- var router = this.__container__.lookup('router:main');
- router.handleURL(url);
- },
- /**
- Called when the Application has become ready.
- The call will be delayed until the DOM has become ready.
- @event ready
- */
- ready: Ember.K,
- /**
- @deprecated Use 'Resolver' instead
- Set this to provide an alternate class to `Ember.DefaultResolver`
- @property resolver
- */
- resolver: null,
- /**
- Set this to provide an alternate class to `Ember.DefaultResolver`
- @property resolver
- */
- Resolver: null,
- willDestroy: function() {
- Ember.BOOTED = false;
- // Ensure deactivation of routes before objects are destroyed
- this.__container__.lookup('router:main').reset();
- this.__container__.destroy();
- },
- initializer: function(options) {
- this.constructor.initializer(options);
- }
- });
- Ember.Application.reopenClass({
- initializers: {},
- initializer: function(initializer) {
- // If this is the first initializer being added to a subclass, we are going to reopen the class
- // to make sure we have a new `initializers` object, which extends from the parent class' using
- // prototypal inheritance. Without this, attempting to add initializers to the subclass would
- // pollute the parent class as well as other subclasses.
- if (this.superclass.initializers !== undefined && this.superclass.initializers === this.initializers) {
- this.reopenClass({
- initializers: Ember.create(this.initializers)
- });
- }
- Ember.assert("The initializer '" + initializer.name + "' has already been registered", !this.initializers[initializer.name]);
- Ember.assert("An initializer cannot be registered with both a before and an after", !(initializer.before && initializer.after));
- Ember.assert("An initializer cannot be registered without an initialize function", Ember.canInvoke(initializer, 'initialize'));
- this.initializers[initializer.name] = initializer;
- },
- /**
- This creates a container with the default Ember naming conventions.
- It also configures the container:
- * registered views are created every time they are looked up (they are
- not singletons)
- * registered templates are not factories; the registered value is
- returned directly.
- * the router receives the application as its `namespace` property
- * all controllers receive the router as their `target` and `controllers`
- properties
- * all controllers receive the application as their `namespace` property
- * the application view receives the application controller as its
- `controller` property
- * the application view receives the application template as its
- `defaultTemplate` property
- @private
- @method buildContainer
- @static
- @param {Ember.Application} namespace the application to build the
- container for.
- @return {Ember.Container} the built container
- */
- buildContainer: function(namespace) {
- var container = new Ember.Container();
- Ember.Container.defaultContainer = new DeprecatedContainer(container);
- container.set = Ember.set;
- container.resolver = resolverFor(namespace);
- container.normalize = container.resolver.normalize;
- container.describe = container.resolver.describe;
- container.makeToString = container.resolver.makeToString;
- container.optionsForType('component', { singleton: false });
- container.optionsForType('view', { singleton: false });
- container.optionsForType('template', { instantiate: false });
- container.optionsForType('helper', { instantiate: false });
- container.register('application:main', namespace, { instantiate: false });
- container.register('controller:basic', Ember.Controller, { instantiate: false });
- container.register('controller:object', Ember.ObjectController, { instantiate: false });
- container.register('controller:array', Ember.ArrayController, { instantiate: false });
- container.register('route:basic', Ember.Route, { instantiate: false });
- container.register('event_dispatcher:main', Ember.EventDispatcher);
- container.register('router:main', Ember.Router);
- container.injection('router:main', 'namespace', 'application:main');
- container.register('location:hash', Ember.HashLocation);
- container.register('location:history', Ember.HistoryLocation);
- container.register('location:none', Ember.NoneLocation);
- container.injection('controller', 'target', 'router:main');
- container.injection('controller', 'namespace', 'application:main');
- container.injection('route', 'router', 'router:main');
- return container;
- }
- });
- /**
- This function defines the default lookup rules for container lookups:
- * templates are looked up on `Ember.TEMPLATES`
- * other names are looked up on the application after classifying the name.
- For example, `controller:post` looks up `App.PostController` by default.
- * if the default lookup fails, look for registered classes on the container
- This allows the application to register default injections in the container
- that could be overridden by the normal naming convention.
- @private
- @method resolverFor
- @param {Ember.Namespace} namespace the namespace to look for classes
- @return {*} the resolved value for a given lookup
- */
- function resolverFor(namespace) {
- if (namespace.get('resolver')) {
- Ember.deprecate('Application.resolver is deprecated in favor of Application.Resolver', false);
- }
- var ResolverClass = namespace.get('resolver') || namespace.get('Resolver') || Ember.DefaultResolver;
- var resolver = ResolverClass.create({
- namespace: namespace
- });
- function resolve(fullName) {
- return resolver.resolve(fullName);
- }
- resolve.describe = function(fullName) {
- return resolver.lookupDescription(fullName);
- };
- resolve.makeToString = function(factory, fullName) {
- return resolver.makeToString(factory, fullName);
- };
- resolve.normalize = function(fullName) {
- if (resolver.normalize) {
- return resolver.normalize(fullName);
- } else {
- Ember.deprecate('The Resolver should now provide a \'normalize\' function', false);
- return fullName;
- }
- };
- return resolve;
- }
- Ember.runLoadHooks('Ember.Application', Ember.Application);
- })();
- (function() {
- })();
- (function() {
- /**
- @module ember
- @submodule ember-application
- */
- var get = Ember.get, set = Ember.set;
- function verifyNeedsDependencies(controller, container, needs) {
- var dependency, i, l, missing = [];
- for (i=0, l=needs.length; i<l; i++) {
- dependency = needs[i];
- Ember.assert(Ember.inspect(controller) + "#needs must not specify dependencies with periods in their names (" + dependency + ")", dependency.indexOf('.') === -1);
- if (dependency.indexOf(':') === -1) {
- dependency = "controller:" + dependency;
- }
- // Structure assert to still do verification but not string concat in production
- if (!container.has(dependency)) {
- missing.push(dependency);
- }
- }
- if (missing.length) {
- throw new Ember.Error(Ember.inspect(controller) + " needs [ " + missing.join(', ') + " ] but " + (missing.length > 1 ? 'they' : 'it') + " could not be found");
- }
- }
- var defaultControllersComputedProperty = Ember.computed(function() {
- var controller = this;
- return {
- needs: get(controller, 'needs'),
- container: get(controller, 'container'),
- unknownProperty: function(controllerName) {
- var needs = this.needs,
- dependency, i, l;
- for (i=0, l=needs.length; i<l; i++) {
- dependency = needs[i];
- if (dependency === controllerName) {
- return this.container.lookup('controller:' + controllerName);
- }
- }
- var errorMessage = Ember.inspect(controller) + '#needs does not include `' + controllerName + '`. To access the ' + controllerName + ' controller from ' + Ember.inspect(controller) + ', ' + Ember.inspect(controller) + ' should have a `needs` property that is an array of the controllers it has access to.';
- throw new ReferenceError(errorMessage);
- },
- setUnknownProperty: function (key, value) {
- throw new Error("You cannot overwrite the value of `controllers." + key + "` of " + Ember.inspect(controller));
- }
- };
- });
- /**
- @class ControllerMixin
- @namespace Ember
- */
- Ember.ControllerMixin.reopen({
- concatenatedProperties: ['needs'],
- /**
- An array of other controller objects available inside
- instances of this controller via the `controllers`
- property:
- For example, when you define a controller:
- ```javascript
- App.CommentsController = Ember.ArrayController.extend({
- needs: ['post']
- });
- ```
- The application's single instance of these other
- controllers are accessible by name through the
- `controllers` property:
- ```javascript
- this.get('controllers.post'); // instance of App.PostController
- ```
- Given that you have a nested controller (nested resource):
- ```javascript
- App.CommentsNewController = Ember.ObjectController.extend({
- });
- ```
- When you define a controller that requires access to a nested one:
- ```javascript
- App.IndexController = Ember.ObjectController.extend({
- needs: ['commentsNew']
- });
- ```
- You will be able to get access to it:
- ```javascript
- this.get('controllers.commentsNew'); // instance of App.CommentsNewController
- ```
- This is only available for singleton controllers.
- @property {Array} needs
- @default []
- */
- needs: [],
- init: function() {
- var needs = get(this, 'needs'),
- length = get(needs, 'length');
- if (length > 0) {
- Ember.assert(' `' + Ember.inspect(this) + ' specifies `needs`, but does ' +
- "not have a container. Please ensure this controller was " +
- "instantiated with a container.",
- this.container || Ember.meta(this, false).descs.controllers !== defaultControllersComputedProperty);
- if (this.container) {
- verifyNeedsDependencies(this, this.container, needs);
- }
- // if needs then initialize controllers proxy
- get(this, 'controllers');
- }
- this._super.apply(this, arguments);
- },
- /**
- @method controllerFor
- @see {Ember.Route#controllerFor}
- @deprecated Use `needs` instead
- */
- controllerFor: function(controllerName) {
- Ember.deprecate("Controller#controllerFor is deprecated, please use Controller#needs instead");
- return Ember.controllerFor(get(this, 'container'), controllerName);
- },
- /**
- Stores the instances of other controllers available from within
- this controller. Any controller listed by name in the `needs`
- property will be accessible by name through this property.
- ```javascript
- App.CommentsController = Ember.ArrayController.extend({
- needs: ['post'],
- postTitle: function(){
- var currentPost = this.get('controllers.post'); // instance of App.PostController
- return currentPost.get('title');
- }.property('controllers.post.title')
- });
- ```
- @see {Ember.ControllerMixin#needs}
- @property {Object} controllers
- @default null
- */
- controllers: defaultControllersComputedProperty
- });
- })();
- (function() {
- })();
- (function() {
- /**
- Ember Application
- @module ember
- @submodule ember-application
- @requires ember-views, ember-routing
- */
- })();
- (function() {
- /**
- @module ember
- @submodule ember-extension-support
- */
- /**
- The `DataAdapter` helps a data persistence library
- interface with tools that debug Ember such
- as the [Ember Extension](https://github.com/tildeio/ember-extension)
- for Chrome and Firefox.
- This class will be extended by a persistence library
- which will override some of the methods with
- library-specific code.
- The methods likely to be overridden are:
- * `getFilters`
- * `detect`
- * `columnsForType`
- * `getRecords`
- * `getRecordColumnValues`
- * `getRecordKeywords`
- * `getRecordFilterValues`
- * `getRecordColor`
- * `observeRecord`
- The adapter will need to be registered
- in the application's container as `dataAdapter:main`
- Example:
- ```javascript
- Application.initializer({
- name: "dataAdapter",
- initialize: function(container, application) {
- application.register('dataAdapter:main', DS.DataAdapter);
- }
- });
- ```
- @class DataAdapter
- @namespace Ember
- @extends Ember.Object
- */
- Ember.DataAdapter = Ember.Object.extend({
- init: function() {
- this._super();
- this.releaseMethods = Ember.A();
- },
- /**
- The container of the application being debugged.
- This property will be injected
- on creation.
- @property container
- @default null
- */
- container: null,
- /**
- Number of attributes to send
- as columns. (Enough to make the record
- identifiable).
- @private
- @property attributeLimit
- @default 3
- */
- attributeLimit: 3,
- /**
- Stores all methods that clear observers.
- These methods will be called on destruction.
- @private
- @property releaseMethods
- */
- releaseMethods: Ember.A(),
- /**
- Specifies how records can be filtered.
- Records returned will need to have a `filterValues`
- property with a key for every name in the returned array.
- @public
- @method getFilters
- @return {Array} List of objects defining filters.
- The object should have a `name` and `desc` property.
- */
- getFilters: function() {
- return Ember.A();
- },
- /**
- Fetch the model types and observe them for changes.
- @public
- @method watchModelTypes
- @param {Function} typesAdded Callback to call to add types.
- Takes an array of objects containing wrapped types (returned from `wrapModelType`).
- @param {Function} typesUpdated Callback to call when a type has changed.
- Takes an array of objects containing wrapped types.
- @return {Function} Method to call to remove all observers
- */
- watchModelTypes: function(typesAdded, typesUpdated) {
- var modelTypes = this.getModelTypes(),
- self = this, typesToSend, releaseMethods = Ember.A();
- typesToSend = modelTypes.map(function(type) {
- var wrapped = self.wrapModelType(type);
- releaseMethods.push(self.observeModelType(type, typesUpdated));
- return wrapped;
- });
- typesAdded(typesToSend);
- var release = function() {
- releaseMethods.forEach(function(fn) { fn(); });
- self.releaseMethods.removeObject(release);
- };
- this.releaseMethods.pushObject(release);
- return release;
- },
- /**
- Fetch the records of a given type and observe them for changes.
- @public
- @method watchRecords
- @param {Function} recordsAdded Callback to call to add records.
- Takes an array of objects containing wrapped records.
- The object should have the following properties:
- columnValues: {Object} key and value of a table cell
- object: {Object} the actual record object
- @param {Function} recordsUpdated Callback to call when a record has changed.
- Takes an array of objects containing wrapped records.
- @param {Function} recordsRemoved Callback to call when a record has removed.
- Takes the following parameters:
- index: the array index where the records were removed
- count: the number of records removed
- @return {Function} Method to call to remove all observers
- */
- watchRecords: function(type, recordsAdded, recordsUpdated, recordsRemoved) {
- var self = this, releaseMethods = Ember.A(), records = this.getRecords(type), release;
- var recordUpdated = function(updatedRecord) {
- recordsUpdated([updatedRecord]);
- };
- var recordsToSend = records.map(function(record) {
- releaseMethods.push(self.observeRecord(record, recordUpdated));
- return self.wrapRecord(record);
- });
- var contentDidChange = function(array, idx, removedCount, addedCount) {
- for (var i = idx; i < idx + addedCount; i++) {
- var record = array.objectAt(i);
- var wrapped = self.wrapRecord(record);
- releaseMethods.push(self.observeRecord(record, recordUpdated));
- recordsAdded([wrapped]);
- }
- if (removedCount) {
- recordsRemoved(idx, removedCount);
- }
- };
- var observer = { didChange: contentDidChange, willChange: Ember.K };
- records.addArrayObserver(self, observer);
- release = function() {
- releaseMethods.forEach(function(fn) { fn(); });
- records.removeArrayObserver(self, observer);
- self.releaseMethods.removeObject(release);
- };
- recordsAdded(recordsToSend);
- this.releaseMethods.pushObject(release);
- return release;
- },
- /**
- Clear all observers before destruction
- @private
- */
- willDestroy: function() {
- this._super();
- this.releaseMethods.forEach(function(fn) {
- fn();
- });
- },
- /**
- Detect whether a class is a model.
- Test that against the model class
- of your persistence library
- @private
- @method detect
- @param {Class} klass The class to test
- @return boolean Whether the class is a model class or not
- */
- detect: function(klass) {
- return false;
- },
- /**
- Get the columns for a given model type.
- @private
- @method columnsForType
- @param {Class} type The model type
- @return {Array} An array of columns of the following format:
- name: {String} name of the column
- desc: {String} Humanized description (what would show in a table column name)
- */
- columnsForType: function(type) {
- return Ember.A();
- },
- /**
- Adds observers to a model type class.
- @private
- @method observeModelType
- @param {Class} type The model type class
- @param {Function} typesUpdated Called when a type is modified.
- @return {Function} The function to call to remove observers
- */
- observeModelType: function(type, typesUpdated) {
- var self = this, records = this.getRecords(type);
- var onChange = function() {
- typesUpdated([self.wrapModelType(type)]);
- };
- var observer = {
- didChange: function() {
- Ember.run.scheduleOnce('actions', this, onChange);
- },
- willChange: Ember.K
- };
- records.addArrayObserver(this, observer);
- var release = function() {
- records.removeArrayObserver(self, observer);
- };
- return release;
- },
- /**
- Wraps a given model type and observes changes to it.
- @private
- @method wrapModelType
- @param {Class} type A model class
- @param {Function} typesUpdated callback to call when the type changes
- @return {Object} contains the wrapped type and the function to remove observers
- Format:
- type: {Object} the wrapped type
- The wrapped type has the following format:
- name: {String} name of the type
- count: {Integer} number of records available
- columns: {Columns} array of columns to describe the record
- object: {Class} the actual Model type class
- release: {Function} The function to remove observers
- */
- wrapModelType: function(type, typesUpdated) {
- var release, records = this.getRecords(type),
- typeToSend, self = this;
- typeToSend = {
- name: type.toString(),
- count: Ember.get(records, 'length'),
- columns: this.columnsForType(type),
- object: type
- };
- return typeToSend;
- },
- /**
- Fetches all models defined in the application.
- @private
- @method getModelTypes
- @return {Array} Array of model types
- */
- // TODO: Use the resolver instead of looping over namespaces.
- getModelTypes: function() {
- var namespaces = Ember.A(Ember.Namespace.NAMESPACES), types = Ember.A(), self = this;
- namespaces.forEach(function(namespace) {
- for (var key in namespace) {
- if (!namespace.hasOwnProperty(key)) { continue; }
- var klass = namespace[key];
- if (self.detect(klass)) {
- types.push(klass);
- }
- }
- });
- return types;
- },
- /**
- Fetches all loaded records for a given type.
- @private
- @method getRecords
- @return {Array} An array of records.
- This array will be observed for changes,
- so it should update when new records are added/removed.
- */
- getRecords: function(type) {
- return Ember.A();
- },
- /**
- Wraps a record and observers changes to it.
- @private
- @method wrapRecord
- @param {Object} record The record instance.
- @return {Object} The wrapped record. Format:
- columnValues: {Array}
- searchKeywords: {Array}
- */
- wrapRecord: function(record) {
- var recordToSend = { object: record }, columnValues = {}, self = this;
- recordToSend.columnValues = this.getRecordColumnValues(record);
- recordToSend.searchKeywords = this.getRecordKeywords(record);
- recordToSend.filterValues = this.getRecordFilterValues(record);
- recordToSend.color = this.getRecordColor(record);
- return recordToSend;
- },
- /**
- Gets the values for each column.
- @private
- @method getRecordColumnValues
- @return {Object} Keys should match column names defined
- by the model type.
- */
- getRecordColumnValues: function(record) {
- return {};
- },
- /**
- Returns keywords to match when searching records.
- @private
- @method getRecordKeywords
- @return {Array} Relevant keywords for search.
- */
- getRecordKeywords: function(record) {
- return Ember.A();
- },
- /**
- Returns the values of filters defined by `getFilters`.
- @private
- @method getRecordFilterValues
- @param {Object} record The record instance
- @return {Object} The filter values
- */
- getRecordFilterValues: function(record) {
- return {};
- },
- /**
- Each record can have a color that represents its state.
- @private
- @method getRecordColor
- @param {Object} record The record instance
- @return {String} The record's color
- Possible options: black, red, blue, green
- */
- getRecordColor: function(record) {
- return null;
- },
- /**
- Observes all relevant properties and re-sends the wrapped record
- when a change occurs.
- @private
- @method observerRecord
- @param {Object} record The record instance
- @param {Function} recordUpdated The callback to call when a record is updated.
- @return {Function} The function to call to remove all observers.
- */
- observeRecord: function(record, recordUpdated) {
- return function(){};
- }
- });
- })();
- (function() {
- /**
- Ember Extension Support
- @module ember
- @submodule ember-extension-support
- @requires ember-application
- */
- })();
- (function() {
- /**
- @module ember
- @submodule ember-testing
- */
- var slice = [].slice,
- helpers = {},
- injectHelpersCallbacks = [];
- /**
- This is a container for an assortment of testing related functionality:
- * Choose your default test adapter (for your framework of choice).
- * Register/Unregister additional test helpers.
- * Setup callbacks to be fired when the test helpers are injected into
- your application.
- @class Test
- @namespace Ember
- */
- Ember.Test = {
- /**
- `registerHelper` is used to register a test helper that will be injected
- when `App.injectTestHelpers` is called.
- The helper method will always be called with the current Application as
- the first parameter.
- For example:
- ```javascript
- Ember.Test.registerHelper('boot', function(app) {
- Ember.run(app, app.advanceReadiness);
- });
- ```
- This helper can later be called without arguments because it will be
- called with `app` as the first parameter.
- ```javascript
- App = Ember.Application.create();
- App.injectTestHelpers();
- boot();
- ```
- @public
- @method registerHelper
- @param {String} name The name of the helper method to add.
- @param {Function} helperMethod
- @param options {Object}
- */
- registerHelper: function(name, helperMethod) {
- helpers[name] = {
- method: helperMethod,
- meta: { wait: false }
- };
- },
- /**
- `registerAsyncHelper` is used to register an async test helper that will be injected
- when `App.injectTestHelpers` is called.
- The helper method will always be called with the current Application as
- the first parameter.
- For example:
- ```javascript
- Ember.Test.registerAsyncHelper('boot', function(app) {
- Ember.run(app, app.advanceReadiness);
- });
- ```
- The advantage of an async helper is that it will not run
- until the last async helper has completed. All async helpers
- after it will wait for it complete before running.
- For example:
- ```javascript
- Ember.Test.registerAsyncHelper('deletePost', function(app, postId) {
- click('.delete-' + postId);
- });
- // ... in your test
- visit('/post/2');
- deletePost(2);
- visit('/post/3');
- deletePost(3);
- ```
- @public
- @method registerAsyncHelper
- @param {String} name The name of the helper method to add.
- @param {Function} helperMethod
- */
- registerAsyncHelper: function(name, helperMethod) {
- helpers[name] = {
- method: helperMethod,
- meta: { wait: true }
- };
- },
- /**
- Remove a previously added helper method.
- Example:
- ```javascript
- Ember.Test.unregisterHelper('wait');
- ```
- @public
- @method unregisterHelper
- @param {String} name The helper to remove.
- */
- unregisterHelper: function(name) {
- delete helpers[name];
- delete Ember.Test.Promise.prototype[name];
- },
- /**
- Used to register callbacks to be fired whenever `App.injectTestHelpers`
- is called.
- The callback will receive the current application as an argument.
- Example:
- ```javascript
- Ember.Test.onInjectHelpers(function() {
- Ember.$(document).ajaxStart(function() {
- Test.pendingAjaxRequests++;
- });
- Ember.$(document).ajaxStop(function() {
- Test.pendingAjaxRequests--;
- });
- });
- ```
- @public
- @method onInjectHelpers
- @param {Function} callback The function to be called.
- */
- onInjectHelpers: function(callback) {
- injectHelpersCallbacks.push(callback);
- },
- /**
- This returns a thenable tailored for testing. It catches failed
- `onSuccess` callbacks and invokes the `Ember.Test.adapter.exception`
- callback in the last chained then.
- This method should be returned by async helpers such as `wait`.
- @public
- @method promise
- @param {Function} resolver The function used to resolve the promise.
- */
- promise: function(resolver) {
- return new Ember.Test.Promise(resolver);
- },
- /**
- Used to allow ember-testing to communicate with a specific testing
- framework.
- You can manually set it before calling `App.setupForTesting()`.
- Example:
- ```javascript
- Ember.Test.adapter = MyCustomAdapter.create()
- ```
- If you do not set it, ember-testing will default to `Ember.Test.QUnitAdapter`.
- @public
- @property adapter
- @type {Class} The adapter to be used.
- @default Ember.Test.QUnitAdapter
- */
- adapter: null,
- /**
- Replacement for `Ember.RSVP.resolve`
- The only difference is this uses
- and instance of `Ember.Test.Promise`
- @public
- @method resolve
- @param {Mixed} The value to resolve
- */
- resolve: function(val) {
- return Ember.Test.promise(function(resolve) {
- return resolve(val);
- });
- },
- /**
- This allows ember-testing to play nicely with other asynchronous
- events, such as an application that is waiting for a CSS3
- transition or an IndexDB transaction.
- For example:
- ```javascript
- Ember.Test.registerWaiter(function() {
- return myPendingTransactions() == 0;
- });
- ```
- The `context` argument allows you to optionally specify the `this`
- with which your callback will be invoked.
- For example:
- ```javascript
- Ember.Test.registerWaiter(MyDB, MyDB.hasPendingTransactions);
- ```
- @public
- @method registerWaiter
- @param {Object} context (optional)
- @param {Function} callback
- */
- registerWaiter: function(context, callback) {
- if (arguments.length === 1) {
- callback = context;
- context = null;
- }
- if (!this.waiters) {
- this.waiters = Ember.A();
- }
- this.waiters.push([context, callback]);
- },
- /**
- `unregisterWaiter` is used to unregister a callback that was
- registered with `registerWaiter`.
- @public
- @method unregisterWaiter
- @param {Object} context (optional)
- @param {Function} callback
- */
- unregisterWaiter: function(context, callback) {
- var pair;
- if (!this.waiters) { return; }
- if (arguments.length === 1) {
- callback = context;
- context = null;
- }
- pair = [context, callback];
- this.waiters = Ember.A(this.waiters.filter(function(elt) {
- return Ember.compare(elt, pair)!==0;
- }));
- }
- };
- function helper(app, name) {
- var fn = helpers[name].method,
- meta = helpers[name].meta;
- return function() {
- var args = slice.call(arguments),
- lastPromise = Ember.Test.lastPromise;
- args.unshift(app);
- // some helpers are not async and
- // need to return a value immediately.
- // example: `find`
- if (!meta.wait) {
- return fn.apply(app, args);
- }
- if (!lastPromise) {
- // It's the first async helper in current context
- lastPromise = fn.apply(app, args);
- } else {
- // wait for last helper's promise to resolve
- // and then execute
- run(function() {
- lastPromise = Ember.Test.resolve(lastPromise).then(function() {
- return fn.apply(app, args);
- });
- });
- }
- return lastPromise;
- };
- }
- function run(fn) {
- if (!Ember.run.currentRunLoop) {
- Ember.run(fn);
- } else {
- fn();
- }
- }
- Ember.Application.reopen({
- /**
- This property contains the testing helpers for the current application. These
- are created once you call `injectTestHelpers` on your `Ember.Application`
- instance. The included helpers are also available on the `window` object by
- default, but can be used from this object on the individual application also.
- @property testHelpers
- @type {Object}
- @default {}
- */
- testHelpers: {},
- /**
- This property will contain the original methods that were registered
- on the `helperContainer` before `injectTestHelpers` is called.
- When `removeTestHelpers` is called, these methods are restored to the
- `helperContainer`.
- @property originalMethods
- @type {Object}
- @default {}
- @private
- */
- originalMethods: {},
- /**
- This property indicates whether or not this application is currently in
- testing mode. This is set when `setupForTesting` is called on the current
- application.
- @property testing
- @type {Boolean}
- @default false
- */
- testing: false,
- /**
- This hook defers the readiness of the application, so that you can start
- the app when your tests are ready to run. It also sets the router's
- location to 'none', so that the window's location will not be modified
- (preventing both accidental leaking of state between tests and interference
- with your testing framework).
- Example:
- ```
- App.setupForTesting();
- ```
- @method setupForTesting
- */
- setupForTesting: function() {
- Ember.testing = true;
- this.testing = true;
- this.Router.reopen({
- location: 'none'
- });
- // if adapter is not manually set default to QUnit
- if (!Ember.Test.adapter) {
- Ember.Test.adapter = Ember.Test.QUnitAdapter.create();
- }
- },
- /**
- This will be used as the container to inject the test helpers into. By
- default the helpers are injected into `window`.
- @property helperContainer
- @type {Object} The object to be used for test helpers.
- @default window
- */
- helperContainer: window,
- /**
- This injects the test helpers into the `helperContainer` object. If an object is provided
- it will be used as the helperContainer. If `helperContainer` is not set it will default
- to `window`. If a function of the same name has already been defined it will be cached
- (so that it can be reset if the helper is removed with `unregisterHelper` or
- `removeTestHelpers`).
- Any callbacks registered with `onInjectHelpers` will be called once the
- helpers have been injected.
- Example:
- ```
- App.injectTestHelpers();
- ```
- @method injectTestHelpers
- */
- injectTestHelpers: function(helperContainer) {
- if (helperContainer) { this.helperContainer = helperContainer; }
- this.testHelpers = {};
- for (var name in helpers) {
- this.originalMethods[name] = this.helperContainer[name];
- this.testHelpers[name] = this.helperContainer[name] = helper(this, name);
- protoWrap(Ember.Test.Promise.prototype, name, helper(this, name), helpers[name].meta.wait);
- }
- for(var i = 0, l = injectHelpersCallbacks.length; i < l; i++) {
- injectHelpersCallbacks[i](this);
- }
- },
- /**
- This removes all helpers that have been registered, and resets and functions
- that were overridden by the helpers.
- Example:
- ```javascript
- App.removeTestHelpers();
- ```
- @public
- @method removeTestHelpers
- */
- removeTestHelpers: function() {
- for (var name in helpers) {
- this.helperContainer[name] = this.originalMethods[name];
- delete this.testHelpers[name];
- delete this.originalMethods[name];
- }
- }
- });
- // This method is no longer needed
- // But still here for backwards compatibility
- // of helper chaining
- function protoWrap(proto, name, callback, isAsync) {
- proto[name] = function() {
- var args = arguments;
- if (isAsync) {
- return callback.apply(this, args);
- } else {
- return this.then(function() {
- return callback.apply(this, args);
- });
- }
- };
- }
- Ember.Test.Promise = function() {
- Ember.RSVP.Promise.apply(this, arguments);
- Ember.Test.lastPromise = this;
- };
- Ember.Test.Promise.prototype = Ember.create(Ember.RSVP.Promise.prototype);
- Ember.Test.Promise.prototype.constructor = Ember.Test.Promise;
- // Patch `then` to isolate async methods
- // specifically `Ember.Test.lastPromise`
- var originalThen = Ember.RSVP.Promise.prototype.then;
- Ember.Test.Promise.prototype.then = function(onSuccess, onFailure) {
- return originalThen.call(this, function(val) {
- return isolate(onSuccess, val);
- }, onFailure);
- };
- // This method isolates nested async methods
- // so that they don't conflict with other last promises.
- //
- // 1. Set `Ember.Test.lastPromise` to null
- // 2. Invoke method
- // 3. Return the last promise created during method
- // 4. Restore `Ember.Test.lastPromise` to original value
- function isolate(fn, val) {
- var value, lastPromise;
- // Reset lastPromise for nested helpers
- Ember.Test.lastPromise = null;
- value = fn(val);
- lastPromise = Ember.Test.lastPromise;
- // If the method returned a promise
- // return that promise. If not,
- // return the last async helper's promise
- if ((value && (value instanceof Ember.Test.Promise)) || !lastPromise) {
- return value;
- } else {
- run(function() {
- lastPromise = Ember.Test.resolve(lastPromise).then(function() {
- return value;
- });
- });
- return lastPromise;
- }
- }
- })();
- (function() {
- Ember.onLoad('Ember.Application', function(Application) {
- Application.initializer({
- name: 'deferReadiness in `testing` mode',
- initialize: function(container, application){
- if (application.testing) {
- application.deferReadiness();
- }
- }
- });
- });
- })();
- (function() {
- /**
- @module ember
- @submodule ember-testing
- */
- var $ = Ember.$;
- /**
- This method creates a checkbox and triggers the click event to fire the
- passed in handler. It is used to correct for a bug in older versions
- of jQuery (e.g 1.8.3).
- @private
- @method testCheckboxClick
- */
- function testCheckboxClick(handler) {
- $('<input type="checkbox">')
- .css({ position: 'absolute', left: '-1000px', top: '-1000px' })
- .appendTo('body')
- .on('click', handler)
- .trigger('click')
- .remove();
- }
- $(function() {
- /*
- Determine whether a checkbox checked using jQuery's "click" method will have
- the correct value for its checked property.
- If we determine that the current jQuery version exhibits this behavior,
- patch it to work correctly as in the commit for the actual fix:
- https://github.com/jquery/jquery/commit/1fb2f92.
- */
- testCheckboxClick(function() {
- if (!this.checked && !$.event.special.click) {
- $.event.special.click = {
- // For checkbox, fire native event so checked state will be right
- trigger: function() {
- if ($.nodeName( this, "input" ) && this.type === "checkbox" && this.click) {
- this.click();
- return false;
- }
- }
- };
- }
- });
- // Try again to verify that the patch took effect or blow up.
- testCheckboxClick(function() {
- Ember.warn("clicked checkboxes should be checked! the jQuery patch didn't work", this.checked);
- });
- });
- })();
- (function() {
- /**
- @module ember
- @submodule ember-testing
- */
- var Test = Ember.Test;
- /**
- The primary purpose of this class is to create hooks that can be implemented
- by an adapter for various test frameworks.
- @class Adapter
- @namespace Ember.Test
- */
- Test.Adapter = Ember.Object.extend({
- /**
- This callback will be called whenever an async operation is about to start.
- Override this to call your framework's methods that handle async
- operations.
- @public
- @method asyncStart
- */
- asyncStart: Ember.K,
- /**
- This callback will be called whenever an async operation has completed.
- @public
- @method asyncEnd
- */
- asyncEnd: Ember.K,
- /**
- Override this method with your testing framework's false assertion.
- This function is called whenever an exception occurs causing the testing
- promise to fail.
- QUnit example:
- ```javascript
- exception: function(error) {
- ok(false, error);
- };
- ```
- @public
- @method exception
- @param {String} error The exception to be raised.
- */
- exception: function(error) {
- throw error;
- }
- });
- /**
- This class implements the methods defined by Ember.Test.Adapter for the
- QUnit testing framework.
- @class QUnitAdapter
- @namespace Ember.Test
- @extends Ember.Test.Adapter
- */
- Test.QUnitAdapter = Test.Adapter.extend({
- asyncStart: function() {
- stop();
- },
- asyncEnd: function() {
- start();
- },
- exception: function(error) {
- ok(false, Ember.inspect(error));
- }
- });
- })();
- (function() {
- /**
- * @module ember
- * @submodule ember-testing
- */
- var get = Ember.get,
- Test = Ember.Test,
- helper = Test.registerHelper,
- asyncHelper = Test.registerAsyncHelper,
- countAsync = 0;
- Test.pendingAjaxRequests = 0;
- Test.onInjectHelpers(function() {
- Ember.$(document).ajaxStart(function() {
- Test.pendingAjaxRequests++;
- });
- Ember.$(document).ajaxStop(function() {
- Ember.assert("An ajaxStop event which would cause the number of pending AJAX " +
- "requests to be negative has been triggered. This is most likely " +
- "caused by AJAX events that were started before calling " +
- "`injectTestHelpers()`.", Test.pendingAjaxRequests !== 0);
- Test.pendingAjaxRequests--;
- });
- });
- function currentRouteName(app){
- var appController = app.__container__.lookup('controller:application');
- return get(appController, 'currentRouteName');
- }
- function currentPath(app){
- var appController = app.__container__.lookup('controller:application');
- return get(appController, 'currentPath');
- }
- function currentURL(app){
- var router = app.__container__.lookup('router:main');
- return get(router, 'location').getURL();
- }
- function visit(app, url) {
- var router = app.__container__.lookup('router:main');
- router.location.setURL(url);
- if (app._readinessDeferrals > 0) {
- router['initialURL'] = url;
- Ember.run(app, 'advanceReadiness');
- delete router['initialURL'];
- } else {
- Ember.run(app, app.handleURL, url);
- }
- return wait(app);
- }
- function click(app, selector, context) {
- var $el = findWithAssert(app, selector, context);
- Ember.run($el, 'mousedown');
- if ($el.is(':input')) {
- var type = $el.prop('type');
- if (type !== 'checkbox' && type !== 'radio' && type !== 'hidden') {
- Ember.run($el, function(){
- // Firefox does not trigger the `focusin` event if the window
- // does not have focus. If the document doesn't have focus just
- // use trigger('focusin') instead.
- if (!document.hasFocus || document.hasFocus()) {
- this.focus();
- } else {
- this.trigger('focusin');
- }
- });
- }
- }
- Ember.run($el, 'mouseup');
- Ember.run($el, 'click');
- return wait(app);
- }
- function triggerEvent(app, selector, context, event){
- if (typeof method === 'undefined') {
- event = context;
- context = null;
- }
- var $el = findWithAssert(app, selector, context);
- Ember.run($el, 'trigger', event);
- return wait(app);
- }
- function keyEvent(app, selector, context, type, keyCode) {
- var $el;
- if (typeof keyCode === 'undefined') {
- keyCode = type;
- type = context;
- context = null;
- }
- $el = findWithAssert(app, selector, context);
- var event = Ember.$.Event(type, { keyCode: keyCode });
- Ember.run($el, 'trigger', event);
- return wait(app);
- }
- function fillIn(app, selector, context, text) {
- var $el;
- if (typeof text === 'undefined') {
- text = context;
- context = null;
- }
- $el = findWithAssert(app, selector, context);
- Ember.run(function() {
- $el.val(text).change();
- });
- return wait(app);
- }
- function findWithAssert(app, selector, context) {
- var $el = find(app, selector, context);
- if ($el.length === 0) {
- throw new Ember.Error("Element " + selector + " not found.");
- }
- return $el;
- }
- function find(app, selector, context) {
- var $el;
- context = context || get(app, 'rootElement');
- $el = app.$(selector, context);
- return $el;
- }
- function andThen(app, callback) {
- return wait(app, callback(app));
- }
- function wait(app, value) {
- return Test.promise(function(resolve) {
- // If this is the first async promise, kick off the async test
- if (++countAsync === 1) {
- Test.adapter.asyncStart();
- }
- // Every 10ms, poll for the async thing to have finished
- var watcher = setInterval(function() {
- // 1. If the router is loading, keep polling
- var routerIsLoading = !!app.__container__.lookup('router:main').router.activeTransition;
- if (routerIsLoading) { return; }
- // 2. If there are pending Ajax requests, keep polling
- if (Test.pendingAjaxRequests) { return; }
- // 3. If there are scheduled timers or we are inside of a run loop, keep polling
- if (Ember.run.hasScheduledTimers() || Ember.run.currentRunLoop) { return; }
- if (Test.waiters && Test.waiters.any(function(waiter) {
- var context = waiter[0];
- var callback = waiter[1];
- return !callback.call(context);
- })) { return; }
- // Stop polling
- clearInterval(watcher);
- // If this is the last async promise, end the async test
- if (--countAsync === 0) {
- Test.adapter.asyncEnd();
- }
- // Synchronously resolve the promise
- Ember.run(null, resolve, value);
- }, 10);
- });
- }
- /**
- * Loads a route, sets up any controllers, and renders any templates associated
- * with the route as though a real user had triggered the route change while
- * using your app.
- *
- * Example:
- *
- * ```javascript
- * visit('posts/index').then(function() {
- * // assert something
- * });
- * ```
- *
- * @method visit
- * @param {String} url the name of the route
- * @return {RSVP.Promise}
- */
- asyncHelper('visit', visit);
- /**
- * Clicks an element and triggers any actions triggered by the element's `click`
- * event.
- *
- * Example:
- *
- * ```javascript
- * click('.some-jQuery-selector').then(function() {
- * // assert something
- * });
- * ```
- *
- * @method click
- * @param {String} selector jQuery selector for finding element on the DOM
- * @return {RSVP.Promise}
- */
- asyncHelper('click', click);
- /**
- * Simulates a key event, e.g. `keypress`, `keydown`, `keyup` with the desired keyCode
- *
- * Example:
- *
- * ```javascript
- * keyEvent('.some-jQuery-selector', 'keypress', 13).then(function() {
- * // assert something
- * });
- * ```
- *
- * @method keyEvent
- * @param {String} selector jQuery selector for finding element on the DOM
- * @param {String} the type of key event, e.g. `keypress`, `keydown`, `keyup`
- * @param {Number} the keyCode of the simulated key event
- * @return {RSVP.Promise}
- */
- asyncHelper('keyEvent', keyEvent);
- /**
- * Fills in an input element with some text.
- *
- * Example:
- *
- * ```javascript
- * fillIn('#email', 'you@example.com').then(function() {
- * // assert something
- * });
- * ```
- *
- * @method fillIn
- * @param {String} selector jQuery selector finding an input element on the DOM
- * to fill text with
- * @param {String} text text to place inside the input element
- * @return {RSVP.Promise}
- */
- asyncHelper('fillIn', fillIn);
- /**
- * Finds an element in the context of the app's container element. A simple alias
- * for `app.$(selector)`.
- *
- * Example:
- *
- * ```javascript
- * var $el = find('.my-selector');
- * ```
- *
- * @method find
- * @param {String} selector jQuery string selector for element lookup
- * @return {Object} jQuery object representing the results of the query
- */
- helper('find', find);
- /**
- * Like `find`, but throws an error if the element selector returns no results.
- *
- * Example:
- *
- * ```javascript
- * var $el = findWithAssert('.doesnt-exist'); // throws error
- * ```
- *
- * @method findWithAssert
- * @param {String} selector jQuery selector string for finding an element within
- * the DOM
- * @return {Object} jQuery object representing the results of the query
- * @throws {Error} throws error if jQuery object returned has a length of 0
- */
- helper('findWithAssert', findWithAssert);
- /**
- Causes the run loop to process any pending events. This is used to ensure that
- any async operations from other helpers (or your assertions) have been processed.
- This is most often used as the return value for the helper functions (see 'click',
- 'fillIn','visit',etc).
- Example:
- ```javascript
- Ember.Test.registerAsyncHelper('loginUser', function(app, username, password) {
- visit('secured/path/here')
- .fillIn('#username', username)
- .fillIn('#password', username)
- .click('.submit')
- return wait();
- });
- @method wait
- @param {Object} value The value to be returned.
- @return {RSVP.Promise}
- */
- asyncHelper('wait', wait);
- asyncHelper('andThen', andThen);
- })();
- (function() {
- /**
- Ember Testing
- @module ember
- @submodule ember-testing
- @requires ember-application
- */
- })();
- (function() {
- /**
- Ember
- @module ember
- */
- function throwWithMessage(msg) {
- return function() {
- throw new Ember.Error(msg);
- };
- }
- function generateRemovedClass(className) {
- var msg = " has been moved into a plugin: https://github.com/emberjs/ember-states";
- return {
- extend: throwWithMessage(className + msg),
- create: throwWithMessage(className + msg)
- };
- }
- Ember.StateManager = generateRemovedClass("Ember.StateManager");
- /**
- This was exported to ember-states plugin for v 1.0.0 release. See: https://github.com/emberjs/ember-states
-
- @class StateManager
- @namespace Ember
- */
- Ember.State = generateRemovedClass("Ember.State");
- /**
- This was exported to ember-states plugin for v 1.0.0 release. See: https://github.com/emberjs/ember-states
-
- @class State
- @namespace Ember
- */
- })();
- })();
|