Important Announcement
PubHTML5 Scheduled Server Maintenance on (GMT) Sunday, June 26th, 2:00 am - 8:00 am.
PubHTML5 site will be inoperative during the times indicated!

Home Explore Front -End Web Development

Front -End Web Development

Published by Angarag, 2022-12-06 17:43:43

Description: Front -End Web Development

Search

Read the Text Version

["{{\/each}} <\/div> Similar\tto\tsightings,\tyou\tare\tlisting\tthe\tcryptids\tin\tstyled\twells.\tThe\t{{else}}\tblock allows\tyou\tto\tinclude\ta\tcondition\tin\tyour\ttemplate\twhen\tthere\tare\tno\titems\tin\tthe\tarray you\tare\tlisting.\tYour\tapp\tcan\trender\ta\tdifferent\telement\tfor\tthis\tconditional\tof\tan\tempty model\tarray. Navigate\tto\thttp:\/\/l\u200b ocalhost:4200\/c\u200b ryptids\tto\tsee\tyour\tnew\tcryptid\tlisting (Figure\t23.3). Figure\t23.3\t\tCryptids\tlisting Now,\tin\tapp\/routes\/cryptids.js,\tremove\tthe\tmodel\tcallback\tby\tcommenting\tout the\treturn\tstatement\tto\texercise\tthe\telse\tof\tyour\tconditional. ... \t\tmodel(){ \t\t\t\t\/\/\treturn\tthis.store.findAll('cryptid'); \t\t} }); Reload\thttp:\/\/\u200blocalhost:4200\/c\u200b ryptids.\tYour\tcryptid\tlisting\tis\tnow\tblank (Figure\t23.4).","Figure\t23.4\t\tEmpty\tcryptids\tlisting With\t{{each}}\t{{else}}\t{{\/each}},\tyou\tcan\thave\tconditional\tviews\tbased\ton\tthe presence\tof\tdata\tand\tvery\tlittle\tconditional\tlogic.\tNow\tthat\tyou\thave\tseen\t(and\ttested)\tthe wonders\tof\tconditional\titerators,\treturn\tapp\/routes\/cryptids.js\tto\tits\tprevious state: ... \t\tmodel(){ \t\t\t\t\/\/\treturn\tthis.store.findAll('cryptid'); \t\t} }); Binding\telement\tattributes Element\tattribute\tvalues\tcan\tbe\trendered\tfrom\tcontroller\tproperties\tjust\tas\telement\tcontent is\trendered\tbetween\tDOM\telement\ttags.\tIn\tearlier\tversions\tof\tEmber,\tthere\twas\ta\thelper\tto bind\tattributes\tcalled\t{{bind-attr}}.\tNow,\tthanks\tto\tHTMLBars,\tyou\tcan\tyou\tjust\tuse {{}}\tto\tbind\ta\tproperty\tto\tthe\tattribute. Attribute\tbinding\tis\tcommon\twith\telement\tproperties\tlike\tclass\tand\tsrc.\tYour\tcryptids have\tan\timage\tpath\tin\ttheir\tdata,\tso\tyou\tcan\tdynamically\tbind\ttheir\tsrc\tproperty\tto\ta model\tattribute. Add\tan\timage\tto\tthe\tcryptid\tlisting\tin\tapp\/templates\/cryptids.hbs: <div\tclass=\\\"row\\\"> \t\t{{#each\tmodel\tas\t|cryptid|}} \t\t\t\t<div\tclass=\\\"col-xs-12\tcol-sm-3\ttext-center\\\"> \t\t\t\t\t\t<div\tclass=\\\"media\twell\\\"> \t\t\t\t\t\t\t\t<img\tclass=\\\"media-object\tthumbnail\\\"\tsrc=\\\"{{cryptid.profileImg}}\\\" \t\t\t\t\t\t\t\t\t\talt=\\\"{{cryptid.name}}\\\"\twidth=\\\"100%\\\"\theight=\\\"100%\\\"> \t\t\t\t\t\t\t\t<div\tclass=\\\"caption\\\"> \t\t\t\t\t\t\t\t\t\t<h3>{{cryptid.name}}<\/h3>","<\/div> \t\t\t\t\t\t<\/div> \t\t\t\t<\/div> \t\t{{else}} \t\t\t\t<div\tclass=\\\"jumbotron\\\"> \t\t\t\t\t\t<h1>No\tCreatures<\/h1> \t\t\t\t<\/div> \t\t{{\/each}} <\/div> You\twill\tneed\tto\tadd\tthe\tcryptid\timages\tfrom\tyour\tcourse\tassets\tto\tthe\tdirectory tracker\/public\/assets\/image\/cryptids.\tWhen\tthe\tEmber\tserver\tis\trunning, the\tpublic\tdirectory\tis\tthe\troot\tfor\tassets.\tFor\ta\tproduction\tapplication\tyou\tmay\tneed\tto configure\tthese\tpaths,\tbut\tfor\tdevelopment,\tpublic\/assets\tis\ta\tgood\tplace\tto\twork. These\tfiles\tare\tcopied\tto\tthe\tdist\tdirectory\twhere\tyour\tapplication\tis\tcompiled\tand served. When\tdeploying\tan\tapplication\tto\ta\tserver\tand\tadding\timages\tto\tpersisted\tdata\tyou\tneed\tto be\tconscious\tof\tthe\tpath\tto\tthe\tactual\timage\tfile.\tIn\tour\texample,\twe\tare\tserving\tthe images\tfrom\tthe\tsame\tdirectory\tas\tour\tapplication,\tand\tthe\tdatabase\tstores\tthe\trelative path\tof\tthe\timage\tto\tour\tapplication. Before\tHTMLBars,\tthe\t{{bind-attr}}\thelper\tcould\tbe\tused\tas\tan\tinline\tternary\toperation for\tassigning\tproperties\tbased\ton\ta\tBoolean\tproperty.\tNow,\tyou\tcan\tuse\tthe\tinline\t{{if}} helper.\tThis\tis\tcommon\twhen\tyour\tUI\thas\tstyles\tthat\trepresent\ttrue\tand\tfalse\tstates\tof\ta specific\tproperty. Use\tthe\tternary\tform\tin\tapp\/templates\/cryptids.hbs\tto\thandle\tmissing\timages: <div\tclass=\\\"row\\\"> \t\t{{#each\tmodel\tas\t|cryptid|}} \t\t\t\t<div\tclass=\\\"col-xs-12\tcol-sm-3\ttext-center\\\"> \t\t\t\t\t\t<div\tclass=\\\"media\twell\\\"> \t\t\t\t\t\t\t\t<img\tclass=\\\"media-object\tthumbnail\\\"\tsrc=\\\"{{cryptid.profileImg}}\\\" \t\t\t\t\t\t\t\t\t\tsrc=\\\"{{if\tcryptid.profileImg\tcryptid.profileImg \t\t\t\t\t\t\t\t\t\t'assets\/images\/cryptids\/blank_th.png'}}\\\" \t\t\t\t\t\t\t\t\t\talt=\\\"{{cryptid.name}}\\\"\twidth=\\\"100%\\\"\theight=\\\"100%\\\"> \t\t\t\t\t\t\t\t<div\tclass=\\\"caption\\\"> \t\t\t\t\t\t\t\t... Unlike\tthe\tblock\t{{#if}}\thelper,\tthe\tinline\t{{if}}\thelper\tdoes\tnot\tyield\tblock\tcontent. The\tinline\thelper\tevaluates\tthe\tfirst\targument\tas\ta\tBoolean\tand\toutputs\teither\tthe\tsecond or\tthird\targument. Here,\tyou\tare\tevaluating\tthe\ttruthiness\tof\t{{cryptid.profileImg}}\tfor\tthe\tfirst\targument. If\tit\tis\ttruthy,\tthe\toutput\tis\tthe\tcryptid\u2019s\timage\tpath.\tOtherwise,\ta\tplaceholder\timage\tis specified. You\tcan\tuse\ta\tdynamic\tvalue\tas\tan\targument\tto\tall\tinline\thelpers.\tYou\tcan\talso\tpass\tany JavaScript\tprimitive\tas\tan\targument,\tsuch\tas\ta\tstring,\tnumber,\tor\tBoolean. Before\tyou\tlook\tat\tthe\tresults\tof\tyour\tconditional,\tcreate\ta\tcryptid\twithout\tan\timage\tpath in\tthe\tbeforeModel\thook\tin\tapp\/routes\/cryptids.js: import\tEmber\tfrom\t'ember'; export\tdefault\tEmber.Route.extend({ \t\tbeforeModel(){ \t\t\t\tthis.store.createRecord('cryptid',\t{ \t\t\t\t\t\t\\\"name\\\":\t\\\"Charlie\\\", \t\t\t\t\t\t\\\"cryptidType\\\":\t\\\"unicorn\\\" \t\t\t\t}); \t\t}, \t\tmodel(){ \t\t\t\treturn\tthis.store.findAll('cryptid'); \t\t} }); Now\treload\thttp:\/\/l\u200b ocalhost:4200\/c\u200b ryptids\tand\tcheck\tout\tyour\tnew\timages","(Figure\t23.5). Figure\t23.5\t\tCryptids\tlisting\twith\timages Links As\tdiscussed\tin\tChapter\t20,\trouting\tis\tunique\tto\tbrowser-based\tapplications.\tEmber\tlistens to\ta\tcouple\tof\tevent\thooks\tto\tmanage\trouting\tin\tyour\tapplication.\tFor\tthis\treason,\tyou should\tcreate\tlinks\twith\t{{#link-to}}\tblock\thelpers.\tThis\thelper\ttakes\tthe\troute (represented\tby\ta\tstring)\tas\tthe\tfirst\targument\tto\tcreate\tan\tanchor\telement.\tFor\texample, {{#link-to\t'index'}}Home{{\/link-to}}\tcreates\ta\tlink\tto\tthe\troot\tindex\tpage. To\tsee\thow\tthis\tworks,\tyou\tare\tgoing\tto\tupdate\tyour\tmain\tnavigation\twith\tlinks\tusing {{#link-to}}\thelpers. Begin\tin\tapp\/templates\/application.hbs.\tReplace\tthe\tNavBar\u2019s\ttest\tlinks\twith links\tto\tyour\tsightings,\tcryptids,\tand\twitnesses: ... \t\t\t\t\t\t<div\tclass=\\\"collapse\tnavbar-collapse\\\"\tid=\\\"top-navbar-collapse\\\"> \t\t\t\t\t\t\t\t<ul\tclass=\\\"nav\tnavbar-nav\\\"> \t\t\t\t\t\t\t\t\t\t<li> \t\t\t\t\t\t\t\t\t\t\t\t<a\thref=\\\"#\\\">Test\tLink<\/a> \t\t\t\t\t\t\t\t\t\t<\/li> \t\t\t\t\t\t\t\t\t\t<li> \t\t\t\t\t\t\t\t\t\t\t\t<a\thref=\\\"#\\\">Test\tLink<\/a> \t\t\t\t\t\t\t\t\t\t<\/li> \t\t\t\t\t\t\t\t\t\t<li> \t\t\t\t\t\t\t\t\t\t\t\t{{#link-to\t'sightings'}}Sightings{{\/link-to}} \t\t\t\t\t\t\t\t\t\t<\/li> \t\t\t\t\t\t\t\t\t\t<li> \t\t\t\t\t\t\t\t\t\t\t\t{{#link-to\t'cryptids'}}Cryptids{{\/link-to}} \t\t\t\t\t\t\t\t\t\t<\/li> \t\t\t\t\t\t\t\t\t\t<li> \t\t\t\t\t\t\t\t\t\t\t\t{{#link-to\t'witnesses'}}Witnesses{{\/link-to}} \t\t\t\t\t\t\t\t\t\t<\/li>","<\/ul> \t\t\t\t\t\t<\/div><!--\t\/.navbar-collapse\t--> Now\tthat\tyou\thave\tlinks\tto\tyour\tlisting\tpages,\treload\tyour\tapp\tand\ttest\tthem.\tClick around.\tHit\tthe\tback\tbutton.\tYou\thave\ta\tworking\tweb\tapp!\tTake\ta\tmoment\tto\tcelebrate. Your\tnext\ttask\tis\tto\tmake\tthe\timages\ton\tthe\tcryptids\tpage\tlink\tto\tan\tindividual\tpage\tfor each\tcreature.\tTo\tdo\tthis,\tyou\twill\ttake\tadvantage\tof\tthe\tfact\tthat\tthe\t{{#link-to}}\thelper can\ttake\tmultiple\targuments\tto\tcustomize\tthe\tlink.\tIn app\/templates\/cryptids.hbs,\twrap\tthe\t<img>\ttag\twith\ta\tlink\tto\ta\tspecific cryptid\tusing\tcryptid.id\tas\tthe\tsecond\targument: ... \t\t\t\t<div\tclass=\\\"media\twell\\\"> \t\t\t\t\t\t{{#link-to\t'cryptid'\tcryptid.id}} \t\t\t\t\t\t\t\t<img\tclass=\\\"media-object\tthumbnail\\\" \t\t\t\t\t\t\t\tsrc=\\\"{{if\tcryptid.profileImg\tcryptid.profileImg \t\t\t\t\t\t\t\t'assets\/images\/cryptids\/blank_th.png'}}\\\" \t\t\t\t\t\t\t\talt=\\\"{{cryptid.name}}\\\"\twidth=\\\"100%\\\"\theight=\\\"100%\\\"> \t\t\t\t\t\t{{\/link-to}} \t\t\t\t\t\t<div\tclass=\\\"caption\\\"> \t\t\t\t\t\t\t\t<h3>{{cryptid.name}}<\/h3> \t\t\t\t\t\t<\/div> \t\t\t\t<\/div> ... Now\tthat\tyou\thave\tlinks\tto\tthe\tcryptid\troute\tand\tthe\tanchor\thas\ta\tpath\tto\tcryptids\/\u200b [cryptid_id],\tyou\twill\tneed\tto\tedit\trouter.js\tso\tthe\tCryptidRoute\tknows\tto expect\ta\tdynamic\tvalue. ... Router.map(function()\t{ \t\tthis.route('sightings',\tfunction()\t{ \t\t\t\tthis.route('new'); \t\t}); \t\tthis.route('sighting',\tfunction()\t{ \t\t\t\tthis.route('edit'); \t\t}); \t\tthis.route('cryptids'); \t\tthis.route('cryptid',\t{path:\t'cryptids\/:cryptid_id'}); \t\tthis.route('witnesses'); \t\tthis.route('witness'); }); ... Try\tit\tout.\tYou\tshould\tsee\ta\tblank\tpage\tafter\tclicking\tone\tof\tthe\tcryptid\timages.\tThis\tis good.\tYour\tapp\tis\trouting\tyou\tto\tthe\tCryptidRoute,\twhich\tis\trendering\tthe app\/templates\/cryptid.hbs,\tsingular.\tThat\tfile\tis\tcurrently\tblank. If\tyou\tclicked\ton\tCharlie\tthe\tunicorn\u2019s\timage,\tyou\tprobably\tgot\tan\terror.\tRecall\tthat\this record\twas\tcreated\tin\tthe\tbeforeModel\thook\tand\tdoes\tnot\thave\tan\tid.\tThat\tmeans\tthat the\tvalue\tit\ttries\tto\tpass\tto\tthe\t{{#link-to}}\thelper\tis\tnull. This\tis\ta\tgood\ttime\tto\tremove\tthat\tbeforeModel\thook.\tCreation\tshould\tbe\treserved\tfor pages\tcreating\tnew\tcryptids,\twhich\twe\twill\tcover\tin\tChapter\t24. Remove\tthe\thook\tfrom\tapp\/routes\/cryptids.js. ... \t\tbeforeModel(){ \t\t\t\tthis.store.createRecord('cryptid',\t{ \t\t\t\t\t\t\\\"name\\\":\t\\\"Charlie\\\", \t\t\t\t\t\t\\\"cryptidType\\\":\t\\\"unicorn\\\" \t\t\t\t}); \t\t}, \t\tmodel(){ \t\t\t\treturn\tthis.store.findAll('cryptid'); \t\t} ... Next,\tadd\tthe\trequest\tfor\tcryptid\tdata\tin\tthe\tapp\/routes\/cryptid.js\t(singular). import\tEmber\tfrom\t'ember'; export\tdefault\tEmber.Route.extend({","model(params){ \t\t\t\treturn\tthis.store.findRecord('cryptid',\tparams.cryptid_id); \t\t} }); The\tcryptid_id\tdynamic\troute\tparameter\tis\tpassed\tto\tthe\troute\u2019s\tmodel\thook\tas\tan argument.\tYou\tuse\tthis\tparameter\tto\tcall\tthe\tstore\u2019s\tfindRecord\tmethod. Now,\tedit\tthe\ttemplate\tfor\tan\tindividual\tcryptid,\tapp\/templates\/cryptid.hbs,\tto show\tthe\tcryptid\u2019s\timage\tand\tname. {{outlet}} <div\tclass=\\\"container\ttext-center\\\"> \t\t<img\tclass=\\\"img-rounded\\\"\tsrc=\\\"{{model.profileImg}}\\\"\talt=\\\"{{model.type}}\\\"> \t\t<h3>{{model.name}}<\/h3> <\/div> The\tmodel\tpassed\tto\tthis\ttemplate\tis\ta\tsingle\tobject,\tnot\tan\tarray\tof\tobjects\tto\titerate over.\tThe\tthis.store.findRecord\tmethod\treturns\ta\tsingle\tcryptid\tinstance.\tIn the\ttemplate,\tthe\tmodel\tis\tthis\tinstance\tand\tthe\tproperties\tare\tretrieved\tusing\t{{model. [property-name]}}. In\tthe\tbrowser,\tuse\tyour\tNavBar\tto\tnavigate\tto\tCryptids\tand\tthen\tclick\tone\tof\tthe\tcryptid images\tto\tview\tits\tdetail\tpage\t(Figure\t23.6). Figure\t23.6\t\tCryptid\tdetail\tpage You\twill\texplore\t{{#link-to}}\tmore\tin\tfuture\tchapters.\tRemember,\thelpers\tare\tfunctions that\tare\tinvoked\twhen\tthe\ttemplate\tis\trendered.\tEmber\tcomes\twith\tits\town,\tbut\tyou\tare\tnot limited\tto\tthe\tbuilt-in\thelpers.","Custom\tHelpers The\tsighting\u2019s\tsightedAt\tdate\tproperty\tis\tdisplayed\tas\tan\tugly\traw\tdate\tstring.\tTo format\tyour\tdates\tmore\tnicely,\tyou\tare\tgoing\tto\tuse\tthe\tsame\tmoment\tlibrary\tyou\tused\tin Chattrbox. Add\tmoment\tfrom\tthe\tterminal: bower\tinstall\tmoment\t--save Then,\tuse\tapp.import\tto\tadd\tmoment\tto\tyour\tvendor\tassets\tin\tember-cli-build.js: ... \t\t\/\/\tAdd\tassets\tto\tapp\twith\timport \t\tapp.import(bootstrapPath\t+\t'javascripts\/bootstrap.js'); \t\tapp.import('bower_components\/moment\/moment.js'); \t\treturn\tapp.toTree(); }; Changes\tto\tyour\tserver\tconfigurations\trequire\ta\trestart,\tso\tafter\tyou\tchange\tthe\tember- cli-build.js,\tstop\tyour\tEmber\tserver\t(Control+C)\tand\tstart\tit\tback\tup\t(ember server). Now\tyou\tneed\tto\tgenerate\tthe\thelper\tmodule\tfrom\tthe\tterminal: ember\tg\thelper\tmoment-from From\there,\tyou\twill\tcreate\ta\tfunction\tthat\twill\treturn\tHTML\tas\ta\tstring.\tThe\tfunction\twill have\ta\tdate\tas\tan\targument,\tand\tit\twill\tprocess\tthe\tdate\twith\tthe\tmoment.js\tlibrary.\tYou will\tsurround\tthe\tdate\twith\tan\tHTML\t<span>\ttag\tand\tapply\ta\tBootstrap\ttext\tutility\tto\tthe element. Open\tthe\tgenerated\tfile,\tapp\/helpers\/moment-from.js,\tand\tcreate\tthis\tnew function: import\tEmber\tfrom\t'ember'; export\tfunction\tmomentFrom(params\/*,\thash*\/)\t{ \t\treturn\tparams; } export\tfunction\tmomentFrom(params)\t{ \t\tvar\ttime\t=\twindow.moment(...params); \t\tvar\tformatted\t=\ttime.fromNow(); \t\treturn\tnew\tEmber.Handlebars.SafeString( \t\t\t\t'<span\tclass=\\\"text-primary\\\">' \t\t\t\t+\tformatted\t+\t'<\/span>' \t\t); } export\tdefault\tEmber.Helper.helper(momentFrom); Now\tthat\tyou\thave\tcreated\tthe\thelper,\tuse\tit\tin\tyour app\/templates\/sightings\/index.hbs\ttemplate: ... \t\t\t\t\t\t\t\t\t\t{{#if\tsighting.location}} \t\t\t\t\t\t\t\t\t\t\t\t<h3>{{sighting.location}}\t-\t{{sighting.sightedAt}}<\/h3> \t\t\t\t\t\t\t\t\t\t\t\t<p>{{moment-from\tsighting.sightedAt}}<\/p> \t\t\t\t\t\t\t\t\t\t{{else}} ... The\tmoment-from\thelper\ttakes\ta\tsingle\targument\tand\treturns\tthe\tformatted\tdate\tas\ta\tstring of\tHTML.\tWhen\tyou\tuse\ta\tcustom\thelper\tto\trender\tHTML\telements,\tEmber\tprovides\tthe Ember.Handlebars.SafeString\tmethod\tto\toutput\tclean\tmarkup. Check\tout\tyour\tnewly\tformatted\tdates\t(Figure\t23.7).","Figure\t23.7\t\tSightings\tmoment.js\tdate The\tmoment.js\tlibrary\ttakes\tthe\tdate\tobject\tand\tformats\tit\tas\tsomething\tlike\t\u201c2\tmonths ago.\u201d\tThat\tlooks\tmuch\tbetter\tthan\t\u201cTue\tFeb\t9\t2016\t19:00:00\tGMT-0500\t(EST)\u201d!\t(There are\tmany\toptions\tin\tmoment.js\tfor\tformatting\tdates\t\u2013\texplore\tmomentjs.com\tto\tfind out\tmore.) By\tcreating\ta\thelper\tto\toutput\tthe\tspecific\ttext,\tyou\tcan\tremove\tsome\tlogic\tfrom\tthe template.\tCustom\thelpers\tallow\tyou\tto\tcut\tdown\ton\trepetition\tand\tcentralize\tUI formatting.\tThis\tis\tthe\tfirst\tstep\tto\tabstracting\tyour\tcode\tinto\tEmber\tComponents,\twhich\tyou will\tlearn\tabout\tin\tChapter\t25. In\tthis\tchapter,\tyou\tlearned\tto\tdisplay\tbasic\tmodel\tproperties\tand\tcustomize\tyour templates\twith\tconditionals\tand\tloops.\tYou\tbound\tHTML\telement\tattributes\tto\tproperties and\tcreated\tnew\troutes\twith\tdynamic\tattributes\tto\tload\tindividual\trecords\tas\tthe\tmodel backing\tthe\ttemplate.\tFinally,\tyou\tused\thelpers\tfor\tlinking\tto\tpages\tand\trounded\tout\tyour understanding\tof\thelpers\tby\tbuilding\tyour\town\thelper\tto\tformat\tdates. In\tthe\tnext\tchapter,\tyou\twill\tcomplete\tthe\tapplication\tlifecycle\tby\tcreating\tand\tediting\tdata with\tcontrollers.\tYou\twill\tlearn\tabout\tactions,\tretrieve\tmultiple\tcollections\tfrom\tyour\tdata store,\tand\tcreate\tdecorators.","Bronze\tChallenge:\tAdding\tLink\tRollovers The\tlinks\tyou\tcreated\tneed\tsome\trollover\tcontent.\tTo\tachieve\tthis,\tadd\ta\ttitle\tattribute\tto your\t{{#link-to}}\thelpers.\tThe\ttitle\tshould\tbe\tthe\tsighting\u2019s\tcryptid\tname.","Silver\tChallenge:\tChanging\tthe\tDate\tFormat The\t{{moment-from}}\thelper\thas\tmade\tthe\tdate\tless\tclunky,\tbut\tnow\tit\tis\tnot\tas informative\tas\tit\tshould\tbe.\tReview\tthe\tmoment\tdocumentation\tand\tchange\tthe\toutput\tof the\thelper\tto\tformat\tthe\tdate\tas\t\u201cSunday\tMay\t31,\t2016.\u201d","Gold\tChallenge:\tCreating\ta\tCustom\tThumbnail\tHelper The\tmarkup\tfor\tthe\tcryptid\tthumbnails\tseems\ta\tbit\tlong.\tCreate\ta\tcustom\thelper\tto\tdisplay cryptid\tthumbnails\tby\tpassing\tthe\timage\tpath\tto\tthe\thelper.\tClean\tup\tyour\tcode\tby\tusing the\thelper\tin\tall\tthe\tplaces\tthe\tcryptid\timages\tappear.","24\t Controllers Controllers\tare\tthe\tlast\tpiece\tof\tthe\tMVC\tpattern.\tAs\tyou\tlearned\tin\tChapter\t19, controllers\thold\tapplication\tlogic,\tretrieve\tmodel\tinstances\tand\tgive\tthem\tto\tviews,\tand contain\thandler\tfunctions\tthat\tmake\tchanges\tto\tmodel\tinstances. The\tcontrollers\tyou\twill\tbuild\tin\tthis\tchapter\twill\tnot\tbe\tparticularly\tlarge\tpieces\tof\tcode. That\tis\tthe\tidea\tbehind\tMVC:\tdistributing\tthe\tcomplexity\tof\tan\tapplication\tto\tthe\tplaces\tit belongs.\tManaging\tthe\tdata\tis\tthe\tjob\tof\tthe\tmodels,\tand\thandling\tthe\tUI\tis\tthe\tprovince\tof the\tviews.\tThe\tcontroller\tonly\tneeds\tto,\twell,\tcontrol\tthe\tmodels\tand\tviews. Without\tyour\tknowledge,\tEmber\thas\tbeen\tadding\tcontroller\tobjects\tto\tyour\tapplication when\tit\tis\trunning.\tControllers\tare\ta\tproxy\tbetween\tthe\troute\tobject\tand\tthe\ttemplate, passing\tthe\tmodel\tthrough.\tWhen\tyou\tdo\tnot\tadd\ta\tcontroller\tobject,\tEmber\tknows\tthat\tthe model\tdata\tis\tsufficient\tto\tpass\tto\tthe\ttemplate,\tand\tit\tdoes\tthat\tfor\tyou. Creating\ta\tcontroller\tin\tEmber\tallows\tyou\tto\tdefine\tthe\tevents\tor\tactions\tto\tlisten\tfor\twhen the\troute\tis\tactive.\tIt\talso\tallows\tyou\tto\tdefine\tdecorator\tproperties\tto\taugment\tmodel\tdata you\twant\tto\tdisplay\twithout\tpersisting\tit. One\tof\tthe\tgoals\tof\tthe\tTracker\tapplication\tis\tfor\tthe\tuser\tto\tbe\table\tto\tcreate\tnew sightings.\tFor\tthis\tgoal,\tyou\twill\tneed\tto\tcreate\ta\troute,\ta\tcontroller,\tcontroller\tproperties, and\tcontroller\tactions. You\thave\talready\tcreated\tthe\tnew\tsighting\troute,\tapp\/routes\/sightings\/new.js. For\tthis\tpage,\tyou\tare\tyou\tgoing\tto\tcreate\ta\tnew\tsighting\trecord\tand\tload\tthe\tcollections\tof cryptids\tand\twitnesses.\tEach\tnew\tsighting\twill\tneed\tthe\trelationships\tof\tbelonging\tto\ta cryptid\tand\thaving\tone\tor\tmany\twitnesses.\tThe\tform\tyou\twill\tcreate\twill\tlook\tlike Figure\t24.1.","Figure\t24.1\t\tTracker\u2019s\tNew\tSighting\tform When\tyou\thave\tall\tof\tthat\tset\tup,\tyou\twill\tcreate\ta\tcontroller\tto\tmanage\tevents\tfrom\tthe new\tsighting\tform.\tYou\twill\talso\texpand\ton\tyour\twork\tto\tallow\texisting\tsightings\tto\tbe edited\tand\tdeleted. New\tSightings The\tSightingsRoute\u2019s\tmodel\tthat\tyou\thave\tset\tup\treturns\tall\tthe\tsightings.\tFor\tthe\tnew sightings\tmodel,\tyou\twill\treturn\tthe\tresult\tof\tcreating\ta\tsingle\tnew,\tempty\tsighting.\tAlso, you\twill\treturn\ta\tset\tof\tPromises\tfor\tthe\tcryptids\tand\twitnesses.\tTo\tdo\tthis\tyou\twill\treturn Ember.RSVP.hash({}). Let\u2019s\tget\tstarted.\tOpen\tapp\/routes\/sightings\/new.js\tand\tadd\ta\tmodel\thook\tto return\ta\tcollection\tof\tPromises\tas\tan\tEmber.RSVP.hash: ... export\tdefault\tEmber.Route.extend({ \t\tmodel()\t{ \t\t\t\treturn\tEmber.RSVP.hash({ \t\t\t\t\t\tsighting:\tthis.store.createRecord('sighting') \t\t\t\t}); \t\t} }); When\tthis\troute\tis\tactive,\ta\tnew\trecord\tof\ta\tsighting\tis\treturned.\tIf\tyou\twere\tto\treturn\tto the\tsightings,\tyou\twould\tsee\ta\tblank\tentry,\tbecause\tyou\tcreated\ta\tnew\trecord.\tYou\twill handle\tthe\tdirty\trecords\t(model\tdata\tthat\thas\tbeen\tchanged\tbut\tnot\tsaved\tto\tthe\tpersisted source)\ttoward\tthe\tend\tof\tthis\tchapter.\tFor\tnow,\tknow\tthat\tcreateRecord\thas\tadded\ta new\tsighting\tto\tthe\tlocal\tcollection.","When\tcreating\ta\tnew\tsighting,\tyou\twill\tneed\tthe\tlist\tof\tcryptids\tand\twitnesses.\tHere, Ember.RSVP.hash({})\tis\tused\tto\tsay\tyou\tare\treturning\ta\thash\tof\tPromises.\tThe\tonly\tkey is\tsighting,\twhich\tmeans\tthat\tyour\tmodel\treference\tin\tthe\ttemplate\twill\tneed\tto\tdo\ta look-up\ton\tmodel.sighting\tto\treference\tthe\tsighting\trecord\tyou\tcreated. Add\tthe\tretrieval\tmethods\tfor\tcryptids\tand\twitnesses\tto\tthis\thash\t(do\tnot\tneglect\tthe comma\tafter\tthis.store.createRecord('sighting')). ... export\tdefault\tEmber.Route.extend({ \t\tmodel()\t{ \t\t\t\treturn\tEmber.RSVP.hash({ \t\t\t\t\t\tsighting:\tthis.store.createRecord('sighting'), \t\t\t\t\t\tcryptids:\tthis.store.findAll('cryptid'), \t\t\t\t\t\twitnesses:\tthis.store.findAll('witness') \t\t\t\t}); \t\t} } Next,\tyou\tare\tgoing\tto\tuse\t<select>\ttags\tin\tyour\tnew\tsightings\ttemplate\tto\tpresent\tthe lists\tof\tcryptids\tand\twitnesses\tto\tthe\tuser.\tBut\tbefore\tyou\tset\tup\tyour\ttemplate\twith\tthe new\tmodel\tdata,\tyou\twill\tneed\tan\tEmber\tCLI\tplug-in\tthat\tmakes\tit\teasy\tto\tuse\t<select> tags\twith\tbound\tproperties. From\tthe\tcommand\tline,\tinstall\temberx-select: ember\tinstall\temberx-select You\twill\tuse\tthis\tcomponent,\tusually\tcalled\tx-select,\tin\tyour\ttemplate.\tThis\tsaves\tyou\tfrom writing\tonchange\tactions\tfor\teach\t<select>\ttag. Restart\tember\tserver\tbefore\tusing\tthe\tx-select\tcomponent. With\tall\tthe\tmodel\tdata\tset\tup,\tyou\tcan\tnow\tedit\tthe\ttemplate, app\/templates\/sightings\/new.hbs,\tto\tcreate\tthe\tnew\tsighting\tform: <h1>New\tRoute<\/h1> <h1>New\tSighting<\/h1> <form> \t\t<div\tclass=\\\"form-group\\\"> \t\t\t\t<label\tfor=\\\"name\\\">Cryptid<\/label> \t\t\t\t{{#x-select\tvalue=model.sighting.cryptid\tclass=\\\"form-control\\\"}} \t\t\t\t\t\t{{#x-option}}Select\tCryptid{{\/x-option}} \t\t\t\t\t\t{{#each\tmodel.cryptids\tas\t|cryptid|}} \t\t\t\t\t\t\t\t{{#x-option\tvalue=cryptid}}{{cryptid.name}}{{\/x-option}} \t\t\t\t\t\t{{\/each}} \t\t\t\t{{\/x-select}} \t\t<\/div> \t\t<div\tclass=\\\"form-group\\\"> \t\t\t\t<label>Witnesses<\/label> \t\t\t\t{{#x-select\tvalue=model.sighting.witnesses\tmultiple=true\tclass=\\\"form-control\\\"}} \t\t\t\t\t\t{{#x-option}}Select\tWitnesses{{\/x-option}} \t\t\t\t\t\t{{#each\tmodel.witnesses\tas\t|witness|}} \t\t\t\t\t\t\t\t{{#x-option\tvalue=witness}}{{witness.fullName}}{{\/x-option}} \t\t\t\t\t\t{{\/each}} \t\t\t\t{{\/x-select}} \t\t<\/div> \t\t<div\tclass=\\\"form-group\\\"> \t\t\t\t<label\tfor=\\\"location\\\">Location<\/label>\t{{input\tvalue=model.sighting.location \t\t\t\t\t\ttype=\\\"text\\\"\tclass=\\\"form-control\\\"\tname=\\\"location\\\"\trequired=true}} \t\t<\/div> <\/form> Wow!\tThat\thas\tgot\teverything\tyou\thave\tbeen\tworking\ton\t\u2013\tand\tmore.\tThe\troute\tis\tusing\ta new\tEmber.RSVP\tmethod,\tthe\ttemplate\tis\tusing\thelpers,\tand\tthe\tnew\t{{x-select}}\tand {{x-option}}\tcomponents\tare\tused. The\t{{x-select}}\tcomponent\tis\tbuilt\tto\tuse\tthe\t<select>\telement\tto\tassign\ta\tvalue\tto\ta property.\tIt\tis\tdesigned\tto\twork\tjust\tlike\ta\t<select>\telement\tin\tthe\tEmber\tdata-binding environment.\tYou\tassign\tthe\tvalue\tto\tthe\tsightings\tmodel\u2019s\tcryptid\tproperty\tand\tthe component\twill\thandle\tthe\tonchange\tevent\twhen\ta\tnew\toption\tis\tselected.\tThis\tworks because\tthe\tcryptid\tproperty\tneeds\ta\tcryptid\tmodel\trecord\tas\tits\tvalue.","For\tthe\twitnesses,\tthere\tis\tan\textra\tattribute,\tmultiple=true,\twhich\twill\tallow\tyour\tusers to\tselect\tmultiple\twitnesses\tfor\ta\tsighting.\tMultiple\tselections\twill\ttranslate\tinto\ta collection\tof\thasMany\twitnesses. Before\tyou\tgo\tany\tfurther,\tyou\twill\tneed\tto\tadd\ta\tlink\tto\tthe\tsightings\troute\ttemplate\tso you\thave\ta\tway\tto\tget\tto\tthe\tsightings.new\troute.\tOpen app\/templates\/sightings.hbs\tand\ttake\tcare\tof\tthat. <h1>Sightings<\/h1> <div\tclass=\\\"row\\\"> \t\t<div\tclass=\\\"col-xs-6\\\"> \t\t\t\t<h1>Sightings<\/h1> \t\t<\/div> \t\t<div\tclass=\\\"col-xs-6\th1\\\"> \t\t\t\t{{#link-to\t\\\"sightings.new\\\"\tclass=\\\"pull-right\tbtn\tbtn-primary\\\"}} \t\t\t\t\t\tNew\tSighting \t\t\t\t{{\/link-to}} \t\t<\/div> <\/div> {{outlet}} Your\tlink\ttakes\tadvantage\tof\tthe\tsimplicity\tof\tBootstrap\tformatting\tto\tcreate\ta\tbutton.\tLoad http:\/\/l\u200b ocalhost:4200\/s\u200b ightings\tto\tsee\tit\t(Figure\t24.2). Figure\t24.2\t\tNew\tSighting\tbutton Now\tyou\thave\tthe\tability\tto\tnavigate\tto\tthe\tsightings.new\troute.\tThe\tnew\tbutton\tadds\ta link\tto\tcreate\ta\tnew\tsighting\tfrom\tanywhere\tin\tthe\tsightings\troute\ttree\tstructure. Also,\tnotice\tthat\twhen\tyou\tare\ton\tthe\tsightings.new\troute,\tthe\tbutton\tis\tactive.\tEmber\thas thought\tof\tit\tall,\tgiving\tan\tactive\tclass\tto\ta\tlink\teven\twhen\tthe\tcurrent\troute\tis\tthe\tlink\u2019s route.\tHaving\tan\tactive\tstate\ton\ta\tbutton\tor\tlink\tsignifies\tthe\tcurrent\troute\tin\tthe\tform\tof UI\tcues.","Actions\tare\tthe\tkey\tto\thandling\tform\tevents\t\u2013\tor\tany\tother\tevents\tin\tyour\tapp.\tThe actions\tproperty\tis\ta\thash\twhere\tyou\tassign\tfunctions\tto\tkeys.\tThe\tkeys\twill\tbe\tused\tin the\ttemplates\tto\ttrigger\tthe\tcallback. You\tare\tready\tto\tcreate\tthe\tcontroller\tfor\tthe\tsightings.new\troute,\tusing\tthis\tterminal command: ember\tg\tcontroller\tsightings\/new Ember\tcreates\tthe\tapp\/controllers\/sightings\/new.js\tfile.\tOpen\tit\tand\tadd\tthe create\tand\tcancel\tactions\tyou\twill\tneed\tto\tcreate\tsightings: import\tEmber\tfrom\t'ember'; export\tdefault\tEmber.Controller.extend({ \t\tactions:\t{ \t\t\t\tcreate()\t{ \t\t\t\t}, \t\t\t\tcancel()\t{ \t\t\t\t} \t\t} }); When\tcreating\ta\tform\telement,\tthe\taction\tattribute\tusually\thas\ta\tURL\tto\twhich\tyou submit\tthe\tform.\tWith\tEmber,\tthe\tform\telement\tonly\tneeds\tthe\tname\tof\ta\tfunction\tto\trun. In\tapp\/templates\/sightings\/new.hbs,\thave\tthe\tform\telement\tset\tthe\taction\tfor submit.\tAlso,\tadd\tCreate\tand\tCancel\tbuttons: <h1>New\tSighting<\/h1> <form\t{{action\t\\\"create\\\"\ton=\\\"submit\\\"}}> ... \t\t<div\tclass=\\\"form-group\\\"> \t\t\t\t<label\tfor=\\\"location\\\">Location<\/label>\t{{input\tvalue=model.location \t\t\t\t\t\ttype=\\\"text\\\"\tclass=\\\"form-control\\\"\tname=\\\"location\\\"\trequired=true}} \t\t<\/div> \t\t<button\ttype=\\\"submit\\\"\tclass=\\\"btn\tbtn-primary\tbtn-block\\\">Create<\/button> \t\t<button\t{{action\t'cancel'}}\tclass=\\\"btn\tbtn-link\tbtn-block\\\">Cancel<\/button> <\/form> Atom\tmay\tcomplain\tabout\tthe\tsyntax\tof\tthe\tlines\twith\tthe\t{{action}}\thelpers.\tYou\tcan ignore\tits\tcomplaints.\t(You\tcan\talso\tinstall\tLanguage-Mustache\tand\tenable\tit\tin\tAtom\u2019s preferences\tso\tthat\tAtom\trecognizes\tthis\tsyntax.\tThe\tpackage\tcan\tbe\tfound\tat\tatom.io\/\u200b packages\/l\u200b anguage-mustache.) The\ttwo\t{{action}}\thelpers\thave\tstring\targuments\tmatching\tthe\tactions\tyou\tcreated\tin app\/controllers\/sightings\/new.js.\tActions\tare\tbound\tto\tevent\thandlers.\tThe {{action}}\thelper\taccepts\tan\targument,\ton,\tto\twhich\tthe\taction\tis\tassigned.\tA\tclick\tevent listener\tis\tassigned\tto\tthe\taction\tif\tyou\tdo\tnot\tadd\tthe\ton\targument\tto\tthe\thelper. In\tthe\tnew\tsightings\tform,\tyou\tadded\ton=\\\"submit\\\"\tto\tspecify\tthat\tthe\tcreate\taction\twill be\tcalled\ton\tsubmit.\tYou\tdid\tnot\tgive\tan\ton\targument\tto\tthe\tcancel\tbutton,\ton\tthe\tother hand,\tso\tthe\tevent\tbound\twith\tthe\taction\twill\tbe\ta\tclick. Your\tform\tnow\tworks\tto\tsubmit\tand\tcancel\tusing\tcontroller\tactions,\tbut\tthose\tactions\tneed some\tcode.\tStart\twith\tthe\tcreate\taction.\tUpdate app\/controllers\/sightings\/new.js\tto\tcomplete\tthe\tsighting,\tsave\tit,\tand return\tto\tthe\tsightings\tlisting. ... \t\tactions:\t{ \t\t\t\tcreate()\t{ \t\t\t\t\t\tvar\tself\t=\tthis; \t\t\t\t\t\tthis.get('model.sighting').save().then(function()\t{ \t\t\t\t\t\t\t\tself.transitionToRoute('sightings'); \t\t\t\t\t\t}); \t\t\t\t}, \t\t\t\tcancel()\t{ \t\t\t\t}","} }); When\tthe\tform\tis\tsubmitted,\tcreate\tis\tcalled.\tFirst,\tyou\tset\ta\treference\tto\tthe\tcontroller with\tself.\tNext,\tyou\tget\tthe\tsighting\tmodel\tand\tcall\tsave. The\tlast\tstep\tis\tsaving\tto\tthe\tpersistent\tsource.\tThere\tis\ta\tflag\ton\tthe\tmodel\tfor hasDirtyAttributes\tthat\tis\tset\tto\tfalse\twhen\ta\tmodel\tis\tsaved. Saving\ta\tmodel\treturns\ta\tPromise.\tYou\tchained\tthat\tPromise\twith\tthen,\twhich\ttakes\ta function\tto\tbe\tcalled\twhen\tthe\tmodel\thas\tbeen\tsaved\tsuccessfully.\tFinally,\tyou\treturn\tto the\tsightings\tlisting\twith\ttransitionToRoute. Check\tout\tyour\tform\tat\thttp:\/\/l\u200b ocalhost:4200\/\u200bsightings\/\u200bnew (Figure\t24.3). Figure\t24.3\t\tNew\tSighting\tform Fill\tin\tthe\tform\tand\tclick\tCreate.\tAlthough\tyou\tsuccessfully\tadded\ta\tnew\tsighting,\tyour sightings\tlist\troute\tmodel\tis\tstill\treturning\tthe\trecords\tcreated\tinline.\tOpen app\/routes\/sightings.js,\tdelete\tthe\tdummy\tdata,\tand\treplace\tit\twith\ta\tcall\tto\tthe store\tto\tretrieve\tthe\tsightings. import\tEmber\tfrom\t'ember'; export\tdefault\tEmber.Route.extend({ \t\tmodel()\t{ \t\t\t\tlet\trecord1\t=\tthis.store.createRecord('sighting',\t{ \t\t\t\t\t\tlocation:\t'Atlanta', \t\t\t\t\t\tsightedAt:\tnew\tDate('2016-02-09') \t\t\t\t}); \t\t\t\trecord1.set('location',\t'Paris,\tFrance'); \t\t\t\tconsole.log(\\\"Record\t1\tlocation:\t\\\"\t+\trecord1.get('location')); \t\t\t\tlet\trecord2\t=\tthis.store.createRecord('sighting',\t{ \t\t\t\t\t\tlocation:\t'Calloway', \t\t\t\t\t\tsightedAt:\tnew\tDate('2016-03-14')","}); \t\t\t\tlet\trecord3\t=\tthis.store.createRecord('sighting',\t{ \t\t\t\t\t\tlocation:\t'', \t\t\t\t\t\tsightedAt:\tnew\tDate('2016-03-21') \t\t\t\t}); \t\t\t\treturn\t[record1,\trecord2,\trecord3]; \t\t\t\treturn\tthis.store.findAll('sighting',\t{reload:\ttrue}); \t\t} }); Now\tyour\tapp\thas\tcreation\tand\tretrieval. Notice\tthe\tsecond\targument\tof\tfindAll,\tthe\tobject\tliteral\twith\tthe\tsingle\tkey\treload. This\targument\ttells\tthe\tstore\tto\trequest\tfresh\tdata\tfrom\tthe\tAPI\teach\ttime\tthe\troute\tmodel is\tcalled.\tAdding\tthis\targument\tmakes\tit\texplicit\tthat\tyou\talways\twant\tthe\tfreshest\tdata each\ttime\tyou\tview\tthe\tlist. Next,\tthe\tcancel\taction\tneeds\tto\tdelete\tthe\tin-memory,\tdirty\tsighting\tinstance.\tYou\twill use\tmodel.deleteRecord,\tas\tyou\tdid\tin\tChapter\t21.\tAdd\tit\tto app\/controllers\/sightings\/new.js. ... \t\tactions:\t{ \t\t\t\tcreate()\t{ \t\t\t\t\t\tvar\tself\t=\tthis; \t\t\t\t\t\tthis.get('model.sighting').save().then(function()\t{ \t\t\t\t\t\t\t\tself.transitionToRoute('sightings'); \t\t\t\t\t\t}); \t\t\t\t}, \t\t\t\tcancel()\t{ \t\t\t\t\t\tthis.get('model.sighting').deleteRecord(); \t\t\t\t\t\tthis.transitionToRoute('sightings'); \t\t\t\t} \t\t} ... After\tdeleting\tthe\trecord,\tthe\tuser\twill\tbe\treturned\tto\tthe\tlisting.\tThis\tscenario\tworks\twhen the\tuser\tclicks\tthe\tcancel\tbutton,\tbut\twhat\thappens\twhen\tthe\ttop\tnavigation\tis\tused\tto\tgo back\tto\tthe\tlisting\tor\tanother\troute? If\tyou\tdo\tnot\tdestroy\tthe\tdirty\trecord,\tit\twill\tstay\tin\tmemory\twhile\tthe\tuser\u2019s\tsession\tis active.\tTo\tdestroy\tthe\trecord\tyou\twill\tuse\tan\taction\tin\tthe\troute. In\tthe\tlifecycle\tof\ta\troute,\tthere\tare\tactions\tcalled\tat\tdifferent\tstates\tand\ttransitions.\tYou can\toverride\tthese\tactions\tto\tcustomize\tcallbacks\tfor\tdifferent\tstages\tof\tyour\troute transition. Open\tapp\/routes\/sightings\/new.js\tand\toverride\tthe\twillTransition\taction\tto ensure\tthat\tdirty\trecords\tare\tdeleted: ... \t\tmodel(){ \t\t\t\treturn\tEmber.RSVP.hash({ \t\t\t\t\t\tsighting:\tthis.store.createRecord('sighting'), \t\t\t\t\t\tcryptids:\tthis.store.findAll('cryptid'), \t\t\t\t\t\twitnesses:\tthis.store.findAll('witness') \t\t\t\t}); \t\t}, \t\tactions:\t{ \t\t\t\twillTransition()\t{ \t\t\t\t\t\tvar\tsighting\t=\tthis.get('controller.model.sighting'); \t\t\t\t\t\tif(sighting.get('hasDirtyAttributes')){ \t\t\t\t\t\t\t\tsighting.deleteRecord(); \t\t\t\t\t\t} \t\t\t\t} \t\t} }); The\taction\twillTransition\twill\tfire\twhen\tthe\troute\tchanges.\tUsing\tdeleteRecord will\tdestroy\tthe\tmodel\tobject,\tbut\tonly\twhen\tthe\tmodel\tproperty\thasDirtyAttributes\tis true.","You\thave\tnow\tcovered\tyour\tbases\twith\ta\tdirty\trecord\ton\tcreation.\tYou\tare\talso\tset\tup\tto save\tto\ta\tpersistent\tdata\tsource\twith\tminimal\tchanges\tto\tthe\tcontroller.","Editing\ta\tSighting After\tcreating\tand\treading\tdata,\tCRUD\ttells\tus\tthat\tupdating\tis\tthe\tnext\tstep.\tYou\thave\tan edit\troute\tfor\ta\tsighting,\tbut\tyou\tneed\tto\tput\tit\tto\tuse\tby\tupdating\tthe\tsighting\tlisting\twith a\tbutton\tto\tnavigate\tto\tthe\tedit\troute.\tYou\talso\tneed\tto\tadd\ta\tform\twith\tsighting\tfields\tto the\tedit\ttemplate;\thave\tthe\tedit\troute\u2019s\tmodel\tretrieve\twitnesses,\tcryptids,\tand\tthe sighting;\tand\tadd\troute\tparameters\tto\tthe\tedit\troute\tobject\tin\tapp\/router.js.\tFinally, you\tneed\tto\tcreate\ta\tcontroller\tto\tadd\tactions\tfor\tthe\tedit\tform. Let\u2019s\tstart\twith\tadding\tan\tEdit\tbutton\tin\tapp\/templates\/sightings\/index.hbs. To\tgive\tmore\tcontext\tto\tthe\tlist,\tadd\tthe\tname\tand\timage\tof\tthe\tcryptid\tthat\twas\tsighted\tas well. ... \t\t\t\t\t\t<div\tclass=\\\"media\twell\\\"> \t\t\t\t\t\t\t\t<img\tclass=\\\"media-object\tthumbnail\\\"\tsrc=\\\"{{if\tsighting.cryptid.profileImg \t\t\t\t\t\t\t\t\t\tsighting.cryptid.profileImg\t'assets\/images\/cryptids\/blank_th.png'}}\\\" \t\t\t\t\t\t\t\t\t\talt=\\\"{{sighting.cryptid.name}}\\\"\twidth=\\\"100%\\\"\theight=\\\"100%\\\"> \t\t\t\t\t\t\t\t<div\tclass=\\\"caption\\\"> \t\t\t\t\t\t\t\t\t\t<h3>{{sighting.cryptid.name}}<\/h3> \t\t\t\t\t\t\t\t\t\t{{#if\tsighting.location}} \t\t\t\t\t\t\t\t\t\t\t\t<h3>{{sighting.location}}<\/h3> \t\t\t\t\t\t\t\t\t\t\t\t<p>{{moment-from\tsighting.sightedAt}}<\/p> \t\t\t\t\t\t\t\t\t\t{{else}} \t\t\t\t\t\t\t\t\t\t\t\t<h3\tclass=\\\"text-danger\\\">Bogus\tSighting<\/h3> \t\t\t\t\t\t\t\t\t\t{{\/if}} \t\t\t\t\t\t\t\t<\/div> \t\t\t\t\t\t\t\t{{#link-to\t'sighting.edit'\tsighting.id\ttagName=\\\"button\\\" \t\t\t\t\t\t\t\t\t\tclass=\\\"btn\tbtn-success\tbtn-block\\\"}} \t\t\t\t\t\t\t\t\t\tEdit \t\t\t\t\t\t\t\t{{\/link-to}} \t\t\t\t\t\t<\/div> ... Load\thttp:\/\/l\u200b ocalhost:4200\/s\u200b ightings\tand\tcheck\tout\tyour\tnew\tEdit\tbutton (Figure\t24.4).","Figure\t24.4\t\tSightings\tlist\twith\tEdit\tbutton Now\tadd\tthe\tdynamic\tparameters\tto\tthe\tedit\troute\tin\trouter.js. ... \t\tthis.route('sighting',\tfunction()\t{ \t\t\t\tthis.route('edit',\t{path:\t\\\"sightings\/:sighting_id\/edit\\\"}); \t\t}); ... Add\tthe\tretrieval\tmethods\tfor\tsightings,\tcryptids,\tand\twitnesses\tto\tthe\troute\tin app\/routes\/sighting\/edit.js: ... export\tdefault\tEmber.Route.extend({ \t\tmodel(params)\t{ \t\t\t\treturn\tEmber.RSVP.hash({ \t\t\t\t\t\tsighting:\tthis.store.findRecord('sighting',\tparams.sighting_id), \t\t\t\t\t\tcryptids:\tthis.store.findAll('cryptid'), \t\t\t\t\t\twitnesses:\tthis.store.findAll('witness') \t\t\t\t}); \t\t} }); Next,\tadd\tthe\tform\telements\tto\tapp\/templates\/sighting\/edit.hbs,\tas\tyou\tdid when\tcreating\ta\tnew\tsighting. {{outlet}} <h1>Edit\tSighting: \t\t<small> \t\t\t\t{{model.sighting.location}}\t- \t\t\t\t{{moment-from\tmodel.sightin.sightedAt}} \t\t<\/small> <\/h1> <form\t{{action\t\\\"update\\\"\tmodel\ton=\\\"submit\\\"}}> \t\t<div\tclass=\\\"form-group\\\"> \t\t\t\t<label\tfor=\\\"name\\\">Cryptid<\/label> \t\t\t\t{{input\tvalue=model.sighting.cryptid.name\ttype=\\\"text\\\"\tclass=\\\"form-control\\\" \t\t\t\t\t\tname=\\\"location\\\"\tdisabled=true}} \t\t<\/div> \t\t<div\tclass=\\\"form-group\\\"> \t\t\t\t<label>Witnesses<\/label> \t\t\t\t{{#each\tmodel.sighting.witnesses\tas\t|witness|}} \t\t\t\t\t\t{{input\tvalue=witness.fullName\ttype=\\\"text\\\"\tclass=\\\"form-control\\\" \t\t\t\t\t\t\t\tname=\\\"location\\\"\tdisabled=true}} \t\t\t\t{{\/each}}","<\/div> \t\t<div\tclass=\\\"form-group\\\"> \t\t\t\t<label\tfor=\\\"location\\\">Location<\/label> \t\t\t\t{{input\tvalue=model.sighting.location\ttype=\\\"text\\\"\tclass=\\\"form-control\\\" \t\t\t\t\t\tname=\\\"location\\\"\trequired=true}} \t\t<\/div> \t\t<button\ttype=\\\"submit\\\"\tclass=\\\"btn\tbtn-info\tbtn-block\\\">Update<\/button> \t\t<button\t{{action\t'cancel'}}\tclass=\\\"btn\tbtn-block\\\">Cancel<\/button> <\/form> You\tare\talmost\tthere;\tthe\tcontroller\tis\tthe\tfinal\tstep.\tYou\tneed\tto\tset\tthe\tform\t{{action}} to\tupdate.\tAlso,\tbecause\tTracker\tis\tonly\tgoing\tto\tallow\tlocation\tchanges\tat\tthis\tpoint,\tyou will\trender\tthe\tcryptid\tand\twitnesses\tin\tdisabled\tinput\tfields. Create\tthe\tcontroller: ember\tg\tcontroller\tsighting\/edit Open\tapp\/controllers\/sighting\/edit.js\tand\tadd\tthe\tupdate\tand\tcancel actions: import\tEmber\tfrom\t'ember'; export\tdefault\tEmber.Controller.extend({ \t\tsighting:\tEmber.computed.alias('model.sighting'), \t\tactions:\t{ \t\t\t\tupdate()\t{ \t\t\t\t\t\tif(this.get('sighting').get('hasDirtyAttributes')){ \t\t\t\t\t\t\t\tthis.get('sighting').save().then(()\t=>\t{ \t\t\t\t\t\t\t\t\t\tthis.transitionToRoute('sightings'); \t\t\t\t\t\t\t\t}); \t\t\t\t\t\t} \t\t\t\t}, \t\t\t\tcancel()\t{ \t\t\t\t\t\tif(this.get('sighting').get('hasDirtyAttributes')){ \t\t\t\t\t\t\t\tthis.get('sighting').rollbackAttributes(); \t\t\t\t\t\t} \t\t\t\t\t\tthis.transitionToRoute('sightings'); \t\t\t\t} \t\t} }); Similar\tto\tcreating\tmodel\trecords,\tupdating\tis\tas\teasy\tas\tcalling\tsave.\tYou\tonly\twant\tto call\tthe\tAPI\twhen\tyour\trecord\thas\tchanged,\thence sighting.get('hasDirtyAttributes').\tEmber\thas\tthought\tof\tit\tall! Notice\tthe\tuse\tof\tEmber.computed.alias.\tThis\tis\ta\tcomputed\tproperty\tassigned\tso\tyou\tdo not\thave\tto\ttype\tas\tmuch\twhen\tcalling\ton\tthe\tactive\tsighting.\tYou\tcan\talias\tany\tproperty to\tget\tquick\taccess,\tespecially\tif\tthe\tproperties\tare\tnested.","Deleting\ta\tSighting Every\tso\toften\ta\treported\tcryptid\tsighting\tturns\tout\tto\tbe\ta\thoax.\tWhile\tthis\tmay\tbe\trare, you\tdo\tneed\ta\tway\tto\tdelete\told\tor\tdebunked\tdata.\tRecall\tfrom\tChapter\t21\tthat\tdestroying records\tis\tachieved\tby\tcalling\trecord.destroyRecord.\tSimple\tenough. Make\tit\tso:\tAdd\ta\tdelete\tbutton\tto\tyour\ttemplate\tin app\/templates\/sighting\/edit.hbs. \t\t<button\ttype=\\\"submit\\\"\tclass=\\\"btn\tbtn-info\tbtn-block\\\">Update<\/button> \t\t<button\t{{action\t'cancel'}}\tclass=\\\"btn\tbtn-block\\\">Cancel<\/button> <\/form> <hr> <button\t{{action\t'delete'}}\tclass=\\\"btn\tbtn-block\tbtn-danger\\\"> \t\tDelete <\/button> In\taddition\tto\tthe\tnew\tbutton,\tyou\tadded\ta\thorizontal\trule\t(a\tsimple\tline)\tbetween\tthe\tform elements\tto\tseparate\tthe\tUpdate\tand\tCancel\tbuttons\tfrom\tthe\tnew\tDelete\tbutton.\tThe\t<hr>\tis\ta UI\ttool\tyou\tare\tusing\tto\tindicate\tthat\tdeleting\ta\tsighting\tis\ta\tseparate\tprocess\tfrom\tediting a\tsighting. Next,\tadd\tthe\tdelete\taction\tto\tyour\tcontroller, app\/controllers\/sighting\/edit.js: ... \t\t\t\tcancel()\t{ \t\t\t\t\t\tif(this.get('sighting').get('hasDirtyAttributes')){ \t\t\t\t\t\t\t\tthis.get('sighting').rollbackAttributes(); \t\t\t\t\t\t} \t\t\t\t\t\tthis.transitionToRoute('sightings'); \t\t\t\t}, \t\t\t\tdelete()\t{ \t\t\t\t\t\tvar\tself\t=\tthis; \t\t\t\t\t\tif\t(window.confirm(\\\"Are\tyou\tsure\tyou\twant\tto\tdelete\tthis\tsighting?\\\"))\t{ \t\t\t\t\t\t\t\tthis.get('sighting').destroyRecord().then(()\t=>\t{ \t\t\t\t\t\t\t\t\t\tself.transitionToRoute('sightings'); \t\t\t\t\t\t\t\t}); \t\t\t\t\t\t} \t\t\t\t} \t\t} }); The\tdelete\taction\tadds\ta\twindow.confirm\tto\tget\tthe\tuser\u2019s\tconfirmation.\tAside\tfrom\tthe conditional,\tthis\taction\tis\tlike\tthe\tothers:\tget\tthe\tmodel,\tcall\ta\tmethod,\tand\tadd\tan\tasync then\tto\tcall\twhen\tthe\tAPI\trequest\tis\tdone. Now\tnavigate\tto\thttp:\/\/localhost:4200\/sightings\tand\tclick\tthe\tEdit\tbutton for\tone\tof\tyour\tsightings\tto\tview\tthe\tapp\/templates\/sightings\/edit.hbs\tand its\tnew\tDelete\tbutton\t(Figure\t24.5).","Figure\t24.5\t\tEdit\tSighting\tform The\tcycle\tis\tnow\tcomplete:\tcreation\tto\tdestruction.","Route\tActions Actions\tare\tnot\tjust\tfor\tcontrollers.\tRoutes\tcan\tdeclare\tthe\tactions\tfor\ttemplates\tand override\tlifecycle\tactions.\tWhen\tan\taction\tis\tcalled,\tit\tbubbles\tup\tfrom\tthe\ttemplate\tto\tthe controller\tto\tthe\troute\tand\tto\tparent\troutes. A\troute\tcan\ttherefore\tact\tas\tthe\tcontroller\twhen\ta\tcontroller\tdefinition\tis\tnot\tneeded.\tThis might\tseem\tcontradictory\tafter\tall\tour\ttalk\tabout\tseparating\tthe\tapplication\tconcerns,\tbut Ember\treally\tsplit\tthe\tcontroller\u2019s\tjob\tinto\ttwo\tunits:\troute\tinformation\tand\tcontroller\tlogic. Sometimes\tthe\troute\tfile\thas\tmore\tlogic\tand\tsometimes\tthe\tcontroller\thas\tmore\tlogic.\tThe file\tseparation\tallows\tfor\tsmall\tdigestible\tchunks\tof\tcode\twhen\tthe\tother\tfile\thas numerous\tactions\tor\tdecorators. To\tsee\thow\tthis\tworks,\tyou\tare\tgoing\tto\tmove\tthe\tcreate\tand\tcancel\tactions\tfrom app\/controllers\/sightings\/new.js\tto app\/routes\/sightings\/new.js.\tMaking\tthis\tchange\twill\talso\tbroaden\tyour perspective\ton\tmethods\tand\tobjects\tpassed\tbetween\tthese\tfiles\t\u2013\tand\tit\twill\tbe\tgood practice\tfor\twhen\tyou\tonly\twant\ta\troute\tfile\tand\tcomponents\tcontrolling\tan\tapplication view.\t(You\twill\tlearn\tabout\tcomponents\tin\tthe\tnext\tchapter.) First,\tadd\tthe\tactions\tto\tapp\/routes\/sightings\/new.js: import\tEmber\tfrom\t'ember'; export\tdefault\tEmber.Route.extend({ \t\tmodel()\t{ \t\t... \t\t}, \t\tsighting:\tEmber.computed.alias('controller.model.sighting'), \t\tactions:\t{ \t\t\t\twillTransition()\t{ \t\t\t\t\t\tvar\tsighting\t=\tthis.get('controller.model.sighting'); \t\t\t\t\t\tif(sighting.get('hasDirtyAttributes'))\t{ \t\t\t\t\t\t\t\tsighting.deleteRecord(); \t\t\t\t\t\t} \t\t\t\t}, \t\t\t\tcreate()\t{ \t\t\t\t\t\tvar\tself\t=\tthis; \t\t\t\t\t\tthis.get('sighting').save().then(function(data)\t{ \t\t\t\t\t\t\t\tself.transitionTo('sightings'); \t\t\t\t\t\t}); \t\t\t\t}, \t\t\t\tcancel()\t{ \t\t\t\t\t\tthis.get('sighting').deleteRecord(); \t\t\t\t\t\tthis.transitionToRoute('sightings'); \t\t\t\t} \t\t} }); You\talso\tcreated\tan\tEmber.computed.alias\tfor\tthe\tsighting\tobject\tin\tthe\troute.\tThe\tmajor difference\tin\taccessing\tthe\tcontroller\u2019s\tmodel.sighting\tobject\tfrom\tthe\troute\tis\twhere that\tobject\tlives.\tThe\tmodel\ton\tthe\t\u201cnew\u201d\troute\tis\tnot\tthe\tsame\tas\tthe\tmodel\ton\tthe\t\u201cnew\u201d controller.\tTo\taccess\tthe\tsighting\tobject\tyou\tuse\tget('controller.model.sighting'). Creating\tan\talias\tto\tthis\tobject\twill\tsave\tyou\tfrom\ttyping\tthat\tout\teach\ttime. Now,\tdelete\tthe\tactions\tfrom\tapp\/controllers\/sightings\/new.js: import\tEmber\tfrom\t'ember'; export\tdefault\tEmber.Controller.extend({ \t\tactions:\t{ \t\t\t\tcreate()\t{ \t\t\t\t\t\tvar\tself\t=\tthis; \t\t\t\t\t\tthis.get('model.sighting').save().then(function()\t{ \t\t\t\t\t\t\t\tself.transitionToRoute('sightings'); \t\t\t\t\t\t}); \t\t\t\t},","cancel()\t{ \t\t\t\t\t\tthis.get('model.sighting').deleteRecord(); \t\t\t\t\t\tthis.transitionToRoute('sightings'); \t\t\t\t} \t\t} }); Make\tsure\tyour\tapplication\thas\treloaded\t(or\trestart\tember\tserver).\tNavigate\tto\thttp:\/\/\u200b localhost:4200\/s\u200b ightings\/\u200bnew\tand\tcreate\ta\tnew\tsighting\tto\tmake\tsure\tyou\tcan still\trun\tthe\troute\tactions. At\tthis\tpoint,\tapp\/controllers\/sightings\/new.js\tis\tirrelevant.\tYou\tcan\tdelete this\tfile.\tEmber\twill\tstill\tcreate\tthe\tcontroller\tobject\twhile\tthe\tapp\tis\trunning,\tbut\tyou\tdo not\tneed\tthe\tempty\tfile\tcluttering\tup\tthe\tapp\tdirectory. This\tchapter\tfocused\ton\tEmber\tcontrollers\tto\tshow\tyou\thow\tto\tcreate\tactions\tand properties\tfor\tyour\ttemplates,\ttransition\tto\tnew\troutes\tafter\tsaving\tdata,\tand\tdestroy records\ton\tcancel\tor\ta\troute\tchange.\tActions\tallow\tyou\tto\tchange\tthe\tmodel\tdata\tbacking your\tapplication\twith\ta\tsimple\tcallback.\tThe\tcontroller\tproperties\tallow\tyou\tto\thave\tpage- specific\tproperties\tbefore\tadding\trelationships\tto\tthe\tmodel\tdata.\tLast,\tyou\thave completed\tthe\tbasic\tCRUD\tactions\tof\tupdating\tand\tdeleting\trecords\tfrom\tan\tedit\troute. Ember\tallows\trapid\tdevelopment\tby\tmaking\tthe\tcreation\tof\tcontrollers\toptional\tfor\twhen you\tneed\tthese\tspecific\tdetails\tfor\tyour\troutes.\tBeyond\tthese\tfeatures,\tcontrollers\tallow you\tto\tfine-tune\tyour\tviews\tand\thave\tcontrol\tover\tyour\tmodel\tdata.\tActions\tare\tthe\tkey\tto user\tinteraction.\tFurthermore,\tactions\tcan\tlive\ton\troutes,\tcontrollers,\tand\tcomponents\t\u2013 which\tare\tthe\tsubject\tof\tthe\tnext\t(and\tfinal)\tchapter.","Bronze\tChallenge:\tSighting\tDetail\tPage Create\ta\tsighting\tdetail\tpage\twith\tapp\/templates\/sighting\/index.hbs\tand app\/routes\/sighting.js\tto\tdisplay\tthe\tcryptid\timage,\tlocation,\tand\tlist\tof witnesses.\tAdd\tan\tEdit\tbutton\tfor\tgood\tmeasure.\t(Hint:\tYou\tcan\tadd\tyour\tactions\ton\tthe route.)","Silver\tChallenge:\tSighting\tDate When\tcreating\tand\tediting\ta\tsighting,\tadd\ta\tsightingDate\tproperty\tto\tthe\tcontroller\tand an\tinput\tthat\tis\tbound\tto\tthat\tproperty.\tUse\teither\ta\tbasic\ttext\tinput\tor input[type=\\\"date\\\"]\tto\tcapture\tthe\tsighting\tdate.\tConvert\tthe\tdate\tto\tan\tISO8601\tstring using\tmoment\tand\tset\tthe\tsighting\tproperty\tsightingAt\twith\tthat\tdate\tstring.","Gold\tChallenge:\tAdding\tand\tRemoving\tWitnesses When\tcreating\ta\tnew\tsighting,\tbuild\ta\tlist\tof\twitnesses\tfrom\ta\tselect\tonchange\taction. Create\ta\tnew\tproperty\tfor\tthe\tlist\tof\twitnesses.\tOn\tcreate,\tadd\tthe\tobjects\tto\tthe\tsightings property\twitnesses. While\ton\tthe\tpage,\tmake\tthe\tcollection\tof\tselected\twitnesses\trender\tas\ta\tlist\twith\ta\tRemove button.\tUse\tthe\tselect\telement\tas\ta\tway\tto\tadd\tto\tthe\tlist\twhile\tremoving\tthe\toptions from\tthe\tselect\telement.\tYou\twill\tneed\ttwo\tactions:\taddWitness\tand\tremoveWitness.","25\t Components Components\tare\tobjects\tthat\thold\tthe\tproperties\tof\tcontrollers\tand\tviews\tin\tEmber.\tThe concept\tbehind\tcomponents\tis\tto\thave\treusable\tDOM\telements\tthat\thave\ttheir\town\tcontext or\tscope.\tComponents\taccept\tattributes\tto\tcustomize\tthe\tcontent\trendered\tinside\ttheir templates.\tAlso,\tcomponents\tcan\tallow\tactions\tto\tbe\tassigned\tto\tproperties\tfrom\ta\tparent controller\tor\tparent\troute\t(Figure\t25.1). Figure\t25.1\t\tComponent\tproperty As\tyou\tassess\tthe\tarchitecture\tof\tyour\tapplication,\tyou\twill\tstart\tto\tsee\telements\tgrouped together\twith\tminor\tdifferences\tthroughout\tmany\troutes\tand\ttemplates.\tIf\ta\tgrouping\tof elements\tcan\tbe\tremoved\tfrom\tthe\ttemplate\tand\tdescribed\twith\tvariables\tdepicting\tthe elements\u2019\tstate,\tit\tis\ta\tprime\tcandidate\tto\tbecome\ta\tcomponent. This\tchapter\twill\tshow\tyou\trelatively\tsimple\texamples\tof\twrapping\tDOM\telements\tin JavaScript\tobjects\tto\tbe\tused\tacross\tmultiple\troutes.\tMuch\tlike\tthe\thelpers\tyou\tsaw\tin Chapter\t23,\tcomponents\ttake\targuments\tin\tthe\tform\tof\tattributes\tto\tproduce\tHTML.\tThey also\thave\tthe\tadded\tfeature\tof\thaving\ttheir\town\tactions\tand\tscoped\tproperties\tto\tupdate themselves\tupon\tuser\tinteraction. This\tchapter\twill\tonly\tshow\tyou\tthe\ttip\tof\tthe\ticeberg\tof\tscalable\tapplication\tdevelopment. Ember\tapplications\tin\tproduction\trely\theavily\ton\tcomponents\tto\tcreate\tconsistent\tuser interfaces\tand\tmaintainable\tcode\tbases\tthat\tsometimes\tspan\thundreds\tof\troutes.\tHowever, the\texamples\tyou\tsee\there\twill\tprovide\ta\tfoundation\tof\tunderstanding\tfor\tfuture\tprojects where\tyou\twant\tto\tturn\tyour\troute\ttemplates\tinto\treusable\tpages\tor\tsingle\telements. Iterator\tItems\tas\tComponents An\texample\tof\tgrouped\telements\tthat\tcan\toften\tbecome\ta\tcomponent\tis\telements\trendered","in\t{{#each}}\titerators.\tThere\tmay\tbe\ta\t<div>\tcontainer\telement\twith\ta\tparticular className\tbased\ton\tthe\titerator\tobject,\ta\ttitle,\tan\timage\tpath,\ta\tbutton\twith\tan\taction, and\/or\tstyles\tbased\ton\tthe\tobject\u2019s\tproperty\tstates.\tTo\tmake\tthese\teasily\treusable,\tyou\tcan wrap\tall\tthe\tDOM\tcode\tin\ta\tcomponent\ttemplate\tand\tcreate\ta\tcomponent\tJavaScript\tfile\tto handle\tthe\tdecorators\tand\tactions\tthat\ta\tcontroller\twould\tnormally\thandle. Your\tsightings\tlist\tis\tbuilt\twith\tan\t{{#each}}\titerator,\tand\tyou\tare\tgoing\tto\tcreate\tyour first\tcomponent\tto\trepresent\ta\tsighting\tlist\titem.\tStart\tby\tgenerating\ta\tcomponent\tvia\tEmber CLI\tin\tthe\tterminal: ember\tg\tcomponent\tlisting-item This\tcreates\tthree\tfiles:\tapp\/components\/listing-item.js, app\/templates\/components\/listing-item.hbs,\tand\tthe\ttest\tfile tests\/integration\/components\/listing-item-test.js. Now\tthat\tthe\tcomponent\tis\tcreated,\tyou\tneed\tto\tfind\tthe\tcode\tyou\twant\tthe\tcomponent\tto replace.\tOpen\tapp\/templates\/sightings\/index.hbs\tand\tlocate\tthis\tblock\tof code.\tYou\twill\tbe\tmoving\tpart\tof\tthis\tcode\tto\tthe\tcomponent\tin\ta\tmoment.\tFor\tnow,\tjust take\ta\tlook\tat\tit. <div\tclass=\\\"row\\\"> \t\t{{#each\tmodel\tas\t|sighting|}} \t\t\t\t<div\tclass=\\\"col-xs-12\tcol-sm-3\ttext-center\\\"> \t\t\t\t\t\t<div\tclass=\\\"media\twell\\\"> \t\t\t\t\t\t\t\t<img\tclass=\\\"media-object\tthumbnail\\\"\tsrc=\\\"{{if\tsighting.cryptid.profileImg \t\t\t\t\t\t\t\t\t\tsighting.cryptid.profileImg\t'assets\/images\/cryptids\/blank_th.png'}}\\\" \t\t\t\t\t\t\t\t\t\talt=\\\"{{sighting.cryptid.name}}\\\"\twidth=\\\"100%\\\"\theight=\\\"100%\\\"> \t\t\t\t\t\t\t\t<div\tclass=\\\"caption\\\"> \t\t\t\t\t\t\t\t\t\t<h3>{{sighting.cryptid.name}}<\/h3> \t\t\t\t\t\t\t\t\t\t{{#if\tsighting.location}} \t\t\t\t\t\t\t\t\t\t\t\t<h3>{{sighting.location}}<\/h3> \t\t\t\t\t\t\t\t\t\t\t\t<p>{{moment-from\tsighting.sightedAt}}<\/p> \t\t\t\t\t\t\t\t\t\t{{else}} \t\t\t\t\t\t\t\t\t\t\t\t<h3\tclass=\\\"text-danger\\\">Bogus\tSighting<\/h3> \t\t\t\t\t\t\t\t\t\t{{\/if}} \t\t\t\t\t\t\t\t<\/div> \t\t\t\t\t\t\t\t{{#link-to\t'sighting.edit'\tsighting.id\ttagName=\\\"button\\\" \t\t\t\t\t\t\t\t\t\tclass=\\\"btn\tbtn-success\tbtn-block\\\"}} \t\t\t\t\t\t\t\t\t\tEdit \t\t\t\t\t\t\t\t{{\/link-to}} \t\t\t\t\t\t<\/div> \t\t\t\t<\/div> \t\t{{\/each}} <\/div> The\tcontainer\t<div>\twith\tthe\tcol-xs-12\tclass\t(that\tis,\tthe\tshaded\t<div>)\tneeds\tto\tbe\ton this\tspecific\tpage.\tIt\tis\ta\tvisual\tcontainer\tfor\tthe\tlayout,\tspecifying\tthe\tsize\tof\tits\tcontent. If\tyou\twere\tto\tadd\tthis\tcontainer\tto\tyour\tsightings\tlist\tcomponent,\tthe\tcontainer\tsize would\tbe\tfixed\tfor\tevery\tinstance\tof\tthe\tcomponent\tacross\tthe\tsite. However,\tthe\t<div\tclass=\\\"media\twell\\\">\tcontainer\tand\tits\tcontents\tcan\tbe\tmoved\tto\ta component.\tThis\tcomponent\tcan\tthen\tbe\tadded\tto\tany\tsize\tof\tcontainer\twhile\tholding\tthe characteristics\tof\ta\tsingle\titem\tin\ta\tlist.\tThe\tcomponent\twill\tcontain\tthe\tmajor\telements\tof the\titem\tbeing\tlisted\t\u2013\tmainly\tthe\tmedia\tcontainer\tand\tthe\timage,\ttitle,\tand\tEdit\tbutton. Open\tapp\/templates\/components\/listing-item.hbs\tand\tbegin\tby\tadding the\timage\tand\tcryptid\tname: <img\tclass=\\\"media-object\tthumbnail\\\"\tsrc=\\\"{{imagePath}}\\\"\talt=\\\"{{name}}\\\" \t\twidth=\\\"100%\\\"\theight=\\\"100%\\\"> <div\tclass=\\\"caption\\\"> \t\t<h3>{{name}}<\/h3> \t\t{{yield}} <\/div> A\tcomponent\trepresents\ta\tsingle\tDOM\telement.\tEverything\tin\tthe\tcomponent\ttemplate, then,\tis\ta\tchild\tof\tthat\tDOM\telement.\tBy\tdefault,\tEmber\u2019s\tHTMLBars\tuses\tJavaScript\tto","create\t<div>\telements\tand\trenders\tthe\tcomponent\ttemplate\tinside\tthem. The\tcode\tyou\tjust\twrote\tadds\tan\timage\tand\ta\tcaption\twith\ta\tname\tto\tthe\t<div>\telement created\tby\ta\t{{#listing-item}}\tcomponent.\tJust\tlike\tother\ttemplates,\tthe\tcomponent\thas dynamic\tportions\tthat\tare\tvariables\tyou\twill\tpass\tin.\t(We\twill\tdiscuss\tthe\t{{yield}}\tin\ta moment.) When\tyou\tadd\tthis\tcomponent\tto\tthe\troute\ttemplate,\tit\twill\trender\tthis: <div> \t\t<img\tclass=\\\"media-object\tthumbnail\\\"\tsrc=\\\"[cryptid's\timagePath\tstring]\\\" \t\t\t\talt=\\\"[cryptid's\tname\tstring]\\\"\twidth=\\\"100%\\\"\theight=\\\"100%\\\"> \t\t<div\tclass=\\\"caption\\\"> \t\t\t\t<h3>[cryptid's\tname\tstring]<\/h3> \t\t\t\t{{yield}} \t\t<\/div> <\/div> The\tdynamic\tportions\tof\tthe\ttemplate,\t{{name}}\tand\t{{imagePath}},\twill\tbe\tattributes provided\tto\tthe\tcomponent.\tThe\t{{yield}}\tis\twhere\tyou\tcan\tpass\tchild\telements\tto\tthe component\tto\trender\tin\ta\tspecific\tlocation\tof\tthe\tDOM\tnode\tstructure.\tThe\tcomponent template\tfile\tacts\ta\tlayout\tor\tmaster\tset\tof\telements,\tand\tthe\t{{yield}}\tis\tfor\telements you\tneed\tto\tadd\twith\teach\tinstance\tof\tthe\tcomponent.\tYou\twill\tuse\tit\tlater\tin\tthe\tchapter. While\tthis\tis\tnot\texactly\tthe\tmarkup\tyou\tare\treplacing\tin app\/templates\/sightings\/index.hbs,\tit\tis\ta\tgood\tstarting\tpoint. Replace\tyour\texisting\tcode\tin\tapp\/templates\/sightings\/index.hbs\twith\tthe component. <div\tclass=\\\"row\\\"> \t\t{{#each\tmodel\tas\t|sighting|}} \t\t\t\t<div\tclass=\\\"col-xs-12\tcol-sm-3\ttext-center\\\"> \t\t\t\t\t\t<div\tclass=\\\"media\twell\\\"> \t\t\t\t\t\t\t\t<img\tclass=\\\"media-object\tthumbnail\\\"\tsrc=\\\"{{if\tsighting.cryptid.profileImg \t\t\t\t\t\t\t\t\t\tsighting.cryptid.profileImg\t'assets\/images\/cryptids\/blank_th.png'}}\\\" \t\t\t\t\t\t\t\t\t\talt=\\\"{{sighting.cryptid.name}}\\\"\twidth=\\\"100%\\\"\theight=\\\"100%\\\"> \t\t\t\t\t\t\t\t<div\tclass=\\\"caption\\\"> \t\t\t\t\t\t\t\t\t\t<h3>{{sighting.cryptid.name}}<\/h3> \t\t\t\t\t\t\t\t\t\t{{#if\tsighting.location}} \t\t\t\t\t\t\t\t\t\t\t\t<h3>{{sighting.location}}<\/h3> \t\t\t\t\t\t\t\t\t\t\t\t<p>{{moment-from\tsighting.sightedAt}}<\/p> \t\t\t\t\t\t\t\t\t\t{{else}} \t\t\t\t\t\t\t\t\t\t\t\t<h3\tclass=\\\"text-danger\\\">Bogus\tSighting<\/h3> \t\t\t\t\t\t\t\t\t\t{{\/if}} \t\t\t\t\t\t\t\t<\/div> \t\t\t\t\t\t\t\t{{#link-to\t'sighting.edit'\tsighting.id\ttagName=\\\"button\\\" \t\t\t\t\t\t\t\t\t\tclass=\\\"btn\tbtn-success\tbtn-block\\\"}} \t\t\t\t\t\t\t\t\t\tEdit \t\t\t\t\t\t\t\t{{\/link-to}} \t\t\t\t\t\t<\/div> \t\t\t\t\t\t{{#listing-item\timagePath=sighting.cryptid.profileImg \t\t\t\t\t\t\t\tname=sighting.cryptid.name}} \t\t\t\t\t\t{{\/listing-item}} \t\t\t\t<\/div> \t\t{{\/each}} <\/div> You\thave\treplaced\ta\tlot\tof\tcode\twith\ta\tsingle\tline.\tSome\tfunctionality\twas\tremoved\tin\tthe process,\tbut\tyou\twill\tfix\tthat\tsoon.\tThe\tnext\tstep\tis\tadding\tthe\tcontainer\tstyles\tback\ton\tthe component\telement.\tIt\tis\tmissing\tthe\t\\\"media\\\"\tand\t\\\"well\\\"\tBootstrap\tstyles. Open\tapp\/components\/listing-item.js\tand\tadd\ta\tclassNames\tproperty\tto the\tlisting-item\tcomponent: import\tEmber\tfrom\t'ember'; export\tdefault\tEmber.Component.extend({ \t\tclassNames:\t[\\\"media\\\",\t\\\"well\\\"] }); The\tproperty\tclassNames\tpasses\tits\tvalue\tto\tthe\tclassNames\tattribute\tof\tthe\t<div> element\tcreated\tby\tthe\tcomponent.","Now\tthe\trendered\tcomponent\tlooks\tlike\tthis: <div\tclass=\\\"media\twell\\\"> \t\t<img\tclass=\\\"media-object\tthumbnail\\\"\tsrc=\\\"[imagePath\tstring]\\\"\talt=\\\"[name\tstring]\\\" \t\t\t\twidth=\\\"100%\\\"\theight=\\\"100%\\\"> \t\t<div\tclass=\\\"caption\\\"> \t\t\t\t<h3>[name\tstring]<\/h3> \t\t\t\t{{yield}} \t\t<\/div> <\/div> Next,\tyou\twill\tadd\telements\tto\tthe\t{{yield}}\tsection\tof\tthe\tcomponent\ttemplate\tto\tmake the\timplementation\tof\tthis\tcomponent\tinstance\tunique\tto\tthe\tsightings\troute.\tFirst,\tin app\/templates\/sightings\/index.hbs,\tadd\tthe\tcomponent\u2019s\tcontextual\tcontent that\twill\trender\tin\tthe\t{{yield}}: ... \t\t\t\t\t\t{{#listing-item\timagePath=sighting.cryptid.profileImg \t\t\t\t\t\t\t\tname=sighting.cryptid.name}} \t\t\t\t\t\t\t\t{{#if\tsighting.location}} \t\t\t\t\t\t\t\t\t\t<h3>{{sighting.location}}<\/h3> \t\t\t\t\t\t\t\t\t\t\t\t<p>{{moment-from\tsighting.sightedAt}}<\/p> \t\t\t\t\t\t\t\t{{else}} \t\t\t\t\t\t\t\t\t\t<h3\tclass=\\\"text-danger\\\">Bogus\tSighting<\/h3> \t\t\t\t\t\t\t\t{{\/if}} \t\t\t\t\t\t\t\t{{#link-to\t'sighting.edit'\tsighting.id\ttagName=\\\"button\\\" \t\t\t\t\t\t\t\t\t\tclass=\\\"btn\tbtn-success\tbtn-block\\\"}} \t\t\t\t\t\t\t\t\t\tEdit \t\t\t\t\t\t\t\t{{\/link-to}} \t\t\t\t\t\t{{\/listing-item}} This\tcode,\twhich\tshould\tlook\tfamiliar,\treinstates\tthe\tsighting\tlocation\tand\ttime\tas\twell\tas the\tEdit\tbutton.","Components\tfor\tDRY\tCode Time\tto\tDRY\tup\tyour\tcode. Say\twhat?\tDRY\tis\ta\tprogramming\tprinciple\tthat\tstands\tfor\t\u201cdon\u2019t\trepeat\tyourself.\u201d\tIn other\twords,\tif\tyou\twrite\tsomething\tdown,\twrite\tit\tdown\tin\tjust\tone\tplace. Both\tthe\tlistings\tfor\tsightings\tand\tcryptids\tuse\tan\telement\twith\tclass=\\\"media\twell\\\"\twith images\tand\ttitles.\tSo\tthis\tis\tsome\tcode\tthat\tcan\tbe\tDRYed\tup.\tThe\tcomponent\tdoes\tnot match\texactly\tto\tthe\tcryptid\tlisting\titem,\tbut\tthat\tis\twhere\tthe\t{{yield}}\tcomes\tin. Add\tthe\tnew\t{{#listing-item}}\tcomponent\tto\tthe\tcryptids\tlisting.\tIn app\/templates\/cryptids.hbs,\treplace\tthe\t<div\tclass=\\\"media\twell\\\">\telement and\tits\tchild\telements: <div\tclass=\\\"row\\\"> \t\t{{#each\tmodel\tas\t|cryptid|}} \t\t\t\t<div\tclass=\\\"col-xs-12\tcol-sm-3\ttext-center\\\"> \t\t\t\t\t\t<div\tclass=\\\"media\twell\\\"> \t\t\t\t\t\t\t\t{{#link-to\t'cryptid'\tcryptid.id}} \t\t\t\t\t\t\t\t\t\t<img\tclass=\\\"media-object\tthumbnail\\\" \t\t\t\t\t\t\t\t\t\t\t\tsrc=\\\"{{if\tcryptid.profileImg\tcryptid.profileImg \t\t\t\t\t\t\t\t\t\t\t\t'assets\/images\/cryptids\/blank_th.png'}}\\\" \t\t\t\t\t\t\t\t\t\t\t\talt=\\\"{{cryptid.name}}\\\"\twidth=\\\"100%\\\"\theight=\\\"100%\\\"> \t\t\t\t\t\t\t\t{{\/link-to}} \t\t\t\t\t\t\t\t<div\tclass=\\\"caption\\\"> \t\t\t\t\t\t\t\t\t\t<h3>{{cryptid.name}}<\/h3> \t\t\t\t\t\t\t\t<\/div> \t\t\t\t\t\t<\/div> \t\t\t\t\t\t{{#link-to\t'cryptid'\tcryptid.id}} \t\t\t\t\t\t\t\t{{listing-item\timagePath=cryptid.profileImg\tname=cryptid.name}} \t\t\t\t\t\t{{\/link-to}} \t\t\t\t<\/div> \t\t{{else}} \t\t\t\t<div\tclass=\\\"jumbotron\\\"> \t\t\t\t\t\t<h1>No\tCreatures<\/h1> \t\t\t\t<\/div> \t\t{{\/each}} <\/div> Did\tyou\tnotice\tthat\tthere\tis\ta\tdifference\tbetween\tthe\tway\tyou\treferenced\tyour\tcomponent in\tthe\ttwo\ttemplates?\tThe\tsightings\titerator\tuses\tthe\t{{yield}}\tto\tadd\telements\tinside\tthe <div\tclass=\\\"caption\\\">\tand\tthe\tcryptids\ttemplate\tdoes\tnot.\tWhen\tyou\twant\ta\tcomponent to\tonly\tuse\tthe\tcode\tin\tthe\tcomponent\ttemplate\tfile,\tyou\tcan\tuse\tan\tinline\tcomponent, written\twithout\tthe\thash\t(#)\t\u2013\t{{listing-item}}\t\u2013\tas\tyou\tdid\there.\tWith\tthis\tsyntax,\tthere is\tno\tclosing\tHTMLBars\telement\t({{\/listing-item}}). To\tadd\ta\tlink\tto\tthe\tcomponent,\tyou\twrapped\tthe\tcomponent\twith\ta\t{{#link-to}}.\tIn\tthe old\titerated\telements,\tonly\tthe\timage\tlinked\tto\tthe\tcryptid\tdetail\tpage.\tNow\tthe\tentire element\tlinks\tto\tthe\tcryptid\tdetail.\tThis\texample\tshows\tthe\tflexibility\tof\tcomponents\tto conform\tto\tthe\tcontext\tof\tdifferent\troute\ttemplates\twhile\trendering\tsimilar\tcontent.\tYou could\talso\tadd\tmore\tattributes\tto\tthe\tcomponent\tto\taccount\tfor\tthe\tneed\tfor\ta\tlink\tinside the\tcomponent\ttemplate.","Data\tDown,\tActions\tUp Next,\tyou\twill\tadd\ta\tcomponent\tto\tbe\tcontrolled\tby\ta\tstate\tchange\tin\tthe\tapplication. Specifically,\tyou\twill\tcreate\ta\tflash\talert\tto\tdisplay\ta\tmessage\twhen\tyou\tcreate\ta\tnew listing. One\tof\tthe\ttenets\tof\tcomponents\tis\t\u201cdata\t(or\tstate)\tdown\tand\tactions\tup.\u201d\tUnlike controllers,\tcomponents\tshould\tnot\tchange\tthe\tstate\tof\tan\tapplication;\tthey\tshould\tpass changes\tup\tthrough\tactions.\tThe\tstate\tof\ta\tcomponent,\ton\tthe\tother\thand,\tshould\tbe\tpassed in\tfrom\ta\tparent\ttemplate\t\u2013\tdata\tdown\t(Figure\t25.2). Figure\t25.2\t\tComponent\tData\tDown\tActions\tUp A\tcomponent\tcould\teasily\treplace\ta\tcontroller\tby\tpassing\ta\troute\u2019s\tmodel\tdirectly\tto\tthe component\twithout\tthe\tneed\tfor\ta\tcontroller\u2019s\tdecorators\tor\tactions. You\twill\tcreate\ta\tnew\tcomponent\tand\tactions\tand\tadd\tthe\tcomponent\tto\tthe app\/templates\/application.hbs\tfile\tto\trender\ta\tglobal\tmessage\twhen\ta\tnew sighting\tis\tcreated. First,\tgenerate\tthe\tnew\tcomponent\tin\tthe\tterminal: ember\tg\tcomponent\tflash-alert The\t{{flash-alert}}\tcomponent\twill\tbe\ta\tcontainer\telement\tfor\ta\t<strong>\talert\ttitle and\ta\ttext\tmessage. Open\tand\tedit\tapp\/templates\/components\/flash-alert.hbs\tto\tmake\tit\tso: {{yield}} <strong>{{typeTitle}}!<\/strong>\t{{message}} Edit\tthe\tcomponent\tfile\tapp\/components\/flash-alert.js\tand\tadd\tclassNames: import\tEmber\tfrom\t'ember'; export\tdefault\tEmber.Component.extend({ \t\tclassNames:\t[\\\"alert\\\"] });","Now\tthe\tcomponent\twill\trender\tthe\tfollowing\telements: <div\tclass=\\\"alert\\\"> \t\t<strong>{{typeTitle}}!<\/strong>\t{{message}} <\/div>","Class\tName\tBindings This\tcontainer\tgives\tthe\timpression\tthat\tit\twill\tdisplay\tthe\tcorrect\tmessage,\tbut\tthe\tstyling will\tnot\tgive\tyou\tcontext\tto\tits\talert\ttype.\tBootstrap\thas\tstate-variant\tstyles\tfor\talert components:\t\\\"alert-success\\\",\t\\\"alert-info\\\",\t\\\"alert-warning\\\",\tand\t\\\"alert-danger\\\". In\torder\tto\tset\tthe\tcorrect\talert\ttype,\tyou\twill\tadd\tone\tof\tthese\tclasses\twith\ta\tcomputed property\tand\tclass\tname\tbinding. Start\twith\tthe\tcomputed\tproperty\tin\tapp\/components\/flash-alert.js: ... export\tdefault\tEmber.Component.extend({ \t\tclassNames:\t[\\\"alert\\\"], \t\ttypeClass:\tEmber.computed('alertType',\tfunction()\t{ \t\t\t\treturn\t\\\"alert-\\\"\t+\tthis.get('alertType'); \t\t}) }); Here,\tyou\tadded\ta\tcomputed\tproperty\tfor\ttypeClass,\twhich\twill\tbe\tused\tto\tadd\ta className\tto\tthe\tcomponent\u2019s\t<div>\telement.\tThe\tcomputed\tproperty\texpects\ta\tproperty called\talertType,\twhich\tyou\twill\tadd\tlater,\tand\treturns\ta\tstring\twith\t\\\"alert-\\\"\tprefixing the\t\\\"alertType\\\".\tThis\tallows\tyou\tto\tpass\tin\ta\tproperty\twith\ta\tvalue\tof\t\\\"success\\\", \\\"info\\\",\t\\\"warning\\\",\tor\t\\\"danger\\\"\tto\tsupply\tthe\tcontext\tof\tthe\talert.\tYou\twill\tbe\tusing\tthis same\talertType\tproperty\tfor\tanother\tcomputed\tproperty. Finally,\tadd\tthe\tclassName\tbindings\tin\tthe\tapp\/components\/flash-alert.js\tfile for\tclassNames\tbound\tto\tcomponent\tproperties: ... export\tdefault\tEmber.Component.extend({ \t\tclassNames:\t[\\\"alert\\\"], \t\tclassNameBindings:\t['typeClass'], \t\ttypeClass:\tEmber.computed('alertType',\tfunction()\t{ \t\t\t\treturn\t\\\"alert-\\\"\t+\tthis.get('alertType'); \t\t}) }); This\tadds\ta\tclassName\tto\tthe\tclassNames\tarray\tfrom\tthe\tvalue\tof\tthe\tcomputed\tproperty typeClass.\tWhen\tyou\tset\tthe\talertType\tproperty,\tthe\tstyle\tchanges\ton\tthe\tcomponent. Using\tclassNameBindings\tis\tspecific\tto\tthe\tclassNames\tattribute\ton\tan\telement.\tThere\tis a\tcomplementary\tEmber\tcomponent\tproperty\tfor\tthe\tother\tattributes:\tattributeBindings. You\tcan\tbind\tcomponent\tproperties\tto\tthe\tattributes\tof\tthe\tcomponent\u2019s\telement.\tA\tbasic example\tis\tsetting\tthe\thref\tof\ta\tlink\tto\ta\tcomputedProperty.\tFor\texample: export\tdefault\tEmber.Component.extend({ \t\tattributeBindings:\t['href',\t'customHREF:href'], \t\thref:\t\\\"http:\/\/www.mydomain.com\\\", \t\tcustomHREF:\t\\\"http:\/\/www.mydomain.com\\\" }); With\tattribute\tbindings\tyou\tcan\tset\tall\tthe\tcomponent\u2019s\tattributes\twith\tthe\tstate\tdata passed\tinto\tthe\tcomponent.\tTo\texplore\tthis\tfurther,\tcheck\tout\tthe\tEmber\tdocumentation online. Next,\tadd\ta\tcomputed\tproperty\tto\tdisplay\tthe\talert\ttype\tas\ta\tstring.\tThe\tcomponent template\tis\texpecting\ta\tproperty\ttypeTitle,\tso\tyou\thave\tto\tadd\tthis\tcomputed\tproperty\tto app\/components\/flash-alert.js: import\tEmber\tfrom\t'ember'; export\tdefault\tEmber.Component.extend({","... \t\ttypeClass:\tEmber.computed('alertType',\tfunction()\t{ \t\t\t\treturn\t\\\"alert-\\\"\t+\tthis.get('alertType'); \t\t}), \t\ttypeTitle:\tEmber.computed('alertType',\tfunction()\t{ \t\t\t\treturn\tEmber.String.capitalize(this.get('alertType')); \t\t}) }); Now\tyou\thave\ta\tstring\tin\tthe\tmessage\tbound\tto\tthe\talert\ttype\tthat\tis\tcapitalized\tand\tis\ta <strong>\telement\tin\tthe\ttemplate.\tYou\thave\tbound\ta\tclass\tname\tand\ta\tdecorated\tproperty to\tthe\tcomponent\tby\tcomputing\tthe\tvalue\tof\teach\tfrom\tdata\tpassed\tinto\tthe\tcomponent\tvia the\talertType\tproperty. Next\tyou\tneed\tto\tadd\tyour\tcomponent\tto\tyour\tpage\tin app\/templates\/application.hbs: <header> \t\t... <\/header> <div\tclass=\\\"container\\\"> \t\t{{flash-alert}} \t\t{{outlet}} <\/div> This\tcomponent\tis\tan\tinline\tcomponent,\tmeaning\tthere\twill\tbe\tno\telements\tto\trender\tin the\t{{yield}}\tand\tthe\tcomponent\ttemplate\tdoes\tnot\tneed\ta\t{{yield}}.","Data\tDown At\tthis\tpoint\tthe\talert\tdoes\tnot\thave\tany\tstate\tdata\tto\trender\tcontent\tand\tclassNames,\tonly containers.\tPass\tin\tthe\tstate\tof\tthe\tcomponent\tin app\/templates\/application.hbs: ... \t\t{{flash-alert\tmessage=\\\"This\tis\tthe\tAlert\tMessage\\\"\talertType=\\\"success\\\"}} \t\t{{outlet}} <\/div> Start\tthe\tserver\tand\topen\tyour\tbrowser\tto\thttp:\/\/l\u200b ocalhost:4200\/\u200bsightings to\tsee\tthe\t{{flash-alert}}\tcomponent\trendered\twith\tthe\tinline\tdata\tyou\tsupplied (Figure\t25.3). Figure\t25.3\t\tFlash\talert Now\tthat\tthe\talert\tis\ton\tthe\tscreen,\tyou\twant\tto\tset\tthe\tproperties\tof\tmessage\tand alertType\tdynamically.\tYou\twill\tneed\tan\tapplication\tcontroller\tto\tachieve\tthis.\tUsing\tthe Ember\tCLI\tgenerator,\tadd\tthe\tcontroller: ember\tg\tcontroller\tapplication The\tapplication\tcontroller\twill\tmaintain\tthe\tdynamic\tstate\tof\tthe\t{{flash-alert}} component.\tThe\tcontroller\twill\tneed\tproperties\tto\tmaintain\tthe\tstate\tof\tthe\talert.\tAdd\tthe following\tproperty\tto\tapp\/controllers\/application.js: import\tEmber\tfrom\t'ember'; export\tdefault\tEmber.Controller.extend({ \t\talertMessage:\tnull, \t\talertType:\tnull, \t\tisAlertShowing:\tfalse","}); You\tadded\tproperties\tthat\twill\tbe\tset\tby\tan\taction.\tNow\tyou\thave\tcontroller\tproperties\tto pass\tto\tthe\tflash-alert\tcomponent\tin\tapp\/templates\/application.hbs: ... <\/header> <div\tclass=\\\"container\\\"> \t\t{{#if\tisAlertShowing}} \t\t\t\t{{flash-alert\tmessage=\\\"This\tis\tthe\tAlert\tMessage\\\"\talertType=\\\"success\\\"}} \t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\talertMessage\talertType=alertType}} \t\t{{\/if}} \t\t{{outlet}} <\/div> The\tcontroller\u2019s\tproperties\tcan\tnow\tbe\tset\tfrom\tan\taction.\tThe\tapplication\tonly\tneeds\tto render\tthe\tcomponent\twhen\tthe\tisAlertShowing\tproperty\tis\tset\tto\ttrue\tand\twhen\tthe other\tproperties\thave\tvalues.\tThe\taction\tto\tset\tthese\tproperties\twill\tbe\tcoming\tfrom controllers\tall\tover\tthe\tapplication.\tHow\tdo\tyou\tbubble\tthese\tactions\tup?\tEmber\tdoes\tit\tfor you\t(Figure\t25.4). Figure\t25.4\t\tFlash\talert\tprocess You\thave\tto\tadd\tan\taction\tto\ta\troute.\tYou\tcan\tcall\tan\taction\tfrom\ta\tcontroller,\tand\tit\twill hit\tthe\tcurrent\tcontroller,\tthen\tthe\tcurrent\troute,\tthe\tparent\troutes,\tthen\tthe\tapplication route. Because\tyour\tflash-alert\tis\tin\tthe\tapp\/templates\/application.hbs,\tyou\tneed an\tapplication\troute.\tCreate\tone:","ember\tg\troute\tapplication You\twill\tsee\tthis\tmessage\treturned\tin\tthe\tcommand\tline: [?]\tOverwrite\tapp\/templates\/application.hbs? Enter\tn\tor\tno\tto\tcontinue.\tYou\tdo\tnot\twant\tto\toverwrite\tthe\ttemplate\tfile\tyou\tjust\tcreated. You\tonly\twant\tto\tadd\tthe\tJavaScript\tfile\tapp\/routes\/application.js. Next\tyou\twill\tneed\tto\tedit\tthe\troute\tin\tapp\/routes\/application.js: import\tEmber\tfrom\t'ember'; export\tdefault\tEmber.Route.extend({ \t\tactions:\t{ \t\t\t\tflash(data){ \t\t\t\t\t\tthis.controller.set('alertMessage',\tdata.message); \t\t\t\t\t\tthis.controller.set('alertType',\tdata.alertType); \t\t\t\t\t\tthis.controller.set('isAlertShowing',\ttrue); \t\t\t\t} \t\t} });","Actions\tUp Now\tyou\thave\tan\taction\tto\tset\tthe\tflash-alert\tto\tdisplay\tthe\tappropriate\tmessage.\tYou just\tneed\tto\tpass\tthe\taction\tsome\tdata.\tThat\tdata\twill\tbe\tan\tobject\twith\tkeys\tfor\talertType and\tmessage. The\talertType\twill\tbe\ta\tpart\tof\tboth\tthe\tmessage\tand\tthe\tstyle\twith\tBootstrap\talert\tvariants: \\\"success\\\",\t\\\"warning\\\",\t\\\"info\\\",\tor\t\\\"danger\\\".\tCalling\tthe\taction\tfrom\ta\tcontroller\twill look\tlike\tthis: this.send('flash',\t{alertType:\t\\\"success\\\",\tmessage:\t\\\"You\tDid\tIt!\tHooray!\\\"}); You\twill\tbe\tadding\tthe\tcall\tto\tthe\tapplication\taction\tafter\tcreating\ta\tnew\tsighting.\tIn app\/routes\/sightings\/new.js,\tadd\tthe\tfollowing: ... \t\t\t\tcreate()\t{ \t\t\t\t\t\tvar\tself\t=\tthis; \t\t\t\t\t\tthis.get('sighting').save().then(function(data){ \t\t\t\t\t\t\t\tself.send('flash',\t{alertType:\t\\\"success\\\",\tmessage:\t\\\"New\tsighting.\\\"}); \t\t\t\t\t\t\t\tself.transitionTo('sightings'); \t\t\t\t\t\t}); ... Now\tnavigate\tto\tcreate\ta\tnew\tsighting\tby\tclicking\tthe\tNew\tSighting\tbutton.\tSelect\ta\tcryptid and\ta\twitness,\ttype\ta\tnew\tlocation,\tand\tclick\tSave.\tOnce\tthe\tsighting\tis\tsaved\tin\tthe database,\tthe\tapp\twill\troute\tyou\tto\tthe\tlist\tof\tsightings\twith\ta\tnew\tflash\tmessage\tat\tthe\ttop of\tthe\tlist\t(Figure\t25.5). Figure\t25.5\t\tFlash\talert\t\u2013\tnew\tsighting The\tlast\tstep\tis\tadding\tthe\taction\tand\tevent\tthat\twill\tremove\tthe\talert\tfrom\tthe\tscreen.\tYou","only\twant\tto\tshow\tthe\tflash-alert\tafter\tyou\thave\tcreated\ta\tnew\tmessage,\tso\tyou\tneed\tto flip\tthe\tswitch\tto\thide\tthe\talert\twhen\tyou\tremove\tit. In\tapp\/controllers\/application.js,\tadd\tan\taction\tcalled\tremoveAlert: ... export\tdefault\tEmber.Controller.extend({ \t\talertMessage:\tnull, \t\talertType:\tnull, \t\tisAlertShowing:\tfalse, \t\tactions:\t{ \t\t\t\tremoveAlert(){ \t\t\t\t\t\tthis.set('alertMessage',\t\\\"\\\"); \t\t\t\t\t\tthis.set('alertType',\t\\\"success\\\"); \t\t\t\t\t\tthis.set('isAlertShowing',\tfalse); \t\t\t\t} \t} }); This\twill\tset\tisAlertShowing\tto\tfalse,\tset\tthe\talertMessage\tto\tan\tempty\tstring,\tand\tset the\talertType\tto\t\\\"success\\\". Next,\tsend\tthe\tremoveAlert\taction\tto\tthe\tcomponent.\tAdd\tthe\tfollowing\tto app\/templates\/application.hbs: ... <\/header> <div\tclass=\\\"container\\\"> \t\t{{#if\tisAlertShowing}} \t\t\t\t{{flash-alert\tmessage=alertMessage\talertType=alertType}} \t\t\t\t\t\tclose=(action\t\\\"removeAlert\\\")}} \t\t{{\/if}} \t\t{{outlet}} <\/div> The\tsyntax\tclose=(action\t\\\"removeAlert\\\")\tprobably\tlooks\tweird.\tThis\tis\tnew\tto\tEmber 2.0\tand\tis\tcalled\ta\tclosure\taction.\tThe\tfunction\tliteral\tis\tpassed\tthrough\tto\tbe\tcalled\tfrom the\tcomponent\tas\tan\tattribute\tnamed\tclose,\tmuch\tlike\tan\talias. Older\tversions\tof\tEmber\thad\ta\tmore\tcomplex\tversion\tof\tthis\tflow.\tClosure\tactions\tare\tmore than\tjust\tfunctions\tpassed\tfrom\tan\tobject\tas\tan\targument\tunder\tthe\thood.\tTo\tfind\tout\tmore about\tclosure\tactions,\tvisit\tthe\tEmberJS\tblog\tpost\tabout\tthe\tfeatures\tfor\tversion\t1.13\tand 2.0\tat\temberjs.com\/\u200bblog\/2\u200b 015\/0\u200b 6\/\u200b12\/\u200bember-1-13-0-released.html. Next,\tyou\tneed\tto\tcall\tthis\taction\tfrom\tthe\tcomponent.\tComponents\tare\tinstances\tof\tDOM elements;\tthus,\tthey\thave\tkey\/value\tpairs\trepresenting\tDOM\telement\tevents.\tYou\tcan\tadd a\tdeclaration\tof\ta\tclick\tevent\tlistener\tand\tEmber\twill\tadd\tthe\tlistener\tto\tthe\t<div>\telement wrapping\tthe\ttemplate.\tAdd\tthe\tfollowing\tto\tapp\/components\/flash-alert.js: import\tEmber\tfrom\t'ember'; export\tdefault\tEmber.Component.extend({ \t\t... \t\ttypeTitle:\tEmber.computed('alertType',\tfunction()\t{ \t\t\t\treturn\tEmber.String.capitalize(this.get('alertType')); \t\t}), \t\tclick()\t{ \t\t\t\tthis.get('close')(); \t\t} }); The\tproperty\tclose,\twhen\tcalled,\twill\tinvoke\tthe\taction\t\\\"removeAlert\\\"\tdefined\ton\tthe application\tcontroller.\tBy\tusing\ta\tclosure\taction,\tyou\thave\tassigned\ta\tcomponent\u2019s property\tto\ta\tfunction\tdefined\tin\tthe\tparent\tcontroller\tand\ttied\tthe\tcomponent\u2019s functionality\tto\tthe\tscope\tof\tits\tparent.\tYou\tcan\tadd\ta\tflash-alert\tat\tany\tlevel\tand\tassign different\tfunctionality\tto\tclose\tbased\ton\tits\tcontext. You\thave\tadded\ta\tcomponent\tthat\tresponds\tto\tdata\tgoing\tdown\tto\tcustomize\tthe component\tand\tto\tactions\tgoing\tup\tto\tset\tthe\texternal\tstate\tof\tthe\tcomponent\tfrom\tthe","parent\tcontroller.\tThis\tthe\tlifecycle\tof\ta\tcomponent:\tdata\tdown,\tactions\tup.\tKeep\tthis pattern\tin\tmind\twhen\tcreating\tcomponents. Throughout\tthe\tApplication\tArchitecture\tchapters\tyou\thave\tlearned\tabout\tthe\tstructure\tof modern\tapplications\tbuilt\twith\tEmber.\tYou\thave\tlearned\tabout\tthe\tpatterns\tof\tMVC\tand how\tthis\tframework\thelps\tyou\tseparate\tconcerns\twith\tpre-built\tJavaScript\tobjects.\tIt\talso helps\tyou\tmaintain\tsanity\twith\tnaming\tpatterns,\tscaffolding,\tbuild\ttools,\tand\tconventions. From\there\tyou\tcan\tfeel\tconfident\tcreating\ta\tnew\tapp\twith\tember\tnew.\tYou\tcan\tdive straight\tinto\tyour\tapplication\tneeds\tmodeling\tdata,\tcreating\troutes,\tand\tbuilding components. The\tEmber\tcommunity\tmaintains\tthis\twonderful\tframework\tand\tcontinues\tto\tbuild\tin efficiencies\tas\tJavaScript\tmatures.\tThis\tframework\tis\tbuilt\tby\tpeople\twho\thave\tstruggled with\tthe\tsame\tchallenges\tyou\twill\tface\tas\tyou\thone\tyour\tJavaScript\tskills.\tRemember\tto ask\tquestions\twhen\tsomething\tdoes\tnot\twork,\thelp\tfix\tbugs\twhen\tyou\tfind\tsomething\tthat is\tbroken,\tand\tgive\tback\twhen\tyou\tcan.\tYou\tare\tnow\tpart\tof\tthe\tgreater\tJavaScript community.","Bronze\tChallenge:\tCustomizing\tthe\tAlert\tMessage The\t{{flash-alert}}\tyou\ttrigger\twhen\tadding\ta\tsighting\tis\tgeneric.\tAdd\tthe\tsighting location\tand\tdate\tto\tthe\tmessage.","Silver\tChallenge:\tMaking\tthe\tNavBar\ta\tComponent Make\tthe\tNavBar\tin\tthe\tapplication\ttemplate\ta\tcomponent.\tAdd\ta\tproperty\tstate\tto\tshow two\tversions\tof\tthe\tnavigation.\tAdd\tconditional\tstatements\tin\tthe\tNavBar\tcomponent\tfor showing\tspecific\tlinks.","Gold\tChallenge:\tArray\tof\tAlerts Restructure\tthe\tflash-alert\tcomponent\tto\taccept\tan\tarray\tof\talerts\twith\tdifferent\talert types\tand\tmessages.\tYou\tmay\tneed\tto\thave\tmultiple\twarnings\ton\tthe\tscreen\tat\tthe\tsame time.\tUse\tan\tEmber.ArrayProxy\tin\tplace\tof\tthe\tindividual\tproperties\tsetting\tthe\talert.\tAdd to\tthe\tarray\twith\tthe\tmessage,\ttype,\tand\ta\tnew\tindex\tproperty\tso\tthat\tyou\tcan\tremove\tthe item\tfrom\tthe\tarray\twhen\tyou\tclick\tit.","26\t Afterword Congratulations!\tYou\tare\tat\tthe\tend\tof\tthis\tguide.\tNot\teveryone\thas\tthe\tdiscipline\tto\tdo what\tyou\thave\tdone\tand\tlearn\twhat\tyou\thave\tlearned.\tTake\ta\tquick\tmoment\tto\tgive yourself\ta\tpat\ton\tthe\tback. Your\thard\twork\thas\tpaid\toff:\tYou\tare\tnow\ta\tfront-end\tdeveloper. The\tFinal\tChallenge We\thave\tone\tlast\tchallenge\tfor\tyou:\tBecome\ta\tgood\tfront-end\tdeveloper.\tGood\tdevelopers are\tgood\tin\ttheir\town\tways,\tso\tyou\tmust\tfind\tyour\town\tpath\tfrom\there\ton\tout. Where\tmight\tyou\tstart?\tHere\tare\tsome\tideas: Write\tcode.\tNow.\tYou\twill\tquickly\tforget\twhat\tyou\thave\tlearned\there\tif\tyou\tdo\tnot\tapply your\tknowledge.\tContribute\tto\ta\tproject\tor\twrite\ta\tsimple\tapplication\tof\tyour\town. Whatever\tyou\tdo,\twaste\tno\ttime:\tWrite\tcode. Learn.\tYou\thave\tlearned\ta\tlittle\tbit\tabout\ta\tlot\tof\tthings\tin\tthis\tbook.\tDid\tany\tof\tthem spark\tyour\timagination?\tWrite\tsome\tcode\tto\tplay\taround\twith\tyour\tfavorite\tthing.\tFind and\tread\tmore\tdocumentation\tabout\tit\t\u2013\tor\tan\tentire\tbook,\tif\tthere\tis\tone.\tAlso,\tcheck\tout the\tJavaScript\tJabber\tpodcast\tfor\tsome\tthoughtful\tand\tentertaining\tdiscussion\tabout\tthe latest\tdevelopments\tin\tfront-end\tdevelopment\t(devchat.tv\/j\u200b s-jabber). Meet\tpeople.\tLocal\tmeetups\tare\ta\tgood\tplace\tto\tmeet\tlike-minded\tdevelopers.\tLots\tof\ttop- notch\tfront-end\tdevelopers\tare\tactive\ton\tTwitter.\tAnd\tyou\tcan\tattend\tfront-end conferences\tto\tmeet\tother\tdevelopers\t(maybe\teven\tus!). Explore\tthe\topen\tsource\tcommunity.\tFront-end\tdevelopment\tis\texploding\ton www.github.com.\tWhen\tyou\tfind\ta\tcool\tlibrary,\tcheck\tout\tother\tprojects\tfrom\tits contributors.\tShare\tyour\town\tcode,\ttoo\t\u2013\tyou\tnever\tknow\twho\twill\tfind\tit\tuseful\tor interesting.\tWe\tfind\tthe\tWDRL\t(Web\tDevelopment\tReading\tList)\tmailing\tlist\tto\tbe\ta\tgreat way\tto\tsee\twhat\tis\thappening\tin\tthe\tfront-end\tcommunity\t(wdrl.info).","Shameless\tPlugs You\tcan\tfind\tus\ton\tTwitter.\tChris\tis\t@radishmouse\tand\tTodd\tis\t@tgandee. If\tyou\tenjoyed\tthis\tbook,\tcheck\tout\tthe\tother\tBig\tNerd\tRanch\tGuides\tat www.bignerdranch.com\/b\u200b ooks.\tWe\talso\thave\ta\tbroad\tselection\tof\tweek-long courses\tfor\tdevelopers,\twhere\twe\tmake\tit\teasy\tto\tlearn\ta\tbook\u2019s\tworth\tof\tstuff\tin\tonly\ta week.\tAnd\tof\tcourse,\tif\tyou\tjust\tneed\tsomeone\tto\twrite\tgreat\tcode,\twe\tdo\tcontract programming,\ttoo.\tFor\tmore\tinfo,\tgo\tto\twww.bignerdranch.com.","Thank\tYou Without\treaders\tlike\tyou,\tour\twork\twould\tnot\texist.\tThank\tyou\tfor\tbuying\tand\treading\tour book."]


Like this book? You can publish your book online for free in a few minutes!
Create your own flipbook