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

["class\tChatMessage\t{ \t\tconstructor(data)\t{ \t\t\t\tvar\t{message:\tm,\tuser:\tu='batman',\ttimestamp:\tt=(new\tDate()).getTime()}\t=\tdata; \t\t\t\tthis.message\t=\tm; \t\t\t\tthis.user\t=\tu; \t\t\t\tthis.timestamp\t=\tt; \t\t} } ... OK,\tthe\tdetour\tis\tover.\tTime\tto\tget\tback\tto\tbuilding\tChattrbox! Your\tChatMessage\tclass\tstores\tall\tof\tthe\timportant\tinformation\tas\tproperties,\tbut\tits instances\talso\tinherit\tChatMessage\u2019s\tmethods\tand\tother\tinformation.\tThat\tmakes ChatMessage\tinstances\tunsuitable\tfor\tsending\tthrough\tWebSockets.\tA\tstripped-down version\tof\tthat\tinformation\tis\tnecessary. Write\ta\tserialize\tmethod\tin\tapp.js\tto\trepresent\tthe\tdata\tin\tChatMessage\u2019s properties\tas\ta\tplain\tJavaScript\tobject. ... class\tChatMessage\t{ \t\tconstructor({ \t\t\t\tmessage:\tm, \t\t\t\tuser:\tu='batman', \t\t\t\ttimestamp:\tt=(new\tDate()).getTime() \t\t})\t{ \t\t\t\tthis.user\t=\tuser; \t\t\t\tthis.message\t=\tmessage; \t\t\t\tthis.timestamp\t=\ttimestamp; \t\t} \t\tserialize()\t{ \t\t\t\treturn\t{ \t\t\t\t\t\tuser:\tthis.user, \t\t\t\t\t\tmessage:\tthis.message, \t\t\t\t\t\ttimestamp:\tthis.timestamp \t\t\t\t}; \t\t} } export\tdefault\tChatApp; Your\tChatMessage\tclass\tis\tnow\tready\tfor\tuse.\tIt\tis\ttime\tto\tmove\ton\tto\tthe\tnext\tmodule for\tChattrbox.","Creating\tthe\tws-client\tModule The\tws-client.js\tmodule\twill\thandle\tcommunicating\twith\tyour\tNode\tWebSocket server. It\twill\thave\tfour\tresponsibilities: connecting\tto\tthe\tserver performing\tinitial\tsetup\twhen\tthe\tconnection\tis\tfirst\topened forwarding\tincoming\tmessages\tto\ttheir\thandlers sending\toutgoing\tmessages Check\tout\thow\tthose\tresponsibilities\trelate\tto\tyour\tother\tcomponents\t(Figure\t17.14). Figure\t17.14\t\tws-client\u2019s\tinterfaces As\tyou\tbuild\tout\tyour\tclient,\tyou\twill\tget\ta\ttour\tof\tsome\tnew\tES6\tfeatures\tas\twell. Connection\thandling First,\tbuild\tout\tyour\tcollection\thandling.\tBegin\tby\topening\tws-client.js\tand declaring\ta\tvariable\tfor\tthe\tWebSocket\tconnection. let\tsocket; This\tdeclaration\tuses\ta\tnew\tway\tof\tdefining\tvariables\tin\tES6\tcalled\tlet\tscoping.\tIf\tyou\tuse let\tscoping\tto\tdeclare\ta\tvariable\t\u2013\tusing\tthe\tkeyword\tlet\tinstead\tof\tvar\t\u2013\tyour\tvariable will\tnot\tbe\thoisted. Hoisting\tmeans\tthat\tthe\tvariable\tdeclarations\tget\tmoved\tto\tthe\ttop\tof\tthe\tfunction\tscope\tin","which\tthey\tare\tcreated.\tThis\tis\tsomething\tthat\tthe\tJavaScript\tinterpreter\tdoes\tbehind\tthe scenes.\tUnfortunately,\tit\tcan\tlead\tto\thard-to-find\terrors. You\twill\tread\tmore\tabout\thoisting\tat\tthe\tend\tof\tthe\tchapter.\tFor\tnow,\tknow\tthat\tlet\tis\ta safer\tway\tto\tdeclare\tvariables\tin\tif\/else\tclauses\tand\tin\tthe\tbody\tof\tloops. Now,\tadd\ta\tmethod\tto\tws-client.js\tto\tinitialize\tyour\tconnection. let\tsocket; function\tinit(url)\t\t{ \t\tsocket\t=\tnew\tWebSocket(url); \t\tconsole.log('connecting...'); } The\tinit\tfunction\tconnects\tto\tthe\tWebSockets\tserver.\tNext,\tyou\twant\tto\twire\tup\tws- client.js\tto\tChatApp\tin\tapp.js. To\tbe\ta\tfunctioning\tmodule,\tws-client.js\tneeds\tto\tspecify\twhat\tit\texports.\tYou\tneed to\texport\ta\tsingle\tvalue:\tan\tobject\tcode\twith\tthe\texported\tfunctions\tas\tits\tproperties.\tYou are\tgoing\tto\tuse\tthe\tsame\texport\tdefault\tsyntax\tthat\tyou\tused\tat\tthe\tbeginning\tof\tthe chapter\t\u2013\tplus\tan\tadditional\tbit\tof\tES6\thandiness. Add\tthe\texport\tto\tthe\tend\tof\tws-client.js,\tas\tshown. ... function\tinit(url)\t\t{ \t\tsocket\t=\tnew\tWebSocket(url); \t\tconsole.log('connecting...'); } export\tdefault\t{ \t\tinit, } Notice\tthat\tyou\tdid\tnot\thave\tto\tspecify\tthe\tproperty\tnames.\tThis\tsyntactic\tshortcut\tis\tthe equivalent\tof: export\tdefault\t{ \t\tinit:\tinit } If\tthe\tkey\tand\tvalue\thave\tthe\tsame\tname,\tES6\tallows\tyou\tto\tomit\tthe\tcolon\tand\tthe\tvalue. The\tkey\twill\tautomatically\tbe\tthe\tvariable\tname,\tand\tthe\tvalue\twill\tautomatically\tbe\tthe value\tassociated\twith\tthat\tname.\tThis\tfeature\tof\tES6\tis\tthe\tenhanced\tobject\tliteral\tsyntax. Now\tthat\tyou\thave\tthe\tws-client\tmodule\tset\tup,\tit\tis\ttime\tto\timport\tthe\tvalues\tit provides\tin\tapp.js.\tBegin\tby\tadding\tan\timport\tstatement\tto\tthe\ttop\tof\tapp.js: import\tsocket\tfrom\t'.\/ws-client'; class\tChatApp\t{ \t\tconstructor()\t{ \t\t\t\tconsole.log('Hello\tES6!'); \t\t} } ... socket\twill\tbe\tthe\tobject\tyou\texported\tfrom\tws-client.js. Next,\tin\tthe\tChatApp\tconstructor,\tcall\tsocket.init\twith\tthe\tURL\tof\tyour\tWebSocket server. import\tsocket\tfrom\t'.\/ws-client'; class\tChatApp\t{ \t\tconstructor()\t{ \t\t\t\tconsole.log('Hello\tES6!'); \t\t\t\tsocket.init('ws:\/\/localhost:3001'); \t\t} } ...","Your\tnpm\tscript\tshould\trebuild\tthe\tcode\tfor\tyou.\t(You\tmay\tneed\tto\trestart\tnpm\trun\twatch and\tnpm\trun\tdev\tin\tseparate\twindows,\tif\tyou\thave\tlet\tone\tor\tboth\tof\tthem\tstop.)\tReload your\tbrowser\tand\tyou\tshould\tsee\t'connecting...'\tlogged\tto\tthe\tconsole,\tas\tshown\tin Figure\t17.15. Figure\t17.15\t\tMessage\tlogged\ton\tWebSocket\tinitialization With\tthat,\tyou\thave\tthe\tskeleton\tof\tyour\tapp\tup\tand\trunning. Handling\tevents\tand\tsending\tmessages When\tyour\tApp\tmodule\tcalls\tinit,\ta\tnew\tWebSocket\tobject\tis\tinstantiated\tand\ta connection\tis\tmade\tto\tthe\tserver.\tBut\tyour\tApp\tmodule\tneeds\tto\tknow\twhen\tthis\tprocess has\tcompleted\tso\tthat\tit\tcan\tdo\tsomething\twith\tthe\tconnection. The\tWebSocket\tobject\thas\ta\tset\tof\tspecial\tproperties\tfor\thandling\tevents.\tOne\tof\tthese\tis the\tonopen\tproperty.\tAny\tfunction\tassigned\tto\tthis\tproperty\twill\tbe\tcalled\twhen\tthe connection\tto\tthe\tWebSocket\tserver\tis\tmade.\tInside\tthis\tfunction,\tyou\tcan\tcarry\tout\tany steps\tthat\tneed\tto\tbe\tmade\tupon\tconnecting. In\torder\tfor\tthe\tws-client\tmodule\tto\tbe\tflexible\tand\treusable,\tyou\twill\tnot\thardcode\tthe steps\tthat\tthe\tApp\tmodule\tneeds\tto\tmake\tupon\tconnecting.\tInstead,\tyou\twill\tuse\tthe\tsame pattern\tyou\tused\tfor\tregistering\tclick\tand\tsubmit\thandlers\tin\tCoffeeRun. Add\ta\tfunction\tcalled\tregisterOpenHandler\tto\tws-client.js. registerOpenHandler\twill\taccept\ta\tcallback,\tassign\ta\tfunction\tto\tonopen,\tand\tthen invoke\tthe\tcallback\tinside\tthe\tonopen\tfunction. let\tsocket; function\tinit(url)\t\t{ \t\tsocket\t=\tnew\tWebSocket(url); \t\tconsole.log('connecting...'); } function\tregisterOpenHandler(handlerFunction)\t{ \t\tsocket.onopen\t=\t()\t=>\t{ \t\t\t\tconsole.log('open'); \t\t\t\thandlerFunction(); \t\t}; } ...","This\tfunction\tdefinition\tis\tdifferent\tfrom\twhat\tyou\thave\twritten\tbefore.\tThis\tis\ta\tnew\tES6 syntax\tcalled\tan\tarrow\tfunction.\tArrow\tfunctions\tare\ta\tshorthand\tfor\twriting\tanonymous functions.\tApart\tfrom\tbeing\ta\tbit\teasier\tto\twrite,\tarrow\tfunctions\twork\texactly\tthe\tsame\tas anonymous\tfunctions. registerOpenHandler\ttakes\ta\tfunction\targument\t(handlerFunction)\tand assigns\tan\tanonymous\tfunction\tto\tthe\tonopen\tproperty\tof\tthe\tsocket\tconnection.\tInside\tof this\tanonymous\tfunction,\tyou\tcall\tthe\thandlerFunction\tthat\twas\tpassed\tin. (Using\tan\tanonymous\tfunction\tis\tmore\tcomplicated\tthan\twriting\tsocket.onopen\t= handlerFunction.\tThis\tpattern\twill\tserve\tyou\twell\twhen\tyou\tneed\tto\trespond\tto\tan\tevent but\thave\tintermediary\tsteps\tthat\tmust\thappen\tbefore\tforwarding\tit\ton\t\u2013\tlike\twriting\ta\tlog message,\tas\tyou\thave\tdone\there.) Next,\tyou\tneed\tto\twrite\tan\tinterface\tfor\thandling\tmessages\tas\tthey\tcome\tin\tover\tyour WebSockets\tconnection.\tWrite\ta\tnew\tmethod\tcalled\tregisterMessageHandler\tin ws-client.js.\tAssign\tan\tarrow\tfunction\tto\tthe\tsocket\u2019s\tonmessage\tproperty;\tthis arrow\tfunction\tshould\texpect\tto\treceive\tan\tevent\targument. ... function\tregisterOpenHandler(handlerFunction)\t{ \t\tsocket.onopen\t=\t()\t=>\t{ \t\t\t\tconsole.log('open'); \t\t\t\thandlerFunction(); \t\t}; } function\tregisterMessageHandler(handlerFunction)\t{ \t\tsocket.onmessage\t=\t(e)\t=>\t{ \t\t\t\tconsole.log('message',\te.data); \t\t\t\tlet\tdata\t=\tJSON.parse(e.data); \t\t\t\thandlerFunction(data); \t\t}; } ... Arrow\tfunction\tparameters\tgo\tinside\tthe\tparentheses,\tjust\tas\tthey\tdo\tfor\tregular\tfunctions. The\tChattrbox\tclient\treceives\tan\tobject\tfrom\tthe\tserver\tin\tits\tonmessage\tcallback\tinside registerMessageHandler.\tThis\tobject\trepresents\tthe\tevent\tand\thas\ta\tdata property\tthat\tcontains\tthe\tJSON\tstring\tfrom\tthe\tserver.\tEach\ttime\tyou\treceive\ta\tstring,\tyou convert\tthe\tstring\tto\ta\tJavaScript\tobject.\tYou\tthen\tforward\tit\talong\tto handlerFunction. The\tlast\tbit\tis\tthe\tpiece\tthat\twill\tactually\tsend\tthe\tmessage\tto\tyour\tWebSocket.\tWrite\tthis in\tws-client.js\tas\ta\tfunction\tcalled\tsendMessage.\tYou\twill\tdo\tthis\tin\ttwo\tparts. First,\tyou\twill\tturn\tyour\tmessage\tpayload\t(containing\tthe\tmessage,\tthe\tusername,\tand\tthe timestamp)\tinto\ta\tJSON\tstring.\tThen\tyou\twill\tsend\tthat\tJSON\tstring\tto\tthe\tWebSocket server. ... function\tregisterMessageHandler(handlerFunction)\t{ \t\tsocket.onmessage\t=\t(e)\t=>\t{ \t\t\t\tconsole.log('message',\te.data); \t\t\t\tlet\tdata\t=\tJSON.parse(e.data); \t\t\t\thandlerFunction(data); \t\t}; } function\tsendMessage(payload)\t{ \t\tsocket.send(JSON.stringify(payload)); } ... Finally,\tadd\texports\tfor\tyour\tnew\tmethods\tusing\tthe\tenhanced\tobject\tliteral\tsyntax. ... function\tsendMessage(payload)\t{","socket.send(JSON.stringify(payload)); } export\tdefault\t{ \t\tinit, \t\tregisterOpenHandler, \t\tregisterMessageHandler, \t\tsendMessage } With\tthat,\tws-client.js\thas\teverything\tit\tneeds\tto\tcommunicate\tback\tand\tforth\twith the\tserver.\tYour\tlast\tjob\tin\tws-client.js\twill\tbe\tto\ttest\tit\tby\tsending\ta\tmessage. Sending\tand\techoing\ta\tmessage Update\tthe\tChatApp\tconstructor\tin\tapp.js.\tAfter\tcalling\tsocket.init,\tcall registerOpenHandler\tand\tregisterMessageHandler,\tpassing\tthem\tarrow functions. import\tsocket\tfrom\t'.\/ws-client'; class\tChatApp\t{ \t\tconstructor()\t{ \t\t\t\tsocket.init('ws:\/\/localhost:3001'); \t\t\t\tsocket.registerOpenHandler(()\t=>\t{ \t\t\t\t\t\tlet\tmessage\t=\tnew\tChatMessage({\tmessage:\t'pow!'\t}); \t\t\t\t\t\tsocket.sendMessage(message.serialize()); \t\t\t\t}); \t\t\t\tsocket.registerMessageHandler((data)\t=>\t{ \t\t\t\t\t\tconsole.log(data); \t\t\t\t}); \t\t} } ... When\tthe\tconnection\tis\topen,\tyou\tare\timmediately\tsending\ta\tdummy\tmessage.\tAnd\twhen a\tmessage\tis\treceived,\tyou\tare\tlogging\tit\tto\tthe\tconsole. Save\tyour\tcode\tand\treload\tthe\tbrowser\twhen\tthe\tbuild\tprocess\tfinishes.\tYou\tshould\tsee that\ta\tmessage\twas\tsent\tand\techoed\tback\t(Figure\t17.16). Figure\t17.16\t\tCall\tand\tresponse\twith\tWebSockets Excellent\twork!\tYou\thave\ttwo\tof\tthe\tthree\tprimary\tmodules\tfor\tChattrbox\tworking.\tYou will\tfinish\tChattrbox\tin\tthe\tnext\tchapter\tby\tcreating\ta\tmodule\tthat\tconnects\tyour\texisting modules\tto\tthe\tUI.\tThis\tmodule\twill\tdraw\tnew\tmessages\tto\tthe\tmessage\tlist\tand\tsend messages\twhen\tthe\tform\tis\tsubmitted.","For\tthe\tMore\tCurious:\tCompiling\tto\tJavaScript\tfrom Other\tLanguages There\tare\tquite\ta\tfew\tlanguages\tthat\twill\tcompile\tto\tJavaScript.\tHere\tis\ta\tshort\tlist: CoffeeScript:\tcoffeescript.org TypeScript:\twww.typescriptlang.org C\/C++:\tkripken.github.io\/\u200bemscripten-site One\tof\tthe\tmost\tprominent\tis\tCoffeeScript,\twhich\tprovides\tshorthand\tsyntax\tfor\tsome\tof the\tmost\tcommon\tpatterns\t(e.g.,\tthe\tarrow\tsyntax\tfor\tanonymous\tfunctions).\tIn\tfact, CoffeeScript\thad\ta\tsignificant\tinfluence\ton\tES6. Google,\tMicrosoft,\tMozilla,\tand\tothers\tare\tcollaborating\ton\ta\tproject\tto\tstandardize\tan assembly\tlanguage\tfor\tJavaScript\tengines,\tcalled\tWebAssembly.\tThe\tgoal\tis\tto\tcreate\ta high-performance,\tlow-level\tlanguage\tthat\tcan\tbe\tcompiled\tto\tfrom\tmany\tdifferent languages. The\tintention\tfor\tWebAssembly\tis\tto\tsupplement\tJavaScript\t\u2013\tnot\treplace\tit\t\u2013\tand\tto capitalize\ton\tthe\tstrengths\tof\tmultiple\tlanguages.\tJavaScript\tis\tgood\tat\tcreating\tbrowser- based\tapplications,\tfor\texample,\tbut\tnot\tat\trendering\tmath-intensive\tgame\tgraphics.\tC\tand C++,\tmeanwhile,\texcel\tat\trendering\tgame\tcode.\tRather\tthan\tporting\tC++\tcode\tover\tto JavaScript\tand\tpotentially\tintroducing\tbugs,\tit\tcould\tbe\tcompiled\tto\tWebAssembly. The\tWebAssembly\tproject\tsprang\tout\tof\tan\tearlier\tproject\tcalled\tasm.js,\twhich\tspecified\ta subset\tof\tthe\tJavaScript\tlanguage\tfor\twriting\thigh-performance\tcode. For\tmore\tinformation\tabout\tasm.js\tand\tWebAssembly,\tcheck\tout\tthis\tblog\tpost\tby\tthe creator\tof\tJavaScript:\tbrendaneich.com\/2\u200b 015\/\u200b06\/f\u200b rom-asm-js-to- webassembly.","Bronze\tChallenge:\tDefault\tImport\tName In\tmain.js,\tyour\timport\tstatement\tcreates\ta\tlocal\tvariable\tnamed\tChatApp.\tWhat happens\tif\tyou\tchange\tthis\tto\tApplicationForChatting? Try\tit\t(making\tsure\tyou\talso\tchange\tthe\tnew\tstatement\ton\tthe\tnext\tline)\tand\tfind\tout whether\tit\tstill\tworks.\tIf\tso,\twhy?\tIf\tnot,\twhy\tnot?","Silver\tChallenge:\tClosed\tConnection\tAlert In\tthe\tws-client\tmodule,\tadd\tanother\tfunction\tcalled\tregisterCloseHandler.\tIt should\ttake\ta\tcallback\tthat\tis\tinvoked\twhen\tthe\tclose\tevent\tis\ttriggered\ton\tthe\tsocket. In\tmain.js,\tuse\tregisterCloseHandler\tto\talert\tthe\tuser\tthat\tthe\tconnection\tis closed.\tThen,\ttest\tit\tto\tmake\tsure\tit\tworks. How\tcan\tyou\ttest\tit?\tObviously,\tyou\tcannot\tclose\tthe\tbrowser\twindow.\tYou\twill\tneed\tto close\tthe\tother\tend\tof\tthe\tconnection. For\tan\tadded\tbonus,\twrite\ta\tfunction\tthat\tattempts\tto\treconnect.\tYou\tcan\teither\tuse\ta setTimeout\tor\tyou\tcan\tprompt\tthe\tuser\tfor\tconfirmation\t(search\tthe\tMDN\tfor\tdetails).","For\tthe\tMore\tCurious:\tHoisting JavaScript\twas\tcreated\tso\tthat\tnonprofessional\tprogrammers\tcould\tcreate\tweb\tcontent with\tsome\tbasic\tinteractivity.\tAlthough\tthe\tlanguage\thas\tfeatures\tintended\tto\tmake\tcode error-resistant,\tsome\tof\tits\tfeatures\tend\tup\tcausing\terrors\tin\tpractice.\tOne\tof\tthese\tfeatures is\thoisting. When\tthe\tJavaScript\tengine\tinterprets\tyour\tcode,\tit\tfinds\tall\tof\tthe\tvariable\tand\tfunction declarations\tand\tmoves\tthem\tto\tthe\ttop\tof\tthe\tfunction\tthey\tare\tin.\t(Or,\tif\tthey\tare\tnot\tin\ta function,\tthey\tare\tevaluated\tbefore\tthe\trest\tof\tthe\tcode.) This\tis\tbest\tillustrated\twith\tan\texample.\tWhen\tyou\twrite\tthis\tcode: function\tlogSomeValues\t()\t{ \t\tconsole.log(myVal); \t\tvar\tmyVal\t=\t5; \t\tconsole.log(myVal); } it\tis\tinterpreted\tas\tthough\tyou\thad\twritten: function\tlogSomeValues\t()\t{ \t\tvar\tmyVal; \t\tconsole.log(myVal); \t\tmyVal\t=\t5; \t\tconsole.log(myVal); } If\tyou\tcalled\tlogSomeValues\tthe\tconsole,\tyou\twould\tsee\tthis: >\tlogSomeValues(); undefined 5 Notice\tthat\tit\tis\tonly\tthe\tdeclaration\tthat\tis\thoisted.\tThe\tassignment\tstays\tin\tplace. Naturally,\tthis\tcan\tcause\tconfusion,\tespecially\tif\tyou\twere\tto\ttry\tto\tdeclare\tvariables\tin\tan if\tstatement\tor\tinside\tof\ta\tloop.\tIn\tother\tlanguages,\tthe\tcurly\tbraces\tdenote\ta\tblock,\twhich has\tits\town\tscope.\tIn\tJavaScript,\tcurly\tbrace\tblocks\tdo\tnot\tcreate\tscope.\tOnly\tfunctions create\tscope. Take\ta\tlook\tat\tanother\texample: var\tmyVal\t=\t\t11; function\tdoNotWriteCodeLikeThis()\t{ \t\tif\t(myVal\t>\t10)\t{ \t\t\t\tvar\tmyVal\t=\t0; \t\t\t\tconsole.log('myVal\twas\tgreater\tthan\t10;\tresetting\tto\t0'); \t\t}\telse\t{ \t\t\t\tconsole.log('no\tneed\tto\treset.'); \t\t} \t\treturn\tmyVal; } You\tmight\texpect\tthat\t\u2018myVal\twas\tgreater\tthan\t10;\tresetting\tto\t0\u2019 would\tbe\tprinted\tto\tthe\tconsole\tand\tthe\tvalue\t0\treturned.\tInstead,\tthis\tis\twhat\twould\tbe printed: >\tdoNotWriteCodeLikeThis(); no\tneed\tto\treset. undefined The\tdeclaration\tvar\tmyVal\tis\tmoved\tto\tthe\ttop\tof\tthe\tfunction,\tso\tbefore\tthe\tif\tclause\tis evaluated,\tmyVal\thas\ta\tvalue\tof\tundefined.\tThe\tassignment\tstays\tinside\tthe\tif\tblock. Function\tdeclarations\tare\talso\thoisted,\tbut\tin\ttheir\tentirety.\tThat\tmeans\tthat\tthis\tworks\tjust fine:","boo(); \/\/\tDeclare\tafter\tcalling: function\tboo()\t{ \t\tconsole.log('BOO!!'); } JavaScript\tmoves\tthe\tentire\tfunction\tdeclaration\tblock\tto\tthe\ttop,\tallowing\tthe\tinvocation of\tboo\tto\thappen\twithout\tany\tproblems: >\tboo(); BOO!! let\tstatements\tare\timmune\tto\thoisting.\tconst\tstatements,\twhich\tlet\tyou\tdeclare\tvariables that\tcannot\tbe\treassigned,\tare\talso\timmune.","For\tthe\tMore\tCurious:\tArrow\tFunctions We\tfibbed.\tArrow\tfunctions\tdo\tnot\twork\texactly\tlike\tanonymous\tfunctions.\tFor\tsome situations,\tthey\tare\tbetter. In\taddition\tto\tproviding\tshorter\tsyntax,\tarrow\tfunctions: work\tas\tthough\tyou\thad\twritten\tfunction\t()\t{}.bind(this),\tmaking\tthis work\tas\texpected\tin\tthe\tbody\tof\tthe\tarrow\tfunction allow\tyou\tto\tomit\tthe\tcurly\tbraces\tif\tyou\tonly\thave\tone\tstatement return\tthe\tresult\tof\tthe\tsingle\tstatement\twhen\tcurly\tbraces\tare\tomitted For\texample,\there\tis\tCoffeeRun\u2019s\tCheckList.prototype.addClickHandler method: CheckList.prototype.addClickHandler\t=\tfunction(fn)\t{ \t\tthis.$element.on('click',\t'input',\tfunction\t(event)\t{ \t\t\t\tvar\temail\t=\tevent.target.value; \t\t\t\tfn(email) \t\t\t\t\t\t.then(\tfunction\t()\t{ \t\t\t\t\t\t\t\tthis.removeRow(email); \t\t\t\t\t\t}.bind(this)); \t\t}.bind(this)); }; Replacing\tthe\tanonymous\tfunctions\twith\tarrow\tfunctions\tmakes\tthis\tcode\ta\tbit\tclearer: CheckList.prototype.addClickHandler\t=\t(fn)\t=>\t{ \t\tthis.$element.on('click',\t'input',\t(event)\t=>\t{ \t\t\t\tlet\temail\t=\tevent.target.value; \t\t\t\tfn(email) \t\t\t\t\t\t.then(()\t=>\tthis.removeRow(email)); \t\t}); }; The\twork\tthat\taddClickHandler\tis\tdoing\tis\tmore\tapparent\twithout\tthe\textra\tnoise\tof function\tand\t.bind(this).","18\t ES6,\tthe\tAdventure\tContinues Chattrbox\tis\ta\tworking\tapplication,\tbut\tright\tnow\tit\tfocuses\ton\t\u201cunder\tthe\thood\u201d\tbusiness logic.\tIt\tconnects\tto\tthe\tWebSockets\tserver.\tIt\tdefines\ta\tmessage\tformat\tand\tis\table\tto\tsend and\treceive\tmessages. In\tthis\tchapter,\tyou\twill\tcomplete\tChattrbox\tby\twiring\tup\tthe\tUI\tlayer.\tYou\twill\tcontinue to\tuse\tNode\tand\tnpm\tto\tmanage\tyour\tbuild\tprocess\tand\tact\tas\ta\tserver,\tand\tat\tthe\tend\tof\tthe chapter\tyou\twill\thave\ta\tfully\tfunctional\tweb-based\tchat\tapp\t(Figure\t18.1). Figure\t18.1\t\tThe\tcompleted\tChattrbox","When\tyou\tbuilt\tCoffeeRun,\tyou\tcreated\tFormHandler\tand\tCheckList\tmodules\tthat corresponded\tto\tthe\tform\tand\tthe\tlist\tarea.\tYou\twill\tuse\tthe\tsame\tpattern\twith\tChattrbox for\tcreating\tthe\tChatForm\tand\tChatList\tmodules. You\twill\talso\tcreate\ta\tUserStore\tmodule\tthat\twill\thold\tinformation\tabout\tthe\tcurrent chat\tuser.\tThese\twill\tmake\tChattrbox\tmore\trobust\tand\tmake\tits\tmain\tmodules\tmore reusable. Installing\tjQuery\tas\ta\tNode\tModule Chattrbox\twill\tmake\tuse\tof\tjQuery\tfor\tDOM\tmanipulation.\tBut\tyou\twill\tnot\tload\tjQuery from\tcdnjs.com,\tas\tyou\tdid\tfor\tCoffeeRun,\tnor\twill\tyou\tuse\ta\t<script>\ttag\tin\tyour HTML,\tthe\tway\tyou\thave\tbeen\tintegrating\tclient-side\tdependencies. With\tBrowserify,\tthis\tis\tno\tlonger\tnecessary.\tBrowserify\tautomatically\tbuilds\tyour\tJavaScript dependencies\tinto\tyour\tapplication\tbundle\tto\tbe\tused\tin\tyour\tbrowser.\tSo,\tall\tyou\tneed\tto do\tto\tintegrate\tjQuery\tis\tinclude\tit\tvia\timport,\tand\tBrowserify\twill\ttake\tcare\tof\tthe\trest. Begin\tby\tinstalling\tthe\tjQuery\tlibrary\tto\tthe\tnode_modules\tfolder: npm\tinstall\t--save-dev\tjquery Open\tdom.js\tto\tbegin\twriting\tthis\tmodule.\tThe\tdom.js\tmodule\twill\tuse\tjQuery,\tso\tadd an\timport\tstatement\tto\tinclude\tit. import\t$\tfrom\t'jquery'; Later\tin\tthis\tchapter,\tyou\twill\tinstall\tand\tuse\tanother\tthird-party\tlibrary.\tWhen\tyou\tdo,\tyou will\tfollow\tthese\tsame\tsteps\tfor\tinstalling\tand\timporting\tit.","Creating\tthe\tChatForm\tClass As\tyou\tdid\twith\tCoffeeRun,\tyou\twill\tcreate\tan\tobject\tto\tmanage\tthe\tform\telement\tin\tthe DOM.\tThis\twill\tbe\tthe\tChatForm\tclass.\tUsing\tES6\tclasses\twill\tmake\tyour\tcode\ta\tbit more\treadable\tthan\tyour\tcode\tfrom\tCoffeeRun. Creating\ta\tChatForm\tinstance\tand\tinitializing\tits\tevent\thandlers\twill\toccur\tin\ttwo separate\tsteps,\tbecause\ta\tconstructor\u2019s\tjob\tshould\tonly\tbe\tto\tset\tthe\tproperties\tof\tan instance.\tOther\twork\t(like\tattaching\tevent\tlisteners)\tshould\tbe\tdone\tin\tother\tmethods. Define\tChatForm\tin\tdom.js\twith\ta\tconstructor\tthat\taccepts\tselectors.\tIn\tthe constructor,\tadd\tproperties\tfor\tthe\telements\tthe\tinstance\twill\tneed\tto\ttrack. import\t$\tfrom\t'jquery'; class\tChatForm\t{ \t\tconstructor(formSel,\tinputSel)\t{ \t\t\t\tthis.$form\t=\t$(formSel); \t\t\t\tthis.$input\t=\t$(inputSel); \t\t} } Next,\tadd\tan\tinit\tmethod\tthat\twill\tassociate\ta\tcallback\twith\tthe\tform\u2019s\tsubmit\tevent. ... class\tChatForm\t{ \t\tconstructor(formSel,\tinputSel)\t{ \t\t\t\tthis.$form\t=\t$(formSel); \t\t\t\tthis.$input\t=\t$(inputSel); \t\t} \t\tinit(submitCallback)\t{ \t\t\t\tthis.$form.submit((event)\t=>\t{ \t\t\t\t\t\tevent.preventDefault(); \t\t\t\t\t\tlet\tval\t=\tthis.$input.val(); \t\t\t\t\t\tsubmitCallback(val); \t\t\t\t\t\tthis.$input.val(''); \t\t\t\t}); \t\t\t\tthis.$form.find('button').on('click',\t()\t=>\tthis.$form.submit()); \t\t} } In\tthe\tinit\tmethod,\tyou\tused\tan\tarrow\tfunction\tfor\tthe\tsubmit\thandler.\tInside\tthe\tarrow function,\tyou\tprevented\tthe\tdefault\tform\taction,\tretrieved\tthe\tvalue\tfrom\tthe\tinput\tfield, and\tthen\tpassed\tthat\tvalue\tto\tsubmitCallback.\tFinally,\tyou\treset\tthe\tvalue\tof\tthe input. To\tmake\tsure\tthe\tform\tsubmits\twhen\tthe\tbutton\tis\tclicked,\tyou\tadded\ta\tclick\thandler\tthat causes\tthe\tform\tto\tfire\tits\tsubmit\tevent.\tYou\tdid\tthis\tby\tgetting\tthe\tform\telement\twith jQuery\tand\tthen\tcalling\tjQuery\u2019s\tsubmit\tmethod.\tYou\tused\tthe\tsingle-expression\tversion of\tthe\tarrow\tfunction,\tallowing\tyou\tto\tomit\tthe\tcurly\tbraces. To\tmake\tthis\tmodule\tuseful,\tyou\tneed\tto\texport\tChatForm.\tIn\tthe\tprevious\tchapter,\tyou used\texport\tdefault\tfor\tthis\tpurpose.\tThis\tallowed\tyou\tto\texport\ta\tsingle\tvalue\tfor\tthe module.\tIn\tsome\tcases,\tyou\tused\ta\tplain\tJavaScript\tobject\tto\tpackage\tup\tmultiple\tvalues within\tthat\tsingle\tdefault\tvalue. In\tthis\tchapter,\tyou\twill\tuse\tnamed\texports\tto\texport\tmultiple\tnamed\tvalues\tinstead\tof\ta single\tdefault\tvalue. Export\tChatForm\tas\ta\tnamed\tvalue\tto\tusers\tof\tthis\tmodule\tby\tadding\tthe\texport keyword\tjust\tbefore\tthe\tclass\tdeclaration.","... export\tclass\tChatForm\t{ \t\tconstructor(formSel,\tinputSel)\t{ \t\t\t\tthis.$form\t=\t$(formSel); \t\t\t\tthis.$input\t=\t$(inputSel); \t\t} \t\t... Easy\tenough.\tNow\tto\timport\tChatForm\tin\tapp.js. In\tOttergram\tand\tCoffeeRun,\tyou\tused\tthe\tvar\tkeyword\tfor\tselector\tstrings.\tIn\tES6,\tyou can\tdeclare\tconstants\tfor\tthis\tpurpose,\tbecause\tthe\tvalues\tof\tthe\tstrings\twill\tnot\tchange. Like\tlet,\tconst\tis\tblock-scoped,\tmeaning\tthat\tit\tis\tvisible\tto\tany\tcode\tinside\tthe\tsame\tset of\tcurly\tbraces.\tWhen\tit\tis\toutside\tall\tcurly\tbraces\t(which\twill\tbe\tthe\tcase\there),\tit\tis visible\tto\tany\tcode\tin\tthe\tsame\tfile. In\tapp.js,\timport\tthe\tChatForm\tclass\tand\tcreate\tconstants\tfor\tthe\tform\u2019s\tselector\tand message\tinput\tselector.\tAlso,\tcreate\tan\tinstance\tof\tChatForm\tin\tChatApp\u2019s\tconstructor function. import\tsocket\tfrom\t'.\/ws-client'; import\t{ChatForm}\tfrom\t'.\/dom'; const\tFORM_SELECTOR\t=\t'[data-chat=\\\"chat-form\\\"]'; const\tINPUT_SELECTOR\t=\t'[data-chat=\\\"message-input\\\"]'; class\tChatApp\t{ \t\tconstructor()\t{ \t\t\t\tthis.chatForm\t=\tnew\tChatForm(FORM_SELECTOR,\tINPUT_SELECTOR); \t\t\t\tsocket.init('ws:\/\/localhost:3001'); \t\t\t\tsocket.registerOpenHandler(()\t=>\t{ \t\t\t\t\t\tlet\tmessage\t=\tnew\tChatMessage('pow!'); \t\t\t\t\t\tsocket.sendMessage(message.serialize()); \t\t\t\t}); \t\t\t\tsocket.registerMessageHandler((data)\t=>\t{ \t\t\t\t\t\tconsole.log(data); \t\t\t\t}); \t\t} } ... When\tyou\timported\tChatForm,\tyou\twrapped\tit\tin\tcurly\tbraces:\t{ChatForm}.\tThis signifies\ta\tnamed\timport.\tThe\tnamed\timport\tfor\tChatForm\tdeclares\ta\tlocal\tvariable named\tChatForm\tand\tbinds\tit\tto\tthe\tvalue\tfrom\tthe\tdom\tmodule\tof\tthe\tsame\tname. Connecting\tChatForm\tto\tthe\tsocket In\tthe\tlast\tchapter,\tyou\tsent\ta\tdummy\tmessage:\t\\\"pow!\\\".\tNow\tyou\tare\tready\tto\tsend\treal form\tdata\tfrom\tChatForm. Inside\tthe\tsocket.registerOpenHandler\tcallback,\tyou\twill\tinitialize\tthe\tChatForm instance.\tIt\tis\timportant\tto\tdo\tthis\tafter\tthe\tsocket\tis\topen,\tinstead\tof\tinitializing immediately\tafter\tcreating\tthe\tinstance.\tBy\twaiting,\tyou\tprevent\tyour\tuser\tfrom\tentering chat\tmessages\tbefore\tthey\tcan\tactually\tbe\tsent\tto\tthe\tserver.\t(That\twould\tbe\ta\tbad\tthing.) Remember\tthat\tChatForm\u2019s\tinit\tmethod\taccepts\ta\tcallback.\tThis\tcallback\twill\tbe\tused to\thandle\tform\tsubmissions. In\tapp.js,\tdelete\tyour\tdummy\tmessage\tcode\tand\treplace\tit\twith\ta\tcall\tto ChatForm.init,\tpassing\tit\ta\tcallback\tthat\tsends\tmessage\tdata\tcoming\tfrom\tChatForm\tto your\tsocket. ... class\tChatApp\t{ \t\tconstructor()\t{ \t\t\t\tthis.chatForm\t=\tnew\tChatForm(FORM_SELECTOR,\tINPUT_SELECTOR);","socket.init('ws:\/\/localhost:3001'); \t\t\t\tsocket.registerOpenHandler(()\t=>\t{ \t\t\t\t\t\tlet\tmessage\t=\tnew\tChatMessage('pow!'); \t\t\t\t\t\tsocket.sendMessage(message.serialize()); \t\t\t\t\t\tthis.chatForm.init((data)\t=>\t{ \t\t\t\t\t\t\t\tlet\tmessage\t=\tnew\tChatMessage(data); \t\t\t\t\t\t\t\tsocket.sendMessage(message.serialize()); \t\t\t\t\t\t}); \t\t\t\t}); \t\t\t\tsocket.registerMessageHandler((data)\t=>\t{ \t\t\t\t\t\tconsole.log(data); \t\t\t\t}); \t\t} } ... Let\u2019s\tlook\tat\twhat\tChatApp\tis\tdoing\tnow.\tFirst,\tit\topens\tthe\tsocket\tconnection\tto\tthe server.\tWhen\tthe\tconnection\tis\topen,\tChatApp\tinitializes\tyour\tinstance\tof\tChatForm with\ta\tform\tsubmission\tcallback. Now,\twhen\tthe\tuser\tsubmits\ta\tmessage\tin\tthe\tform\tthe\tChatForm\tinstance\twill\ttake\tthat data\tand\tsend\tit\tto\tChatApp\u2019s\tcallback,\tand\tthe\tcallback\twill\tthen\tpackage\tit\tup\tas\ta ChatMessage\tand\tsend\tit\tto\tthe\tWebSockets\tserver.","Creating\tthe\tChatList\tClass That\ttakes\tcare\tof\tsending\toutgoing\tchat\tmessages.\tYour\tnext\tjob\tis\tto\tdisplay\tnew messages\tfrom\tthe\tserver\tas\tthey\tcome\tin.\tTo\tdo\tthat,\tyou\twill\tcreate\ta\tsecond\tclass\tin dom.js\trepresenting\tthe\tlist\tof\tchat\tmessages\tthe\tuser\tsees. ChatList\twill\tcreate\tDOM\telements\tfor\teach\tmessage,\twhich\twill\tdisplay\tthe\tname\tof the\tuser\twho\tsent\tthe\tmessage\tand\tthe\tmessage\ttext.\tIn\tdom.js,\tcreate\tand\texport\ta class\tdefinition\tfor\ta\tnew\tclass\tcalled\tChatList\tto\tfulfill\tthis\trole: import\t$\tfrom\t'jquery'; export\tclass\tChatForm\t{ \t\t... } export\tclass\tChatList\t{ \t\tconstructor(listSel,\tusername)\t{ \t\t\t\tthis.$list\t=\t$(listSel); \t\t\t\tthis.username\t=\tusername; \t\t} } ChatList\taccepts\tthe\tattribute\tselector\tand\tthe\tusername.\tIt\tneeds\tthe\tattribute\tselector so\tthat\tit\tknows\twhere\tto\tattach\tthe\tmessage\tlist\telements\tit\tcreates.\tAnd\tit\tneeds\tthe username\tso\tit\tcan\tsee\twhich\tmessages\twere\tsent\tby\tyour\tuser\tand\twhich\twere\tsent\tby everyone\telse.\t(Your\tmessages\twill\tbe\tdisplayed\tdifferently\tfrom\tthose\tsent\tby\tother users.) Now\tthat\tChatList\thas\ta\tconstructor,\tit\talso\tneeds\tto\tbe\table\tto\tcreate\tDOM\telements for\tmessages. Add\ta\tdrawMessage\tmethod\tto\tChatList.\tIt\twill\texpect\tto\treceive\tan\tobject argument,\twhich\tit\twill\tdestructure\tinto\tlocal\tvariables\tfor\tthe\tusername,\ttimestamp,\tand text\tassociated\twith\tthe\tmessage.\t(To\tmake\tit\tclearer\twhat\tthe\tdestructuring\tassignment\tis doing,\tsingle\tcharacter\tlocal\tvariables\tare\tused.) ... export\tclass\tChatList\t{ \t\tconstructor(listSel,\tusername)\t{ \t\t\t\tthis.$list\t=\t$(listSel); \t\t\t\tthis.username\t=\tusername; \t\t} \t\tdrawMessage({user:\tu,\ttimestamp:\tt,\tmessage:\tm})\t{ \t\t\t\tlet\t$messageRow\t=\t$('<li>',\t{ \t\t\t\t\t\t'class':\t'message-row' \t\t\t\t}); \t\t\t\tif\t(this.username\t===\tu)\t{ \t\t\t\t\t\t$messageRow.addClass('me'); \t\t\t\t} \t\t\t\tlet\t$message\t=\t$('<p>'); \t\t\t\t$message.append($('<span>',\t{ \t\t\t\t\t\t'class':\t'message-username', \t\t\t\t\t\ttext:\tu \t\t\t\t})); \t\t\t\t$message.append($('<span>',\t{ \t\t\t\t\t\t'class':\t'timestamp', \t\t\t\t\t\t'data-time':\tt, \t\t\t\t\t\ttext:\t(new\tDate(t)).getTime() \t\t\t\t})); \t\t\t\t$message.append($('<span>',\t{ \t\t\t\t\t\t'class':\t'message-message', \t\t\t\t\t\ttext:\tm \t\t\t\t})); \t\t\t\t$messageRow.append($message);","$(this.listId).append($messageRow); \t\t\t\t$messageRow.get(0).scrollIntoView(); \t\t} } Your\tdrawMessage\tmethod\tcreates\ta\trow\tfor\tthe\tmessage\twith\tthe\tusername, timestamp,\tand\tthe\tmessage\titself\tdisplayed.\tIf\tyou\tare\tthe\tsender\tof\tthe\tmessage,\tit\tadds an\textra\tCSS\tclass\tfor\tstyling.\tIt\tthen\tappends\tyour\tmessage\u2019s\trow\tto\tChatList\u2019s\tlist element\tand\tscrolls\tthe\tnew\tmessage\trow\tinto\tview. With\tthat,\tChatList\tis\tready\tto\trock.\tTime\tto\tintegrate\tit\tinto\tChatApp. In\tapp.js,\tupdate\tthe\tdom\timport\tstatement\tso\tthat\tit\talso\timports\tChatList.\tAdd\ta const\tfor\tthe\tlist\tselector,\tthen\tinstantiate\ta\tnew\tChatList\tin\tthe\tconstructor. import\tsocket\tfrom\t'.\/ws-client'; import\t{ChatForm,\tChatList}\tfrom\t'.\/dom'; const\tFORM_SELECTOR\t=\t'[data-chat=\\\"chat-form\\\"]'; const\tINPUT_SELECTOR\t=\t'[data-chat=\\\"message-input\\\"]'; const\tLIST_SELECTOR\t=\t'[data-chat=\\\"message-list\\\"]'; class\tChatApp\t{ \t\tconstructor()\t{ \t\t\t\tthis.chatForm\t=\tnew\tChatForm(FORM_SELECTOR,\tINPUT_SELECTOR); \t\t\t\tthis.chatList\t=\tnew\tChatList(LIST_SELECTOR,\t'wonderwoman'); \t\t\t\tsocket.init('ws:\/\/localhost:3001'); \t\t\t\t... You\tare\talmost\tup\tand\trunning.\tThe\tfinal\tstep\tto\tgetting\tbasic\tchat\tfunctionality\tis\tto\tdraw new\tmessages\tas\tthey\tcome\tin\tby\tcalling\tchatList.drawMessage.\tDo\tthis\tin registerMessageHandler\tin\tapp.js: ... class\tChatApp\t{ \t\t... \t\t\t\tsocket.registerMessageHandler((data)\t=>\t{ \t\t\t\t\t\tconsole.log(data); \t\t\t\t\t\tlet\tmessage\t=\tnew\tChatMessage(data); \t\t\t\t\t\tthis.chatList.drawMessage(message.serialize()); \t\t\t\t}); \t\t} } ... You\tcreate\ta\tnew\tChatMessage\tusing\tthe\tincoming\tdata,\tthen\tyou\tserialize\tthe message.\tThis\tis\ta\tprecautionary\tstep\tto\tstrip\taway\textra\tmetadata\tthat\tmight\thave\tbeen added\tto\tthe\tdata.\tCreating\ta\tnew\tChatMessage\tfrom\tthe\tsocket\tdata\tgives\tyou\tyour message,\tand\tthis.chatList.drawMessage\tdraws\tthat\tserialize\tmessage\tinto your\tbrowser. Time\tto\tgive\tit\ta\twhirl.\tIf\tyou\thave\tnot\talready,\tstart\tWatchify\t(with\tnpm\trun\twatch)\tand nodemon\t(with\tnpm\trun\tdev).\tOpen\tor\trefresh\tyour\tbrowser\tand\ttype\tin\ta\tmessage (Figure\t18.2).","Figure\t18.2\t\tSeeing\tyour\town\tchat\tmessage Hooray!\tYou\tnow\thave\ta\tworking\tchat\tapplication.\tIt\tjust\tneeds\ta\tfew\tdesign\ttouches\tfor some\tpolish.","Using\tGravatars Gravatar\tis\ta\tfree\tservice\tthat\tlets\tyou\tassociate\ta\tprofile\tpicture\twith\tyour\temail\taddress. Gravatar\tmakes\teach\tuser\u2019s\tprofile\timage\tavailable\tvia\ta\tspecially\tformatted\tURL.\tFor example,\tFigure\t18.3\tshows\tthe\tGravatar\tof\tone\tof\tour\ttest\taccounts. Figure\t18.3\t\tGravatar\timage\texample See\tthe\tend\tof\tthe\tURL?\tThat\tis\ta\tunique\tidentifier\tgenerated\tfrom\tthe\tuser\u2019s\temail address.\tThis\tidentifier\tis\tcalled\ta\thash\tand\tis\teasy\tto\tgenerate\twith\tthe\thelp\tof\ta\tthird- party\tlibrary\tcalled\tcrypto-js. Add\tcrypto-js\tto\tyour\tproject\tusing\tnpm: npm\tinstall\t--save-dev\tcrypto-js crypto-js\tis\tnow\tinstalled\tin\tyour\tproject\u2019s\tlocal\tnode_modules\tfolder\tand\tready\tfor\tuse. When\tyou\tcreate\tstrings\tin\tJavaScript,\tyou\toften\tneed\tto\tconcatenate\tthe\tstring\twith\tsome other\tvalue.\tES6\tprovides\ta\tbetter\tway\tto\tcreate\tstrings\tthat\tinclude\tvalues\tfrom expressions\tand\tvariables,\tcalled\ttemplate\tstrings.\tYou\twill\tuse\tthis\tfeature\tto\tcreate\tthe URL\tfor\taccessing\tGravatar\timages. In\tdom.js,\tadd\tanother\timport\tstatement\tfor\tthe\tmd5\tsubmodule\tof\tthe\tcrypto-js library,\tusing\ta\t\/\tto\tseparate\tthe\tname\tof\tthe\tmain\tmodule\tand\tthe\tname\tof\tthe\tsubmodule. Then,\twrite\ta\tcreateGravatarUrl\tfunction\tthat\taccepts\ta\tusername,\tgenerates\tan MD5\thash,\tand\treturns\tthe\tURL\tfor\tthe\tGravatar. import\t$\tfrom\t'jquery'; import\tmd5\tfrom\t'crypto-js\/md5'; function\tcreateGravatarUrl(username)\t{ \t\tlet\tuserhash\t=\tmd5(username); \t\treturn\t`http:\/\/www.gravatar.com\/avatar\/${userhash.toString()}`; } ... Take\tnote:\tIn\treturn\t`http:\/\/www.gravatar.com\/avatar\/${userhash.toString()}`, those\tare\tnot\tsingle\tquote\tcharacters.\tThey\tare\tbackticks,\tlocated\tjust\tbelow\tthe\tEscape key\ton\tmost\tUS\tkeyboard\tlayouts. Inside\tthe\tbackticks,\tyou\tcan\tuse\tthe\t${userhash.toString()}\tsyntax\tto\tinclude JavaScript\texpression\tvalues\tdirectly\tin\tyour\tstring.\tHere,\tyou\trefer\tto\tthe\tvariable userhash\tand\tcall\tits\ttoString\tmethod,\tbut\tany\texpression\tis\tvalid\tinside\tof\tthe\tcurly brackets. Next,\tuse\tthis\tfunction\tto\tdisplay\tthe\tGravatar\tin\tnew\tmessages.\tAt\tthe\tbottom\tof","ChatList\u2019s\tdrawMessage\tmethod\t(still\tin\tdom.js),\tcreate\ta\tnew\timage\telement\tand set\tits\tsrc\tattribute\tto\tthe\tuser\u2019s\tGravatar. ... \t\t\t\t$message.append($('<span>',\t{ \t\t\t\t\t\tclass:\t'message-message', \t\t\t\t\t\ttext:\tm \t\t\t\t})); \t\t\t\tlet\t$img\t=\t$('<img>',\t{ \t\t\t\t\t\tsrc:\tcreateGravatarUrl(u), \t\t\t\t\t\ttitle:\tu \t\t\t\t}); \t\t\t\t$messageRow.append($img); \t\t\t\t$messageRow.append($message); \t\t\t\t$(this.listId).append($messageRow); \t\t\t\t$messageRow.get(0).scrollIntoView(); ... Run\tyour\tchat\tapp,\tand\tyou\tshould\tsee\ta\tGravatar\tpop\tup\tthis\ttime\t(Figure\t18.4). Figure\t18.4\t\tShowing\ta\tGravatar Sadly,\tthere\tis\tno\tGravatar\tfor\tthe\twonderwoman\tusername.\tAs\ta\tresult,\tyou\tget\tthe unexciting\tdefault\tGravatar.","Prompting\tfor\tUsername It\twould\tbe\treally\tcool\tto\tbe\tWonder\tWoman.\tBut\tit\tis\tmore\tcool\tto\tbe\ta\tJavaScript developer\tusing\tChattrbox.\t(Especially\tbecause\treal\tusers\tactually\thave\tGravatars.)\tIn order\tto\tknow\twho\tis\tusing\tChattrbox,\tyou\twill\tneed\tto\tprompt\tusers\tfor\ttheir\tusernames. It\tis\tthe\tresponsibility\tof\tthe\tdom\tmodule\tto\tinteract\twith\tthe\tUI,\tso\tcreate\ta promptForUsername\tfunction\tin\tdom.js.\tAdd\tit\tto\tthe\texports\tinstead\tof\tmaking\tit part\tof\tChatForm\tor\tChatList. ... function\tcreateGravatarUrl(username)\t{ \t\tlet\tuserhash\t=\tmd5(username); \t\treturn\t`http:\/\/www.gravatar.com\/avatar\/${userhash.toString()}`; } export\tfunction\tpromptForUsername()\t{ \t\tlet\tusername\t=\tprompt('Enter\ta\tusername'); \t\treturn\tusername.toLowerCase(); } ... In\tthe\tpromptForUsername\tfunction,\tyou\tcreated\ta\tlet\tvariable\tto\thold\tthe\ttext entered\tby\tthe\tuser.\t(The\tprompt\tfunction\tis\tbuilt\tinto\tthe\tbrowser\tand\treturns\ta\tstring.) Then\tyou\treturned\ta\tlowercase\tversion\tof\tthat\ttext. Next,\tyou\twill\tneed\tto\tupdate\tapp.js\tto\tuse\tthis\tnew\tfunction.\tUpdate\tthe\timport statement\tfor\tthe\tdom\tmodule\tand\tcall\tthe\tpromptForUsername\tfunction\tto\tget\ta value\tfor\tthe\tusername\tvariable: import\tsocket\tfrom\t'.\/ws-client'; import\t{ChatForm,\tChatList,\tpromptForUsername}\tfrom\t'.\/dom'; const\tFORM_SELECTOR\t=\t'[data-chat=\\\"chat-form\\\"]'; const\tINPUT_SELECTOR\t=\t'[data-chat=\\\"message-input\\\"]'; const\tLIST_SELECTOR\t=\t'[data-chat=\\\"message-list\\\"]'; let\tusername\t=\t''; username\t=\tpromptForUsername(); class\tChatApp\t{ \t\t... Now,\tupdate\tChatMessage\tto\tuse\tthat\tusername\tas\tthe\tdefault.\tRemember,\tonly messages\treceived\tfrom\tthe\tserver\thave\ta\tdata.user\tvalue. ... class\tChatMessage\t{ \t\tconstructor({ \t\t\t\tmessage:\tm, \t\t\t\tuser:\tu='batman',\tusername, \t\t\t\ttimestamp:\tt=(new\tDate()).getTime() ... Finally,\tpass\tthe\tusername\tto\tthe\tChatList\tconstructor: ... class\tChatApp\t{ \t\tconstructor()\t{ \t\t\t\tthis.chatForm\t=\tnew\tChatForm(FORM_SELECTOR,\tINPUT_SELECTOR); \t\t\t\tthis.chatList\t=\tnew\tChatList(LIST_SELECTOR,\t'wonderwoman'\tusername); \t\t\t\t... After\tthe\tbuild\tprocess\tcompletes,\treload\tyour\tbrowser\tand\tenter\ta\tusername\tin\tthe\tprompt (Figure\t18.5).","Figure\t18.5\t\tPrompting\tfor\ta\tusername Now,\ttry\tsending\tmessages.\tYou\tshould\tsee\tyour\tselected\tusername\techoed\tback\tat\tyou,\tas well\tas\tthe\tGravatar\tassociated\twith\tthat\tusername\t(Figure\t18.6). Figure\t18.6\t\tYour\tuser\u2019s\tname Gravatars\tare\tassigned\tusing\temail\taddresses.\tIf\tyou\tdo\tnot\thave\tone\tassociated\twith\tyour email\taddress,\ttry\[email protected]\tor\[email protected].","User\tSession\tStorage Typing\tthe\tusername\teach\ttime\tyou\treload\tthe\tpage\tgets\ttedious.\tIt\twould\tbe\tbetter\tto store\tthat\tusername\tin\tthe\tbrowser.\tFor\tsimple\tstorage,\tthe\tbrowser\tprovides\ttwo\tAPIs\tfor storing\tkey\/value\tpairs\t(with\tone\tlimitation\t\u2013\tthe\tvalue\tmust\tbe\ta\tstring).\tThese\tare localStorage\tand\tsessionStorage.\tThe\tdata\tstored\tin\tlocalStorage\tand\tsessionStorage\tis associated\twith\tyour\tweb\tapplication\u2019s\tserver\taddress.\tCode\tfrom\tdifferent\tsites\tcannot access\teach\tother\u2019s\tdata. Using\tlocalStorage\twould\twork.\tBut\tyou\tmight\tonly\twant\tto\tkeep\tthe\tusername\tuntil you\tclose\tthe\ttab\tor\tthe\twindow,\tso\tin\tthis\tcase\tyou\twill\tuse\tthe\tsessionStorage\tAPI.\tIt\tis just\tlike\tlocalStorage,\tbut\tthe\tdata\tis\terased\twhen\tthe\tbrowsing\tsession\tends\t(either\tby closing\tthe\tbrowser\ttab\tor\tthe\twindow). You\twill\tcreate\ta\tnew\tset\tof\tclasses\tto\tmanage\tyour\tsessionStorage\tinformation. Create\ta\tnew\tfile\tnamed\tstorage.js\tin\tthe\tapp\/scripts\/src\tfolder\tand\tadd\ta class\tdefinition: class\tStore\t{ \t\tconstructor(storageApi)\t{ \t\t\t\tthis.api\t=\tstorageApi; \t\t} \t\tget()\t{ \t\t\t\treturn\tthis.api.getItem(this.key); \t\t} \t\tset(value)\t{ \t\t\t\tthis.api.setItem(this.key,\tvalue); \t\t} } Your\tnew\tStore\tclass\tis\tgeneric\tand\tcan\tbe\tused\twith\teither\tlocalStorage\tor sessionStorage.\tIt\tis\ta\tthin\twrapper\taround\tthe\tWeb\tStorage\tAPIs.\tYou\tspecify\twhich storage\tAPI\tyou\twant\tto\tuse\twhen\tyou\tinstantiate\tone. Notice\tthat\tthere\tare\treferences\tto\tthis.key,\twhich\tis\tnot\tset\tin\tthe\tconstructor.\tThis implementation\tof\tStore\tis\tnot\tintended\tto\tbe\tused\ton\tits\town.\tInstead,\tyou\tuse\tit\tby building\ta\tsubclass\tthat\tdefines\tthe\tkey\tproperty. Create\ta\tsubclass,\tusing\tthe\textends\tkeyword,\tthat\tyou\tcan\tuse\tfor\tstoring\tthe\tusername in\tsessionStorage: class\tStore\t{ \t\tconstructor(storageApi)\t{ \t\t\t\tthis.api\t=\tstorageApi; \t\t} \t\tget()\t{ \t\t\t\treturn\tthis.api.getItem(this.key); \t\t} \t\tset(value)\t{ \t\t\t\tthis.api.setItem(this.key,\tvalue); \t\t} } export\tclass\tUserStore\textends\tStore\t{ \t\tconstructor(key)\t{ \t\t\t\tsuper(sessionStorage); \t\t\t\tthis.key\t=\tkey; \t\t} } UserStore\tonly\tdefines\ta\tconstructor,\twhich\tperforms\ttwo\tactions.\tFirst,\tit\tcalls super,\twhich\tinvokes\tthe\tStore\u2019s\tconstructor,\tpassing\tit\ta\treference\tto","sessionStorage.\tSecond,\tit\tsets\tthe\tvalue\tof\tthis.key. Now\tthe\tvalue\tof\tapi\tis\tset\tfor\tthe\tStore,\tand\tthe\tvalue\tof\tkey\tis\tset\tfor\tthe UserStore\tinstance.\tThis\tmeans\tthat\tall\tthe\tpieces\tare\tin\tplace\tfor\ta\tUserStore instance\tto\tinvoke\tthe\tget\tand\tset\tmethods. UserStore\twill\tbe\twhat\tapp.js\twill\tuse,\tso\tthat\tis\twhat\tyou\texport\there. Now\tto\tuse\tyour\tnew\tUserStore.\tImport\tUserStore\tinto\tapp.js,\tcreate\tan instance,\tand\tuse\tit\tto\tstash\tthe\tusername: import\tsocket\tfrom\t'.\/ws-client'; import\t{UserStore}\tfrom\t'.\/storage'; import\t{ChatForm,\tChatList,\tpromptForUsername}\tfrom\t'.\/dom'; const\tFORM_SELECTOR\t=\t'[data-chat=\\\"chat-form\\\"]'; const\tINPUT_SELECTOR\t=\t'[data-chat=\\\"message-input\\\"]'; const\tLIST_SELECTOR\t=\t'[data-chat=\\\"message-list\\\"]'; let\tusername\t=\t''; let\tuserStore\t=\tnew\tUserStore('x-chattrbox\/u'); let\tusername\t=\tuserStore.get(); if\t(!username)\t{ \t\tusername\t=\tpromptForUsername(); \t\tuserStore.set(username); } class\tChatApp\t{ \t\t... Run\tChattrbox\tone\tmore\ttime\tin\tyour\tbrowser.\tThis\ttime,\tyou\tshould\tonly\tbe\tprompted for\tyour\tusername\twhen\tyou\tinitially\tload\tthe\tpage.\tSubsequent\treloads\tshould\thave\tthe same\tusername\tyou\tinitially\tentered. To\tconfirm\tthat\tyour\tusername\tis\tbeing\tstored\tin\tsessionStorage,\tyou\tcan\tuse\tthe resources\tpanel\tin\tthe\tDevTools.\tAfter\tyou\tclick\tto\tactivate\tthe\tresources\tpanel,\tyou\twill see\ta\tlist\ton\tthe\tleft.\tClick\tthe\t \tnext\tto\tthe\tSession\tStorage\titem\tin\tthe\tlist,\trevealing http:\/\/l\u200b ocalhost:3000.\tClick\tthis\tURL\tto\treveal\tthe\tdata\tbeing\tstored\tby UserStore\t(Figure\t18.7). Figure\t18.7\t\tThe\tresources\tpanel\tin\tthe\tDevTools At\tthe\tbottom\tof\tthis\tlist\tof\tkey\tvalue\tpairs,\tthere\tare\tbuttons\tfor\trefreshing\tthe\tlist\tand\tfor deleting\titems\tfrom\tthe\tlist.\tYou\tcan\tuse\tthese\tif\tyou\tneed\tto\tmanually\tmodify\tthe\tstored data.","Formatting\tand\tUpdating\tMessage\tTimestamps Your\tmessages\thave\ttimestamps\tthat\tare\tnot\tvery\thuman-friendly.\t(Seriously,\twho\ttells time\tby\tthe\tnumber\tof\tmilliseconds\tsince\tJanuary\t1,\t1970?)\tTo\tprovide\tnicer\ttimestamps (such\tas\t\u201c10\tminutes\tago\u201d),\tyou\twill\tadd\ta\tmodule\tcalled\tmoment.\tInstall\tit\tusing\tnpm\tand save\tit\tas\ta\tdevelopment\tdependency: npm\tinstall\t--save-dev\tmoment Each\tof\tyour\tmessages\tstores\tits\ttimestamp\tas\ta\tdata\tattribute.\tWrite\tan\tinit\tmethod\tfor ChatList\tthat\tcalls\tthe\tbuilt-in\tfunction\tsetInterval,\twhich\ttakes\ttwo\targuments:\ta function\tto\trun\tand\thow\toften\tthat\tfunction\tshould\tbe\trun.\tYour\tfunction\twill\tupdate\teach message\twith\ta\thuman-readable\ttimestamp. To\tset\tthe\ttimestamp\tstring,\tuse\tjQuery\tin\tdom.js\tto\tfind\tall\telements\twith\ta\tdata-time attribute\twhose\tvalue\tis\tthe\tnumerical\ttimestamp.\tCreate\ta\tnew\tDate\tobject\tusing\tthat numerical\ttimestamp\tand\tpass\tthe\tobject\tto\tmoment.\tThen\tcall\tthe\tfromNow\tmethod\tto produce\tthe\tfinal\ttimestamp\tstring\tand\tset\tthat\tstring\tas\tthe\telement\u2019s\tHTML\ttext. ... \t\tdrawMessage({user:\tu,\ttimestamp:\tt,\tmessage:\tm})\t{ \t\t\t\t... \t\t} \t\tinit()\t{ \t\t\t\tthis.timer\t=\tsetInterval(()\t=>\t{ \t\t\t\t\t\t$('[data-time]').each((idx,\telement)\t=>\t{ \t\t\t\t\t\t\t\tlet\t$element\t=\t$(element); \t\t\t\t\t\t\t\tlet\ttimestamp\t=\tnew\tDate().setTime($element.attr('data-time')); \t\t\t\t\t\t\t\tlet\tago\t=\tmoment(timestamp).fromNow(); \t\t\t\t\t\t\t\t$element.html(ago); \t\t\t\t\t\t}); \t\t\t\t},\t1000); \t\t} } You\tare\trunning\tthis\tfunction\tevery\t1,000\tmilliseconds.\tTo\tmake\tsure\ta\thuman-readable timestamp\tappears\timmediately,\tupdate\tdrawMessage.\tUse\tmoment\tto\tcreate\ta formatted\ttimestamp\tstring\twhen\tthe\tmessage\tis\tfirst\tdrawn\tto\tthe\tchat\tlist. ... \t\tdrawMessage({user:\tu,\ttimestamp:\tt,\tmessage:\tm})\t{ \t\t\t\t... \t\t\t\t$message.append($('<span>',\t{ \t\t\t\t\t\t'class':\t'timestamp', \t\t\t\t\t\t'data-time':\tt, \t\t\t\t\t\ttext:\t(new\tDate(t)).getTime() \t\t\t\t\t\ttext:\tmoment(t).fromNow() \t\t\t\t})); \t\t\t\t... Finally,\tupdate\tapp.js,\tadding\ta\tcall\tto\tthis.chatList.init\tinside\tthe socket.registerOpenHandler\tcallback: ... class\tChatApp\t{ \t\tconstructor\t()\t{ \t\t\t\tthis.chatForm\t=\tnew\tChatForm(FORM_SELECTOR,\tINPUT_SELECTOR); \t\t\t\tthis.chatList\t=\tnew\tChatList(LIST_SELECTOR,\tusername); \t\t\t\tsocket.init('ws:\/\/localhost:3001'); \t\t\t\tsocket.registerOpenHandler(()\t=>\t{ \t\t\t\t\t\tthis.chatForm.init((text)\t=>\t{ \t\t\t\t\t\t\t\tlet\tmessage\t=\tnew\tChatMessage({message:\ttext}); \t\t\t\t\t\t\t\tsocket.sendMessage(message.serialize()); \t\t\t\t\t\t}); \t\t\t\t\t\tthis.chatList.init(); \t\t\t\t}); \t\t\t\t... Save\tand\tlet\tyour\tnpm\tscripts\tcompile\tyour\tchanges.\tRefresh\tthe\tbrowser\tand\tstart","chatting.\tYou\tshould\tsee\tyour\tnew\ttimestamps\tappear\twith\tyour\tmessage\ttext.\tAfter\ta couple\tof\tminutes,\tyou\twill\tnotice\tthat\tthe\tmessage\ttimestamps\tupdate\t(Figure\t18.8). Figure\t18.8\t\tNot-so-secret\tidentities You\thave\tcome\tto\tthe\tend\tof\tthe\troad\twith\tChattrbox.\tThough\tit\tonly\tspanned\ta\tfew chapters,\tit\thad\tquite\ta\tfew\tmoving\tparts.\tYou\tlearned\thow\tto\twrite\ttwo\tkinds\tof\tservers\tin Node.js:\ta\tbasic\tweb\tserver\tand\ta\tWebSocket\tserver.\tYou\tbuilt\tthe\tclient\tapplication\tusing ES6,\tutilizing\tBabel\tand\tBrowserify\tto\tcompile\tyour\tcode\tto\tES5\tso\tthat\tChattrbox\tcan\tbe used\tin\tolder\tbrowsers,\tand\tyou\tautomated\tyour\tworkflow\twith\tnpm\tscripts. Chattrbox\tis\tthe\tculmination\tof\tthe\ttechniques\tyou\thave\tlearned\tso\tfar.\tThe\tnext application,\tTracker,\twill\tintroduce\tyou\tto\tEmber.js,\ta\tframework\tfor\tbuilding\tlarge applications.\tIt\twill\tbuild\ton\tyour\thard-won\tknowledge\tof\tmodularity,\tasynchronous programming,\tand\tworkflow\ttools.","Bronze\tChallenge:\tAdding\tVisual\tEffects\tto\tMessages Give\tnew\tmessages\ta\tvisual\teffect.\tYou\tcan\tfade\tthem\tin\tor\thave\tthem\tslide\tin.\t(Check jQuery\u2019s\tEffects\tdocumentation\tfor\toptions.) For\tan\tadded\tchallenge,\tapply\tthis\teffect\tonly\tto\ttruly\tnew\tmessages\t\u2013\tnot\tto\tmessages already\tin\tthe\tchat\tthat\tare\tloaded\tby\tthe\tapp\twhen\tusers\tfirst\tsign\ton\tor\trefresh\ttheir browser. How\tcan\tyou\ttell\twhich\tmessages\tare\told\tand\twhich\tare\tnew?\tEach\tmessage\thas\ta\tdata attribute\tthat\tcan\thelp\tyou\ttell\twhether\tit\tis\tmore\tthan\ta\tsecond\tor\ttwo\told.","Silver\tChallenge:\tCaching\tMessages If\tyou\tare\tin\tthe\tmiddle\tof\ta\tchat\tand\tneed\tto\treload\tthe\tbrowser,\tall\tof\tyour\tmessages disappear.\tIt\tis\tnice\tthat\tyour\tUserStore\tremembers\tyour\tusername\t\u2013\tbut\tit\twould\tbe better\tif\tyou\talso\thad\ta\tsimilar\tmechanism\tfor\tcaching\tchat\tmessages. Create\ta\tMessageStore\tthat\tsubclasses\tStore.\tIt\tshould\tstore\tmessages\tas\tthey\tcome in,\tmaking\tsure\tnot\tto\tstore\tthe\tsame\tmessage\tmore\tthan\tonce. When\tthe\tpage\tloads,\tChattrbox\tshould\tget\tany\tcached\tmessages\tfrom\tMessageStore. Decide\tif\tyou\twould\tlike\tmessages\tto\tpersist\teven\tif\tthe\tbrowser\ttab\tis\tclosed\tand\tre- opened.\t(If\tso,\twhat\talternative\tto\tsessionStorage\twould\tyou\tuse?)","Gold\tChallenge:\tSeparate\tChat\tRooms This\tchallenge\twill\trequire\tyou\tto\tmodify\tboth\tthe\tserver\tand\tclient\tapplications. Add\tseparate\tchat\trooms\tfor\tyour\tusers.\tAfter\tthey\tenter\ttheir\tusername,\tprompt\tthem\tto enter\tthe\tname\tof\tthe\tchat\troom\tthey\twould\tlike\tto\tuse. When\tusers\tare\tlogged\tin\tto\ta\tchat\troom,\tthey\tshould\tonly\treceive\tmessages\tfor\tthat\troom over\tthe\tWebSocket\tconnection.\tYou\tmight\tneed\tto\tchange\thow\tyou\tstore\tmessages\ton\tthe server,\thow\tyou\tsend\tmessages\tto\tthe\tclient,\tor\tboth. For\tan\tadded\tchallenge,\tshow\ta\tdropdown\tof\tavailable\tchat\trooms\tin\tthe\tclient\tUI\tso\tthat users\tcan\tswitch\tfrom\troom\tto\troom.\tWhen\tchanging\tto\tanother\troom,\tmake\tsure\tthat\tany new\tmessages\tare\treceived\tfrom\tthe\tserver\tand\tdisplayed\tin\tthe\tchat\tlist.","Part\tIV\t Application\tArchitecture","19\t Introduction\tto\tMVC\tand\tEmber Model-View-Controller\t(MVC)\tis\tan\textremely\tuseful\tsoftware\tdesign\tpattern.\tIt\tworks well\tin\tweb\tapplications,\tallowing\tyou\tto\tbuild\tstructure\tin\tseparate\tlayers.\tThis\tchapter introduces\tthe\tMVC\tpattern\tand\twalks\tyou\tthrough\tinstalling\tand\tsetting\tup\tEmber,\ta framework\tfor\tMVC.\tThe\tnext\tfew\tchapters\tfocus\ton\tindividual\tpieces\tof\tthe\tpattern\tas you\tcreate\ta\tnew\tapplication\tlayer\tby\tlayer. There\tare\tmany\tinterpretations\tof\tMVC,\tespecially\tin\tthe\tfront-end\tworld.\tFigure\t19.1 shows\tthe\tinterpretation\twe\twill\tuse. Figure\t19.1\t\tThe\tModel-View-Controller\tpattern Here\tis\ta\tbreakdown\tof\twhat\teach\tlayer\tdoes: \tModels\tmanage\tdata.\tWhen\tdata\tchanges,\tthe\tmodel\ttells\tanyone\twho\tis listening. \tViews\tmanage\tthe\tuser\tinterface.\tThey\thandle\tthe\tpresentation\tof\tmodels\tand listen\tfor\tany\tchanges.\tAlso,\twhen\tUI\tevents\tfire\tin\tresponse\tto\tuser\tinput,\tthey call\thandler\tfunctions\tin\tthe\tcontroller. \tControllers\thold\tapplication\tlogic.\tThey\tretrieve\tmodel\tinstances\tand\tgive\tthem to\tviews.\tThey\talso\tcontain\thandler\tfunctions\tthat\tmake\tchanges\tto\tmodel","instances. If\tthis\tseems\tcircular,\tit\tis.\tThe\tthree\tpieces\twork\ttogether.\tApplication\tdata\tflows\tfrom the\tmodels\tto\tthe\tview.\tEvent\tdata\tflows\tfrom\tthe\tview\tto\tthe\tcontroller.\tControllers trigger\tdata\tchanges\tin\tthe\tmodel\tbased\ton\tUI\tevents. You\tmay\tbe\twondering\thow,\tthen,\tyou\tget\tinto\tthe\tcircular\tpattern\tof\tMVC.\tIn\tChapter\t8, you\tcreated\tthe\tCoffeeRun\tapplication\tand\tenclosed\tall\tthe\tfunctionality\tyou\tneeded\tin\tthe Window.App\tobject.\tEach\tadded\tmodule\thad\ta\tspecific\trole\tin\tthe\tapplication\tand\twas named\tfor\tits\tfunctionality.\tThe\tMVC\tpattern\tneeds\tan\tinitial\tset-up\tfunction,\tlike\tcreating a\tnew\tTruck,\tto\tload\tcontrollers.\tControllers,\tin\tturn,\tload\tmodels\tand\tviews. Your\tnext\tapplication,\tcalled\tTracker,\twill\tload\tan\tinitial\tDOM\tstate\tin\tan\tHTML\tfile\tas only\tan\tempty\t<body>\ttag.\tThe\tscripts\tto\tinitialize\tyour\tapplication\twill\tbe\tloaded\tfrom this\tHTML\tfile\tas\twell.\tIn\tthe\tMVC\tpattern,\tviews\t(HTML\tcontent)\tare\tdynamically rendered\tdepending\ton\tthe\troute\tand\tstate\tof\tthe\tdata\t(models). The\tapplication\tyou\tare\tgoing\tto\tbuild\twill\trequire\tmore\tthan\tCoffeeRun\u2019s\tseven\tmodules. The\tMVC\tpattern\thelps\tyou\tbreak\tup\tmodules\tinto\tfunctionality-specific\tfiles\tand maintain\tconsistent\torganization\t\u2013\twhether\tyou\thave\ta\tdozen\tmodules\tor\ta\thundred. Tracker Your\tTracker\tapplication\twill\tinclude\tURL\trouting,\tone\tof\tthe\tbest\tfeatures\tof\tweb applications.\tIt\twill\thave\tmodels\tto\tdefine\tthe\tdata,\tcontrollers\tto\thandle\tuser\tactions, templates\tto\tdefine\tthe\tUI,\tand\troutes\tto\tassign\tthe\tmodels\tto\tthe\ttemplates.\tAs\tyou\tbuild the\tapplication,\tyou\twill\tpick\tup\tsome\tnew\tpatterns\tand\ttechniques\tthat\twill\tmake\tyour code\tlightweight\tand\telegant. Your\tcustomer\tfor\tthe\tTracker\tapplication\tis\ta\tcryptozoologist,\ttraveling\tthe\tworld\tin search\tof\tanimals\tlike\tBigfoot,\tchupacabras,\tthe\tLoch\tNess\tMonster,\tand\tunicorns.\tThis client\twants\tan\tapp\tfor\ttracking\tthese\tmysterious\tcreatures\tand\trecording\tinformation about\tany\tsightings.\tThe\trequirements\tmay\tchange\t(and\tthey\tusually\tdo),\tbut\tto\tbegin with\tthe\tuser\tshould\tbe\table\tto: list\texisting\tsightings add\tnew\tsightings link\tcreatures\tto\tsightings see\tthe\tlatest\tsightings\tvia\tflash\tmessages Each\tsighting\tshould\thave\tthe\tfollowing\tmodel\tdata\tand\tassociations: Sighting\tmodel\tattribute Attribute\ttype date\tcreature\twas\tseen date\tobject location string","creature creature\tmodel\tkey witness(es) array\tof\twitness\tkeys Each\tcreature\tshould\thave\tthe\tfollowing\tmodel\tdata: Creature\tmodel\tattribute Attribute\ttype name string type string image\tpath string And\teach\twitness\tshould\thave\tthe\tfollowing\tmodel\tdata\tand\tassociations: Witness\tmodel\tattribute Attribute\ttype first\tname string last\tname string full\tname string:\tconcatenation\tof\tfirst\tname\tand\tlast\tname email string sighting(s) array\tof\tsighting\tkeys Building\tTracker\twill\tbe\tslightly\tdifferent\tthan\tbuilding\tthe\tprevious\tapplications\tin\tthis book.\tIt\twill\tmore\tclosely\tresemble\treal-world\tapplication\tdevelopment.\tThere\twill\tbe more\tcode\tin\tsections\tand\tless\tinstant\tfeedback.\tHowever,\tyou\twill\tget\ta\trealistic\tsense\tof app\tdevelopment\tand\twill\tbuild\ta\tsatisfyingly\tcomplex\tapp\t(Figure\t19.2).","Figure\t19.2\t\tFinished\tTracker\tapp","Ember:\tAn\tMVC\tFramework As\tyou\tbuild\tTracker,\tyou\twill\tlearn\tthe\tbasic\tpattern\tof\tweb\tapplication\tdevelopment using\tEmber,\tone\tof\tthe\tleading\tMVC\tframeworks.\tEmber\tincorporates\tconcepts\tand naming\tconventions\tthat\tallow\tfor\trapid\tdevelopment.\tAs\tyou\tbuild\tyour\tapplication,\tyou will\tlearn\tthe\tEmber\tfundamentals. As\tdescribed\ton\tEmber\u2019s\thomepage\t(emberjs.com),\tEmber\tis\t\u201ca\tframework\tfor\tbuilding ambitious\tweb\tapplications.\u201d\tIn\tcontrast\tto\ta\tlibrary\tlike\tjQuery,\ta\tframework\tlike\tEmber informs\tyour\tapp\u2019s\tstructure\tand\toften\tincludes\tscaffolding\ttools,\twhich\tare\tscripts\tto create\tboilerplate\tfiles\tin\tthe\tcorrect\tdirectories.\tSince\tits\tinception\tin\t2011,\tthe\tEmber community\thas\tbeen\tbuilding\ta\tdiverse\tecosystem\tof\tlibraries\tand\ttooling\tto\taccelerate development. You\twill\tstart\tyour\tEmber\tjourney\twith\tEmber\tCLI,\tEmber\u2019s\ttool\tfor\tscaffolding,\tdevelopment, testing,\tand\tbuilding.\tIf\tyou\tare\tnot\tfamiliar\twith\tthe\tterm\tCLI,\tit\tstands\tfor\t\u201ccommand- line\tinterface.\u201d\tYou\twill\tcreate\ta\tnew\tproject,\tload\tdependencies,\tgenerate\tyour\tEmber objects,\tand\tbuild\tand\trun\tyour\tTracker\tapplication\tfrom\tthis\ttool. Installing\tEmber To\tget\tstarted,\tyou\twill\tneed\tto\tinstall\tsome\ttools. First,\tmake\tsure\tyou\tare\tusing\tthe\tlatest\tversion\tof\tNode.js\t(>0.12.0).\tYou\tcan\tcheck\tyour version\twith\tthe\tterminal\tcommand\tnode\t--version.\tAt\tthe\ttime\tof\tthis\twriting,\tNode\tis\tat version\t5.5.0.\t(Yeah,\tthat\tis\ta\tlarge\tdifference\tfrom\tthe\tminimum\trequirement\tof\t0.12.0. For\tmore\ton\tthe\thistory\tof\twhen\tand\twhy\tNode\tjumped\tfrom\tversion\t0.12.0\tto\t4.0.0,\tcheck out\tWikipedia\u2019s\tarticle\tat\ten.wikipedia.org\/w\u200b iki\/\u200bNode.js.) If\tnecessary,\tdownload\tan\tupdated\tversion\tof\tNode.js\tfrom\tnodejs.org. Once\tNode\tis\tup\tto\tdate,\tyou\tare\tready\tto\tinstall\tEmber\tCLI\tusing\tthe\tfollowing\tterminal command: npm\tinstall\t-g\[email protected] The\tinstallation\tmay\ttake\ta\tfew\tminutes.\tIf\tyou\tget\tthis\terror:\tPlease\ttry\trunning\tthis command\tagain\tas\troot\/Administrator,\tthen\tthere\tis\tan\tissue\twith\towner\tpermissions. Do\tnot\trerun\tthe\tinstall\tcommand\twith\tsudo,\tas\tnpm\tand\tsudo\tdo\tnot\tplay\twell\ttogether. Instead,\trun\tthis\tcommand:\tsudo\tchown\t-R\t$USER\t\/usr\/local.\tThen\trerun\tthe\toriginal install\tcommand\t(without\tsudo). You\tmay\tget\tother\terrors\twhen\tyou\tinstall\tEmber\tCLI\tthat\thave\tto\tdo\twith\tincompatibility with\tyour\texisting\tsystem.\tMost\terrors\thave\tinstructions\tfor\trepairing\tthe\tinstall\tprocess. Some\tinstall\terrors\twill\trequire\tbasic\tinternet\tsearches\tto\tupdate\texisting\tprograms running\ton\tyour\tcomputer.\tIf\tyou\tneed\tmore\tinformation,\tthe\tEmber\tCLI\twebsite\thas\ta\tpage for\tcommon\tissues\tat\tember-cli.com\/\u200buser-guide\/\u200b#commonissues. Next,\tinstall\tBower,\tanother\tasset\tmanagement\ttool.","npm\tinstall\t-g\tbower Bower\tand\tnpm\tare\trequired\tto\tcreate\tan\tEmber\tapplication. Next,\tinstall\tthe\tEmber\tInspector\tplug-in\tfor\tChrome.\tTo\tdo\tthis,\topen\tChrome\tand,\tin\tthe address\tbar,\tenter\tchrome:\/\/extensions\/.\tAt\tthe\tbottom\tof\tthe\textension\tpage,\tclick\tGet more\textensions.\tSearch\tfor\t\u201cEmber\tInspector\u201d\t(Figure\t19.3),\tclick\tAdd\tto\tChrome,\tand\tfollow the\tprompts\tto\tinstall\tthe\textension. Figure\t19.3\t\tInstalling\tthe\tEmber\tInspector\textension\tfor\tChrome Ember\tCLI\tuses\ta\tprogram\tcalled\tWatchman\twhen\tit\tis\trunning.\tWatchman\tis\ta\tcommand-line tool\tthat\tintegrates\twith\tbrowsers\tto\tenable\tlive\treload\tof\tapplications. On\ta\tMac,\tyou\tcan\tinstall\tWatchman\tvia\tHomebrew.\tHomebrew,\ta\tpackage\tmanager\tfor\tOS\tX, can\tbe\tdownloaded\tusing\ta\tterminal\tcommand\tyou\tcan\tcopy\tfrom\tits\twebsite,\tbrew.sh. Once\tHomebrew\tis\tinstalled,\tinstall\tWatchman\t(version\t3.0.0\tor\tgreater)\twith\tthis\tterminal command: brew\tinstall\twatchman Instructions\tfor\tinstalling\tWatchman\ton\tWindows\tcan\tbe\tfound\tat facebook.github.io\/\u200bwatchman\/\u200bdocs\/\u200binstall.html With\tthat,\tyou\thave\tthe\ttools\tyou\tneed\tto\tbegin\tyour\tEmber\tproject,\tTracker. Creating\tan\tEmber\tapplication Ember\u2019s\temphasis\ton\tconventions\tand\tpatterns\tallows\tyou\tto\tcreate\tan\tapplication\twith minimal\tcode.\tThe\tframework\tdoes\ta\tlot\tof\tthe\twork\tbehind\tthe\tscenes,\tgenerating\ta number\tof\tobjects\tand\tevents\twhen\tyour\tapplication\tstarts.\tAs\tyou\tbuild\tout\tmore\tof\tthe Tracker\tapp,\tyou\twill\tuse\tyour\town\tobjects\tin\tplace\tof\tthe\tones\tEmber\tcreated\tfor\tyou. In\tthe\tterminal,\tnavigate\tto\tyour\tprojects\tfolder.\tThe\tcommand\tember\tnew\t[project name]\twill\tcreate\ta\tdirectory\tand\twill\tscaffold\tall\tthe\tnecessary\tfiles\tto\tstart\tdeveloping. Create\ta\tnew\tEmber\tapp\tcalled\ttracker: ember\tnew\ttracker","Creating\ta\tnew\tEmber\tapplication\tmay\ttake\ta\tfew\tminutes.\tAs\tyou\tcan\tsee\tfrom\tthe terminal\toutput,\tsome\tof\twhich\tis\tshown\tbelow,\tthe\tember\tnew\tcommand\tcreates\tthe\tbase project\tfiles\tand\tdirectory\tstructure.\tAlso,\tit\tuses\tnpm\tand\tBower\tto\tload\texternal\tlibrary assets.\tThese\tlibraries\tare\tessential\tto\trunning\tan\tEmber\tapplication\tand\talso\tto\trunning\tthe server\tto\tcompile,\tbuild,\tand\ttest\tyour\tapplication. installing\tapp \t\tcreate\t.bowerrc \t\tcreate\t.editorconfig \t\tcreate\t.ember-cli \t\tcreate\t.jshintrc \t\tcreate\t.travis.yml \t\tcreate\t.watchmanconfig \t\tcreate\tREADME.md \t\tcreate\tapp\/app.js \t\tcreate\tapp\/components\/.gitkeep \t\tcreate\tapp\/controllers\/.gitkeep \t\tcreate\tapp\/helpers\/.gitkeep \t\tcreate\tapp\/index.html \t\tcreate\tapp\/models\/.gitkeep \t\tcreate\tapp\/router.js \t\tcreate\tapp\/routes\/.gitkeep \t\tcreate\tapp\/styles\/app.css \t\tcreate\tapp\/templates\/application.hbs \t\t.\t.\t. Successfully\tinitialized\tgit. Installed\tpackages\tfor\ttooling\tvia\tnpm. Installed\tbrowser\tpackages\tvia\tBower. When\tEmber\thas\tfinished\tsetting\tup\tthe\tTracker\tapp,\tverify\tthat\teverything\tis\tworking\tby starting\ta\tlocal\tserver. Starting\tup\tthe\tserver In\ta\tmoment,\tyou\tare\tgoing\tto\tuse\tthe\tcommand\tember\tserver\t(or\tember\ts,\tfor\tthose looking\tto\tsave\ta\tfew\tkeystrokes)\tto\tbuild\tyour\tapplication\tand\tstart\ta\tserver\tso\tthat\tyou can\taccess\tit\tlocally.\tAs\ta\tconvenience,\tember\tserver\twatches\tyour\tfiles\tfor\tchanges\tand restarts\tthe\tbuild\/serve\/watch\tprocess\tto\tmake\tsure\tyou\tonly\tsee\tthe\tlatest\tcode\tin\tthe browser\t(much\tlike\tthe\tbrowser-sync\ttool\tyou\tused\tin\tOttergram\tand\tCoffeeRun). Ember\tCLI\tuses\tthe\tBroccoli\tprogram\tfor\tcompilation.\tIf\tyou\thave\tprogrammed\tin\tlanguages like\tJava\tor\tObjective-C,\tyou\tmay\tthink\tof\t\u201ccompilation\u201d\ta\tbit\tdifferently\tthan\twhat\tit means\tin\tJavaScript.\tIn\tthis\tcase,\tBroccoli\tcombines\tall\tof\tthe\tJavaScript\tfiles\tneeded\tto\trun your\tapplication,\twhile\tensuring\tthat\tall\tdependencies\tare\tmet. It\tis\ttime\tto\tfight\tfor\tthe\tuser.\tChange\tdirectories\tinto\tthe\tTracker\tfolder\tand\tstart\tup\tthe server: cd\ttracker ember\tserver In\tChrome,\topen\ta\tnew\tbrowser\twindow\tand\tgo\tto\thttp:\/\/localhost:4200\tto\tsee\tyour new\tEmber\tapp\tin\taction\t(Figure\t19.4).\tYou\twill\talso\twant\tto\topen\tthe\tEmber\ttab\tin\tthe DevTools,\tas\tshown.","Figure\t19.4\t\tEmber\tserver As\tmentioned\tabove,\tEmber\tCLI\twill\treload\tthe\tbrowser\tpage\twhen\tyou\tmake\tchanges\tto the\tapplication\tfiles.\tThis\tis\tcalled\tLivereload,\tand\tyou\twill\tsee\tit\tmentioned\tin\tthe terminal\toutput\tas: Livereload\tserver\ton\thttp:\/\/localhost:49152 In\tFigure\t19.4,\tnotice\tthat\tboth\tthe\tconsole\tand\tEmber\tInspector\tlist\tvarious\tcomponents\tthat were\tgenerated\tfor\tyou,\talong\twith\ttheir\tversions.\tThis\tbook\tuses\tversion\t2.4\tof\tboth Ember\tand\tEmber\tData.\tAt\tthe\ttime\tof\tpublication,\tEmber\tCLI\tgenerates\ta\t2.x\tEmber application,\tas\tyou\tcan\tsee\tby\tthe\tversion\tnumbers\tin\tthe\tfigure.\tIf\tyou\tsee\tversion numbers\tstarting\twith\t1.x.x,\tyou\tmay\thave\tskipped\tthe\tstep\tto\tinstall\tor\tupdate\tEmber-CLI. (Note\tthat\tthe\tVersion\tyou\tsee\tafter\tstarting\ta\tserver\tin\tthe\tterminal\tis\tthe\tversion\tof\tEmber CLI,\tnot\tthe\tversion\tof\tthe\tactual\tEmber\tapp\tyou\tare\tlaunching.)","External\tLibraries\tand\tAddons Ember\tCLI\tis\tset\tup\tto\toffer\tdevelopers\tspeed\tin\tmany\tways,\tincluding\tadding\tcode\tfrom\tthe open-source\tcommunity.\tIn\tprevious\tchapters,\tyou\tadded\tnode\tmodules\tto\tyour\tlocal environment\tvia\tnpm.\tEarlier\tin\tthis\tchapter\twe\tdiscussed\tloading\texternal\tlibraries\tvia Bower,\tanother\tpackage\tmanager. Ember\tCLI\tworks\twell\twith\tboth\tof\tthese\tpackage\tmanagers.\tInstalling\tlibraries\tor\ttools\tis done\twith\tsimple\tcommands\tlike: npm\tinstall\t[package\tname]\t--save-dev npm\tinstall\t[package\tname]\t--save bower\tinstall\t[package\tname]\t--save When\tusing\texternal\tlibraries,\tthese\tcommand-line\ttools\tload\tfiles\tto\tthe\tdirectories bower_components\tand\tnode_modules. You\tused\tBootstrap\tfor\tCoffeeRun,\tand\tyou\tare\tgoing\tto\tstart\tTracker\twith\tit\tas\twell.\tTo\tadd Bootstrap\twith\tBower,\tenter\tthe\tfollowing\tcommand\tin\tthe\tterminal: bower\tinstall\tbootstrap-sass\t--save You\thave\tnow\tloaded\tthe\tBootstrap\tlibrary\tlocally,\twith\tall\tits\tJavaScript\tand\tstyle\tfiles. You\twill\troll\tthis\tlibrary\tinto\tthe\tEmber\tCLI\tbuild\tprocess\tso\tthat\tyou\tcan\tship\tyour application\twith\tthe\tBootstrap\tassets\tyour\tapplication\tneeds. The\tmodern\tweb\tworkflow\tfor\tdeveloping\tscripts\tand\tstyles\tincludes\tcompilation.\tYou\tare going\tto\tadd\ta\ttool\tto\thelp\treduce\tthe\tcomplexity\tof\tcompilation:\tember-cli-sass\twill\thandle converting\tSCSS\tstylesheets\tto\tCSS\tduring\tthe\tEmber\tCLI\tbuild\tprocess.\tSCSS,\tcommonly referred\tto\tas\t\u201cSass,\u201d\tadds\tmany\tfamiliar\tprogrammatic\tconstructs\tto\tyour\tstylesheets\tlike variables,\tfunctions,\tloops,\tand\tkey\/value\tpairs\t\u2013\twithout\tlosing\tthe\tCSS\tsyntax\tyou\tknow and\tlove. Install\tember-cli-sass\tfrom\tthe\tterminal: ember\tinstall\tember-cli-sass ember-cli-sass\tis\tan\texample\tof\tan\tEmber\taddon.\tAddons\t(www.emberaddons.com)\tare projects\tthat\thave\tadded\texternal\tlibraries\tor\tconfiguration\tcode,\tcreated\thelpers\tor components,\tor\tdone\tsome\tother\ttype\tof\theavy\tlifting\tfor\tyou.\tEmber\tCLI\tmakes\tit\teasy\tto add\tthese\texisting\tprojects\tto\tyour\tproject\twith\tthe\tember\tinstall\tcommand. Note\tthat\tEmber\tCLI\tis\ta\trelatively\tnew\ttool,\tand\taddons\tcan\tbe\tout\tof\tsync.\tIf\tyou\trun\tinto problems\twith\ta\tparticular\taddon,\tvisit\tthe\tissues\tpage\tat\tthe\taddon\u2019s\tGitHub\trepository. You\thave\tjust\tadded\tthe\tability\tto\tcompile\tSCSS\tfiles.\tNow\tyou\tneed\tto\tchange\tthe app\/styles\/app.css\tto\tbe\ta\t.scss\tfile.\tRename\tapp\/styles\/app.css\tto app\/styles\/app.scss.\tRestart\tthe\tEmber\tserver\twhen\tyou\tare\tdone\tso\tthe\tnew\tCLI tools\tinitialize. To\ttest\tthe\tnew\tCLI\ttool,\tyou\tare\tgoing\tto\tadd\ta\tSCSS\tvariable\tto\tyour\tstylesheet.\tWith\ta $\tas\ta\tprefix,\tcreate\ta\tname\/value\tpair\tto\ttest\tthe\tSCSS\tcompilation\tin app\/styles\/app.scss: $bg-color:\tcoral; html\t{","background:\t$bg-color; } Check\tyour\tbrowser.\tYour\tpage\tnow\tdisplays\ta\tbackground\tcolor\t(Figure\t19.5). Figure\t19.5\t\tCompiling\tSCSS:\ttest Next,\tyou\twill\tadd\tBootstrap\tstyles\tand\tscripts\tto\tyour\tproject.\tEarlier\tyou\tadded\tthe\tSCSS version\tof\tBootstrap\tvia\tbower\tinstall\tbootstrap-sass.\tTo\tadd\tthe\tlibrary\tto\tyour stylesheet,\tyou\twill\tneed\tto\timport\tthe\tstyle\tlibrary\tinto\tyour\tfile\tand\tconfigure\tEmber\tCLI to\tbuild\tyour\tapplication\twith\tthose\tassets.","Configuration Broccoli,\tthe\tcompilation\tengine\twe\tmentioned\tearlier,\trequires\tsome\tconfiguration\twhen adding\tnew\tJavaScript\tand\tstylesheet\tassets. Ember-CLI\tgenerates\ta\tconfiguration\tfile\tnamed\tember-cli-build.js.\tThis\tfile\tis where\tyou\tcan\tinject\tdependencies\tand\tconfigure\tthe\toutput\tstructure\tof\tyour\tapplication. For\tTracker,\tyou\twill\tonly\tbe\tadding\texternal\tlibraries\tand\tsettings\tfor\tSCSS\tcompilation. Open\tember-cli-build.js,\tassign\ta\tvariable\tto\tthe\tdirectory\tpath\tto\tbootstrap, and\tadd\tBootstrap\u2019s\tstylesheets\tdirectory\tto\tthe\tkey\tincludePaths\tin\tthe sassOptions: ... var\tEmberApp\t=\trequire('ember-cli\/lib\/broccoli\/ember-app'); module.exports\t=\tfunction(defaults)\t{ \t\tvar\tbootstrapPath\t=\t'bower_components\/bootstrap-sass\/assets\/'; \t\tvar\tapp\t=\tnew\tEmberApp(defaults,\t{ \t\t\t\t\/\/\tAdd\toptions\there \t\t\t\tsassOptions:\t{ \t\t\t\t\t\tincludePaths:\t[ \t\t\t\t\t\t\t\tbootstrapPath\t+\t'stylesheets' \t\t\t\t\t\t] \t\t\t\t} \t\t}); \t\t\/\/\t...\tTemplate\tcomments\t... \t\t\/\/\tCreate\tpaths\tto\tbootstrap\tassets \t\t\/\/\tAdd\tassets\tto\tapp\twith\timport \t\tapp.import(bootstrapPath\t+\t'javascripts\/bootstrap.js'); \t\treturn\tapp.toTree(); }; You\thave\tadded\ta\tconfiguration\tfor\tEmber\tCLI\tto\tlook\tfor\t*.scss\tfiles\tin\tthe bower_components\/bootstrap-sass\/assets\/stylesheets\tdirectory.\tSave your\tfile\tand\trestart\tthe\tEmber\tserver\tso\tthe\tnew\tconfiguration\tcan\tload\twith\tthe application. You\tcan\tnow\tuse\tthe\t@import\tdirective\tin\tyour\tapp.scss\tfile\tto\timport\tBootstrap\u2019s\tstyles: $bg-color:\tcoral; html\t{ \t\tbackground:\t$bg-color; } \/\/\t---------------------------- \/\/\tbootstrap\tvariable\toverrides \/\/\t---------------------------- \/\/\tend\tbootstrap\tvariable\toverrides @import\t'bootstrap'; The\t@import\tdirective\tadds\tthe\tcontents\tof\tbootstrap.scss\tto\tapp.scss,\twhich will\tbe\tcreated\tby\tthe\tEmber\tCLI\tbuild\tprocess.\tBootstrap\u2019s\tfile\tis\tfound\tin\tthe\tdirectory bower_components\/bootstrap-sass\/assets\/stylesheets\/. In\tember-cli-build.js,\tyou\tadded\tBootstrap\u2019s\tJavaScript\tcomponents\tto\tthe application\tbuild\tprocess\twith\tapp.import(bootstrapPath\t+ 'javascripts\/bootstrap.js');.\tAn\timport\tin\tthe\tCLI\tbuild\tconfiguration\tadds\tthe\tfile to\tthe\tlist\tof\tassets\tto\tbe\tconcatenated\tinto\ta\tsingle\tdist\/assets\/vendor.js\tfile. Bootstrap\u2019s\tbootstrap.js\thas\tindividual\tJavaScript\tmodules\tfor\tcollapsing\tDOM\telements, modals,\ttabs,\tdropdowns,\tand\tmany\tothers\tall\tin\ta\tsingle\tfile.\tAdding\tall\tthe\tJavaScript","components\tis\tprobably\toverkill,\tbut\tin\tthe\tfuture\tyou\tcan\ttweak\tyour\tember-cli- build.js\tconfiguration\tto\tonly\tadd\tthe\tspecific\tcomponents\tyou\tneed. After\tyou\tadd\tassets,\tyou\tshould\talways\tmake\tsure\tthey\tare\tworking\tbefore\tyou\tmove forward.\tIn\tthe\tapp\tdirectory,\tthere\tis\tan\tindex.html\tfile\t\u2013\tbut\tthis\tis\tnot\tthe\tplace\tto test\tyour\tnew\tBootstrap\tcode.\tThis\tfile\tis\tmainly\tfor\tthe\tbuild\tprocess. Instead,\tall\tof\tyour\tHTML\telements\twill\tbe\tadded\tto\tapplication\ttemplates,\tin\tthe app\/templates\tdirectory.\tYou\twill\tlearn\tabout\ttemplates\tin\tgreater\tdetail\tin Chapter\t23. For\tnow,\tadd\ta\tBootstrap\tNavBar\tcomponent\tto\tapp\/templates\/application.hbs: <h2\tid=\\\"title\\\">Welcome\tto\tEmber<\/h2> {{outlet}} <header> \t\t<nav\tclass=\\\"navbar\tnavbar-default\\\"> \t\t\t\t<div\tclass=\\\"container-fluid\\\"> \t\t\t\t\t\t<!--\tBrand\tand\ttoggle\tget\tgrouped\tfor\tbetter\tmobile\tdisplay\t--> \t\t\t\t\t\t<div\tclass=\\\"navbar-header\\\"> \t\t\t\t\t\t\t\t<button\ttype=\\\"button\\\"\tclass=\\\"navbar-toggle\tcollapsed\\\" \t\t\t\t\t\t\t\t\t\tdata-toggle=\\\"collapse\\\"\tdata-target=\\\"#top-navbar-collapse\\\"> \t\t\t\t\t\t\t\t\t\t<span\tclass=\\\"sr-only\\\">Toggle\tnavigation<\/span> \t\t\t\t\t\t\t\t\t\t<span\tclass=\\\"icon-bar\\\"><\/span> \t\t\t\t\t\t\t\t\t\t<span\tclass=\\\"icon-bar\\\"><\/span> \t\t\t\t\t\t\t\t\t\t<span\tclass=\\\"icon-bar\\\"><\/span> \t\t\t\t\t\t\t\t<\/button> \t\t\t\t\t\t\t\t<a\tclass=\\\"navbar-brand\\\">Tracker<\/a> \t\t\t\t\t\t<\/div> \t\t\t\t\t\t<!--\tCollect\tthe\tnav\tlinks,\tforms,\tand\tother\tcontent\tfor\ttoggling\t--> \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<\/ul> \t\t\t\t\t\t<\/div><!--\t\/.navbar-collapse\t--> \t\t\t\t<\/div><!--\t\/.container-fluid\t--> \t\t<\/nav> <\/header> <div\tclass=\\\"container\\\"> \t\t{{outlet}} <\/div> You\thave\tadded\tBootstrap\u2019s\tNavBar\tcomponent\twith\tspecific\tHTML\tattributes:\tIDs,\tclass names,\tand\tdata\tattributes.\tAlso,\tthe\texisting\t{{outlet}}\thas\tbeen\tmoved\tfrom\tthe\tmain containing\telement\tto\tinside\ta\t<div>\telement.\tThis\tpiece\tof\tcode\tis\thow\ttemplates\tnest child\ttemplates.\tYou\twill\tlearn\tmore\tabout\tthe\t{{outlet}}\tin\tthe\tnext\tchapter. The\tresult\tof\tyour\tcode\tis\tshown\tin\tFigure\t19.6.","Figure\t19.6\t\tBootstrap\tNavBar The\tNavBar\tcomponent\tis\tresponsive\tand\tshows\ta\tcollapse\tbutton\twhen\tthe\tbrowser window\tis\tless\tthan\t768px\twide.\tThis\tbutton\tresponds\tto\tclick\tevents\tby\topening\tand closing\tthe\tlist\tof\tlinks\t(Figure\t19.7).\tThe\tevent\tlistener\tsetup\tfor\tthe\tcollapse\tfeature\tis the\tcode\twritten\tin\tthe\tbootstrap.js\tfile.","Figure\t19.7\t\tTesting\tBootstrap\tNavBar\u2018s\tcollapse\tcomponent Congratulations\t\u2013\tyou\thave\tan\tEmber\tapp\tup\tand\trunning!\tYou\tinstalled\ttools\tto\tgenerate code,\tcompile\tassets,\tload\tdependencies,\tand\tserve\tthe\tapp.\tYou\tnow\thave\ta\tsolid\tstarting point\tfor\tbuilding\tthe\trest\tof\tyour\tapp\tin\tupcoming\tchapters.","For\tthe\tMore\tCurious:\tnpm\tand\tBower\tInstall The\toptions\t--save-dev\tand\t--save\tat\tthe\tend\tof\tthe\tcommands\tnpm\tinstall\tand\tbower install\tadd\tkey\/value\tpairs\tof\tlibrary\tnames\tand\tversions\tto\ta\tJSON\tfor\teach\ttool.\tIn\tthe case\tof\tBower,\tthe\tJSON\tis\tbower.json;\tfor\tnpm\t\u2013\tas\tyou\tsaw\tin\tChattrbox\t\u2013\tit\tis package.json. For\texample,\tin\tbower.json\tthe\tkey\/value\tpairs\tadded\tin\tthis\tchapter\twere: { \t\t\\\"name\\\":\t\\\"tracker\\\", \t\t\\\"dependencies\\\":\t{ \t\t\t\t\\\"ember\\\":\t\\\"~2.4.3\\\", \t\t} } The\tfile\tbower.json\tlists\tthe\tdependency\tember.js\twith\tits\tminimum\tversion\tnumber. The\tlibraries\tand\tassets\tlisted\twill\tnot\tbe\tsaved\tto\tyour\tdevelopment\tproject\trepository\tor version\tcontrol\tsystem,\tonly\tthe\tbower.json\tfile.\tA\tdeveloper\twho\tchecks\tout\tthe\tcode can\trun\tbower\tinstall\tand\tnpm\tinstall\tto\tcreate\ta\tlocal\tenvironment\tfor\tdevelopment.","Bronze\tChallenge:\tLimiting\tImports Change\tember-cli-build.js\tto\tonly\timport\tthe\tcollapse.js\tand transition.js\tfiles.\tWhen\tyou\tdo\tthis,\tyour\tvendor.js\twill\tbe\tsmaller\tin\tsize\tand your\tNavBar\tcomponent\twill\tstill\twork. Before\tyou\tmake\tany\tchanges,\tfind\tdist\/assets\/vendor.js\tand\tnote\tthe\tnumber of\tlines\tof\tcode\t(or\tfile\tsize).\tMake\tthe\tchange\tand\tcompare\tthe\tnew\tfile\tsize.","Silver\tChallenge:\tAdding\tFont\tAwesome Font\tAwesome\tis\ta\tUI\tlibrary\tfor\tadding\tcommonly\tused\ticons\tto\tyour\tproject.\tThe\ticons can\tbe\tscaled,\tjust\tlike\ta\tfont.\tAdd\tFont\tAwesome\twith\tEmber\tCLI\taddons\tand\tadd\tan\ticon to\tapp\/templates\/application.hbs.\tCheck\tout\tthe\taddon\u2019s\tGitHub\trepository for\tmore\tinformation.","Gold\tChallenge:\tCustomizing\tthe\tNavBar Bootstrap\tis\twritten\tin\tSCSS\tand\tmakes\tliberal\tuse\tof\tvariables\tand\tfunctions.\tWhen\tyou use\tthe\tSCSS\tversion\tin\tyour\tproject,\tyou\tcan\tcontrol\thow\tthe\tlibrary\tcompiles\tits\tstyle rules.\tYou\tcan\teven\tcreate\tBootstrap\tthemes\tto\tmodify\tthe\tdefault\tvariables. Change\tthe\tbackground-color,\tborder-radius,\tand\tpadding\tvalue\tof\tthe\tNavBar\tby\tonly adding\tor\tchanging\tvariables\tin\tyour\tapp\/stylesheets\/app.scss."]


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