["20\t Routing,\tRoutes,\tand\tModels At\tthis\tpoint,\tyou\thave\tthe\tshell\tfor\tyour\tTracker\tapplication.\tNow\tyou\tneed\tto\tdecide what\tpages\t\u2013\tor\troutes\t\u2013\tyour\tapplication\twill\tcontain. Routing\tis\tlike\ta\ttraffic\tcop:\tWhen\ta\tuser\tpulls\tup\ta\tspecific\tURL,\trouting\tdirects\tthe\tuser to\tthe\tdata\tthat\tmakes\tup\tthat\tpage.\tIn\tearlier\tprojects,\tyou\tbuilt\tevent\tlisteners\tfor\tform submission\tand\tbutton\tclicks.\tRouting\tis\tlike\tan\tevent\tlistener,\tbut\tit\twatches\tfor\tchanges to\tthe\tcurrent\tURL. Every\twebsite\tuses\tsome\tform\tof\trouting.\tFor\texample,\tif\tyou\tgo\tto www.bignerdranch.com\/\u200bwe-teach\/,\tthe\tserver\tmaps\tthe\troute\t\/we-teach\/\tto\tthe HTML\tfiles\tin\ta\tfolder\ton\tthe\tserver\tnamed\twe-teach.\tOther\tservers\tmay\tdo\tit\tdifferently: Instead\tof\tretrieving\tstatic\tHTML\tfiles,\tthey\tmay\trun\ta\tfunction\tthat\toutputs\tsome\tHTML. An\tEmber\tapp\tcan\tdo\tthe\tsame\tthing,\tbut\twithout\tasking\ta\tserver\tfor\tthe\tHTML.\tWhen your\tapp\tneeds\tto\tgo\tto\ta\tdifferent\tscreen,\tit\twill\tupdate\tthe\tURL\twith\ta\tnew\troute\tname. The\tRouter,\twhich\tis\ta\tchild\tof\tthe\tmain\tapplication\tobject,\thas\tevent\tlisteners\tand handlers\tfor\tURL\tchanges.\tUsing\tthe\tnew\troute,\tit\tdoes\ta\tlookup\tin\tits\trouting\ttable\tand finds\tan\tEmber.Route\tobject.\tThe\tRouter\tthen\tcalls\ta\tseries\tof\tmethods\tfrom\tthis\troute object,\twhich\tstarts\tthe\tprocess\tof\tgetting\tthe\tdata\tneeded\tfor\tthe\tnext\tscreen.\tThis\tprocess of\tcallbacks\tis\tcalled\troute\tlifecycle\thooks. Creating\troutes\tis\tfundamental\tto\tEmber\tdevelopment.\tEmber\u2019s\tnaming\tconventions\tassume you\twill\tbe\tcreating\tassociated\tcontrollers\tand\ttemplates\twith\tnames\tthat\tmatch\tyour routes.\tSo,\tfor\texample,\twhen\tyou\tcreate\ta\troute\tcalled\tsightings,\tthe\trouter\twill\tmap\ta request\tfor\t\/sightings\tto\tSightingsRoute,\twhich\tin\tturn\tsets\tup\ta\tSightingsController and,\tfinally,\trenders\ta\tapp\/templates\/sightings.hbs\ttemplate. In\tthis\tchapter,\tyou\twill\tlearn\tabout\tEmber\tapplication\tconstructs\tand\tuse\tEmber\tCLI\tto create\tTracker\u2019s\troute\tmodule\tfiles\tand\ttemplates.\tRoutes\tare\tthe\tkey\tto\tan\tEmber application,\tand\twork\tin\tthis\tchapter\twill\tset\tyou\tup\tto\tdevelop\tyour\tapp\tover\tthe\tnext\tfive chapters. Figure\t20.1\tshows\tTracker\tat\tthe\tend\tof\tthis\tchapter.","Figure\t20.1\t\tTracker\tapp ember\tgenerate Ember\tCLI\tprovides\ta\tscaffolding\ttool\tcalled\tgenerate\tthat\tcan\tbe\tuseful\twhile\tyou\tare learning\tEmber\u2019s\tconventions\tand\tnaming\tpatterns.\tYou\twill\tuse\tember\tgenerate,\tor\tember g\tfor\tshort,\tto\tcreate\tfiles\tand\tadd\tboilerplate\tcode\tto\tyour\tproject. Recall\tthat\tTracker\u2019s\tpurpose\tis\tto\trecord\tsightings\tof\tcryptids\t\u2013\tcreatures\tlike\tthe Sasquatch.\tIt\twill\ttrack\tinformation\tabout\tsighting\tevents,\tcryptids,\tand\twitnesses.\tIt\twill need\tquite\ta\tfew\troutes: Route Route\tpath Route\tdata index \/index no\tdata\t\u2013\tredirects\tto\tsightings sightings \/sightings list\tof\tsightings cryptids \/cryptids list\tof\tcryptids witnesses \/witnesses list\tof\twitnesses sighting \/sighting individual\tsighting\tdetails cryptid \/cryptid individual\tcryptid\tdetails witness \/witness individual\twitness\tdetails","sightings\tindex \/sightings\/index landing\tpage\tfor\tsightings\tlist sightings\tnew \/sightings\/new form\tto\tcreate\tnew\tsighting sighting\tindex \/sighting\/:sighting_id\/index landing\tpage\tfor\tindividual\tsighting sighting\tedit \/sighting\/:sighting_id\/edit form\tto\tedit\tindividual\tsighting You\twill\tcreate\tall\tof\tthese\twith\tember\tgenerate.\tOpen\tthe\tterminal\tand\tnavigate\tto\tyour tracker\tdirectory.\tRun\tthe\tfollowing\tcommands,\tone\tline\tat\ta\ttime,\tto\tgenerate\tyour routes: ember\tg\troute\tindex ember\tg\troute\tsightings ember\tg\troute\tsightings\/index ember\tg\troute\tsightings\/new ember\tg\troute\tsighting ember\tg\troute\tsighting\/index ember\tg\troute\tsighting\/edit ember\tg\troute\tcryptids ember\tg\troute\tcryptid ember\tg\troute\twitnesses ember\tg\troute\twitness This\twill\tlook\tsomething\tlike\tFigure\t20.2.","Figure\t20.2\t\tGenerating\troutes Now,\ttake\ta\tlook\tat\twhat\tember\tg\tcreated\tfor\tyou.\tYou\tshould\thave\tnew\tfiles\tunder\tthe routes\/\tand\ttemplates\/\tdirectories. Open\tapp\/routes\/index.js\tand\tnotice\tthat\tthe\tmodule\timports\tEmber\tand\texports an\tEmber.Route: import\tEmber\tfrom\t'ember'; export\tdefault\tEmber.Route.extend({ }); The\tmethod\t.extend\tcreates\ta\tnew\tsubclass\tof\tan\tEmber.Route\tand\taccepts\ta\tJavaScript object\tas\tits\targument.\tUsing\tthe\tES6\tmodule\tsyntax,\tyou\tcan\tcreate\tindividual\tmodules for\teach\troute.","Ember\tCLI\twill\tautomatically\tfind\tthe\tEmber.Route\tmodule\tyou\tjust\tcreated\tand\timport\tit into\tyour\tapp\t\u2013\twhether\tyou\tuse\tthe\tgenerate\tcommand,\tas\tyou\tdid\there,\tor\tcreate modules\tmanually.\tgenerate\tis\tconvenient\tbecause\tit\tadds\tsome\tboilerplate\tcode\tto\tthe file. Open\tapp\/templates\/index.hbs,\twhich\tEmber\tCLI\talso\tgenerated\tfor\tyou.\tThis template\twill\tbe\tused\tfor\tthe\tIndexRoute.\tIt\thas\ta\tsingle\tline\tin\tit:\t{{outlet}}. You\tprobably\tremember\tthis\tbit\tof\tcode\tfrom\tthe\tlast\tchapter,\twhen\tyou\twere\tediting\tthe templates\/application.hbs\tfile.\tThis\thelper\tallows\ttemplates\tto\tnest\tcontent\tbetween routes.\tYou\twill\tlearn\tmore\tabout\tthe\t{{outlet}}\thelper\tshortly. For\tnow,\tleave\tthis\tline\tin\tapp\/templates\/index.hbs\talone\tand\tadd\tan\tHTML <h1>\telement\tabove\tit: <h1>Index\tRoute<\/h1> {{outlet}} Now\tstart\tup\tthe\tapp\twith\tember\tserver.\tLeave\tthe\tserver\trunning\twhile\tworking\ton\tyour project.\tIf\tyou\tneed\tto\tinteract\twith\tthe\tEmber\tCLI\t(for\texample,\tto\tgenerate\tmore\tmodules), just\topen\ta\tsecond\tterminal\twindow.\tThe\tserver\twill\tload\tthe\tnew\tmodules\tinto\tyour\tEmber app\tand\treload\tyour\tbrowser. In\tChrome,\tnavigate\tto\thttp:\/\/localhost:4200.\tYour\tapp\tshould\tlook\tlike\tFigure\t20.3. Figure\t20.3\t\tIndex\troute You\tshould\tsee\tthe\tNavBar\telements\tfrom\tapp\/templates\/application.hbs\tand the\t<h1>\telement\tfrom\tapp\/templates\/index.hbs.\tHow\tdid\tthis\telement\tget\there? When\tyou\tcreated\tyour\tapplication,\ta\tnumber\tof\tfiles\twere\tgenerated\tfor\tyou,\tincluding","app.js\tand\trouter.js.\tThe\tapp.js\tfile\tis\tthe\tstarting\tpoint\tfor\tyour\tapplication, and\tit\thandles\tthings\tlike\tinitialization.\tIt\thas\tfunctions\tto\tcreate\ta\tnew\tEmber\tapp,\tmuch like\tcreating\ta\tnew\tTruck\tin\tCoffeeRun. In\tparticular,\tthe\tEmber\tapp\twill\tinstantiate\ta\tRouter\tobject\tand\tan\tApplicationRoute object\twhen\tthe\tapplication\tis\tloaded\tor\trestarted.\tThese\ttwo\tEmber\tobjects\tcontrol\tyour application. In\tyour\trouter.js\tfile,\tyou\twill\tregister\troutes\tto\tassociate\tURLs\twith\tspecific\tpages. Each\troute\tcan\tbe\tconfigured\twith\ta\tfew\toptions.\tYou\tcan\teven\tcreate\tnested\troutes.\tThis powerful\tfeature\tof\tEmber\tlets\tyou\treuse\tcontent\tand\tlogic\ton\tdifferent\tscreens. Open\trouter.js\tand\ttake\ta\tlook\tat\tthe\tmethod\tthat\tregisters\tyour\troutes: import\tEmber\tfrom\t'ember'; import\tconfig\tfrom\t'.\/config\/environment'; const\tRouter\t=\tEmber.Router.extend({ \t\tlocation:\tconfig.locationType }); 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\tthis.route('witnesses'); \t\tthis.route('witness'); }); export\tdefault\tRouter; Router.map\tis\tbeing\tpassed\ta\tcallback.\tInside\tthis\tcallback,\tthe\troute\tmethod registers\tyour\troutes.\tThis\tmethod\talso\ttakes\tcallbacks.\tEmber\tconverts\tthese\tnested callbacks\tinto\ta\thierarchy\tof\troutes.\tAt\tthe\ttop\tof\tthis\thierarchy\tis\tthe\tApplicationRoute. When\tyou\tvisit\tthe\tURL\tfor\ta\tnested\troute,\tEmber\tuses\tthe\tHTML\tfrom\tthe\tparent template.\tInside\tthat\tparent\ttemplate,\tit\twill\tlook\tfor\tan\t{{outlet}}\thelper,\twhich indicates\t\u201cThis\tis\twhere\tyou\tshould\tput\tthe\tHTML\tfrom\tthe\tchild\ttemplate.\u201d Let\u2019s\tsee\thow\tthis\tworks\tin\tyour\tapp. Ember\trenders\tthe\tcontent\tof\tthe\tApplicationRoute\twith\tthe\tcontent\tof\tthe\tIndexRoute nested\tinside.\tBehind\tthe\tscenes,\tEmber\tis\tchecking\tfor\ta\tfile\tcalled\tindex.js\tin\tthe routes\/\tfolder\tof\tyour\tproject.\tYou\tcan\tcreate\ta\tlanding\tpage\tfor\tany\troute\tby\tcreating\tan index.js\tin\tthe\tcorresponding\tfolder.\tIn\tfact,\tthis\tis\ta\tcommon\tpractice\tthat\tyou\tshould use\tin\tyour\town\tEmber\tapps. You\tmay\thave\tnoticed\tthat\tthere\tare\tno\treferences\tto\tindex\troutes\tin\trouter.js.\tEmber autogenerates\tthe\tindex\troute\tfor\tall\tparent\troutes\twith\tnested\tchild\troutes,\tjust\tas\tthough they\tappeared\tin\trouter.js: ... Router.map(function()\t{ \t\tthis.route('index'); \t\tthis.route('sightings',\tfunction()\t{ \t\t\t\tthis.route('index'); \t\t\t\tthis.route('new'); \t\t}); \t\tthis.route('sighting',\tfunction()\t{ \t\t\t\tthis.route('index'); \t\t\t\tthis.route('edit'); \t\t}); \t\tthis.route('cryptids'); \t\tthis.route('cryptid'); \t\tthis.route('witnesses');","this.route('witness'); }); ...","Nesting\tRoutes Routes\tallow\tyou\tto\tstructure\tdata\tin\tviews.\tLike\tfolders,\tnested\troutes\tgroup\ttogether related\troutes\tunder\ta\tbase\tURL.\tIt\tis\thelpful\tto\tthink\tof\tparent\troutes\tas\trepresenting nouns\tand\tchild\troutes\tas\trepresenting\tverbs\tor\tadjectives: \/\/\tParent\troute\tis\tnoun this.route('sightings',\tfunction()\t{ \t\t\/\/\tChild\troute\tis\tverb\tor\tadjective \t\tthis.route('new'); }); sightings\tis\ta\tparent\troute\trepresenting\tsightings,\twhich\tare\tthings\t(nouns),\tand\tnew\tis\ta nested\troute\trepresenting\tthe\taction\tof\tcreating\ta\tsighting\t(a\tverb).\tthis.route\tis\tused to\tbuild\tup\tthe\tURL\tincluding\tthe\tparent\tand\tchild. With\ttemplate\tnesting,\tparts\tof\tyour\tsite\tcan\tbe\trendered\ton\tall\troutes\t(such\tas navigation),\twhile\tothers\twill\tonly\tshow\ton\tmore\tspecific\troutes\t(like\tIndexRoute\ton\tthe root\tURL).\tYou\twill\tinstruct\teach\troute\ton\thow\tto\tretrieve\tits\tdata\tusing\tcallback functions. Now,\tyou\tare\tgoing\tto\tedit\tsome\tof\tthe\ttemplate\tfiles\tyou\tgenerated\talong\twith\tyour routes\tand\tnavigate\tto\tdifferent\tpages\tof\tyour\tapplication.\tThe\tcode\tyou\tare\tadding\tin\tthis section\tis\ttemporary,\tbut\tit\twill\tallow\tyou\tto\tsee\tthe\trelationship\tbetween\tyour\troutes. To\tbegin,\topen\tthe\tapp\/templates\/sightings.hbs\ttemplate\tfile\tand\tadd\tan\t<h1> element\tabove\tthe\texisting\t{{outlet}}\thelper. <h1>Sightings<\/h1> {{outlet}} Next,\tedit\tthe\tapp\/templates\/sightings\/index.hbs\ttemplate.\tAdd\tanother <h1>\telement,\tand\tthis\ttime\tdelete\tthe\t{{outlet}}\thelper.\tParent\ttemplates\tuse {{outlet}}\tto\tnest\tchild\tviews.\tapp\/templates\/sightings\/index.hbs\tis\ta child\ttemplate\twithout\tany\tnested\troute,\tso\tit\tdoes\tnot\tneed\tan\t{{outlet}}\thelper. {{outlet}} <h1>Index\tRoute<\/h1> Save\tyour\tfiles\tand\tpoint\tyour\tbrowser\tto\thttp:\/\/l\u200b ocalhost:4200\/s\u200b ightings\/ to\tsee\tthe\tresults\t(Figure\t20.4).","Figure\t20.4\t\tSightings:\tnested\troutes Next,\tedit\tapp\/templates\/sightings\/new.hbs.\tThis\troute\ttree\talso\tends\twith this\tchild\troute,\tso\tdelete\t{{outlet}}\tand\tadd\tan\t<h1>\telement. {{outlet}} <h1>New\tRoute<\/h1> Now,\tchange\tthe\tURL\tin\tyour\tbrowser\tto\thttp:\/\/localhost:4200\/sightings\/new (Figure\t20.5).","Figure\t20.5\t\tSightings:\tnew\troute Your\tTracker\tapp\tnow\thas\tnested\troutes\trendering\ta\ttemplate\tfor\tthe\tparent app\/templates\/sightings.hbs\tfile\tand\tfor\teach\tchild: app\/templates\/sightings\/index.hbs\tand app\/templates\/sightings\/new.hbs.\tThe\tparent\ttemplate\tuses\t{{outlet}}\tto nest\tthe\tviews.","Ember\tInspector The\tEmber\tInspector\tgives\tyou\tan\teasy\tway\tto\tsee\tall\tof\tyour\tapplication\u2019s\troutes.\tClick\tthe Routes\tmenu\titem\tin\tthe\tEmber\tInspector\tto\tsee\tthem\t(Figure\t20.6). Figure\t20.6\t\tRoute\tstructure That\tis\ta\tlot\tof\troutes!\tEven\tmore\tthan\tyou\tgenerated.\tNotice\tthat\tthere\tare\tnumerous routes\tending\tin\tloading\tand\terror.\tThese\tare\tautogenerated\troutes\tfor\tthe\tlifecycle\tstates of\tloading\tdata\tin\troutes.\tLike\tindex\troutes,\tthese\tobjects\tare\tcreated\tby\tEmber\tto\tfill\tgaps to\tget\tfrom\tone\troute\tstate\tto\tanother\troute\tstate.","Assigning\tModels The\tnext\tstep\tis\tto\tget\tdata\tto\teach\troute\tusing\tthe\troute\u2019s\tmodel\tcallback.\tEach Ember.Route\thas\ta\tmethod\tto\tassign\ta\tmodel\t(which,\tremember,\tis\tthe\tdata\tbacking\tthe template)\tto\ta\tcontroller.\tThis\tmethod,\tcalled\tmodel,\treturns\tdata\tas\ta\tPromise. Under\tthe\thood,\tthe\tEmber\tapp\tinitializes\tthe\tRoute\tobject\twhen\tthe\tURL\tchanges.\tThis Route\tobject\thas\tfour\thooks\tto\tset\titself\tup:\tbeforeModel,\tmodel,\tafterModel,\tand setupController. We\twill\tfocus\ton\tthe\tmodel\tcallback\tfor\tnow. Add\tsome\tdummy\tdata\tin\tthe\tmodel\tcallback\tin\tthe\tSightingsRoute, app\/routes\/sightings.js: import\tEmber\tfrom\t'ember'; export\tdefault\tEmber.Route.extend({ \t\tmodel(){ \t\t\t\treturn\t[ \t\t\t\t\t\t{ \t\t\t\t\t\t\t\tid:\t1, \t\t\t\t\t\t\t\tlocation:\t'Asilomar', \t\t\t\t\t\t\t\tsightedAt:\tnew\tDate('2016-03-07') \t\t\t\t\t\t}, \t\t\t\t\t\t{ \t\t\t\t\t\t\t\tid:\t2, \t\t\t\t\t\t\t\tlocation:\t'Asilomar', \t\t\t\t\t\t\t\tsightedAt:\tnew\tDate('2016-03-07') \t\t\t\t\t\t}, \t\t\t\t\t\t{ \t\t\t\t\t\t\t\tid:\t3, \t\t\t\t\t\t\t\tlocation:\t'Asilomar', \t\t\t\t\t\t\t\tsightedAt:\tnew\tDate('2016-03-07') \t\t\t\t\t\t}, \t\t\t\t\t\t{ \t\t\t\t\t\t\t\tid:\t4, \t\t\t\t\t\t\t\tlocation:\t'Asilomar', \t\t\t\t\t\t\t\tsightedAt:\tnew\tDate('2016-03-07') \t\t\t\t\t\t}, \t\t\t\t\t\t{ \t\t\t\t\t\t\t\tid:\t5, \t\t\t\t\t\t\t\tlocation:\t'Asilomar', \t\t\t\t\t\t\t\tsightedAt:\tnew\tDate('2016-03-07') \t\t\t\t\t\t}, \t\t\t\t\t\t{ \t\t\t\t\t\t\t\tid:\t6, \t\t\t\t\t\t\t\tlocation:\t'Asilomar', \t\t\t\t\t\t\t\tsightedAt:\tnew\tDate('2016-03-07') \t\t\t\t\t\t} \t\t\t\t]; \t\t} }); Notice\tthe\tsyntax\tof\tthe\tmodel\thook: model()\t{ \t\t[your\tcode\tgoes\there] } This\tis\tES6\tshorthand\tfor: model:\tfunction()\t{ \t\t[your\tcode\tgoes\there] } Throughout\tthe\tnext\tchapters\tyou\twill\tbe\tusing\tthis\tsyntax\tto\tdefine\tyour\tobject\tmethods in\tEmber. The\tmodel\tcallback\tis\ta\tplace\tto\tretrieve\tdata\tneeded\tto\trender\ta\ttemplate.\tThe\troute lifecycle\tmethods\tin\tan\tEmber.Route\treturn\tobjects\tfor\teach\thook.\tThe\tmodel\thook\twill eventually\treturn\tdata\tto\ta\tsetupController\thook,\twhich\tsets\ta\tproperty\tnamed","model\ton\tSightingsController.\tYou\tcan\taccess\tthis\tdata\tin\tyour\ttemplates: app\/templates\/sightings.hbs\tand app\/templates\/sightings\/index.hbs. Edit\tapp\/templates\/sightings\/index.hbs\tas\tshown.\tWe\twill\texplain\tthe\tcode after\tyou\tenter\tit. <h1>Index\tRoute<\/h1> <div\tclass=\\\"panel\tpanel-default\\\"> \t\t<ul\tclass=\\\"list-group\\\"> \t\t\t\t{{#each\tmodel\tas\t|sighting|}} \t\t\t\t\t\t<li\tclass=\\\"list-group-item\\\"> \t\t\t\t\t\t\t\t{{sighting.location}}\t-\t{{sighting.sightedAt}} \t\t\t\t\t\t<\/li> \t\t\t\t{{\/each}} \t\t<\/ul> <\/div> This\tcode\tmight\tlook\tstrange\tif\tyou\thave\tnever\tused\ttemplate\tlanguages.\tThe\twords\tin\tthe double\tcurlies\t({{\t}})\tare\tessentially\tJavaScript\tfunctions\tdisguised\tas\tstatements.\tIn English,\tthese\tlines\tsay,\t\u201cFor\teach\tsighting\tin\tthe\tmodel\tproperty\t(which\tis\texpected\tto be\tan\tarray),\trender\tan\t<li>\telement\twith\tthe\tsighting\u2019s\tlocation\tand\tsightedAt\tdate.\u201d You\twill\tlearn\tabout\t{{\t}}\tsyntax\tin\tgeneral\tand\t{{#each}}\tin\tparticular\tin\tChapter\t23. Switch\tto\thttp:\/\/l\u200b ocalhost:4200\/s\u200b ightings\tin\tyour\tbrowser,\twhere\tyour\tapp should\tlook\tlike\tFigure\t20.7. Figure\t20.7\t\tIndex\tmodel\tlisting You\thave\tnow\tcompleted\tthe\tfirst\thalf\tof\tthe\troute\tcycle\tby\tpassing\tdata\tto\tthe\ttemplate for\tit\tto\tdisplay.\tIn\tthe\tnext\tchapter,\tyou\twill\texplore\tthe\tHandlebars\ttemplating\tlanguage. This\tlanguage\tallows\tyou\trepresent\tthe\tstate\tof\tyour\tapplication\twith\tproperties\tfrom\ta controller,\trendering\tonly\tnecessary\tDOM\telements\tas\tthe\tstate\tof\tthe\tapplication\tchanges.","beforeModel As\tdescribed\tabove,\tthe\troute\tobject\tcalls\ta\tsequence\tof\tfunctions,\tstarting\twith beforeModel.\tThis\tfunction\tis\ta\tgood\tplace\tto\tcheck\tthe\tstate\tof\tthe\tapplication\tbefore retrieving\tdata.\tIt\tis\talso\ta\tgood\tplace\tto\treroute\ta\tuser\twho\tcannot\tbe\ton\ta\tpage,\tsuch\tas\tto check\tfor\tuser\tauthentication. You\twill\tuse\tbeforeModel\tto\tunconditionally\ttransition\tthe\tuser\tto\ta\tnew\tpage.\tThe IndexRoute\tis\ta\tgood\tplace\tto\tdo\tthis.\tYou\tmay\twant\tto\tadd\ta\tdashboard\tin\tthe\tfuture,\tbut for\tnow\tthe\tlanding\tpage\twill\tbe\tsightings. In\tapp\/routes\/index.js,\tadd\ta\tbeforeModel\tcallback: import\tEmber\tfrom\t'ember'; export\tdefault\tEmber.Route.extend({ \t\tbeforeModel(){ \t\t\t\tthis.transitionTo('sightings'); \t\t} }); Now,\twhen\tyou\tnavigate\tto\thttp:\/\/\u200blocalhost:4200\/\tthe\tURL\tchanges\tto http:\/\/l\u200b ocalhost:4200\/s\u200b ightings,\tand\tyou\tshould\tsee\tthe\tsightings\tlist\tfrom app\/templates\/sightings\/index.hbs. The\tlast\ttwo\trouting\thooks,\tafterModel\tand\tsetupController,\twill\tnot\tbe\tused\tin Tracker.\tWhen\tcreating\ta\troute\tfile,\tyou\tare\tcloning\tthe\tEmber.Route\tobject\tand overwriting\tthe\tmethod,\tmuch\tlike\tan\tinterface\tin\tlanguages\tlike\tJava.\tThe setupController\thook\twill\trun\tby\tdefault\tto\tset\tthe\tmodel\tproperty\ton\tthe\troute object\u2019s\tcontroller. At\tthis\tpoint,\tyour\tapplication\thas\tsome\tbasic\troutes\tthat\toutline\tits\tfunctionality:\ta landing\tpage,\ta\tlist\tof\tsightings,\tand\ta\troute\tfor\tadding\ta\tnew\tsighting.\tYou\tcreated templates\tfor\tyour\troutes\tand\tadded\tmodel\tdata\tto\tthe\tsightings\troute.\tYou\trerouted\tthe index\troute\tto\tthe\tsightings\tindex.\tYou\tare\toff\tto\ta\tgreat\tstart! In\tthe\tnext\tchapter,\tyou\twill\tlearn\tabout\tEmber.Models,\tadapters,\tcomputed\tproperties,\tand storage\tmechanisms.","For\tthe\tMore\tCurious:\tsetupController\tand\tafterModel The\thook\tsetupController\tis\tfor\tsetting\tproperties\ton\ta\tcontroller\tthat\twill\trender those\tproperties.\tIt\tis\tpossible\tto\trun\tthe\tdefault\tbehavior\tof\tsetting\tthe\tcontroller\u2019s model\tproperty\twhile\tsetting\tother\tactive\tcontroller\tproperties\twith\tthis._super: setupController(controller,\tmodel)\t{ \t\tthis._super(controller,\tmodel); \t\t\/\/\tthis.controllerFor('[other\tcontroller]').set(\\\"[property\tname]\\\",\t[value]); } The\thook\tafterModel\tis\trun\tafter\tthe\tmodel\thook\t(which\tis\ta\tPromise)\tis\tresolved. Note\tthat\tthere\tare\tspecial\tcases\twhere\tthe\tmodel\thook\twould\tnot\tbe\tcalled\tbecause\tthe Promise\thas\talready\tbeen\tresolved.\tIn\tthese\tcases,\tafterModel\tis\tcalled\tbefore setupController\tand\tcan\tbe\tused\tas\ta\tmethod\tto\ttest\tthe\tintegrity\tof\tthe\tmodel\tdata before\tpassing\tit\tto\tthe\tcontroller.","21\t Models\tand\tData\tBinding For\tthe\tnext\tpart\tof\tthe\tTracker\tapp,\tyou\twill\tfocus\tonly\ton\tthe\tdata\tlayer. You\thave\talready\tworked\tquite\ta\tbit\twith\tdata\tin\tthe\tform\tof\tobject\tliterals.\tYou\thave created\tand\tmodified\tobjects\tand\ttheir\tproperties,\tand\tyou\thave\tcreated\tfunctions\tto quickly\tmake\tobjects\twith\tdefault\tvalues.\tYou\tknow\thow\tto\tstore\tdata\tin\tlocalStorage and\tsessionStorage. In\tTracker,\tyou\tare\tgoing\tto\twork\twith\tdata\tin\tthe\tform\tof\tmodels.\tModels\tare\tessentially functions\tthat\tcreate\tobjects\twith\tspecific\tproperties\tand\tmethods.\tThey\tare\tthe architecture\tof\tdata\tflowing\tthrough\tyour\tapplication. Ember\thas\tan\tobject\tclass\tthat\tcan\ttake\tcare\tof\tyour\tinitial\tneed\tto\tdefine\tyour\tapp\u2019s\tdata architecture:\tEmber.Object.\tAll\tEmber\tclasses\tinherit\tfrom\tthis\tclass.\tWith\ta\tsimple definition\tand\tnaming\tpattern,\tEmber\tgives\tyou\tthe\tpower\tto\tcreate,\tretrieve,\tupdate,\tand destroy\tmodel\tinstances\twhile\tthe\tapp\tis\trunning. However,\tfor\tyour\tmodern\tapplication\tyou\tneed\tmore\tthan\twhat\tEmber.Object provides.\tYour\tmodels\tneed\tto\tbe\table\tto\tpersist\tthemselves\twhen\tbusiness\tlogic\tasks them\tto\tretrieve\tor\tsave\tdata\tfrom\ta\tdata\tsource. Enter\tEmber\tData,\ta\tJavaScript\tlibrary\tbuilt\ton\ttop\tof\tEmber.Object,\twhich\twill\thelp you\tadd\tmodel-specific\tfunctionality.\tEmber\tData\tadds\tclasses\tbuilt\ton\tEmber.Object that\tabstract\tthe\tcomplexity\tof\tworking\twith\tvarious\tdata\tsources:\tRESTful\tAPIs, localStorage,\tand\teven\tstatic\tfixture\tdata. Ember\tData\talso\tadds\tan\tin-memory\tstore\tfor\tdata.\tThe\tdata\tstore\tis\twhere\tyou\tcreate, retrieve,\tupdate,\tand\tdelete\tyour\tmodel\tinstances. Model\tDefinitions Ember\tCLI\thas\talready\tloaded\tthe\tEmber\tData\tlibrary,\tso\tyou\tare\tready\tto\tbuild\tyour\tmodels. In\tthe\tlast\tchapter,\tyou\tused\tember\tg\troute\t[route\tname]\tto\tmake\tEmber\tCLI\tcreate\troute files\tfor\tyou.\tYou\tcan\talso\tuse\tember\tgenerate\tto\tcreate\ta\tmodel\tfile\twith\tthe\tcommand ember\tg\tmodel\t[model\tname]. Create\tthe\tmodel\tfiles\tyou\twill\tneed\tto\tback\tyour\troutes\tfor\tcryptids,\tsightings,\tand witnesses: ember\tg\tmodel\tcryptid ember\tg\tmodel\tsighting ember\tg\tmodel\twitness Cryptids\twill\thave\ta\tmodel\tdefinition\tin\tthe\tfile\tapp\/models\/cryptid.js.\tOpen\tthis file\tand\tadd\tattributes\tfor\tname,\tcryptid\ttype\t(species),\tprofile\timage,\tand\tsightings\tto\tthe cryptid\tmodel:","import\tDS\tfrom\t'ember-data'; export\tdefault\tDS.Model.extend({ \t\tname:\tDS.attr('string'), \t\tcryptidType:\tDS.attr('string'), \t\tprofileImg:\tDS.attr('string'), \t\tsightings:\tDS.hasMany('sighting') }); Ember\tData,\treferenced\there\tas\tDS\t(for\t\u201cdata\tstore\u201d),\thas\tan\tattr\tmethod\tthat\tis\tused\tto specify\tmodel\tattributes.\tWhen\tdata\tis\tparsed\tfrom\tthe\tsource,\tattr\treturns\tthe\tvalue.\tIf you\tgive\tattr\tan\tattribute\ttype,\tit\twill\tbe\tcoerced\tto\tthat\ttype.\tIf\tyou\tdo\tnot\tset\tthe attribute\ttype,\tyour\tdata\twill\tbe\tpassed\tthrough\tto\tthe\tappropriate\tkey\tunchanged. There\tare\ta\tfew\tattribute\ttypes\tbuilt\tin:\tstring,\tnumber,\tboolean,\tand\tdate.\tYou can\talso\tcreate\tcustom\tmodel\tattributes\tusing\ttransforms,\twhich\tyou\twill\tlearn\tabout\tin Chapter\t22. attr\tcan\talso\ttake\ta\tsecond\targument\tto\tspecify\tdefault\tvalues.\tThis\toptional\targument\tis a\thash\twith\ta\tsingle\tkey:\tdefaultValue.\tHere\tare\tsome\texamples: name:\tDS.attr('string',\t{defaultValue:\t'Bob'}), isNew:\tDS.attr('boolean',\t{defaultValue:\ttrue}), createdAt:\tDS.attr('date',\t{defaultValue:\tnew\tDate()}), numOfChildren:\tDS.attr('number',\t{defaultValue:\t1}) In\tthe\tcryptid\tdefinition,\tyou\tused\tthe\tstring\tattribute\ttype\tfor\tthe\tname, cryptidType,\tand\tprofileImg\tattributes.\t(Why\ta\tstring\ttype\tfor\tprofileImg? It\twill\treference\tthe\timage\tpath,\tnot\tthe\timage\titself.) The\tsightings\tattribute\tuses\ta\tdifferent\tmethod\tto\tdefine\tits\tdata:\thasMany.\tThis method\tis\tpart\tof\tEmber\tData\u2019s\trelationship\tmethods.\tWhen\tyou\tquery\ta\tRESTful\tAPI\tfor\ta cryptid,\tit\twill\thave\tassociated\tsightings.\tThat\tassociation\twill\tbe\treturned\tas\tan\tarray\tof sighting\tids\treferencing\tan\tinstance\tof\ta\tsighting\tmodel. Ember\tData\thas\tmethods\tto\thandle\tone-to-one,\tone-to-many,\tand\tmany-to-many\trelationship types: Relationship \u201cOwning\u201d\tmodel \u201cOwned\u201d\tmodel one-to-one DS.hasOne DS.belongsTo one-to-many DS.hasMany DS.belongsTo many-to-many DS.hasMany DS.hasMany The\tfirst\targument\tis\tthe\tmodel\tto\tassociate.\tIn\tyour\tapp,\ta\tcryptid\twill\thave\tmany sighting\tinstances\t(you\twould\tbe\tsurprised\thow\toften\tpeople\tsee\tthese\tcreatures).\tThe second\targument\tis\tan\toptional\thash\twhich,\tsimilar\tto\tattr\u2019s\tsecond\targument,\tis\ta configuration\tobject\tto\tset\tvalues\twhen\tevaluating\tthe\tfunction.\tIt\tcontains\tan\tasync\tkey and\ta\tvalue\t(with\ta\tdefault\tof\ttrue). Model\trelationships\tcould\trequire\trequests\tto\ta\tserver\tto\tretrieve\tother\tmodel\tdata.\tFor cryptids,\ta\trequest\tto\tsightings\tis\tneeded\tto\tdisplay\tsighting\tdata\tfor\teach\tcryptid.\tThe same\tis\ttrue\tfor\tthe\tinverse\trelationship\tof\tsightings\tbelonging\tto\ta\tcryptid.\tThe\tdefault value,\tasync:\ttrue,\trequires\ta\tseparate\trequest\tand\tAPI\tendpoint\tto\tretrieve\tthe\tlinked data.","If\tyour\tAPI\thas\tthe\tability\tto\tsend\tall\tthe\tdata\ttogether,\tyou\tcan\tset\tthe\tasync\tvalue\tto false.\tFor\tthe\tTracker\tapp,\tleave\tthe\tvalue\tas\tthe\tdefault,\ttrue. Next,\topen\tthe\tmodel\tfor\twitnesses\tin\tapp\/models\/witness.js\tand\tadd\tattributes for\ta\twitness\u2019s\tfirst\tand\tlast\tname,\temail\taddress,\tand\trecorded\tsightings: import\tDS\tfrom\t'ember-data'; export\tdefault\tDS.Model.extend({ \t\tfName:\tDS.attr('string'), \t\tlName:\tDS.attr('string'), \t\temail:\tDS.attr('string'), \t\tsightings:\tDS.hasMany('sighting') }); You\tdefined\ta\twitness\tto\tbe\tan\tobject\tthat\tcontains\ta\tfirst\tname\t(fName),\ta\tlast\tname (lName),\tan\temail\taddress\t(email),\tand\ta\tmany-to-many\trelationship\tto\tsightings (sightings). Finally,\topen\tyour\tthird\tmodel\tfile:\tapp\/models\/sighting.js.\tAdd\tattributes\tto your\tsightings\tmodel\tfor\tthe\twho,\twhat,\twhere,\tand\twhen\tof\tthe\tsighting\tas\twell\tas\tthe date\tthe\tsighting\twas\trecorded: import\tDS\tfrom\t'ember-data'; export\tdefault\tDS.Model.extend({ \t\tlocation:\tDS.attr('string'), \t\tcreatedAt:\tDS.attr('date'), \t\tsightedAt:\tDS.attr('date'), \t\tcryptid:\tDS.belongsTo('cryptid'), \t\twitnesses:\tDS.hasMany('witness') }); Sightings\tare\tdefined\tmuch\tlike\twitnesses\tand\tcryptids,\twith\tbasic\tproperties\tdefined\tas strings.\tThe\tlocation\tis\ta\tvalue\tthe\tuser\twill\tinput\tin\tthe\tapp,\twhile\tcreatedAt\tand sightedAt\twill\tbe\tadded\tserver-side\twhen\tthe\tsighting\thas\tbeen\tadded\tto\tthe\tdatabase. The\trelationship\tfor\tthe\tproperty\tcryptid\tis\tsomething\tnew, DS.belongsTo(\u2018cryptid\u2019).\tThis\tmethod\tis\ta\tone-to-many\trelationship\tlinking\ta cryptid\tinstance\tto\tthe\tsighting\tinstance\t\u2013\tone-to-many\tbecause\teach\tcryptid\twill have\tmany\tsightings.","createRecord When\tthe\tapplication\tinitializes,\tEmber\tData\tcreates\tstore,\ta\tlocal\tstore\tobject.\tthis.store is\tthe\tobject\tthat\twill\tcreate,\tretrieve,\tupdate,\tand\tdelete\tall\tof\tthe\tTracker\tapp\u2019s\tmodel records.\tEmber\tinjects\tthe\tstore\tobject\tin\tall\tRoutes,\tControllers,\tand\tComponents.\tIn\tthe scope\tof\troute\tmethods,\tyou\thave\taccess\tto\tthe\tstore\tfrom\tthis. To\tcreate\ta\trecord,\tyou\twill\tcall\tthis.store.createRecord.\tThis\tmethod\texpects two\targuments:\ta\tmodel\tname,\tas\ta\tstring,\tand\trecord\tdata,\tas\tan\tobject. Open\tapp\/routes\/sightings.js.\tDelete\tyour\tdummy\tsightings\tand\tcreate\tthree new\tsighting\trecords,\teach\twith\ta\tlocation\tvalue\tas\ta\tstring\tand\ta\tsightedAt value\tas\ta\tnew\tDate: import\tEmber\tfrom\t'ember'; export\tdefault\tEmber.Route.extend({ \t\tmodel()\t{ \t\t\t\treturn\t[ \t\t\t\t\t\t{ \t\t\t\t\t\t\t\tid:\t1, \t\t\t\t\t\t\t\tlocation:\t'Asilomar', \t\t\t\t\t\t\t\tsighted_at:\tnew\tDate('2016-03-07') \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\tid:\t6, \t\t\t\t\t\t\t\tlocation:\t'Asilomar', \t\t\t\t\t\t\t\tsightedAt:\tnew\tDate('2016-03-07') \t\t\t\t\t\t} \t\t\t\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\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\t}); \t\t\t\tlet\trecord3\t=\tthis.store.createRecord('sighting',\t{ \t\t\t\t\t\tlocation:\t'Asilomar', \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} }); In\tChapter\t20,\tthe\tsightings\troute\tmodel\treturned\tan\tarray.\tInstead\tof\treturning JavaScript\tobjects,\tyou\thave\tcreated\tthree\tsighting\trecords\tand\treturned\tthese\trecords\tin an\tarray.\tRun\tember\tserver,\tif\tit\tis\tnot\trunning\talready,\tto\tsee\tyour\tnew\trecords\ton\tthe sightings\troute,\thttp:\/\/\u200blocalhost:4200\/s\u200b ightings\t(Figure\t21.1).","Figure\t21.1\t\tcreate\tsightings This\texample\tshows\tthat\tcreating\tEmber\tData\tmodels\tis\tvery\tsimilar\tto\tcreating\tJavaScript objects.\tThe\tadvantage\tto\thaving\tEmber\tData\tmodel\tobjects\tis\tall\tthe\tmethods\tthese\tobjects give\tyou.\tLet\u2019s\tstart\twith\tget\tand\tset.","get\tand\tset At\tthe\tcore\tof\tEmber\tData\u2019s\tmodel\trecords\tis\tan\tEmber.Object.\tThis\tobject\tdefinition contains\tthe\tmethods\tget\tand\tset.\tUnlike\tmost\tlanguages,\tJavaScript\tdoes\tnot\tforce\tthe use\tof\tgetters\tand\tsetters\ton\tobject\tinstances.\tEmber\tapplies\tthe\tconcepts\tof\tgetters\tand setters\twith\tthese\tmethods\tto\tforce\ta\tfunction\tto\tbe\trun\twhen\tchanging\tan\tobject\tproperty. This\tallows\tEmber\tto\tadd\tevent\ttriggers\tto\tset\tand\tmake\tprogrammers\tbe\tintentional when\tgetting\tproperties. The\tget\tmethod\ttakes\ta\tsingle\targument,\tthe\tproperty\tname,\tto\tretrieve\tthe\tproperty value.\tTry\tit\tout\tin\tthe\tapp\/routes\/sightings.js\tmodel\tcallback. 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\tconsole.log(\\\"Record\t1\tlocation:\t\\\"\t\t+\t\trecord1.get('location')\t); \t\t\t\t... \t\t\t\treturn\t[record1,\trecord2,\trecord3]; \t\t} }); Reload\tyour\tbrowser.\tMake\tsure\tthe\tDevTools\tare\topen\tand\tselect\tthe\tJavaScript\tconsole tab.\tYou\tshould\tsee\tthe\tlog\tnotes\tfrom\tEmber,\tending\twith\tthe\tline:\t\u201cRecord\t1\tlocation: Atlanta.\u201d Next,\tback\tin\tapp\/routes\/sightings.js,\tset\tthe\tvalue\tof\trecord1\u2019s location\tafter\tcreating\tthe\trecord\tand\tbefore\tyou\tlog\tthe\tproperty. 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\t... \t\t\t\treturn\t[record1,\trecord2,\trecord3]; \t\t} }); Reload\tthe\tbrowser\tand\tyou\twill\tsee\tthat\tthe\tconsole\treflects\tthe\tset\tvalue:\t\u201cRecord\t1 location:\tParis,\tFrance\u201d\t(Figure\t21.2).","Figure\t21.2\t\tset\tlocation These\tare\tbasic\texamples\tof\tget\tand\tset.\tWhen\tsetting\ta\tproperty\ton\ta\tmodel\trecord, you\tcan\talso\tassign\tother\tmodel\trecords\tto\tthe\trecord\tproperty\tin\torder\tto\tcreate\ta relationship\tbetween\ttwo\tmodel\trecords\tfor\tproperties\tthat\twere\tdefined\twith\thasMany or\tbelongsTo.","Computed\tProperties Computed\tproperties\tare\ta\thuge\tpart\tof\tmanaging\tmodel\tproperties\tfor\tyour\ttemplates\tand components.\tEmber.computed\tis\ta\tmethod\tthat\ttakes\tthe\tvalues\tof\tscoped\tproperties and\treturns\ta\tvalue\twhen\tthe\tmethod\tends.\tInvoking\tthe\tfollowing,\tfor\texample,\twould give\tyou\ta\tcomputed\tproperty\twith\tthe\tobject\u2019s\tfirst_name\tproperty\tchanged\tto lowercase: Ember.computed('first_name',\tfunction(){ \t\treturn\tthis.get('first_name').toLowerCase(); }); In\tthis\texample,\tEmber.computed\tis\tacting\tas\tan\tevent\tlistener\tfor\tchanges\tto first_name.\tYou\tdo\tnot\thave\tto\tchange\tthe\tset\tmethod\tto\ttrigger\tan\tevent,\tyou\tdo\tnot have\tto\tadd\tan\tevent\tlistener,\tand\tyou\tdo\tnot\thave\tto\tchange\tthe\tfirst_name\tproperty. All\tyou\tdo\tis\tcreate\ta\tnew\tproperty\tthat\treturns\tthe\tvalue\tyou\twant. The\tuse\tof\tcomputed\tproperties\tis\tfairly\tglobal\tin\tEmber.\tYou\twill\talso\tbe\tcreating computed\tproperties\tfor\tcomponents\tin\tChapter\t25.\tA\tcomputed\tproperty\tis\tused\teither\tas a\tdecorator\tfor\ta\tview\tor\tcomponent,\tlike\tthe\texample\tabove,\tor\tto\tretrieve\tspecific\tdata embedded\tdeep\tin\tthe\tmodel\tobject. \u201cDecorating\u201d\tdata\tmeans\tformatting\tit\ta\tcertain\tway\t\u2013\tsuch\tas\tmaking\ta\tstring\tlowercase. Data\tfrom\tan\tAPI\tis\tnot\talways\tformatted\tthe\tway\tyou\twant\tit.\tDecorators\tare\tfunctions that\tinput\targuments\tand\toutput\tobjects\tor\tarrays\tto\tbe\tused\tspecifically\tfor\tthe\tview\tlayer of\tan\tapplication.\tThe\tformatting\tor\tconstruction\tof\tnew\tdecorated\tdata\tgenerally\tdoes\tnot return\tto\tthe\tdatabase.\tFor\tthis\treason,\tdecorators\tare\tgenerally\tadded\tto\ta\tcontroller, unless\tevery\tpage\tis\trendering\tdata\tfrom\ta\tmodel\tthat\tis\tnot\tformatted\tin\tthe\tdatabase. Add\ta\tcomputed\tproperty\tfor\ta\tfullName\tto\tyour\twitness\tmodel\tin app\/models\/witness.js. import\tDS\tfrom\t'ember-data'; export\tdefault\tDS.Model.extend({ \t\tfName:\tDS.attr('string'), \t\tlName:\tDS.attr('string'), \t\temail:\tDS.attr('string'), \t\tsightings:\tDS.hasMany('sighting'), \t\tfullName:\t\tEmber.computed('fName',\t'lName',\tfunction(){ \t\t\t\treturn\tthis.get('fName')\t+\t'\t'\t+\tthis.get('lName'); \t\t}) }); (If\tthe\tautorestarting\tserver\tcomplains,\tbe\tsure\tyou\tadded\tthe\ttrailing\tcomma\tto\tthe sightings\tproperty\tdeclaration.\tIt\tis\tan\teasy\tone\tto\tmiss.) The\tproperty\tyou\tadded\tto\tthe\twitness\tmodel\tis\ta\tfunction\tthat\twill\tbe\tinvoked\tevery time\tfName\tand\tlName\tchange.\tComputed\tproperties\tcan\ttake\tany\tnumber\tof\targuments as\tobserved\tproperties\twith\tthe\tfinal\targument\tbeing\tthe\tfunction\tto\treturn\ta\tvalue.\tEach argument\tthat\tis\ta\tproperty\twill\ttrigger\tthe\tfunction\targument\tto\tbe\tinvoked. Open\tapp\/routes\/witnesses.js\tand\tcreate\ta\tnew\twitness\trecord\tto\ttest\tthe computed\tproperty\tof\tthe\twitness\tmodel: import\tEmber\tfrom\t'ember'; export\tdefault\tEmber.Route.extend({ \t\tmodel()\t{","let\twitnessRecord\t=\tthis.store.createRecord('witness',\t{ \t\t\t\t\t\tfName:\t\\\"Todd\\\", \t\t\t\t\t\tlName:\t\\\"Gandee\\\", \t\t\t\t\t\temail:\t\\\"[email protected]\\\" \t\t\t\t}); \t\t\t\treturn\t[witnessRecord]; \t\t} }); To\tget\tyour\twitness\tdata\tonscreen,\tedit\tapp\/templates\/witnesses.hbs\tto\tuse\tthe same\t{{#each}}\titerator\tused\tin\tapp\/templates\/sightings\/index.hbs: {{outlet}} <h1>Witnesses<\/h1> <div\tclass=\\\"row\\\"> \t\t{{#each\tmodel\tas\t|witness|}} \t\t\t\t<div\tclass=\\\"col-xs-12\tcol-sm-6\tcol-md-4\\\"> \t\t\t\t\t\t<div\tclass=\\\"well\\\"> \t\t\t\t\t\t\t\t<div\tclass=\\\"thumbnail\\\"> \t\t\t\t\t\t\t\t\t\t<div\tclass=\\\"caption\\\"> \t\t\t\t\t\t\t\t\t\t\t\t<h3>{{witness.fullName}}<\/h3> \t\t\t\t\t\t\t\t\t\t\t\t<div\tclass=\\\"panel\tpanel-danger\\\"> \t\t\t\t\t\t\t\t\t\t\t\t\t\t<div\tclass=\\\"panel-heading\\\">Sightings<\/div> \t\t\t\t\t\t\t\t\t\t\t\t<\/div> \t\t\t\t\t\t\t\t\t\t<\/div> \t\t\t\t\t\t\t\t<\/div> \t\t\t\t\t\t<\/div> \t\t\t\t<\/div> \t\t{{\/each}} <\/div> Navigate\tto\thttp:\/\/localhost:4200\/witnesses\tand\tcheck\tout\tthe\tresults\t(Figure\t21.3). Figure\t21.3\t\tWitnesses\tlisting In\tthis\tview,\tyou\tadded\tthe\tlisting\tof\twitnesses\t(which,\tat\tthe\tmoment,\tincludes\tjust\tone witness)\tand\tused\ta\tcomputed\tproperty\tto\tdisplay\tthe\twitness\u2019s\tfullName\tproperty.\tThis property\twas\tgenerated\tfrom\tthe\tvalues\tyou\tadded\twhen\tcreating\tthe\twitness\trecord.\tWith witnessRecord.set,\tyou\tcan\tsupply\ta\tdifferent\tfirst\tname\tor\tlast\tname\tbefore\tthe model\tcallback\treturns\tthe\trecord\tto\tsee\tthe\tproperty\tchange. You\thave\tcome\tpretty\tfar\tin\tthese\tfirst\tfew\tchapters\ton\tEmber!\tYou\tcan\tnow\tdefine\tyour","data\tmodels,\tcreate\trecords,\tcreate\tcomputed\tproperties,\tand\tget\tand\tset\tproperty\tvalues. In\ta\tmoment,\tyou\twill\tread\tabout\tretrieving,\tupdating,\tand\tdestroying\trecords\tusing\tan API. In\tthe\tnext\tchapter,\tyou\twill\tlearn\tabout\tusing\tadapters,\tserializers,\tand\ttransforms\tto\tlink your\tdata\tmodels\twith\tdata\ton\tthe\tweb.","For\tthe\tMore\tCurious:\tRetrieving\tData As\tmentioned\tabove,\tthe\tdata\tstore\tmanages\tmodel\tdata\tand\tknows\thow\tto\tretrieve\tit.\tIn the\tprevious\tchapter,\tyou\treturned\tdata\tin\tthe\tSightingRoute\tmodel\tcallback\twith\tan array.\tIn\tChapter\t22,\tyou\twill\tretrieve\tmodel\tdata\tin\tthis\troute\tand\treturn\tdata\tas\ta Promise\tusing\tthis.store.findAll\tand\tother\tdata\tretrieval\tmethods. Below\tis\ta\ttable\tof\tmethods\tEmber\tData\u2019s\tstore\tobject\thas\tat\tits\tdisposal\tfor\tretrieving\tdata from\tan\tAPI,\tstoring\tit\tin\tmemory,\tand\treturning\tit\tto\tthe\trequester. Request\ttype Retrieve\tall\trecords Retrieve\ta\tsingle\trecord find\tpersisted\tand\tlocal\trecords findAll findRecord find\tlocal\trecords\tonly peekAll peekRecord find\tfiltered\trecords query queryRecord Retrieval\tmethods\tcome\tin\tseveral\tflavors:\tpersisted\tand\tlocal,\tlocal\tonly,\tand\tfiltered persisted\tand\tlocal.\tMost\tuse\tcases\tcall\tfor\tfindAll\tand\tfindRecord.\tThe\targuments for\teach\tmatch\tclosely\tto\tthe\tAPI\tendpoints\tthat\tEmber\tData\twill\tcreate\tto\trequest\tthe\tdata from\tthe\tAPI. For\tfindAll,\tthe\tonly\trequired\targument\tis\tthe\tmodel\tname.\tFor\texample,\ta\trequest\tfor all\tthe\twitnesses\twould\tbe\tfindAll(\u2018witness\u2019).\tNotice\tthe\tsingular\tname? Remember,\tthis\targument\tis\tthe\tmodel\tname.\tEmber\tData\twill\tmake\tsure\tthe\trequest\thas\ta plural\tname\twhen\tit\tbuilds\tthe\tAjax\tURL\t\/witnesses\/. For\tfindRecord,\tan\tadditional\targument\tis\tneeded\tto\tindicate\ta\tspecific\trecord.\tThis argument\tis\tthe\tidentifier,\tusually\tthe\tid\tof\tthe\trecord,\tsuch\tas this.store.findRecord('witness',\t5).\tWhen\tcalled,\tfindRecord('witness',\t5)\twill create\ta\trequest\tfor\tdata\tat\t\/witnesses\/5. For\tpeekAll\tand\tpeekRecord,\tthe\tsame\targuments\tare\tneeded\tto\tretrieve\tdata. Invoking\tthese\tmethods\twill\treturn\tthe\tdata\timmediately,\tnot\tas\ta\tPromise. Querying\tyour\tAPI\tfor\tdata\tis\tanother\tway\tto\tform\trequests.\tIf\tyour\tAPI\tsupports\tquery parameters,\tor\tparams,\tfor\tindividual\tendpoints,\tEmber\tData\u2019s\tquery\tand\tqueryRecord are\tgreat\toptions.\tLike\tthe\tother\tstore\tmethods,\tthese\tmethods\ttake\tthe\tmodel\tname\tas their\tfirst\targument.\tThe\tlast\targument\tis\tthe\tquery\tobject,\twhose\tkey\/value\tpairs\tare converted\tinto\tquery\tstring\tvalues.\tFor\tquery,\tthe\trequest\twill\tfind\tall\trecords\tfiltered\tby keys\tand\tvalues.\tqueryRecord\tis\tused\twhen\tyou\tknow\tthe\trequest\twill\treturn\ta\tsingle record. For\texample,\tcalling this.store.query('user',{fName:\t\\\"todd\\\"}) would\tproduce\tthis\trequest:\t\/users\/?f_name=todd.\tAlternatively, this.store.queryRecord('user',\t{email:\t'[email protected]'})","would\tproduce\tthis\trequest:\t\/[email protected]. All\tof\tthese\tstore\tmethods\tleverage\tadapters,\twhich\tyou\twill\tread\tabout\tin\tthe\tnext chapter.","For\tthe\tMore\tCurious:\tSaving\tand\tDestroying\tData Updating\t(i.e.,\tsaving)\tand\tdestroying\trecords\tare\tthe\tnext\tlogical\tsteps\tafter\tcreating\tand retrieving.\t(There\tis\ta\tmnemonic\tfor\tthis:\tCRUD,\twhich\tstands\tfor\t\u201ccreate,\tread,\tupdate, destroy.\u201d)\tThe\tmethods\tsave\tand\tdestroyRecord\tare\tavailable\tdirectly\ton\tmodel instances.\tThese\tmethods\ttrigger\trequests\tthrough\tthe\tadapter\tto\tupdate\tthe\tdata\tstore. They\treturn\tPromise\tobjects,\tso\tyou\tcan\tchain\tcallbacks\twith\t.then\tto\tdo\tsomething\twith the\treturned\tdata. As\tyou\tsaw\tin\tthis\tchapter,\tset\tis\tthe\tway\tto\tchange\tproperty\tvalues\ton\tyour\tmodel records.\tIf\tyou\tchange\ta\tvalue\tlocally\tin\tthe\tapp,\tthe\tdata\twill\tbe\tdifferent\tfrom\tthe persisted\tsource.\tTherefore,\tafter\tyou\tchange\ta\tvalue\twith\tset,\tyou\tshould\tsave\tyour data.\tYou\tcan\tdo\tthis\twith\tmodelRecord.save.\tSaving\ta\tmodel\twill\ttell\tthe\tstore\tto make\ta\trequest\tto\tthe\tAPI\twith\ta\tPOST\tor\tPUT,\tdepending\ton\tthe\tstate\tof\tthe\trecord. Although\tthis\twas\tnot\tmentioned\tabove,\twhen\tretrieving\tdata\tthe\tstore\twill\tmake\tget requests\tfor\tdata.\tWhen\tsaving\tdata,\trequests\tare\tsent\twith\ta\ttype\tPOST\twhen\tthe\tdata\tdoes not\texist\tin\tthe\tpersisted\tsource\tand\tPUT\twhen\tthe\tdata\tdoes\texist\tand\tis\tbeing\tupdated. When\tyou\tcall\tcreateRecord,\tas\tnoted\tearlier,\tyou\tare\tnot\tsaving\tthe\tdata\tto\tthe database.\tYou\tare\tmerely\tmaking\tan\tin-memory\tobject.\tCalling\tsave\tis\tfor\tcreation\tand updating. Destroying\trecords\tis\tthe\tlast\tstep.\tLike\tthe\tmethods\tfor\tretrieving\tand\tupdating\trecords, modelRecord.destroyRecord\tuses\ta\trequest\tmethod\t\u2013\tin\tthis\tcase\tDELETE\t\u2013\tto remove\ta\trecord\tfrom\tthe\tpersisted\tsource.\tThe\tstore\tthen\tremoves\tthe\trecord\tfrom memory.\tLike\tsave,\tdestroyRecord\tis\tactually\ttwo\tfunction\tcalls\tin\tone, deleteRecord\tand\tsave,\tbecause\tdeleteRecord\tonly\tdeletes\tthe\trecord\tlocally. destroyRecord\tis\tmore\tcommonly\tused\tbecause\tit\tcombines\tthese\tsteps\tinto\ta\tsingle action.","Bronze\tChallenge:\tChanging\tthe\tComputed\tProperty The\tfullName\tcomputed\tproperty\tcurrently\tuses\tfName\tand\tlName.\tChange\tthe\tbound properties\tto\tbe\temail\tand\tfName\tto\tcreate\ta\tfullName\tthat\tdisplays\tas\tTodd\t- [email protected].","Silver\tChallenge:\tFlagging\tNew\tSightings Add\ta\tnew\tBoolean\tattribute\tto\tsightings,\tisNew.\tGive\tthis\tattribute\ta\tdefaultValue of\tfalse.\tAdd\tthis\tproperty\tto\tone\tof\tyour\tcreated\trecords\tand\tset\tit\tto\ttrue.\tNavigate\tto the\tsightings\troute\tand\tuse\tthe\tEmber\tInspector\tin\tChrome\tto\treview\tthe\tdata\tfor\tthe\troute. Only\tone\tsighting\tinstance\tshould\thave\tan\tisNew\tproperty\tset\tto\ttrue.","Gold\tChallenge:\tAdding\tTitles The\twitnesses\tneed\ta\tproper\ttitle,\tsuch\tas\tMr.\tGandee.\tAdd\ta\ttitle\tproperty\tto\tthe model,\tthen\tset\tit\tfor\teach\twitness\trecord\texcept\tone.\tAdd\tan\tobject\targument\twith\ta defaultValue.\tPick\tan\tinteresting\tdefault\ttitle\tfor\tinstances\twithout\ta\tspecified\ttitle. After\tcreating\tthe\ttitle\tproperty\tand\tsupplying\tyour\tcreated\trecord\twith\tsome\tnew\tdata, add\ta\tcomputed\tproperty\tto\tdisplay\tthe\ttitleName. Have\tfun\twith\tyour\tdefault\ttitle.\tWikipedia\thas\ta\tnice\tlist\tof\ttitles (en.wikipedia.org\/\u200bwiki\/T\u200b itle).\t\u201cMahatma\tGandee\u201d\tsounds\tpretty\tgood\u2026","22\t Data\t\u2013\tAdapters,\tSerializers,\tand Transforms Applications\trequire\tdata\tgoing\tin\tand\tout\tof\tthe\tinterface.\tConnecting\tto\ta\tdata\tsource\tis an\timportant\taspect\tof\tdeveloping\tan\tapplication.\tOtherwise,\tyou\thave\ta\tcomplex\tsystem of\tforms,\tevents,\tand\tlistings\twith\tno\tdata\tto\tdisplay. In\tthis\tchapter,\tyou\twill\tlearn\tsome\tof\tthe\tbasics\tof\twiring\tup\ta\tdata\tsource\tin\tEmber.\tYou will\tuse\tan\tAPI\tcreated\tfor\tthis\tbook\tand\tcreate\tan\tadapter\tfor\tyour\tapplication. This\tchapter\tis\ta\tlittle\tdifferent\tfrom\tthe\tother\tchapters\tin\tthe\tbook.\tIt\thas\tmore information\tand\tless\tcoding.\tHowever,\tthis\tchapter\twill\tgive\tyou\tan\timportant\treal-world view\tof\tapplication\tdevelopment\twith\ta\tserver\tand\tdatabase\tthat\tmay\tnot\tbe\tunder\tyour control.\tIn\tthe\tnext\tchapter\tyou\twill\treturn\tto\tyour\tregularly\tscheduled\tcoding. As\tmentioned\tin\tChapter\t21,\tadapters\tare\tthe\ttranslators\tof\tyour\tapplication.\tWhen\tyou communicate\twith\ta\tdata\tsource,\tyour\tapplication\twill\tneed\tto\trequest\tand\tsend\tdata\tin\ta variety\tof\tways.\tEmber\tData\tcomes\twith\tbuilt-in\tadapter\tobjects\tto\thandle\tsome\tof\tthe\tmost common\tdata\tscenarios:\tJSONAPI\tand\tgeneric\t\u201cRESTful\u201d\tAPIs. You\tare\tgoing\tto\tuse\tthe\tJSONAPIAdapter\tobject\tto\tconnect\tto\ta\tdata\tsource\tand\treturn JSONAPI-formatted\tdata.\tThe\tRESTAdapter\tobject\tis\tset\tof\tmethods\tto\twork\twith\tdata formatted\tfrom\tAPIs\tgenerated\tfrom\tRails\tand\tActiveRecord\tplug-ins\tfor\tRails. The\tJSONAPI\tspec\twas\tcreated\tto\tgive\tAPI\tconsumers\ta\tpredictable\tand\tscalable\tpattern for\tsending\tand\treceiving\tdata\tfrom\tservers.\tWhile\tthere\tare\tnumerous\tserver\tlanguages\t\u2013 and\tAPI\tobject\tpattern\tconventions\tfor\teach\t\u2013\tJSONAPI\tset\tout\tto\tbe\ta\tpattern\tany\tlanguage could\tuse\tso\tthat\tfront-end\tapplications\twere\tnot\taffected\tby\ta\tchange\tin\tserver technology.\tYou\tcan\tfind\tout\tmore\tabout\tJSONAPI\tat\tits\twebsite,\tjsonapi.org. In\tthis\tchapter,\tyou\twill\talso\tlearn\tabout\tsecurity\tissues\tas\twell\tas\tserializers,\twhich\tare the\ttranslation\tlayer\tin\tthe\tadapter\tflow.\tFinally,\tyou\twill\tbe\tintroduced\tto\ttransforms,\tthe tool\tto\tcoerce\tyour\tdata\tinto\tthe\ttypes\tyour\tmodels\texpect.\tAdapters,\tserializers,\tand transforms\twork\ttogether\tas\tshown\tin\tFigure\t22.1.","Figure\t22.1\t\tAdapter,\tserializers,\tand\ttransforms At\tthe\tend\tof\tthis\tchapter,\tTracker\twill\tlook\tlike\tFigure\t22.2.","Figure\t22.2\t\tTracker\tat\tthe\tend\tof\tthis\tchapter Adapters The\tEmber\tteam\thas\tbuilt\ttheir\tframework\twith\tspecific\tconventions\tin\tmind.\tAdapters\tare a\tlarge\tpart\tof\tthose\tconventions.\tThe\tJSONAPIAdapter\twill\tcommunicate\twith\ta\tREST\tAPI for\tall\trequests\toriginating\tfrom\tthe\tstore.\tEach\trequest\twill\tadd\tthe\tmodel\tname\tand appropriate\tattribute\tdata\tto\ta\trelative\tpath\tof\tthe\tdomain. To\tgenerate\ta\tspecific\tURL\tfor\tAjax\trequests,\tthe\tadapter\tneeds\tthe\tproperties\thost\tand namespace.\tThe\tadapter\tmakes\tAjax\trequests\tand\texpects\tthe\tJSON\tresponses\tto\tbe formed\twith\ta\tparticular\tstructure.\tFor\texample,\tthe\tJSONAPIAdapter\twould\texpect\ta response\tfor\twitnesses\ton\ta\tGET\trequest\tto\tlook\tlike\tthis: { \\\"links\\\":\t{ \t\t\\\"self\\\":\t\\\"http:\/\/bnr-tracker-api.herokuapp.com\/api\/witnesses\\\" }, \\\"data\\\":\t[ \t\t{ \t\t\t\t\\\"id\\\":\t\\\"5556013e89ad2a030066f6e0\\\", \t\t\t\t\\\"type\\\":\t\\\"witnesses\\\", \t\t\t\t\\\"attributes\\\":\t{ \t\t\t\t\t\t\\\"lname\\\":\t\\\"Gandee\\\", \t\t\t\t\t\t\\\"fname\\\":\t\\\"Todd\\\" \t\t\t\t}, \t\t\t\t\\\"links\\\":\t{ \t\t\t\t\t\t\\\"self\\\":\t\\\"\/api\/witnesses\/5556013e89ad2a030066f6e0\\\" \t\t\t\t}, \t\t\t\t\\\"relationships\\\":\t{ \t\t\t\t\t\t\\\"sightings\\\":\t{ \t\t\t\t\t\t\\\"data\\\":\t[], \t\t\t\t\t\t\\\"links\\\":\t{ \t\t\t\t\t\t\t\t\\\"self\\\": \t\t\t\t\t\t\t\t\\\"\/api\/witnesses\/5556013e89ad2a030066f6e0\/relationships\/sightings\\\" \t\t\t\t\t\t\t\t} \t\t\t\t\t\t} \t\t\t\t}","} ] } With\teach\tresponse,\tfor\texample,\tthe\ttype\tof\teach\tobject\tis\texpected\tto\tbe\tthe\tmodel name\trequested,\tin\torder\tto\tresolve\tall\trecords\tfor\tthat\tmodel\ttype.\tAlso,\tan\tid\tis\texpected to\tbe\tthe\tprimary\tkey\tfor\teach\tindividual\tmodel\tobject. Begin\tby\tgenerating\tan\tapplication\tadapter: ember\tg\tadapter\tapplication Your\tapplication\twill\tbe\tmaking\trequests\tto\tthe\tBig\tNerd\tRanch\tTracker\tAPI.\tAs\twe\thave said,\tthe\tJSONAPIAdapter\tproperty\tvalues\trequire\ta\thost\tURL\tand\ta\tnamespace,\twhich\tis added\tto\tthe\tend\tof\tthe\thost\twhen\tmaking\tan\tAjax\trequest\tfor\tmodel\tdata.\tOpen app\/adapters\/application.js\tand\tdeclare\tthe\thost\tand\tnamespace. import\tJSONAPIAdapter\tfrom\t'ember-data\/adapters\/json-api'; export\tdefault\tDS.JSONAPIAdapter.extend({ \t\thost:\t'https:\/\/bnr-tracker-api.herokuapp.com', \t\tnamespace:\t'api' }); Like\tother\tEmber\tclasses,\tnaming\tpatterns\talso\tapply\tto\tadapters\t\u2013\tand\tadapters\tcan\tbe created\tto\tcustomize\tany\tmodel\u2019s\tAPI\tneeds.\tIrregularities\tin\tthe\tdata\tstructure\ton\tthe server\tcan\tbe\tcontained\tin\ta\tsingle\tadapter\trather\tthan\tforcing\tall\tmodels\tto\tconform\tto edge\tcases. What\tyou\thave\tadded\tto\tapp\/adapters\/application.js\tis\ta\tglobal\tsetting\tfor\tall data\trequests.\tThis\tis\tall\tyou\tneed\tfor\tTracker,\tbecause\tthe\tAPI\tis\tsending\tall\tJSON responses\tfrom\tthe\tsame\thost\tand\tnamespace.\tBut\tif\tthe\twitness\tmodel,\tfor\texample, needed\ta\tdifferent\tnamespace\tor\thost,\tyou\tcould\tcreate\tan app\/adapters\/witness.js\tfile\tand\tconfigure\tthat\tparticular\tadapter\tfor\twitness requests. Next,\tyou\tneed\tto\tretrieve\tyour\tdata\tfrom\tthe\tAPI\tvia\tthe\tstore.\tThe\tAPI\thas\tcryptids\tand witnesses\tready\tto\tgo. Open\tapp\/routes\/witnesses.js.\tDelete\tthe\tdummy\tdata\tand\treplace\tit\twith\ta\tcall to\tthe\tretrieval\tmethod\tyou\tlearned\tin\tChapter\t21. ... \t\tmodel(){ \t\t\t\tlet\twitnessRecord\t=\tthis.store.createRecord('witness',\t{ \t\t\t\t\t\tfname:\t\\\"Todd\\\", \t\t\t\t\t\tlname:\t\\\"Gandee\\\", \t\t\t\t\t\temail:\t\\\"[email protected]\\\" \t\t\t\t}); \t\t\t\treturn\t[witnessRecord]; \t\t\t\treturn\tthis.store.findAll('witness'); \t\t} }); Now,\trestart\tyour\tapplication\tfrom\tthe\tterminal\twith\tember\tserver\tand\tpoint\tyour browser\tto\thttp:\/\/\u200blocalhost:4200\/\u200bwitnesses\t(Figure\t22.3).","Figure\t22.3\t\tWitnesses\tlisting Like\tmost\tlifecycle\tflows\tin\tEmber,\tadapters\thave\ta\tnumber\tof\tmethods\tthat\tget\tyour\tdata from\tthe\tAPI\tto\tthe\tstore\tand\tto\tyour\troutes,\tcontrollers,\tand\ttemplates.\tThe\tpurpose\tof\ta specific\tadapter,\tlike\tthe\tJSONAPIAdapter,\tis\tto\thandle\ta\tbroad\tpattern\tand\tdeal\twith\tan expected\tinput\/output\tfor\teach\tmodel.\tOur\texample\tuses\ta\tNode.js\tserver\tbacked\tby\ta MongoDB\tdatabase\tusing\ta\tjson-api\tnode\tmodule\tto\thave\tAPI\tendpoints\tthat\twork\twith JSONAPI-spec\u2019d\tdata. Working\twith\tan\tAPI\tthat\thas\ta\tspecific\tpattern\tto\tfollow\t(with\tminor\tedge\tcases\tof irregularity)\tmakes\tfor\ta\thappy\tprogrammer.\tYou\talso\tmight\tneed\tto\thandle\tsome\textra data\tin\tyour\trequest,\tlike\tauthentication\tor\trequest\theaders.\tAdapters\tcan\tbe\tcustomized\tto deal\twith\tany\tscenario. Ember\tpreviously\thad\tbuilt-in\tadapters\tfor\tother\tdata\tsource\tscenarios,\tlike\tlocalStorage and\tfixture\tdata.\tThese\tadapters,\tand\tothers,\tare\tnow\taddons.\tThey\tcan\tbe\tadded\tvia\tEmber CLI\twhen\tyour\tmodel\tdata\tneeds\tto\tsync\twith\tother\tsources. If\tyou\tfind\tthat\tyou\thave\tto\thack\ttogether\tan\tadapter,\tdive\tinto\tthe\tdocumentation\tto\tfind your\tanswers.\tSome\tmethods\tand\tproperties\tof\tnote\tare:\tajaxOptions,\tajaxError, handleResponse,\tand\theaders. At\tthis\tpoint,\tyour\tapp\tis\tmaking\trequests\tto\ta\tserver\tto\treceive\ta\tcollection\tof\twitnesses. Before\tyou\tmove\ton\tto\treading\tabout\tthe\tcontent\tsecurity\tpolicy,\tserializers,\tand transforms,\tyou\twill\tuse\tthe\tsame\tmethod,\tthis.store.findAll,\tto\tretrieve\tall\tthe cryptid\trecords\tfrom\tthe\tAPI.\tBecause\tthere\tis\tno\tview\ttemplate\tfor\tcryptids,\tyou will\texamine\tthe\treturned\trecords\twith\tthe\tEmber\tInspector.","Add\tthe\tmethod\tcall\tto\tapp\/routes\/cryptids.js: import\tEmber\tfrom\t'ember'; export\tdefault\tEmber.Route.extend({ \t\tmodel(){ \t\t\t\treturn\tthis.store.findAll('cryptid'); \t\t} }); Now\tthat\tyou\thave\tthe\tdata\treturning,\treload\tthe\tapp\tin\tthe\tbrowser\tand\tnavigate\tto http:\/\/\u200blocalhost:4200\/\u200bcryptids.\tUse\tthe\tEmber\tInspector\tto\texamine\tthe returning\tdata:\tIn\tthe\tDevTools,\tselect\tthe\tEmber\ttab\tand\tclick\tData,\tthen\tcryptid(4) (Figure\t22.4). Figure\t22.4\t\tCryptid\tdata Up\tto\tthis\tpoint,\tthere\thas\tnot\tbeen\ta\tneed\tto\tuse\tthe\tEmber\tInspector\tin\tdepth.\tIt\tis\tused\there to\tshow\tyou\thow\tto\texamine\tin-memory\tdata.\tEmber\tretrieves\tthe\tdata\tfrom\tthe\tAPI\tand populates\tthe\tstore\twith\tthe\tcorrect\tmodel\tdata.\tWhen\tdebugging\tissues\twith\tmodel\tdata, tracing\tthe\tpath\tof\tthe\trequest\tcan\tbe\ttedious.\tThe\tEmber\tInspector\tshould\tbe\tthe\tfirst\tplace to\tlook\twhen\tyou\thave\tthis\tproblem.","Content\tSecurity\tPolicy Ember\tmakes\tuse\tof\ta\tnew\tsecurity\tlayer\tin\tJavaScript\tfor\tdetecting\tcross-origin\trequests before\tthey\thit\tyour\tserver.\tThe\tworking\tstandard\tis\tcalled\tContent\tSecurity\tPolicy. Ember\tCLI\thas\ta\tcontentSecurityPolicy\tobject\tto\tadd\tthe\tappropriate\tinformation.\tThe defaults\tare\tfairly\tstrict\twhen\tit\tcomes\tto\trequesting\tdata,\tscripts,\timages,\tstyles,\tand\tother file\ttypes\toutside\tof\tyour\tapp\u2019s\tdomain. There\tis\tan\taddon\tto\tset\tsome\tdefaults\tand\tintegrate\tthe\tsecurity\tpolicy\tinto\tyour\tapp: ember-cli-content-security-policy.\tYou\tdo\tnot\tneed\tit\tfor\tTracker,\tbut\tit\tis\tgood\tto\tknow\tabout. This\taddon\tmakes\tit\teasier\tto\tadd\tenvironment\tvariables\tto\tset\tthe\tsecurity\tpolicy.\tThe security\tpolicy\tobject\twill\twork\twith\tthe\tbrowser\tspecification\tcontent-security- policy.\tThis\tbrowser\tspec,\tavailable\tin\tsome\tnewer\tbrowsers,\tis\ta\tstandard\tintroduced\tto prevent\tcross-site\tscripting\tand\tother\tcode\tinjection\tattacks\tresulting\tfrom\texecution\tof malicious\tcontent\tin\tthe\tapplication. Here\tis\tan\texample\tof\tthe\tcontentSecurityPolicy\tobject: module.exports\t=\tfunction(environment)\t{ \t\t... \t\t\/\/\tconfig\/environment.js \t\tENV.contentSecurityPolicy\t=\t{ \t\t\t\t'default-src':\t\\\"\\\", \t\t\t\t'script-src':\t\\\"\\\", \t\t\t\t'font-src':\t\\\"\\\", \t\t\t\t'connect-src':\t\\\"\\\", \t\t\t\t'img-src':\t\\\"\\\", \t\t\t\t'style-src':\t\\\"\\\", \t\t\t\t'media-src':\tnull \t\t} \t\t... } Each\tline\tof\tthe\tsecurity\tpolicy\tcreates\ta\twhitelist\t\u2013\ta\tset\tof\tsafe\tpaths\t\u2013\tfor\teach\ttype\tof request.\tdefault-src\tis\ta\tcatch-all\tsetting\tand\tis\toriginally\tset\tto\tnull\tto\tforce programmers\tto\twhitelist\tthe\tsettings\tthey\tneed.\tOther\tsettings,\tlike\tscript-src\tand connect-src,\tare\tfor\trequests\texternal\tto\tthe\tapplication\tdomain,\tlike\thttps:\/\/\u200bbnr- tracker-api.herokuapp.com. For\tmore\tinformation,\tsee\tthe\tMDN\tContent\tSecurity\tPolicy\tpage\tand\tthe\tGitHub repository\tfor\tthe\tEmber\tCLI\taddon.","Serializers When\tdata\tcomes\tin\tand\tgoes\tout,\tthe\tJSON\tstructure\tis\tserialized\tand\tdeserialized.\tThe adapter\tuses\tthe\tserializer\tto\tget\tthe\tdata\tin\tand\tout\tof\tthe\tstore\tto\tbuild\tand\tresolve request\/response\tdata. You\tcan\tcreate\ta\tserializer\twith\tember\tg\tserializer\t[application\tor\tmodel\tname], which\twill\tcreate\ta\tserializer\tfile\twith\tboilerplate\tcode,\tlike\tthis: import\tDS\tfrom\t'ember-data'; export\tdefault\tDS.JSONAPISerializer.extend({ }); The\tserializer\tis\tan\tobject\tassigned\tto\tthe\tserializer\tproperty\tin\tthe\tadapter.\tWithout\ta specific\tserializer\tfile\tin\tyour\tapplication,\tEmber\twill\tuse\ta\tdefault\tadapter\tand\tserializer, the\tJSONAPIAdapter\tand\tJSONAPISerializer. When\tyou\tinclude\ta\tnew\tserializer\tfor\tthe\tapplication,\tthat\tserializer\twill\tbe\tused\tas\tthe defaultSerializer\tfor\tthe\tcorresponding\tapp\/adapters\/application.js\tfile. Using\tthe\tmodel\u2019s\tname\tas\tthe\tcommand\toption\tin\tember\tg\tserializer\tallows\tyou customize\tserialization\tof\tdata\tfor\ta\tspecific\tmodel. As\twith\tthe\tJSONAPIAdapter,\tthe\tconfiguration\tshould\tonly\tbe\tchanged\tif\tyour\tAPI\tdoes not\tconform\tto\tthe\tJSONAPI\tspecification\tor\tif\tyou\thave\tstrange\tedge\tcases.\tIf,\tin\tyour\town project,\tyou\twere\tto\tneed\tchanges\tto\trequest\tor\tresponse\tdata,\there\tare\tsome\tmethods\tto investigate\tin\tthe\tEmber\tData\tdocumentation:\tkeyForAttribute, keyForRelationship,\tmodelNameFromPayloadKey,\tand\tserialize. keyForAttribute\tis\ta\tmethod\tto\ttransform\tattribute\tnames\tfrom\tthe\tmodel\tto\ta keyname\tsent\tin\tthe\trequest.\tThis\tmethod\texpects\tthree\targuments:\tkey,\ttypeClass, and\tmethod.\tFor\tthe\tJSONAPISerializer,\tthis\tmethod\treturns\tthe\tkey\tdasherized, meaning\tthat\tany\tunderscores\tor\tcamelcasing\tin\tthe\tkeyname\twill\tbe\tconverted\tto\tdashes. For\texample,\tif\ta\tmodel\thas\ta\tproperty\tfirst_name,\tthe\trequest\tobject\twill\thave\ta\tkey\tof first-name.\tIf\tyour\tAPI\texpects\tfirst_name,\tyou\twill\tneed\tto\tmake\ta\tchange\tto\tyour\tthe keyForAttribute\tmethod\tto\tresolve\tthe\tnaming\tissue. keyForRelationship\tfollows\tthe\tsame\tprocess,\tonly\tfor\trelationship\tkeys.\tIf\tyour model\tnames\tcontain\tunderscores\tyou\twill\tneed\tto\tedit\tthis\tmethod\tfor JSONAPISerializer\twhen\tyou\thave\tlinked\tmodels\twith\tbelongsTo\tor\thasMany relationships.\tSome\tAPIs\texpect\trelationships\tto\tadd\ta\tsuffix\tof\t_id\tor\t_ids\tfor\tthese relationships.\tThis\tis\tthe\tmethod\tto\tmake\tthat\tchange. The\tbnr-tracker-api\tyou\tare\tusing\tfor\tTracker\tuses\tthe\tappropriate\tJSONAPI\tkey\tnames with\tdashes,\tsuch\tas\tcryptid-type.\tYou\twill\tnot\tneed\tto\tadd\ta\tserializer\tin\tyour\tapp. However,\tfor\tillustration,\there\tis\tan\texample\tof\tusing\tthe\tEmber\tutility\tmethod Ember.String.underscore\tto\tchange\tthe\tattribute\tkeys\tof\tthe\tincoming\tand outgoing\tJSON\tdata. import\tEmber\tfrom\t'ember'; import\tDS\tfrom\t'ember-data'; var\tunderscore\t=\tEmber.String.underscore; export\tdefault\tDS.JSONAPISerializer.extend({ \t\tkeyForAttribute(attr)\t{","return\tunderscore(attr); \t\t}, \t\tkeyForRelationship(rawKey)\t{ \t\t\t\treturn\tunderscore(rawKey); \t\t} }); Ember\tprovides\ta\tnumber\tof\tstring\tmanipulation\tmethods\ton\tthe\tEmber.String\tobject. Ember.String.underscore\tconverts\ta\tstring\twith\tdashes\tor\tcamelcase\tto\tuse\tan underscore\tto\tseparate\twords. The\tserializer\tmethods\tare\tcalled\tduring\tthe\tlifecycle\tof\ta\trequest\tfor\tdata,\tsuch\tas this.store.findAll('witness').\tOne\tway\tto\texamine\tthe\tcallback\tflow\tof\ta\tdata request\twould\tbe\tto\tadd\ta\tdebugger\tstatement\tin\tthis\tcode\tto\tsee\twhat\tcomes\tin\tand\twhat goes\tout\tof\tthe\tmethod. Here\tis\tan\texample: import\tEmber\tfrom\t'ember'; import\tDS\tfrom\t'ember-data'; var\tunderscore\t=\tEmber.String.underscore; export\tdefault\tDS.JSONAPISerializer.extend({ \t\tkeyForAttribute(attr)\t{ \t\t\t\tlet\treturnValue\t=\tunderscore(rawKey); \t\t\t\tdebugger; \t\t\t\treturn\treturnValue; \t\t\t\treturn\tunderscore(rawKey); \t\t}, \t\tkeyForRelationship(rawKey)\t{ \t\t\t\treturn\tunderscore(rawKey); \t\t} }); When\tworking\twith\ta\tnew\tAPI,\tthis\tstyle\tof\tdebugging\tcomes\tin\thandy.\tYou\thave\taccess to\tthe\tEmber.String\tobject\tfor\twhen\tyou\tneed\tto\tmanipulate\tthe\tattr\targument\tto serialize\tyour\tkey\tnames.\tThankfully,\tEmber\tand\tthe\tEmber\tcommunity\tbuild\tadapters\tand serializers\tfor\tnumerous\tAPI\tpatterns.","Transforms Ember\tData\tgives\tyou\tthe\tability\tto\ttransform\tdata\tfrom\tyour\tAPI\tto\tfit\tthe\tneeds\tof\tyour application.\tYou\thave\talready\tseen\tin\tChapter\t21\tthat\tEmber\tData\thas\tbuilt-in\ttransforms\t\u2013 the\tmethods\tDS.attr(\u2018string\u2019),\tDS.attr(\u2018boolean\u2019), DS.attr(\u2018number\u2019),\tand\tDS.attr(\u2018date\u2019). When\tyou\tadd\ta\ttransform\tto\tyour\tapplication,\tyou\tcan\tcall\tDS.attr\twith\tyour\ttarget attribute\ttype.\tTransforms\tare\tlike\tJavaScript\tcoercion\t\u2013\tthey\ttake\ta\tvalue\tand\treturn\tthe value\tin\ta\tspecified\ttype. Here\tis\tan\texample\tof\ta\tbasic\tDS.attr(\u2018object\u2019)\ttransform: export\tdefault\tDS.Transform.extend({ \t\tdeserialize(value)\t{ \t\t\t\tif\t(!Ember.$.isPlainObject(value))\t{ \t\t\t\t\t\treturn\t{}; \t\t\t\t}\telse\t{ \t\t\t\t\t\treturn\tvalue; \t\t\t\t} \t\t}, \t\tserialize(value)\t{ \t\t\t\tif\t(!Ember.$.isPlainObject(value))\t{ \t\t\t\t\t\treturn\t{}; \t\t\t\t}\telse\t{ \t\t\t\t\t\treturn\tvalue; \t\t\t\t} \t\t} }); The\ttransform\thas\ttwo\tmethods:\tdeserialize\tand\tserialize.\tThe\tfirst, deserialize,\ttests\twhether\tthe\tincoming\tdata\tis\tan\tobject\tand\treturns\tit.\tOtherwise,\tit returns\tan\tempty\tobject.\tThe\tsecond,\tserialize,\treturns\tthe\toutgoing\tdata\tif\tit\tis\tan object,\tand\totherwise\treturns\tan\tempty\tobject.\tThe\ttransforms\tguarantee\tthat\tthe\tdata returning\tfrom\tthe\tAPI\tand\tgoing\tout\tto\tthe\tAPI\tare\tthe\ttype\tdefined\tin\tthe\tmodel.","For\tthe\tMore\tCurious:\tEmber\tCLI\tMirage One\tthe\tmost\tcommon\tblockers\tfor\tfront-end\tapplication\tdevelopment\tis\tthe\tAPI.\tA number\tof\tproblems\tcan\tcome\tup:\tthe\tAPI\tmight\tnot\thave\tbeen\tcreated,\tthe\tAPI\tmight\tbe in\tdevelopment,\tor\tthe\tAPI\tmight\tbe\tbehind\ta\tfirewall\twhile\tyou\tare\tdeveloping. The\teasiest\tsolution\tto\tan\tinaccessible\tAPI\tis\tusing\tstatic\ttext\tor\tfixture\tdata.\tBut\tthis solution\tmight\tintroduce\ta\tnumber\tof\tother\tissues,\tlike\tchanging\tthe\tapplication\tlogic\tto suit\tthe\tinaccessible\tdata,\tchanging\tthe\trequests\tto\tthis.store,\tor\tchanging\tthe\tadapter\tor serializer. Ember\tCLI\tMirage\tis\tan\taddon\tthat\twill\tproxy\tthe\trequests\tto\tspecific\tAPI\troutes.\tYou\tcan\tset up\tmodels\tto\testablish\trelationships,\tset\tup\tfactories\tto\tseed\tdata,\tcreate\tfixture\tdata\tfor specific\tresponses,\tand\tdefine\tCRUD\troutes\tto\tintercept\tspecific\trequests\tto\tthe\tAPI.\tOnce these\tare\tset\tup,\tyou\tcan\tdevelop\tas\tthough\tthe\tAPI\twere\tin\tplace. While\tMirage\tis\tenabled,\tall\trequests\twill\tbe\tdiverted\tto\tthe\tlocal\tMirage\tsetup. At\tthe\ttime\tof\tprinting,\tember-cli-mirage\tis\tin\tversion\t0.2.0-beta.8.\tYou\tare\tgoing\tto explore\tMirage\tin\tjust\ta\tmoment\tas\tpart\tof\ta\tchallenge.\tTo\tfind\tout\tmore\tabout\tEmber\tCLI Mirage,\tvisit\twww.ember-cli-mirage.com. In\tthe\tnext\tchapter,\tyou\twill\tbring\ttogether\tthe\tconcepts\tof\tthe\tlast\tthree\tchapters\tand create\ta\tworking\tapplication\tthat\troutes\trequests\tand\tdisplays\tdata.\tIn\tChapter\t24,\tyou\twill create,\tedit,\tand\tdelete\tcontent.\tYou\twill\tsee\tthe\tpower\tof\tEmber\tData,\tadapters,\tand serializers\twhen\tyou\tcan\tcall\tsave\tand\tdestroyRecord\ton\tmodels\tand\tyour\tdata\tis sent\tto\tthe\tAPI\twithout\tmuch\theartache.","Silver\tChallenge:\tContent\tSecurity Adding\tlayers\tof\tsecurity\tin\tan\tapplication\tis\talways\timportant.\tAs\tmentioned\tabove,\tthere is\ta\tnew\tbrowser\tAPI\tfor\tContent\tSecurity\tPolicy,\tand\tEmber\thas\tan\tenvironment\tobject\tto handle\tconfiguring\tyour\tapplication\u2019s\twhitelist\tpolicy.\tInstall\tthe\taddon\tand\tfollow\tthe console\terrors\tto\tmake\tsure\tyour\tapplication\u2019s\texternal\trequest\tendpoints\tare\tadded\tto\tthe policy.","Gold\tChallenge:\tMirage Ember\tCLI\tMirage\tis\ta\tgreat\taddition\tto\tyour\tdevelopment\tarsenal.\tIt\tallows\tyou\tto\tdevelop your\tapplication\u2019s\tAPI\tneeds\tbefore\tthe\tback-end\tteam\tfinishes\ttheir\tstack. Install\tember-cli-mirage\tfrom\tyour\tterminal\tto\tstart\tusing\tit: ember\tinstall\tember-cli-mirage Next,\tto\tturn\tMirage\ton\tand\toff,\tadd\tan\tenvironment\tvariable\tto config\/environment.js: if\t(environment\t===\t'development')\t{ \t\t\/\/\tENV.APP.LOG_RESOLVER\t=\ttrue; \t\t\/\/\tENV.APP.LOG_ACTIVE_GENERATION\t=\ttrue; \t\t\/\/\tENV.APP.LOG_TRANSITIONS\t=\ttrue; \t\t\/\/\tENV.APP.LOG_TRANSITIONS_INTERNAL\t=\ttrue; \t\t\/\/\tENV.APP.LOG_VIEW_LOOKUPS\t=\ttrue; \t\tENV['ember-cli-mirage']\t=\t{ \t\t\t\tenabled:\ttrue \t\t} } Finally,\tadd\tfake\tdata\tin\tthe\tform\tof\tfactories\tfor\tyour\twitness\tand\tcryptid\tendpoints. To\twork\twith\tyour\tTracker\tapp,\tyou\tcan\tretrieve\ta\tconfigured\tapp\/mirage\tdirectory from\tthe\tsupplied\texample\tassets\tin\tTracker\/Data_Chapter\/mirage-example. We\twill\tkeep\tthe\texample\tup\tto\tdate\tfor\tcurrent\treleases\tof\tthe\taddon.","23\t Views\tand\tTemplates The\tV\tin\tMVC\tis\tfor\tviews.\tIn\tTracker,\tthe\tviews\twill\tbe\ttemplates.\tTemplates\tare processed\twith\tJavaScript\tto\tcreate\tHTML\telements.\tThis\tallows\tyou\tto\tchange\tthe\tDOM without\tfiring\toff\ta\tnew\trequest\tto\tthe\tserver. In\tthis\tchapter,\tyou\twill\tbe\tcreating\ttemplate\tfiles\tand\tadding\tthe\tdata\tretrieved\tin\tthe route\tmodel\thook.\tThe\ttemplate\tlanguage\tand\thelper\tfunctions\tbuilt\tinto\tEmber\twill\tallow you\tto\tcreate\ttemplates\twith\tminimal\teffort\tbeyond\tregular\tHTML\tsyntax. By\tthe\tend\tof\tthis\tchapter,\tyou\twill\thave\tcreated\tlistings\tlike\tFigure\t23.1. Figure\t23.1\t\tSightings\tlisting Handlebars You\twill\tbe\tusing\tHandlebars,\ta\tpowerful\tlanguage\tfor\tcreating\tdynamic\ttemplates.\tIt\tis similar\tto\tserver-side\ttemplating\tlanguages\tlike\tPHP,\tJSP,\tASP,\tand\tERB.\tIt\tincludes\tHTML element\ttags\tand\tdelimiters\tto\tprocess\tdata\tobjects. In\tHandlebars,\tthe\tdelimiters\tare\tdouble\tcurly\tbraces,\tlike\t{{}}.\tInside\tthe\t\u201cdouble\tcurlies,\u201d you\tcan\trender\tstrings\tof\tdata\tand\texecute\tlimited\tlogic\tusing\thelper\tmethods.\tYou\thave","seen\ttwo\thelper\tmethods\tlike\tthis\talready:\t{{outlet}}\tand\t{{#each}}. Ember\u2019s\timplementation\tof\tHandlebars\trecently\ttransitioned\tto\ta\tnew\tmechanism,\tnicknamed \u201cHTMLBars.\u201d\tSome\tof\tthe\tdetails\tof\tthe\tlanguage\tin\tthis\tchapter\tare\tfrom\tHTMLBars, but\tare\tapplicable\tto\tolder\tversions\tof\tEmber\t(at\tleast\tback\tto\tversion\t1.13.x)\tas\twell.","Models In\tEmber,\ttemplates\tare\talways\tbacked\tby\tmodels.\tThis\tmeans\tthat\tan\tobject\t(or\tarray\tof objects)\twill\tbe\tpassed\tas\tan\targument\twhen\tthe\ttemplate\tis\trendered\tto\ta\tstring\tof\tHTML and\tappended\tto\tthe\tDOM. The\tmodel\tobject\tcan\thave\tproperties\twith\tstrings,\tarrays,\tor\tother\tobjects.\tWhen\twriting templates\twith\tEmber\tand\tHandlebars,\tyou\taccess\tthis\tobject\twith\tthe\tdouble\tcurlies. When\tyou\twant\tto\tdisplay\tthe\tvalue\tof\ta\tmodel\tproperty,\tyou\twrite\t{{model.name}}, where\tname\tis\ta\tproperty\ton\tthe\tmodel\tobject.\tThe\tdot\tsyntax\tshould\tfeel\tfamiliar,\tbut\tdo not\tbe\tfooled\tinto\tthinking\tany\tJavaScript\tcode\tcan\tgo\twithin\tthe\tcurlies.","Helpers Handlebars\ttemplates\tare\tstrings\tinterpolated\tby\tJavaScript\tfunctions.\tWhen\tthe\tfunction comes\tacross\tdouble\tcurlies,\tit\ttries\tto\tresolve\tthe\tinstance\tof\tthe\tdelimiter\twith\tan\tobject property\tor\tinvoke\ta\tnested\tfunction\tto\treturn\ta\tstring.\tThese\tnested\tfunctions\tare\tcalled helpers\tand\tare\tcreated\tin\tthe\tapplication.\tThere\tare\ta\tfew\thelpers\tbuilt\tinto\tthe\tHandlebars library,\tand\tEmber\tadds\tsome\tof\tits\town. Helpers\tcan\ttake\ttwo\tforms.\tThe\tfirst\tare\tinline\thelpers,\twhich\tuse\tthe\tsyntax\t{{[helper name]\t[arguments]}}.\tThe\targuments\tcan\tinclude\ta\thash\tof\toptions,\tsuch\tas: {{input\ttype=\\\"text\\\"\tvalue=firstName\tdisabled=entryNotAllowed\tsize=\\\"50\\\"}} More\tcomplex\thelpers\tuse\ta\tblock\tsyntax: {{#[helper\tname]\t[arguments]}} \t\t[block\tcontent] {{\/[helper\tname]}} For\texample,\tif\tyou\twanted\tto\tpresent\ta\tsign-in\tlink\tonly\tto\tusers\twho\twere\tnot\talready logged\tin,\tyou\tcould\tuse\tsomething\tlike\tthe\tblock\tbelow: {{#if\tnotSignedIn}} \t\t<a\thref=\\\"\/\\\">Sign\tIn<\/a> {{\/if}} For\tblock\thelpers,\tcontent\tcan\tbe\tpassed\tto\tthe\tblock\tto\taugment\tthe\toutput\twith\tdynamic segments.\tHandlebars\u2019\tbuilt-in\tconditionals,\tdescribed\tin\tthe\tnext\tsection,\tare\tblock\thelpers. You\tare\tgoing\tto\tuse\thelpers\tto\trender\tsections\tof\tyour\ttemplates\tfor\tsightings\tand cryptids\tas\twell\tas\tthe\tNavBar. Conditionals Conditional\tstatements\tlet\tyou\tintroduce\tbasic\tcontrol\tflow\tinto\tHandlebars\ttemplates.\tTheir syntax\tlooks\tlike\tthis: {{#if\targument}} \t\t[render\tblock\tcontent] {{else}} \t\t[render\tother\tcontent] {{\/if}} Or,\talternatively,\tlike\tthis: {{#unless\targument}} \t\t[render\tblock\tcontent] {{\/unless}} Conditional\tstatements\ttake\ta\tsingle\targument\tthat\tresolves\tto\ta\ttruthy\tor\tfalsy\tvalue. (Those\tare\tnot\ttypos.\tA\ttruthy\tvalue\tis\tone\tthat\tevaluates\tto\ttrue\tin\ta\tBoolean\tcontext.\tAll values\tare\ttruthy\texcept\tthose\tdefined\tas\tfalsy:\tthe\tvalues\tfalse,\t0,\t\u201d\u201d,\tnull,\tundefined,\tand NaN.) Time\tto\tget\tto\twork.\tOpen\tapp\/templates\/sightings\/index.hbs\tand\tadd\ta conditional\tstatement\tso\tthat\tsighting\tentries\tdisplay\teither\tthe\tlocation\tor,\tif\tthere\tis\tno location\tdata,\ta\tpolite\twarning\tabout\tthe\tmissing\tdata. <div\tclass=\\\"panel\tpanel-default\\\"> \t\t<ul\tclass=\\\"list-group\\\"> \t\t\t\t{{#each\tmodel\tas\t|sighting|}} \t\t\t\t\t\t<li\tclass=\\\"list-group-item\\\">","{{sighting.location}}\t-\t{{sighting.sightedAt}} \t\t\t\t\t\t<\/li> \t\t\t\t{{\/each}} \t\t<\/ul> <\/div> <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<div\tclass=\\\"caption\\\"> \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{{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<\/div> \t\t\t\t<\/div> \t\t{{\/each}} <\/div> You\tchanged\tthe\tDOM\tstructure\tof\tyour\tsightings\ttemplate\tto\tstart\toutputting\tsighting information\tin\tthe\tform\tof\tstyled\telements\tusing\tBootstrap\u2019s\twells\tstyle.\tThe\tuse\tof\t{{#if}} and\t{{else}}\tallows\tyou\tto\trender\tdifferent\tHTML\twhen\tthe\tlocation\tof\tthe\tsighting\thas not\tbeen\tadded. Now\tyou\tneed\tto\tchange\tthe\tdata\tsent\tfrom\tthe\troute\tto\tthe\ttemplate\tto\tsee\tthe\tresults\tof your\tnew\tconditional.\tIn\tyour\tsightings\troute\tmodel\tin\tapp\/routes\/sightings.js, set\tthe\tlocation\tproperty\tof\tone\tsighting\tto\tthe\tempty\tstring. ... \t\tmodel(){ \t\t\t\t... \t\t\t\tlet\trecord3\t=\tthis.store.createRecord('sighting',\t{ \t\t\t\t\t\tlocation:\t'Asilomar', \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} }); Start\tyour\tserver\tand\tpoint\tyour\tbrowser\tto\thttp:\/\/l\u200b ocalhost:4200\/\u200bsightings to\tsee\tthe\tnew\tlist\tof\tsightings\t(Figure\t23.2).","Figure\t23.2\t\tBogus\tsighting The\tconditional\tstatement\tevaluated\tthe\ttruthy\tvalue\tof\tthe\tempty\tstring\tfor\tthe\tlast sighting\tinstance.\tThus,\tthe\ttemplate\trendered\tthe\tblock\tcontent\twith\tthe\ttext\t\u201cBogus Sighting.\u201d Loops\twith\t{{#each}} You\thave\talready\tused\tthe\t{{#each}}\tblock\thelper\tin\tthe\tindex\ttemplate.\tThis\thelper renders\teach\tobject\tin\tthe\tarray\tas\tan\tinstance\tof\tthe\tcontent\tin\tthe\tblock.\tThe\targument for\t{{#each}}\tis\tthe\tarray\tas\tan\t|instance|\tcontained\tin\tthe\tblock\targument.\tThe\tblock will\tonly\trender\tif\tthe\targument\tpassed\tin\tto\t{{#each}}\tis\tan\tarray\twith\tat\tleast\tone element. Like\tthe\t{{#if}}\tblock\thelper,\tthis\thelper\tsupports\tan\t{{else}}\tblock\tthat\twill\trender when\tthe\tarray\targument\tis\tempty. Use\t{{#each}}\t{{else}}\t{{\/each}}\tto\tcreate\ta\tlisting\tof\tall\trecorded\tcryptids\t\u2013\tor,\tif there\tare\tnone,\tthe\ttext\t\u201cNo\tCreatures\u201d\t\u2013\tin\tapp\/templates\/cryptids.hbs: {{outlet}} <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<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<\/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>"]
Search
Read the Text Version
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
- 219
- 220
- 221
- 222
- 223
- 224
- 225
- 226
- 227
- 228
- 229
- 230
- 231
- 232
- 233
- 234
- 235
- 236
- 237
- 238
- 239
- 240
- 241
- 242
- 243
- 244
- 245
- 246
- 247
- 248
- 249
- 250
- 251
- 252
- 253
- 254
- 255
- 256
- 257
- 258
- 259
- 260
- 261
- 262
- 263
- 264
- 265
- 266
- 267
- 268
- 269
- 270
- 271
- 272
- 273
- 274
- 275
- 276
- 277
- 278
- 279
- 280
- 281
- 282
- 283
- 284
- 285
- 286
- 287
- 288
- 289
- 290
- 291
- 292
- 293
- 294
- 295
- 296
- 297
- 298
- 299
- 300
- 301
- 302
- 303
- 304
- 305
- 306
- 307
- 308
- 309
- 310
- 311
- 312
- 313
- 314
- 315
- 316
- 317
- 318
- 319
- 320
- 321
- 322
- 323
- 324
- 325
- 326
- 327
- 328
- 329
- 330
- 331
- 332
- 333
- 334
- 335
- 336
- 337
- 338
- 339
- 340
- 341
- 342
- 343
- 344
- 345
- 346
- 347
- 348
- 349
- 350
- 351
- 352
- 353
- 354
- 355
- 356
- 357
- 358
- 359
- 360
- 361
- 362
- 363
- 364
- 365
- 366
- 367
- 368
- 369
- 370
- 371
- 372
- 373
- 374
- 375
- 376
- 377
- 378
- 379
- 380
- 381
- 382
- 383
- 384
- 385
- 386
- 387
- 388
- 389
- 390
- 391
- 392
- 393
- 394
- 395
- 396
- 397
- 398
- 399
- 400
- 401
- 402
- 403
- 404
- 405
- 406
- 407
- 408
- 409
- 410
- 411
- 412
- 413
- 414
- 415
- 416
- 417
- 418
- 419
- 420
- 421
- 422
- 423
- 424
- 425
- 426
- 427
- 428
- 429
- 430
- 431
- 432
- 433
- 434
- 435
- 436
- 437
- 438
- 439
- 440
- 441
- 442
- 443
- 444
- 445
- 446
- 447
- 448
- 449
- 450
- 451
- 452
- 453
- 454
- 455
- 456
- 457
- 458
- 459
- 460
- 461
- 462
- 463
- 464
- 465
- 466
- 467
- 468
- 469
- 470
- 471
- 472
- 473
- 474
- 475
- 476
- 477
- 478
- 479
- 480
- 481
- 482
- 483
- 484
- 485
- 486
- 487
- 488
- 489
- 490
- 491
- 492
- 493
- 494
- 495
- 496
- 497
- 498
- 499
- 500
- 501
- 502
- 503
- 504
- 505
- 506
- 507
- 508
- 509
- 510
- 511
- 512
- 513
- 514
- 515
- 516
- 517
- 518
- 519
- 520
- 521
- 522
- 523
- 524
- 525
- 526
- 527
- 528
- 529
- 530
- 531
- 532
- 533
- 534
- 535
- 536
- 537
- 538
- 539
- 540
- 541
- 542
- 543
- 544
- 545
- 546
- 547
- 548
- 549
- 550
- 551
- 552
- 553
- 554
- 555
- 556
- 557
- 558
- 559
- 560
- 561
- 562
- 563
- 564
- 565
- 566
- 567
- 568
- 569
- 570
- 571
- 572
- 573
- 574
- 575
- 576