12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022702370247025702670277028702970307031703270337034703570367037703870397040704170427043704470457046704770487049705070517052705370547055705670577058705970607061706270637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106710771087109711071117112711371147115711671177118711971207121712271237124712571267127712871297130713171327133713471357136713771387139714071417142714371447145714671477148714971507151715271537154715571567157715871597160716171627163716471657166716771687169717071717172717371747175717671777178717971807181718271837184718571867187718871897190719171927193719471957196719771987199720072017202720372047205720672077208720972107211721272137214721572167217721872197220722172227223722472257226722772287229723072317232723372347235723672377238723972407241724272437244724572467247724872497250725172527253725472557256725772587259726072617262726372647265726672677268726972707271727272737274727572767277727872797280728172827283728472857286728772887289729072917292729372947295729672977298729973007301730273037304730573067307730873097310731173127313731473157316731773187319732073217322732373247325732673277328732973307331733273337334733573367337733873397340734173427343734473457346734773487349735073517352735373547355735673577358735973607361736273637364736573667367736873697370737173727373737473757376737773787379738073817382738373847385738673877388738973907391739273937394739573967397739873997400740174027403740474057406740774087409741074117412741374147415741674177418741974207421742274237424742574267427742874297430743174327433743474357436743774387439744074417442744374447445744674477448744974507451745274537454745574567457745874597460746174627463746474657466746774687469747074717472747374747475747674777478747974807481748274837484748574867487748874897490749174927493749474957496749774987499750075017502750375047505750675077508750975107511751275137514751575167517751875197520752175227523752475257526752775287529753075317532753375347535753675377538753975407541754275437544754575467547754875497550755175527553755475557556755775587559756075617562756375647565756675677568756975707571757275737574757575767577757875797580758175827583758475857586758775887589759075917592759375947595759675977598759976007601760276037604760576067607760876097610761176127613761476157616761776187619762076217622762376247625762676277628762976307631763276337634763576367637763876397640764176427643764476457646764776487649765076517652765376547655765676577658765976607661766276637664766576667667766876697670767176727673767476757676767776787679768076817682768376847685768676877688768976907691769276937694769576967697769876997700770177027703770477057706770777087709771077117712771377147715771677177718771977207721772277237724772577267727772877297730773177327733773477357736773777387739774077417742774377447745774677477748774977507751775277537754775577567757775877597760776177627763776477657766776777687769777077717772777377747775777677777778777977807781778277837784778577867787778877897790779177927793779477957796779777987799780078017802780378047805780678077808780978107811781278137814781578167817781878197820782178227823782478257826782778287829783078317832783378347835783678377838783978407841784278437844784578467847784878497850785178527853785478557856785778587859786078617862786378647865786678677868786978707871787278737874787578767877787878797880788178827883788478857886788778887889789078917892789378947895789678977898789979007901790279037904790579067907790879097910791179127913791479157916791779187919792079217922792379247925792679277928792979307931793279337934793579367937793879397940794179427943794479457946794779487949795079517952795379547955795679577958795979607961796279637964796579667967796879697970797179727973797479757976797779787979798079817982798379847985798679877988798979907991799279937994799579967997799879998000800180028003800480058006800780088009801080118012801380148015801680178018801980208021802280238024802580268027802880298030803180328033803480358036803780388039804080418042804380448045804680478048804980508051805280538054805580568057805880598060806180628063806480658066806780688069807080718072807380748075807680778078807980808081808280838084808580868087808880898090809180928093809480958096809780988099810081018102810381048105810681078108810981108111811281138114811581168117811881198120812181228123812481258126812781288129813081318132813381348135813681378138813981408141814281438144814581468147814881498150815181528153815481558156815781588159816081618162816381648165816681678168816981708171817281738174817581768177817881798180818181828183818481858186818781888189819081918192819381948195819681978198819982008201820282038204820582068207820882098210821182128213821482158216821782188219822082218222822382248225822682278228822982308231823282338234823582368237823882398240824182428243824482458246824782488249825082518252825382548255825682578258825982608261826282638264826582668267826882698270827182728273827482758276827782788279828082818282828382848285828682878288828982908291829282938294829582968297829882998300830183028303830483058306830783088309831083118312831383148315831683178318831983208321832283238324832583268327832883298330833183328333833483358336833783388339834083418342834383448345834683478348834983508351835283538354835583568357835883598360836183628363836483658366836783688369837083718372837383748375837683778378837983808381838283838384838583868387838883898390839183928393839483958396839783988399840084018402840384048405840684078408840984108411841284138414841584168417841884198420842184228423842484258426842784288429843084318432843384348435843684378438843984408441844284438444844584468447844884498450845184528453845484558456845784588459846084618462846384648465846684678468846984708471847284738474847584768477847884798480848184828483848484858486848784888489849084918492849384948495849684978498849985008501850285038504850585068507850885098510851185128513851485158516851785188519852085218522852385248525852685278528852985308531853285338534853585368537853885398540854185428543854485458546854785488549855085518552855385548555855685578558855985608561856285638564856585668567856885698570857185728573857485758576857785788579858085818582858385848585858685878588858985908591859285938594859585968597859885998600860186028603860486058606860786088609861086118612861386148615861686178618861986208621862286238624862586268627862886298630863186328633863486358636863786388639864086418642864386448645864686478648864986508651865286538654865586568657865886598660866186628663866486658666866786688669867086718672867386748675867686778678867986808681868286838684868586868687868886898690869186928693869486958696869786988699870087018702870387048705870687078708870987108711871287138714871587168717871887198720872187228723872487258726872787288729873087318732873387348735873687378738873987408741874287438744874587468747874887498750875187528753875487558756875787588759876087618762876387648765876687678768876987708771877287738774877587768777877887798780878187828783878487858786878787888789879087918792879387948795879687978798879988008801880288038804880588068807880888098810881188128813881488158816881788188819882088218822882388248825882688278828882988308831883288338834883588368837883888398840884188428843884488458846884788488849885088518852885388548855885688578858885988608861886288638864886588668867886888698870887188728873887488758876887788788879888088818882888388848885888688878888888988908891889288938894889588968897889888998900890189028903890489058906890789088909891089118912891389148915891689178918891989208921892289238924892589268927892889298930893189328933893489358936893789388939894089418942894389448945894689478948894989508951895289538954895589568957895889598960896189628963896489658966896789688969897089718972897389748975897689778978897989808981898289838984898589868987898889898990899189928993899489958996899789988999900090019002900390049005900690079008900990109011901290139014901590169017901890199020902190229023902490259026902790289029903090319032903390349035903690379038903990409041904290439044904590469047904890499050905190529053905490559056905790589059906090619062906390649065906690679068906990709071907290739074907590769077907890799080908190829083908490859086908790889089909090919092909390949095909690979098909991009101910291039104910591069107910891099110911191129113911491159116911791189119912091219122912391249125912691279128912991309131913291339134913591369137913891399140914191429143914491459146914791489149915091519152915391549155915691579158915991609161916291639164916591669167916891699170917191729173917491759176917791789179918091819182918391849185918691879188918991909191919291939194919591969197919891999200920192029203920492059206920792089209921092119212921392149215921692179218921992209221922292239224922592269227922892299230923192329233923492359236923792389239924092419242924392449245924692479248924992509251925292539254925592569257925892599260926192629263926492659266926792689269927092719272927392749275927692779278927992809281928292839284928592869287928892899290929192929293929492959296929792989299930093019302930393049305930693079308930993109311931293139314931593169317931893199320932193229323932493259326932793289329933093319332933393349335933693379338933993409341934293439344934593469347934893499350935193529353935493559356935793589359936093619362936393649365936693679368936993709371937293739374937593769377937893799380938193829383938493859386938793889389939093919392939393949395939693979398939994009401940294039404940594069407940894099410941194129413941494159416941794189419942094219422942394249425942694279428942994309431943294339434943594369437943894399440944194429443944494459446944794489449945094519452945394549455945694579458945994609461946294639464946594669467946894699470947194729473947494759476947794789479948094819482948394849485948694879488948994909491949294939494949594969497949894999500950195029503950495059506950795089509951095119512951395149515951695179518951995209521952295239524952595269527952895299530953195329533953495359536953795389539954095419542954395449545954695479548954995509551955295539554955595569557955895599560956195629563956495659566956795689569957095719572957395749575957695779578957995809581958295839584958595869587958895899590959195929593959495959596959795989599960096019602960396049605960696079608960996109611961296139614961596169617961896199620962196229623962496259626962796289629963096319632963396349635963696379638963996409641964296439644964596469647964896499650965196529653965496559656965796589659966096619662966396649665966696679668966996709671967296739674967596769677967896799680968196829683968496859686968796889689969096919692969396949695969696979698969997009701970297039704970597069707970897099710971197129713971497159716971797189719972097219722972397249725972697279728972997309731973297339734973597369737973897399740974197429743974497459746974797489749975097519752975397549755975697579758975997609761976297639764976597669767976897699770977197729773977497759776977797789779978097819782978397849785978697879788978997909791979297939794979597969797979897999800980198029803980498059806980798089809981098119812981398149815981698179818981998209821982298239824982598269827982898299830983198329833983498359836983798389839984098419842984398449845984698479848984998509851985298539854985598569857985898599860986198629863986498659866986798689869987098719872987398749875987698779878987998809881988298839884988598869887988898899890989198929893989498959896989798989899990099019902990399049905990699079908990999109911991299139914991599169917991899199920992199229923992499259926992799289929993099319932993399349935993699379938993999409941994299439944994599469947994899499950995199529953995499559956995799589959996099619962996399649965996699679968996999709971997299739974997599769977997899799980998199829983998499859986998799889989999099919992999399949995999699979998999910000100011000210003100041000510006100071000810009100101001110012100131001410015100161001710018100191002010021100221002310024100251002610027100281002910030100311003210033100341003510036100371003810039100401004110042100431004410045100461004710048100491005010051100521005310054100551005610057100581005910060100611006210063100641006510066100671006810069100701007110072100731007410075100761007710078100791008010081100821008310084100851008610087100881008910090100911009210093100941009510096100971009810099101001010110102101031010410105101061010710108101091011010111101121011310114101151011610117101181011910120101211012210123101241012510126101271012810129101301013110132101331013410135101361013710138101391014010141101421014310144101451014610147101481014910150101511015210153101541015510156101571015810159101601016110162101631016410165101661016710168101691017010171101721017310174101751017610177101781017910180101811018210183101841018510186101871018810189101901019110192101931019410195101961019710198101991020010201102021020310204102051020610207102081020910210102111021210213102141021510216102171021810219102201022110222102231022410225102261022710228102291023010231102321023310234102351023610237102381023910240102411024210243102441024510246102471024810249102501025110252102531025410255102561025710258102591026010261102621026310264102651026610267102681026910270102711027210273102741027510276102771027810279102801028110282102831028410285102861028710288102891029010291102921029310294102951029610297102981029910300103011030210303103041030510306103071030810309103101031110312103131031410315103161031710318103191032010321103221032310324103251032610327103281032910330103311033210333103341033510336103371033810339103401034110342103431034410345103461034710348103491035010351103521035310354103551035610357103581035910360103611036210363103641036510366103671036810369103701037110372103731037410375103761037710378103791038010381103821038310384103851038610387103881038910390103911039210393103941039510396103971039810399104001040110402104031040410405104061040710408104091041010411104121041310414104151041610417104181041910420104211042210423104241042510426104271042810429104301043110432104331043410435104361043710438104391044010441104421044310444104451044610447104481044910450104511045210453104541045510456104571045810459104601046110462104631046410465104661046710468104691047010471104721047310474104751047610477104781047910480104811048210483104841048510486104871048810489104901049110492104931049410495104961049710498104991050010501105021050310504105051050610507105081050910510105111051210513105141051510516105171051810519105201052110522105231052410525105261052710528105291053010531105321053310534105351053610537105381053910540105411054210543105441054510546105471054810549105501055110552105531055410555105561055710558105591056010561105621056310564105651056610567105681056910570105711057210573105741057510576105771057810579105801058110582105831058410585105861058710588105891059010591105921059310594105951059610597105981059910600106011060210603106041060510606106071060810609106101061110612106131061410615106161061710618106191062010621106221062310624106251062610627106281062910630106311063210633106341063510636106371063810639106401064110642106431064410645106461064710648106491065010651106521065310654106551065610657106581065910660106611066210663106641066510666106671066810669106701067110672106731067410675106761067710678106791068010681106821068310684106851068610687106881068910690106911069210693106941069510696106971069810699107001070110702107031070410705107061070710708107091071010711107121071310714107151071610717107181071910720107211072210723107241072510726107271072810729107301073110732107331073410735107361073710738107391074010741107421074310744107451074610747107481074910750107511075210753107541075510756107571075810759107601076110762107631076410765107661076710768107691077010771107721077310774107751077610777107781077910780107811078210783107841078510786107871078810789107901079110792107931079410795107961079710798107991080010801108021080310804108051080610807108081080910810108111081210813108141081510816108171081810819108201082110822108231082410825108261082710828108291083010831108321083310834108351083610837108381083910840108411084210843108441084510846108471084810849108501085110852108531085410855108561085710858108591086010861108621086310864108651086610867108681086910870108711087210873108741087510876108771087810879108801088110882108831088410885108861088710888108891089010891108921089310894108951089610897108981089910900109011090210903109041090510906109071090810909109101091110912109131091410915109161091710918109191092010921109221092310924109251092610927109281092910930109311093210933109341093510936109371093810939109401094110942109431094410945109461094710948109491095010951109521095310954109551095610957109581095910960109611096210963109641096510966109671096810969109701097110972109731097410975109761097710978109791098010981109821098310984109851098610987109881098910990109911099210993109941099510996109971099810999110001100111002110031100411005110061100711008110091101011011110121101311014110151101611017110181101911020110211102211023110241102511026110271102811029110301103111032110331103411035110361103711038110391104011041110421104311044110451104611047110481104911050110511105211053110541105511056110571105811059110601106111062110631106411065110661106711068110691107011071110721107311074110751107611077110781107911080110811108211083110841108511086110871108811089110901109111092110931109411095110961109711098110991110011101111021110311104111051110611107111081110911110111111111211113111141111511116111171111811119111201112111122111231112411125111261112711128111291113011131111321113311134111351113611137111381113911140111411114211143111441114511146111471114811149111501115111152111531115411155111561115711158111591116011161111621116311164111651116611167111681116911170111711117211173111741117511176111771117811179111801118111182111831118411185111861118711188111891119011191111921119311194111951119611197111981119911200112011120211203112041120511206112071120811209112101121111212112131121411215112161121711218112191122011221112221122311224112251122611227112281122911230112311123211233112341123511236112371123811239112401124111242112431124411245112461124711248112491125011251112521125311254112551125611257112581125911260112611126211263112641126511266112671126811269112701127111272112731127411275112761127711278112791128011281112821128311284112851128611287112881128911290112911129211293112941129511296112971129811299113001130111302113031130411305113061130711308113091131011311113121131311314113151131611317113181131911320113211132211323113241132511326113271132811329113301133111332113331133411335113361133711338113391134011341113421134311344113451134611347113481134911350113511135211353113541135511356113571135811359113601136111362113631136411365113661136711368113691137011371113721137311374113751137611377113781137911380113811138211383113841138511386113871138811389113901139111392113931139411395113961139711398113991140011401114021140311404114051140611407114081140911410114111141211413114141141511416114171141811419114201142111422114231142411425114261142711428114291143011431114321143311434114351143611437114381143911440114411144211443114441144511446114471144811449114501145111452114531145411455114561145711458114591146011461114621146311464114651146611467114681146911470114711147211473114741147511476114771147811479114801148111482114831148411485114861148711488114891149011491114921149311494114951149611497114981149911500115011150211503115041150511506115071150811509115101151111512115131151411515115161151711518115191152011521115221152311524115251152611527115281152911530115311153211533115341153511536115371153811539115401154111542115431154411545115461154711548115491155011551115521155311554115551155611557115581155911560115611156211563115641156511566115671156811569115701157111572115731157411575115761157711578115791158011581115821158311584115851158611587115881158911590115911159211593115941159511596115971159811599116001160111602116031160411605116061160711608116091161011611116121161311614116151161611617116181161911620116211162211623116241162511626116271162811629116301163111632116331163411635116361163711638116391164011641116421164311644116451164611647116481164911650116511165211653116541165511656116571165811659116601166111662116631166411665116661166711668116691167011671116721167311674116751167611677116781167911680116811168211683116841168511686116871168811689116901169111692116931169411695116961169711698116991170011701117021170311704117051170611707117081170911710117111171211713117141171511716117171171811719117201172111722117231172411725117261172711728117291173011731117321173311734117351173611737117381173911740117411174211743117441174511746117471174811749117501175111752117531175411755117561175711758117591176011761117621176311764117651176611767117681176911770117711177211773117741177511776117771177811779117801178111782117831178411785117861178711788117891179011791117921179311794117951179611797117981179911800118011180211803118041180511806118071180811809118101181111812118131181411815118161181711818118191182011821118221182311824118251182611827118281182911830118311183211833118341183511836118371183811839118401184111842118431184411845118461184711848118491185011851118521185311854118551185611857118581185911860118611186211863118641186511866118671186811869118701187111872118731187411875118761187711878118791188011881118821188311884118851188611887118881188911890118911189211893118941189511896118971189811899119001190111902119031190411905119061190711908119091191011911119121191311914119151191611917119181191911920119211192211923119241192511926119271192811929119301193111932119331193411935119361193711938119391194011941119421194311944119451194611947119481194911950119511195211953119541195511956119571195811959119601196111962119631196411965119661196711968119691197011971119721197311974119751197611977119781197911980119811198211983119841198511986119871198811989119901199111992119931199411995119961199711998119991200012001120021200312004120051200612007120081200912010120111201212013120141201512016120171201812019120201202112022120231202412025120261202712028120291203012031120321203312034120351203612037120381203912040120411204212043120441204512046120471204812049120501205112052120531205412055120561205712058120591206012061120621206312064120651206612067120681206912070120711207212073120741207512076120771207812079120801208112082120831208412085120861208712088120891209012091120921209312094120951209612097120981209912100121011210212103121041210512106121071210812109121101211112112121131211412115121161211712118121191212012121121221212312124121251212612127121281212912130121311213212133121341213512136121371213812139121401214112142121431214412145121461214712148121491215012151121521215312154121551215612157121581215912160121611216212163121641216512166121671216812169121701217112172121731217412175121761217712178121791218012181121821218312184121851218612187121881218912190121911219212193121941219512196121971219812199122001220112202122031220412205122061220712208122091221012211122121221312214122151221612217122181221912220122211222212223122241222512226122271222812229122301223112232122331223412235122361223712238122391224012241122421224312244122451224612247122481224912250122511225212253122541225512256122571225812259122601226112262122631226412265122661226712268122691227012271122721227312274122751227612277122781227912280122811228212283122841228512286122871228812289122901229112292122931229412295122961229712298122991230012301123021230312304123051230612307123081230912310123111231212313123141231512316123171231812319123201232112322123231232412325123261232712328123291233012331123321233312334123351233612337123381233912340123411234212343123441234512346123471234812349123501235112352123531235412355123561235712358123591236012361123621236312364123651236612367123681236912370123711237212373123741237512376123771237812379123801238112382123831238412385123861238712388123891239012391123921239312394123951239612397123981239912400124011240212403124041240512406124071240812409124101241112412124131241412415124161241712418124191242012421124221242312424124251242612427124281242912430124311243212433124341243512436124371243812439124401244112442124431244412445124461244712448124491245012451124521245312454124551245612457124581245912460124611246212463124641246512466124671246812469124701247112472124731247412475124761247712478124791248012481124821248312484124851248612487124881248912490124911249212493124941249512496124971249812499125001250112502125031250412505125061250712508125091251012511125121251312514125151251612517125181251912520125211252212523125241252512526125271252812529125301253112532125331253412535125361253712538125391254012541125421254312544125451254612547125481254912550125511255212553125541255512556125571255812559125601256112562125631256412565125661256712568125691257012571125721257312574125751257612577125781257912580125811258212583125841258512586125871258812589125901259112592125931259412595125961259712598125991260012601126021260312604126051260612607126081260912610126111261212613126141261512616126171261812619126201262112622126231262412625126261262712628126291263012631126321263312634126351263612637126381263912640126411264212643126441264512646126471264812649126501265112652126531265412655126561265712658126591266012661126621266312664126651266612667126681266912670126711267212673126741267512676126771267812679126801268112682126831268412685126861268712688126891269012691126921269312694126951269612697126981269912700127011270212703127041270512706127071270812709127101271112712127131271412715127161271712718127191272012721127221272312724127251272612727127281272912730127311273212733127341273512736127371273812739127401274112742127431274412745127461274712748127491275012751127521275312754127551275612757127581275912760127611276212763127641276512766127671276812769127701277112772127731277412775127761277712778127791278012781127821278312784127851278612787127881278912790127911279212793127941279512796127971279812799128001280112802128031280412805128061280712808128091281012811128121281312814128151281612817128181281912820128211282212823128241282512826128271282812829128301283112832128331283412835128361283712838128391284012841128421284312844128451284612847128481284912850128511285212853128541285512856128571285812859128601286112862128631286412865128661286712868128691287012871128721287312874128751287612877128781287912880128811288212883128841288512886128871288812889128901289112892128931289412895128961289712898128991290012901129021290312904129051290612907129081290912910129111291212913129141291512916129171291812919129201292112922129231292412925129261292712928129291293012931129321293312934129351293612937129381293912940129411294212943129441294512946129471294812949129501295112952129531295412955129561295712958129591296012961129621296312964129651296612967129681296912970129711297212973129741297512976129771297812979129801298112982129831298412985129861298712988129891299012991129921299312994129951299612997129981299913000130011300213003130041300513006130071300813009130101301113012130131301413015130161301713018130191302013021130221302313024130251302613027130281302913030130311303213033130341303513036130371303813039130401304113042130431304413045130461304713048130491305013051130521305313054130551305613057130581305913060130611306213063130641306513066130671306813069130701307113072130731307413075130761307713078130791308013081130821308313084130851308613087130881308913090130911309213093130941309513096130971309813099131001310113102131031310413105131061310713108131091311013111131121311313114131151311613117131181311913120131211312213123131241312513126131271312813129131301313113132131331313413135131361313713138131391314013141131421314313144131451314613147131481314913150131511315213153131541315513156131571315813159131601316113162131631316413165131661316713168131691317013171131721317313174131751317613177131781317913180131811318213183131841318513186131871318813189131901319113192131931319413195131961319713198131991320013201132021320313204132051320613207132081320913210132111321213213132141321513216132171321813219132201322113222132231322413225132261322713228132291323013231132321323313234132351323613237132381323913240132411324213243132441324513246132471324813249132501325113252132531325413255132561325713258132591326013261132621326313264132651326613267132681326913270132711327213273132741327513276132771327813279132801328113282132831328413285132861328713288132891329013291132921329313294132951329613297132981329913300133011330213303133041330513306133071330813309133101331113312133131331413315133161331713318133191332013321133221332313324133251332613327133281332913330133311333213333133341333513336133371333813339133401334113342133431334413345133461334713348133491335013351133521335313354133551335613357133581335913360133611336213363133641336513366133671336813369133701337113372133731337413375133761337713378133791338013381133821338313384133851338613387133881338913390133911339213393133941339513396133971339813399134001340113402134031340413405134061340713408134091341013411134121341313414134151341613417134181341913420134211342213423134241342513426134271342813429134301343113432134331343413435134361343713438134391344013441134421344313444134451344613447134481344913450134511345213453134541345513456134571345813459134601346113462134631346413465134661346713468134691347013471134721347313474134751347613477134781347913480134811348213483134841348513486134871348813489134901349113492134931349413495134961349713498134991350013501135021350313504135051350613507135081350913510135111351213513135141351513516135171351813519135201352113522135231352413525135261352713528135291353013531135321353313534135351353613537135381353913540135411354213543135441354513546135471354813549135501355113552135531355413555135561355713558135591356013561135621356313564135651356613567135681356913570135711357213573135741357513576135771357813579135801358113582135831358413585135861358713588135891359013591135921359313594135951359613597135981359913600136011360213603136041360513606136071360813609136101361113612136131361413615136161361713618136191362013621136221362313624136251362613627136281362913630136311363213633136341363513636136371363813639136401364113642136431364413645136461364713648136491365013651136521365313654136551365613657136581365913660136611366213663136641366513666136671366813669136701367113672136731367413675136761367713678136791368013681136821368313684136851368613687136881368913690136911369213693136941369513696136971369813699137001370113702137031370413705137061370713708137091371013711137121371313714137151371613717137181371913720137211372213723137241372513726137271372813729137301373113732137331373413735137361373713738137391374013741137421374313744137451374613747137481374913750137511375213753137541375513756137571375813759137601376113762137631376413765137661376713768137691377013771137721377313774137751377613777137781377913780137811378213783137841378513786137871378813789137901379113792137931379413795137961379713798137991380013801138021380313804138051380613807138081380913810138111381213813138141381513816138171381813819138201382113822138231382413825138261382713828138291383013831138321383313834138351383613837138381383913840138411384213843138441384513846138471384813849138501385113852138531385413855138561385713858138591386013861138621386313864138651386613867138681386913870138711387213873138741387513876138771387813879138801388113882138831388413885138861388713888138891389013891138921389313894138951389613897138981389913900139011390213903139041390513906139071390813909139101391113912139131391413915139161391713918139191392013921139221392313924139251392613927139281392913930139311393213933139341393513936139371393813939139401394113942139431394413945139461394713948139491395013951139521395313954139551395613957139581395913960139611396213963139641396513966139671396813969139701397113972139731397413975139761397713978139791398013981139821398313984139851398613987139881398913990139911399213993139941399513996139971399813999140001400114002140031400414005140061400714008140091401014011140121401314014140151401614017140181401914020140211402214023140241402514026140271402814029140301403114032140331403414035140361403714038140391404014041140421404314044140451404614047140481404914050140511405214053140541405514056140571405814059140601406114062140631406414065140661406714068140691407014071140721407314074140751407614077140781407914080140811408214083140841408514086140871408814089140901409114092140931409414095140961409714098140991410014101141021410314104141051410614107141081410914110141111411214113141141411514116141171411814119141201412114122141231412414125141261412714128141291413014131141321413314134141351413614137141381413914140141411414214143141441414514146141471414814149141501415114152141531415414155141561415714158141591416014161141621416314164141651416614167141681416914170141711417214173141741417514176141771417814179141801418114182141831418414185141861418714188141891419014191141921419314194141951419614197141981419914200142011420214203142041420514206142071420814209142101421114212142131421414215142161421714218142191422014221142221422314224142251422614227142281422914230142311423214233142341423514236142371423814239142401424114242142431424414245142461424714248142491425014251142521425314254142551425614257142581425914260142611426214263142641426514266142671426814269142701427114272142731427414275142761427714278142791428014281142821428314284142851428614287142881428914290142911429214293142941429514296142971429814299143001430114302143031430414305143061430714308143091431014311143121431314314143151431614317143181431914320143211432214323143241432514326143271432814329143301433114332143331433414335143361433714338143391434014341143421434314344143451434614347143481434914350143511435214353143541435514356143571435814359143601436114362143631436414365143661436714368143691437014371143721437314374143751437614377143781437914380143811438214383143841438514386143871438814389143901439114392143931439414395143961439714398143991440014401144021440314404144051440614407144081440914410144111441214413144141441514416144171441814419144201442114422144231442414425144261442714428144291443014431144321443314434144351443614437144381443914440144411444214443144441444514446144471444814449144501445114452144531445414455144561445714458144591446014461144621446314464144651446614467144681446914470144711447214473144741447514476144771447814479144801448114482144831448414485144861448714488144891449014491144921449314494144951449614497144981449914500145011450214503145041450514506145071450814509145101451114512145131451414515145161451714518145191452014521145221452314524145251452614527145281452914530145311453214533145341453514536145371453814539145401454114542145431454414545145461454714548145491455014551145521455314554145551455614557145581455914560145611456214563145641456514566145671456814569145701457114572145731457414575145761457714578145791458014581145821458314584145851458614587145881458914590145911459214593145941459514596145971459814599146001460114602146031460414605146061460714608146091461014611146121461314614146151461614617146181461914620146211462214623146241462514626146271462814629146301463114632146331463414635146361463714638146391464014641146421464314644146451464614647146481464914650146511465214653146541465514656146571465814659146601466114662146631466414665146661466714668146691467014671146721467314674146751467614677146781467914680146811468214683146841468514686146871468814689146901469114692146931469414695146961469714698146991470014701147021470314704147051470614707147081470914710147111471214713147141471514716147171471814719147201472114722147231472414725147261472714728147291473014731147321473314734147351473614737147381473914740147411474214743147441474514746147471474814749147501475114752147531475414755147561475714758147591476014761147621476314764147651476614767147681476914770147711477214773147741477514776147771477814779147801478114782147831478414785147861478714788147891479014791147921479314794147951479614797147981479914800148011480214803148041480514806148071480814809148101481114812148131481414815148161481714818148191482014821148221482314824148251482614827148281482914830148311483214833148341483514836148371483814839148401484114842148431484414845148461484714848148491485014851148521485314854148551485614857148581485914860148611486214863148641486514866148671486814869148701487114872148731487414875148761487714878148791488014881148821488314884148851488614887148881488914890148911489214893148941489514896148971489814899149001490114902149031490414905149061490714908149091491014911149121491314914149151491614917149181491914920149211492214923149241492514926149271492814929149301493114932149331493414935149361493714938149391494014941149421494314944149451494614947149481494914950149511495214953149541495514956149571495814959149601496114962149631496414965149661496714968149691497014971149721497314974149751497614977149781497914980149811498214983149841498514986149871498814989149901499114992149931499414995149961499714998149991500015001150021500315004150051500615007150081500915010150111501215013150141501515016150171501815019150201502115022150231502415025150261502715028150291503015031150321503315034150351503615037150381503915040150411504215043150441504515046150471504815049150501505115052150531505415055150561505715058150591506015061150621506315064150651506615067150681506915070150711507215073150741507515076150771507815079150801508115082150831508415085150861508715088150891509015091150921509315094150951509615097150981509915100151011510215103151041510515106151071510815109151101511115112151131511415115151161511715118151191512015121151221512315124151251512615127151281512915130151311513215133151341513515136151371513815139151401514115142151431514415145151461514715148151491515015151151521515315154151551515615157151581515915160151611516215163151641516515166151671516815169151701517115172151731517415175151761517715178151791518015181151821518315184151851518615187151881518915190151911519215193151941519515196151971519815199152001520115202152031520415205152061520715208152091521015211152121521315214152151521615217152181521915220152211522215223152241522515226152271522815229152301523115232152331523415235152361523715238152391524015241152421524315244152451524615247152481524915250152511525215253152541525515256152571525815259152601526115262152631526415265152661526715268152691527015271152721527315274152751527615277152781527915280152811528215283152841528515286152871528815289152901529115292152931529415295152961529715298152991530015301153021530315304153051530615307153081530915310153111531215313153141531515316153171531815319153201532115322153231532415325153261532715328153291533015331153321533315334153351533615337153381533915340153411534215343153441534515346153471534815349153501535115352153531535415355153561535715358153591536015361153621536315364153651536615367153681536915370153711537215373153741537515376153771537815379153801538115382153831538415385153861538715388153891539015391153921539315394153951539615397153981539915400154011540215403154041540515406154071540815409154101541115412154131541415415154161541715418154191542015421154221542315424154251542615427154281542915430154311543215433154341543515436154371543815439154401544115442154431544415445154461544715448154491545015451154521545315454154551545615457154581545915460154611546215463154641546515466154671546815469154701547115472154731547415475154761547715478154791548015481154821548315484154851548615487154881548915490154911549215493154941549515496154971549815499155001550115502155031550415505155061550715508155091551015511155121551315514155151551615517155181551915520155211552215523155241552515526155271552815529155301553115532155331553415535155361553715538155391554015541155421554315544155451554615547155481554915550155511555215553155541555515556155571555815559155601556115562155631556415565155661556715568155691557015571155721557315574155751557615577155781557915580155811558215583155841558515586155871558815589155901559115592155931559415595155961559715598155991560015601156021560315604156051560615607156081560915610156111561215613156141561515616156171561815619156201562115622156231562415625156261562715628156291563015631156321563315634156351563615637156381563915640156411564215643156441564515646156471564815649156501565115652156531565415655156561565715658156591566015661156621566315664156651566615667156681566915670156711567215673156741567515676156771567815679156801568115682156831568415685156861568715688156891569015691156921569315694156951569615697156981569915700157011570215703157041570515706157071570815709157101571115712157131571415715157161571715718157191572015721157221572315724157251572615727157281572915730157311573215733157341573515736157371573815739157401574115742157431574415745157461574715748157491575015751157521575315754157551575615757157581575915760157611576215763157641576515766157671576815769157701577115772157731577415775157761577715778157791578015781157821578315784157851578615787157881578915790157911579215793157941579515796157971579815799158001580115802158031580415805158061580715808158091581015811158121581315814158151581615817158181581915820158211582215823158241582515826158271582815829158301583115832158331583415835158361583715838158391584015841158421584315844158451584615847158481584915850158511585215853158541585515856158571585815859158601586115862158631586415865158661586715868158691587015871158721587315874158751587615877158781587915880158811588215883158841588515886158871588815889158901589115892158931589415895158961589715898158991590015901159021590315904159051590615907159081590915910159111591215913159141591515916159171591815919159201592115922159231592415925159261592715928159291593015931159321593315934159351593615937159381593915940159411594215943159441594515946159471594815949159501595115952159531595415955159561595715958159591596015961159621596315964159651596615967159681596915970159711597215973159741597515976159771597815979159801598115982159831598415985159861598715988159891599015991159921599315994159951599615997159981599916000160011600216003160041600516006160071600816009160101601116012160131601416015160161601716018160191602016021160221602316024160251602616027160281602916030160311603216033160341603516036160371603816039160401604116042160431604416045160461604716048160491605016051160521605316054160551605616057160581605916060160611606216063160641606516066160671606816069160701607116072160731607416075160761607716078160791608016081160821608316084160851608616087160881608916090160911609216093160941609516096160971609816099161001610116102161031610416105161061610716108161091611016111161121611316114161151611616117161181611916120161211612216123161241612516126161271612816129161301613116132161331613416135161361613716138161391614016141161421614316144161451614616147161481614916150161511615216153161541615516156161571615816159161601616116162161631616416165161661616716168161691617016171161721617316174161751617616177161781617916180161811618216183161841618516186161871618816189161901619116192161931619416195161961619716198161991620016201162021620316204162051620616207162081620916210162111621216213162141621516216162171621816219162201622116222162231622416225162261622716228162291623016231162321623316234162351623616237162381623916240162411624216243162441624516246162471624816249162501625116252162531625416255162561625716258162591626016261162621626316264162651626616267162681626916270162711627216273162741627516276162771627816279162801628116282162831628416285162861628716288162891629016291162921629316294162951629616297162981629916300163011630216303163041630516306163071630816309163101631116312163131631416315163161631716318163191632016321163221632316324163251632616327163281632916330163311633216333163341633516336163371633816339163401634116342163431634416345163461634716348163491635016351163521635316354163551635616357163581635916360163611636216363163641636516366163671636816369163701637116372163731637416375163761637716378163791638016381163821638316384163851638616387163881638916390163911639216393163941639516396163971639816399164001640116402164031640416405164061640716408164091641016411164121641316414164151641616417164181641916420164211642216423164241642516426164271642816429164301643116432164331643416435164361643716438164391644016441164421644316444164451644616447164481644916450164511645216453164541645516456164571645816459164601646116462164631646416465164661646716468164691647016471164721647316474164751647616477164781647916480164811648216483164841648516486164871648816489164901649116492164931649416495164961649716498164991650016501165021650316504165051650616507165081650916510165111651216513165141651516516165171651816519165201652116522165231652416525165261652716528165291653016531165321653316534165351653616537165381653916540165411654216543165441654516546165471654816549165501655116552165531655416555165561655716558165591656016561165621656316564165651656616567165681656916570165711657216573165741657516576165771657816579165801658116582165831658416585165861658716588165891659016591165921659316594165951659616597165981659916600166011660216603166041660516606166071660816609166101661116612166131661416615166161661716618166191662016621166221662316624166251662616627166281662916630166311663216633166341663516636166371663816639166401664116642166431664416645166461664716648166491665016651166521665316654166551665616657166581665916660166611666216663166641666516666166671666816669166701667116672166731667416675166761667716678166791668016681166821668316684166851668616687166881668916690166911669216693166941669516696166971669816699167001670116702167031670416705167061670716708167091671016711167121671316714167151671616717167181671916720167211672216723167241672516726167271672816729167301673116732167331673416735167361673716738167391674016741167421674316744167451674616747167481674916750167511675216753167541675516756167571675816759167601676116762167631676416765167661676716768167691677016771167721677316774167751677616777167781677916780167811678216783167841678516786167871678816789167901679116792167931679416795167961679716798167991680016801168021680316804168051680616807168081680916810168111681216813168141681516816168171681816819168201682116822168231682416825168261682716828168291683016831168321683316834168351683616837168381683916840168411684216843168441684516846168471684816849168501685116852168531685416855168561685716858168591686016861168621686316864168651686616867168681686916870168711687216873168741687516876168771687816879168801688116882168831688416885168861688716888168891689016891168921689316894168951689616897168981689916900169011690216903169041690516906169071690816909169101691116912169131691416915169161691716918169191692016921169221692316924169251692616927169281692916930169311693216933169341693516936169371693816939169401694116942169431694416945169461694716948169491695016951169521695316954169551695616957169581695916960169611696216963169641696516966169671696816969169701697116972169731697416975169761697716978169791698016981169821698316984169851698616987169881698916990169911699216993169941699516996169971699816999170001700117002170031700417005170061700717008170091701017011170121701317014170151701617017170181701917020170211702217023170241702517026170271702817029170301703117032170331703417035170361703717038170391704017041170421704317044170451704617047170481704917050170511705217053170541705517056170571705817059170601706117062170631706417065170661706717068170691707017071170721707317074170751707617077170781707917080170811708217083170841708517086170871708817089170901709117092170931709417095170961709717098170991710017101171021710317104171051710617107171081710917110171111711217113171141711517116171171711817119171201712117122171231712417125171261712717128171291713017131171321713317134171351713617137171381713917140171411714217143171441714517146171471714817149171501715117152171531715417155171561715717158171591716017161171621716317164171651716617167171681716917170171711717217173171741717517176171771717817179171801718117182171831718417185171861718717188171891719017191171921719317194171951719617197171981719917200172011720217203172041720517206172071720817209172101721117212172131721417215172161721717218172191722017221172221722317224172251722617227172281722917230172311723217233172341723517236172371723817239172401724117242172431724417245172461724717248172491725017251172521725317254172551725617257172581725917260172611726217263172641726517266172671726817269172701727117272172731727417275172761727717278172791728017281172821728317284172851728617287172881728917290172911729217293172941729517296172971729817299173001730117302173031730417305173061730717308173091731017311173121731317314173151731617317173181731917320173211732217323173241732517326173271732817329173301733117332173331733417335173361733717338173391734017341173421734317344173451734617347173481734917350173511735217353173541735517356173571735817359173601736117362173631736417365173661736717368173691737017371173721737317374173751737617377173781737917380173811738217383173841738517386173871738817389173901739117392173931739417395173961739717398173991740017401174021740317404174051740617407174081740917410174111741217413174141741517416174171741817419174201742117422174231742417425174261742717428174291743017431174321743317434174351743617437174381743917440174411744217443174441744517446174471744817449174501745117452174531745417455174561745717458174591746017461174621746317464174651746617467174681746917470174711747217473174741747517476174771747817479174801748117482174831748417485174861748717488174891749017491174921749317494174951749617497174981749917500175011750217503175041750517506175071750817509175101751117512175131751417515175161751717518175191752017521175221752317524175251752617527175281752917530175311753217533175341753517536175371753817539175401754117542175431754417545175461754717548175491755017551175521755317554175551755617557175581755917560175611756217563175641756517566175671756817569175701757117572175731757417575175761757717578175791758017581175821758317584175851758617587175881758917590175911759217593175941759517596175971759817599176001760117602176031760417605176061760717608176091761017611176121761317614176151761617617176181761917620176211762217623176241762517626176271762817629176301763117632176331763417635176361763717638176391764017641176421764317644176451764617647176481764917650176511765217653176541765517656176571765817659176601766117662176631766417665176661766717668176691767017671176721767317674176751767617677176781767917680176811768217683176841768517686176871768817689176901769117692176931769417695176961769717698176991770017701177021770317704177051770617707177081770917710177111771217713177141771517716177171771817719177201772117722177231772417725177261772717728177291773017731177321773317734177351773617737177381773917740177411774217743177441774517746177471774817749177501775117752177531775417755177561775717758177591776017761177621776317764177651776617767177681776917770177711777217773177741777517776177771777817779177801778117782177831778417785177861778717788177891779017791177921779317794177951779617797177981779917800178011780217803178041780517806178071780817809178101781117812178131781417815178161781717818178191782017821178221782317824178251782617827178281782917830178311783217833178341783517836178371783817839178401784117842178431784417845178461784717848178491785017851178521785317854178551785617857178581785917860178611786217863178641786517866178671786817869178701787117872178731787417875178761787717878178791788017881178821788317884178851788617887178881788917890178911789217893178941789517896178971789817899179001790117902179031790417905179061790717908179091791017911179121791317914179151791617917179181791917920179211792217923179241792517926179271792817929179301793117932179331793417935179361793717938179391794017941179421794317944179451794617947179481794917950179511795217953179541795517956179571795817959179601796117962179631796417965179661796717968179691797017971179721797317974179751797617977179781797917980179811798217983179841798517986179871798817989179901799117992179931799417995179961799717998179991800018001180021800318004180051800618007180081800918010180111801218013180141801518016180171801818019180201802118022180231802418025180261802718028180291803018031180321803318034180351803618037180381803918040180411804218043180441804518046180471804818049180501805118052180531805418055180561805718058180591806018061180621806318064180651806618067180681806918070180711807218073180741807518076180771807818079180801808118082180831808418085180861808718088180891809018091180921809318094180951809618097180981809918100181011810218103181041810518106181071810818109181101811118112181131811418115181161811718118181191812018121181221812318124181251812618127181281812918130181311813218133181341813518136181371813818139181401814118142181431814418145181461814718148181491815018151181521815318154181551815618157181581815918160181611816218163181641816518166181671816818169181701817118172181731817418175181761817718178181791818018181181821818318184181851818618187181881818918190181911819218193181941819518196181971819818199182001820118202182031820418205182061820718208182091821018211182121821318214182151821618217182181821918220182211822218223182241822518226182271822818229182301823118232182331823418235182361823718238182391824018241182421824318244182451824618247182481824918250182511825218253182541825518256182571825818259182601826118262182631826418265182661826718268182691827018271182721827318274182751827618277182781827918280182811828218283182841828518286182871828818289182901829118292182931829418295182961829718298182991830018301183021830318304183051830618307183081830918310183111831218313183141831518316183171831818319183201832118322183231832418325183261832718328183291833018331183321833318334183351833618337183381833918340183411834218343183441834518346183471834818349183501835118352183531835418355183561835718358183591836018361183621836318364183651836618367183681836918370183711837218373183741837518376183771837818379183801838118382183831838418385183861838718388183891839018391183921839318394183951839618397183981839918400184011840218403184041840518406184071840818409184101841118412184131841418415184161841718418184191842018421184221842318424184251842618427184281842918430184311843218433184341843518436184371843818439184401844118442184431844418445184461844718448184491845018451184521845318454184551845618457184581845918460184611846218463184641846518466184671846818469184701847118472184731847418475184761847718478184791848018481184821848318484184851848618487184881848918490184911849218493184941849518496184971849818499185001850118502185031850418505185061850718508185091851018511185121851318514185151851618517185181851918520185211852218523185241852518526185271852818529185301853118532185331853418535185361853718538185391854018541185421854318544185451854618547185481854918550185511855218553185541855518556185571855818559185601856118562185631856418565185661856718568185691857018571185721857318574185751857618577185781857918580185811858218583185841858518586185871858818589185901859118592185931859418595185961859718598185991860018601186021860318604186051860618607186081860918610186111861218613186141861518616186171861818619186201862118622186231862418625186261862718628186291863018631186321863318634186351863618637186381863918640186411864218643186441864518646186471864818649186501865118652186531865418655186561865718658186591866018661186621866318664186651866618667186681866918670186711867218673186741867518676186771867818679186801868118682186831868418685186861868718688186891869018691186921869318694186951869618697186981869918700187011870218703187041870518706187071870818709187101871118712187131871418715187161871718718187191872018721187221872318724187251872618727187281872918730187311873218733187341873518736187371873818739187401874118742187431874418745187461874718748187491875018751187521875318754187551875618757187581875918760187611876218763187641876518766187671876818769187701877118772187731877418775187761877718778187791878018781187821878318784187851878618787187881878918790187911879218793187941879518796187971879818799188001880118802188031880418805188061880718808188091881018811188121881318814188151881618817188181881918820188211882218823188241882518826188271882818829188301883118832188331883418835188361883718838188391884018841188421884318844188451884618847188481884918850188511885218853188541885518856188571885818859188601886118862188631886418865188661886718868188691887018871188721887318874188751887618877188781887918880188811888218883188841888518886188871888818889188901889118892188931889418895188961889718898188991890018901189021890318904189051890618907189081890918910189111891218913189141891518916189171891818919189201892118922189231892418925189261892718928189291893018931189321893318934189351893618937189381893918940189411894218943189441894518946189471894818949189501895118952189531895418955189561895718958189591896018961189621896318964189651896618967189681896918970189711897218973189741897518976189771897818979189801898118982189831898418985189861898718988189891899018991189921899318994189951899618997189981899919000190011900219003190041900519006190071900819009190101901119012190131901419015190161901719018190191902019021190221902319024190251902619027190281902919030190311903219033190341903519036190371903819039190401904119042190431904419045190461904719048190491905019051190521905319054190551905619057190581905919060190611906219063190641906519066190671906819069190701907119072190731907419075190761907719078190791908019081190821908319084190851908619087190881908919090190911909219093190941909519096190971909819099191001910119102191031910419105191061910719108191091911019111191121911319114191151911619117191181911919120191211912219123191241912519126191271912819129191301913119132191331913419135191361913719138191391914019141191421914319144191451914619147191481914919150191511915219153191541915519156191571915819159191601916119162191631916419165191661916719168191691917019171191721917319174191751917619177191781917919180191811918219183191841918519186191871918819189191901919119192191931919419195191961919719198191991920019201192021920319204192051920619207192081920919210192111921219213192141921519216192171921819219192201922119222192231922419225192261922719228192291923019231192321923319234192351923619237192381923919240192411924219243192441924519246192471924819249192501925119252192531925419255192561925719258192591926019261192621926319264192651926619267192681926919270192711927219273192741927519276192771927819279192801928119282192831928419285192861928719288192891929019291192921929319294192951929619297192981929919300193011930219303193041930519306193071930819309193101931119312193131931419315193161931719318193191932019321193221932319324193251932619327193281932919330193311933219333193341933519336193371933819339193401934119342193431934419345193461934719348193491935019351193521935319354193551935619357193581935919360193611936219363193641936519366193671936819369193701937119372193731937419375193761937719378193791938019381193821938319384193851938619387193881938919390193911939219393193941939519396193971939819399194001940119402194031940419405194061940719408194091941019411194121941319414194151941619417194181941919420194211942219423194241942519426194271942819429194301943119432194331943419435194361943719438194391944019441194421944319444194451944619447194481944919450194511945219453194541945519456194571945819459194601946119462194631946419465194661946719468194691947019471194721947319474194751947619477194781947919480194811948219483194841948519486194871948819489194901949119492194931949419495194961949719498194991950019501195021950319504195051950619507195081950919510195111951219513195141951519516195171951819519195201952119522195231952419525195261952719528195291953019531195321953319534195351953619537195381953919540195411954219543195441954519546195471954819549195501955119552195531955419555195561955719558195591956019561195621956319564195651956619567195681956919570195711957219573195741957519576195771957819579195801958119582195831958419585195861958719588195891959019591195921959319594195951959619597195981959919600196011960219603196041960519606196071960819609196101961119612196131961419615196161961719618196191962019621196221962319624196251962619627196281962919630196311963219633196341963519636196371963819639196401964119642196431964419645196461964719648196491965019651196521965319654196551965619657196581965919660196611966219663196641966519666196671966819669196701967119672196731967419675196761967719678196791968019681196821968319684196851968619687196881968919690196911969219693196941969519696196971969819699197001970119702197031970419705197061970719708197091971019711197121971319714197151971619717197181971919720197211972219723197241972519726197271972819729197301973119732197331973419735197361973719738197391974019741197421974319744197451974619747197481974919750197511975219753197541975519756197571975819759197601976119762197631976419765197661976719768197691977019771197721977319774197751977619777197781977919780197811978219783197841978519786197871978819789197901979119792197931979419795197961979719798197991980019801198021980319804198051980619807198081980919810198111981219813198141981519816198171981819819198201982119822198231982419825198261982719828198291983019831198321983319834198351983619837198381983919840198411984219843198441984519846198471984819849198501985119852198531985419855198561985719858198591986019861198621986319864198651986619867198681986919870198711987219873198741987519876198771987819879198801988119882198831988419885198861988719888198891989019891198921989319894198951989619897198981989919900199011990219903199041990519906199071990819909199101991119912199131991419915199161991719918199191992019921199221992319924199251992619927199281992919930199311993219933199341993519936199371993819939199401994119942199431994419945199461994719948199491995019951199521995319954199551995619957199581995919960199611996219963199641996519966199671996819969199701997119972199731997419975199761997719978199791998019981199821998319984199851998619987199881998919990199911999219993199941999519996199971999819999200002000120002200032000420005200062000720008200092001020011200122001320014200152001620017200182001920020200212002220023200242002520026200272002820029200302003120032200332003420035200362003720038200392004020041200422004320044200452004620047200482004920050200512005220053200542005520056200572005820059200602006120062200632006420065200662006720068200692007020071200722007320074200752007620077200782007920080200812008220083200842008520086200872008820089200902009120092200932009420095200962009720098200992010020101201022010320104201052010620107201082010920110201112011220113201142011520116201172011820119201202012120122201232012420125201262012720128201292013020131201322013320134201352013620137201382013920140201412014220143201442014520146201472014820149201502015120152201532015420155201562015720158201592016020161201622016320164201652016620167201682016920170201712017220173201742017520176201772017820179201802018120182201832018420185201862018720188201892019020191201922019320194201952019620197201982019920200202012020220203202042020520206202072020820209202102021120212202132021420215202162021720218202192022020221202222022320224202252022620227202282022920230202312023220233202342023520236202372023820239202402024120242202432024420245202462024720248202492025020251202522025320254202552025620257202582025920260202612026220263202642026520266202672026820269202702027120272202732027420275202762027720278202792028020281202822028320284202852028620287202882028920290202912029220293202942029520296202972029820299203002030120302203032030420305203062030720308203092031020311203122031320314203152031620317203182031920320203212032220323203242032520326203272032820329203302033120332203332033420335203362033720338203392034020341203422034320344203452034620347203482034920350203512035220353203542035520356203572035820359203602036120362203632036420365203662036720368203692037020371203722037320374203752037620377203782037920380203812038220383203842038520386203872038820389203902039120392203932039420395203962039720398203992040020401204022040320404204052040620407204082040920410204112041220413204142041520416204172041820419204202042120422204232042420425204262042720428204292043020431204322043320434204352043620437204382043920440204412044220443204442044520446204472044820449204502045120452204532045420455204562045720458204592046020461204622046320464204652046620467204682046920470204712047220473204742047520476204772047820479204802048120482204832048420485204862048720488204892049020491204922049320494204952049620497204982049920500205012050220503205042050520506205072050820509205102051120512205132051420515205162051720518205192052020521205222052320524205252052620527205282052920530205312053220533205342053520536205372053820539205402054120542205432054420545205462054720548205492055020551205522055320554205552055620557205582055920560205612056220563205642056520566205672056820569205702057120572205732057420575205762057720578205792058020581205822058320584205852058620587205882058920590205912059220593205942059520596205972059820599206002060120602206032060420605206062060720608206092061020611206122061320614206152061620617206182061920620206212062220623206242062520626206272062820629206302063120632206332063420635206362063720638206392064020641206422064320644206452064620647206482064920650206512065220653206542065520656206572065820659206602066120662206632066420665206662066720668206692067020671206722067320674206752067620677206782067920680206812068220683206842068520686206872068820689206902069120692206932069420695206962069720698206992070020701207022070320704207052070620707207082070920710207112071220713207142071520716207172071820719207202072120722207232072420725207262072720728207292073020731207322073320734207352073620737207382073920740207412074220743207442074520746207472074820749207502075120752207532075420755207562075720758207592076020761207622076320764207652076620767207682076920770207712077220773207742077520776207772077820779207802078120782207832078420785207862078720788207892079020791207922079320794207952079620797207982079920800208012080220803208042080520806208072080820809208102081120812208132081420815208162081720818208192082020821208222082320824208252082620827208282082920830208312083220833208342083520836208372083820839208402084120842208432084420845208462084720848208492085020851208522085320854208552085620857208582085920860208612086220863208642086520866208672086820869208702087120872208732087420875208762087720878208792088020881208822088320884208852088620887208882088920890208912089220893208942089520896208972089820899209002090120902209032090420905209062090720908209092091020911209122091320914209152091620917209182091920920209212092220923209242092520926209272092820929209302093120932209332093420935209362093720938209392094020941209422094320944209452094620947209482094920950209512095220953209542095520956209572095820959209602096120962209632096420965209662096720968209692097020971209722097320974209752097620977209782097920980209812098220983209842098520986209872098820989209902099120992209932099420995209962099720998209992100021001210022100321004210052100621007210082100921010210112101221013210142101521016210172101821019210202102121022210232102421025210262102721028210292103021031210322103321034210352103621037210382103921040210412104221043210442104521046210472104821049210502105121052210532105421055210562105721058210592106021061210622106321064210652106621067210682106921070210712107221073210742107521076210772107821079210802108121082210832108421085210862108721088210892109021091210922109321094210952109621097210982109921100211012110221103211042110521106211072110821109211102111121112211132111421115211162111721118211192112021121211222112321124211252112621127211282112921130211312113221133211342113521136211372113821139211402114121142211432114421145211462114721148211492115021151211522115321154211552115621157211582115921160211612116221163211642116521166211672116821169211702117121172211732117421175211762117721178211792118021181211822118321184211852118621187211882118921190211912119221193211942119521196211972119821199212002120121202212032120421205212062120721208212092121021211212122121321214212152121621217212182121921220212212122221223212242122521226212272122821229212302123121232212332123421235212362123721238212392124021241212422124321244212452124621247212482124921250212512125221253212542125521256212572125821259212602126121262212632126421265212662126721268212692127021271212722127321274212752127621277212782127921280212812128221283212842128521286212872128821289212902129121292212932129421295212962129721298212992130021301213022130321304213052130621307213082130921310213112131221313213142131521316213172131821319213202132121322213232132421325213262132721328213292133021331213322133321334213352133621337213382133921340213412134221343213442134521346213472134821349213502135121352213532135421355213562135721358213592136021361213622136321364213652136621367213682136921370213712137221373213742137521376213772137821379213802138121382213832138421385213862138721388213892139021391213922139321394213952139621397213982139921400214012140221403214042140521406214072140821409214102141121412214132141421415214162141721418214192142021421214222142321424214252142621427214282142921430214312143221433214342143521436214372143821439214402144121442214432144421445214462144721448214492145021451214522145321454214552145621457214582145921460214612146221463214642146521466214672146821469214702147121472214732147421475214762147721478214792148021481214822148321484214852148621487214882148921490214912149221493214942149521496214972149821499215002150121502215032150421505215062150721508215092151021511215122151321514215152151621517215182151921520215212152221523215242152521526215272152821529215302153121532215332153421535215362153721538215392154021541215422154321544215452154621547215482154921550215512155221553215542155521556215572155821559215602156121562215632156421565215662156721568215692157021571215722157321574215752157621577215782157921580215812158221583215842158521586215872158821589215902159121592215932159421595215962159721598215992160021601216022160321604216052160621607216082160921610216112161221613216142161521616216172161821619216202162121622216232162421625216262162721628216292163021631216322163321634216352163621637216382163921640216412164221643216442164521646216472164821649216502165121652216532165421655216562165721658216592166021661216622166321664216652166621667216682166921670216712167221673216742167521676216772167821679216802168121682216832168421685216862168721688216892169021691216922169321694216952169621697216982169921700217012170221703217042170521706217072170821709217102171121712217132171421715217162171721718217192172021721217222172321724217252172621727217282172921730217312173221733217342173521736217372173821739217402174121742217432174421745217462174721748217492175021751217522175321754217552175621757217582175921760217612176221763217642176521766217672176821769217702177121772217732177421775217762177721778217792178021781217822178321784217852178621787217882178921790217912179221793217942179521796217972179821799218002180121802218032180421805218062180721808218092181021811218122181321814218152181621817218182181921820218212182221823218242182521826218272182821829218302183121832218332183421835218362183721838218392184021841218422184321844218452184621847218482184921850218512185221853218542185521856218572185821859218602186121862218632186421865218662186721868218692187021871218722187321874218752187621877218782187921880218812188221883218842188521886218872188821889218902189121892218932189421895218962189721898218992190021901219022190321904219052190621907219082190921910219112191221913219142191521916219172191821919219202192121922219232192421925219262192721928219292193021931219322193321934219352193621937219382193921940219412194221943219442194521946219472194821949219502195121952219532195421955219562195721958219592196021961219622196321964219652196621967219682196921970219712197221973219742197521976219772197821979219802198121982219832198421985219862198721988219892199021991219922199321994219952199621997219982199922000220012200222003220042200522006220072200822009220102201122012220132201422015220162201722018220192202022021220222202322024220252202622027220282202922030220312203222033220342203522036220372203822039220402204122042220432204422045220462204722048220492205022051220522205322054220552205622057220582205922060220612206222063220642206522066220672206822069220702207122072220732207422075220762207722078220792208022081220822208322084220852208622087220882208922090220912209222093220942209522096220972209822099221002210122102221032210422105221062210722108221092211022111221122211322114221152211622117221182211922120221212212222123221242212522126221272212822129221302213122132221332213422135221362213722138221392214022141221422214322144221452214622147221482214922150221512215222153221542215522156221572215822159221602216122162221632216422165221662216722168221692217022171221722217322174221752217622177221782217922180221812218222183221842218522186221872218822189221902219122192221932219422195221962219722198221992220022201222022220322204222052220622207222082220922210222112221222213222142221522216222172221822219222202222122222222232222422225222262222722228222292223022231222322223322234222352223622237222382223922240222412224222243222442224522246222472224822249222502225122252222532225422255222562225722258222592226022261222622226322264222652226622267222682226922270222712227222273222742227522276222772227822279222802228122282222832228422285222862228722288222892229022291222922229322294222952229622297222982229922300223012230222303223042230522306223072230822309223102231122312223132231422315223162231722318223192232022321223222232322324223252232622327223282232922330223312233222333223342233522336223372233822339223402234122342223432234422345223462234722348223492235022351223522235322354223552235622357223582235922360223612236222363223642236522366223672236822369223702237122372223732237422375223762237722378223792238022381223822238322384223852238622387223882238922390223912239222393223942239522396223972239822399224002240122402224032240422405224062240722408224092241022411224122241322414224152241622417224182241922420224212242222423224242242522426224272242822429224302243122432224332243422435224362243722438224392244022441224422244322444224452244622447224482244922450224512245222453224542245522456224572245822459224602246122462224632246422465224662246722468224692247022471224722247322474224752247622477224782247922480224812248222483224842248522486224872248822489224902249122492224932249422495224962249722498224992250022501225022250322504225052250622507225082250922510225112251222513225142251522516225172251822519225202252122522225232252422525225262252722528225292253022531225322253322534225352253622537225382253922540225412254222543225442254522546225472254822549225502255122552225532255422555225562255722558225592256022561225622256322564225652256622567225682256922570225712257222573225742257522576225772257822579225802258122582225832258422585225862258722588225892259022591225922259322594225952259622597225982259922600226012260222603226042260522606226072260822609226102261122612226132261422615226162261722618226192262022621226222262322624226252262622627226282262922630226312263222633226342263522636226372263822639226402264122642226432264422645226462264722648226492265022651226522265322654226552265622657226582265922660226612266222663226642266522666226672266822669226702267122672226732267422675226762267722678226792268022681226822268322684226852268622687226882268922690226912269222693226942269522696226972269822699227002270122702227032270422705227062270722708227092271022711227122271322714227152271622717227182271922720227212272222723227242272522726227272272822729227302273122732227332273422735227362273722738227392274022741227422274322744227452274622747227482274922750227512275222753227542275522756227572275822759227602276122762227632276422765227662276722768227692277022771227722277322774227752277622777227782277922780227812278222783227842278522786227872278822789227902279122792227932279422795227962279722798227992280022801228022280322804228052280622807228082280922810228112281222813228142281522816228172281822819228202282122822228232282422825228262282722828228292283022831228322283322834228352283622837228382283922840228412284222843228442284522846228472284822849228502285122852228532285422855228562285722858228592286022861228622286322864228652286622867228682286922870228712287222873228742287522876228772287822879228802288122882228832288422885228862288722888228892289022891228922289322894228952289622897228982289922900229012290222903229042290522906229072290822909229102291122912229132291422915229162291722918229192292022921229222292322924229252292622927229282292922930229312293222933229342293522936229372293822939229402294122942229432294422945229462294722948229492295022951229522295322954229552295622957229582295922960229612296222963229642296522966229672296822969229702297122972229732297422975229762297722978229792298022981229822298322984229852298622987229882298922990229912299222993229942299522996229972299822999230002300123002230032300423005230062300723008230092301023011230122301323014230152301623017230182301923020230212302223023230242302523026230272302823029230302303123032230332303423035230362303723038230392304023041230422304323044230452304623047230482304923050230512305223053230542305523056230572305823059230602306123062230632306423065230662306723068230692307023071230722307323074230752307623077230782307923080230812308223083230842308523086230872308823089230902309123092230932309423095230962309723098230992310023101231022310323104231052310623107231082310923110231112311223113231142311523116231172311823119231202312123122231232312423125231262312723128231292313023131231322313323134231352313623137231382313923140231412314223143231442314523146231472314823149231502315123152231532315423155231562315723158231592316023161231622316323164231652316623167231682316923170231712317223173231742317523176231772317823179231802318123182231832318423185231862318723188231892319023191231922319323194231952319623197231982319923200232012320223203232042320523206232072320823209232102321123212232132321423215232162321723218232192322023221232222322323224232252322623227232282322923230232312323223233232342323523236232372323823239232402324123242232432324423245232462324723248232492325023251232522325323254232552325623257232582325923260232612326223263232642326523266232672326823269232702327123272232732327423275232762327723278232792328023281232822328323284232852328623287232882328923290232912329223293232942329523296232972329823299233002330123302233032330423305233062330723308233092331023311233122331323314233152331623317233182331923320233212332223323233242332523326233272332823329233302333123332233332333423335233362333723338233392334023341233422334323344233452334623347233482334923350233512335223353233542335523356233572335823359233602336123362233632336423365233662336723368233692337023371233722337323374233752337623377233782337923380233812338223383233842338523386233872338823389233902339123392233932339423395233962339723398233992340023401234022340323404234052340623407234082340923410234112341223413234142341523416234172341823419234202342123422234232342423425234262342723428234292343023431234322343323434234352343623437234382343923440234412344223443234442344523446234472344823449234502345123452234532345423455234562345723458234592346023461234622346323464234652346623467234682346923470234712347223473234742347523476234772347823479234802348123482234832348423485234862348723488234892349023491234922349323494234952349623497234982349923500235012350223503235042350523506235072350823509235102351123512235132351423515235162351723518235192352023521235222352323524235252352623527235282352923530235312353223533235342353523536235372353823539235402354123542235432354423545235462354723548235492355023551235522355323554235552355623557235582355923560235612356223563235642356523566235672356823569235702357123572235732357423575235762357723578235792358023581235822358323584235852358623587235882358923590235912359223593235942359523596235972359823599236002360123602236032360423605236062360723608236092361023611236122361323614236152361623617236182361923620236212362223623236242362523626236272362823629236302363123632236332363423635236362363723638236392364023641236422364323644236452364623647236482364923650236512365223653236542365523656236572365823659236602366123662236632366423665236662366723668236692367023671236722367323674236752367623677236782367923680236812368223683236842368523686236872368823689236902369123692236932369423695236962369723698236992370023701237022370323704237052370623707237082370923710237112371223713237142371523716237172371823719237202372123722237232372423725237262372723728237292373023731237322373323734237352373623737237382373923740237412374223743237442374523746237472374823749237502375123752237532375423755237562375723758237592376023761237622376323764237652376623767237682376923770237712377223773237742377523776237772377823779237802378123782237832378423785237862378723788237892379023791237922379323794237952379623797237982379923800238012380223803238042380523806238072380823809238102381123812238132381423815238162381723818238192382023821238222382323824238252382623827238282382923830238312383223833238342383523836238372383823839238402384123842238432384423845238462384723848238492385023851238522385323854238552385623857238582385923860238612386223863238642386523866238672386823869238702387123872238732387423875238762387723878238792388023881238822388323884238852388623887238882388923890238912389223893238942389523896238972389823899239002390123902239032390423905239062390723908239092391023911239122391323914239152391623917239182391923920239212392223923239242392523926239272392823929239302393123932239332393423935239362393723938239392394023941239422394323944239452394623947239482394923950239512395223953239542395523956239572395823959239602396123962239632396423965239662396723968239692397023971239722397323974239752397623977239782397923980239812398223983239842398523986239872398823989239902399123992239932399423995239962399723998239992400024001240022400324004240052400624007240082400924010240112401224013240142401524016240172401824019240202402124022240232402424025240262402724028240292403024031240322403324034240352403624037240382403924040240412404224043240442404524046240472404824049240502405124052240532405424055240562405724058240592406024061240622406324064240652406624067240682406924070240712407224073240742407524076240772407824079240802408124082240832408424085240862408724088240892409024091240922409324094240952409624097240982409924100241012410224103241042410524106241072410824109241102411124112241132411424115241162411724118241192412024121241222412324124241252412624127241282412924130241312413224133241342413524136241372413824139241402414124142241432414424145241462414724148241492415024151241522415324154241552415624157241582415924160241612416224163241642416524166241672416824169241702417124172241732417424175241762417724178241792418024181241822418324184241852418624187241882418924190241912419224193241942419524196241972419824199242002420124202242032420424205242062420724208242092421024211242122421324214242152421624217242182421924220242212422224223242242422524226242272422824229242302423124232242332423424235242362423724238242392424024241242422424324244242452424624247242482424924250242512425224253242542425524256242572425824259242602426124262242632426424265242662426724268242692427024271242722427324274242752427624277242782427924280242812428224283242842428524286242872428824289242902429124292242932429424295242962429724298242992430024301243022430324304243052430624307243082430924310243112431224313243142431524316243172431824319243202432124322243232432424325243262432724328243292433024331243322433324334243352433624337243382433924340243412434224343243442434524346243472434824349243502435124352243532435424355243562435724358243592436024361243622436324364243652436624367243682436924370243712437224373243742437524376243772437824379243802438124382243832438424385243862438724388243892439024391243922439324394243952439624397243982439924400244012440224403244042440524406244072440824409244102441124412244132441424415244162441724418244192442024421244222442324424244252442624427244282442924430244312443224433244342443524436244372443824439244402444124442244432444424445244462444724448244492445024451244522445324454244552445624457244582445924460244612446224463244642446524466244672446824469244702447124472244732447424475244762447724478244792448024481244822448324484244852448624487244882448924490244912449224493244942449524496244972449824499245002450124502245032450424505245062450724508245092451024511245122451324514245152451624517245182451924520245212452224523245242452524526245272452824529245302453124532245332453424535245362453724538245392454024541245422454324544245452454624547245482454924550245512455224553245542455524556245572455824559245602456124562245632456424565245662456724568245692457024571245722457324574245752457624577245782457924580245812458224583245842458524586245872458824589245902459124592245932459424595245962459724598245992460024601246022460324604246052460624607246082460924610246112461224613246142461524616246172461824619246202462124622246232462424625246262462724628246292463024631246322463324634246352463624637246382463924640246412464224643246442464524646246472464824649246502465124652246532465424655246562465724658246592466024661246622466324664246652466624667246682466924670246712467224673246742467524676246772467824679246802468124682246832468424685246862468724688246892469024691246922469324694246952469624697246982469924700247012470224703247042470524706247072470824709247102471124712247132471424715247162471724718247192472024721247222472324724247252472624727247282472924730247312473224733247342473524736247372473824739247402474124742247432474424745247462474724748247492475024751247522475324754247552475624757247582475924760247612476224763247642476524766247672476824769247702477124772247732477424775247762477724778247792478024781247822478324784247852478624787247882478924790247912479224793247942479524796247972479824799248002480124802248032480424805248062480724808248092481024811248122481324814248152481624817248182481924820248212482224823248242482524826248272482824829248302483124832248332483424835248362483724838248392484024841248422484324844248452484624847248482484924850248512485224853248542485524856248572485824859248602486124862248632486424865248662486724868248692487024871248722487324874248752487624877248782487924880248812488224883248842488524886248872488824889248902489124892248932489424895248962489724898248992490024901249022490324904249052490624907249082490924910249112491224913249142491524916249172491824919249202492124922249232492424925249262492724928249292493024931249322493324934249352493624937249382493924940249412494224943249442494524946249472494824949249502495124952249532495424955249562495724958249592496024961249622496324964249652496624967249682496924970249712497224973249742497524976249772497824979249802498124982249832498424985249862498724988249892499024991249922499324994249952499624997249982499925000250012500225003250042500525006250072500825009250102501125012250132501425015250162501725018250192502025021250222502325024250252502625027250282502925030250312503225033250342503525036250372503825039250402504125042250432504425045250462504725048250492505025051250522505325054250552505625057250582505925060250612506225063250642506525066250672506825069250702507125072250732507425075250762507725078250792508025081250822508325084250852508625087250882508925090250912509225093250942509525096250972509825099251002510125102251032510425105251062510725108251092511025111251122511325114251152511625117251182511925120251212512225123251242512525126251272512825129251302513125132251332513425135251362513725138251392514025141251422514325144251452514625147251482514925150251512515225153251542515525156251572515825159251602516125162251632516425165251662516725168251692517025171251722517325174251752517625177251782517925180251812518225183251842518525186251872518825189251902519125192251932519425195251962519725198251992520025201252022520325204252052520625207252082520925210252112521225213252142521525216252172521825219252202522125222252232522425225252262522725228252292523025231252322523325234252352523625237252382523925240252412524225243252442524525246252472524825249252502525125252252532525425255252562525725258252592526025261252622526325264252652526625267252682526925270252712527225273252742527525276252772527825279252802528125282252832528425285252862528725288252892529025291252922529325294252952529625297252982529925300253012530225303253042530525306253072530825309253102531125312253132531425315253162531725318253192532025321253222532325324253252532625327253282532925330253312533225333253342533525336253372533825339253402534125342253432534425345253462534725348253492535025351253522535325354253552535625357253582535925360253612536225363253642536525366253672536825369253702537125372253732537425375253762537725378253792538025381253822538325384253852538625387253882538925390253912539225393253942539525396253972539825399254002540125402254032540425405254062540725408254092541025411254122541325414254152541625417254182541925420254212542225423254242542525426254272542825429254302543125432254332543425435254362543725438254392544025441254422544325444254452544625447254482544925450254512545225453254542545525456254572545825459254602546125462254632546425465254662546725468254692547025471254722547325474254752547625477254782547925480254812548225483254842548525486254872548825489254902549125492254932549425495254962549725498254992550025501255022550325504255052550625507255082550925510255112551225513255142551525516255172551825519255202552125522255232552425525255262552725528255292553025531255322553325534255352553625537255382553925540255412554225543255442554525546255472554825549255502555125552255532555425555255562555725558255592556025561255622556325564255652556625567255682556925570255712557225573255742557525576255772557825579255802558125582255832558425585255862558725588255892559025591255922559325594255952559625597255982559925600256012560225603256042560525606256072560825609256102561125612256132561425615256162561725618256192562025621256222562325624256252562625627256282562925630256312563225633256342563525636256372563825639256402564125642256432564425645256462564725648256492565025651256522565325654256552565625657256582565925660256612566225663256642566525666256672566825669256702567125672256732567425675256762567725678256792568025681256822568325684256852568625687256882568925690256912569225693256942569525696256972569825699257002570125702257032570425705257062570725708257092571025711257122571325714257152571625717257182571925720257212572225723257242572525726257272572825729257302573125732257332573425735257362573725738257392574025741257422574325744257452574625747257482574925750257512575225753257542575525756257572575825759257602576125762257632576425765257662576725768257692577025771257722577325774257752577625777257782577925780257812578225783257842578525786257872578825789257902579125792257932579425795257962579725798257992580025801258022580325804258052580625807258082580925810258112581225813258142581525816258172581825819258202582125822258232582425825258262582725828258292583025831258322583325834258352583625837258382583925840258412584225843258442584525846258472584825849258502585125852258532585425855258562585725858258592586025861258622586325864258652586625867258682586925870258712587225873258742587525876258772587825879258802588125882258832588425885258862588725888258892589025891258922589325894258952589625897258982589925900259012590225903259042590525906259072590825909259102591125912259132591425915259162591725918259192592025921259222592325924259252592625927259282592925930259312593225933259342593525936259372593825939259402594125942259432594425945259462594725948259492595025951259522595325954259552595625957259582595925960259612596225963259642596525966259672596825969259702597125972259732597425975259762597725978259792598025981259822598325984259852598625987259882598925990259912599225993259942599525996259972599825999260002600126002260032600426005260062600726008260092601026011260122601326014260152601626017260182601926020260212602226023260242602526026260272602826029260302603126032260332603426035260362603726038260392604026041260422604326044260452604626047260482604926050260512605226053260542605526056260572605826059260602606126062260632606426065260662606726068260692607026071260722607326074260752607626077260782607926080260812608226083260842608526086260872608826089260902609126092260932609426095260962609726098260992610026101261022610326104261052610626107261082610926110261112611226113261142611526116261172611826119261202612126122261232612426125261262612726128261292613026131261322613326134261352613626137261382613926140261412614226143261442614526146261472614826149261502615126152261532615426155261562615726158261592616026161261622616326164261652616626167261682616926170261712617226173261742617526176261772617826179261802618126182261832618426185261862618726188261892619026191261922619326194261952619626197261982619926200262012620226203262042620526206262072620826209262102621126212262132621426215262162621726218262192622026221262222622326224262252622626227262282622926230262312623226233262342623526236262372623826239262402624126242262432624426245262462624726248262492625026251262522625326254262552625626257262582625926260262612626226263262642626526266262672626826269262702627126272262732627426275262762627726278262792628026281262822628326284262852628626287262882628926290262912629226293262942629526296262972629826299263002630126302263032630426305263062630726308263092631026311263122631326314263152631626317263182631926320263212632226323263242632526326263272632826329263302633126332263332633426335263362633726338263392634026341263422634326344263452634626347263482634926350263512635226353263542635526356263572635826359263602636126362263632636426365263662636726368263692637026371263722637326374263752637626377263782637926380263812638226383263842638526386263872638826389263902639126392263932639426395263962639726398263992640026401264022640326404264052640626407264082640926410264112641226413264142641526416264172641826419264202642126422264232642426425264262642726428264292643026431264322643326434264352643626437264382643926440264412644226443264442644526446264472644826449264502645126452264532645426455264562645726458264592646026461264622646326464264652646626467264682646926470264712647226473264742647526476264772647826479264802648126482264832648426485264862648726488264892649026491264922649326494264952649626497264982649926500265012650226503265042650526506265072650826509265102651126512265132651426515265162651726518265192652026521265222652326524265252652626527265282652926530265312653226533265342653526536265372653826539265402654126542265432654426545265462654726548265492655026551265522655326554265552655626557265582655926560265612656226563265642656526566265672656826569265702657126572265732657426575265762657726578265792658026581265822658326584265852658626587265882658926590265912659226593265942659526596265972659826599266002660126602266032660426605266062660726608266092661026611266122661326614266152661626617266182661926620266212662226623266242662526626266272662826629266302663126632266332663426635266362663726638266392664026641266422664326644266452664626647266482664926650266512665226653266542665526656266572665826659266602666126662266632666426665266662666726668266692667026671266722667326674266752667626677266782667926680266812668226683266842668526686266872668826689266902669126692266932669426695266962669726698266992670026701267022670326704267052670626707267082670926710267112671226713267142671526716267172671826719267202672126722267232672426725267262672726728267292673026731267322673326734267352673626737267382673926740267412674226743267442674526746267472674826749267502675126752267532675426755267562675726758267592676026761267622676326764267652676626767267682676926770267712677226773267742677526776267772677826779267802678126782267832678426785267862678726788267892679026791267922679326794267952679626797267982679926800268012680226803268042680526806268072680826809268102681126812268132681426815268162681726818268192682026821268222682326824268252682626827268282682926830268312683226833268342683526836268372683826839 |
- // Version: v1.0.0-rc.1
- // Last commit: 8b061b4 (2013-02-15 12:10:22 -0800)
- (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;
- }
- }
- Ember.ENV = 'undefined' === typeof ENV ? {} : 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 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 && Ember.TESTING_DEPRECATION) { return; }
- if (arguments.length === 1) { test = false; }
- if (test) { return; }
- if (Ember && Ember.ENV.RAISE_ON_DEPRECATION) { throw new 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);
- };
- /**
- Display a deprecation warning with the provided message and a stack trace
- (Chrome and Firefox only) when the wrapped method is called.
- Ember build tools will not remove calls to `Ember.deprecateFunc()`, though
- no warnings will be shown in production.
- @method deprecateFunc
- @param {String} message A description of the deprecation.
- @param {Function} func The function to be deprecated.
- */
- Ember.deprecateFunc = function(message, func) {
- return function() {
- Ember.deprecate(message);
- return func.apply(this, arguments);
- };
- };
- })();
- // Version: v1.0.0-rc.1
- // Last commit: 8b061b4 (2013-02-15 12:10:22 -0800)
- (function() {
- var define, requireModule;
- (function() {
- var registry = {}, seen = {};
- define = function(name, deps, callback) {
- registry[name] = { deps: deps, callback: callback };
- };
- requireModule = function(name) {
- if (seen[name]) { return seen[name]; }
- seen[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(deps[i]));
- }
- }
- var value = callback.apply(this, reified);
- return seen[name] = exports || value;
- };
- })();
- (function() {
- /*globals Em:true ENV */
- /**
- @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.0.0-rc.1
- */
- 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.0.0-rc.1'
- @final
- */
- Ember.VERSION = '1.0.0-rc.1';
- /**
- Standard environmental variables. You can define these in a global `ENV`
- variable before loading Ember to control various configuration
- settings.
- @property ENV
- @type Hash
- */
- Ember.ENV = Ember.ENV || ('undefined' === typeof ENV ? {} : ENV);
- Ember.config = Ember.config || {};
- // ..........................................................
- // 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;
- /**
- Empty function. Useful for some operations.
- @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;
- // ..........................................................
- // LOGGER
- //
- function consoleMethod(name) {
- if (imports.console && imports.console[name]) {
- // Older IE doesn't support apply, but Chrome needs it
- if (imports.console[name].apply) {
- return function() {
- imports.console[name].apply(imports.console, arguments);
- };
- } else {
- return function() {
- var message = Array.prototype.join.call(arguments, ', ');
- imports.console[name](message);
- };
- }
- }
- }
- /**
- Inside Ember-Metal, simply uses the methods from `imports.console`.
- Override this to provide more robust logging functionality.
- @class Logger
- @namespace Ember
- */
- Ember.Logger = {
- log: consoleMethod('log') || Ember.K,
- warn: consoleMethod('warn') || Ember.K,
- error: consoleMethod('error') || Ember.K,
- info: consoleMethod('info') || Ember.K,
- debug: consoleMethod('debug') || consoleMethod('info') || Ember.K
- };
- // ..........................................................
- // 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.
- @event onerror
- @for Ember
- @param {Exception} error the error object
- */
- Ember.onerror = null;
- /**
- @private
- Wrap code block in a try/catch if {{#crossLink "Ember/onerror"}}{{/crossLink}} is set.
- @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.apply(context || this);
- } catch (error) {
- Ember.onerror(error);
- }
- } else {
- return func.apply(context || this);
- }
- };
- Ember.merge = function(original, updates) {
- for (var prop in updates) {
- if (!updates.hasOwnProperty(prop)) { continue; }
- original[prop] = updates[prop];
- }
- };
- })();
- (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;
- // 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() {
- /**
- @module ember-metal
- */
- 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;
- /**
- @private
- 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.
- @property GUID_KEY
- @for Ember
- @type String
- @final
- */
- Ember.GUID_KEY = GUID_KEY;
- var GUID_DESC = {
- writable: false,
- configurable: false,
- enumerable: false,
- value: null
- };
- /**
- @private
- 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.
- @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';
- var ret = (prefix + (uuid++));
- if (obj) {
- GUID_DESC.value = ret;
- o_defineProperty(obj, GUID_KEY, GUID_DESC);
- }
- return ret ;
- };
- /**
- @private
- 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.
- @method guidFor
- @for Ember
- @param obj {Object} 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 cache, 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 = {
- 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;
- // Placeholder for non-writable metas.
- var EMPTY_META = {
- descs: {},
- watching: {}
- };
- if (MANDATORY_SETTER) { EMPTY_META.values = {}; }
- Ember.EMPTY_META = EMPTY_META;
- if (Object.freeze) Object.freeze(EMPTY_META);
- var isDefinePropertySimulated = Ember.platform.defineProperty.isSimulated;
- function Meta(obj) {
- this.descs = {};
- this.watching = {};
- this.cache = {};
- this.source = obj;
- }
- 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 () { };
- }
- /**
- 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 {Hash}
- */
- 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;
- };
- /**
- @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) {
- 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;
- };
- /**
- @private
- 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.
- @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__;
- 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}
- */
- 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`.
- @method canInvoke
- @for Ember
- @param {Object} obj The object to check for the method
- @param {String} methodName The method name to check for
- */
- Ember.canInvoke = canInvoke;
- /**
- Checks to see if the `methodName` exists on the `obj`,
- and if it does, invokes it with the arguments passed.
- @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 {anything} 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.
- @method tryFinally
- @for Ember
- @param {Function} function The function to run the try callback
- @param {Function} function The function to run the finally callback
- @param [binding]
- @return {anything} The return value is the that of the finalizer,
- unless that valueis 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.
- @method tryCatchFinally
- @for Ember
- @param {Function} function The function to run the try callback
- @param {Function} function The function to run the catchable callback
- @param {Function} function The function to run the finally callback
- @param [binding]
- @return {anything} 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, finalReturn;
- 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;
- };
- }
- })();
- (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(); };
- })();
- 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);
- };
- 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;
- };
- 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 = {};
- };
- Ember.Instrumentation.reset = function() {
- subscribers = [];
- cache = {};
- };
- Ember.instrument = Ember.Instrumentation.instrument;
- Ember.subscribe = Ember.Instrumentation.subscribe;
- })();
- (function() {
- var utils = Ember.EnumerableUtils = {
- map: function(obj, callback, thisArg) {
- return obj.map ? obj.map.call(obj, callback, thisArg) : Array.prototype.map.call(obj, callback, thisArg);
- },
- forEach: function(obj, callback, thisArg) {
- return obj.forEach ? obj.forEach.call(obj, callback, thisArg) : Array.prototype.forEach.call(obj, callback, thisArg);
- },
- indexOf: function(obj, element, index) {
- return obj.indexOf ? obj.indexOf.call(obj, element, index) : Array.prototype.indexOf.call(obj, element, index);
- },
- indexesOf: function(obj, elements) {
- return elements === undefined ? [] : utils.map(elements, function(item) {
- return utils.indexOf(obj, item);
- });
- },
- addObject: function(array, item) {
- var index = utils.indexOf(array, item);
- if (index === -1) { array.push(item); }
- },
- removeObject: function(array, item) {
- var index = utils.indexOf(array, item);
- if (index !== -1) { array.splice(index, 1); }
- },
- replace: function(array, idx, amt, objects) {
- if (array.replace) {
- return array.replace(idx, amt, objects);
- } else {
- var args = Array.prototype.concat.apply([idx, amt], objects);
- return array.splice.apply(array, args);
- }
- },
- intersection: function(array1, array2) {
- var intersection = [];
- array1.forEach(function(element) {
- if (array2.indexOf(element) >= 0) {
- intersection.push(element);
- }
- });
- return intersection;
- }
- };
- })();
- (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;
- };
- 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() {
- /**
- @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 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;
- 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} function
- @param target
- */
- forEach: function(fn, self) {
- // allow mutation during iteration
- var list = this.list.slice();
- 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.list.slice();
- 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 = {
- /**
- Retrieve the value associated with a given key.
- @method get
- @param {anything} key
- @return {anything} 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 {anything} key
- @param {anything} value
- */
- set: function(key, value) {
- var keys = this.keys,
- values = this.values,
- guid = guidFor(key);
- keys.add(key);
- values[guid] = value;
- },
- /**
- Removes a value from the map for an associated key.
- @method remove
- @param {anything} 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),
- value;
- if (values.hasOwnProperty(guid)) {
- keys.remove(key);
- value = values[guid];
- delete values[guid];
- return true;
- } else {
- return false;
- }
- },
- /**
- Check whether a key is present.
- @method has
- @param {anything} 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 {anything} 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 {anything} [options.defaultValue]
- */
- var MapWithDefault = Ember.MapWithDefault = function(options) {
- Map.call(this);
- this.defaultValue = options.defaultValue;
- };
- /**
- @method create
- @static
- @param [options]
- @param {anything} [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 {anything} key
- @return {anything} 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() {
- /**
- @module ember-metal
- */
- var META_KEY = Ember.META_KEY, get, set;
- var MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER;
- var IS_GLOBAL = /^([A-Z$]|([0-9][A-Z$]))/;
- 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 get 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;
- }
- if (!obj || keyName.indexOf('.') !== -1) {
- Ember.assert("Cannot call get with '"+ keyName +"' on an undefined object.", obj !== undefined);
- return getPath(obj, keyName);
- }
- Ember.assert("You need to provide an object and key to `get`.", !!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;
- }
- };
- /**
- 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 `unknownProperty`
- method then that will be invoked as well.
- If you plan to run on IE8 and older browsers then you should use this
- method anytime you want to set 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 set
- 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.
- @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.
- */
- 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;
- }
- 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)) {
- 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.get = get;
- Ember.set = set;
- Ember.config.overrideAccessors();
- get = Ember.get;
- set = Ember.set;
- }
- function firstKey(path) {
- return path.match(FIRST_KEY)[0];
- }
- // assumes path is already normalized
- function normalizeTuple(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 = firstKey(path);
- 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 Error('Invalid Path');
- return [ target, path ];
- }
- function getPath(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 && idx<len; idx++) {
- root = get(root, parts[idx], true);
- if (root && root.isDestroyed) { return undefined; }
- }
- return root;
- }
- 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.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 Error('You passed an empty path');
- }
- if (!root) {
- if (tolerant) { return; }
- else { throw new Error('Object in path '+path+' could not be found or was destroyed.'); }
- }
- return set(root, keyName, value);
- }
- /**
- @private
- 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.
- @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.
- */
- Ember.normalizeTuple = function(target, path) {
- return normalizeTuple(target, path);
- };
- Ember.getWithDefault = function(root, key, defaultValue) {
- var value = get(root, key);
- if (value === undefined) { return defaultValue; }
- return value;
- };
- Ember.get = get;
- Ember.getPath = Ember.deprecateFunc('getPath is deprecated since get now supports paths', Ember.get);
- Ember.set = set;
- Ember.setPath = Ember.deprecateFunc('setPath is deprecated since set now supports paths', Ember.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} keyName The property key to set
- @param {Object} value The value to set
- */
- Ember.trySet = function(root, path, value) {
- return set(root, path, value, true);
- };
- Ember.trySetPath = Ember.deprecateFunc('trySetPath has been renamed to trySet', Ember.trySet);
- /**
- 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
- */
- Ember.isGlobalPath = function(path) {
- return IS_GLOBAL.test(path);
- };
- })();
- (function() {
- /**
- @module ember-metal
- */
- var GUID_KEY = Ember.GUID_KEY,
- META_KEY = Ember.META_KEY,
- EMPTY_META = Ember.EMPTY_META,
- metaFor = Ember.meta,
- o_create = Ember.create,
- objectDefineProperty = Ember.platform.defineProperty;
- var MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER;
- // ..........................................................
- // DESCRIPTOR
- //
- /**
- Objects of this type can implement an interface to responds 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
- */
- var Descriptor = 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];
- };
- };
- /**
- @private
- 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'));
- ```
- @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 {anything} [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
- }
- desc.setup(obj, keyName);
- } 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() {
- // Ember.tryFinally
- /**
- @module ember-metal
- */
- var AFTER_OBSERVERS = ':change';
- var BEFORE_OBSERVERS = ':before';
- var guidFor = Ember.guidFor;
- var deferred = 0;
- /*
- this.observerSet = {
- [senderGuid]: { // variable name: `keySet`
- [keyName]: listIndex
- }
- },
- this.observers = [
- {
- sender: obj,
- keyName: keyName,
- eventName: eventName,
- listeners: [
- [target, method, onceFlag, suspendedFlag]
- ]
- },
- ...
- ]
- */
- function ObserverSet() {
- this.clear();
- }
- ObserverSet.prototype.add = function(sender, keyName, eventName) {
- var observerSet = this.observerSet,
- observers = this.observers,
- senderGuid = Ember.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; }
- Ember.sendEvent(sender, observer.eventName, [sender, observer.keyName], observer.listeners);
- }
- };
- ObserverSet.prototype.clear = function() {
- this.observerSet = {};
- this.observers = [];
- };
- var beforeObserverSet = new ObserverSet(), observerSet = new ObserverSet();
- /**
- @method beginPropertyChanges
- @chainable
- */
- Ember.beginPropertyChanges = function() {
- deferred++;
- };
- /**
- @method endPropertyChanges
- */
- Ember.endPropertyChanges = function() {
- deferred--;
- if (deferred<=0) {
- beforeObserverSet.clear();
- observerSet.flush();
- }
- };
- /**
- 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){
- Ember.beginPropertyChanges();
- Ember.tryFinally(cb, Ember.endPropertyChanges, binding);
- };
- /**
- Set a list of properties on an object. These properties are set inside
- a single `beginPropertyChanges` and `endPropertyChanges` batch, so
- observers will be buffered.
- @method setProperties
- @param target
- @param {Hash} properties
- @return target
- */
- Ember.setProperties = function(self, hash) {
- Ember.changeProperties(function(){
- for(var prop in hash) {
- if (hash.hasOwnProperty(prop)) Ember.set(self, prop, hash[prop]);
- }
- });
- return self;
- };
- 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;
- };
- Ember.notifyBeforeObservers = function(obj, keyName) {
- if (obj.isDestroying) { return; }
- var eventName = beforeEvent(keyName), listeners, listenersDiff;
- if (deferred) {
- listeners = beforeObserverSet.add(obj, keyName, eventName);
- listenersDiff = Ember.listenersDiff(obj, eventName, listeners);
- Ember.sendEvent(obj, eventName, [obj, keyName], listenersDiff);
- } else {
- Ember.sendEvent(obj, eventName, [obj, keyName]);
- }
- };
- Ember.notifyObservers = function(obj, keyName) {
- if (obj.isDestroying) { return; }
- var eventName = changeEvent(keyName), listeners;
- if (deferred) {
- listeners = observerSet.add(obj, keyName, eventName);
- Ember.listenersUnion(obj, eventName, listeners);
- } else {
- Ember.sendEvent(obj, eventName, [obj, keyName]);
- }
- };
- })();
- (function() {
- /**
- @module ember-metal
- */
- var guidFor = Ember.guidFor, // utils.js
- metaFor = Ember.meta, // utils.js
- get = Ember.get, // accessors.js
- set = Ember.set, // accessors.js
- normalizeTuple = Ember.normalizeTuple, // accessors.js
- GUID_KEY = Ember.GUID_KEY, // utils.js
- META_KEY = Ember.META_KEY, // utils.js
- // circular reference observer depends on Ember.watch
- // we should move change events to this file or its own property_events.js
- notifyObservers = Ember.notifyObservers, // observer.js
- forEach = Ember.ArrayPolyfills.forEach, // array.js
- FIRST_KEY = /^([^\.\*]+)/,
- IS_PATH = /[\.\*]/;
- var MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER,
- o_defineProperty = Ember.platform.defineProperty;
- function firstKey(path) {
- return path.match(FIRST_KEY)[0];
- }
- // returns true if the passed path is just a keyName
- function isKeyName(path) {
- return path==='*' || !IS_PATH.test(path);
- }
- // ..........................................................
- // DEPENDENT KEYS
- //
- 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);
- }
- }
- }
- 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; }
- }
- // ..........................................................
- // CHAIN
- //
- 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);
- Ember.watch(obj, keyName);
- }
- function removeChainWatcher(obj, keyName, node) {
- if (!obj || 'object' !== typeof obj) { return; } // nothing to do
- var m = metaFor(obj, false);
- if (!m.hasOwnProperty('chainWatchers')) { return; } // nothing to do
- var nodes = m.chainWatchers;
- if (nodes[keyName]) {
- nodes = nodes[keyName];
- for (var i = 0, l = nodes.length; i < l; i++) {
- if (nodes[i] === node) { nodes.splice(i, 1); }
- }
- }
- Ember.unwatch(obj, keyName);
- }
- 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.
- function flushPendingChains() {
- if (pendingQueue.length === 0) { return; } // nothing to do
- var queue = pendingQueue;
- pendingQueue = [];
- forEach.call(queue, function(q) { q[0].add(q[1]); });
- Ember.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 isProto(pvalue) {
- return metaFor(pvalue, false).proto === pvalue;
- }
- // 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 = function(parent, key, value) {
- var obj;
- 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;
- ChainNodePrototype.value = function() {
- if (this._value === undefined && this._watching) {
- var obj = this._parent.value();
- this._value = (obj && !isProto(obj)) ? get(obj, this._key) : undefined;
- }
- 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() {
- var chains = this._chains;
- if (chains) {
- for(var key in chains) {
- if (!chains.hasOwnProperty(key)) { continue; }
- chains[key].willChange();
- }
- }
- if (this._parent) { this._parent.chainWillChange(this, this._key, 1); }
- };
- ChainNodePrototype.chainWillChange = function(chain, path, depth) {
- if (this._key) { path = this._key + '.' + path; }
- if (this._parent) {
- this._parent.chainWillChange(this, path, depth+1);
- } else {
- if (depth > 1) { Ember.propertyWillChange(this.value(), path); }
- path = 'this.' + path;
- if (this._paths[path] > 0) { Ember.propertyWillChange(this.value(), path); }
- }
- };
- ChainNodePrototype.chainDidChange = function(chain, path, depth) {
- if (this._key) { path = this._key + '.' + path; }
- if (this._parent) {
- this._parent.chainDidChange(this, path, depth+1);
- } else {
- if (depth > 1) { Ember.propertyDidChange(this.value(), path); }
- path = 'this.' + path;
- if (this._paths[path] > 0) { Ember.propertyDidChange(this.value(), path); }
- }
- };
- ChainNodePrototype.didChange = function(suppressEvent) {
- // 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(suppressEvent);
- }
- }
- if (suppressEvent) { return; }
- // and finally tell parent about my path changing...
- if (this._parent) { this._parent.chainDidChange(this, this._key, 1); }
- };
- // 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) {
- var m = 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.overrideChains = function(obj, keyName, m) {
- chainsDidChange(obj, keyName, m, true);
- };
- function chainsWillChange(obj, keyName, m, arg) {
- if (!m.hasOwnProperty('chainWatchers')) { return; } // nothing to do
- var nodes = m.chainWatchers;
- nodes = nodes[keyName];
- if (!nodes) { return; }
- for(var i = 0, l = nodes.length; i < l; i++) {
- nodes[i].willChange(arg);
- }
- }
- function chainsDidChange(obj, keyName, m, arg) {
- if (!m.hasOwnProperty('chainWatchers')) { return; } // nothing to do
- var nodes = m.chainWatchers;
- nodes = nodes[keyName];
- if (!nodes) { return; }
- // looping in reverse because the chainWatchers array can be modified inside didChange
- for (var i = nodes.length - 1; i >= 0; i--) {
- nodes[i].didChange(arg);
- }
- }
- // ..........................................................
- // WATCH
- //
- /**
- @private
- 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()`
- @method watch
- @for Ember
- @param obj
- @param {String} keyName
- */
- Ember.watch = function(obj, keyName) {
- // can't watch length on Array - it is special...
- if (keyName === 'length' && Ember.typeOf(obj) === 'array') { return this; }
- var m = metaFor(obj), watching = m.watching, desc;
- // activate watching first time
- if (!watching[keyName]) {
- watching[keyName] = 1;
- if (isKeyName(keyName)) {
- desc = m.descs[keyName];
- if (desc && desc.willWatch) { desc.willWatch(obj, keyName); }
- 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: true,
- set: Ember.MANDATORY_SETTER_FUNCTION,
- get: Ember.DEFAULT_GETTER_FUNCTION(keyName)
- });
- }
- } else {
- chainsFor(obj).add(keyName);
- }
- } else {
- watching[keyName] = (watching[keyName] || 0) + 1;
- }
- return this;
- };
- Ember.isWatching = function isWatching(obj, key) {
- var meta = obj[META_KEY];
- return (meta && meta.watching[key]) > 0;
- };
- Ember.watch.flushPending = flushPendingChains;
- Ember.unwatch = function(obj, keyName) {
- // can't watch length on Array - it is special...
- if (keyName === 'length' && Ember.typeOf(obj) === 'array') { return this; }
- var m = metaFor(obj), watching = m.watching, desc;
- if (watching[keyName] === 1) {
- watching[keyName] = 0;
- if (isKeyName(keyName)) {
- desc = m.descs[keyName];
- if (desc && desc.didUnwatch) { desc.didUnwatch(obj, keyName); }
- if ('function' === typeof obj.didUnwatchProperty) {
- obj.didUnwatchProperty(keyName);
- }
- if (MANDATORY_SETTER && keyName in obj) {
- o_defineProperty(obj, keyName, {
- configurable: true,
- enumerable: true,
- writable: true,
- value: m.values[keyName]
- });
- delete m.values[keyName];
- }
- } else {
- chainsFor(obj).remove(keyName);
- }
- } else if (watching[keyName]>1) {
- watching[keyName]--;
- }
- return this;
- };
- /**
- @private
- 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.
- @method rewatch
- @for Ember
- @param obj
- */
- Ember.rewatch = function(obj) {
- var m = metaFor(obj, false), chains = m.chains;
- // make sure the object has its own guid.
- if (GUID_KEY in obj && !obj.hasOwnProperty(GUID_KEY)) {
- Ember.generateGuid(obj, 'ember');
- }
- // make sure any chained watchers update.
- if (chains && chains.value() !== obj) {
- m.chains = chains.copy(obj);
- }
- return this;
- };
- Ember.finishChains = function(obj) {
- var m = metaFor(obj, false), chains = m.chains;
- if (chains) {
- if (chains.value() !== obj) {
- m.chains = chains = chains.copy(obj);
- }
- chains.didChange(true);
- }
- };
- // ..........................................................
- // 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, value) {
- var m = metaFor(obj, false),
- watching = m.watching[keyName] > 0 || keyName === 'length',
- proto = m.proto,
- desc = 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);
- Ember.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 = metaFor(obj, false),
- watching = m.watching[keyName] > 0 || keyName === 'length',
- proto = m.proto,
- desc = 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);
- Ember.notifyObservers(obj, keyName);
- }
- Ember.propertyDidChange = propertyDidChange;
- 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,
- guidFor = Ember.guidFor,
- a_slice = [].slice,
- o_create = Ember.create,
- META_KEY = Ember.META_KEY,
- watch = Ember.watch,
- unwatch = Ember.unwatch;
- // ..........................................................
- // 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(obj, 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;
- }
- /* return obj[META_KEY].deps */
- function metaForDeps(obj, meta) {
- var deps = meta.deps;
- // If the current object has no dependencies...
- if (!deps) {
- // initialize the dependencies with a pointer back to
- // the current object
- deps = meta.deps = {};
- } else if (!meta.hasOwnProperty('deps')) {
- // otherwise if the dependencies are inherited from the
- // object's superclass, clone the deps
- deps = meta.deps = o_create(deps);
- }
- return 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(obj, meta);
- for(idx = 0, len = depKeys.length; idx < len; idx++) {
- depKey = depKeys[idx];
- // Lookup keys meta for depKey
- keys = keysForDep(obj, depsMeta, depKey);
- // Increment the number of times depKey depends on keyName.
- keys[keyName] = (keys[keyName] || 0) + 1;
- // Watch the depKey
- watch(obj, depKey);
- }
- }
- 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(obj, meta);
- for(idx = 0, len = depKeys.length; idx < len; idx++) {
- depKey = depKeys[idx];
- // Lookup keys meta for depKey
- keys = keysForDep(obj, depsMeta, depKey);
- // Increment the number of times depKey depends on keyName.
- keys[keyName] = (keys[keyName] || 0) - 1;
- // Watch the depKey
- unwatch(obj, depKey);
- }
- }
- // ..........................................................
- // COMPUTED PROPERTY
- //
- /**
- @class ComputedProperty
- @namespace Ember
- @extends Ember.Descriptor
- @constructor
- */
- function ComputedProperty(func, opts) {
- this.func = func;
- this._cacheable = (opts && opts.cacheable !== undefined) ? opts.cacheable : true;
- this._dependentKeys = opts && opts.dependentKeys;
- }
- Ember.ComputedProperty = ComputedProperty;
- ComputedProperty.prototype = new Ember.Descriptor();
- var ComputedPropertyPrototype = ComputedProperty.prototype;
- /**
- Call on a computed property to set it into cacheable mode. When in this
- mode the computed property will automatically cache the return value of
- your function until one of the dependent keys changes.
- ```javascript
- MyApp.president = Ember.Object.create({
- fullName: function() {
- return this.get('firstName') + ' ' + this.get('lastName');
- // After calculating the value of this function, Ember will
- // return that value without re-executing this function until
- // one of the dependent properties change.
- }.property('firstName', 'lastName')
- });
- ```
- Properties are cacheable by default.
- @method cacheable
- @param {Boolean} aFlag optional set to `false` to disable caching
- @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.create({
- value: function() {
- return OutsideService.getValue();
- }.property().volatile()
- });
- ```
- @method volatile
- @chainable
- */
- ComputedPropertyPrototype.volatile = function() {
- return this.cacheable(false);
- };
- /**
- 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.create({
- 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')
- });
- ```
- @method property
- @param {String} path* zero or more property paths
- @chainable
- */
- ComputedPropertyPrototype.property = function() {
- var args = [];
- for (var i = 0, l = arguments.length; i < l; i++) {
- args.push(arguments[i]);
- }
- 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.willWatch = function(obj, keyName) {
- // watch already creates meta for this instance
- var meta = obj[META_KEY];
- Ember.assert('watch should have setup meta to be writable', meta.source === obj);
- if (!(keyName in meta.cache)) {
- addDependentKeys(this, obj, keyName, meta);
- }
- };
- ComputedPropertyPrototype.didUnwatch = function(obj, keyName) {
- var meta = obj[META_KEY];
- Ember.assert('unwatch should have setup meta to be writable', meta.source === obj);
- if (!(keyName in meta.cache)) {
- // unwatch already creates meta for this instance
- removeDependentKeys(this, obj, keyName, meta);
- }
- };
- /* 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];
- if (!meta.watching[keyName]) {
- removeDependentKeys(this, obj, keyName, meta);
- }
- }
- }
- };
- /* impl descriptor API */
- ComputedPropertyPrototype.get = function(obj, keyName) {
- var ret, cache, meta;
- if (this._cacheable) {
- meta = metaFor(obj);
- cache = meta.cache;
- if (keyName in cache) { return cache[keyName]; }
- ret = cache[keyName] = this.func.call(obj, keyName);
- if (!meta.watching[keyName]) {
- addDependentKeys(this, obj, keyName, meta);
- }
- } else {
- ret = this.func.call(obj, keyName);
- }
- return ret;
- };
- /* impl descriptor API */
- 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,
- cachedValue, ret;
- this._suspended = obj;
- try {
- if (cacheable && cache.hasOwnProperty(keyName)) {
- cachedValue = cache[keyName];
- hadCachedValue = true;
- }
- // Check if the CP has been wrapped
- if (func.wrappedFunction) { func = func.wrappedFunction; }
- // 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 (func.length === 3) {
- ret = func.call(obj, keyName, value, cachedValue);
- } else if (func.length === 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 (!watched && !hadCachedValue) {
- addDependentKeys(this, obj, keyName, meta);
- }
- cache[keyName] = ret;
- }
- if (watched) { Ember.propertyDidChange(obj, keyName); }
- } finally {
- this._suspended = oldSuspended;
- }
- return ret;
- };
- /* called when property is defined */
- ComputedPropertyPrototype.setup = function(obj, keyName) {
- var meta = obj[META_KEY];
- if (meta && meta.watching[keyName]) {
- addDependentKeys(this, obj, keyName, metaFor(obj));
- }
- };
- /* called before property is overridden */
- ComputedPropertyPrototype.teardown = function(obj, keyName) {
- var meta = metaFor(obj);
- if (meta.watching[keyName] || 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];
- }
- 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
- */
- Ember.cacheFor = function cacheFor(obj, key) {
- var cache = metaFor(obj, false).cache;
- if (cache && key in cache) {
- return cache[key];
- }
- };
- /**
- @method computed.not
- @for Ember
- @param {String} dependentKey
- */
- Ember.computed.not = function(dependentKey) {
- return Ember.computed(dependentKey, function(key) {
- return !get(this, dependentKey);
- });
- };
- /**
- @method computed.empty
- @for Ember
- @param {String} dependentKey
- */
- Ember.computed.empty = function(dependentKey) {
- return Ember.computed(dependentKey, function(key) {
- var val = get(this, dependentKey);
- return val === undefined || val === null || val === '' || (Ember.isArray(val) && get(val, 'length') === 0);
- });
- };
- /**
- @method computed.bool
- @for Ember
- @param {String} dependentKey
- */
- Ember.computed.bool = function(dependentKey) {
- return Ember.computed(dependentKey, function(key) {
- return !!get(this, dependentKey);
- });
- };
- /**
- @method computed.alias
- @for Ember
- @param {String} dependentKey
- */
- Ember.computed.alias = function(dependentKey) {
- return Ember.computed(dependentKey, function(key, value){
- if (arguments.length === 1) {
- return get(this, dependentKey);
- } else {
- set(this, dependentKey, value);
- return value;
- }
- });
- };
- })();
- (function() {
- /**
- @module ember-metal
- */
- var o_create = Ember.create,
- metaFor = Ember.meta,
- metaPath = Ember.metaPath,
- META_KEY = Ember.META_KEY;
- /*
- 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, onceFlag, suspendedFlag]
- ]
- }
- }
- */
- function indexOf(array, target, method) {
- var index = -1;
- for (var i = 0, l = array.length; i < l; i++) {
- if (target === array[i][0] && 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 - 1; i >= 0; i--) {
- var target = actions[i][0],
- method = actions[i][1],
- once = actions[i][2],
- suspended = actions[i][3],
- actionIndex = indexOf(otherActions, target, method);
- if (actionIndex === -1) {
- otherActions.push([target, method, once, suspended]);
- }
- }
- }
- 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 - 1; i >= 0; i--) {
- var target = actions[i][0],
- method = actions[i][1],
- once = actions[i][2],
- suspended = actions[i][3],
- actionIndex = indexOf(otherActions, target, method);
- if (actionIndex !== -1) { continue; }
- otherActions.push([target, method, once, suspended]);
- diffActions.push([target, method, once, suspended]);
- }
- 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`
- */
- 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);
- if (actionIndex !== -1) { return; }
- actions.push([target, method, once, undefined]);
- if ('function' === typeof obj.didAddListener) {
- obj.didAddListener(eventName, target, method);
- }
- }
- /**
- Remove an event listener
- Arguments should match those passed to {{#crossLink "Ember/addListener"}}{{/crossLink}}
- @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, once) {
- var actions = actionsFor(obj, eventName),
- actionIndex = indexOf(actions, target, method);
- // action doesn't exist, give up silently
- if (actionIndex === -1) { return; }
- actions.splice(actionIndex, 1);
- 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 - 1; i >= 0; i--) {
- _removeListener(actions[i][0], actions[i][1]);
- }
- }
- }
- /**
- @private
- 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.
- @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),
- action;
- if (actionIndex !== -1) {
- action = actions[actionIndex].slice(); // copy it, otherwise we're modifying a shared object
- action[3] = true; // mark the action as suspended
- actions[actionIndex] = action; // replace the shared object with our copy
- }
- function tryable() { return callback.call(target); }
- function finalizer() { if (action) { action[3] = undefined; } }
- return Ember.tryFinally(tryable, finalizer);
- }
- /**
- @private
- 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.
- @method suspendListener
- @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 = [],
- eventName, actions, action, 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) {
- action = actions[actionIndex].slice();
- action[3] = true;
- actions[actionIndex] = action;
- suspendedActions.push(action);
- }
- }
- function tryable() { return callback.call(target); }
- function finalizer() {
- for (i = 0, l = suspendedActions.length; i < l; i++) {
- suspendedActions[i][3] = undefined;
- }
- }
- return Ember.tryFinally(tryable, finalizer);
- }
- /**
- @private
- Return a list of currently watched events
- @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;
- }
- /**
- @method sendEvent
- @for Ember
- @param obj
- @param {String} eventName
- @param {Array} params
- @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 - 1; i >= 0; i--) { // looping in reverse for once listeners
- if (!actions[i] || actions[i][3] === true) { continue; }
- var target = actions[i][0],
- method = actions[i][1],
- once = actions[i][2];
- if (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.apply(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++) {
- var target = actions[i][0],
- method = actions[i][1];
- ret.push([target, method]);
- }
- return ret;
- }
- 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() {
- // Ember.Logger
- // Ember.watch.flushPending
- // Ember.beginPropertyChanges, Ember.endPropertyChanges
- // Ember.guidFor, Ember.tryFinally
- /**
- @module ember-metal
- */
- // ..........................................................
- // HELPERS
- //
- var slice = [].slice,
- forEach = Ember.ArrayPolyfills.forEach;
- // invokes passed params - normalizing so you can pass target/func,
- // target/string or just func
- function invoke(target, method, args, ignore) {
- if (method === undefined) {
- method = target;
- target = undefined;
- }
- if ('string' === typeof method) { method = target[method]; }
- if (args && ignore > 0) {
- args = args.length > ignore ? slice.call(args, ignore) : null;
- }
- return Ember.handleErrors(function() {
- // IE8's Function.prototype.apply doesn't accept undefined/null arguments.
- return method.apply(target || this, args || []);
- }, this);
- }
- // ..........................................................
- // RUNLOOP
- //
- var timerMark; // used by timers...
- /**
- Ember RunLoop (Private)
- @class RunLoop
- @namespace Ember
- @private
- @constructor
- */
- var RunLoop = function(prev) {
- this._prev = prev || null;
- this.onceTimers = {};
- };
- RunLoop.prototype = {
- /**
- @method end
- */
- end: function() {
- this.flush();
- },
- /**
- @method prev
- */
- prev: function() {
- return this._prev;
- },
- // ..........................................................
- // Delayed Actions
- //
- /**
- @method schedule
- @param {String} queueName
- @param target
- @param method
- */
- schedule: function(queueName, target, method) {
- var queues = this._queues, queue;
- if (!queues) { queues = this._queues = {}; }
- queue = queues[queueName];
- if (!queue) { queue = queues[queueName] = []; }
- var args = arguments.length > 3 ? slice.call(arguments, 3) : null;
- queue.push({ target: target, method: method, args: args });
- return this;
- },
- /**
- @method flush
- @param {String} queueName
- */
- flush: function(queueName) {
- var queueNames, idx, len, queue, log;
- if (!this._queues) { return this; } // nothing to do
- function iter(item) {
- invoke(item.target, item.method, item.args);
- }
- function tryable() {
- forEach.call(queue, iter);
- }
- Ember.watch.flushPending(); // make sure all chained watchers are setup
- if (queueName) {
- while (this._queues && (queue = this._queues[queueName])) {
- this._queues[queueName] = null;
- // the sync phase is to allow property changes to propagate. don't
- // invoke observers until that is finished.
- if (queueName === 'sync') {
- log = Ember.LOG_BINDINGS;
- if (log) { Ember.Logger.log('Begin: Flush Sync Queue'); }
- Ember.beginPropertyChanges();
- Ember.tryFinally(tryable, Ember.endPropertyChanges);
- if (log) { Ember.Logger.log('End: Flush Sync Queue'); }
- } else {
- forEach.call(queue, iter);
- }
- }
- } else {
- queueNames = Ember.run.queues;
- len = queueNames.length;
- idx = 0;
- outerloop:
- while (idx < len) {
- queueName = queueNames[idx];
- queue = this._queues && this._queues[queueName];
- delete this._queues[queueName];
- if (queue) {
- // the sync phase is to allow property changes to propagate. don't
- // invoke observers until that is finished.
- if (queueName === 'sync') {
- log = Ember.LOG_BINDINGS;
- if (log) { Ember.Logger.log('Begin: Flush Sync Queue'); }
- Ember.beginPropertyChanges();
- Ember.tryFinally(tryable, Ember.endPropertyChanges);
- if (log) { Ember.Logger.log('End: Flush Sync Queue'); }
- } else {
- forEach.call(queue, iter);
- }
- }
- // Loop through prior queues
- for (var i = 0; i <= idx; i++) {
- if (this._queues && this._queues[queueNames[i]]) {
- // Start over at the first queue with contents
- idx = i;
- continue outerloop;
- }
- }
- idx++;
- }
- }
- timerMark = null;
- return this;
- }
- };
- Ember.RunLoop = RunLoop;
- // ..........................................................
- // 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 loop,
- args = arguments;
- run.begin();
- function tryable() {
- if (target || method) {
- return invoke(target, method, args, 2);
- }
- }
- return Ember.tryFinally(tryable, run.end);
- };
- var run = Ember.run;
- /**
- 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
- an 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() {
- run.currentRunLoop = new RunLoop(run.currentRunLoop);
- };
- /**
- 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() {
- Ember.assert('must have a current run loop', run.currentRunLoop);
- function tryable() { run.currentRunLoop.end(); }
- function finalizer() { run.currentRunLoop = run.currentRunLoop.prev(); }
- Ember.tryFinally(tryable, finalizer);
- };
- /**
- 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', 'timers']
- */
- Ember.run.queues = ['sync', 'actions', 'destroy', 'timers'];
- /**
- 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 `run.queues` property.
- ```javascript
- Ember.run.schedule('timers', this, function(){
- // this will be executed at the end of the RunLoop, when timers are run
- console.log("scheduled on timers queue");
- });
- Ember.run.schedule('sync', this, function(){
- // this will be executed at the end of the RunLoop, when bindings are synced
- console.log("scheduled on sync queue");
- });
- // Note the functions will be run in order based on the run queues order. Output would be:
- // scheduled on sync queue
- // scheduled on timers 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) {
- var loop = run.autorun();
- loop.schedule.apply(loop, arguments);
- };
- var scheduledAutorun;
- function autorun() {
- scheduledAutorun = null;
- if (run.currentRunLoop) { run.end(); }
- }
- // Used by global test teardown
- Ember.run.hasScheduledTimers = function() {
- return !!(scheduledAutorun || scheduledLater || scheduledNext);
- };
- // Used by global test teardown
- Ember.run.cancelTimers = function () {
- if (scheduledAutorun) {
- clearTimeout(scheduledAutorun);
- scheduledAutorun = null;
- }
- if (scheduledLater) {
- clearTimeout(scheduledLater);
- scheduledLater = null;
- }
- if (scheduledNext) {
- clearTimeout(scheduledNext);
- scheduledNext = null;
- }
- timers = {};
- };
- /**
- Begins a new RunLoop if necessary and schedules a timer to flush the
- RunLoop at a later time. This method is used by parts of Ember to
- ensure the RunLoop always finishes. You normally do not need to call this
- method directly. Instead use `Ember.run()`
- @method autorun
- @example
- Ember.run.autorun();
- @return {Ember.RunLoop} the new current RunLoop
- */
- Ember.run.autorun = function() {
- if (!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);
- run.begin();
- if (!scheduledAutorun) {
- scheduledAutorun = setTimeout(autorun, 1);
- }
- }
- return run.currentRunLoop;
- };
- /**
- 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.
- ```javascript
- Ember.run.sync();
- ```
- @method sync
- @return {void}
- */
- Ember.run.sync = function() {
- run.autorun();
- run.currentRunLoop.flush('sync');
- };
- // ..........................................................
- // TIMERS
- //
- var timers = {}; // active timers...
- var scheduledLater;
- function invokeLaterTimers() {
- scheduledLater = null;
- var now = (+ new Date()), earliest = -1;
- for (var key in timers) {
- if (!timers.hasOwnProperty(key)) { continue; }
- var timer = timers[key];
- if (timer && timer.expires) {
- if (now >= timer.expires) {
- delete timers[key];
- invoke(timer.target, timer.method, timer.args, 2);
- } else {
- if (earliest<0 || (timer.expires < earliest)) earliest=timer.expires;
- }
- }
- }
- // schedule next timeout to fire...
- if (earliest > 0) { scheduledLater = setTimeout(invokeLaterTimers, earliest-(+ new Date())); }
- }
- /**
- 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
- {{#crossLink "Ember/run.cancel"}}{{/crossLink}} later.
- */
- Ember.run.later = function(target, method) {
- var args, expires, timer, guid, wait;
- // setTimeout compatibility...
- if (arguments.length===2 && 'function' === typeof target) {
- wait = method;
- method = target;
- target = undefined;
- args = [target, method];
- } else {
- args = slice.call(arguments);
- wait = args.pop();
- }
- expires = (+ new Date()) + wait;
- timer = { target: target, method: method, expires: expires, args: args };
- guid = Ember.guidFor(timer);
- timers[guid] = timer;
- run.once(timers, invokeLaterTimers);
- return guid;
- };
- function invokeOnceTimer(guid, onceTimers) {
- if (onceTimers[this.tguid]) { delete onceTimers[this.tguid][this.mguid]; }
- if (timers[guid]) { invoke(this.target, this.method, this.args); }
- delete timers[guid];
- }
- function scheduleOnce(queue, target, method, args) {
- var tguid = Ember.guidFor(target),
- mguid = Ember.guidFor(method),
- onceTimers = run.autorun().onceTimers,
- guid = onceTimers[tguid] && onceTimers[tguid][mguid],
- timer;
- if (guid && timers[guid]) {
- timers[guid].args = args; // replace args
- } else {
- timer = {
- target: target,
- method: method,
- args: args,
- tguid: tguid,
- mguid: mguid
- };
- guid = Ember.guidFor(timer);
- timers[guid] = timer;
- if (!onceTimers[tguid]) { onceTimers[tguid] = {}; }
- onceTimers[tguid][mguid] = guid; // so it isn't scheduled more than once
- run.schedule(queue, timer, invokeOnceTimer, guid, onceTimers);
- }
- return guid;
- }
- /**
- Schedules an item to run one time during the current RunLoop. Calling
- this method with the same target/method combination will have no effect.
- 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 doFoo = function() { foo(); }
- Ember.run.once(myContext, doFoo);
- Ember.run.once(myContext, doFoo);
- // doFoo will only be executed once at the end of the RunLoop
- });
- ```
- @method once
- @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
- */
- Ember.run.once = function(target, method) {
- return scheduleOnce('actions', target, method, slice.call(arguments, 2));
- };
- Ember.run.scheduleOnce = function(queue, target, method, args) {
- return scheduleOnce(queue, target, method, slice.call(arguments, 3));
- };
- var scheduledNext;
- function invokeNextTimers() {
- scheduledNext = null;
- for(var key in timers) {
- if (!timers.hasOwnProperty(key)) { continue; }
- var timer = timers[key];
- if (timer.next) {
- delete timers[key];
- invoke(timer.target, timer.method, timer.args, 2);
- }
- }
- }
- /**
- Schedules an item to run after control has been returned to the system.
- This is often equivalent to calling `setTimeout(function() {}, 1)`.
- ```javascript
- Ember.run.next(myContext, function(){
- // code to be executed in the next RunLoop, which will be scheduled after the current one
- });
- ```
- @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
- */
- Ember.run.next = function(target, method) {
- var guid,
- timer = {
- target: target,
- method: method,
- args: slice.call(arguments),
- next: true
- };
- guid = Ember.guidFor(timer);
- timers[guid] = timer;
- if (!scheduledNext) { scheduledNext = setTimeout(invokeNextTimers, 1); }
- return guid;
- };
- /**
- Cancels a scheduled item. Must be a value returned by `Ember.run.later()`,
- `Ember.run.once()`, or `Ember.run.next()`.
- ```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);
- ```
- @method cancel
- @param {Object} timer Timer object to cancel
- @return {void}
- */
- Ember.run.cancel = function(timer) {
- delete timers[timer];
- };
- })();
- (function() {
- // Ember.Logger
- // get, set, trySet
- // guidFor, isArray, 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,
- isGlobalPath = Ember.isGlobalPath;
- 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}
- */
- 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} propertyPath 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} propertyPath 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;
- },
- 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 {{#crossLink "Ember.Binding/from"}}{{/crossLink}}
- @method from
- @static
- */
- from: function() {
- var C = this, binding = new C();
- return binding.from.apply(binding, arguments);
- },
- /**
- See {{#crossLink "Ember.Binding/to"}}{{/crossLink}}
- @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 {{#crossLink "Binding/oneWay"}}{{/crossLink}}
- @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.
- */
- 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 "Customizing Your 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-metal
- */
- var Mixin, REQUIRED, Alias,
- a_map = Ember.ArrayPolyfills.map,
- a_indexOf = Ember.ArrayPolyfills.indexOf,
- a_forEach = Ember.ArrayPolyfills.forEach,
- a_slice = [].slice,
- EMPTY_META = {}, // dummy for non-writable meta
- o_create = Ember.create,
- defineProperty = Ember.defineProperty,
- guidFor = Ember.guidFor;
- function mixinsMeta(obj) {
- var m = Ember.meta(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 concatenatedProperties(props, values, base) {
- var concats;
- // reset before adding each new mixin to pickup concats from previous
- concats = values.concatenatedProperties || base.concatenatedProperties;
- if (props.concatenatedProperties) {
- concats = concats ? concats.concat(props.concatenatedProperties) : props.concatenatedProperties;
- }
- 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 addNormalizedProperty(base, key, value, meta, descs, values, concats) {
- 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 {
- // impl super if needed...
- if (isMethod(value)) {
- value = giveMethodSuper(base, key, value, values, descs);
- } else if ((concats && a_indexOf.call(concats, key) >= 0) || key === 'concatenatedProperties') {
- value = applyConcatenatedProperties(base, key, value, values);
- }
- descs[key] = undefined;
- values[key] = value;
- }
- }
- function mergeMixins(mixins, m, descs, values, base) {
- var mixin, props, key, concats, 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 = Ember.meta(base);
- concats = concatenatedProperties(props, values, base);
- for (key in props) {
- if (!props.hasOwnProperty(key)) { continue; }
- addNormalizedProperty(base, key, props[key], meta, descs, values, concats);
- }
- // 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);
- if (mixin._without) { a_forEach.call(mixin._without, removeKeys); }
- }
- }
- }
- function writableReq(obj) {
- var m = Ember.meta(obj), req = m.required;
- if (!req || !m.hasOwnProperty('required')) {
- req = m.required = req ? o_create(req) : {};
- }
- return req;
- }
- 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 || Ember.meta(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 updateObservers(obj, key, observer, observerKey, method) {
- if ('function' !== typeof observer) { return; }
- var paths = observer[observerKey];
- if (paths) {
- for (var i=0, l=paths.length; i<l; i++) {
- Ember[method](obj, paths[i], null, key);
- }
- }
- }
- function replaceObservers(obj, key, observer) {
- var prevObserver = obj[key];
- updateObservers(obj, key, prevObserver, '__ember_observesBefore__', 'removeBeforeObserver');
- updateObservers(obj, key, prevObserver, '__ember_observes__', 'removeObserver');
- updateObservers(obj, key, observer, '__ember_observesBefore__', 'addBeforeObserver');
- updateObservers(obj, key, observer, '__ember_observes__', 'addObserver');
- }
- function applyMixin(obj, mixins, partial) {
- var descs = {}, values = {}, m = Ember.meta(obj),
- key, value, desc;
- // Go through all mixins and hashes passed in, and:
- //
- // * Handle concatenated properties
- // * Set up _super wrapping if necessary
- // * Set up computed property descriptors
- // * Copying `toString` in broken browsers
- mergeMixins(mixins, mixinsMeta(obj), descs, values, obj);
- for(key in values) {
- if (key === 'contructor' || !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; }
- replaceObservers(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 or .create.
- App.CommentView = Ember.View.extend(App.Editable, {
- template: Ember.Handlebars.compile('{{#if 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`.
- @class Mixin
- @namespace Ember
- */
- Ember.Mixin = function() { return initMixin(this, arguments); };
- Mixin = Ember.Mixin;
- 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 mixins = Ember.meta(obj, false).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 mixins = Ember.meta(obj, false).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 property or method available via an additional name.
- ```javascript
- App.PaintSample = Ember.Object.extend({
- color: 'red',
- colour: Ember.alias('color'),
- name: function(){
- return "Zed";
- },
- moniker: Ember.alias("name")
- });
- var paintSample = App.PaintSample.create()
- paintSample.get('colour'); // 'red'
- paintSample.moniker(); // 'Zed'
- ```
- @method alias
- @for Ember
- @param {String} methodName name of the method or property to alias
- @return {Ember.Descriptor}
- @deprecated Use `Ember.aliasMethod` or `Ember.computed.alias` instead
- */
- Ember.alias = function(methodName) {
- return new Alias(methodName);
- };
- Ember.deprecateFunc("Ember.alias is deprecated. Please use Ember.aliasMethod or Ember.computed.alias instead.", Ember.alias);
- /**
- 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
- //
- /**
- @method observer
- @for Ember
- @param {Function} func
- @param {String} propertyNames*
- @return func
- */
- Ember.observer = function(func) {
- var paths = a_slice.call(arguments, 1);
- func.__ember_observes__ = paths;
- return func;
- };
- // If observers ever become asynchronous, Ember.immediateObserver
- // must remain synchronous.
- /**
- @method immediateObserver
- @for Ember
- @param {Function} func
- @param {String} propertyNames*
- @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);
- };
- /**
- @method beforeObserver
- @for Ember
- @param {Function} func
- @param {String} propertyNames*
- @return func
- */
- Ember.beforeObserver = function(func) {
- var paths = a_slice.call(arguments, 1);
- func.__ember_observesBefore__ = paths;
- return func;
- };
- })();
- (function() {
- /**
- Ember Metal
- @module ember
- @submodule ember-metal
- */
- })();
- (function() {
- define("rsvp",
- [],
- function() {
- "use strict";
- var browserGlobal = (typeof window !== 'undefined') ? window : {};
- var MutationObserver = browserGlobal.MutationObserver || browserGlobal.WebKitMutationObserver;
- var RSVP, async;
- if (typeof process !== 'undefined' &&
- {}.toString.call(process) === '[object process]') {
- async = function(callback, binding) {
- process.nextTick(function() {
- callback.call(binding);
- });
- };
- } else if (MutationObserver) {
- var queue = [];
- var observer = new MutationObserver(function() {
- var toProcess = queue.slice();
- queue = [];
- toProcess.forEach(function(tuple) {
- var callback = tuple[0], binding = tuple[1];
- callback.call(binding);
- });
- });
- var element = document.createElement('div');
- observer.observe(element, { attributes: true });
- // Chrome Memory Leak: https://bugs.webkit.org/show_bug.cgi?id=93661
- window.addEventListener('unload', function(){
- observer.disconnect();
- observer = null;
- });
- async = function(callback, binding) {
- queue.push([callback, binding]);
- element.setAttribute('drainQueue', 'drainQueue');
- };
- } else {
- async = function(callback, binding) {
- setTimeout(function() {
- callback.call(binding);
- }, 1);
- };
- }
- var Event = function(type, options) {
- this.type = type;
- for (var option in options) {
- if (!options.hasOwnProperty(option)) { continue; }
- this[option] = options[option];
- }
- };
- var indexOf = function(callbacks, callback) {
- for (var i=0, l=callbacks.length; i<l; i++) {
- if (callbacks[i][0] === callback) { return i; }
- }
- return -1;
- };
- var callbacksFor = function(object) {
- var callbacks = object._promiseCallbacks;
- if (!callbacks) {
- callbacks = object._promiseCallbacks = {};
- }
- return callbacks;
- };
- var EventTarget = {
- mixin: function(object) {
- object.on = this.on;
- object.off = this.off;
- object.trigger = this.trigger;
- return object;
- },
- on: function(eventNames, callback, binding) {
- var allCallbacks = callbacksFor(this), callbacks, eventName;
- eventNames = eventNames.split(/\s+/);
- binding = binding || this;
- while (eventName = eventNames.shift()) {
- callbacks = allCallbacks[eventName];
- if (!callbacks) {
- callbacks = allCallbacks[eventName] = [];
- }
- if (indexOf(callbacks, callback) === -1) {
- callbacks.push([callback, binding]);
- }
- }
- },
- off: function(eventNames, callback) {
- var allCallbacks = callbacksFor(this), callbacks, eventName, index;
- eventNames = eventNames.split(/\s+/);
- while (eventName = eventNames.shift()) {
- if (!callback) {
- allCallbacks[eventName] = [];
- continue;
- }
- callbacks = allCallbacks[eventName];
- index = indexOf(callbacks, callback);
- if (index !== -1) { callbacks.splice(index, 1); }
- }
- },
- trigger: function(eventName, options) {
- var allCallbacks = callbacksFor(this),
- callbacks, callbackTuple, callback, binding, event;
- if (callbacks = allCallbacks[eventName]) {
- // Don't cache the callbacks.length since it may grow
- for (var i=0; i<callbacks.length; i++) {
- callbackTuple = callbacks[i];
- callback = callbackTuple[0];
- binding = callbackTuple[1];
- if (typeof options !== 'object') {
- options = { detail: options };
- }
- event = new Event(eventName, options);
- callback.call(binding, event);
- }
- }
- }
- };
- var Promise = function() {
- this.on('promise:resolved', function(event) {
- this.trigger('success', { detail: event.detail });
- }, this);
- this.on('promise:failed', function(event) {
- this.trigger('error', { detail: event.detail });
- }, this);
- };
- var noop = function() {};
- var invokeCallback = function(type, promise, callback, event) {
- var hasCallback = typeof callback === 'function',
- value, error, succeeded, failed;
- if (hasCallback) {
- try {
- value = callback(event.detail);
- succeeded = true;
- } catch(e) {
- failed = true;
- error = e;
- }
- } else {
- value = event.detail;
- succeeded = true;
- }
- if (value && typeof value.then === 'function') {
- value.then(function(value) {
- promise.resolve(value);
- }, function(error) {
- promise.reject(error);
- });
- } else if (hasCallback && succeeded) {
- promise.resolve(value);
- } else if (failed) {
- promise.reject(error);
- } else {
- promise[type](value);
- }
- };
- Promise.prototype = {
- then: function(done, fail) {
- var thenPromise = new Promise();
- if (this.isResolved) {
- RSVP.async(function() {
- invokeCallback('resolve', thenPromise, done, { detail: this.resolvedValue });
- }, this);
- }
- if (this.isRejected) {
- RSVP.async(function() {
- invokeCallback('reject', thenPromise, fail, { detail: this.rejectedValue });
- }, this);
- }
- this.on('promise:resolved', function(event) {
- invokeCallback('resolve', thenPromise, done, event);
- });
- this.on('promise:failed', function(event) {
- invokeCallback('reject', thenPromise, fail, event);
- });
- return thenPromise;
- },
- resolve: function(value) {
- resolve(this, value);
- this.resolve = noop;
- this.reject = noop;
- },
- reject: function(value) {
- reject(this, value);
- this.resolve = noop;
- this.reject = noop;
- }
- };
- function resolve(promise, value) {
- RSVP.async(function() {
- promise.trigger('promise:resolved', { detail: value });
- promise.isResolved = true;
- promise.resolvedValue = value;
- });
- }
- function reject(promise, value) {
- RSVP.async(function() {
- promise.trigger('promise:failed', { detail: value });
- promise.isRejected = true;
- promise.rejectedValue = value;
- });
- }
- function all(promises) {
- var i, results = [];
- var allPromise = new Promise();
- var remaining = promises.length;
- if (remaining === 0) {
- allPromise.resolve([]);
- }
- var resolver = function(index) {
- return function(value) {
- resolve(index, value);
- };
- };
- var resolve = function(index, value) {
- results[index] = value;
- if (--remaining === 0) {
- allPromise.resolve(results);
- }
- };
- var reject = function(error) {
- allPromise.reject(error);
- };
- for (i = 0; i < remaining; i++) {
- promises[i].then(resolver(i), reject);
- }
- return allPromise;
- }
- EventTarget.mixin(Promise.prototype);
- RSVP = { async: async, Promise: Promise, Event: Event, EventTarget: EventTarget, all: all, raiseOnUncaughtExceptions: true };
- return RSVP;
- });
- })();
- (function() {
- define("container",
- [],
- function() {
- var objectCreate = Object.create || function(parent) {
- function F() {}
- F.prototype = parent;
- return new F();
- };
- function InheritingDict(parent) {
- this.parent = parent;
- this.dict = {};
- }
- InheritingDict.prototype = {
- get: function(key) {
- var dict = this.dict;
- if (dict.hasOwnProperty(key)) {
- return dict[key];
- }
- if (this.parent) {
- return this.parent.get(key);
- }
- },
- set: function(key, value) {
- this.dict[key] = value;
- },
- has: function(key) {
- var dict = this.dict;
- if (dict.hasOwnProperty(key)) {
- return true;
- }
- if (this.parent) {
- return this.parent.has(key);
- }
- return false;
- },
- eachLocal: function(callback, binding) {
- var dict = this.dict;
- for (var prop in dict) {
- if (dict.hasOwnProperty(prop)) {
- callback.call(binding, prop, dict[prop]);
- }
- }
- }
- };
- 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.typeInjections = new InheritingDict(parent && parent.typeInjections);
- this.injections = {};
- this._options = new InheritingDict(parent && parent._options);
- this._typeOptions = new InheritingDict(parent && parent._typeOptions);
- }
- Container.prototype = {
- child: function() {
- var container = new Container(this);
- this.children.push(container);
- return container;
- },
- set: function(object, key, value) {
- object[key] = value;
- },
- register: function(type, name, factory, options) {
- var fullName;
- if (type.indexOf(':') !== -1){
- options = factory;
- factory = name;
- fullName = type;
- } else {
- Ember.deprecate('register("'+type +'", "'+ name+'") is now deprecated in-favour of register("'+type+':'+name+'");', true);
- fullName = type + ":" + name;
- }
- this.registry.set(fullName, factory);
- this._options.set(fullName, options || {});
- },
- resolve: function(fullName) {
- return this.resolver(fullName) || this.registry.get(fullName);
- },
- lookup: function(fullName) {
- if (this.cache.has(fullName)) {
- return this.cache.get(fullName);
- }
- var value = instantiate(this, fullName);
- if (!value) { return; }
- if (isSingleton(this, fullName)) {
- this.cache.set(fullName, value);
- }
- return value;
- },
- has: function(fullName) {
- if (this.cache.has(fullName)) {
- return true;
- }
- return !!factoryFor(this, fullName);
- },
- optionsForType: function(type, options) {
- if (this.parent) { illegalChildOperation('optionsForType'); }
- this._typeOptions.set(type, options);
- },
- options: function(type, options) {
- this.optionsForType(type, options);
- },
- typeInjection: function(type, property, fullName) {
- if (this.parent) { illegalChildOperation('typeInjection'); }
- var injections = this.typeInjections.get(type);
- if (!injections) {
- injections = [];
- this.typeInjections.set(type, injections);
- }
- injections.push({ property: property, fullName: fullName });
- },
- injection: function(factoryName, property, injectionName) {
- if (this.parent) { illegalChildOperation('injection'); }
- if (factoryName.indexOf(':') === -1) {
- return this.typeInjection(factoryName, property, injectionName);
- }
- var injections = this.injections[factoryName] = this.injections[factoryName] || [];
- injections.push({ property: property, fullName: injectionName });
- },
- destroy: function() {
- this.isDestroyed = true;
- for (var i=0, l=this.children.length; i<l; i++) {
- this.children[i].destroy();
- }
- this.children = [];
- eachDestroyable(this, function(item) {
- item.isDestroying = true;
- });
- eachDestroyable(this, function(item) {
- item.destroy();
- });
- delete this.parent;
- this.isDestroyed = true;
- },
- reset: function() {
- for (var i=0, l=this.children.length; i<l; i++) {
- resetCache(this.children[i]);
- }
- resetCache(this);
- }
- };
- 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, lookup;
- for (var i=0, l=injections.length; i<l; i++) {
- injection = injections[i];
- lookup = container.lookup(injection.fullName);
- hash[injection.property] = lookup;
- }
- 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) {
- return container.resolve(fullName);
- }
- function instantiate(container, fullName) {
- var factory = factoryFor(container, fullName);
- var splitName = fullName.split(":"),
- type = splitName[0], name = splitName[1],
- value;
- if (option(container, fullName, 'instantiate') === false) {
- return factory;
- }
- if (factory) { console.log(factory)
- var injections = [];
- injections = injections.concat(container.typeInjections.get(type) || []);
- injections = injections.concat(container.injections[fullName] || []);
- var hash = buildInjections(container, injections);
- hash.container = container;
- hash._debugContainerKey = fullName;
- value = factory.create(hash);
- return value;
- }
- }
- 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 = {};
- }
- return Container;
- });
- })();
- (function() {
- /*globals ENV */
- /**
- @module ember
- @submodule ember-runtime
- */
- var indexOf = Ember.EnumerableUtils.indexOf;
- // ........................................
- // 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 |
- | 'number' | Number primitive |
- | 'boolean' | Boolean primitive |
- | 'null' | Null value |
- | 'undefined' | Undefined value |
- | 'function' | A function |
- | 'array' | An instance of Array |
- | 'class' | A Ember class (created using Ember.Object.extend()) |
- | 'instance' | A 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(101); // 'number'
- Ember.typeOf(true); // 'boolean'
- Ember.typeOf(Ember.makeArray); // 'function'
- Ember.typeOf([1,2,90]); // 'array'
- 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 item {Object} 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 ret = 'object';
- }
- return ret;
- };
- /**
- 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 obj === null || obj === undefined || (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) ;
- /**
- 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} object 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);
- };
- /**
- 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) {
- if (typeof obj !== 'object' || obj === null) {
- 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(", ") + "}";
- };
- /**
- 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.keys = function(obj) {
- var ret = [];
- for(var key in obj) {
- if (obj.hasOwnProperty(key)) { ret.push(key); }
- }
- return ret;
- };
- }
- // ..........................................................
- // ERROR
- //
- 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.prototype.constructor.apply(this, arguments);
- // 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);
- })();
- (function() {
- /**
- @module ember
- @submodule ember-runtime
- */
- var STRING_DASHERIZE_REGEXP = (/[ _]/g);
- var STRING_DASHERIZE_CACHE = {};
- var STRING_DECAMELIZE_REGEXP = (/([a-z])([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);
- /**
- 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 {Object...} [args]
- @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,0) - 1 : idx++ ;
- s = formats[argIndex];
- return ((s === null) ? '(null)' : (s === undefined) ? '' : s).toString();
- }) ;
- },
- /**
- 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 or spaces 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,
- ret = cache[str];
- if (ret) {
- return ret;
- } else {
- ret = Ember.String.decamelize(str).replace(STRING_DASHERIZE_REGEXP,'-');
- cache[str] = ret;
- }
- return ret;
- },
- /**
- Returns the lowerCaseCamel form of a string.
- ```javascript
- 'innerHTML'.camelize(); // 'innerHTML'
- 'action_name'.camelize(); // 'actionName'
- 'css-class-name'.camelize(); // 'cssClassName'
- '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() : '';
- });
- },
- /**
- 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
- '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
- @return {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 {{#crossLink "Ember.String/fmt"}}{{/crossLink}}
- @method fmt
- @for String
- */
- String.prototype.fmt = function() {
- return fmt(this, arguments);
- };
- /**
- See {{#crossLink "Ember.String/w"}}{{/crossLink}}
- @method w
- @for String
- */
- String.prototype.w = function() {
- return w(this);
- };
- /**
- See {{#crossLink "Ember.String/loc"}}{{/crossLink}}
- @method loc
- @for String
- */
- String.prototype.loc = function() {
- return loc(this, arguments);
- };
- /**
- See {{#crossLink "Ember.String/camelize"}}{{/crossLink}}
- @method camelize
- @for String
- */
- String.prototype.camelize = function() {
- return camelize(this);
- };
- /**
- See {{#crossLink "Ember.String/decamelize"}}{{/crossLink}}
- @method decamelize
- @for String
- */
- String.prototype.decamelize = function() {
- return decamelize(this);
- };
- /**
- See {{#crossLink "Ember.String/dasherize"}}{{/crossLink}}
- @method dasherize
- @for String
- */
- String.prototype.dasherize = function() {
- return dasherize(this);
- };
- /**
- See {{#crossLink "Ember.String/underscore"}}{{/crossLink}}
- @method underscore
- @for String
- */
- String.prototype.underscore = function() {
- return underscore(this);
- };
- /**
- See {{#crossLink "Ember.String/classify"}}{{/crossLink}}
- @method classify
- @for String
- */
- String.prototype.classify = function() {
- return classify(this);
- };
- /**
- See {{#crossLink "Ember.String/capitalize"}}{{/crossLink}}
- @method capitalize
- @for String
- */
- String.prototype.capitalize = function() {
- return capitalize(this);
- };
- }
- })();
- (function() {
- /**
- @module ember
- @submodule ember-runtime
- */
- var a_slice = Array.prototype.slice;
- 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.create({
- firstName: "Barack",
- lastName: "Obama",
- fullName: function() {
- return this.get('firstName') + ' ' + this.get('lastName');
- // Call this flag to mark the function as a property
- }.property()
- });
- MyApp.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.create({
- firstName: "Barack",
- lastName: "Obama",
- 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 {{#crossLink "Ember.ComputedProperty"}}{{/crossLink}},
- {{#crossLink "Ember/computed"}}{{/crossLink}}
- @method property
- @for Function
- */
- Function.prototype.property = function() {
- var ret = Ember.computed(this);
- 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.create({
- valueObserver: function() {
- // Executes whenever the "value" property changes
- }.observes('value')
- });
- ```
- See {{#crossLink "Ember.Observable/observes"}}{{/crossLink}}
- @method observes
- @for Function
- */
- Function.prototype.observes = function() {
- this.__ember_observes__ = a_slice.call(arguments);
- return this;
- };
- /**
- 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 changes 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.create({
- valueObserver: function() {
- // Executes whenever the "value" property is about to change
- }.observesBefore('value')
- });
- ```
- See {{#crossLink "Ember.Observable/observesBefore"}}{{/crossLink}}
- @method observesBefore
- @for Function
- */
- Function.prototype.observesBefore = function() {
- this.__ember_observesBefore__ = a_slice.call(arguments);
- return this;
- };
- }
- })();
- (function() {
- })();
- (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 using this
- with an `Ember.Object` subclass, you should be sure to change the length
- property using `set().`
- 2. If you must implement `nextObject().` See documentation.
- Once you have these two methods implement, 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
- @extends Ember.Mixin
- @since Ember 0.9
- */
- Ember.Enumerable = Ember.Mixin.create(
- /** @scope Ember.Enumerable.prototype */ {
- // compatibility
- isEnumerable: true,
- /**
- 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.firstObject(); // "a"
- var arr = [];
- arr.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.lastObject(); // "c"
- var arr = [];
- arr.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 `mapProperty`
- @method getEach
- @param {String} key name of the property
- @return {Array} The mapped array.
- */
- getEach: function(key) {
- return this.mapProperty(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 = [];
- 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 mapProperty
- @param {String} key name of the property
- @return {Array} The mapped array.
- */
- mapProperty: function(key) {
- return this.map(function(next) {
- return get(next, key);
- });
- },
- /**
- 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 = [];
- 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):
- 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 filterProperty
- @param {String} key the property to test
- @param {String} [value] optional value to test against.
- @return {Array} filtered array
- */
- filterProperty: function(key, value) {
- return this.filter(iter.apply(this, arguments));
- },
- /**
- 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
- */
- rejectProperty: 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 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 findProperty
- @param {String} key the property to test
- @param {String} [value] optional value to test against.
- @return {Object} found item or `undefined`
- */
- findProperty: function(key, value) {
- return this.find(iter.apply(this, arguments));
- },
- /**
- 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);
- });
- },
- /**
- 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 everyProperty
- @param {String} key the property to test
- @param {String} [value] optional value to test against.
- @return {Boolean}
- */
- everyProperty: 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.some(isManager)) { Paychecks.addBiggerBonus(); }
- ```
- @method some
- @param {Function} callback The callback to execute
- @param {Object} [target] The target object to use
- @return {Array} A filtered array.
- */
- some: function(callback, target) {
- return !!this.find(function(x, idx, i) {
- return !!callback.call(target, x, idx, i);
- });
- },
- /**
- 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 someProperty
- @param {String} key the property to test
- @param {String} [value] optional value to test against.
- @return {Boolean} `true`
- */
- someProperty: function(key, value) {
- return this.some(iter.apply(this, arguments));
- },
- /**
- 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.call(null, 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 = [];
- 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) : method.call(x);
- }
- }, 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 = [];
- this.forEach(function(o, idx) { ret[idx] = o; });
- return ret ;
- },
- /**
- Returns a copy of the array with all null elements removed.
- ```javascript
- var arr = ["a", null, "c", null];
- arr.compact(); // ["a", "c"]
- ```
- @method compact
- @return {Array} the array without null elements.
- */
- compact: function() { return this.without(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 = [] ;
- 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 = [];
- 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
- */
- '[]': Ember.computed(function(key, value) {
- return this;
- }),
- // ..........................................................
- // ENUMERABLE OBSERVERS
- //
- /**
- Registers an enumerable observer. Must implement `Ember.EnumerableObserver`
- mixin.
- @method addEnumerableObserver
- @param target {Object}
- @param opts {Hash}
- */
- 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 target {Object}
- @param [opts] {Hash}
- */
- 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 {Number} [start] optional start offset for the content change.
- For unordered enumerables, you should always pass -1.
- @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 notify = this.propertyDidChange, 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 ;
- }
- }) ;
- })();
- (function() {
- /**
- @module ember
- @submodule ember-runtime
- */
- // ..........................................................
- // HELPERS
- //
- var get = Ember.get, set = Ember.set, meta = Ember.meta, map = Ember.EnumerableUtils.map, cacheFor = Ember.cacheFor;
- function none(obj) { return obj===null || obj===undefined; }
- // ..........................................................
- // 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 if 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
- @extends Ember.Mixin
- @uses Ember.Enumerable
- @since Ember 0.9.0
- */
- Ember.Array = Ember.Mixin.create(Ember.Enumerable, /** @scope Ember.Array.prototype */ {
- // compatibility
- isSCArray: true,
- /**
- 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.
- */
- 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.
- */
- 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 []
- */
- '[]': 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 beginIndex {Integer} (Optional) index to begin slicing from.
- @param endIndex {Integer} (Optional) index to end the slice at.
- @return {Array} New array with specified slice
- */
- slice: function(beginIndex, endIndex) {
- var ret = [];
- var length = get(this, 'length') ;
- if (none(beginIndex)) beginIndex = 0 ;
- if (none(endIndex) || (endIndex > length)) endIndex = length ;
- 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, true) === 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(start, removeCount, addCount)` - This method will be
- called just before the array is modified.
- * `arrayDidChange(start, removeCount, addCount)` - This method will be
- called just after the array is modified.
- Both callbacks will be passed the 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`, `didChange`, and a `context` 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.
- @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: 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;
- },
- 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.
- @property @each
- */
- '@each': Ember.computed(function() {
- if (!this.__each) this.__each = new Ember.EachProxy(this);
- return this.__each;
- })
- }) ;
- })();
- (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
- @extends Ember.Mixin
- @since Ember 0.9
- */
- Ember.Comparable = Ember.Mixin.create( /** @scope Ember.Comparable.prototype */{
- /**
- walk like a duck. Indicates that the object can be compared.
- @property isComparable
- @type Boolean
- @default true
- */
- isComparable: true,
- /**
- 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
- @extends Ember.Mixin
- @since Ember 0.9
- */
- Ember.Copyable = Ember.Mixin.create(
- /** @scope Ember.Copyable.prototype */ {
- /**
- Override to return a copy of the receiver. Default implementation raises
- an exception.
- @method copy
- @param deep {Boolean} 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 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 = Context.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
- @extends Ember.Mixin
- @since Ember 0.9
- */
- Ember.Freezable = Ember.Mixin.create(
- /** @scope Ember.Freezable.prototype */ {
- /**
- 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 the object if of a type supported by the enumerable.
- ```javascript
- set.addObject(contact);
- ```
- ## Removing Objects
- To remove an object form an enumerable, use the `removeObject()` method. This
- will only remove the object if it is already 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
- @extends Ember.Mixin
- @uses Ember.Enumerable
- */
- Ember.MutableEnumerable = Ember.Mixin.create(Ember.Enumerable,
- /** @scope Ember.MutableEnumerable.prototype */ {
- /**
- __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 in 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 objects 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, forEach = Ember.EnumerableUtils.forEach;
- /**
- 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
- @extends Ember.Mixin
- @uses Ember.Array
- @uses Ember.MutableEnumerable
- */
- Ember.MutableArray = Ember.Mixin.create(Ember.Array, Ember.MutableEnumerable,
- /** @scope Ember.MutableArray.prototype */ {
- /**
- __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
- */
- insertAt: function(idx, object) {
- if (idx > get(this, 'length')) throw new 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 `Ember.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 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", "blue"];
- colors.pushObject("black"); // ["red", "green", "blue", "black"]
- colors.pushObject(["yellow", "orange"]); // ["red", "green", "blue", "black", ["yellow", "orange"]]
- ```
- @method pushObject
- @param {anything} obj object to push
- */
- 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", "green", "blue"];
- colors.pushObjects("black"); // ["red", "green", "blue", "black"]
- colors.pushObjects(["yellow", "orange"]); // ["red", "green", "blue", "black", "yellow", "orange"]
- ```
- @method pushObjects
- @param {Ember.Enumerable} objects the objects to add
- @return {Ember.Array} receiver
- */
- pushObjects: function(objects) {
- 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", "green", "blue"];
- colors.unshiftObject("yellow"); // ["yellow", "red", "green", "blue"]
- colors.unshiftObject(["black", "white"]); // [["black", "white"], "yellow", "red", "green", "blue"]
- ```
- @method unshiftObject
- @param {anything} obj object to unshift
- */
- 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", "green", "blue"];
- colors.unshiftObjects(["black", "white"]); // ["black", "white", "red", "green", "blue"]
- 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, defineProperty = Ember.defineProperty;
- /**
- ## 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.create({
- 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` to be called
- 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
- @extends Ember.Mixin
- */
- Ember.Observable = Ember.Mixin.create(/** @scope Ember.Observable.prototype */ {
- /**
- 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.getEach('firstName', 'lastName').compact().join(' ');
- }.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} key 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() {
- var ret = {};
- var propertyNames = arguments;
- if (arguments.length === 1 && Ember.typeOf(arguments[0]) === 'array') {
- propertyNames = arguments[0];
- }
- for(var i = 0; i < propertyNames.length; i++) {
- ret[propertyNames[i]] = get(this, propertyNames[i]);
- }
- return ret;
- },
- /**
- 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 `unknownProperty()` 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 `unknownProperty()` handler will be called instead. This
- gives you an opportunity to implement complex "virtual" properties that
- are not predefined on the object. If `unknownProperty()` 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} key 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;
- },
- /**
- To set multiple properties at once, call `setProperties`
- with a Hash:
- ```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} key 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, anytime the key's value is set, your observer
- will be notified. Note that the observers are triggered anytime 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');
- },
- /**
- @deprecated
- @method getPath
- @param {String} path The property path to retrieve
- @return {Object} The property value or undefined.
- */
- getPath: function(path) {
- Ember.deprecate("getPath is deprecated since get now supports paths");
- return this.get(path);
- },
- /**
- @deprecated
- @method setPath
- @param {String} path The path to the property that will be set
- @param {Object} value The value to set or `null`.
- @return {Ember.Observable}
- */
- setPath: function(path, value) {
- Ember.deprecate("setPath is deprecated since set now supports paths");
- return this.set(path, value);
- },
- /**
- 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 {Object} increment The amount to increment by. Defaults to 1
- @return {Object} The new property value
- */
- incrementProperty: function(keyName, increment) {
- if (!increment) { increment = 1; }
- 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 {Object} increment The amount to decrement by. Defaults to 1
- @return {Object} The new property value
- */
- decrementProperty: function(keyName, increment) {
- if (!increment) { increment = 1; }
- set(this, keyName, (get(this, keyName) || 0)-increment);
- return get(this, keyName);
- },
- /**
- Set the value of a boolean property to the opposite of it's
- current value.
- ```javascript
- starship.toggleProperty('warpDriveEnaged');
- ```
- @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
- */
- var get = Ember.get, set = Ember.set;
- /**
- @class TargetActionSupport
- @namespace Ember
- @extends Ember.Mixin
- */
- Ember.TargetActionSupport = Ember.Mixin.create({
- target: null,
- action: 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'),
- triggerAction: function() {
- var action = get(this, 'action'),
- target = get(this, 'targetObject');
- if (target && action) {
- var ret;
- if (typeof target.send === 'function') {
- ret = target.send(action, this);
- } else {
- if (typeof action === 'string') {
- action = target[action];
- }
- ret = action.call(target, this);
- }
- 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'
- ```
- @class Evented
- @namespace Ember
- @extends Ember.Mixin
- */
- 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
- */
- on: function(name, target, method) {
- Ember.addListener(this, name, target, method);
- },
- /**
- 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
- */
- one: function(name, target, method) {
- if (!method) {
- method = target;
- target = null;
- }
- Ember.addListener(this, name, target, method, true);
- },
- /**
- 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);
- },
- fire: function(name) {
- Ember.deprecate("Ember.Evented#fire() has been deprecated in favor of trigger() for compatibility with jQuery. It will be removed in 1.0. Please update your code to call trigger() instead.");
- this.trigger.apply(this, arguments);
- },
- /**
- Cancels subscription for give 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
- */
- off: function(name, target, method) {
- Ember.removeListener(this, name, target, method);
- },
- /**
- 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.async = function(callback, binding) {
- Ember.run.schedule('actions', binding, callback);
- };
- /**
- @module ember
- @submodule ember-runtime
- */
- var get = Ember.get,
- slice = Array.prototype.slice;
- /**
- @class Deferred
- @namespace Ember
- @extends Ember.Mixin
- */
- Ember.DeferredMixin = Ember.Mixin.create({
- /**
- Add handlers to be called when the Deferred object is resolved or rejected.
- @method then
- @param {Function} doneCallback a callback function to be called when done
- @param {Function} failCallback a callback function to be called when failed
- */
- then: function(doneCallback, failCallback) {
- var promise = get(this, 'promise');
- return promise.then.apply(promise, arguments);
- },
- /**
- Resolve a Deferred object and call any `doneCallbacks` with the given args.
- @method resolve
- */
- resolve: function(value) {
- get(this, 'promise').resolve(value);
- },
- /**
- Reject a Deferred object and call any `failCallbacks` with the given args.
- @method reject
- */
- reject: function(value) {
- get(this, 'promise').reject(value);
- },
- promise: Ember.computed(function() {
- return new RSVP.Promise();
- })
- });
- })();
- (function() {
- })();
- (function() {
- Ember.Container = requireModule('container');
- Ember.Container.set = Ember.set;
- })();
- (function() {
- /**
- @module ember
- @submodule ember-runtime
- */
- // NOTE: this object should never be included directly. Instead use Ember.
- // 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,
- a_slice = Array.prototype.slice,
- GUID_KEY = Ember.GUID_KEY,
- guidFor = Ember.guidFor,
- generateGuid = Ember.generateGuid,
- meta = Ember.meta,
- rewatch = Ember.rewatch,
- finishChains = Ember.finishChains,
- 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);
- 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];
- for (var keyName in properties) {
- 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));
- 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);
- delete m.proto;
- finishChains(this);
- this.init.apply(this, arguments);
- };
- 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;
- }
- var CoreObject = makeCtor();
- CoreObject.toString = function() { return "Ember.CoreObject"; };
- CoreObject.PrototypeMixin = Mixin.create({
- reopen: function() {
- applyMixin(this, arguments, true);
- return this;
- },
- isInstance: true,
- 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.
- @property concatenatedProperties
- @type Array
- @default null
- */
- concatenatedProperties: null,
- /**
- @property isDestroyed
- @default false
- */
- isDestroyed: false,
- /**
- @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.
- @method destroy
- @return {Ember.Object} receiver
- */
- destroy: function() {
- if (this._didCallDestroy) { return; }
- this.isDestroying = true;
- this._didCallDestroy = true;
- if (this.willDestroy) { this.willDestroy(); }
- schedule('destroy', this, this._scheduledDestroy);
- return this;
- },
- /**
- @private
- Invoked by the run loop to actually destroy the object. This is
- scheduled for execution by the `destroy` method.
- @method _scheduledDestroy
- */
- _scheduledDestroy: function() {
- destroy(this);
- set(this, 'isDestroyed', true);
- if (this.didDestroy) { this.didDestroy(); }
- },
- 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.
- 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:
- 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.
- 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,
- 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, 'ember');
- meta(proto).proto = proto; // this will disable observers on prototype
- Class.ClassMixin.apply(Class);
- return Class;
- },
- createWithMixins: function() {
- var C = this;
- if (arguments.length>0) { this._initMixins(arguments); }
- return new C();
- },
- create: function() {
- var C = this;
- if (arguments.length>0) { this._initProperties(arguments); }
- return new C();
- },
- reopen: function() {
- this.willReopen();
- reopen.apply(this.PrototypeMixin, arguments);
- return this;
- },
- 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 desc = meta(this.proto(), false).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);
- /**
- @class CoreObject
- @namespace Ember
- */
- Ember.CoreObject = CoreObject;
- })();
- (function() {
- /**
- @module ember
- @submodule ember-runtime
- */
- var get = Ember.get, set = Ember.set, guidFor = Ember.guidFor, none = Ember.isNone;
- /**
- 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` 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,
- /** @scope Ember.Set.prototype */ {
- // ..........................................................
- // 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 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 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 Error(Ember.FROZEN_ERROR);
- if (none(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 Error(Ember.FROZEN_ERROR);
- if (none(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 "Ember.Set<%@>".fmt(array.join(','));
- }
- });
- })();
- (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") { 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 {
- 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
- */
- /**
- Defines a namespace that will contain an executable application. This is
- very similar to a normal namespace except that it is expected to include at
- least a 'ready' function which can be run to initialize the application.
- Currently `Ember.Application` is very similar to `Ember.Namespace.` However,
- this class may be augmented by additional frameworks so it is important to
- use this instance when building new applications.
- # Example Usage
- ```javascript
- MyApp = Ember.Application.create({
- VERSION: '1.0.0',
- store: Ember.Store.create().from(Ember.fixtures)
- });
- MyApp.ready = function() {
- //..init code goes here...
- }
- ```
- @class Application
- @namespace Ember
- @extends Ember.Namespace
- */
- Ember.Application = Ember.Namespace.extend();
- })();
- (function() {
- /**
- @module ember
- @submodule ember-runtime
- */
- 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,
- /** @scope Ember.ArrayProxy.prototype */ {
- /**
- 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);
- },
- /**
- @private
- Invoked when the content property is about to change. Notifies observers that the
- entire array content will change.
- @method _contentWillChange
- */
- _contentWillChange: Ember.beforeObserver(function() {
- this._teardownContent();
- }, 'content'),
- _teardownContent: function() {
- var content = get(this, 'content');
- if (content) {
- content.removeArrayObserver(this, {
- willChange: 'contentArrayWillChange',
- didChange: 'contentArrayDidChange'
- });
- }
- },
- contentArrayWillChange: Ember.K,
- contentArrayDidChange: Ember.K,
- /**
- @private
- Invoked when the content property changes. Notifies observers that the
- entire array content has changed.
- @method _contentDidChange
- */
- _contentDidChange: Ember.observer(function() {
- var content = get(this, 'content');
- Ember.assert("Can't set ArrayProxy's content to itself", content !== this);
- this._setupContent();
- }, 'content'),
- _setupContent: function() {
- var content = get(this, 'content');
- if (content) {
- content.addArrayObserver(this, {
- willChange: 'contentArrayWillChange',
- didChange: 'contentArrayDidChange'
- });
- }
- },
- _arrangedContentWillChange: Ember.beforeObserver(function() {
- var arrangedContent = get(this, 'arrangedContent'),
- len = arrangedContent ? get(arrangedContent, 'length') : 0;
- this.arrangedContentArrayWillChange(this, 0, len, undefined);
- this.arrangedContentWillChange(this);
- this._teardownArrangedContent(arrangedContent);
- }, 'arrangedContent'),
- _arrangedContentDidChange: Ember.observer(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);
- }, 'arrangedContent'),
- _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) {
- Ember.assert('The content property of '+ this.constructor + ' should be set before modifying it', this.get('content'));
- if (get(this, 'content')) this.replaceContent(idx, amt, objects);
- return this;
- },
- 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 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;
- 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(
- /** @scope Ember.ObjectProxy.prototype */ {
- /**
- The object whose properties will be forwarded.
- @property content
- @type Ember.Object
- @default null
- */
- content: null,
- _contentDidChange: Ember.observer(function() {
- Ember.assert("Can't set ObjectProxy's content to itself", this.get('content') !== this);
- }, 'content'),
- 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 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
- */
- var set = Ember.set, get = Ember.get, guidFor = Ember.guidFor;
- var forEach = Ember.EnumerableUtils.forEach;
- 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.addBeforeObserver(item, keyName, proxy, 'contentKeyWillChange');
- Ember.addObserver(item, keyName, proxy, 'contentKeyDidChange');
- // keep track of the indicies 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[indicies.indexOf(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 {anything}
- */
- 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, array, 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, key, array, lim;
- lim = addedCnt>0 ? idx+addedCnt : -1;
- Ember.beginPropertyChanges(this);
- for(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');
- Ember.endPropertyChanges(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;
- // 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 (!objects || objects.length === 0) {
- this.splice(idx, amt) ;
- } else {
- var args = [idx, amt].concat(objects) ;
- this.splice.apply(this,args) ;
- }
- 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
- @extends Ember.Mixin
- @uses Ember.MutableArray
- @uses Ember.MutableEnumerable
- @uses Ember.Copyable
- @uses Ember.Freezable
- */
- Ember.NativeArray = NativeArray;
- /**
- Creates an `Ember.NativeArray` from an Array like object.
- Does not modify the original object.
- @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.
- @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() {
- var DeferredMixin = Ember.DeferredMixin, // mixins/deferred
- EmberObject = Ember.Object, // system/object
- get = Ember.get;
- var Deferred = Ember.Object.extend(DeferredMixin);
- Deferred.reopenClass({
- promise: function(callback, binding) {
- var deferred = Deferred.create();
- callback.call(binding, deferred);
- return get(deferred, 'promise');
- }
- });
- Ember.Deferred = Deferred;
- })();
- (function() {
- /**
- @module ember
- @submodule ember-runtime
- */
- var loadHooks = Ember.ENV.EMBER_LOAD_HOOKS || {};
- var loaded = {};
- /**
- @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);
- }
- };
- /**
- @method runLoadHooks
- @for Ember
- @param name {String} name of hook
- @param object {Object} object to pass to callbacks
- */
- Ember.runLoadHooks = function(name, object) {
- var hooks;
- loaded[name] = object;
- if (hooks = loadHooks[name]) {
- loadHooks[name].forEach(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`.
- Within an `Ember.Router`-managed application single shared instaces of every
- Controller object in your application's namespace will be added to the
- application's `Ember.Router` instance. See `Ember.Application#initialize`
- for additional information.
- ## Views
- By default a controller instance will be the rendering context
- for its associated `Ember.View.` This connection is made during calls to
- `Ember.ControllerMixin#connectOutlet`.
- Within the view's template, the `Ember.View` instance can be accessed
- through the controller with `{{view}}`.
- ## Target Forwarding
- By default a controller will target your application's `Ember.Router`
- instance. Calls to `{{action}}` within the template of a controller's view
- are forwarded to the router. See `Ember.Handlebars.helpers.action` for
- additional information.
- @class ControllerMixin
- @namespace Ember
- @extends Ember.Mixin
- */
- Ember.ControllerMixin = Ember.Mixin.create({
- /* ducktype as a controller */
- isController: true,
- /**
- The object to which events from the view should be sent.
- For example, when a Handlebars template uses the `{{action}}` helper,
- it will attempt to send the event 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,
- store: null,
- model: Ember.computed.alias('content'),
- send: function(actionName) {
- var args = [].slice.call(arguments, 1), target;
- if (this[actionName]) {
- Ember.assert("The controller " + this + " does not have the action " + actionName, typeof this[actionName] === 'function');
- this[actionName].apply(this, args);
- } else if(target = get(this, 'target')) {
- Ember.assert("The target for controller " + this + " (" + target + ") did not define a `send` method", typeof target.send === 'function');
- target.send.apply(target, arguments);
- }
- }
- });
- /**
- @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'}
- ```
- @class SortableMixin
- @namespace Ember
- @extends Ember.Mixin
- @uses Ember.MutableEnumerable
- */
- Ember.SortableMixin = Ember.Mixin.create(Ember.MutableEnumerable, {
- /**
- Specifies which properties dictate the arrangedContent's sort order.
- @property {Array} sortProperties
- */
- sortProperties: null,
- /**
- Specifies the arrangedContent's sort direction
- @property {Boolean} sortAscending
- */
- sortAscending: true,
- orderBy: function(item1, item2) {
- var result = 0,
- sortProperties = get(this, 'sortProperties'),
- sortAscending = get(this, 'sortAscending');
- Ember.assert("you need to define `sortProperties`", !!sortProperties);
- forEach(sortProperties, function(propertyName) {
- if (result === 0) {
- result = Ember.compare(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'),
- 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(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();
- }, 'content'),
- sortAscendingWillChange: Ember.beforeObserver(function() {
- this._lastSortAscending = get(this, 'sortAscending');
- }, 'sortAscending'),
- sortAscendingDidChange: Ember.observer(function() {
- if (get(this, 'sortAscending') !== this._lastSortAscending) {
- var arrangedContent = get(this, 'arrangedContent');
- arrangedContent.reverseObjects();
- }
- }, 'sortAscending'),
- 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);
- var arrangedContent = get(this, 'arrangedContent');
- 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, isGlobalPath = Ember.isGlobalPath,
- 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
- }
- }
- });
- ```
- @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
- @type String
- @default null
- */
- lookupItemController: function(object) {
- return get(this, 'itemController');
- },
- objectAtContent: function(idx) {
- var length = get(this, 'length'),
- object = get(this,'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._resetSubContainers();
- },
- arrayContentDidChange: function(idx, removedCnt, addedCnt) {
- var subContainers = get(this, 'subContainers'),
- subContainersToRemove = subContainers.slice(idx, idx+removedCnt);
- forEach(subContainersToRemove, function(subContainer) {
- if (subContainer) { subContainer.destroy(); }
- });
- replace(subContainers, idx, removedCnt, new Array(addedCnt));
- // The shadow array of subcontainers 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();
- if (!this.get('content')) { this.set('content', Ember.A()); }
- this._resetSubContainers();
- },
- controllerAt: function(idx, object, controllerClass) {
- var container = get(this, 'container'),
- subContainers = get(this, 'subContainers'),
- subContainer = subContainers[idx],
- controller;
- if (!subContainer) {
- subContainer = subContainers[idx] = container.child();
- }
- controller = subContainer.lookup("controller:" + controllerClass);
- if (!controller) {
- throw new Error('Could not resolve itemController: "' + controllerClass + '"');
- }
- controller.set('target', this);
- controller.set('content', object);
- return controller;
- },
- subContainers: null,
- _resetSubContainers: function() {
- var subContainers = get(this, 'subContainers');
- if (subContainers) {
- forEach(subContainers, function(subContainer) {
- if (subContainer) { subContainer.destroy(); }
- });
- }
- this.set('subContainers', Ember.A());
- }
- });
- })();
- (function() {
- /**
- @module ember
- @submodule ember-runtime
- */
- /**
- `Ember.ObjectController` is part of Ember's Controller layer. A single shared
- instance of each `Ember.ObjectController` subclass in your application's
- namespace will be created at application initialization and be stored on your
- application's `Ember.Router` instance.
- `Ember.ObjectController` derives its 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 = Ember.imports.jQuery;
- Ember.assert("Ember Views require jQuery 1.8 or 1.9", jQuery && (jQuery().jquery.match(/^1\.(8|9)(\.\d+)?(pre|rc\d?)?/) || Ember.ENV.FORCE_JQUERY));
- /**
- Alias for jQuery
- @method $
- @for Ember
- */
- Ember.$ = jQuery;
- })();
- (function() {
- /**
- @module ember
- @submodule ember-views
- */
- // 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 = (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 = (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 {
- Ember.assert("Can't set innerHTML on "+element.tagName+" in this browser", element.outerHTML);
- var startTag = element.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 indexOf = Ember.ArrayPolyfills.indexOf;
- 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(" ");
- }
- };
- /**
- `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.
- @class RenderBuffer
- @namespace Ember
- @constructor
- */
- Ember.RenderBuffer = function(tagName) {
- return new Ember._RenderBuffer(tagName);
- };
- Ember._RenderBuffer = function(tagName) {
- this.tagNames = [tagName || null];
- this.buffer = [];
- };
- Ember._RenderBuffer.prototype =
- /** @scope Ember.RenderBuffer.prototype */ {
- // The root view's element
- _element: null,
- /**
- @private
- An internal set used to de-dupe class names when `addClass()` is
- used. After each call to `addClass()`, the `classes` property
- will be updated.
- @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.push(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
- var elementClasses = this.elementClasses = (this.elementClasses || new ClassSet());
- this.elementClasses.add(className);
- this.classes = this.elementClasses.list;
- return this;
- },
- setClasses: function(classNames) {
- this.classes = classNames;
- },
- /**
- 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 an 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) {
- var style = 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._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.push('<' + tagName);
- if (id) {
- buffer.push(' id="' + this._escapeAttribute(id) + '"');
- this.elementId = null;
- }
- if (classes) {
- buffer.push(' class="' + this._escapeAttribute(classes.join(' ')) + '"');
- this.classes = null;
- }
- if (style) {
- buffer.push(' style="');
- for (prop in style) {
- if (style.hasOwnProperty(prop)) {
- buffer.push(prop + ':' + this._escapeAttribute(style[prop]) + ';');
- }
- }
- buffer.push('"');
- this.elementStyle = null;
- }
- if (attrs) {
- for (attr in attrs) {
- if (attrs.hasOwnProperty(attr)) {
- buffer.push(' ' + attr + '="' + this._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.push(' ' + prop + '="' + prop + '"');
- } else {
- buffer.push(' ' + prop + '="' + this._escapeAttribute(props[prop]) + '"');
- }
- }
- }
- }
- this.elementProperties = null;
- }
- buffer.push('>');
- },
- pushClosingTag: function() {
- var tagName = this.tagNames.pop();
- if (tagName) { this.buffer.push('</' + 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
- element = document.createElement(tagName),
- $element = Ember.$(element),
- id = this.elementId,
- classes = this.classes,
- attrs = this.elementAttributes,
- props = this.elementProperties,
- style = this.elementStyle,
- styleBuffer = '', attr, prop;
- if (id) {
- $element.attr('id', id);
- this.elementId = null;
- }
- if (classes) {
- $element.attr('class', classes.join(' '));
- this.classes = 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._element) {
- return this.element().outerHTML;
- } else {
- return this.innerString();
- }
- },
- innerString: function() {
- return this.buffer.join('');
- },
- _escapeAttribute: function(value) {
- // Stolen shamelessly from Handlebars
- var escape = {
- "<": "<",
- ">": ">",
- '"': """,
- "'": "'",
- "`": "`"
- };
- var badChars = /&(?!\w+;)|[<>"'`]/g;
- var possible = /[&<>"'`]/;
- var escapeChar = function(chr) {
- return escape[chr] || "&";
- };
- var string = value.toString();
- if(!possible.test(string)) { return string; }
- return string.replace(badChars, escapeChar);
- }
- };
- })();
- (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(
- /** @scope Ember.EventDispatcher.prototype */{
- /**
- @private
- 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.
- @property rootElement
- @type DOMElement
- @default 'body'
- */
- rootElement: 'body',
- /**
- @private
- 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.
- @method setup
- @param addedEvents {Hash}
- */
- setup: function(addedEvents) {
- var event, 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'
- };
- Ember.$.extend(events, addedEvents || {});
- var 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]);
- }
- }
- },
- /**
- @private
- 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');
- ```
- @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.delegate('.ember-view', event + '.ember', function(evt, triggeringManager) {
- return Ember.handleErrors(function() {
- 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.delegate('[data-ember-action]', event + '.ember', function(evt) {
- return Ember.handleErrors(function() {
- 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 = 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() {
- return view.handleEvent(eventName, evt);
- });
- },
- destroy: function() {
- var rootElement = get(this, 'rootElement');
- Ember.$(rootElement).undelegate('.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;
- queues.splice(Ember.$.inArray('actions', queues)+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(function() {
- var containers = get(this, '_childContainers'),
- container;
- for (var prop in containers) {
- if (!containers.hasOwnProperty(prop)) { continue; }
- containers[prop].destroy();
- }
- set(this, '_childContainers', {});
- }, 'model')
- });
- })();
- (function() {
- })();
- (function() {
- var states = {};
- /**
- @module ember
- @submodule ember-views
- */
- var get = Ember.get, set = Ember.set, addObserver = Ember.addObserver, removeObserver = Ember.removeObserver;
- var meta = Ember.meta, guidFor = Ember.guidFor, fmt = Ember.String.fmt;
- var a_slice = [].slice;
- var a_forEach = Ember.EnumerableUtils.forEach;
- var a_addObject = Ember.EnumerableUtils.addObject;
- var childViewsProperty = Ember.computed(function() {
- var childViews = this._childViews, ret = Ember.A(), view = this;
- a_forEach(childViews, function(view) {
- if (view.isVirtual) {
- ret.pushObjects(get(view, 'childViews'));
- } else {
- ret.push(view);
- }
- });
- ret.replace = function (idx, removedCount, addedViews) {
- if (view instanceof Ember.ContainerView) {
- Ember.deprecate("Manipulating a 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 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 = Ember.Object.extend(Ember.Evented, {
- isView: true,
- states: states,
- init: function() {
- this._super();
- // Register the view for event handling. This hash is used by
- // Ember.EventDispatcher to dispatch incoming events.
- if (!this.isVirtual) {
- Ember.assert("Attempted to register a view with an id already in use: "+this.elementId, !Ember.View.views[this.elementId]);
- Ember.View.views[this.elementId] = this;
- }
- this.addBeforeObserver('elementId', function() {
- throw new Error("Changing a view's elementId after creation is not allowed");
- });
- 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').volatile(),
- instrumentName: 'core_view',
- instrumentDetails: function(hash) {
- hash.object = this.toString();
- },
- /**
- @private
- 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.
- */
- renderToBuffer: function(parentBuffer, bufferOperation) {
- var name = 'render.' + this.instrumentName,
- details = {};
- this.instrumentDetails(details);
- return Ember.instrument(name, details, function() {
- return this._renderToBuffer(parentBuffer, bufferOperation);
- }, this);
- },
- _renderToBuffer: function(parentBuffer, bufferOperation) {
- Ember.run.sync();
- // 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;
- },
- /**
- @private
- Override the default event firing from `Ember.Evented` to
- also call methods with the given name.
- @method trigger
- @param name {String}
- */
- 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);
- }
- },
- has: function(name) {
- return Ember.typeOf(this[name]) === 'function' || this._super(name);
- },
- willDestroy: function() {
- var parent = this._parentView;
- // 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('destroyed');
- // next remove view from global hash
- if (!this.isVirtual) delete Ember.View.views[this.elementId];
- },
- clearRenderedChildren: Ember.K,
- triggerRecursively: Ember.K,
- invokeRecursively: Ember.K,
- transitionTo: Ember.K,
- destroyElement: Ember.K
- });
- /**
- `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.create({
- 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.create({
- 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` 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`
- 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'
- });
- ```
- Using a value for `templateName` that does not have a Handlebars template
- with a matching `data-template-name` attribute will throw an error.
- Assigning a value to both `template` and `templateName` properties 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 `Handlebars.helpers.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 presedence 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
- // OutsideView 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`.
- ### Event Names
- Possible events names for any of the responding approaches described above
- are:
- 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 `Handlebars.helpers.view` for
- additional information.
- @class View
- @namespace Ember
- @extends Ember.Object
- @uses Ember.Evented
- */
- Ember.View = Ember.CoreView.extend(
- /** @scope Ember.View.prototype */ {
- concatenatedProperties: ['classNames', 'classNameBindings', 'attributeBindings'],
- /**
- @property isView
- @type Boolean
- @default true
- @final
- */
- isView: true,
- // ..........................................................
- // TEMPLATE SUPPORT
- //
- /**
- The name of the template to lookup if no template is provided.
- `Ember.View` will look for a template with this name in this view's
- `templates` object. By default, this will be a global object
- shared in `Ember.TEMPLATES`.
- @property templateName
- @type String
- @default null
- */
- templateName: null,
- /**
- The name of the layout to lookup if no layout is provided.
- `Ember.View` will look for a template with this name in this view's
- `templates` object. By default, this will be a global object
- shared in `Ember.TEMPLATES`.
- @property layoutName
- @type String
- @default null
- */
- layoutName: null,
- /**
- The hash in which to look for `templateName`.
- @property templates
- @type Ember.Object
- @default Ember.TEMPLATES
- */
- templates: Ember.TEMPLATES,
- /**
- 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'),
- container: Ember.computed(function() {
- var parentView = get(this, '_parentView');
- if (parentView) { return get(parentView, 'container'); }
- return Ember.Container && Ember.Container.defaultContainer;
- }),
- /**
- 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'),
- templateForName: function(name, type) {
- if (!name) { return; }
- Ember.assert("templateNames are not allowed to contain periods: "+name, name.indexOf('.') === -1);
- var container = get(this, 'container');
- if (container) {
- return 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
- 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
- */
- _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;
- }),
- /**
- @private
- If a value that affects template rendering changes, the view should be
- re-rendered to reflect the new value.
- @method _displayPropertyDidChange
- */
- _contextDidChange: Ember.observer(function() {
- this.rerender();
- }, 'context'),
- /**
- If `false`, the view will appear hidden in DOM.
- @property isVisible
- @type Boolean
- @default null
- */
- isVisible: true,
- /**
- @private
- Array of child views. You should never edit this array directly.
- Instead, use `appendChild` and `removeFromParent`.
- @property childViews
- @type Array
- @default []
- */
- childViews: childViewsProperty,
- _childViews: [],
- // When it's a virtual view, we need to notify the parent that their
- // childViews will change.
- _childViewsWillChange: Ember.beforeObserver(function() {
- if (this.isVirtual) {
- var parentView = get(this, 'parentView');
- if (parentView) { Ember.propertyWillChange(parentView, 'childViews'); }
- }
- }, 'childViews'),
- // When it's a virtual view, we need to notify the parent that their
- // childViews did change.
- _childViewsDidChange: Ember.observer(function() {
- if (this.isVirtual) {
- var parentView = get(this, 'parentView');
- if (parentView) { Ember.propertyDidChange(parentView, 'childViews'); }
- }
- }, '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.
- @property 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`.
- @property 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');
- }
- },
- /**
- @private
- When the parent view changes, recursively invalidate `controller`
- @method _parentViewDidChange
- */
- _parentViewDidChange: Ember.observer(function() {
- if (this.isDestroying) { return; }
- if (get(this, 'parentView.controller') && !get(this, 'controller')) {
- this.notifyPropertyChange('controller');
- }
- }, '_parentView'),
- _controllerDidChange: Ember.observer(function() {
- if (this.isDestroying) { return; }
- this.rerender();
- this.forEachChildView(function(view) {
- view.propertyDidChange('controller');
- });
- }, '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(); }
- }
- },
- /**
- @private
- 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
- */
- _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) {
- // 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);
- },
- /**
- @private
- 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
- */
- _applyAttributeBindings: function(buffer, attributeBindings) {
- var attributeValue, elem, type;
- 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.$();
- if (!elem) { return; }
- 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);
- },
- /**
- @private
- 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
- */
- _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.
- @property $
- @param {String} [selector] a jQuery-compatible selector string
- @return {jQuery} the CoreQuery 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.call(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.call(this, 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 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} A selector, element, HTML string, or jQuery object
- @return {Ember.View} received
- */
- replaceIn: function(target) {
- 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;
- },
- /**
- @private
- 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
- */
- _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.
- 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.
- 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,
- /**
- @private
- Run this callback on the current view and recursively on child views.
- @method invokeRecursively
- @param fn {Function}
- */
- invokeRecursively: function(fn) {
- var childViews = [this], currentViews, view;
- while (childViews.length) {
- currentViews = childViews.slice();
- childViews = [];
- for (var i=0, l=currentViews.length; i<l; i++) {
- view = currentViews[i];
- fn.call(view, view);
- if (view._childViews) {
- childViews.push.apply(childViews, view._childViews);
- }
- }
- }
- },
- triggerRecursively: function(eventName) {
- var childViews = [this], currentViews, view;
- while (childViews.length) {
- currentViews = childViews.slice();
- childViews = [];
- for (var i=0, l=currentViews.length; i<l; i++) {
- view = currentViews[i];
- if (view.trigger) { view.trigger(eventName); }
- if (view._childViews) {
- childViews.push.apply(childViews, view._childViews);
- }
- }
- }
- },
- /**
- 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.
- Normally you will not call or override this method yourself, but you may
- want to implement the above callbacks when it is run.
- @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: function() {},
- /**
- @private
- 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
- */
- _notifyWillDestroyElement: function() {
- this.triggerRecursively('willClearRender');
- this.triggerRecursively('willDestroyElement');
- },
- _elementWillChange: Ember.beforeObserver(function() {
- this.forEachChildView(function(view) {
- Ember.propertyWillChange(view, 'element');
- });
- }, 'element'),
- /**
- @private
- 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
- */
- _elementDidChange: Ember.observer(function() {
- this.forEachChildView(function(view) {
- Ember.propertyDidChange(view, 'element');
- });
- }, '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 () {
- return this.currentState.renderToBufferIfNeeded(this, this);
- },
- 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
- @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.create({
- 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.create({
- 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.create({
- classNameBindings: ['isUrgent:urgent']
- isUrgent: true
- });
- ```
- This list of properties is inherited from the view's superclasses as well.
- @property classNameBindings
- @type Array
- @default []
- */
- classNameBindings: [],
- /**
- 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.create({
- 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.create({
- attributeBindings: ['enabled'],
- enabled: true
- });
- ```
- @property attributeBindings
- */
- attributeBindings: [],
- // .......................................................
- // CORE DISPLAY METHODS
- //
- /**
- @private
- 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
- */
- 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());
- var viewController = get(this, 'viewController');
- if (viewController) {
- viewController = get(viewController);
- if (viewController) {
- set(viewController, 'view', this);
- }
- }
- },
- 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(view) {
- this.removeChild(view);
- });
- },
- destroyAllChildren: function() {
- return this.mutateChildViews(function(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 willDestroy
- */
- willDestroy: function() {
- // calling this._super() will nuke computed properties and observers,
- // so collect any information we need before calling super.
- var childViews = this._childViews,
- parent = this._parentView,
- childLen, i;
- // destroy the element -- this will avoid each child view destroying
- // the element over and over again...
- if (!this.removedFromDOM) { this.destroyElement(); }
- 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 (this.viewName) {
- var nonVirtualParentView = get(this, 'parentView');
- if (nonVirtualParentView) {
- set(nonVirtualParentView, this.viewName, null);
- }
- }
- // 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('destroyed');
- childLen = childViews.length;
- for (i=childLen-1; i>=0; i--) {
- childViews[i].destroy();
- }
- // next remove view from global hash
- if (!this.isVirtual) delete Ember.View.views[get(this, 'elementId')];
- },
- /**
- 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} viewClass
- @param {Hash} [attrs] Attributes to add
- @return {Ember.View} new instance
- */
- createChildView: function(view, attrs) {
- if (view.isView && view._parentView === this) { return view; }
- if (Ember.CoreView.detect(view)) {
- attrs = attrs || {};
- attrs._parentView = this;
- attrs.templateData = attrs.templateData || get(this, 'templateData');
- 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 {
- Ember.assert('You must pass instance or subclass of View', view.isView);
- if (attrs) {
- view.setProperties(attrs);
- }
- if (!get(view, 'templateData')) {
- set(view, 'templateData', get(this, 'templateData'));
- }
- set(view, '_parentView', this);
- }
- return view;
- },
- becameVisible: Ember.K,
- becameHidden: Ember.K,
- /**
- @private
- When the view's `isVisible` property changes, toggle the visibility
- element of the actual DOM element.
- @method _isVisibleDidChange
- */
- _isVisibleDidChange: Ember.observer(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();
- }
- }, 'isVisible'),
- _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) {
- this.currentState = this.states[state];
- this.state = state;
- if (children !== false) {
- this.forEachChildView(function(view) {
- view.transitionTo(state);
- });
- }
- },
- // .......................................................
- // EVENT HANDLING
- //
- /**
- @private
- Handle events from `Ember.EventDispatcher`
- @method handleEvent
- @param eventName {String}
- @param evt {Event}
- */
- handleEvent: function(eventName, evt) {
- return this.currentState.handleEvent(this, eventName, evt);
- },
- registerObserver: function(root, path, target, observer) {
- Ember.addObserver(root, path, target, observer);
- this.one('willClearRender', function() {
- Ember.removeObserver(root, path, target, observer);
- });
- }
- });
- /*
- 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
- * 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.
- var DOMManager = {
- prepend: function(view, html) {
- view.$().prepend(html);
- },
- after: function(view, html) {
- view.$().after(html);
- },
- html: function(view, html) {
- view.$().html(html);
- },
- replace: function(view) {
- var element = get(view, 'element');
- set(view, 'element', null);
- view._insertElementLater(function() {
- Ember.$(element).replaceWith(get(view, 'element'));
- });
- },
- remove: function(view) {
- view.$().remove();
- },
- empty: function(view) {
- view.$().empty();
- }
- };
- Ember.View.reopen({
- domManager: DOMManager
- });
- Ember.View.reopenClass({
- /**
- @private
- Parse a path and return an object which holds the parsed properties.
- For example a path like "content.isEnabled:enabled:disabled" wil return the
- following object:
- ```javascript
- {
- path: "content.isEnabled",
- className: "enabled",
- falsyClassName: "disabled",
- classNames: ":enabled:disabled"
- }
- ```
- @method _parsePropertyPath
- @static
- */
- _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
- };
- },
- /**
- @private
- 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
- */
- _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 !== undefined && 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;
- }
- }
- });
- /**
- 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 (value !== elem.prop(name)) {
- // value and booleans 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
- };
- })();
- (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();
- view.triggerRecursively('willInsertElement');
- // after createElement, the view will be in the hasElement state.
- fn.call(view);
- view.transitionTo('inDOM');
- view.triggerRecursively('didInsertElement');
- },
- renderToBufferIfNeeded: function(view) {
- return view.renderToBuffer();
- },
- 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, meta = Ember.meta;
- 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;
- childView = view.createChildView(childView, options);
- view._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();
- view._notifyWillDestroyElement();
- view.transitionTo('preRender');
- 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) {
- return view.buffer;
- },
- // 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;
- }
- });
- })();
- (function() {
- /**
- @module ember
- @submodule ember-views
- */
- var get = Ember.get, set = Ember.set, meta = Ember.meta;
- 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
- }
- }
- });
- var inDOM = Ember.View.states.inDOM = Ember.create(hasElement);
- Ember.merge(inDOM, {
- 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 destroyedError = "You can't call %@ on a destroyed view", fmt = Ember.String.fmt;
- var destroyed = Ember.View.states.destroyed = Ember.create(Ember.View.states._default);
- Ember.merge(destroyed, {
- appendChild: function() {
- throw fmt(destroyedError, ['appendChild']);
- },
- rerender: function() {
- throw fmt(destroyedError, ['rerender']);
- },
- destroyElement: function() {
- throw fmt(destroyedError, ['destroyElement']);
- },
- empty: function() {
- throw fmt(destroyedError, ['empty']);
- },
- setElement: function() {
- throw fmt(destroyedError, ["set('element', ...)"]);
- },
- renderToBufferIfNeeded: function() {
- throw fmt(destroyedError, ["renderToBufferIfNeeded"]);
- },
- // 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.destroyed = Ember.create(into._default);
- into.inBuffer = Ember.create(into._default);
- into.hasElement = Ember.create(into._default);
- into.inDOM = Ember.create(into.hasElement);
- var viewState;
- 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, meta = Ember.meta;
- var forEach = Ember.EnumerableUtils.forEach;
- /**
- A `ContainerView` is an `Ember.View` subclass that implements `Ember.MutableArray`
- allowing programatic 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 programatic 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.
- ## Binding a View to Display
- If you would like to display a single view in your ContainerView, you can set
- its `currentView` property. When the `currentView` property is set to a view
- instance, it will be added to the ContainerView. If the `currentView` property
- is later changed to a different view, the new view will replace the old view.
- If `currentView` is set to `null`, the last `currentView` will be removed.
- This functionality is useful for cases where you want to bind the display of
- a ContainerView to a controller or state manager. For example, you can bind
- the `currentView` of a container to a controller like this:
- ```javascript
- App.appController = Ember.Object.create({
- view: Ember.View.create({
- templateName: 'person_template'
- })
- });
- ```
- ```handlebars
- {{view Ember.ContainerView currentViewBinding="App.appController.view"}}
- ```
- @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) {
- _childViews.push(this.createChildView(currentView));
- }
- },
- replace: function(idx, removedCount, addedViews) {
- var addedCount = addedViews ? get(addedViews, 'length') : 0;
- 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);
- 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;
- }),
- /**
- @private
- Instructs each child view to render to the passed render buffer.
- @method render
- @param {Ember.RenderBuffer} buffer the buffer to render to
- */
- render: function(buffer) {
- this.forEachChildView(function(view) {
- view.renderToBuffer(buffer);
- });
- },
- instrumentName: 'render.container',
- /**
- @private
- 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.
- @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;
- },
- /**
- @private
- 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.
- @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 (!get(view, 'templateData')) {
- set(view, 'templateData', templateData);
- }
- });
- },
- currentView: null,
- _currentViewWillChange: Ember.beforeObserver(function() {
- var currentView = get(this, 'currentView');
- if (currentView) {
- currentView.destroy();
- }
- }, 'currentView'),
- _currentViewDidChange: Ember.observer(function() {
- var currentView = get(this, 'currentView');
- if (currentView) {
- this.pushObject(currentView);
- }
- }, '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 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;
- for (i = 0, len = childViews.length; i < len; i++) {
- childView = childViews[i];
- buffer = childView.renderToBufferIfNeeded();
- if (buffer) {
- childView.triggerRecursively('willInsertElement');
- if (previous) {
- previous.domManager.after(previous, buffer.string());
- } else {
- view.domManager.prepend(view, buffer.string());
- }
- childView.transitionTo('inDOM');
- childView.propertyDidChange('element');
- childView.triggerRecursively('didInsertElement');
- }
- previous = childView;
- }
- }
- });
- })();
- (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 maintaing 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
- anUndorderedListView = Ember.CollectionView.create({
- tagName: 'ul',
- content: ['A','B','C'],
- itemViewClass: Ember.View.extend({
- template: Ember.Handlebars.compile("the letter: {{view.content}}")
- })
- });
- anUndorderedListView.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'
- ```
- ## Programatic 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;
- }
- 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.
- ## Use in templates via the `{{collection}}` `Ember.Handlebars` helper
- `Ember.Handlebars` provides a helper specifically for adding
- `CollectionView`s to templates. See `Ember.Handlebars.collection` for more
- details
- @class CollectionView
- @namespace Ember
- @extends Ember.ContainerView
- @since Ember 0.9
- */
- Ember.CollectionView = Ember.ContainerView.extend(
- /** @scope Ember.CollectionView.prototype */ {
- /**
- A list of items to be displayed by the `Ember.CollectionView`.
- @property content
- @type Ember.Array
- @default null
- */
- content: null,
- /**
- @private
- This provides metadata about what kind of empty view class this
- collection would like if it is being instantiated from another
- system (like Handlebars)
- @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,
- init: function() {
- var ret = this._super();
- this._contentDidChange();
- return ret;
- },
- _contentWillChange: Ember.beforeObserver(function() {
- var content = this.get('content');
- if (content) { content.removeArrayObserver(this); }
- var len = content ? get(content, 'length') : 0;
- this.arrayWillChange(content, 0, len);
- }, 'content'),
- /**
- @private
- 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.
- @method _contentDidChange
- */
- _contentDidChange: Ember.observer(function() {
- var content = get(this, 'content');
- if (content) {
- Ember.assert(fmt("an Ember.CollectionView's content must implement Ember.Array. You passed %@", [content]), Ember.Array.detect(content));
- content.addArrayObserver(this);
- }
- var len = content ? get(content, 'length') : 0;
- this.arrayDidChange(content, 0, null, len);
- }, 'content'),
- willDestroy: function() {
- var content = get(this, 'content');
- if (content) { content.removeArrayObserver(this); }
- this._super();
- if (this._createdEmptyView) {
- this._createdEmptyView.destroy();
- }
- },
- 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);
- }
- for (idx = start + removedCount - 1; idx >= start; idx--) {
- childView = childViews[idx];
- if (removingAll) { childView.removedFromDOM = true; }
- 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} addedObjects the objects that were added to the content
- @param {Array} removedObjects the objects that were removed from the content
- @param {Number} changeIndex the index at which the changes occurred
- */
- arrayDidChange: function(content, start, removed, added) {
- var itemViewClass = get(this, 'itemViewClass'),
- addedViews = [], view, item, idx, len, itemTagName;
- if ('string' === typeof itemViewClass) {
- itemViewClass = get(itemViewClass);
- }
- Ember.assert(fmt("itemViewClass must be a subclass of Ember.View, not %@", [itemViewClass]), Ember.View.detect(itemViewClass));
- len = content ? get(content, 'length') : 0;
- if (len) {
- for (idx = start; idx < start+added; idx++) {
- item = content.objectAt(idx);
- view = this.createChildView(itemViewClass, {
- content: item,
- contentIndex: idx
- });
- addedViews.push(view);
- }
- } else {
- var emptyView = get(this, 'emptyView');
- if (!emptyView) { return; }
- var isClass = Ember.CoreView.detect(emptyView);
- emptyView = this.createChildView(emptyView);
- addedViews.push(emptyView);
- set(this, 'emptyView', emptyView);
- if (isClass) { this._createdEmptyView = emptyView; }
- }
- this.replace(start, 0, addedViews);
- },
- createChildView: function(view, attrs) {
- view = this._super(view, attrs);
- var itemTagName = get(view, 'tagName');
- var tagName = (itemTagName === null || itemTagName === undefined) ? Ember.CollectionView.CONTAINER_MAP[get(this, 'tagName')] : itemTagName;
- set(view, 'tagName', tagName);
- 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() {
- })();
- (function() {
- /*globals jQuery*/
- /**
- Ember Views
- @module ember
- @submodule ember-views
- @requires ember-runtime
- @main ember-views
- */
- })();
- (function() {
- define("metamorph",
- [],
- function() {
- "use strict";
- // ==========================================================================
- // Project: metamorph
- // Copyright: ©2011 My Company Inc. All rights reserved.
- // ==========================================================================
- var K = function(){},
- guid = 0,
- document = window.document,
- // Feature-detect the W3C range API, the extended check is for IE9 which only partially supports ranges
- supportsRange = ('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 = (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 = (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);
- };
- 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);
- // 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
- */
- // 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 = this.Handlebars || Ember.imports.Handlebars;
- Ember.assert("Ember Handlebars requires Handlebars 1.0.0-rc.3 or greater", Handlebars && Handlebars.VERSION.match(/^1\.0\.[0-9](\.rc\.[23456789]+)?/));
- /**
- 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);
- /**
- @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 "''";
- };
- /**
- @private
- 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.
- @method appendToBuffer
- @param string {String}
- */
- Ember.Handlebars.JavaScriptCompiler.prototype.appendToBuffer = function(string) {
- return "data.buffer.push("+string+");";
- };
- var prefix = "ember" + (+new Date()), incr = 1;
- /**
- @private
- 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.
- @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(['_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,
- bindAttr: 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);
- return Ember.Handlebars.template(templateSpec);
- };
- }
- })();
- (function() {
- var slice = Array.prototype.slice;
- /**
- @private
- If a path starts with a reserved keyword, returns the root
- that should be used.
- @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;
- // In cases where the path begins with a keyword, change the
- // root to the value represented by that keyword, and ensure
- // the path is relative to it.
- root = normalizedPath.root;
- path = normalizedPath.path;
- value = Ember.get(root, path);
- // If the path starts with a capital letter, look it up on Ember.lookup,
- // which defaults to the `window` object in browsers.
- if (value === undefined && root !== Ember.lookup && Ember.isGlobalPath(path)) {
- value = Ember.get(Ember.lookup, path);
- }
- return value;
- };
- Ember.Handlebars.getPath = Ember.deprecateFunc('`Ember.Handlebars.getPath` has been changed to `Ember.Handlebars.get` for consistency.', Ember.Handlebars.get);
- 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;
- };
- /**
- @private
- 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.
- @method helperMissing
- @for Ember.Handlebars.helpers
- @param {String} path
- @param {Hash} options
- */
- Ember.Handlebars.registerHelper('helperMissing', function(path, options) {
- var error, view = "";
- 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]));
- });
- /**
- 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 = arguments[arguments.length - 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.
- @method registerBoundHelper
- @for Ember.Handlebars
- @param {String} name
- @param {Function} function
- @param {String} dependentKeys*
- */
- Ember.Handlebars.registerBoundHelper = function(name, fn) {
- var dependentKeys = slice.call(arguments, 2);
- function helper() {
- var properties = slice.call(arguments, 0, -1),
- numProperties = properties.length,
- options = arguments[arguments.length - 1],
- normalizedProperties = [],
- data = options.data,
- hash = options.hash,
- view = data.view,
- currentContext = (options.contexts && options.contexts[0]) || this,
- normalized,
- pathRoot, path,
- loc, hashOption;
- // Detect bound options (e.g. countBinding="otherCount")
- hash.boundOptions = {};
- for (hashOption in hash) {
- if (!hash.hasOwnProperty(hashOption)) { continue; }
- if (Ember.IS_BINDING.test(hashOption) && typeof hash[hashOption] === 'string') {
- // Lop off 'Binding' suffix.
- hash.boundOptions[hashOption.slice(0, -7)] = hash[hashOption];
- }
- }
- // Expose property names on data.properties object.
- data.properties = [];
- for (loc = 0; loc < numProperties; ++loc) {
- data.properties.push(properties[loc]);
- normalizedProperties.push(normalizePath(currentContext, properties[loc], data));
- }
- if (data.isUnbound) {
- return evaluateUnboundHelper(this, fn, normalizedProperties, options);
- }
- if (dependentKeys.length === 0) {
- return evaluateMultiPropertyBoundHelper(currentContext, fn, normalizedProperties, options);
- }
- Ember.assert("Dependent keys can only be used with single-property helpers.", properties.length === 1);
- normalized = normalizedProperties[0];
- pathRoot = normalized.root;
- path = normalized.path;
- var bindView = new Ember._SimpleHandlebarsView(
- path, pathRoot, !options.hash.unescaped, options.data
- );
- bindView.normalizedValue = function() {
- var value = Ember._SimpleHandlebarsView.prototype.normalizedValue.call(bindView);
- return fn.call(view, value, options);
- };
- view.appendChild(bindView);
- view.registerObserver(pathRoot, path, bindView, rerenderBoundHelperView);
- for (var i=0, l=dependentKeys.length; i<l; i++) {
- view.registerObserver(pathRoot, path + '.' + dependentKeys[i], bindView, rerenderBoundHelperView);
- }
- }
- helper._rawFunction = fn;
- Ember.Handlebars.registerHelper(name, helper);
- };
- /**
- @private
- Renders the unbound form of an otherwise bound helper function.
- @param {Function} fn
- @param {Object} context
- @param {Array} normalizedProperties
- @param {String} options
- */
- function evaluateMultiPropertyBoundHelper(context, fn, normalizedProperties, options) {
- var numProperties = normalizedProperties.length,
- self = this,
- data = options.data,
- view = data.view,
- hash = options.hash,
- boundOptions = hash.boundOptions,
- watchedProperties,
- boundOption, bindView, loc, property, len;
- bindView = new Ember._SimpleHandlebarsView(null, null, !hash.unescaped, data);
- bindView.normalizedValue = function() {
- var args = [], value, boundOption;
- // Copy over bound options.
- for (boundOption in boundOptions) {
- if (!boundOptions.hasOwnProperty(boundOption)) { continue; }
- property = normalizePath(context, boundOptions[boundOption], data);
- bindView.path = property.path;
- bindView.pathRoot = property.root;
- hash[boundOption] = Ember._SimpleHandlebarsView.prototype.normalizedValue.call(bindView);
- }
- for (loc = 0; loc < numProperties; ++loc) {
- property = normalizedProperties[loc];
- bindView.path = property.path;
- bindView.pathRoot = property.root;
- args.push(Ember._SimpleHandlebarsView.prototype.normalizedValue.call(bindView));
- }
- args.push(options);
- return fn.apply(context, args);
- };
- view.appendChild(bindView);
- // Assemble liast of watched properties that'll re-render this helper.
- watchedProperties = [];
- for (boundOption in boundOptions) {
- if (boundOptions.hasOwnProperty(boundOption)) {
- watchedProperties.push(normalizePath(context, boundOptions[boundOption], data));
- }
- }
- watchedProperties = watchedProperties.concat(normalizedProperties);
- // Observe each property.
- for (loc = 0, len = watchedProperties.length; loc < len; ++loc) {
- property = watchedProperties[loc];
- view.registerObserver(property.root, property.path, bindView, rerenderBoundHelperView);
- }
- }
- /**
- @private
- An observer function used with bound helpers which
- will schedule a re-render of the _SimpleHandlebarsView
- connected with the helper.
- */
- function rerenderBoundHelperView() {
- Ember.run.scheduleOnce('render', this, 'rerender');
- }
- /**
- @private
- Renders the unbound form of an otherwise bound helper function.
- @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, loc, len, property, 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];
- args.push(Ember.Handlebars.get(context, property.path, options));
- }
- args.push(options);
- return fn.apply(context, args);
- }
- /**
- @private
- Overrides Handlebars.template so that we can distinguish
- user-created, top-level templates from inner contexts.
- @method template
- @for Ember.Handlebars
- @param {String} template spec
- */
- Ember.Handlebars.template = function(spec){
- var t = Handlebars.template(spec);
- t.isTop = true;
- return t;
- };
- })();
- (function() {
- /**
- @method htmlSafe
- @for Ember.String
- @static
- */
- Ember.String.htmlSafe = function(str) {
- return new Handlebars.SafeString(str);
- };
- var htmlSafe = Ember.String.htmlSafe;
- if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.String) {
- /**
- See {{#crossLink "Ember.String/htmlSafe"}}{{/crossLink}}
- @method htmlSafe
- @for String
- */
- 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');
- // DOMManager should just abstract dom manipulation between jquery and metamorph
- var DOMManager = {
- remove: function(view) {
- view.morph.remove();
- },
- prepend: function(view, html) {
- view.morph.prepend(html);
- },
- after: function(view, html) {
- view.morph.after(html);
- },
- html: function(view, html) {
- view.morph.html(html);
- },
- // This is messed up.
- replace: function(view) {
- var morph = view.morph;
- view.transitionTo('preRender');
- Ember.run.schedule('render', this, function() {
- if (view.isDestroying) { return; }
- view.clearRenderedChildren();
- var buffer = view.renderToBuffer();
- view.invokeRecursively(function(view) {
- view.propertyDidChange('element');
- });
- view.triggerRecursively('willInsertElement');
- morph.replaceWith(buffer.string());
- view.transitionTo('inDOM');
- view.triggerRecursively('didInsertElement');
- });
- },
- empty: function(view) {
- view.morph.html("");
- }
- };
- // The `morph` and `outerHTML` properties are internal only
- // and not observable.
- /**
- @class _Metamorph
- @namespace Ember
- @extends Ember.Mixin
- @private
- */
- Ember._Metamorph = Ember.Mixin.create({
- isVirtual: true,
- tagName: '',
- instrumentName: 'render.metamorph',
- init: function() {
- this._super();
- this.morph = Metamorph();
- },
- 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.View
- @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;
- }
- Ember._SimpleHandlebarsView = SimpleHandlebarsView;
- SimpleHandlebarsView.prototype = {
- isVirtual: true,
- isView: true,
- destroy: function () {
- if (this.updateId) {
- Ember.run.cancel(this.updateId);
- this.updateId = null;
- }
- this.morph = null;
- },
- 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 (get(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: 'render.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: Ember.computed(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;
- }).property('path', 'pathRoot', 'valueNormalizerFunc').volatile(),
- 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 inverseTemplate = get(this, 'inverseTemplate'),
- displayTemplate = get(this, 'displayTemplate');
- var result = get(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) {
- 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 forEach = Ember.ArrayPolyfills.forEach;
- var EmberHandlebars = Ember.Handlebars, helpers = EmberHandlebars.helpers;
- // 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(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
- });
- 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(handlebarsGet(currentContext, property, options));
- }
- }
- function simpleBind(property, options) {
- var data = options.data,
- view = data.view,
- currentContext = this,
- normalized, observer;
- 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 result = handlebarsGet(currentContext, property, options);
- if (result === null || result === undefined) { result = ""; }
- data.buffer.push(result);
- } 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.
- data.buffer.push(handlebarsGet(currentContext, property, options));
- }
- }
- /**
- @private
- '_triageMustache' is used internally select between a binding and helper 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.
- @method _triageMustache
- @for Ember.Handlebars.helpers
- @param {String} property Property/helperID to triage
- @param {Function} fn Context to provide for rendering
- @return {String} HTML string
- */
- EmberHandlebars.registerHelper('_triageMustache', function(property, fn) {
- Ember.assert("You cannot pass more than one argument to the _triageMustache helper", arguments.length <= 2);
- if (helpers[property]) {
- return helpers[property].call(this, fn);
- }
- else {
- return helpers.bind.apply(this, arguments);
- }
- });
- /**
- @private
- `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.
- @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(property, options) {
- Ember.assert("You cannot pass more than one argument to the bind helper", arguments.length <= 2);
- var context = (options.contexts && options.contexts[0]) || this;
- if (!options.fn) {
- return simpleBind.call(context, property, options);
- }
- return bind.call(context, property, options, false, function(result) {
- return !Ember.isNone(result);
- });
- });
- /**
- @private
- 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}}
- ```
- @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(property, fn) {
- var context = (fn.contexts && fn.contexts[0]) || this;
- var func = function(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;
- }
- };
- return bind.call(context, property, fn, true, func, func, ['isTruthy', 'length']);
- });
- /**
- @method with
- @for Ember.Handlebars.helpers
- @param {Function} context
- @param {Hash} options
- @return {String} HTML string
- */
- EmberHandlebars.registerHelper('with', function(context, options) {
- if (arguments.length === 4) {
- var keywordName, path, rootPath, normalized;
- 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);
- if (Ember.isGlobalPath(path)) {
- Ember.bind(options.data.keywords, keywordName, 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);
- options.data.keywords[contextKey] = rootPath;
- // if the path is '' ("this"), just bind directly to the current context
- var contextPath = path ? contextKey + '.' + path : contextKey;
- Ember.bind(options.data.keywords, keywordName, contextPath);
- }
- return bind.call(this, path, options, true, function(result) {
- return !Ember.isNone(result);
- });
- } 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`
- @method if
- @for Ember.Handlebars.helpers
- @param {Function} context
- @param {Hash} options
- @return {String} HTML string
- */
- EmberHandlebars.registerHelper('if', function(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);
- 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(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;
- return helpers.boundIf.call(options.contexts[0], context, options);
- });
- /**
- `bindAttr` allows you to create a binding between DOM element attributes and
- Ember objects. For example:
- ```handlebars
- <img {{bindAttr 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">
- ```
- `bindAttr` cannot redeclare existing DOM element attributes. The use of `src`
- in the following `bindAttr` example will be ignored and the hard coded value
- of `src="/failwhale.gif"` will take precedence:
- ```handlebars
- <img src="/failwhale.gif" {{bindAttr src="imageUrl" alt="imageTitle"}}>
- ```
- ### `bindAttr` and the `class` attribute
- `bindAttr` supports a special syntax for handling a number of cases unique
- to the `class` DOM element attribute. The `class` attribute combines
- multiple discreet 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 `bindAttr`. 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 {{bindAttr 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 {{bindAttr 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 {{bindAttr 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 {{bindAttr 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 {{bindAttr class=":class-name-to-always-apply view.someBool:class-name-if-true view.someProperty"}}>
- ```
- @method bindAttr
- @for Ember.Handlebars.helpers
- @param {Hash} options
- @return {String} HTML string
- */
- EmberHandlebars.registerHelper('bindAttr', function(options) {
- var attrs = options.hash;
- Ember.assert("You must specify at least one hash argument to bindAttr", !!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 && classBindings !== undefined) {
- 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 a String for a bound attribute, not %@", [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);
- };
- invoker = function() {
- Ember.run.scheduleOnce('render', observer);
- };
- // 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.
- if (path !== 'this') {
- view.registerObserver(normalized.root, normalized.path, invoker);
- }
- // 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(' '));
- });
- /**
- @private
- 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.
- @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;
- }
- }
- };
- invoker = function() {
- Ember.run.scheduleOnce('render', observer);
- };
- if (path !== '' && path !== 'this') {
- view.registerObserver(pathRoot, path, invoker);
- }
- // 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 PARENT_VIEW_PATH = /^parentView\./;
- var EmberHandlebars = Ember.Handlebars;
- EmberHandlebars.ViewHelper = Ember.Object.create({
- propertiesFromHTMLOptions: function(options, thisContext) {
- var hash = options.hash, data = options.data;
- var extensions = {},
- classes = hash['class'],
- dup = false;
- if (hash.id) {
- extensions.elementId = hash.id;
- 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['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') {
- return '_parentView.context';
- } else {
- return '_parentView.context.' + path;
- }
- },
- helper: function(thisContext, path, options) {
- var inverse = options.inverse,
- data = options.data,
- view = data.view,
- fn = options.fn,
- hash = options.hash,
- newView;
- if ('string' === typeof path) {
- 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 = options.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. Ember will search for the
- view class starting at the `Ember.View` of the template where `{{view}}` was
- used as the root object:
- ```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 "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(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() {
- /*globals Handlebars */
- // 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` 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
- 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(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;
- // If passed a path string, convert that into an object.
- // Otherwise, just default to the standard class.
- var collectionClass;
- collectionClass = path ? handlebarsGet(this, path, options) : Ember.CollectionView;
- Ember.assert(fmt("%@ #collection: Could not find collection class %@", [data.view, path]), !!collectionClass);
- var hash = options.hash, itemHash = {}, match;
- // Extract item view class if provided else default to the standard class
- var itemViewClass, itemViewPath = hash.itemViewClass;
- var collectionPrototype = collectionClass.proto();
- delete hash.itemViewClass;
- itemViewClass = itemViewPath ? handlebarsGet(collectionPrototype, itemViewPath, options) : collectionPrototype.itemViewClass;
- Ember.assert(fmt("%@ #collection: Could not find itemViewClass %@", [data.view, itemViewPath]), !!itemViewClass);
- // 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];
- }
- }
- }
- var tagName = hash.tagName || collectionPrototype.tagName;
- if (fn) {
- itemHash.template = fn;
- delete options.fn;
- }
- var emptyViewClass;
- if (inverse && inverse !== 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 viewString = view.toString();
- 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(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.helperMissing;
- out = helper.apply(this, Array.prototype.slice.call(arguments, 1));
- delete options.data.isUnbound;
- return out;
- }
- context = (fn.contexts && fn.contexts[0]) || this;
- return handlebarsGet(context, property, fn);
- });
- })();
- (function() {
- /*jshint debug:true*/
- /**
- @module ember
- @submodule ember-handlebars
- */
- var handlebarsGet = Ember.Handlebars.get, normalizePath = Ember.Handlebars.normalizePath;
- /**
- `log` allows you to output the value of a value in the current rendering
- context.
- ```handlebars
- {{log myVariable}}
- ```
- @method log
- @for Ember.Handlebars.helpers
- @param {String} property
- */
- Ember.Handlebars.registerHelper('log', function(property, options) {
- var context = (options.contexts && 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}}
- ```
- @method debugger
- @for Ember.Handlebars.helpers
- @param {String} property
- */
- Ember.Handlebars.registerHelper('debugger', function() {
- debugger;
- });
- })();
- (function() {
- /**
- @module ember
- @submodule ember-handlebars
- */
- var get = Ember.get, set = Ember.set;
- Ember.Handlebars.EachView = Ember.CollectionView.extend(Ember._Metamorph, {
- init: function() {
- var itemController = get(this, 'itemController');
- var binding;
- if (itemController) {
- var controller = Ember.ArrayController.create();
- set(controller, 'itemController', itemController);
- set(controller, 'container', get(this, 'controller.container'));
- set(controller, '_eachView', this);
- set(controller, 'target', get(this, 'controller'));
- 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();
- },
- disableContentObservers: function(callback) {
- Ember.removeBeforeObserver(this, 'content', null, '_contentWillChange');
- Ember.removeObserver(this, 'content', null, '_contentDidChange');
- callback.apply(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;
- },
- willDestroy: function() {
- var arrayController = get(this, '_arrayController');
- if (arrayController) {
- arrayController.destroy();
- }
- return this._super();
- }
- });
- 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() {
- this.content.addArrayObserver(this, {
- willChange: 'contentArrayWillChange',
- didChange: 'contentArrayDidChange'
- });
- },
- removeArrayObservers: function() {
- 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() {
- 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() {
- Ember.run.scheduleOnce('render', this.containingView, 'rerender');
- },
- destroy: function() {
- this.removeContentObservers();
- this.removeArrayObservers();
- }
- };
- /**
- 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>
- ```
- ### 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}}
- ```
- @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
- */
- Ember.Handlebars.registerHelper('each', function(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;
- }
- 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>
- ```
- 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>');
- ```
- @method template
- @for Ember.Handlebars.helpers
- @param {String} templateName the template to render
- */
- Ember.Handlebars.registerHelper('template', function(name, options) {
- var template = Ember.TEMPLATES[name];
- Ember.assert("Unable to find template with name '"+name+"'.", !!template);
- Ember.TEMPLATES[name](this, { data: options.data });
- });
- Ember.Handlebars.registerHelper('partial', function(name, options) {
- var nameParts = name.split("/"),
- lastPart = nameParts[nameParts.length - 1];
- nameParts[nameParts.length - 1] = "_" + lastPart;
- var underscoredName = nameParts.join("/");
- var template = Ember.TEMPLATES[underscoredName],
- deprecatedTemplate = Ember.TEMPLATES[name];
- Ember.deprecate("You tried to render the partial " + name + ", which should be at '" + underscoredName + "', but Ember found '" + name + "'. Please use a leading underscore in your partials", template);
- Ember.assert("Unable to find partial with name '"+name+"'.", template || deprecatedTemplate);
- template = template || deprecatedTemplate;
- template(this, { data: options.data });
- });
- })();
- (function() {
- /**
- @module ember
- @submodule ember-handlebars
- */
- var get = Ember.get, set = Ember.set;
- /**
- 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
- ```
- @method yield
- @for Ember.Handlebars.helpers
- @param {Hash} options
- @return {String} HTML string
- */
- Ember.Handlebars.registerHelper('yield', function(options) {
- var view = options.data.view, template;
- while (view && !get(view, 'layout')) {
- view = get(view, 'parentView');
- }
- Ember.assert("You called yield in a template that was not a layout", !!view);
- template = get(view, 'template');
- if (template) { template(this, options); }
- });
- })();
- (function() {
- })();
- (function() {
- })();
- (function() {
- /**
- @module ember
- @submodule ember-handlebars
- */
- var set = Ember.set, get = Ember.get;
- /**
- The `Ember.Checkbox` view class renders a checkbox
- [input](https://developer.mozilla.org/en/HTML/Element/Input) element. It
- allows for binding an Ember property (`checked`) to the status of the
- checkbox.
- Example:
- ```handlebars
- {{view Ember.Checkbox checkedBinding="receiveEmail"}}
- ```
- You can add a `label` tag yourself in the template where the `Ember.Checkbox`
- is being used.
- ```html
- <label>
- {{view Ember.Checkbox classNames="applicaton-specific-checkbox"}}
- Some Title
- </label>
- ```
- 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`'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', 'disabled', 'tabindex'],
- type: "checkbox",
- checked: false,
- disabled: false,
- init: function() {
- this._super();
- this.on("change", this, this._updateElementValue);
- },
- _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
- @extends Ember.Mixin
- @private
- */
- Ember.TextSupport = Ember.Mixin.create({
- value: "",
- attributeBindings: ['placeholder', 'disabled', 'maxlength', 'tabindex'],
- placeholder: null,
- disabled: false,
- maxlength: null,
- insertNewline: Ember.K,
- cancel: Ember.K,
- 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);
- },
- 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());
- }
- });
- Ember.TextSupport.KEY_EVENTS = {
- 13: 'insertNewline',
- 27: 'cancel'
- };
- })();
- (function() {
- /**
- @module ember
- @submodule ember-handlebars
- */
- var get = Ember.get, set = Ember.set;
- /**
- The `Ember.TextField` view class renders a text
- [input](https://developer.mozilla.org/en/HTML/Element/Input) element. It
- allows for binding Ember properties to the text field contents (`value`),
- live-updating as the user inputs text.
- Example:
- ```handlebars
- {{view Ember.TextField valueBinding="firstName"}}
- ```
- ## Layout and LayoutName properties
- Because HTML `input` elements are self closing `layout` and `layoutName`
- properties will not be applied. See `Ember.View`'s layout section for more
- information.
- ## HTML Attributes
- By default `Ember.TextField` provides support for `type`, `value`, `size`,
- `placeholder`, `disabled`, `maxlength` and `tabindex` attributes on a
- test field. If you need to support more attributes have a look at the
- `attributeBindings` property in `Ember.View`'s HTML Attributes section.
- To globally add support for additional attributes you can reopen
- `Ember.TextField` or `Ember.TextSupport`.
- ```javascript
- Ember.TextSupport.reopen({
- attributeBindings: ["required"]
- })
- ```
- @class TextField
- @namespace Ember
- @extends Ember.View
- @uses Ember.TextSupport
- */
- Ember.TextField = Ember.View.extend(Ember.TextSupport,
- /** @scope Ember.TextField.prototype */ {
- classNames: ['ember-text-field'],
- tagName: "input",
- attributeBindings: ['type', 'value', 'size', 'pattern'],
- /**
- 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` the pattern attribute of input element.
- @property pattern
- @type String
- @default null
- */
- pattern: null,
- /**
- 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,
- /**
- 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,
- insertNewline: function(event) {
- var controller = get(this, 'controller'),
- action = get(this, 'action');
- if (action) {
- controller.send(action, get(this, 'value'), this);
- if (!get(this, 'bubbles')) {
- event.stopPropagation();
- }
- }
- }
- });
- })();
- (function() {
- /**
- @module ember
- @submodule ember-handlebars
- */
- var get = Ember.get, set = Ember.set;
- /**
- @class Button
- @namespace Ember
- @extends Ember.View
- @uses Ember.TargetActionSupport
- @deprecated
- */
- Ember.Button = Ember.View.extend(Ember.TargetActionSupport, {
- classNames: ['ember-button'],
- classNameBindings: ['isActive'],
- tagName: 'button',
- propagateEvents: false,
- attributeBindings: ['type', 'disabled', 'href', 'tabindex'],
- /**
- @private
- Overrides `TargetActionSupport`'s `targetObject` computed
- property to use Handlebars-specific path resolution.
- @property targetObject
- */
- targetObject: Ember.computed(function() {
- var target = get(this, 'target'),
- root = get(this, 'context'),
- data = get(this, 'templateData');
- if (typeof target !== 'string') { return target; }
- return Ember.Handlebars.get(root, target, { data: data });
- }).property('target'),
- // Defaults to 'button' if tagName is 'input' or 'button'
- type: Ember.computed(function(key) {
- var tagName = this.tagName;
- if (tagName === 'input' || tagName === 'button') { return 'button'; }
- }),
- disabled: false,
- // Allow 'a' tags to act like buttons
- href: Ember.computed(function() {
- return this.tagName === 'a' ? '#' : null;
- }),
- mouseDown: function() {
- if (!get(this, 'disabled')) {
- set(this, 'isActive', true);
- this._mouseDown = true;
- this._mouseEntered = true;
- }
- return get(this, 'propagateEvents');
- },
- mouseLeave: function() {
- if (this._mouseDown) {
- set(this, 'isActive', false);
- this._mouseEntered = false;
- }
- },
- mouseEnter: function() {
- if (this._mouseDown) {
- set(this, 'isActive', true);
- this._mouseEntered = true;
- }
- },
- mouseUp: function(event) {
- if (get(this, 'isActive')) {
- // Actually invoke the button's target and action.
- // This method comes from the Ember.TargetActionSupport mixin.
- this.triggerAction();
- set(this, 'isActive', false);
- }
- this._mouseDown = false;
- this._mouseEntered = false;
- return get(this, 'propagateEvents');
- },
- keyDown: function(event) {
- // Handle space or enter
- if (event.keyCode === 13 || event.keyCode === 32) {
- this.mouseDown();
- }
- },
- keyUp: function(event) {
- // Handle space or enter
- if (event.keyCode === 13 || event.keyCode === 32) {
- this.mouseUp();
- }
- },
- // TODO: Handle proper touch behavior. Including should make inactive when
- // finger moves more than 20x outside of the edge of the button (vs mouse
- // which goes inactive as soon as mouse goes out of edges.)
- touchStart: function(touch) {
- return this.mouseDown(touch);
- },
- touchEnd: function(touch) {
- return this.mouseUp(touch);
- },
- init: function() {
- Ember.deprecate("Ember.Button is deprecated and will be removed from future releases. Consider using the `{{action}}` helper.");
- this._super();
- }
- });
- })();
- (function() {
- /**
- @module ember
- @submodule ember-handlebars
- */
- var get = Ember.get, set = Ember.set;
- /**
- The `Ember.TextArea` view class renders a
- [textarea](https://developer.mozilla.org/en/HTML/Element/textarea) element.
- It allows for binding Ember properties to the text area contents (`value`),
- live-updating as the user inputs text.
- ## 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`'s layout
- section for more information.
- ## HTML Attributes
- By default `Ember.TextArea` provides support for `rows`, `cols`,
- `placeholder`, `disabled`, `maxlength` and `tabindex` attributes on a
- textarea. If you need to support more attributes have a look at the
- `attributeBindings` property in `Ember.View`'s HTML Attributes section.
- To globally add support for additional attributes you can reopen
- `Ember.TextArea` or `Ember.TextSupport`.
- ```javascript
- Ember.TextSupport.reopen({
- attributeBindings: ["required"]
- })
- ```
- @class TextArea
- @namespace Ember
- @extends Ember.View
- @uses Ember.TextSupport
- */
- Ember.TextArea = Ember.View.extend(Ember.TextSupport, {
- classNames: ['ember-text-area'],
- tagName: "textarea",
- attributeBindings: ['rows', 'cols'],
- rows: null,
- cols: null,
- _updateElementValue: Ember.observer(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);
- }
- }, '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,
- replace = Ember.EnumerableUtils.replace,
- isArray = Ember.isArray,
- precompileTemplate = Ember.Handlebars.compile;
- /**
- 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.
- ### `content` as an 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.names = ["Yehuda", "Tom"];
- ```
- ```handlebars
- {{view Ember.Select contentBinding="App.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 directly or as a binding:
- ```javascript
- App.names = Ember.Object.create({
- selected: 'Tom',
- content: ["Yehuda", "Tom"]
- });
- ```
- ```handlebars
- {{view Ember.Select
- contentBinding="App.names.content"
- valueBinding="App.names.selected"
- }}
- ```
- 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 `App.names.selected` to "Yehuda".
- ### `content` as an 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.programmers = [
- Ember.Object.create({firstName: "Yehuda", id: 1}),
- Ember.Object.create({firstName: "Tom", id: 2})
- ];
- ```
- ```handlebars
- {{view Ember.Select
- contentBinding="App.programmers"
- optionValuePath="content.id"
- optionLabelPath="content.firstName"}}
- ```
- Would result in the following HTML:
- ```html
- <select class="ember-select">
- <option value>Please Select</option>
- <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 by providing a
- `valueBinding` option:
- ```javascript
- App.programmers = [
- Ember.Object.create({firstName: "Yehuda", id: 1}),
- Ember.Object.create({firstName: "Tom", id: 2})
- ];
- App.currentProgrammer = Ember.Object.create({
- id: 2
- });
- ```
- ```handlebars
- {{view Ember.Select
- contentBinding="App.programmers"
- optionValuePath="content.id"
- optionLabelPath="content.firstName"
- valueBinding="App.currentProgrammer.id"}}
- ```
- Would result in the following HTML with a selected option:
- ```html
- <select class="ember-select">
- <option value>Please Select</option>
- <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` value of `App.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 providing a `selectionBinding`. When the selected
- `<option>` is changed, the property path provided to `selectionBinding`
- will be updated to match the content object of the rendered `<option>`
- element:
- ```javascript
- App.controller = Ember.Object.create({
- selectedPerson: null,
- content: [
- Ember.Object.create({firstName: "Yehuda", id: 1}),
- Ember.Object.create({firstName: "Tom", id: 2})
- ]
- });
- ```
- ```handlebars
- {{view Ember.Select
- contentBinding="App.controller.content"
- optionValuePath="content.id"
- optionLabelPath="content.firstName"
- selectionBinding="App.controller.selectedPerson"}}
- ```
- Would result in the following HTML with a selected option:
- ```html
- <select class="ember-select">
- <option value>Please Select</option>
- <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` value of `App.controller`
- to match the content object of the newly selected `<option>`. In this
- case it is the first object in the `App.content.content`
- ### 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.controller = Ember.Object.create({
- selected: null,
- content: [
- "Yehuda",
- "Tom"
- ]
- });
- ```
- ``` handlebars
- {{view Ember.Select
- contentBinding="App.controller.content"
- valueBinding="App.controller.selected"
- }}
- ```
- Would result in the following HTML:
- ```html
- <select class="ember-select">
- <option value="Yehuda">Yehuda</option>
- <option value="Tom">Tom</option>
- </select>
- ```
- Although `App.controller.selected` 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.controller = Ember.Object.create({
- selected: null,
- content: [
- "Yehuda",
- "Tom"
- ]
- });
- ```
- ```handlebars
- {{view Ember.Select
- contentBinding="App.controller.content"
- valueBinding="App.controller.selected"
- 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(
- /** @scope Ember.Select.prototype */ {
- tagName: 'select',
- classNames: ['ember-select'],
- defaultTemplate: Ember.Handlebars.template(function anonymous(Handlebars,depth0,helpers,partials,data) {
- this.compilerInfo = [2,'>= 1.0.0-rc.3'];
- helpers = helpers || Ember.Handlebars.helpers; data = data || {};
- var buffer = '', stack1, hashTypes, escapeExpression=this.escapeExpression, self=this;
- function program1(depth0,data) {
- var buffer = '', hashTypes;
- data.buffer.push("<option value=\"\">");
- hashTypes = {};
- data.buffer.push(escapeExpression(helpers._triageMustache.call(depth0, "view.prompt", {hash:{},contexts:[depth0],types:["ID"],hashTypes:hashTypes,data:data})));
- data.buffer.push("</option>");
- return buffer;
- }
- function program3(depth0,data) {
- var hashTypes;
- hashTypes = {'contentBinding': "STRING"};
- data.buffer.push(escapeExpression(helpers.view.call(depth0, "Ember.SelectOption", {hash:{
- 'contentBinding': ("this")
- },contexts:[depth0],types:["ID"],hashTypes:hashTypes,data:data})));
- }
- hashTypes = {};
- stack1 = helpers['if'].call(depth0, "view.prompt", {hash:{},inverse:self.noop,fn:self.program(1, program1, data),contexts:[depth0],types:["ID"],hashTypes:hashTypes,data:data});
- if(stack1 || stack1 === 0) { data.buffer.push(stack1); }
- hashTypes = {};
- stack1 = helpers.each.call(depth0, "view.content", {hash:{},inverse:self.noop,fn:self.program(3, program3, data),contexts:[depth0],types:["ID"],hashTypes:hashTypes,data:data});
- if(stack1 || stack1 === 0) { data.buffer.push(stack1); }
- return buffer;
- }),
- attributeBindings: ['multiple', 'disabled', 'tabindex'],
- /**
- The `multiple` attribute of the select element. Indicates whether multiple
- options can be selected.
- @property multiple
- @type Boolean
- @default false
- */
- multiple: 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`.
- @property optionLabelPath
- @type String
- @default 'content'
- */
- optionLabelPath: 'content',
- /**
- The path of the option values. See `content`.
- @property optionValuePath
- @type String
- @default 'content'
- */
- optionValuePath: 'content',
- _change: function() {
- if (get(this, 'multiple')) {
- this._changeMultiple();
- } else {
- this._changeSingle();
- }
- },
- selectionDidChange: Ember.observer(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();
- }
- }, 'selection.@each'),
- valueDidChange: Ember.observer(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.find(function(obj) {
- return value === (valuePath ? get(obj, valuePath) : obj);
- });
- this.set('selection', selection);
- }
- }, 'value'),
- _triggerChange: function() {
- var selection = get(this, 'selection');
- var value = get(this, 'value');
- if (selection) { this.selectionDidChange(); }
- if (value) { this.valueDidChange(); }
- this._change();
- },
- _changeSingle: function() {
- var selectedIndex = this.$()[0].selectedIndex,
- content = get(this, 'content'),
- prompt = get(this, 'prompt');
- if (!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);
- }
- });
- 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').volatile(),
- labelPathDidChange: Ember.observer(function() {
- var labelPath = get(this, 'parentView.optionLabelPath');
- if (!labelPath) { return; }
- Ember.defineProperty(this, 'label', Ember.computed(function() {
- return get(this, labelPath);
- }).property(labelPath));
- }, 'parentView.optionLabelPath'),
- valuePathDidChange: Ember.observer(function() {
- var valuePath = get(this, 'parentView.optionValuePath');
- if (!valuePath) { return; }
- Ember.defineProperty(this, 'value', Ember.computed(function() {
- return get(this, valuePath);
- }).property(valuePath));
- }, 'parentView.optionValuePath')
- });
- })();
- (function() {
- })();
- (function() {
- /*globals Handlebars */
- /**
- @module ember
- @submodule ember-handlebars
- */
- /**
- @private
- 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.
- @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),
- type = script.attr('type');
- 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());
- // 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) );
- }
- /*
- 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('application', bootstrap);
- })();
- (function() {
- /**
- Ember Handlebars
- @module ember
- @submodule ember-handlebars
- @requires ember-views
- */
- Ember.runLoadHooks('Ember.Handlebars', Ember.Handlebars);
- })();
- (function() {
- define("route-recognizer",
- [],
- function() {
- "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, char;
- for (var i=0, l=string.length; i<l; i++) {
- char = string.charAt(i);
- callback({ validChars: char });
- }
- },
- 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(char) {
- // DEBUG "Processing `" + char + "`:"
- 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(char) !== -1) { returned.push(child); }
- } else if (typeof (chars = charSpec.invalidChars) !== 'undefined') {
- if (chars.indexOf(char) === -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 a.types.statics - b.types.statics; }
- return 0;
- });
- }
- function recognizeChar(states, char) {
- var nextStates = [];
- for (var i=0, l=states.length; i<l; i++) {
- var state = states[i];
- nextStates = nextStates.concat(state.match(char));
- }
- return nextStates;
- }
- function findHandler(state, path) {
- var handlers = state.handlers, regex = state.regex;
- var captures = path.match(regex), currentCapture = 1;
- var result = [];
- 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(char) {
- var state;
- currentState = currentState.put(char);
- });
- 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();
- }
- handlers.push({ handler: route.handler, names: names });
- }
- 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; }
- return output;
- },
- recognize: function(path) {
- var states = [ this.rootState ], i, l;
- // DEBUG GROUP path
- var pathLen = path.length;
- if (path.charAt(0) !== "/") { path = "/" + path; }
- if (pathLen > 1 && path.charAt(pathLen - 1) === "/") {
- path = path.substr(0, pathLen - 1);
- }
- 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) {
- return findHandler(state, path);
- }
- }
- };
- 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);
- }
- }
- };
- 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);
- routeArray.push({ path: path, handler: handler });
- }
- 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);
- };
- return RouteRecognizer;
- });
- })();
- (function() {
- define("router",
- ["route-recognizer"],
- function(RouteRecognizer) {
- "use strict";
- /**
- @private
- This file references several internal structures:
- ## `RecognizedHandler`
- * `{String} handler`: A handler name
- * `{Object} params`: A hash of recognized parameters
- ## `UnresolvedHandlerInfo`
- * `{Boolean} isDynamic`: whether a handler has any dynamic segments
- * `{String} name`: the name of a handler
- * `{Object} context`: the active context for the handler
- ## `HandlerInfo`
- * `{Boolean} isDynamic`: whether a handler has any dynamic segments
- * `{String} name`: the original unresolved handler name
- * `{Object} handler`: a handler object
- * `{Object} context`: the active context for the handler
- */
- function Router() {
- this.recognizer = new RouteRecognizer();
- }
- 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);
- },
- /**
- 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) {
- var results = this.recognizer.recognize(url),
- objects = [];
- if (!results) {
- throw new Error("No route matched the URL '" + url + "'");
- }
- collectObjects(this, results, 0, []);
- },
- /**
- Hook point for updating the URL.
- @param {String} url a URL to update to
- */
- updateURL: function() {
- throw "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) {
- var args = Array.prototype.slice.call(arguments, 1);
- doTransition(this, name, this.updateURL, args);
- },
- /**
- 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) {
- var args = Array.prototype.slice.call(arguments, 1);
- doTransition(this, name, this.replaceURL, args);
- },
- /**
- @private
- This method takes a handler name and a list of contexts and returns
- a serialized parameter hash suitable to pass to `recognizer.generate()`.
- @param {String} handlerName
- @param {Array[Object]} contexts
- @return {Object} a serialized parameter hash
- */
- paramsForHandler: function(handlerName, callback) {
- var output = this._paramsForHandler(handlerName, [].slice.call(arguments, 1));
- return output.params;
- },
- /**
- 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 params = this.paramsForHandler.apply(this, arguments);
- return this.recognizer.generate(handlerName, params);
- },
- /**
- @private
- Used internally by `generate` and `transitionTo`.
- */
- _paramsForHandler: function(handlerName, objects, doUpdate) {
- var handlers = this.recognizer.handlersFor(handlerName),
- params = {},
- toSetup = [],
- startIdx = handlers.length,
- objectsToMatch = objects.length,
- object, objectChanged, handlerObj, handler, names, i, len;
- // Find out which handler to start matching at
- for (i=handlers.length-1; i>=0 && objectsToMatch>0; i--) {
- if (handlers[i].names.length) {
- objectsToMatch--;
- startIdx = i;
- }
- }
- if (objectsToMatch > 0) {
- throw "More objects were passed than dynamic segments";
- }
- // Connect the objects to the routes
- for (i=0, len=handlers.length; i<len; i++) {
- handlerObj = handlers[i];
- handler = this.getHandler(handlerObj.handler);
- names = handlerObj.names;
- objectChanged = false;
- // If it's a dynamic segment
- if (names.length) {
- // If we have objects, use them
- if (i >= startIdx) {
- object = objects.shift();
- objectChanged = true;
- // Otherwise use existing context
- } else {
- object = handler.context;
- }
- // Serialize to generate params
- if (handler.serialize) {
- merge(params, handler.serialize(object, names));
- }
- // If it's not a dynamic segment and we're updating
- } else if (doUpdate) {
- // If we've passed the match point we need to deserialize again
- // or if we never had a context
- if (i > startIdx || !handler.hasOwnProperty('context')) {
- if (handler.deserialize) {
- object = handler.deserialize({});
- objectChanged = true;
- }
- // Otherwise use existing context
- } else {
- object = handler.context;
- }
- }
- // Make sure that we update the context here so it's available to
- // subsequent deserialize calls
- if (doUpdate && objectChanged) {
- // TODO: It's a bit awkward to set the context twice, see if we can DRY things up
- setContext(handler, object);
- }
- toSetup.push({
- isDynamic: !!handlerObj.names.length,
- handler: handlerObj.handler,
- name: handlerObj.name,
- context: object
- });
- }
- return { params: params, toSetup: toSetup };
- },
- isActive: function(handlerName) {
- var contexts = [].slice.call(arguments, 1);
- var currentHandlerInfos = this.currentHandlerInfos,
- found = false, names, object, handlerInfo, handlerObj;
- for (var i=currentHandlerInfos.length-1; i>=0; i--) {
- handlerInfo = currentHandlerInfos[i];
- if (handlerInfo.name === handlerName) { found = true; }
- if (found) {
- if (contexts.length === 0) { break; }
- if (handlerInfo.isDynamic) {
- object = contexts.pop();
- if (handlerInfo.context !== object) { return false; }
- }
- }
- }
- return contexts.length === 0 && found;
- },
- trigger: function(name) {
- var args = [].slice.call(arguments);
- trigger(this, args);
- }
- };
- function merge(hash, other) {
- for (var prop in other) {
- if (other.hasOwnProperty(prop)) { hash[prop] = other[prop]; }
- }
- }
- function isCurrent(currentHandlerInfos, handlerName) {
- return currentHandlerInfos[currentHandlerInfos.length - 1].name === handlerName;
- }
- /**
- @private
- This function is called the first time the `collectObjects`
- function encounters a promise while converting URL parameters
- into objects.
- It triggers the `enter` and `setup` methods on the `loading`
- handler.
- @param {Router} router
- */
- function loading(router) {
- if (!router.isLoading) {
- router.isLoading = true;
- var handler = router.getHandler('loading');
- if (handler) {
- if (handler.enter) { handler.enter(); }
- if (handler.setup) { handler.setup(); }
- }
- }
- }
- /**
- @private
- This function is called if a promise was previously
- encountered once all promises are resolved.
- It triggers the `exit` method on the `loading` handler.
- @param {Router} router
- */
- function loaded(router) {
- router.isLoading = false;
- var handler = router.getHandler('loading');
- if (handler && handler.exit) { handler.exit(); }
- }
- /**
- @private
- This function is called if any encountered promise
- is rejected.
- It triggers the `exit` method on the `loading` handler,
- the `enter` method on the `failure` handler, and the
- `setup` method on the `failure` handler with the
- `error`.
- @param {Router} router
- @param {Object} error the reason for the promise
- rejection, to pass into the failure handler's
- `setup` method.
- */
- function failure(router, error) {
- loaded(router);
- var handler = router.getHandler('failure');
- if (handler && handler.setup) { handler.setup(error); }
- }
- /**
- @private
- */
- function doTransition(router, name, method, args) {
- var output = router._paramsForHandler(name, args, true);
- var params = output.params, toSetup = output.toSetup;
- var url = router.recognizer.generate(name, params);
- method.call(router, url);
- setupContexts(router, toSetup);
- }
- /**
- @private
- This function is called after a URL change has been handled
- by `router.handleURL`.
- Takes an Array of `RecognizedHandler`s, and converts the raw
- params hashes into deserialized objects by calling deserialize
- on the handlers. This process builds up an Array of
- `HandlerInfo`s. It then calls `setupContexts` with the Array.
- If the `deserialize` method on a handler returns a promise
- (i.e. has a method called `then`), this function will pause
- building up the `HandlerInfo` Array until the promise is
- resolved. It will use the resolved value as the context of
- `HandlerInfo`.
- */
- function collectObjects(router, results, index, objects) {
- if (results.length === index) {
- loaded(router);
- setupContexts(router, objects);
- return;
- }
- var result = results[index];
- var handler = router.getHandler(result.handler);
- var object = handler.deserialize && handler.deserialize(result.params);
- if (object && typeof object.then === 'function') {
- loading(router);
- // The chained `then` means that we can also catch errors that happen in `proceed`
- object.then(proceed).then(null, function(error) {
- failure(router, error);
- });
- } else {
- proceed(object);
- }
- function proceed(value) {
- if (handler.context !== object) {
- setContext(handler, object);
- }
- var updatedObjects = objects.concat([{
- context: value,
- handler: result.handler,
- isDynamic: result.isDynamic
- }]);
- collectObjects(router, results, index + 1, updatedObjects);
- }
- }
- /**
- @private
- Takes an Array of `UnresolvedHandlerInfo`s, resolves the handler names
- into handlers, and then figures out what to do with each of the handlers.
- 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 `deserialize` callback 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} router
- @param {Array[UnresolvedHandlerInfo]} handlerInfos
- */
- function setupContexts(router, handlerInfos) {
- resolveHandlers(router, handlerInfos);
- var partition =
- partitionHandlers(router.currentHandlerInfos || [], handlerInfos);
- router.currentHandlerInfos = handlerInfos;
- eachHandler(partition.exited, function(handler, context) {
- delete handler.context;
- if (handler.exit) { handler.exit(); }
- });
- eachHandler(partition.updatedContext, function(handler, context) {
- setContext(handler, context);
- if (handler.setup) { handler.setup(context); }
- });
- var aborted = false;
- eachHandler(partition.entered, function(handler, context) {
- if (aborted) { return; }
- if (handler.enter) { handler.enter(); }
- setContext(handler, context);
- if (handler.setup) {
- if (false === handler.setup(context)) {
- aborted = true;
- }
- }
- });
- if (router.didTransition) {
- router.didTransition(handlerInfos);
- }
- }
- /**
- @private
- Iterates over an array of `HandlerInfo`s, passing the handler
- and context into the callback.
- @param {Array[HandlerInfo]} handlerInfos
- @param {Function(Object, Object)} callback
- */
- function eachHandler(handlerInfos, callback) {
- for (var i=0, l=handlerInfos.length; i<l; i++) {
- var handlerInfo = handlerInfos[i],
- handler = handlerInfo.handler,
- context = handlerInfo.context;
- callback(handler, context);
- }
- }
- /**
- @private
- Updates the `handler` field in each element in an Array of
- `UnresolvedHandlerInfo`s from a handler name to a resolved handler.
- When done, the Array will contain `HandlerInfo` structures.
- @param {Router} router
- @param {Array[UnresolvedHandlerInfo]} handlerInfos
- */
- function resolveHandlers(router, handlerInfos) {
- var handlerInfo;
- for (var i=0, l=handlerInfos.length; i<l; i++) {
- handlerInfo = handlerInfos[i];
- handlerInfo.name = handlerInfo.handler;
- handlerInfo.handler = router.getHandler(handlerInfo.handler);
- }
- }
- /**
- @private
- This function is called when transitioning from one URL to
- another to determine which handlers are not 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 three 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.
- @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(oldHandlers, newHandlers) {
- var handlers = {
- updatedContext: [],
- exited: [],
- entered: []
- };
- var handlerChanged, contextChanged, 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) {
- contextChanged = true;
- handlers.updatedContext.push(newHandler);
- }
- }
- for (i=newHandlers.length, l=oldHandlers.length; i<l; i++) {
- handlers.exited.unshift(oldHandlers[i]);
- }
- return handlers;
- }
- function trigger(router, args) {
- var currentHandlerInfos = router.currentHandlerInfos;
- var name = args.shift();
- if (!currentHandlerInfos) {
- throw new Error("Could not trigger event '" + name + "'. There are no active handlers");
- }
- for (var i=currentHandlerInfos.length-1; i>=0; i--) {
- var handlerInfo = currentHandlerInfos[i],
- handler = handlerInfo.handler;
- if (handler.events && handler.events[name]) {
- handler.events[name].apply(handler, args);
- return;
- }
- }
- throw new Error("Nothing handled the event '" + name + "'.");
- }
- function setContext(handler, context) {
- handler.context = context;
- if (handler.contextDidChange) { handler.contextDidChange(); }
- }
- return 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);
- callback.call(dsl);
- this.push(options.path, name, dsl.generate());
- } else {
- this.push(options.path, name);
- }
- },
- push: function(url, name, callback) {
- if (url === "" || url === "/") { this.explicitIndex = true; }
- this.matches.push([url, name, callback]);
- },
- route: function(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 (this.parent && this.parent !== 'application') {
- name = this.parent + "." + name;
- }
- this.push(options.path, name);
- },
- 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];
- match(dslMatch[0]).to(dslMatch[1], dslMatch[2]);
- }
- };
- }
- };
- DSL.map = function(callback) {
- var dsl = new DSL();
- callback.call(dsl);
- return dsl;
- };
- Ember.RouterDSL = DSL;
- })();
- (function() {
- /**
- @module ember
- @submodule ember-routing
- */
- Ember.controllerFor = function(container, controllerName, context) {
- return container.lookup('controller:' + controllerName) ||
- Ember.generateController(container, controllerName, context);
- };
- Ember.generateController = function(container, controllerName, context) {
- var controller;
- if (context && Ember.isArray(context)) {
- controller = Ember.ArrayController.extend({
- content: context
- });
- } else if (context) {
- controller = Ember.ObjectController.extend({
- content: context
- });
- } else {
- controller = Ember.Controller.extend();
- }
- controller.toString = function() {
- return "(generated " + controllerName + " controller)";
- };
- container.register('controller', controllerName, controller);
- return container.lookup('controller:' + controllerName);
- };
- })();
- (function() {
- /**
- @module ember
- @submodule ember-routing
- */
- var Router = requireModule("router");
- var get = Ember.get, set = Ember.set, classify = Ember.String.classify;
- var DefaultView = Ember._MetamorphView;
- function setupLocation(router) {
- var location = get(router, 'location'),
- rootURL = get(router, 'rootURL');
- if ('string' === typeof location) {
- location = set(router, 'location', Ember.Location.create({
- implementation: location
- }));
- if (typeof rootURL === 'string') {
- set(location, 'rootURL', rootURL);
- }
- }
- }
- /**
- 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({
- location: 'hash',
- init: function() {
- this.router = this.constructor.router;
- this._activeViews = {};
- setupLocation(this);
- },
- url: Ember.computed(function() {
- return get(this, 'location').getURL();
- }),
- startRouting: function() {
- this.router = this.router || this.constructor.map(Ember.K);
- var router = this.router,
- location = get(this, 'location'),
- container = this.container,
- self = this;
- setupRouter(this, router, location);
- container.register('view', 'default', DefaultView);
- container.register('view', 'toplevel', Ember.View.extend());
- location.onUpdateURL(function(url) {
- self.handleURL(url);
- });
- this.handleURL(location.getURL());
- },
- didTransition: function(infos) {
- // Don't do any further action here if we redirected
- for (var i=0, l=infos.length; i<l; i++) {
- if (infos[i].handler.redirected) { return; }
- }
- var appController = this.container.lookup('controller:application'),
- path = routePath(infos);
- set(appController, 'currentPath', path);
- this.notifyPropertyChange('url');
- if (get(this, 'namespace').LOG_TRANSITIONS) {
- Ember.Logger.log("Transitioned into '" + path + "'");
- }
- },
- handleURL: function(url) {
- this.router.handleURL(url);
- this.notifyPropertyChange('url');
- },
- transitionTo: function(name) {
- var args = [].slice.call(arguments);
- doTransition(this, 'transitionTo', args);
- },
- replaceWith: function() {
- var args = [].slice.call(arguments);
- doTransition(this, 'replaceWith', args);
- },
- generate: function() {
- var url = this.router.generate.apply(this.router, arguments);
- return this.location.formatURL(url);
- },
- isActive: function(routeName) {
- var router = this.router;
- return router.isActive.apply(router, arguments);
- },
- send: function(name, context) {
- this.router.trigger.apply(this.router, arguments);
- },
- hasRoute: function(route) {
- return this.router.hasRoute(route);
- },
- _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);
- }
- });
- Ember.Router.reopenClass({
- defaultFailureHandler: {
- setup: function(error) {
- Ember.Logger.error('Error while loading route:', error);
- // Using setTimeout allows us to escape from the Promise's try/catch block
- setTimeout(function() { throw error; });
- }
- }
- });
- function getHandlerFunction(router) {
- var seen = {}, container = router.container;
- return function(name) {
- var handler = container.lookup('route:' + name);
- if (seen[name]) { return handler; }
- seen[name] = true;
- if (!handler) {
- if (name === 'loading') { return {}; }
- if (name === 'failure') { return router.constructor.defaultFailureHandler; }
- container.register('route', name, Ember.Route.extend());
- handler = container.lookup('route:' + name);
- }
- handler.routeName = name;
- return handler;
- };
- }
- function handlerIsActive(router, handlerName) {
- var handler = router.container.lookup('route:' + handlerName),
- currentHandlerInfos = router.router.currentHandlerInfos,
- handlerInfo;
- for (var i=0, l=currentHandlerInfos.length; i<l; i++) {
- handlerInfo = currentHandlerInfos[i];
- if (handlerInfo.handler === handler) { return true; }
- }
- return false;
- }
- function routePath(handlerInfos) {
- var path = [];
- for (var i=1, l=handlerInfos.length; i<l; i++) {
- var name = handlerInfos[i].name,
- nameParts = name.split(".");
- path.push(nameParts[nameParts.length - 1]);
- }
- return path.join(".");
- }
- function setupRouter(emberRouter, router, location) {
- var lastURL;
- router.getHandler = getHandlerFunction(emberRouter);
- 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);
- };
- }
- function doTransition(router, method, args) {
- var passedName = args[0], name;
- if (!router.router.hasRoute(args[0])) {
- name = args[0] = passedName + '.index';
- } else {
- name = passedName;
- }
- Ember.assert("The route " + passedName + " was not found", router.router.hasRoute(name));
- router.router[method].apply(router.router, args);
- router.notifyPropertyChange('url');
- }
- Ember.Router.reopenClass({
- map: function(callback) {
- var router = this.router = new Router();
- var dsl = Ember.RouterDSL.map(function() {
- this.resource('application', { path: "/" }, function() {
- callback.call(this);
- });
- });
- router.map(dsl.generate());
- return router;
- }
- });
- })();
- (function() {
- /**
- @module ember
- @submodule ember-routing
- */
- var get = Ember.get, set = Ember.set,
- classify = Ember.String.classify,
- decamelize = Ember.String.decamelize;
- /**
- 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
- */
- Ember.Route = Ember.Object.extend({
- /**
- @private
- @method exit
- */
- exit: function() {
- this.deactivate();
- teardownView(this);
- },
- /**
- @private
- @method enter
- */
- enter: function() {
- this.activate();
- },
- /**
- 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.
- Events can also be invoked from other parts of your application via `Route#send`.
- The context of event will be the this route.
- @see {Ember.Route#send}
- @see {Handlebars.helpers.action}
- @property events
- @type Hash
- @default null
- */
- events: null,
- /**
- 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 for the first time.
- It is not executed when the model for the route changes.
- @method activate
- */
- activate: Ember.K,
- /**
- Transition into another route. Optionally supply a model for the
- route in question. The model will be serialized into the URL
- using the `serialize` hook.
- @method transitionTo
- @param {String} name the name of the route
- @param {...Object} models the
- */
- transitionTo: function() {
- if (this._checkingRedirect) { this.redirected = true; }
- return this.router.transitionTo.apply(this.router, arguments);
- },
- /**
- Transition into another route while replacing the current URL if
- possible. Identical to `transitionTo` in all other respects.
- @method replaceWith
- @param {String} name the name of the route
- @param {...Object} models the
- */
- replaceWith: function() {
- if (this._checkingRedirect) { this.redirected = true; }
- return this.router.replaceWith.apply(this.router, arguments);
- },
- send: function() {
- return this.router.send.apply(this.router, arguments);
- },
- /**
- @private
- This hook is the entry point for router.js
- @method setup
- */
- setup: function(context) {
- this.redirected = false;
- this._checkingRedirect = true;
- this.redirect(context);
- this._checkingRedirect = false;
- if (this.redirected) { return false; }
- var controller = this.controllerFor(this.routeName, context);
- if (controller) {
- this.controller = controller;
- set(controller, 'model', context);
- }
- 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);
- }
- },
- /**
- 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.
- @method redirect
- @param {Object} model the model for this route
- */
- redirect: Ember.K,
- /**
- @private
- The hook called by `router.js` to convert parameters into the context
- for this handler. The public Ember hook is `model`.
- @method deserialize
- */
- deserialize: function(params) {
- var model = this.model(params);
- return this.currentModel = model;
- },
- /**
- @private
- Called when the context is changed by router.js.
- */
- contextDidChange: function() {
- this.currentModel = this.context;
- },
- /**
- A hook you can implement to convert the URL into the model for
- this route.
- ```js
- App.Route.map(function(match) {
- match("/posts/:post_id").to("post");
- });
- ```
- 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.
- @method model
- @param {Object} params the parameters extracted from the URL
- */
- model: function(params) {
- var match, name, sawParams, value;
- for (var prop in params) {
- if (match = prop.match(/^(.*)_id$/)) {
- name = match[1];
- value = params[prop];
- }
- sawParams = true;
- }
- if (!name && sawParams) { return params; }
- else if (!name) { return; }
- var className = classify(name),
- namespace = this.router.namespace,
- modelClass = namespace[className];
- Ember.assert("You used the dynamic segment " + name + "_id in your router, but " + namespace + "." + className + " did not exist and you did not override your state's `model` hook.", modelClass);
- return modelClass.find(value);
- },
- /**
- A hook you can implement to convert the route's model into parameters
- for the URL.
- ```js
- App.Route.map(function(match) {
- match("/posts/:post_id").to("post");
- });
- 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 inserts the model's `id` into the
- route's dynamic segment (in this case, `:post_id`).
- 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; }
- var name = params[0], object = {};
- if (/_id$/.test(name)) {
- object[name] = get(model, 'id');
- } else {
- object[name] = model;
- }
- 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.
- ```js
- App.Route.map(function(match) {
- match("/posts/:post_id").to("post");
- });
- ```
- For the `post` route, the controller is `App.PostController`.
- By default, the `setupController` hook sets the `content` property of
- the controller to the `model`.
- If no explicit controller is defined, the route 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`.
- 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.
- @method setupController
- */
- setupController: Ember.K,
- /**
- Returns the controller for a particular route.
- ```js
- App.PostRoute = Ember.Route.extend({
- setupController: function(controller, post) {
- this._super(controller, post);
- this.controllerFor('posts').set('currentPost', post);
- }
- });
- ```
- By default, the controller for `post` is the shared instance of
- `App.PostController`.
- @method controllerFor
- @param {String} name the name of the route
- @param {Object} model the model associated with the route (optional)
- @return {Ember.Controller}
- */
- controllerFor: function(name, model) {
- var container = this.router.container,
- controller = container.lookup('controller:' + name);
- if (!controller) {
- model = model || this.modelFor(name);
- Ember.assert("You are trying to look up a controller that you did not define, and for which Ember does not know the model.\n\nThis is not a controller for a route, so you must explicitly define the controller ("+this.router.namespace.toString() + "." + Ember.String.capitalize(Ember.String.camelize(name))+"Controller) or pass a model as the second parameter to `controllerFor`, so that Ember knows which type of controller to create for you.", model || this.container.lookup('route:' + name));
- controller = Ember.generateController(container, name, model);
- }
- return controller;
- },
- /**
- Returns the current model for a given route.
- This is the object returned by the `model` hook of the route
- in question.
- @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);
- 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.
- @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(match) {
- match("/").to("index");
- match("/posts/:post_id").to("post");
- });
- 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) {
- if (typeof name === 'object' && !options) {
- options = name;
- name = this.routeName;
- }
- name = name ? name.replace(/\//g, '.') : this.routeName;
- var container = this.container,
- view = container.lookup('view:' + name),
- template = container.lookup('template:' + name);
- if (!view && !template) { return; }
- options = normalizeOptions(this, name, template, options);
- view = setupView(view, container, options);
- if (options.outlet === 'main') { this.lastRenderedTemplate = name; }
- appendView(this, view, options);
- },
- willDestroy: function() {
- teardownView(this);
- }
- });
- function parentRoute(route) {
- var handlerInfos = route.router.router.currentHandlerInfos;
- 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, isRecursive) {
- var parent = parentRoute(route), template;
- if (!parent) { return; }
- Ember.warn("The immediate parent route did not render into the main outlet and the default 'into' option may not be expected", !isRecursive);
- if (template = parent.lastRenderedTemplate) {
- return template;
- } else {
- return parentTemplate(parent, true);
- }
- }
- 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;
- Ember.assert("An outlet ("+options.outlet+") was specified but this view will render at the root level.", 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.routeName;
- }
- if (typeof controller === 'string') {
- controller = route.container.lookup('controller:' + controller);
- }
- options.controller = controller;
- return options;
- }
- function setupView(view, container, options) {
- var defaultView = options.into ? 'view:default' : 'view:toplevel';
- view = view || container.lookup(defaultView);
- 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);
- route.teardownView = teardownOutlet(parentView, options.outlet);
- parentView.connectOutlet(options.outlet, view);
- } else {
- var rootElement = get(route, 'router.namespace.rootElement');
- route.router._connectActiveView(options.name, view);
- route.teardownView = teardownTopLevel(view);
- view.appendTo(rootElement);
- }
- }
- function teardownTopLevel(view) {
- return function() { view.destroy(); };
- }
- function teardownOutlet(parentView, outlet) {
- return function() { parentView.disconnectOutlet(outlet); };
- }
- function teardownView(route) {
- if (route.teardownView) { route.teardownView(); }
- delete route.teardownView;
- delete route.lastRenderedTemplate;
- }
- })();
- (function() {
- })();
- (function() {
- /**
- @module ember
- @submodule ember-routing
- */
- var get = Ember.get, set = Ember.set;
- Ember.onLoad('Ember.Handlebars', function(Handlebars) {
- var resolveParams = Ember.Handlebars.resolveParams,
- isSimpleClick = Ember.ViewUtils.isSimpleClick;
- function fullRouteName(router, name) {
- if (!router.hasRoute(name)) {
- name = name + '.index';
- }
- return name;
- }
- function resolvedPaths(options) {
- var types = options.options.types.slice(1),
- data = options.options.data;
- return resolveParams(options.context, options.params, { types: types, data: data });
- }
- function args(linkView, router, route) {
- var passedRouteName = route || linkView.namedRoute, routeName;
- routeName = fullRouteName(router, passedRouteName);
- Ember.assert("The route " + passedRouteName + " was not found", router.hasRoute(routeName));
- var ret = [ routeName ];
- return ret.concat(resolvedPaths(linkView.parameters));
- }
- var LinkView = Ember.View.extend({
- tagName: 'a',
- namedRoute: null,
- currentWhen: null,
- title: null,
- activeClass: 'active',
- replace: false,
- attributeBindings: ['href', 'title'],
- classNameBindings: 'active',
- // 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}}
- concreteView: Ember.computed(function() {
- return get(this, 'parentView');
- }).property('parentView').volatile(),
- active: Ember.computed(function() {
- var router = this.get('router'),
- params = resolvedPaths(this.parameters),
- currentWithIndex = this.currentWhen + '.index',
- isActive = router.isActive.apply(router, [this.currentWhen].concat(params)) ||
- router.isActive.apply(router, [currentWithIndex].concat(params));
- if (isActive) { return get(this, 'activeClass'); }
- }).property('namedRoute', 'router.url'),
- router: Ember.computed(function() {
- return this.get('controller').container.lookup('router:main');
- }),
- click: function(event) {
- if (!isSimpleClick(event)) { return true; }
- event.preventDefault();
- if (this.bubbles === false) { event.stopPropagation(); }
- var router = this.get('router');
- if (this.get('replace')) {
- router.replaceWith.apply(router, args(this, router));
- } else {
- router.transitionTo.apply(router, args(this, router));
- }
- },
- href: Ember.computed(function() {
- var router = this.get('router');
- return router.generate.apply(router, args(this, router));
- })
- });
- LinkView.toString = function() { return "LinkView"; };
- /**
- @method linkTo
- @for Ember.Handlebars.helpers
- @param {String} routeName
- @param {Object} [context]*
- @return {String} HTML string
- */
- Ember.Handlebars.registerHelper('linkTo', function(name) {
- var options = [].slice.call(arguments, -1)[0];
- var params = [].slice.call(arguments, 1, -1);
- var hash = options.hash;
- hash.namedRoute = name;
- hash.currentWhen = hash.currentWhen || name;
- hash.parameters = {
- context: this,
- options: options,
- params: params
- };
- return Ember.Handlebars.helpers.view.call(this, LinkView, options);
- });
- });
- })();
- (function() {
- /**
- @module ember
- @submodule ember-routing
- */
- var get = Ember.get, set = Ember.set;
- Ember.onLoad('Ember.Handlebars', function(Handlebars) {
- /**
- @module ember
- @submodule ember-handlebars
- */
- Handlebars.OutletView = Ember.ContainerView.extend(Ember._Metamorph);
- /**
- The `outlet` helper allows you to specify that the current
- view's controller will fill in the view for a given area.
- ``` handlebars
- {{outlet}}
- ```
- By default, when the the current controller's `view` property changes, the
- outlet will replace its current view with the new view. You can set the
- `view` property directly, but it's normally best to use `connectOutlet`.
- ``` javascript
- # Instantiate App.PostsView and assign to `view`, so as to render into outlet.
- controller.connectOutlet('posts');
- ```
- You can also specify a particular name other than `view`:
- ``` handlebars
- {{outlet masterView}}
- {{outlet detailView}}
- ```
- Then, you can control several outlets from a single controller.
- ``` javascript
- # Instantiate App.PostsView and assign to controller.masterView.
- controller.connectOutlet('masterView', 'posts');
- # Also, instantiate App.PostInfoView and assign to controller.detailView.
- controller.connectOutlet('detailView', 'postInfo');
- ```
- @method outlet
- @for Ember.Handlebars.helpers
- @param {String} property the property on the controller
- that holds the view for this outlet
- */
- Handlebars.registerHelper('outlet', function(property, options) {
- var outletSource;
- if (property && property.data && property.data.isRenderData) {
- options = property;
- property = 'main';
- }
- outletSource = options.data.view;
- while (!(outletSource.get('template.isTop'))){
- outletSource = outletSource.get('_parentView');
- }
- options.data.view.set('outletSource', outletSource);
- options.hash.currentViewBinding = '_view.outletSource._outlets.' + property;
- return Handlebars.helpers.view.call(this, Handlebars.OutletView, options);
- });
- });
- })();
- (function() {
- /**
- @module ember
- @submodule ember-routing
- */
- var get = Ember.get, set = Ember.set;
- Ember.onLoad('Ember.Handlebars', function(Handlebars) {
- /**
- Renders the named template in the current context using the singleton
- instance of the same-named controller.
- If a view class with the same name exists, uses the view class.
- If a `model` is specified, it becomes the model for that controller.
- The default target for `{{action}}`s in the rendered template is the
- named controller.
- @method action
- @for Ember.Handlebars.helpers
- @param {String} actionName
- @param {Object?} model
- @param {Hash} options
- */
- Ember.Handlebars.registerHelper('render', function(name, contextString, options) {
- Ember.assert("You must pass a template to render", arguments.length >= 2);
- var container, router, controller, view, context;
- if (arguments.length === 2) {
- options = contextString;
- contextString = undefined;
- }
- if (typeof contextString === 'string') {
- context = Ember.Handlebars.get(options.contexts[1], contextString, options);
- }
- name = name.replace(/\//g, '.');
- container = options.data.keywords.controller.container;
- router = container.lookup('router:main');
- Ember.assert("This view is already rendered", !router || !router._lookupActiveView(name));
- view = container.lookup('view:' + name) || container.lookup('view:default');
- if (controller = options.hash.controller) {
- controller = container.lookup('controller:' + controller);
- } else {
- controller = Ember.controllerFor(container, name, context);
- }
- if (controller && context) {
- controller.set('model', context);
- }
- var root = options.contexts[1];
- if (root) {
- view.registerObserver(root, contextString, function() {
- controller.set('model', Ember.Handlebars.get(root, contextString, options));
- });
- }
- controller.set('target', options.data.keywords.controller);
- options.hash.viewName = Ember.String.camelize(name);
- options.hash.template = container.lookup('template:' + name);
- options.hash.controller = controller;
- if (router) {
- 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.Handlebars.resolveParams,
- isSimpleClick = Ember.ViewUtils.isSimpleClick;
- var EmberHandlebars = Ember.Handlebars,
- handlebarsGet = EmberHandlebars.get,
- SafeString = EmberHandlebars.SafeString,
- 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: {}
- };
- ActionHelper.registerAction = function(actionName, options) {
- var actionId = (++Ember.uuid).toString();
- ActionHelper.registeredActions[actionId] = {
- eventName: options.eventName,
- handler: function(event) {
- if (!isSimpleClick(event)) { return true; }
- event.preventDefault();
- if (options.bubbles === false) {
- event.stopPropagation();
- }
- var view = options.view,
- contexts = options.contexts,
- target = options.target;
- if (target.target) {
- target = handlebarsGet(target.root, target.target, target.options);
- } else {
- target = target.root;
- }
- Ember.run(function() {
- 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 view's controller
- or supplied `target` option (see 'Specifying a Target').
- If the view's 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 Handlebars template on the page
- ```handlebars
- <script type="text/x-handlebars" data-template-name='a-template'>
- <div {{action anActionName}}>
- click me
- </div>
- </script>
- ```
- And application code
- ```javascript
- AController = Ember.Controller.extend({
- anActionName: function() {}
- });
- AView = Ember.View.extend({
- controller: AController.create(),
- templateName: 'a-template'
- });
- aView = AView.create();
- aView.appendTo('body');
- ```
- Will results 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` method of the
- `AController`. 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.
- To also 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`
- '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
- <script type="text/x-handlebars" data-template-name='a-template'>
- <div {{action anActionName on="doubleClick"}}>
- click me
- </div>
- </script>
- ```
- 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 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
- path to an object, accessible in the current context:
- ```handlebars
- <script type="text/x-handlebars" data-template-name='a-template'>
- <div {{action anActionName target="MyApplication.someObject"}}>
- click me
- </div>
- </script>
- ```
- Clicking "click me" in the rendered HTML of the above template will trigger
- the `anActionName` method of the object at `MyApplication.someObject`.
- If an action's target does not implement a method that matches the supplied
- action name an error will be thrown.
- ```handlebars
- <script type="text/x-handlebars" data-template-name='a-template'>
- <div {{action aMethodNameThatIsMissing}}>
- click me
- </div>
- </script>
- ```
- With the following application code
- ```javascript
- AView = Ember.View.extend({
- templateName; 'a-template',
- // note: no method 'aMethodNameThatIsMissing'
- anActionName: function(event) {}
- });
- aView = AView.create();
- aView.appendTo('body');
- ```
- Will throw `Uncaught TypeError: Cannot call method 'call' of undefined` when
- "click me" is clicked.
- ### 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
- <script type="text/x-handlebars" data-template-name='a-template'>
- {{#each person in people}}
- <div {{action edit person}}>
- click me
- </div>
- {{/each}}
- </script>
- ```
- Clicking "click me" will trigger the `edit` method on the current view's
- controller with the current person as a parameter.
- @method action
- @for Ember.Handlebars.helpers
- @param {String} actionName
- @param {Object} [context]*
- @param {Hash} options
- */
- EmberHandlebars.registerHelper('action', function(actionName) {
- var options = arguments[arguments.length - 1],
- contexts = a_slice.call(arguments, 1, -1);
- var hash = options.hash,
- view = options.data.view,
- controller, link;
- // create a hash to pass along to registerAction
- var action = {
- eventName: hash.on || "click"
- };
- action.parameters = {
- context: this,
- options: options,
- params: contexts
- };
- action.view = view = get(view, 'concreteView');
- 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;
- var actionId = ActionHelper.registerAction(actionName, action);
- return new SafeString('data-ember-action="' + actionId + '"');
- });
- });
- })();
- (function() {
- /**
- @module ember
- @submodule ember-routing
- */
- if (Ember.ENV.EXPERIMENTAL_CONTROL_HELPER) {
- var get = Ember.get, set = Ember.set;
- /**
- The control helper is currently under development and is considered experimental.
- To enable it, set `ENV.EXPERIMENTAL_CONTROL_HELPER = true` before requiring Ember.
- @method control
- @for Ember.Handlebars.helpers
- @param {String} path
- @param {String} modelPath
- @param {Hash} options
- @return {String} HTML string
- */
- Ember.Handlebars.registerHelper('control', function(path, modelPath, options) {
- if (arguments.length === 2) {
- options = modelPath;
- modelPath = undefined;
- }
- var model;
- if (modelPath) {
- model = Ember.Handlebars.get(this, modelPath, options);
- }
- var controller = options.data.keywords.controller,
- view = options.data.keywords.view,
- children = get(controller, '_childContainers'),
- controlID = options.hash.controlID,
- container, subContainer;
- if (children.hasOwnProperty(controlID)) {
- subContainer = children[controlID];
- } else {
- container = get(controller, 'container'),
- subContainer = container.child();
- children[controlID] = subContainer;
- }
- var normalizedPath = path.replace(/\//g, '.');
- var childView = subContainer.lookup('view:' + normalizedPath) || subContainer.lookup('view:default'),
- childController = subContainer.lookup('controller:' + normalizedPath),
- childTemplate = subContainer.lookup('template:' + path);
- Ember.assert("Could not find controller for path: " + normalizedPath, childController);
- Ember.assert("Could not find view for path: " + normalizedPath, childView);
- set(childController, 'target', controller);
- set(childController, 'model', model);
- options.hash.template = childTemplate;
- options.hash.controller = childController;
- function observer() {
- var model = Ember.Handlebars.get(this, modelPath, options);
- set(childController, 'model', model);
- childView.rerender();
- }
- Ember.addObserver(this, modelPath, observer);
- childView.one('willDestroyElement', this, function() {
- Ember.removeObserver(this, modelPath, observer);
- });
- Ember.Handlebars.helpers.view.call(this, childView, options);
- });
- }
- })();
- (function() {
- })();
- (function() {
- /**
- @module ember
- @submodule ember-routing
- */
- var get = Ember.get, set = Ember.set;
- Ember.ControllerMixin.reopen({
- 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);
- },
- transitionTo: function() {
- Ember.deprecate("transitionTo is deprecated. Please use transitionToRoute.");
- return this.transitionToRoute.apply(this, arguments);
- },
- 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);
- },
- 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({
- init: function() {
- set(this, '_outlets', {});
- this._super();
- },
- connectOutlet: function(outletName, view) {
- 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);
- }
- },
- disconnectOutlet: function(outletName) {
- var outlets = get(this, '_outlets');
- set(outlets, outletName, null);
- }
- });
- })();
- (function() {
- })();
- (function() {
- /**
- @module ember
- @submodule ember-routing
- */
- var get = Ember.get, set = Ember.set;
- /*
- This file implements the `location` API used by Ember's router.
- That API is:
- 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.
- TODO: This should perhaps be moved so that it's visible in the doc output.
- */
- /**
- Ember.Location returns an instance of the correct implementation of
- the `location` API.
- You can pass it a `implementation` ('hash', 'history', 'none') to force a
- particular implementation.
- @class Location
- @namespace Ember
- @static
- */
- Ember.Location = {
- 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);
- },
- registerImplementation: function(name, implementation) {
- this.implementations[name] = implementation;
- },
- implementations: {}
- };
- })();
- (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({
- path: '',
- getURL: function() {
- return get(this, 'path');
- },
- setURL: function(path) {
- set(this, 'path', path);
- },
- onUpdateURL: function(callback) {
- this.updateCallback = callback;
- },
- handleURL: function(url) {
- set(this, 'path', url);
- this.updateCallback(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;
- }
- });
- Ember.Location.registerImplementation('none', Ember.NoneLocation);
- })();
- (function() {
- /**
- @module ember
- @submodule ember-routing
- */
- var get = Ember.get, set = Ember.set;
- /**
- 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({
- init: function() {
- set(this, 'location', get(this, 'location') || window.location);
- },
- /**
- @private
- Returns the current `location.hash`, minus the '#' at the front.
- @method getURL
- */
- getURL: function() {
- return get(this, 'location').hash.substr(1);
- },
- /**
- @private
- Set the `location.hash` and remembers what was set. This prevents
- `onUpdateURL` callbacks from triggering when the hash was set by
- `HashLocation`.
- @method setURL
- @param path {String}
- */
- setURL: function(path) {
- get(this, 'location').hash = path;
- set(this, 'lastSetURL', path);
- },
- /**
- @private
- 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.
- @method onUpdateURL
- @param callback {Function}
- */
- onUpdateURL: function(callback) {
- var self = this;
- var guid = Ember.guidFor(this);
- Ember.$(window).bind('hashchange.ember-location-'+guid, function() {
- Ember.run(function() {
- var path = location.hash.substr(1);
- if (get(self, 'lastSetURL') === path) { return; }
- set(self, 'lastSetURL', null);
- callback(location.hash.substr(1));
- });
- });
- },
- /**
- @private
- 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.
- @method formatURL
- @param url {String}
- */
- formatURL: function(url) {
- return '#'+url;
- },
- willDestroy: function() {
- var guid = Ember.guidFor(this);
- Ember.$(window).unbind('hashchange.ember-location-'+guid);
- }
- });
- Ember.Location.registerImplementation('hash', Ember.HashLocation);
- })();
- (function() {
- /**
- @module ember
- @submodule ember-routing
- */
- var get = Ember.get, set = Ember.set;
- var popstateReady = false;
- /**
- 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({
- init: function() {
- set(this, 'location', get(this, 'location') || window.location);
- this.initState();
- },
- /**
- @private
- Used to set state on first call to setURL
- @method initState
- */
- initState: function() {
- this.replaceState(this.formatURL(this.getURL()));
- set(this, 'history', window.history);
- },
- /**
- Will be pre-pended to path upon state change
- @property rootURL
- @default '/'
- */
- rootURL: '/',
- /**
- @private
- Returns the current `location.pathname` without rootURL
- @method getURL
- */
- getURL: function() {
- var rootURL = get(this, 'rootURL'),
- url = get(this, 'location').pathname;
- rootURL = rootURL.replace(/\/$/, '');
- url = url.replace(rootURL, '');
- return url;
- },
- /**
- @private
- Uses `history.pushState` to update the url without a page reload.
- @method setURL
- @param path {String}
- */
- setURL: function(path) {
- path = this.formatURL(path);
- if (this.getState() && this.getState().path !== path) {
- popstateReady = true;
- this.pushState(path);
- }
- },
- /**
- @private
- Uses `history.replaceState` to update the url without a page reload
- or history modification.
- @method replaceURL
- @param path {String}
- */
- replaceURL: function(path) {
- path = this.formatURL(path);
- if (this.getState() && this.getState().path !== path) {
- popstateReady = true;
- this.replaceState(path);
- }
- },
- /**
- @private
- Get the current `history.state`
- @method getState
- */
- getState: function() {
- return get(this, 'history').state;
- },
- /**
- @private
- Pushes a new state
- @method pushState
- @param path {String}
- */
- pushState: function(path) {
- window.history.pushState({ path: path }, null, path);
- },
- /**
- @private
- Replaces the current state
- @method replaceState
- @param path {String}
- */
- replaceState: function(path) {
- window.history.replaceState({ path: path }, null, path);
- },
- /**
- @private
- Register a callback to be invoked whenever the browser
- history changes, including using forward and back buttons.
- @method onUpdateURL
- @param callback {Function}
- */
- onUpdateURL: function(callback) {
- var guid = Ember.guidFor(this),
- self = this;
- Ember.$(window).bind('popstate.ember-location-'+guid, function(e) {
- if(!popstateReady) {
- return;
- }
- callback(self.getURL());
- });
- },
- /**
- @private
- Used when using `{{action}}` helper. The url is always appended to the rootURL.
- @method formatURL
- @param url {String}
- */
- formatURL: function(url) {
- var rootURL = get(this, 'rootURL');
- if (url !== '') {
- rootURL = rootURL.replace(/\/$/, '');
- }
- return rootURL + url;
- },
- willDestroy: function() {
- var guid = Ember.guidFor(this);
- Ember.$(window).unbind('popstate.ember-location-'+guid);
- }
- });
- Ember.Location.registerImplementation('history', Ember.HistoryLocation);
- })();
- (function() {
- })();
- (function() {
- /**
- Ember Routing
- @module ember
- @submodule ember-routing
- @requires ember-states
- @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 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, set = Ember.set,
- classify = Ember.String.classify,
- decamelize = Ember.String.decamelize;
- /**
- 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.
- 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 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 loadedmetadata media
- // player event
- 'loadedmetadata': "loadedMetadata"
- }
- });
- ```
- 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/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:
- ```javascript
- window.App = Ember.Application.create({
- LOG_TRANSITIONS: true
- });
- ```
- 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.
- To begin routing, you must have at a minimum a top-level controller and view.
- You define these as `App.ApplicationController` and `App.ApplicationView`,
- respectively. Your application will not work if you do not define these two
- mandatory classes. For example:
- ```javascript
- App.ApplicationView = Ember.View.extend({
- templateName: 'application'
- });
- App.ApplicationController = Ember.Controller.extend();
- ```
- @class Application
- @namespace Ember
- @extends Ember.Namespace
- */
- var Application = Ember.Application = Ember.Namespace.extend({
- /**
- 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 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 loadedmetadata media
- // player event
- 'loadedmetadata': "loadedMetadata"
- }
- });
- ```
- @property customEvents
- @type Object
- @default null
- */
- customEvents: null,
- isInitialized: false,
- // 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.Router || this.defaultRouter();
- if (this.Router) { this.Router.namespace = this; }
- this._super();
- this.deferUntilDOMReady();
- this.scheduleInitialize();
- Ember.debug('-------------------------------');
- Ember.debug('Ember.VERSION : ' + Ember.VERSION);
- Ember.debug('Handlebars.VERSION : ' + Ember.Handlebars.VERSION);
- Ember.debug('jQuery.VERSION : ' + Ember.$().jquery);
- Ember.debug('-------------------------------');
- },
- /**
- @private
- Build the container for the current application.
- Also register a default application view in case the application
- itself does not.
- @method buildContainer
- @return {Ember.Container} the configured container
- */
- buildContainer: function() {
- var container = this.__container__ = Application.buildContainer(this);
- return container;
- },
- /**
- @private
- 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
- App = Ember.Application.create();
- App.Router.map(function(match) {
- match("/").to("index");
- });
- ```
- @method defaultRouter
- @return {Ember.Router} the default router
- */
- defaultRouter: function() {
- // Create a default App.Router if one was not supplied to make
- // it possible to do App.Router.map(...) without explicitly
- // creating a router first.
- if (this.router === undefined) {
- return Ember.Router.extend();
- }
- },
- /**
- @private
- Defer Ember readiness until DOM readiness. By default, Ember
- will wait for both DOM readiness and application initialization,
- as well as any deferrals registered by initializers.
- @method deferUntilDOMReady
- */
- deferUntilDOMReady: function() {
- this.deferReadiness();
- var self = this;
- this.$().ready(function() {
- self.advanceReadiness();
- });
- },
- /**
- @private
- Automatically initialize the application once the DOM has
- become ready.
- The initialization itself is deferred using Ember.run.once,
- 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.
- @method scheduleInitialize
- */
- scheduleInitialize: function() {
- var self = this;
- this.$().ready(function() {
- if (self.isDestroyed || self.isInitialized) return;
- Ember.run.once(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 cannot defer readiness since the `ready()` hook has already been called.", this._readinessDeferrals > 0);
- this._readinessDeferrals++;
- },
- /**
- @method advanceReadiness
- @see {Ember.Application#deferReadiness}
- */
- advanceReadiness: function() {
- 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.register('model:user', App.Person, {singleton: false });
- App.register('fruit:favorite', App.Orange);
- App.register('communication:main', App.Email, {singleton: false});
- ```
- @method register
- @param type {String}
- @param name {String}
- @param factory {String}
- @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('model:user', 'email', 'model:email')
- App.inject('model', 'source', 'source:main')
- ```
- @method inject
- @param factoryNameOrType {String}
- @param property {String}
- @param injectionName {String}
- **/
- inject: function(){
- var container = this.__container__;
- container.injection.apply(container, arguments);
- },
- /**
- @private
- 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.
- @method initialize
- */
- initialize: function() {
- Ember.assert("Application initialize may only be called once", !this.isInitialized);
- Ember.assert("Cannot initialize a destroyed application", !this.isDestroyed);
- this.isInitialized = true;
- // At this point, the App.Router must already be assigned
- this.__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: function() {
- get(this, '__container__').destroy();
- this.buildContainer();
- this.isInitialized = false;
- this.initialize();
- this.startRouting();
- },
- /**
- @private
- @method runInitializers
- */
- runInitializers: function() {
- var initializers = get(this.constructor, 'initializers'),
- container = this.__container__,
- graph = new Ember.DAG(),
- namespace = this,
- properties, i, initializer;
- for (i=0; i<initializers.length; i++) {
- initializer = initializers[i];
- graph.addEdges(initializer.name, initializer.initialize, initializer.before, initializer.after);
- }
- graph.topsort(function (vertex) {
- var initializer = vertex.value;
- 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;
- }
- },
- /**
- @private
- Setup up the event dispatcher to receive events on the
- application's `rootElement` with any registered
- `customEvents`.
- @method setupEventDispatcher
- */
- setupEventDispatcher: function() {
- var eventDispatcher = this.createEventDispatcher(),
- customEvents = get(this, 'customEvents');
- eventDispatcher.setup(customEvents);
- },
- /**
- @private
- Create an event dispatcher for the application's `rootElement`.
- @method createEventDispatcher
- */
- createEventDispatcher: function() {
- var rootElement = get(this, 'rootElement'),
- eventDispatcher = Ember.EventDispatcher.create({
- rootElement: rootElement
- });
- set(this, 'eventDispatcher', eventDispatcher);
- return eventDispatcher;
- },
- /**
- @private
- If the application has a router, use it to route to the current URL, and
- trigger a new call to `route` whenever the URL changes.
- @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,
- willDestroy: function() {
- Ember.BOOTED = false;
- var eventDispatcher = get(this, 'eventDispatcher');
- if (eventDispatcher) { eventDispatcher.destroy(); }
- get(this, '__container__').destroy();
- },
- initializer: function(options) {
- this.constructor.initializer(options);
- }
- });
- Ember.Application.reopenClass({
- concatenatedProperties: ['initializers'],
- initializers: Ember.A(),
- initializer: function(initializer) {
- var initializers = get(this, 'initializers');
- Ember.assert("The initializer '" + initializer.name + "' has already been registered", !initializers.findProperty('name', initializers.name));
- Ember.assert("An injection cannot be registered with both a before and an after", !(initializer.before && initializer.after));
- Ember.assert("An injection cannot be registered without an injection function", Ember.canInvoke(initializer, 'initialize'));
- initializers.push(initializer);
- },
- /**
- @private
- 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
- @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 = Ember.Container.defaultContainer || container;
- container.set = Ember.set;
- container.resolver = resolverFor(namespace);
- container.optionsForType('view', { singleton: false });
- container.optionsForType('template', { instantiate: false });
- container.register('application', 'main', namespace, { instantiate: false });
- container.injection('router:main', 'namespace', 'application:main');
- container.typeInjection('controller', 'target', 'router:main');
- container.typeInjection('controller', 'namespace', 'application:main');
- container.typeInjection('route', 'router', 'router:main');
- return container;
- }
- });
- /**
- @private
- 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.
- @param {Ember.Namespace} namespace the namespace to look for classes
- @return {any} the resolved value for a given lookup
- */
- function resolverFor(namespace) {
- return function(fullName) {
- var nameParts = fullName.split(":"),
- type = nameParts[0], name = nameParts[1];
- if (type === 'template') {
- var templateName = name.replace(/\./g, '/');
- if (Ember.TEMPLATES[templateName]) {
- return Ember.TEMPLATES[templateName];
- }
- templateName = decamelize(templateName);
- if (Ember.TEMPLATES[templateName]) {
- return Ember.TEMPLATES[templateName];
- }
- }
- if (type === 'controller' || type === 'route' || type === 'view') {
- name = name.replace(/\./g, '_');
- }
- var className = classify(name) + classify(type);
- var factory = get(namespace, className);
- if (factory) { return factory; }
- };
- }
- Ember.runLoadHooks('Ember.Application', Ember.Application);
- })();
- (function() {
- })();
- (function() {
- /**
- @module ember
- @submodule ember-routing
- */
- var get = Ember.get, set = Ember.set;
- var ControllersProxy = Ember.Object.extend({
- controller: null,
- unknownProperty: function(controllerName) {
- var controller = get(this, 'controller'),
- needs = get(controller, 'needs'),
- container = controller.get('container'),
- dependency;
- for (var i=0, l=needs.length; i<l; i++) {
- dependency = needs[i];
- if (dependency === controllerName) {
- return container.lookup('controller:' + controllerName);
- }
- }
- }
- });
- function verifyDependencies(controller) {
- var needs = get(controller, 'needs'),
- container = get(controller, 'container'),
- dependency, satisfied = true;
- for (var i=0, l=needs.length; i<l; i++) {
- dependency = needs[i];
- if (dependency.indexOf(':') === -1) {
- dependency = "controller:" + dependency;
- }
- if (!container.has(dependency)) {
- satisfied = false;
- Ember.assert(controller + " needs " + dependency + " but it does not exist", false);
- }
- }
- return satisfied;
- }
- Ember.ControllerMixin.reopen({
- concatenatedProperties: ['needs'],
- needs: [],
- init: function() {
- this._super.apply(this, arguments);
- // Structure asserts to still do verification but not string concat in production
- if(!verifyDependencies(this)) {
- Ember.assert("Missing dependencies", false);
- }
- },
- controllerFor: function(controllerName) {
- Ember.deprecate("Controller#controllerFor is depcrecated, please use Controller#needs instead");
- var container = get(this, 'container');
- return container.lookup('controller:' + controllerName);
- },
- controllers: Ember.computed(function() {
- return ControllersProxy.create({ controller: this });
- })
- });
- })();
- (function() {
- })();
- (function() {
- /**
- Ember Application
- @module ember
- @submodule ember-application
- @requires ember-views, ember-states, ember-routing
- */
- })();
- (function() {
- var get = Ember.get, set = Ember.set;
- /**
- @module ember
- @submodule ember-states
- */
- /**
- @class State
- @namespace Ember
- @extends Ember.Object
- @uses Ember.Evented
- */
- Ember.State = Ember.Object.extend(Ember.Evented,
- /** @scope Ember.State.prototype */{
- isState: true,
- /**
- A reference to the parent state.
- @property parentState
- @type Ember.State
- */
- parentState: null,
- start: null,
- /**
- The name of this state.
- @property name
- @type String
- */
- name: null,
- /**
- The full path to this state.
- @property path
- @type String
- */
- path: Ember.computed(function() {
- var parentPath = get(this, 'parentState.path'),
- path = get(this, 'name');
- if (parentPath) {
- path = parentPath + '.' + path;
- }
- return path;
- }),
- /**
- @private
- Override the default event firing from `Ember.Evented` to
- also call methods with the given name.
- @method trigger
- @param name
- */
- trigger: function(name) {
- if (this[name]) {
- this[name].apply(this, [].slice.call(arguments, 1));
- }
- this._super.apply(this, arguments);
- },
- init: function() {
- var states = get(this, 'states'), foundStates;
- set(this, 'childStates', Ember.A());
- set(this, 'eventTransitions', get(this, 'eventTransitions') || {});
- var name, value, transitionTarget;
- // As a convenience, loop over the properties
- // of this state and look for any that are other
- // Ember.State instances or classes, and move them
- // to the `states` hash. This avoids having to
- // create an explicit separate hash.
- if (!states) {
- states = {};
- for (name in this) {
- if (name === "constructor") { continue; }
- if (value = this[name]) {
- if (transitionTarget = value.transitionTarget) {
- this.eventTransitions[name] = transitionTarget;
- }
- this.setupChild(states, name, value);
- }
- }
- set(this, 'states', states);
- } else {
- for (name in states) {
- this.setupChild(states, name, states[name]);
- }
- }
- set(this, 'pathsCache', {});
- set(this, 'pathsCacheNoContext', {});
- },
- setupChild: function(states, name, value) {
- if (!value) { return false; }
- if (value.isState) {
- set(value, 'name', name);
- } else if (Ember.State.detect(value)) {
- value = value.create({
- name: name
- });
- }
- if (value.isState) {
- set(value, 'parentState', this);
- get(this, 'childStates').pushObject(value);
- states[name] = value;
- return value;
- }
- },
- lookupEventTransition: function(name) {
- var path, state = this;
- while(state && !path) {
- path = state.eventTransitions[name];
- state = state.get('parentState');
- }
- return path;
- },
- /**
- A Boolean value indicating whether the state is a leaf state
- in the state hierarchy. This is `false` if the state has child
- states; otherwise it is true.
- @property isLeaf
- @type Boolean
- */
- isLeaf: Ember.computed(function() {
- return !get(this, 'childStates').length;
- }),
- /**
- A boolean value indicating whether the state takes a context.
- By default we assume all states take contexts.
- @property hasContext
- @default true
- */
- hasContext: true,
- /**
- This is the default transition event.
- @event setup
- @param {Ember.StateManager} manager
- @param context
- @see Ember.StateManager#transitionEvent
- */
- setup: Ember.K,
- /**
- This event fires when the state is entered.
- @event enter
- @param {Ember.StateManager} manager
- */
- enter: Ember.K,
- /**
- This event fires when the state is exited.
- @event exit
- @param {Ember.StateManager} manager
- */
- exit: Ember.K
- });
- Ember.State.reopenClass({
- /**
- Creates an action function for transitioning to the named state while
- preserving context.
- The following example StateManagers are equivalent:
- ```javascript
- aManager = Ember.StateManager.create({
- stateOne: Ember.State.create({
- changeToStateTwo: Ember.State.transitionTo('stateTwo')
- }),
- stateTwo: Ember.State.create({})
- })
- bManager = Ember.StateManager.create({
- stateOne: Ember.State.create({
- changeToStateTwo: function(manager, context){
- manager.transitionTo('stateTwo', context)
- }
- }),
- stateTwo: Ember.State.create({})
- })
- ```
- @method transitionTo
- @static
- @param {String} target
- */
- transitionTo: function(target) {
- var transitionFunction = function(stateManager, contextOrEvent) {
- var contexts = [], transitionArgs,
- Event = Ember.$ && Ember.$.Event;
- if (contextOrEvent && (Event && contextOrEvent instanceof Event)) {
- if (contextOrEvent.hasOwnProperty('contexts')) {
- contexts = contextOrEvent.contexts.slice();
- }
- }
- else {
- contexts = [].slice.call(arguments, 1);
- }
- contexts.unshift(target);
- stateManager.transitionTo.apply(stateManager, contexts);
- };
- transitionFunction.transitionTarget = target;
- return transitionFunction;
- }
- });
- })();
- (function() {
- /**
- @module ember
- @submodule ember-states
- */
- var get = Ember.get, set = Ember.set, fmt = Ember.String.fmt;
- var arrayForEach = Ember.ArrayPolyfills.forEach;
- /**
- A Transition takes the enter, exit and resolve states and normalizes
- them:
- * takes any passed in contexts into consideration
- * adds in `initialState`s
- @class Transition
- @private
- */
- var Transition = function(raw) {
- this.enterStates = raw.enterStates.slice();
- this.exitStates = raw.exitStates.slice();
- this.resolveState = raw.resolveState;
- this.finalState = raw.enterStates[raw.enterStates.length - 1] || raw.resolveState;
- };
- Transition.prototype = {
- /**
- Normalize the passed in enter, exit and resolve states.
- This process also adds `finalState` and `contexts` to the Transition object.
- @method normalize
- @param {Ember.StateManager} manager the state manager running the transition
- @param {Array} contexts a list of contexts passed into `transitionTo`
- */
- normalize: function(manager, contexts) {
- this.matchContextsToStates(contexts);
- this.addInitialStates();
- this.removeUnchangedContexts(manager);
- return this;
- },
- /**
- Match each of the contexts passed to `transitionTo` to a state.
- This process may also require adding additional enter and exit
- states if there are more contexts than enter states.
- @method matchContextsToStates
- @param {Array} contexts a list of contexts passed into `transitionTo`
- */
- matchContextsToStates: function(contexts) {
- var stateIdx = this.enterStates.length - 1,
- matchedContexts = [],
- state,
- context;
- // Next, we will match the passed in contexts to the states they
- // represent.
- //
- // First, assign a context to each enter state in reverse order. If
- // any contexts are left, add a parent state to the list of states
- // to enter and exit, and assign a context to the parent state.
- //
- // If there are still contexts left when the state manager is
- // reached, raise an exception.
- //
- // This allows the following:
- //
- // |- root
- // | |- post
- // | | |- comments
- // | |- about (* current state)
- //
- // For `transitionTo('post.comments', post, post.get('comments')`,
- // the first context (`post`) will be assigned to `root.post`, and
- // the second context (`post.get('comments')`) will be assigned
- // to `root.post.comments`.
- //
- // For the following:
- //
- // |- root
- // | |- post
- // | | |- index (* current state)
- // | | |- comments
- //
- // For `transitionTo('post.comments', otherPost, otherPost.get('comments')`,
- // the `<root.post>` state will be added to the list of enter and exit
- // states because its context has changed.
- while (contexts.length > 0) {
- if (stateIdx >= 0) {
- state = this.enterStates[stateIdx--];
- } else {
- if (this.enterStates.length) {
- state = get(this.enterStates[0], 'parentState');
- if (!state) { throw "Cannot match all contexts to states"; }
- } else {
- // If re-entering the current state with a context, the resolve
- // state will be the current state.
- state = this.resolveState;
- }
- this.enterStates.unshift(state);
- this.exitStates.unshift(state);
- }
- // in routers, only states with dynamic segments have a context
- if (get(state, 'hasContext')) {
- context = contexts.pop();
- } else {
- context = null;
- }
- matchedContexts.unshift(context);
- }
- this.contexts = matchedContexts;
- },
- /**
- Add any `initialState`s to the list of enter states.
- @method addInitialStates
- */
- addInitialStates: function() {
- var finalState = this.finalState, initialState;
- while(true) {
- initialState = get(finalState, 'initialState') || 'start';
- finalState = get(finalState, 'states.' + initialState);
- if (!finalState) { break; }
- this.finalState = finalState;
- this.enterStates.push(finalState);
- this.contexts.push(undefined);
- }
- },
- /**
- Remove any states that were added because the number of contexts
- exceeded the number of explicit enter states, but the context has
- not changed since the last time the state was entered.
- @method removeUnchangedContexts
- @param {Ember.StateManager} manager passed in to look up the last
- context for a states
- */
- removeUnchangedContexts: function(manager) {
- // Start from the beginning of the enter states. If the state was added
- // to the list during the context matching phase, make sure the context
- // has actually changed since the last time the state was entered.
- while (this.enterStates.length > 0) {
- if (this.enterStates[0] !== this.exitStates[0]) { break; }
- if (this.enterStates.length === this.contexts.length) {
- if (manager.getStateMeta(this.enterStates[0], 'context') !== this.contexts[0]) { break; }
- this.contexts.shift();
- }
- this.resolveState = this.enterStates.shift();
- this.exitStates.shift();
- }
- }
- };
- var sendRecursively = function(event, currentState, isUnhandledPass) {
- var log = this.enableLogging,
- eventName = isUnhandledPass ? 'unhandledEvent' : event,
- action = currentState[eventName],
- contexts, sendRecursiveArguments, actionArguments;
- contexts = [].slice.call(arguments, 3);
- // Test to see if the action is a method that
- // can be invoked. Don't blindly check just for
- // existence, because it is possible the state
- // manager has a child state of the given name,
- // and we should still raise an exception in that
- // case.
- if (typeof action === 'function') {
- if (log) {
- if (isUnhandledPass) {
- Ember.Logger.log(fmt("STATEMANAGER: Unhandled event '%@' being sent to state %@.", [event, get(currentState, 'path')]));
- } else {
- Ember.Logger.log(fmt("STATEMANAGER: Sending event '%@' to state %@.", [event, get(currentState, 'path')]));
- }
- }
- actionArguments = contexts;
- if (isUnhandledPass) {
- actionArguments.unshift(event);
- }
- actionArguments.unshift(this);
- return action.apply(currentState, actionArguments);
- } else {
- var parentState = get(currentState, 'parentState');
- if (parentState) {
- sendRecursiveArguments = contexts;
- sendRecursiveArguments.unshift(event, parentState, isUnhandledPass);
- return sendRecursively.apply(this, sendRecursiveArguments);
- } else if (!isUnhandledPass) {
- return sendEvent.call(this, event, contexts, true);
- }
- }
- };
- var sendEvent = function(eventName, sendRecursiveArguments, isUnhandledPass) {
- sendRecursiveArguments.unshift(eventName, get(this, 'currentState'), isUnhandledPass);
- return sendRecursively.apply(this, sendRecursiveArguments);
- };
- /**
- StateManager is part of Ember's implementation of a finite state machine. A
- StateManager instance manages a number of properties that are instances of
- `Ember.State`,
- tracks the current active state, and triggers callbacks when states have changed.
- ## Defining States
- The states of StateManager can be declared in one of two ways. First, you can
- define a `states` property that contains all the states:
- ```javascript
- managerA = Ember.StateManager.create({
- states: {
- stateOne: Ember.State.create(),
- stateTwo: Ember.State.create()
- }
- })
- managerA.get('states')
- // {
- // stateOne: Ember.State.create(),
- // stateTwo: Ember.State.create()
- // }
- ```
- You can also add instances of `Ember.State` (or an `Ember.State` subclass)
- directly as properties of a StateManager. These states will be collected into
- the `states` property for you.
- ```javascript
- managerA = Ember.StateManager.create({
- stateOne: Ember.State.create(),
- stateTwo: Ember.State.create()
- })
- managerA.get('states')
- // {
- // stateOne: Ember.State.create(),
- // stateTwo: Ember.State.create()
- // }
- ```
- ## The Initial State
- When created a StateManager instance will immediately enter into the state
- defined as its `start` property or the state referenced by name in its
- `initialState` property:
- ```javascript
- managerA = Ember.StateManager.create({
- start: Ember.State.create({})
- })
- managerA.get('currentState.name') // 'start'
- managerB = Ember.StateManager.create({
- initialState: 'beginHere',
- beginHere: Ember.State.create({})
- })
- managerB.get('currentState.name') // 'beginHere'
- ```
- Because it is a property you may also provide a computed function if you wish
- to derive an `initialState` programmatically:
- ```javascript
- managerC = Ember.StateManager.create({
- initialState: function(){
- if (someLogic) {
- return 'active';
- } else {
- return 'passive';
- }
- }.property(),
- active: Ember.State.create({}),
- passive: Ember.State.create({})
- })
- ```
- ## Moving Between States
- A StateManager can have any number of `Ember.State` objects as properties
- and can have a single one of these states as its current state.
- Calling `transitionTo` transitions between states:
- ```javascript
- robotManager = Ember.StateManager.create({
- initialState: 'poweredDown',
- poweredDown: Ember.State.create({}),
- poweredUp: Ember.State.create({})
- })
- robotManager.get('currentState.name') // 'poweredDown'
- robotManager.transitionTo('poweredUp')
- robotManager.get('currentState.name') // 'poweredUp'
- ```
- Before transitioning into a new state the existing `currentState` will have
- its `exit` method called with the StateManager instance as its first argument
- and an object representing the transition as its second argument.
- After transitioning into a new state the new `currentState` will have its
- `enter` method called with the StateManager instance as its first argument
- and an object representing the transition as its second argument.
- ```javascript
- robotManager = Ember.StateManager.create({
- initialState: 'poweredDown',
- poweredDown: Ember.State.create({
- exit: function(stateManager){
- console.log("exiting the poweredDown state")
- }
- }),
- poweredUp: Ember.State.create({
- enter: function(stateManager){
- console.log("entering the poweredUp state. Destroy all humans.")
- }
- })
- })
- robotManager.get('currentState.name') // 'poweredDown'
- robotManager.transitionTo('poweredUp')
- // will log
- // 'exiting the poweredDown state'
- // 'entering the poweredUp state. Destroy all humans.'
- ```
- Once a StateManager is already in a state, subsequent attempts to enter that
- state will not trigger enter or exit method calls. Attempts to transition
- into a state that the manager does not have will result in no changes in the
- StateManager's current state:
- ```javascript
- robotManager = Ember.StateManager.create({
- initialState: 'poweredDown',
- poweredDown: Ember.State.create({
- exit: function(stateManager){
- console.log("exiting the poweredDown state")
- }
- }),
- poweredUp: Ember.State.create({
- enter: function(stateManager){
- console.log("entering the poweredUp state. Destroy all humans.")
- }
- })
- })
- robotManager.get('currentState.name') // 'poweredDown'
- robotManager.transitionTo('poweredUp')
- // will log
- // 'exiting the poweredDown state'
- // 'entering the poweredUp state. Destroy all humans.'
- robotManager.transitionTo('poweredUp') // no logging, no state change
- robotManager.transitionTo('someUnknownState') // silently fails
- robotManager.get('currentState.name') // 'poweredUp'
- ```
- Each state property may itself contain properties that are instances of
- `Ember.State`. The StateManager can transition to specific sub-states in a
- series of transitionTo method calls or via a single transitionTo with the
- full path to the specific state. The StateManager will also keep track of the
- full path to its currentState
- ```javascript
- robotManager = Ember.StateManager.create({
- initialState: 'poweredDown',
- poweredDown: Ember.State.create({
- charging: Ember.State.create(),
- charged: Ember.State.create()
- }),
- poweredUp: Ember.State.create({
- mobile: Ember.State.create(),
- stationary: Ember.State.create()
- })
- })
- robotManager.get('currentState.name') // 'poweredDown'
- robotManager.transitionTo('poweredUp')
- robotManager.get('currentState.name') // 'poweredUp'
- robotManager.transitionTo('mobile')
- robotManager.get('currentState.name') // 'mobile'
- // transition via a state path
- robotManager.transitionTo('poweredDown.charging')
- robotManager.get('currentState.name') // 'charging'
- robotManager.get('currentState.path') // 'poweredDown.charging'
- ```
- Enter transition methods will be called for each state and nested child state
- in their hierarchical order. Exit methods will be called for each state and
- its nested states in reverse hierarchical order.
- Exit transitions for a parent state are not called when entering into one of
- its child states, only when transitioning to a new section of possible states
- in the hierarchy.
- ```javascript
- robotManager = Ember.StateManager.create({
- initialState: 'poweredDown',
- poweredDown: Ember.State.create({
- enter: function(){},
- exit: function(){
- console.log("exited poweredDown state")
- },
- charging: Ember.State.create({
- enter: function(){},
- exit: function(){}
- }),
- charged: Ember.State.create({
- enter: function(){
- console.log("entered charged state")
- },
- exit: function(){
- console.log("exited charged state")
- }
- })
- }),
- poweredUp: Ember.State.create({
- enter: function(){
- console.log("entered poweredUp state")
- },
- exit: function(){},
- mobile: Ember.State.create({
- enter: function(){
- console.log("entered mobile state")
- },
- exit: function(){}
- }),
- stationary: Ember.State.create({
- enter: function(){},
- exit: function(){}
- })
- })
- })
- robotManager.get('currentState.path') // 'poweredDown'
- robotManager.transitionTo('charged')
- // logs 'entered charged state'
- // but does *not* log 'exited poweredDown state'
- robotManager.get('currentState.name') // 'charged
- robotManager.transitionTo('poweredUp.mobile')
- // logs
- // 'exited charged state'
- // 'exited poweredDown state'
- // 'entered poweredUp state'
- // 'entered mobile state'
- ```
- During development you can set a StateManager's `enableLogging` property to
- `true` to receive console messages of state transitions.
- ```javascript
- robotManager = Ember.StateManager.create({
- enableLogging: true
- })
- ```
- ## Managing currentState with Actions
- To control which transitions are possible for a given state, and
- appropriately handle external events, the StateManager can receive and
- route action messages to its states via the `send` method. Calling to
- `send` with an action name will begin searching for a method with the same
- name starting at the current state and moving up through the parent states
- in a state hierarchy until an appropriate method is found or the StateManager
- instance itself is reached.
- If an appropriately named method is found it will be called with the state
- manager as the first argument and an optional `context` object as the second
- argument.
- ```javascript
- managerA = Ember.StateManager.create({
- initialState: 'stateOne.substateOne.subsubstateOne',
- stateOne: Ember.State.create({
- substateOne: Ember.State.create({
- anAction: function(manager, context){
- console.log("an action was called")
- },
- subsubstateOne: Ember.State.create({})
- })
- })
- })
- managerA.get('currentState.name') // 'subsubstateOne'
- managerA.send('anAction')
- // 'stateOne.substateOne.subsubstateOne' has no anAction method
- // so the 'anAction' method of 'stateOne.substateOne' is called
- // and logs "an action was called"
- // with managerA as the first argument
- // and no second argument
- someObject = {}
- managerA.send('anAction', someObject)
- // the 'anAction' method of 'stateOne.substateOne' is called again
- // with managerA as the first argument and
- // someObject as the second argument.
- ```
- If the StateManager attempts to send an action but does not find an appropriately named
- method in the current state or while moving upwards through the state hierarchy, it will
- repeat the process looking for a `unhandledEvent` method. If an `unhandledEvent` method is
- found, it will be called with the original event name as the second argument. If an
- `unhandledEvent` method is not found, the StateManager will throw a new Ember.Error.
- ```javascript
- managerB = Ember.StateManager.create({
- initialState: 'stateOne.substateOne.subsubstateOne',
- stateOne: Ember.State.create({
- substateOne: Ember.State.create({
- subsubstateOne: Ember.State.create({}),
- unhandledEvent: function(manager, eventName, context) {
- console.log("got an unhandledEvent with name " + eventName);
- }
- })
- })
- })
- managerB.get('currentState.name') // 'subsubstateOne'
- managerB.send('anAction')
- // neither `stateOne.substateOne.subsubstateOne` nor any of it's
- // parent states have a handler for `anAction`. `subsubstateOne`
- // also does not have a `unhandledEvent` method, but its parent
- // state, `substateOne`, does, and it gets fired. It will log
- // "got an unhandledEvent with name anAction"
- ```
- Action detection only moves upwards through the state hierarchy from the current state.
- It does not search in other portions of the hierarchy.
- ```javascript
- managerC = Ember.StateManager.create({
- initialState: 'stateOne.substateOne.subsubstateOne',
- stateOne: Ember.State.create({
- substateOne: Ember.State.create({
- subsubstateOne: Ember.State.create({})
- })
- }),
- stateTwo: Ember.State.create({
- anAction: function(manager, context){
- // will not be called below because it is
- // not a parent of the current state
- }
- })
- })
- managerC.get('currentState.name') // 'subsubstateOne'
- managerC.send('anAction')
- // Error: <Ember.StateManager:ember132> could not
- // respond to event anAction in state stateOne.substateOne.subsubstateOne.
- ```
- Inside of an action method the given state should delegate `transitionTo` calls on its
- StateManager.
- ```javascript
- robotManager = Ember.StateManager.create({
- initialState: 'poweredDown.charging',
- poweredDown: Ember.State.create({
- charging: Ember.State.create({
- chargeComplete: function(manager, context){
- manager.transitionTo('charged')
- }
- }),
- charged: Ember.State.create({
- boot: function(manager, context){
- manager.transitionTo('poweredUp')
- }
- })
- }),
- poweredUp: Ember.State.create({
- beginExtermination: function(manager, context){
- manager.transitionTo('rampaging')
- },
- rampaging: Ember.State.create()
- })
- })
- robotManager.get('currentState.name') // 'charging'
- robotManager.send('boot') // throws error, no boot action
- // in current hierarchy
- robotManager.get('currentState.name') // remains 'charging'
- robotManager.send('beginExtermination') // throws error, no beginExtermination
- // action in current hierarchy
- robotManager.get('currentState.name') // remains 'charging'
- robotManager.send('chargeComplete')
- robotManager.get('currentState.name') // 'charged'
- robotManager.send('boot')
- robotManager.get('currentState.name') // 'poweredUp'
- robotManager.send('beginExtermination', allHumans)
- robotManager.get('currentState.name') // 'rampaging'
- ```
- Transition actions can also be created using the `transitionTo` method of the `Ember.State` class. The
- following example StateManagers are equivalent:
- ```javascript
- aManager = Ember.StateManager.create({
- stateOne: Ember.State.create({
- changeToStateTwo: Ember.State.transitionTo('stateTwo')
- }),
- stateTwo: Ember.State.create({})
- })
- bManager = Ember.StateManager.create({
- stateOne: Ember.State.create({
- changeToStateTwo: function(manager, context){
- manager.transitionTo('stateTwo', context)
- }
- }),
- stateTwo: Ember.State.create({})
- })
- ```
- @class StateManager
- @namespace Ember
- @extends Ember.State
- **/
- Ember.StateManager = Ember.State.extend({
- /**
- @private
- When creating a new statemanager, look for a default state to transition
- into. This state can either be named `start`, or can be specified using the
- `initialState` property.
- @method init
- */
- init: function() {
- this._super();
- set(this, 'stateMeta', Ember.Map.create());
- var initialState = get(this, 'initialState');
- if (!initialState && get(this, 'states.start')) {
- initialState = 'start';
- }
- if (initialState) {
- this.transitionTo(initialState);
- Ember.assert('Failed to transition to initial state "' + initialState + '"', !!get(this, 'currentState'));
- }
- },
- stateMetaFor: function(state) {
- var meta = get(this, 'stateMeta'),
- stateMeta = meta.get(state);
- if (!stateMeta) {
- stateMeta = {};
- meta.set(state, stateMeta);
- }
- return stateMeta;
- },
- setStateMeta: function(state, key, value) {
- return set(this.stateMetaFor(state), key, value);
- },
- getStateMeta: function(state, key) {
- return get(this.stateMetaFor(state), key);
- },
- /**
- The current state from among the manager's possible states. This property should
- not be set directly. Use `transitionTo` to move between states by name.
- @property currentState
- @type Ember.State
- */
- currentState: null,
- /**
- The path of the current state. Returns a string representation of the current
- state.
- @property currentPath
- @type String
- */
- currentPath: Ember.computed.alias('currentState.path'),
- /**
- The name of transitionEvent that this stateManager will dispatch
- @property transitionEvent
- @type String
- @default 'setup'
- */
- transitionEvent: 'setup',
- /**
- If set to true, `errorOnUnhandledEvents` will cause an exception to be
- raised if you attempt to send an event to a state manager that is not
- handled by the current state or any of its parent states.
- @property errorOnUnhandledEvents
- @type Boolean
- @default true
- */
- errorOnUnhandledEvent: true,
- send: function(event) {
- var contexts = [].slice.call(arguments, 1);
- Ember.assert('Cannot send event "' + event + '" while currentState is ' + get(this, 'currentState'), get(this, 'currentState'));
- return sendEvent.call(this, event, contexts, false);
- },
- unhandledEvent: function(manager, event) {
- if (get(this, 'errorOnUnhandledEvent')) {
- throw new Ember.Error(this.toString() + " could not respond to event " + event + " in state " + get(this, 'currentState.path') + ".");
- }
- },
- /**
- Finds a state by its state path.
- Example:
- ```javascript
- manager = Ember.StateManager.create({
- root: Ember.State.create({
- dashboard: Ember.State.create()
- })
- });
- manager.getStateByPath(manager, "root.dashboard")
- // returns the dashboard state
- ```
- @method getStateByPath
- @param {Ember.State} root the state to start searching from
- @param {String} path the state path to follow
- @return {Ember.State} the state at the end of the path
- */
- getStateByPath: function(root, path) {
- var parts = path.split('.'),
- state = root;
- for (var i=0, len=parts.length; i<len; i++) {
- state = get(get(state, 'states'), parts[i]);
- if (!state) { break; }
- }
- return state;
- },
- findStateByPath: function(state, path) {
- var possible;
- while (!possible && state) {
- possible = this.getStateByPath(state, path);
- state = get(state, 'parentState');
- }
- return possible;
- },
- /**
- A state stores its child states in its `states` hash.
- This code takes a path like `posts.show` and looks
- up `root.states.posts.states.show`.
- It returns a list of all of the states from the
- root, which is the list of states to call `enter`
- on.
- @method getStatesInPath
- @param root
- @param path
- */
- getStatesInPath: function(root, path) {
- if (!path || path === "") { return undefined; }
- var parts = path.split('.'),
- result = [],
- states,
- state;
- for (var i=0, len=parts.length; i<len; i++) {
- states = get(root, 'states');
- if (!states) { return undefined; }
- state = get(states, parts[i]);
- if (state) { root = state; result.push(state); }
- else { return undefined; }
- }
- return result;
- },
- goToState: function() {
- // not deprecating this yet so people don't constantly need to
- // make trivial changes for little reason.
- return this.transitionTo.apply(this, arguments);
- },
- transitionTo: function(path, context) {
- // XXX When is transitionTo called with no path
- if (Ember.isEmpty(path)) { return; }
- // The ES6 signature of this function is `path, ...contexts`
- var contexts = context ? Array.prototype.slice.call(arguments, 1) : [],
- currentState = get(this, 'currentState') || this;
- // First, get the enter, exit and resolve states for the current state
- // and specified path. If possible, use an existing cache.
- var hash = this.contextFreeTransition(currentState, path);
- // Next, process the raw state information for the contexts passed in.
- var transition = new Transition(hash).normalize(this, contexts);
- this.enterState(transition);
- this.triggerSetupContext(transition);
- },
- contextFreeTransition: function(currentState, path) {
- var cache = currentState.pathsCache[path];
- if (cache) { return cache; }
- var enterStates = this.getStatesInPath(currentState, path),
- exitStates = [],
- resolveState = currentState;
- // Walk up the states. For each state, check whether a state matching
- // the `path` is nested underneath. This will find the closest
- // parent state containing `path`.
- //
- // This allows the user to pass in a relative path. For example, for
- // the following state hierarchy:
- //
- // | |root
- // | |- posts
- // | | |- show (* current)
- // | |- comments
- // | | |- show
- //
- // If the current state is `<root.posts.show>`, an attempt to
- // transition to `comments.show` will match `<root.comments.show>`.
- //
- // First, this code will look for root.posts.show.comments.show.
- // Next, it will look for root.posts.comments.show. Finally,
- // it will look for `root.comments.show`, and find the state.
- //
- // After this process, the following variables will exist:
- //
- // * resolveState: a common parent state between the current
- // and target state. In the above example, `<root>` is the
- // `resolveState`.
- // * enterStates: a list of all of the states represented
- // by the path from the `resolveState`. For example, for
- // the path `root.comments.show`, `enterStates` would have
- // `[<root.comments>, <root.comments.show>]`
- // * exitStates: a list of all of the states from the
- // `resolveState` to the `currentState`. In the above
- // example, `exitStates` would have
- // `[<root.posts>`, `<root.posts.show>]`.
- while (resolveState && !enterStates) {
- exitStates.unshift(resolveState);
- resolveState = get(resolveState, 'parentState');
- if (!resolveState) {
- enterStates = this.getStatesInPath(this, path);
- if (!enterStates) {
- Ember.assert('Could not find state for path: "'+path+'"');
- return;
- }
- }
- enterStates = this.getStatesInPath(resolveState, path);
- }
- // If the path contains some states that are parents of both the
- // current state and the target state, remove them.
- //
- // For example, in the following hierarchy:
- //
- // |- root
- // | |- post
- // | | |- index (* current)
- // | | |- show
- //
- // If the `path` is `root.post.show`, the three variables will
- // be:
- //
- // * resolveState: `<state manager>`
- // * enterStates: `[<root>, <root.post>, <root.post.show>]`
- // * exitStates: `[<root>, <root.post>, <root.post.index>]`
- //
- // The goal of this code is to remove the common states, so we
- // have:
- //
- // * resolveState: `<root.post>`
- // * enterStates: `[<root.post.show>]`
- // * exitStates: `[<root.post.index>]`
- //
- // This avoid unnecessary calls to the enter and exit transitions.
- while (enterStates.length > 0 && enterStates[0] === exitStates[0]) {
- resolveState = enterStates.shift();
- exitStates.shift();
- }
- // Cache the enterStates, exitStates, and resolveState for the
- // current state and the `path`.
- var transitions = currentState.pathsCache[path] = {
- exitStates: exitStates,
- enterStates: enterStates,
- resolveState: resolveState
- };
- return transitions;
- },
- triggerSetupContext: function(transitions) {
- var contexts = transitions.contexts,
- offset = transitions.enterStates.length - contexts.length,
- enterStates = transitions.enterStates,
- transitionEvent = get(this, 'transitionEvent');
- Ember.assert("More contexts provided than states", offset >= 0);
- arrayForEach.call(enterStates, function(state, idx) {
- state.trigger(transitionEvent, this, contexts[idx-offset]);
- }, this);
- },
- getState: function(name) {
- var state = get(this, name),
- parentState = get(this, 'parentState');
- if (state) {
- return state;
- } else if (parentState) {
- return parentState.getState(name);
- }
- },
- enterState: function(transition) {
- var log = this.enableLogging;
- var exitStates = transition.exitStates.slice(0).reverse();
- arrayForEach.call(exitStates, function(state) {
- state.trigger('exit', this);
- }, this);
- arrayForEach.call(transition.enterStates, function(state) {
- if (log) { Ember.Logger.log("STATEMANAGER: Entering " + get(state, 'path')); }
- state.trigger('enter', this);
- }, this);
- set(this, 'currentState', transition.finalState);
- }
- });
- })();
- (function() {
- /**
- Ember States
- @module ember
- @submodule ember-states
- @requires ember-runtime
- */
- })();
- })();
- // Version: v1.0.0-rc.1
- // Last commit: 8b061b4 (2013-02-15 12:10:22 -0800)
- (function() {
- /**
- Ember
- @module ember
- */
- })();
|