jquery design patterns · 2016. 5. 12. · table of contents jquery design patterns credits about...

Post on 04-Sep-2020

2 Views

Category:

Documents

0 Downloads

Preview:

Click to see full reader

TRANSCRIPT

jQueryDesignPatterns

TableofContents

jQueryDesignPatterns

Credits

AbouttheAuthor

AbouttheReviewer

www.PacktPub.com

eBooks,discountoffers,andmore

Whysubscribe?

Preface

Whatthisbookcovers

Whatyouneedforthisbook

Whothisbookisfor

Conventions

Readerfeedback

Customersupport

Downloadingtheexamplecode

Errata

Piracy

Questions

1.ARefresheronjQueryandtheCompositePattern

jQueryandDOMscripting

ManipulatingtheDOMusingjQuery

MethodChainingandFluentInterfaces

TheCompositePattern

HowtheCompositePatternisusedbyjQuery

ComparingthebenefitsovertheplainDOMAPI

UsingtheCompositePatterntodevelopapplications

Asampleusecase

TheCompositeCollectionImplementation

Anexampleexecution

Alternativeimplementations

TheIteratorPattern

HowtheIteratorPatternisusedbyjQuery

HowitpairswiththeCompositePattern

Wherecanitbeused

Summary

2.TheObserverPattern

IntroducingtheObserverPattern

HowitisusedbyjQuery

ThejQueryonmethod

Thedocument-readyobserver

Demonstrateasampleusecase

Howitiscomparedwitheventattributes

Avoidmemoryleaks

IntroducingtheDelegatedEventObserverPattern

Howitsimplifiesourcode

Comparethememoryusagebenefits

Summary

3.ThePublish/SubscribePattern

IntroducingthePublish/SubscribePattern

HowitdiffersfromtheObserverPattern

HowitisadoptedbyjQuery

CustomeventsinjQuery

ImplementingaPub/Subschemeusingcustomevents

Demonstratingasampleusecase

UsingPub/Subonthedashboardexample

Extendingtheimplementation

Usinganyobjectasabroker

Usingcustomeventnamespacing

Summary

4.DivideandConquerwiththeModulePattern

ModulesandNamespaces

Encapsulatinginternalpartsofanimplementation

AvoidingglobalvariableswithNamespaces

Thebenefitsofthesepatterns

Thewideacceptance

TheObjectLiteralPattern

TheModulePattern

TheIIFEbuildingblock

ThesimpleIIFEModulePattern

HowitisusedbyjQuery

TheNamespaceParameterModulevariant

TheIIFE-containedModulevariant

TheRevealingModulePattern

UsingES5StrictMode

IntroducingES6Modules

UsingModulesinjQueryapplications

Themaindashboardmodule

Thecategoriesmodule

TheinformationBoxmodule

Thecountermodule

Overviewoftheimplementation

Summary

5.TheFacadePattern

IntroducingtheFacadePattern

Thebenefitsofthispattern

HowitisadoptedbyjQuery

ThejQueryDOMTraversalAPI

ThepropertyaccessandmanipulationAPI

UsingFacadesinourapplications

Summary

6.TheBuilderandFactoryPatterns

IntroducingtheFactoryPattern

HowitisadoptedbyjQuery

UsingFactoriesinourapplications

IntroducingtheBuilderPattern

HowitisadoptedbyjQuery’sAPI

HowitisusedbyjQueryinternally

Howtouseitinourapplications

Summary

7.AsynchronousControlFlowPatterns

Programmingwithcallbacks

UsingsimplecallbacksinJavaScript

Settingcallbacksasobjectproperties

UsingcallbacksinjQueryapplications

Writingmethodsthatacceptcallbacks

Orchestratingcallbacks

Queuinginorderexecution

AvoidingtheCallbackHellanti-pattern

Runningconcurrently

IntroducingtheconceptofPromises

UsingPromises

UsingthejQueryPromiseAPI

UsingPromises/A+

ComparingjQueryandA+Promises

Advancedconcepts

ChainingPromises

Handlingthrownerrors

JoiningPromises

HowjQueryusesPromises

TransformingPromisestoothertypes

TransformingtoPromises/A+

TransformingtojQueryPromises

SummarizingthebenefitsofPromises

Summary

8.MockObjectPattern

IntroducingtheMockObjectPattern

UsingMockObjectsinjQueryapplications

Definingtheactualservicerequirements

ImplementingaMockService

UsingtheMockService

Summary

9.Client-sideTemplating

IntroducingUnderscore.js

UsingUnderscore.jstemplatesinourapplications

SeparatingHTMLtemplatesfromJavaScriptcode

IntroducingHandlebars.js

UsingHandlebars.jsinourapplications

SeparatingHTMLtemplatesfromJavaScriptcode

Pre-compilingtemplates

RetrievingHTMLtemplatesasynchronously

Adoptingitinanexistingimplementation

Moderationisbestinallthings

Summary

10.PluginandWidgetDevelopmentPatterns

IntroducingjQueryPlugins

FollowingjQueryprinciples

WorkingonCompositeCollectionObjects

Allowingfurtherchaining

Workingwith$.noConflict()

WrappingwithanIIFE

Creatingreusableplugins

Acceptingconfigurationparameters

WritingstatefuljQueryplugins

ImplementingastatefuljQueryPlugin

Destroyingaplugininstance

Implementinggetterandsettermethods

UsingourplugininourDashboardapplication

UsingthejQueryPluginBoilerplate

Addingmethodstoyourplugin

Choosinganame

Summary

11.OptimizationPatterns

Placingscriptsneartheendofthepage

Bundlingandminifyingresources

UsingIIFEparameters

UsingCDNs

UsingJSDelivrAPI

OptimizingcommonJavaScriptcode

Writingbetterforloops

WritingperformantCSSselectors

WritingefficientjQuerycode

MinimizingDOMtraversals

CachingjQueryobjects

Scopingelementtraversals

ChainingjQuerymethods

Don’toverdoit

ImprovingDOMmanipulations

CreatingDOMelements

Stylingandanimating

Manipulatingdetachedelements

IntroducingtheFlyweightPattern

UsingDelegateObservers

Using$.noop()

Usingthe$.singleplugin

LazyLoadingModules

Summary

Index

jQueryDesignPatterns

jQueryDesignPatternsCopyright©2016PacktPublishing

Allrightsreserved.Nopartofthisbookmaybereproduced,storedinaretrievalsystem,ortransmittedinanyformorbyanymeans,withoutthepriorwrittenpermissionofthepublisher,exceptinthecaseofbriefquotationsembeddedincriticalarticlesorreviews.

Everyefforthasbeenmadeinthepreparationofthisbooktoensuretheaccuracyoftheinformationpresented.However,theinformationcontainedinthisbookissoldwithoutwarranty,eitherexpressorimplied.Neithertheauthor,norPacktPublishing,anditsdealersanddistributorswillbeheldliableforanydamagescausedorallegedtobecauseddirectlyorindirectlybythisbook.

PacktPublishinghasendeavoredtoprovidetrademarkinformationaboutallofthecompaniesandproductsmentionedinthisbookbytheappropriateuseofcapitals.However,PacktPublishingcannotguaranteetheaccuracyofthisinformation.

Firstpublished:February2016

Productionreference:1230216

PublishedbyPacktPublishingLtd.

LiveryPlace

35LiveryStreet

BirminghamB32PB,UK.

ISBN978-1-78588-868-7

www.packtpub.com

CreditsAuthor

ThodorisGreasidis

Reviewer

AamirAfridi

CommissioningEditor

NeilAlexander

AcquisitionEditor

AaronLazar

ContentDevelopmentEditor

RiddhiTuljapurkar

TechnicalEditor

PramodKumavat

CopyEditors

TrishyaHazare

KevinMcGowan

ProjectCoordinator

SanchitaMandal

Proofreader

SafisEditing

Indexer

RekhaNair

Graphics

AbhinashSahu

ProductionCoordinator

ShantanuN.Zagade

CoverWork

ShantanuN.Zagade

AbouttheAuthorThodorisGreasidisisaseniorwebengineerfromGreece.HegraduatedwithhonorsfromtheUniversityofThessaly,holdsapolytechnicdiplomaincomputer,networking,andcommunicationsengineering,andamaster’sdegreeincomputerscience.Heisafull-stackdeveloper,responsibleforimplementinglarge-scalewebapplicationswithintuitiveinterfacesandhigh-availabilitywebservices.

ThodorisispartoftheAngular-UIteamandhasmademanyopensourcecontributions,withaspecialinterestinMozillaprojects.HeisalsoanactivememberoftheAthensAngularJSMeetupandatechnicalreviewerofMasteringjQueryUI,PacktPublishing.

HeisaJavaScriptenthusiastandlovesbitwiseoperations.HisinterestsalsoincludeNodeJS,Python,projectscaffolding,automation,andartificialintelligence,especiallymulti-agentsystems.

Abigthankstoeveryonewhosupportedmeandshowedunderstandingformylimitedfreetimewhilewritingthisbook.

AbouttheReviewerAamirAfridihasbeenpassionateabouttheInternetandwebdevelopmentsince2002.Heholdsamaster’sdegreeine-commerce.Overtheyearsthathavefollowed,hehasworkedforvariouscompaniesandprovidedfrontendengineering,includingmobilewebappsandarchitectureserviceswithafocusonsemanticHTML,CSS,andJavaScript/jQueryandanythingelsehecangethishandson.HehascontributedtoJavaScriptbooksasatechnicalreviewer.Thesedays,heisexploringthemicroservicesarchitecturewithNodeJS,MongoDB,andReactJSatwww.tes.com.Heblogsonhttp://aamirafridi.com.

www.PacktPub.com

eBooks,discountoffers,andmoreDidyouknowthatPacktofferseBookversionsofeverybookpublished,withPDFandePubfilesavailable?YoucanupgradetotheeBookversionatwww.PacktPub.comandasaprintbookcustomer,youareentitledtoadiscountontheeBookcopy.Getintouchwithusat<customercare@packtpub.com>formoredetails.

Atwww.PacktPub.com,youcanalsoreadacollectionoffreetechnicalarticles,signupforarangeoffreenewslettersandreceiveexclusivediscountsandoffersonPacktbooksandeBooks.

https://www2.packtpub.com/books/subscription/packtlib

DoyouneedinstantsolutionstoyourITquestions?PacktLibisPackt’sonlinedigitalbooklibrary.Here,youcansearch,access,andreadPackt’sentirelibraryofbooks.

Whysubscribe?FullysearchableacrosseverybookpublishedbyPacktCopyandpaste,print,andbookmarkcontentOndemandandaccessibleviaawebbrowser

PrefaceSinceitsintroductionin2006,thejQuerylibraryhasmadeDOMtraversalsandmanipulationsmucheasier.ThishasresultedintheappearanceofWebpageswithincreasinglycomplexuserinteractions,thuscontributingtothematuringofWebasaplatformcapableofsupportinglargeapplicationimplementations.

ThisbookpresentsaseriesofbestpracticesthatmaketheimplementationofWebapplicationsmoreefficient.Moreover,wewillanalyzethemostimportantDesignPatternsthatComputerSciencehastooffer,whichcanbeappliedtoWebdevelopment.Inthisway,wewilllearnhowtoutilizetechniquesthatarethoroughlyusedandtestedinotherfieldsofprogramming,whichwereinitiallycreatedasgenericmethodstomodelsolutionsofcomplexproblems.

InjQueryDesignPatterns,wewillanalyzehowvariousDesignPatternsareutilizedintheimplementationofjQueryandhowtheycanbeusedtoimprovetheorganizationofourimplementations.ByadoptingtheDesignPatternsdemonstratedinthisbook,youwillbeabletocreatebetterorganizedimplementationsthatresolvelargeproblemcategoriesfaster.Moreover,whenusedbyadeveloperteam,theycanimprovethecommunicationbetweenthemandleadtohomogenousimplementation,whereeverypartofthecodeiseasilyunderstoodbyothers.

WhatthisbookcoversChapter1,ARefresheronjQueryandtheCompositePattern,willteachthereaderhowtowritethecodeusingtheCompositePatternandmethodchaining(FluentInterface)byanalyzinghowtheyareusedfortheimplementationofjQueryitself.ItalsodemonstratestheIteratorPatternthatnicelypairswiththeCompositeCollectionobjectsthatjQueryreturns.

Chapter2,TheObserverPattern,willteachyouhowtorespondtouseractionsusingtheObserverPattern.ItalsodemonstrateshowtouseEventDelegationasawaytoreducethememoryconsumptionandcomplexityofthecodethathandlesdynamicallyinjectedpageelements.Finally,itwillteachyouhowtoemitandlistenforCustomEventsinordertoachievegreaterflexibilityandcodedecoupling.

Chapter3,ThePublish/SubscribePattern,willteachyouhowtoutilizethePub/SubPatterntocreateacentralpointtoemitandreceiveapplication-levelevents,asawaytodecoupleyourcodeandbusinesslogicfromtheHTMLthatisusedforpresentation.

Chapter4,DivideandConquerwiththeModulePattern,demonstratesandcomparessomeofthemostcommonlyusedModulePatternsintheindustry.ItwillteachyouhowtostructureyourapplicationinsmallindependentModulesusingNamespacing,leadingtoexpandableimplementationsthatfollowtheSeparationofConcernsprinciple.

Chapter5,TheFacadePattern,willteachyouhowtousetheFacadePatterntowrapcomplexAPIsintosimpleronesthatareabettermatchfortheneedsofyourapplication.Italsodemonstrateshowtochangepartsofyourapplication,whilekeepingthesamemodule-levelAPIsandavoidaffectingtherestofyourimplementation.

Chapter6,TheBuilderandFactoryPatterns,explainstheconceptsofandthedifferencesbetweentheBuilderandFactoryPatterns.Itwillteachyouhowandwhentouseeachofthem,inordertoimprovetheclarityofyourcodebyabstractingthegenerationofcomplexresultsintoseparatededicatedmethods.

Chapter7,AsynchronousControlFlowPatterns,willexplainhowjQuery’sDeferredandPromiseAPIsworkandcomparethemwiththeclassicalCallbacksPattern.YouwilllearnhowtousePromisestocontrolthetheexecutionofasynchronousprocedurestoruneitherinanorderorparalleltoeachother.

Chapter8,MockObjectPattern,teachesyouhowtocreateanduseMockObjectsandServicesasawaytoeasethedevelopmentofyourapplicationandgetasenseofitsfunctionality,longbeforeallitspartsarecompleted.

Chapter9,Client-sideTemplating,demonstrateshowtousetheUnderscore.jsandHandlebars.jstemplatinglibrariesasabetterandfasterwaytocreatecomplexHTMLstructureswithJavaScript.Throughthischapter,youwillgetanoverviewoftheirconventions,evaluatetheirfeatures,andfindtheonethatbestmatchesyourtaste.

Chapter10,PluginandWidgetDevelopmentPatterns,introducesthebasicconceptsandconventionsofjQueryPlugindevelopmentandanalyzesthemostcommonlyuseddesign

patterns,sothatyouwillbeabletoidentifyandusethebestmatchforanyusecase.

Chapter11,OptimizationPatterns,guidesyouwiththebesttipstocreateahighlyefficientandrobustimplementation.Youwillbeabletousethischapterasachecklistofbestpracticesthatimprovetheperformanceandlowerthememoryconsumptionofyourapplications,beforemovingthemtoaproductionenvironment.

WhatyouneedforthisbookInordertoruntheexamplesinthisbook,youwillneedtohaveawebserverinstalledonyoursystemtoservethecodefiles.Forexample,youcanuseApacheorIISorNGINX.InordertomaketheinstallationprocessofApacheeasier,youcanusemorecompletedevelopmentenvironmentsolutions,suchasXAMPPorWAMPServer.

Intermsoftechnicalproficiency,thisbookassumesthatyoualreadyhavesomeexperienceofworkingwithjQuery,HTML,CSS,andJSON.AllthecodesamplesinthebookusejQueryv2.2.0andsomeofthechaptersalsodiscusstherespectiveimplementationinjQueryv1.12.0,whichcanbeusedincasesupportforolderbrowsersisneeded.

WhothisbookisforThisbooktargetsexistingjQuerydevelopersornewdeveloperswhowanttotaketheirskillsandunderstandingtoanadvancedlevel.ItisadetailedintroductiontohowthevariousindustrystandardpatternscanbeappliedtojQueryapplications,andalongwithasetofthebestpractices,itcanhelplargeteamscollaborateandcreatewellorganizedandextendableimplementations.

ConventionsInthisbook,youwillfindanumberoftextstylesthatdistinguishbetweendifferentkindsofinformation.Herearesomeexamplesofthesestylesandanexplanationoftheirmeaning.

Codewordsintext,foldernames,filenames,fileextensions,pathnames,dummyURLs,userinput,andTwitterhandlesareshownasfollows:“IntheprecedingCSScode,wefirstdefinedsomebasicstylesforthebox,boxsizer,andclearCSSclasses.”

Ablockofcodeissetasfollows:

$.each([3,5,7],function(index){

console.log(this+1+'!');

});

Whenwewishtodrawyourattentiontoaparticularpartofacodeblock,therelevantlinesoritemsaresetinbold:

$('#categoriesSelector').change(function(){

var$selector=$(this);

varmessage={categoryID:$selector.val()};

broker.trigger('dashboardCategorySelect',[message]);

});

WearefollowingGoogle’sJavaScriptStyleGuide,exceptfromusingfourspacesforindentation,inordertoimprovethereadabilityofthecodeinthebook.Inshort,weareplacingcurlybracketsontopandusesinglequotesforstringliterals.

NoteFormoreinformationonGoogle’sJavaScriptStyleGuideyoucanvisitthefollowingURL:https://google.github.io/styleguide/javascriptguide.xml

Anycommand-lineinputoroutputiswrittenasfollows:

npminstalljquery

Newtermsandimportantwordsareshowninbold.Wordsthatyouseeonthescreen,forexample,inmenusordialogboxes,appearinthetextlikethis:“ThejQueryObjectreturnedisanArray-likeobjectthatactsasawrapperobjectandcarriesthecollectionoftheretrievedelements.”

NoteWarningsorimportantnotesappearinaboxlikethis.

TipTipsandtricksappearlikethis.

ReaderfeedbackFeedbackfromourreadersisalwayswelcome.Letusknowwhatyouthinkaboutthisbook—whatyoulikedordisliked.Readerfeedbackisimportantforusasithelpsusdeveloptitlesthatyouwillreallygetthemostoutof.

Tosendusgeneralfeedback,simplye-mail<feedback@packtpub.com>,andmentionthebook’stitleinthesubjectofyourmessage.

Ifthereisatopicthatyouhaveexpertiseinandyouareinterestedineitherwritingorcontributingtoabook,seeourauthorguideatwww.packtpub.com/authors.

CustomersupportNowthatyouaretheproudownerofaPacktbook,wehaveanumberofthingstohelpyoutogetthemostfromyourpurchase.

DownloadingtheexamplecodeYoucandownloadtheexamplecodefilesfromyouraccountathttp://www.packtpub.comforallthePacktPublishingbooksyouhavepurchased.Ifyoupurchasedthisbookelsewhere,youcanvisithttp://www.packtpub.com/supportandregistertohavethefilese-maileddirectlytoyou.

ErrataAlthoughwehavetakeneverycaretoensuretheaccuracyofourcontent,mistakesdohappen.Ifyoufindamistakeinoneofourbooks—maybeamistakeinthetextorthecode—wewouldbegratefulifyoucouldreportthistous.Bydoingso,youcansaveotherreadersfromfrustrationandhelpusimprovesubsequentversionsofthisbook.Ifyoufindanyerrata,pleasereportthembyvisitinghttp://www.packtpub.com/submit-errata,selectingyourbook,clickingontheErrataSubmissionFormlink,andenteringthedetailsofyourerrata.Onceyourerrataareverified,yoursubmissionwillbeacceptedandtheerratawillbeuploadedtoourwebsiteoraddedtoanylistofexistingerrataundertheErratasectionofthattitle.

Toviewthepreviouslysubmittederrata,gotohttps://www.packtpub.com/books/content/supportandenterthenameofthebookinthesearchfield.TherequiredinformationwillappearundertheErratasection.

PiracyPiracyofcopyrightedmaterialontheInternetisanongoingproblemacrossallmedia.AtPackt,wetaketheprotectionofourcopyrightandlicensesveryseriously.IfyoucomeacrossanyillegalcopiesofourworksinanyformontheInternet,pleaseprovideuswiththelocationaddressorwebsitenameimmediatelysothatwecanpursuearemedy.

Pleasecontactusat<copyright@packtpub.com>withalinktothesuspectedpiratedmaterial.

Weappreciateyourhelpinprotectingourauthorsandourabilitytobringyouvaluablecontent.

QuestionsIfyouhaveaproblemwithanyaspectofthisbook,youcancontactusat<questions@packtpub.com>,andwewilldoourbesttoaddresstheproblem.

Chapter1.ARefresheronjQueryandtheCompositePatternUntiltheWeb2.0erastarted,theWebwasjustadocument-basedmediaandallitofferedwasjustinterconnectingdifferentpages/documentsandclient-sidescriptingthatwasmostlylimitedtoformvalidation.By2005,GmailandGoogleMapswerereleased,andJavaScriptproveditselfasalanguageusedbybigenterprisestocreatelarge-scaleapplicationsandproviderichuserinterfaceinteractions.

EventhoughJavaScripthashadveryfewchangessinceitsoriginalrelease,therewasatremendouschangeintheexpectationsthattheEnterpriseworldhadaboutwhatwebpagesshouldbecapableofdoing.Sincethen,webdeveloperswererequiredtodelivercomplexuserinteractionsand,finally,theterm“webapplication”appearedonthemarket.Asaresult,itstartedtobecomeobviousthattheyshouldcreatesomecodeabstractions,definesomebestpractices,andadoptalltheapplicableDesignPatternsthatcomputersciencehadtooffer.ThewideadoptionofJavaScriptforenterprise-gradeapplicationshelpedtheevolutionofthelanguage,whichwiththeEcmaScript2015/EcmaScript6(ES6)specificationwasexpandedinawaythatallowedevenmoreDesignPatternstobeeasilyutilized.

InAugust2006,thejQuerylibrarywasfirstreleasedbyJohnResigathttp://jquery.com,asanefforttocreateaconvenientAPItolocateDOMelements.Sincethen,ithasbeenanintegralpartofawebdeveloper’stoolkit.jQueryinitscoreusesseveralDesignPatternsandtriestourgetheirusetothedeveloperthroughthemethodsthatitprovides.TheCompositePatternisoneofthemanditisexposedtothedeveloperthroughtheverycorejQuery()method,whichisusedforDOMtraversal,oneofthehighlightsofthejQuerylibrary.

Inthischapter,wewill:

HavearefresheronDOMscriptingusingjQueryIntroducetheCompositePatternSeehowtheCompositePatternisusedbyjQueryDiscussthegainsofferedbyjQueryoverplainJavaScriptDOMmanipulationsIntroducetheIteratorPatternUsetheIteratorPatterninanexampleapplication

jQueryandDOMscriptingByDOMscripting,werefertoanyprocedurethataltersormanipulatestheelementsofawebpageafterithasbeenloadedbythebrowser.TheDOMAPIisaJavaScriptAPIthatwasstandardizedin1998anditprovidestowebdevelopersacollectionofmethodsthatallowthemanipulationoftheDOMtreeelementsthatthebrowsercreatesafterloadingandparsingthewebpage’sHTMLcode.

NoteFormoreinformationontheDocumentObjectMode(DOM)anditsAPIs,youcanvisithttps://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Introduction.

ByutilizingtheDOMAPIintheirJavaScriptcode,webdeveloperscanmanipulatetheDOM’snodesandaddnewelementsorremoveexistingelementsfromthepage.TheprimaryusecaseforDOMscriptingwasinitiallylimitedtoclient-sideformvalidation,butastheyearspassedandJavaScriptgainedthetrustoftheEnterpriseworld,morecomplexuserinteractionsstartedtobeimplemented.

TheinitialversionofthejQuerylibrarywasfirstreleasedinAugust2006andittriedtoeasethewaythewebdevelopersweretraversingandmanipulatingtheDOMtree.Oneofitsmaingoalswastoprovideabstractionsthatresultedinshorter,easier-to-read,andlesserror-pronecode,whilealsoensuringcross-browserinteroperability.

ThesecoreprinciplesthatjQueryfollowsareclearlyvisibleinitshomepage,whereitpresentsitselfas:

…afast,small,andfeature-richJavaScriptlibrary.ItmakesthingslikeHTMLdocumenttraversalandmanipulation,eventhandling,animation,andAjaxmuchsimplerwithaneasy-to-useAPIthatworksacrossamultitudeofbrowsers.Withacombinationofversatilityandextensibility,jQueryhaschangedthewaythatmillionsofpeoplewriteJavaScript.

TheabstractedAPIsthatjQueryprovidedfromthebeginning,andthewaythatdifferentDesignPatternswereorchestrated,ledtowideacceptanceamongthewebdevelopers.Asaresult,thejQuerylibraryisreferencedbymorethan60%ofthemostvisitedwebsitesworldwide,accordingtoseveralsourcessuchasBuiltWith.com(http://trends.builtwith.com/javascript/jQuery).

ManipulatingtheDOMusingjQueryTohavearefresheronjQuery,wewillgothroughanexamplewebpagethatdoessomesimpleDOMmanipulations.Inthisexample,wewillloadasimplystructuredpagethatinitiallylookslikethefollowingfigure:

WewillusesomejQuerycodetochangethepage’scontentandlayoutand,inordertomakeitseffectsclearlyvisible,wewillsetittorunabout700millisecondsafterthepagehasloaded.Theresultofourmanipulationswilllooklikethefollowingfigure:

Nowlet’sreviewtheHTMLcoderequiredfortheprecedingexample:

<!DOCTYPEhtml>

<html>

<head>

<title>DOMManipulations</title>

<linkrel="stylesheet"type="text/css"href="dom-manipulations.css">

</head>

<body>

<h1id="pageHeader">DOMManipulations</h1>

<divclass="boxContainer">

<div>

<pclass="box">

DoingDOMManipulationsiseasywithJS!

</p>

</div>

<div>

<pclass="box">

DoingDOMManipulationsiseasywithJS!

</p>

</div>

<div>

<pclass="box">

DoingDOMManipulationsiseasywithJS!

</p>

</div>

</div>

<pclass="box">

DoingDOMManipulationsiseasywithJS!

</p>

<pclass="box">

DoingDOMManipulationsiseasywithJS!

</p>

<scripttype="text/javascript"src="https://code.jquery.com/jquery-

2.2.0.min.js"></script>

<scripttype="text/javascript"src="jquery-dom-manipulations.js">

</script>

</body>

</html>

TheCSScodeusedisquitesimple,containingonlythreeCSSclassesasfollows:

.box{

padding:7px10px;

border:solid1px#333;

margin:5px3px;

box-shadow:01px2px#777;

}

.boxsizer{

float:left;

width:33.33%;

}

.clear{clear:both;}

TheprecedingcoderesultsinapagelookinglikethefirstfigurewhenopenedinabrowserandbeforeourJavaScriptcodeisexecuted.IntheprecedingCSScode,wefirstdefinedsomebasicstylesforthebox,boxsizer,andclearCSSclasses.Theboxclassstylestheassociatedelementsfoundinthepagebyusingsomepadding,athinborder,somemarginaround,andasmallshadowbelowtheelementsinordertomakethemlooklikeabox.Theboxsizerclasswillmaketheelementsthatuseittotakejust1/3rdofthewidthoftheirparentelementandcreateathree-columnlayout.Finally,theclearclasswillbeusedonanelementasabreakpointforthecolumnlayoutsothatalltheelementsthatfollowwillbepositionedbelowit.TheboxsizerandclearclassesarenotinitiallyusedbyanyelementdefinedintheHTMLcode,butwillbeusedaftertheDOMmanipulationsthatwewilldoinJavaScript.

Inthe<body>elementofourHTML,weinitiallydefinean<h1>headingelementwithIDpageHeadersothatitiseasilyselectablethroughJavaScript.Rightbelowit,wedefinefiveparagraphelements(<p>)withtheboxclass,havingthefirstthreeofthemwrappedinsidethethree<div>elementsandtheninsideanother<div>elementwiththeboxContainerclass.

Reachingourtwo<script>tags,wefirstincludeareferencetothejQuerylibraryfromjQueryCDN.Formoreinformation,youcanvisithttp://code.jquery.com/.Inthesecond<script>tag,wereferencetheJavaScriptfilewiththerequiredcode,forthisexample,whichlooksasfollows:

setTimeout(function(){

$('#pageHeader').css('font-size','3em');

var$boxes=$('.boxContainer.box');

$boxes.append(

'<br/><br/><i>Incaseweneedsimplethings</i>.');

$boxes.parent().addClass('boxsizer');

$('.boxContainer').append('<divclass="clear">');

},700);

AllourcodeiswrappedinsideasetTimeoutcalltodelayitsexecution,accordingtotheusecasedescribedearlier.ThefirstparameterofthesetTimeoutfunctioncallisananonymousfunctionthatwillbeexecutedafteratimerof700millisecondshasexpired,asdefinedinthesecondargument.

Atthefirstlineofouranonymouscallbackfunction,weusethejQuery$()functiontotraversetheDOMandlocatetheelementwiththeIDpageHeader,andusethecss()methodtoincreaseitsfont-sizeto3em.NextweprovideamorecomplexCSSselectortothe$()function,tolocatealltheelementswiththeboxclassthataredescendantsoftheelementwiththeboxContainerclass,andthenstoretheresultinavariablenamed$boxes.

Tip

Variablenamingconventions

Itisacommonpracticeamongdeveloperstousenamingconventionsforvariablesthatholdobjectsofacertaintype.Usingsuchconventionsnotonlyhelpsyourememberwhatthevariableisholding,butalsomakesyourcodeeasiertounderstandbyotherdevelopersofyourteam.AmongjQuerydevelopers,itiscommontousevariablenamesstartingwitha“$”signwhenthevariablestorestheresultofthe$()function(alsoknowasajQuerycollectionobject).

Afterwegetaholdoftheboxelementsthatweareinterestedin,weappendtwobreakingspacesandsomeextratextinitalics,attheendofeachofthem.Then,weusethe$boxesvariableandtraversetheDOMtreeonelevelup,usingtheparent()method.Theparent()methodreturnsadifferentjQueryobjectholdingtheparent<div>elementsofourinitiallyselectedboxesandthenwechainacalltotheaddClass()methodtoassignthemtheboxsizerCSSclass.

TipIfyouneedtotraversealltheparentnodesofaselectedelement,youcanusethe$.fn.parents()method.IfyoujustneedtofindthefirstancestorelementthatmatchesagivenCSSselector,considerusingthe$.fn.closest()methodinstead.

Finally,sincetheboxsizerclassusesfloatstoachievethethree-columnlayout,weneedtoclearthefloatsintheboxContainer.Onceagain,wetraversetheDOMusingthesimple.boxContainerCSSselectorandthe$()function.Then,wecallthe.append()methodtocreateanew<div>elementwiththe.clearCSSclassandinsertitattheendoftheboxContainer.

After700milliseconds,ourjQuerycodewillhavefinished,resultinginthethree-columnlayoutasshownearlier.Initsfinalstate,theHTMLcodeofourboxContainerelementwilllookasfollows:

<divclass="boxContainer">

<divclass="boxsizer">

<pclass="box">

DoingDOMManipulationsiseasywithJS!

<br><br><i>Incaseweneedsimplethings</i>.

</p>

</div>

<divclass="boxsizer">

<pclass="box">

DoingDOMManipulationsiseasywithJS!

<br><br><i>Incaseweneedsimplethings</i>.

</p>

</div>

<divclass="boxsizer">

<pclass="box">

DoingDOMManipulationsiseasywithJS!

<br><br><i>Incaseweneedsimplethings</i>.

</p>

</div>

<divclass="clear"></div>

</div>

MethodChainingandFluentInterfacesActually,intheprecedingexample,wecanalsogoonestepfurtherandcombineallthreebox-relatedcodestatementsintojustone,whichlookssomethingasfollows:

$('.boxContainer.box')

.append('<br/><br/><i>Incaseweneedsimplethings</i>.')

.parent()

.addClass('boxsizer');

ThisSyntaxPatterniscalledMethodChaininganditishighlyrecommendedbyjQueryandtheJavaScriptcommunityingeneral.MethodChainingispartoftheObjectOrientedImplementationPatternofFluentInterfaceswhereeachmethodrelaysitsinstructioncontexttothesubsequentone.

MostjQuerymethodsthatapplyonajQueryobjectalsoreturnthesameoranewjQueryelementcollectionobject.Thisallowsustochainseveralmethods,notonlyresultinginamorereadableandexpressivecodebutalsoreducingtherequiredvariabledeclarations.

TheCompositePatternThekeyconceptoftheCompositePatternistoenableustotreatacollectionofobjectsinthesamewayaswetreatasingleobjectinstance.Manipulatingacompositionbyusingamethodonthecollectionwillresultinapplyingthemanipulationtoeachpartofit.Suchmethodscanbeappliedsuccessfully,regardlessofthenumberofelementsthatarepartofthecompositecollection,orevenwhenthecollectioncontainsnoelements.

Also,theobjectsofacompositecollectiondonotnecessarilyhavetoprovidetheexactsamemethods.TheCompositeObjectcaneitherexposeonlythemethodsthatarecommonamongtheobjectsofthecollection,orcanprovideanabstractedAPIandappropriatelyhandlethemethoddifferentiationsofeachobject.

Let’scontinuebyexploringhowtheintuitiveAPIthatjQueryexposesishighlyinfluencedfromtheCompositePattern.

HowtheCompositePatternisusedbyjQueryTheCompositePatternisanintegralpartofjQuery’sarchitectureandisappliedfromtheverycore$()functionitself.Eachcalltothe$()functioncreatesandreturnsanelementcollectionobject,whichisoftensimplyreferredasajQueryobject.ThisisexactlywhereweseethefirstprincipleoftheCompositePatterns;infact,insteadofreturningasingleelement,the$()functionreturnsacollectionofelements.

ThejQueryobjectreturnedisanArray-likeobjectthatactsasawrapperobjectandcarriesthecollectionoftheretrievedelements.Italsoexposesanumberofextrapropertiesasfollows:

ThelengthoftheretrievedelementcollectionThecontextthattheobjectwasconstructedTheCSSselectorthatwasusedonthe$()functioncallAprevObjectpropertyincaseweneedtoaccessthepreviouselementcollectionafterchainingamethodcall

TipSimpleArray-likeobjectdefinition

AnArray-likeobjectisaJavaScriptobject{}thathasanumericlengthpropertyandtherespectivenumberofproperties,withsequentialnumericpropertynames.Inotherwords,anArray-likeobjectthathasthelength==2propertyisexpectedtoalsohavetwopropertiesdefined,"0"and"1".Giventheaboveproperties,Array-likeobjectsallowyoutoaccesstheircontentusingsimpleforloops,byutilizingJavaScript’sBracketPropertyAccessor’ssyntax:

for(vari=0;i<obj.length;i++){

console.log(obj[i]);

}

WecaneasilyexperimentwiththejQueryobjectsreturnedfromthe$()functionandinspectthepropertiesdescribedabove,byusingthedevelopertoolsofourfavoritebrowser.Toopenthedevelopertoolsonmostofthem,wejustneedtopressF12onWindowsandLinuxorCmd+Opt+IonMac,andrightafterthat,wecanissuesome$()callsintheconsoleandclickonthereturnedobjectstoinspecttheirproperties.

Inthefollowingfigure,wecanseewhattheresultofthe$('#pageHeader')call,whichweusedintheexampleearlier,lookslikeinFirefoxDeveloperTools:

Theresultofthe$('.boxContainer.box')calllooksasfollows:

ThefactthatjQueryusesArray-likeobjectsasawrapperforthereturnedelementsallowsittoexposesomeextramethodsthatapplyonthecollectionreturned.ThisisachievedthroughprototypicalinheritanceofthejQuery.fnobject,resultingineachjQueryobjectalsohavingaccesstoallthemethodsthatjQueryprovides.ThiscompletestheCompositePattern,whichprovidesmethodsthat,whenappliedtoacollection,areappropriatelyappliedtoeachofitsmembers.BecausejQueryusesArray-likeobjectswithprototypicalinheritance,thesemethodscanbeeasilyaccessedaspropertiesoneachjQueryobject,asshownintheexampleinthebeginningofthechapter:$('#pageHeader').css('font-size','3em');.Moreover,jQueryaddssomeextragoodiestoitsDOMmanipulatingcode,followingthegoalofsmallerandlesserror-pronecode.Forexample,whenusingthejQuery.fn.html()methodtochangetheinnerHTMLofaDOMnodethatalreadycontainschildelements,jQueryfirsttriestoremoveanydataandeventhandlersthatareassociatedwiththechildelements,beforeremovingthemfromthepageandappendingtheprovidedHTMLcode.

Let’stakealookathowjQueryimplementsthesecollection-applicablemethods.Forthistask,wecaneitherdownloadandviewthesourcecodefromtheGitHubpageofjQuery(https://github.com/jquery/jquery/releases),orwecanuseatoolsuchasthejQuerySourceViewerthatisavailableathttp://james.padolsey.com/jquery.

NoteDependingontheversionyouareusing,youmightgetdifferentresultstosomedegree.ThemostrecentstablejQueryversionthatwasreleasedandusedasareferencewhilewritingthisbook,wasv2.2.0.

Oneofthesimplestmethodstodemonstratehowmethodsthatapplytocollectionsareimplemented,isjQuery.fn.empty().YoucaneasilylocateitsimplementationinjQuery’ssourcecodebysearchingfor"empty:"orusingthejQuerySourceViewerandsearchingfor"jQuery.fn.empty".Usingeitheroneofthewayswillbringustothefollowingcode:

empty:function(){

varelem,i=0;

for(;(elem=this[i])!=null;i++){

if(elem.nodeType===1){

//Preventmemoryleaks

jQuery.cleanData(getAll(elem,false));

//Removeanyremainingnodes

elem.textContent="";

}

}

returnthis;

}

Asyoucansee,thecodeisnotcomplexatall.jQueryiteratesoveralltheitemsofthecollectionobject(referredtoasthissinceweareinsidethemethodimplementation)byusingaplainforloop.Foreachitemofthecollection,thatis,anElementNode,itclearsanydata-*propertyvaluesusingthejQuery.cleanData()helperfunction,andrightafterthis,itclearsitscontentbysettingittoanemptystring.

NoteFormoreinformationonthedifferentspecifiedNodeTypes,youcanvisithttps://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType.

ComparingthebenefitsovertheplainDOMAPIToclearlydemonstratethebenefitsthattheCompositePatternprovides,wewillrewriteourinitialexamplewithouttheabstractionsthatjQueryoffers.ByusingjustplainJavaScriptandtheDOMAPI,wecanwriteanequivalentcodethatlooksasfollows:

setTimeout(function(){

varheaderElement=document.getElementById('pageHeader');

if(headerElement){

headerElement.style.fontSize='3em';

}

varboxContainerElement=document.getElementsByClassName('boxContainer')

[0];

if(boxContainerElement){

varinnerBoxElements=

boxContainerElement.getElementsByClassName('box');

for(vari=0;i<innerBoxElements.length;i++){

varboxElement=innerBoxElements[i];

boxElement.innerHTML+='<br/><br/><i>Incaseweneedsimple

things</i>.';

boxElement.parentNode.className+='boxsizer';

}

varclearFloatDiv=document.createElement('div');

clearFloatDiv.className='clear';

boxContainerElement.appendChild(clearFloatDiv);

}

},700);

Onceagain,weusesetTimeoutwithananonymousfunctionandset700millisecondsasthesecondparameter.Insidethefunctionitself,weusedocument.getElementByIdtoretrieveelementsthatareknowntohaveauniqueIDinthepage,andlaterdocument.getElementsByClassNamewhenweneedtoretrievealltheelementsthathaveaspecificclass.WealsouseboxContainerElement.getElementsByClassName('box')toretrievealltheelementswiththeboxclassthataredescendantsoftheelementwiththeboxContainerclass.

Themostobviousobservationisthat,inthiscase,weneeded18linesofcodeinordertoachievethesameresults.Forcomparison,whenusingjQuery,weonlyneeded9linesofcode,that’shalfthenumberoflinesofcodecomparedtothelaterimplementation.UsingthejQuery$()functionwithaCSSselectorwasaneasierwaytoretrievetheelementsthatweneeded,anditalsoensurescompatibilitywithbrowsersthatdonotsupportthegetElementsByClassName()method.However,therearemorebenefitsthanjustthecodelinecountandtheimprovedreadability.AsanimplementeroftheCompositePattern,the$()functionalwaysretrieveselementcollections,makingourcodemoreuniformwhencomparedtothedifferentiatedhandlingofeachgetElement*methodweused.Weusethe$()functioninexactlythesameway,regardlessofwhetherwejustwanttoretrieveanelementwithauniqueIDoranumberofelementswithaspecificclass.

AsanextrabenefitofreturningArray-likeobjects,jQuerycanalsoprovidemoreconvenientmethodstotraverseandmanipulatetheDOM,suchasthosewesawinourfirstexample,.css(),.append()and.parent(),whichareaccessibleaspropertiesofthe

returnedobject.Additionally,jQueryalsooffersmethodsthatabstractmorecomplexusecasessuchas.addClass()and.wrap()thathavenoequivalentmethodsavailableaspartoftheDOMAPI.

SincethereturnedjQuerycollectionobjectsdonotdifferinanythingotherthantheelementstheywrap,wecanuseanymethodofthejQueryAPIinthesameway.Aswesawearlier,thesemethodsapplytoeachelementoftheretrievedcollection,regardlessoftheelementcount.Asaresult,wedonotneedaseparateforlooptoiterateovereachretrievedelementandapplyourmanipulationsindividually;instead,weapplyourmanipulations(forexample,.addClass())directlytothecollectionobject.

Tocontinueprovidingthesameexecutionsafetyguarantiesinthelaterexample,wealsoneedtoaddsomeextraifstatementstocheckfornullvalues.Thisisrequiredbecause,forexample,iftheheaderElementisnotfound,anerrorwilloccurandtherestofthelinesofcodewillneverbeexecuted.Someonecouldarguethatthesechecks,suchasif(headerElement)andif(boxContainerElement),arenotrequiredinthisexampleandcanbeomitted.Thismightappeartobecorrectinthisexample,butactuallythisisamongthetopreasonsforerrorswhiledevelopinglarge-scaleapplications,whereelementsarecreated,inserted,andremovedfromtheDOMtreecontinuously.Unfortunately,programmersinalllanguagesandtargetplatformstendtofirstwritetheirimplementationlogicandfillsuchchecksatalatertime,oftenaftertheygetanerrorwhentestingtheirimplementation.

FollowingtheCompositePattern,evenanemptyjQuerycollectionobject(onethatcontainsnoretrievedelements)isstillavalidcollectionobject,wherewecansafelyapplyanymethodthatjQueryprovides.Asaresult,wedonotneedtheextraifstatementstocheckwhetheracollectionactuallycontainsanyelementbeforeapplyingamethodsuchas.css(),justforthesakeofavoidingaJavaScriptruntimeerror.

Overall,theabstractionsthatjQueryoffersbyusingtheCompositePatternleadtofewerlinesofcode,whichismorereadable,uniform,andwithfewertypo-pronelines(comparetyping$('#elementID')versusdocument.getElementById('elementID')).

UsingtheCompositePatterntodevelopapplicationsNowthatwehaveseenhowjQueryusestheCompositePatterninitsarchitectureandalsodidacomparisononthebenefitsitprovided,let’strytowriteanexampleusecaseofourown.Wewilltrytocoverallconceptsthatwehaveseenearlierinthischapter.WewillstructureourCompositetobeanArray-likeobject,operateontotallydifferentstructuredobjects,provideaFluentAPItoallowchaining,andhavemethodsthatapplyonalltheitemsofthecollection.

AsampleusecaseLet’ssaythatwehaveanapplicationthatatsomepointneedstoperformoperationsonnumbers.Ontheotherhand,theitemsthatitneedstooperateoncomefromdifferentsourcesandarenotuniformatall.Tomakethisexampleinteresting,let’ssupposethatonesourceofdataprovidesplainnumbersandanotheroneprovidesobjectswithaspecificpropertythatholdsthenumberweareinterestedin:

varnumberValues=[2,5,8];

varobjectsWithValues=[

{value:7},

{value:4},

{value:6},

{value:9}

];

Theobjectsreturnedbythesecondsourceofourusecasecouldhaveamorecomplexstructureandprobablysomeextraproperties.Suchchangeswouldn’tdifferentiateourexampleimplementationinanyway,sincewhendevelopingaCompositeweareonlyinterestedinprovidingauniformhandlingoverthecommonpartsbetweenthetargeteditems.

TheCompositeCollectionImplementationLet’sproceedanddefinetheConstructorFunctionandtheprototypethatwilldescribeourCompositeCollectionObject:

functionValuesComposite(){

this.length=0;

}

ValuesComposite.prototype.append=function(item){

if((typeofitem==='object'&&'value'initem)||

typeofitem==='number'){

this[this.length]=item;

this.length++;

}

returnthis;

};

ValuesComposite.prototype.increment=function(number){

for(vari=0;i<this.length;i++){

varitem=this[i];

if(typeofitem==='object'&&'value'initem){

item.value+=number;

}elseif(typeofitem==='number'){

this[i]+=number;

}

}

returnthis;

};

ValuesComposite.prototype.getValues=function(){

varresult=[];

for(vari=0;i<this.length;i++){

varitem=this[i];

if(typeofitem==='object'&&'value'initem){

result.push(item.value);

}elseif(typeofitem==='number'){

result.push(item);

}

}

returnresult;

};

TheValuesComposite()constructorfunctioninourexampleisquitesimple.Wheninvokedwiththenewoperator,itreturnsanemptyobjectwithalengthpropertyequaltozero,representingthatthecollectionitwrapsisempty.

NoteFormoreinformationonthePrototype-basedprogrammingmodelofJavaScript,visithttps://developer.mozilla.org/en-US/docs/Web/JavaScript/Introduction_to_Object-Oriented_JavaScript.

Wefirstneedtodefineawaythatwillenableustopopulateourcompositecollectionobjects.Wedefinedtheappendmethodthatcheckswhethertheprovidedparameterisoneofthetypesthatitcanhandle;inthiscase,itappendstheparameterontheCompositeObjectonthenextavailablenumericpropertyandincrementsthelengthpropertyvalue.Forexample,thefirstappendeditem,whetheritisanobjectwithavaluepropertyoraplainnumber,willbeexposedtothe“0”propertyoftheCompositeObjectandwillbeaccessiblewiththeBracketPropertyAccessor’ssyntaxasmyValuesComposition[0].

Theincrementmethodispresentedasasimpleexamplemethodthatcanmanipulatesuchcollectionsbyoperatingoverallthecollectionitems.Itacceptsanumericvalueasaparameterandthenappropriatelyhandlesitbyaddingittoeachitemofourcollection,basedontheirtype.SinceourcompositeisanArray-likeobject,incrementusesaforlooptoiterateoverallthecollectionitemsandeitherincreasestheitem.value(incasetheitemisanobject)ortheactualnumericvaluestored(whenthecollectionitemstoredisanumber).Inthesamemanner,wecancontinueandimplementothermethodsthatwill,

forexample,enableustomultiplythecollectionitemswithaspecificnumber.

InordertoallowchainingthemethodsofourCompositeObject,allthemethodsoftheprototypeneedtoreturnareferencetotheinstanceoftheobject.Weachievethisgoalbysimplyaddingareturnthis;statementasthelastlineforallthemethodsthatmanipulatethecollection,suchasappendandincrement.KeepinmindthatmethodssuchasgetValuesthatdonotmanipulatethecollectionbutareusedtoreturnaresult,bydefinition,can’tbechainedtorelaythecollectionobjectinstancetosubsequentmethodcalls.

Finally,weimplementthegetValuesmethodasaconvenientwaytoretrievetheactualnumericvaluesofalltheitemsinourcollection.Similartotheincrementmethod,thegetValuesmethodabstractsawaythehandlingbetweenthedifferentitemtypesofourcollection.Ititeratesoverthecollectionitems,extractseachnumericvalue,andappendsthemtoaresultarraythatitreturnstoitscaller.

AnexampleexecutionLet’snowseeanactualexamplethatwillusetheCompositeObjectwejustimplemented:

varvaluesComposition=newValuesComposite();

for(vari=0;i<numberValues.length;i++){

valuesComposition.append(numberValues[i]);

}

for(vari=0;i<objectsWithValues.length;i++){

valuesComposition.append(objectsWithValues[i]);

}

valuesComposition.increment(2)

.append(1)

.append(2)

.append({value:3});

console.log(valuesComposition.getValues());

Whentheprecedingcodeisexecutedinabrowser,bywritingthecodeeitherinanexistingpageordirectlyinthebrowser’sconsole,itwilllogaresultthatlooksasfollows:

►Array[4,7,10,9,6,8,11,1,2,3]

WeareusingourdatasourcessuchasthenumberValuesandobjectsWithValuesvariablesthatwereshownearlier.TheprecedingcodeiteratesoverbothofthemandappendstheiritemstoanewlycreatedCompositeObjectinstance.Wethenproceedbyincrementingthevaluesofourcompositecollectionby2.Rightafterthis,wechainthethreeiteminsertionsusingappend,withthefirsttwoappendingnumericvaluesandthethirdappendinganobjectwithavalueproperty.Finally,weusethegetValuesmethodinordertogetanarraywithallthenumericvaluesofourcollectionandlogitinourbrowser’sconsole.

Alternativeimplementations

KeepinmindthataCompositedoesnotneedtobeanArray-likeobject,butiscommonlypreferredsinceJavaScriptmakesiteasytocreatesuchanimplementation.Additionally,Array-likeimplementationsalsohavethebenefitofallowingustoiterateoverthecollectionitemsusingasimpleforloop.

Ontheotherhand,incaseanArray-likeobjectisnotpreferred,wecaneasilyuseapropertyontheCompositeObjecttoholdourcollectionitems.Forexample,thispropertycanbenamedasitemsandbeusedtostoreandaccesstheitemsofthecollectioninsideourmethodsusingthis.items.push(item)andthis.items[i],respectively.

TheIteratorPatternThekeyconceptoftheIteratorPatternistheuseofafunctionwiththesingleresponsibilitytotraverseacollectionandprovideaccesstoitsitems.Thisfunctionisknownastheiteratorandprovidesawaytoaccesstheitemsofthecollection,withoutexposingimplementationspecificsandtheunderlyingdatastructureusedbythecollectionobject.

Iteratorsprovidealevelofencapsulationregardingthewaytheiterationoccurs,decouplingtheiterationovertheitemsofacollectionfromtheimplementationlogicoftheirconsumers.

NoteFormoreinformationontheSingleResponsibilityprinciple,youcanvisithttp://www.oodesign.com/single-responsibility-principle.html.

HowtheIteratorPatternisusedbyjQueryAswesawearlierinthischapter,thejQuerycore$()functionreturnsanArray-likeobjectthatwrapsacollectionofpageelementsanditalsoprovidesaniteratorfunctiontotraverseitandaccesseachelementindividually.ItactuallygoesonestepfurtherandprovidesagenerichelpermethodjQuery.each()thatcaniterateoverarrays,Array-likeobjects,andalsoobjectproperties.

AmoretechnicaldescriptioncanbefoundinjQueryAPIdocumentationpageathttp://api.jquery.com/jQuery.each/,wherethedescriptionofjQuery.each()readsasfollows:

Agenericiteratorfunction,whichcanbeusedtoseamlesslyiterateoverbothobjectsandarrays.ArraysandArray-likeobjectswithalengthproperty(suchasafunction’sargumentsobject)areiteratedbynumericindex,from0tolength-1.Otherobjectsareiteratedviatheirnamedproperties.

ThejQuery.each()helperfunctionisusedinternallyinseveralplacesofthejQuerysourcecode.OneofitsusesisiteratingovertheitemsofajQueryobjectandapplyingmanipulationsoneachofthem,astheCompositePatternsuggests.Asimplesearchforthekeyword.each(reveals56matches.

NoteAsofwritingthisbook,thelateststableversionisv2.2.0andthiswasusedfortheabovestatistics.

WecaneasilytraceitsimplementationinjQuery’ssource,eitherbysearchingfor"each:"(notethattherearetwooccurrences)orusingthejQuerySourceViewerandsearchingfor"jQuery.each()"(likewedidearlierinthischapter):

each:function(obj,callback){

varlength,i=0;

if(isArrayLike(obj)){

length=obj.length;

for(;i<length;i++){

if(callback.call(obj[i],i,obj[i])===false){

break;

}

}

}else{

for(iinobj){

if(callback.call(obj[i],i,obj[i])===false){

break;

}

}

}

returnobj;

}

ThishelperfunctionisalsoaccessibleonanyjQueryobjectbyusingthesameprototypicalinheritancethatwesawearlierformethodssuchas.append().Youcaneasilyfindthecodethatdoesexactlythis,bysearchingfor"jQuery.fn.each()"injQuerySourceViewerordirectlysearchingjQuerysourcecodeforeach:(notethattherearetwooccurrences):

each:function(callback){

returnjQuery.each(this,callback);

}

Usingthemethodversionof".each()"enablesustodirectlyiterateovertheelementsofajQuerycollectionobjectwithamoreconvenientsyntax.

Theexamplecodethatfollowsshowcaseshowthetwoflavorsof.each()canbeusedinourcode:

//usingthehelperfunctiononanarray

$.each([3,5,7],function(index){

console.log(this+1);

});

//usingthemethodonajQueryobject

$('.boxContainer.box').each(function(index){

console.log('I\'mbox#'+(index+1));//indexiszero-based

});

Whenexecuted,theprecedingcodewilllogthefollowingonthebrowser’sconsole:

HowitpairswiththeCompositePatternSincetheCompositePatternencapsulatesacollectionofitemsintoasingleobjectandtheIteratorPatterncanbeusedtoiterateoveranabstracteddatastructure,wecaneasilycharacterizethesetwopatternsascomplementary.

WherecanitbeusedTheIteratorPatterncanbeusedinourapplicationstoabstractthewayweaccessitemsfromadatastructure.Forexample,let’ssupposeweneedtoretrievealltheitemsthataregreaterthan4fromthefollowingtreestructure:

varcollection={

nodeValue:7,

left:{

nodeValue:4,

left:2,

right:{

nodeValue:6,

left:5,

right:9

}

},

right:{

nodeValue:9,

left:8

}

};

Let’snowimplementouriteratorfunction.Sincetreedatastructurescanhavenesting,weendupwiththefollowingrecursiveimplementation:

functioniterateTreeValues(node,callback){

if(node===null||node===undefined){

return;

}

if(typeofnode==='object'){

if('left'innode){

iterateTreeValues(node.left,callback);

}

if('nodeValue'innode){

callback(node.nodeValue);

}

if('right'innode){

iterateTreeValues(node.right,callback);

}

}else{

//itsaleaf,sothenodeisthevalue

callback(node);

}

}

Finally,weendupwithanimplementationthatlooksasfollows:

varvaluesArray=[];

iterateTreeValues(collection,function(value){

if(value>4){

valuesArray.push(value);

}

});

console.log(valuesArray);

Whenexecuted,theprecedingcodewilllogthefollowingonthebrowser’sconsole:

►Array[5,6,9,7,8,9]

Wecanclearlyseethattheiteratorsimplifiedourcode.Wenolongerbotherwiththeimplementationspecificsofthedatastructureusedeverytimeweneedtoaccesssomeitemsthatfulfillcertaincriteria.OurimplementationworksontopofthegenericAPIthattheiteratorexposes,andourimplementationlogicappearsinthecallbackthatweprovidetotheiterator.

Thisencapsulationallowsustodecoupleourimplementationfromthedatastructureused,giventhataniteratorwiththesameAPIwillbeavailable.Forinstance,inthisexample,wecaneasilychangethedatastructureusedtoasortedbinarytreeorasimplearrayandpreserveourimplementationlogicthesame.

SummaryInthischapter,wehadarefresheronJavaScript’sDOMScriptingAPIandjQuery.WewereintroducedtotheCompositePatternandsawhowitisusedbythejQuerylibrary.WesawhowtheCompositePatternsimplifiesourworkflowafterwerewroteourexamplepagewithoutusingjQuery,andlatershowcasedanexampleofusingtheCompositePatterninourapplications.Finally,wewereintroducedtotheIteratorPatternandsawhowwellitpairswhenusedalongwiththeCompositePattern.

NowthatwehavecompletedourintroductiononhowtheCompositePatternplaysanimportantroleinthewayweusejQuerymethodseveryday,wecanmoveontothenextchapterwherewewillshowcasetheObserverPatternandtheconvenientwaytoutilizeitinourpagesusingjQuery.

Chapter2.TheObserverPatternInthischapter,wewillshowcasetheObserverPatternandtheconvenientwayinwhichwecanutilizeitinourpagesusingjQuery.Lateron,wewillalsoexplaintheDelegatedEventObserverPatternvariant,whichwhenproperlyappliedtowebpagescanleadtocodesimplificationsandalsolessenthememoryconsumptionthatapagerequires.

Inthischapter,wewill:

IntroducetheObserverPatternSeehowtheObserverPatternisusedbyjQueryComparetheObserverPatternwithusingtheeventattributesLearnhowtoavoidmemoryleaksfromobserversIntroducetheDelegatedEventObserverPatternandshowcasingitsbenefits

IntroducingtheObserverPatternThekeyconceptoftheObserverPatternisthatthereisanobject,oftenreferredtoastheobservableorthesubject,whoseinternalstatechangesduringitslifetime.Therearealsoseveralotherobjects,referredastheobservers,thatwanttobenotifiedintheeventthatthestateoftheobservable/subjectchanges,inordertoexecutesomeoperations.

Theobserversmayneedtobenotifiedaboutanykindofstatechangeoftheobservableoronlyspecifictypesofchanges.Inthemostcommonimplementation,theobservablemaintainsalistwithitsobserversandnotifiesthemwhenanappropriatestatechangeoccurs.Incaseastatechangeoccurstotheobservable,ititeratesthroughthelistofobserversthatareinterestedforthattypeofstatechangeandexecutesaspecificmethodthattheyhavedefined.

AccordingtothedefinitionoftheObserverPatternandthereferenceimplementationinComputerSciencebooks,theobserversaredescribedasobjectsthatimplementawell-knownprogramminginterface,inmostcases,specifictoeachobservabletheyareinterestedin.Inthecaseofastatechange,theobservablewillexecutethewell-knownmethodofeachobserverasitisdefinedintheprogramminginterface.

NoteFormoreinformationonhowtheObserverPatternisusedintraditional,object-orientedprogramming,youcanvisithttp://www.oodesign.com/observer-pattern.html.

Inthewebstack,theObserverPatternoftenusesplainanonymouscallbackfunctionsasobserversinsteadofobjectswithwell-knownmethods.Anequivalentresult,asdefinedby

theObserverPattern,canbeachievedsincethecallbackfunctionkeepsreferencestothevariablesoftheenvironmentthatitwasdefinedin—apatterncommonlyreferencedasaClosure.ThemainbenefitofusingtheObserverPatternovercallbacksasinvocationorinitializationparametersisthattheObserverPatterncansupportseveralindependenthandlersonasingletarget.

NoteFormoreinformationonclosures,youcanvisithttps://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures.

TipDefiningasimplecallback

Acallbackcanbedefinedasafunctionthatispassedasanargumenttoanotherfunction/methodorisassignedtoapropertyofanobjectandexpectedtobeexecutedatsomelaterpointoftime.Inthisway,thepieceofcodethatwashandedourcallbackwillinvokeorcallit,propagatingtheresultsofanoperationoreventbacktothecontextwherethecallbackwasdefined.

Sincethepatternofregisteringfunctionsasobservershasproventobemoreflexibleandstraightforwardtoprogram,itcanbefoundinprogramminglanguagesoutsidethewebstackaswell.Otherprogramminglanguagesprovideanequivalentfunctionalitythroughlanguagefeaturesorspecialobjectssuchassubroutines,lambdaexpressions,blocks,andfunctionpointers.Forexample,Pythonalsodefinesfunctionsasfirst-classobjectssuchasJavaScript,enablingthemtobeusedascallbacks,whileC#definesDelegatesasaspecialobjecttypeinordertoachievethesameresult.

TheObserverPatternisanintegralpartofdevelopingwebinterfacesthatrespondtouseractions,andeverywebdeveloperhasusedittosomedegree,evenwithoutnoticingit.Thisisbecausethefirstthingthatawebdeveloperneedstodowhilecreatingarichuserinterfaceistoaddeventlistenerstopageelementsanddefinehowthebrowsershouldrespondtothem.

ThisistraditionallyachievedbyusingtheEventTarget.addEventListener()methodonthepageelementsthatweneedtolistentoforeventssuchasa“click”,andprovidingacallbackfunctionwiththecodethatneedstobeexecutedwhenthateventoccurs.ItisworthmentioningthatinordertosupportolderversionsofInternetExplorer,testingfortheexistenceofEventTarget.attachEvent(),andusingthatinstead,isrequired.

NoteFormoreinformationontheaddEventListener()andattachEvent()methods,youcanvisithttps://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListenerandhttps://developer.mozilla.org/en-US/docs/Web/API/EventTarget/attachEvent.

HowitisusedbyjQueryThejQuerylibraryheavilyusestheObserverPatterninseveralpartsofitsimplementation,eitherdirectlybyusingtheaddEventListenermethodorcreatingitsownabstractionoverit.Moreover,jQueryoffersaseriesofabstractionsandconvenientmethodstomakeworkingwiththeObserverPatterneasieronthewebandalsousessomeoftheminternallytoimplementothermethodsaswell.

ThejQueryonmethodThejQuery.fn.on()methodisthecentraljQuerymethodforattachingeventhandlerstoelements,providinganeasywaytoadopttheObserverPattern,whilekeepingourcodeeasytoreadandreason.ItattachestherequestedeventhandleroveralltheelementsofacompositejQuerycollectionobjectreturnedbythe$()function.

SearchingforjQuery.fn.oninjQuery’sSourceViewer(whichisavailableathttp://james.padolsey.com/jquery),ordirectlysearchingjQuery’ssourcecodeforon:function(thefirstcharacterisatab),willleadustothemethod’sdefinition,whichcounts67linesofcode.Actually,thefirst55linesoftheinternalonfunctionarejusthandlingallthedifferentwaysthatthejQuery.fn.on()methodcanbeinvoked;nearitsend,wecanseethatitactuallyusestheinternalmethodjQuery.event.add():

jQuery.fn.extend({

on:function(types,selector,data,fn){

returnon(this,types,selector,data,fn);

}

});

functionon(elem,types,selector,data,fn,one){

/*55linesofcodehandlingthemethodoverloads*/

returnelem.each(function(){

jQuery.event.add(this,types,fn,data,selector);

});

}

ThejQuery.eventobjectistheone-placestopforeventhandlinginjQueryanditsimplementationcountsaround443linesofcode.Itholdsseveralhelperfunctionsformanagingeventssuchasadd,dispatch,fix,handlers,remove,simulate,andtrigger.AllthesefunctionsareusedinternallybyjQueryitselfwherevertheObserverPatternappearsormanagingeventsisrequired.

SearchingforjQuery.event.addinjQuery’sSourceViewerorjQuery.event=directlyinjQuery’ssourcecode,willleadustotherelativelylongimplementationofthehelperfunctionthatcountsaround107linesofcodeinjQueryv2.2.0.Thefollowingcodesnippetshowsatrimmeddownversionofthatmethod,wheresomecoderelatedtothetechnicalimplementationofjQueryandnotrelatedtotheObserverPatternhasbeenremovedforclarity:

add:function(elem,types,handler,data,selector){

/*...4linesofcode…*/

elemData=dataPriv.get(elem);

/*...13linesofcode…*/

//MakesurethatthehandlerhasauniqueID,

//usedtofind/removeitlater

if(!handler.guid){

handler.guid=jQuery.guid++;

}

//Inittheelement'seventstructureandmainhandler,

//ifthisisthefirst

if(!(events=elemData.events)){

events=elemData.events={};

}

/*...9linesofcode…*/

//Handlemultipleeventsseparatedbyaspace

types=(types||"").match(rnotwhite)||[""];

t=types.length;

while(t--){

/*...30linesofcode…*/

//Inittheeventhandlerqueueifwe'rethefirst

if(!(handlers=events[type])){

handlers=events[type]=[];

handlers.delegateCount=0;

//OnlyuseaddEventListenerifthespecialeventshandler

//returnsfalse

if(!special.setup||special.setup.call(elem,data,

namespaces,eventHandle)===false){

if(elem.addEventListener){

elem.addEventListener(type,eventHandle);

}

}

}

/*...9linesofcode…*/

//Addtotheelement'shandlerlist,delegatesinfront

if(selector){

handlers.splice(handlers.delegateCount++,0,handleObj);

}else{

handlers.push(handleObj);

}

/*...3linesofcode…*/

}

}

Now,let’sseehowtheObserverPatternisimplementedbyjQuery.event.add(),byreferringtotheprecedinghighlightedcode.

ThehandlervariableintheargumentsofthejQuery.event.add()methodstoresthefunctionthatwasoriginallypassedasanargumenttothejQuery.fn.on()method.Wecanrefertothisfunctionasourobserverfunction,sinceitisexecutedwhenthe

appropriateeventfiresontheelementthatitwasattachedto.

Inthefirsthighlightedcodearea,jQuerycreatesandassignsaguidpropertytotheobserverfunctionthatisstoredinthehandlervariable.KeepinmindthatassigningpropertiestofunctionsispossibleinJavaScript,sincefunctionsarefirst-classobjects.ThejQuery.guid++statementisexecutedrightaftertheassignmentoftheoldvalueandisrequiredsincejQuery.guidisapage-widecounterusedbyjQueryandjQuerypluginsinternally.TheguidpropertyontheobserverfunctionisusedasawaytoidentifyandlocatetheobserverfunctioninsidetheobserverlistthatjQueryhasforeachelement.Forexample,itisusedbythejQuery.fn.off()methodtolocateandremoveanobserverfunctionfromtheobserverlistassociatedwithanelement.

TipjQuery.guidisapage-widecounterthatisusedbythepluginsandjQueryitselfasacentralizedwaytoretrieveuniqueintegerIDs.ItisoftenusedtoassignuniqueIDstoelements,objects,andfunctions,inordertomakeiteasiertolocatethemincollections.ItistheresponsibilityofeachimplementerthatretrievesandusesthecurrentvalueofjQuery.guidtoalsoincreasethepropertyvalue(byone)aftereachuse.Otherwise,andsincethisisapage-widecounterthatisusedbybothjQuerypluginsandjQuerythemselvesforidentification,thepagewillprobablyfacemalfunctionsthatarehardtodebug.

Inthesecondandthirdhighlightedcodeareas,jQueryinitializesanarraytoholdtheobserverlistsforeachindividualeventthatmayfireonthatelement.OnethingtonoteinthesecondhighlightedcodeareaisthattheobserverlistsfoundintheelemDatavariablearenotapropertyontheactualDOMelement.AsshowninthedataPriv.get(elem)statement,nearthestartofthejQuery.event.add()method,jQueryusesseparatemappingobjectstoholdtheassociationsbetweenDOMelementsandtheirobserverlists.Byusingthisdatacachemechanism,jQueryisabletoavoidpollutingtheDOMelementswiththeextrapropertiesthatareneededbyitsimplementation.

NoteYoucaneasilylocatethedatacachemechanismimplementationinthesourcecodeofjQuerybysearchingforfunctionData().ThiswillbringyoutotheconstructorfunctionoftheDataclassthatisalsofollowedbytheimplementationoftheclassmethodsthataredefinedintheData.prototypeobject.Formoreinformation,youcanvisithttp://api.jquery.com/data.

ThenexthighlightedcodeareaiswherejQuerycheckswhethertheEventTarget.addEventListener()methodisactuallyavailableforthatelementandthenusesittoaddtheeventlistenertotheelement.Inthefinalhighlightedcodearea,jQueryaddstheobserverfunctiontoitsinternallist,whichholdsalltheobserversofthesameeventtypethatareattachedtothatspecificelement.

NoteDependingontheversionyouareusing,youmightgetdifferentresultstosomedegree.

ThemostrecentstablejQueryversionreleasedandusedasreferencewhilewritingthisbookwasv2.2.0.

Incaseyouneedtoprovidesupportforolderbrowsers,forexample,InternetExplorerlowerthanversion9,thenyoushouldusethev1.xversionsofjQuery.Thelatestversionasofthewritingofthisbookwasv1.12.0,whichofferstheexactsameAPIasthev2.2.xversions,butalsohastherequiredcodetoworkonolderbrowsers.

Inordertocovertheimplementationinconsistenciesofolderbrowsers,theimplementationofjQuery.event.add()injQueryv1.xisabitlongerandmorecomplex.OneofthereasonsforthisisbecausejQueryalsoneedstotestwhetherEventTarget.addEventListener()isactuallyavailableinthebrowserthatitisrunningandtrytouseEventTarget.attachEvent()ifthisisnotthecase.

Aswesawintheprecedingcode,thejQueryimplementationfollowstheoperationmodelthattheObserverPatterndescribes,butitalsoincorporatessomeimplementationtricksinordertomakeitworkmoreefficientlywiththeAPIsavailabletowebbrowsers.

Thedocument-readyobserverAnotherconvenientmethodthatjQueryoffers,whichiswidelyusedbydevelopers,isthe$.fn.ready()method.ThismethodacceptsafunctionparameterandexecutesitonlyaftertheDOMtreeofthepagehasbeenfullyloaded.Suchathingcanbeusefulincaseyourcodeisnotloadedlastinthepageandyoudon’twanttoblocktheinitialpagerender,ortheelementsthatitneedstomanipulatearedefinedlaterthanitsown<script>tag.

NoteKeepinmindthatthe$.fn.ready()methodworksslightlydifferentlythanthewindow.onloadcallbackandthe“load”eventofthepage,whichwaituntilalltheresourcesofthepageareloaded.Formoreinformation,youcanvisithttp://api.jquery.com/ready.

Thefollowingcodedemonstratesthemostcommonwaytousethe$.fn.ready()method:

$(document).ready(function(){

/*thiscodewillexecuteonlyafterthepagehasbeenfullyloaded*/

})

IfwetrytolocatetheimplementationofjQuery.fn.ready,wewillseethatitactuallyusesjQuery.ready.promiseinternallytowork:

jQuery.fn.ready=function(fn){

//Addthecallback

jQuery.ready.promise().done(fn);

returnthis;

};

/*…alotlinesofcodeinbetween*/

jQuery.ready.promise=function(obj){

if(!readyList){

readyList=jQuery.Deferred();

//Catchcaseswhere$(document).ready()iscalled

//afterthebrowsereventhasalreadyoccurred.

//Support:IE9-10only

//OlderIEsometimessignals"interactive"toosoon

if(document.readyState==="complete"||(document.readyState!==

"loading"&&!document.documentElement.doScroll)){

//Handleitasynchronouslytoallow…todelayready

window.setTimeout(jQuery.ready);

}else{

//Usethehandyeventcallback

document.addEventListener("DOMContentLoaded",completed);

//Afallbacktowindow.onload,thatwillalwayswork

window.addEventListener("load",completed);

}

}

returnreadyList.promise(obj);

};

Asyoucanseeintheprecedinghighlightedcodeareasoftheimplementation,jQueryusesaddEventListenertoobservewhentheDOMContentLoadedeventisfiredonthedocumentobject.Moreover,toensurethatitwillworkacrossawiderangeofbrowsers,italsoobservesfortheloadeventtobefiredonthewindowobject.

ThejQuerylibraryalsoprovidesshortermethodstoaddtheabovefunctionalityinyourcode.Sincetheaforementionedimplementationdoesnotactuallyneedareferencetothedocument,wecaninsteadjustwrite$().ready(function(){/*...*/}).Therealsoexistsanoverloadofthe$()functionthatachievesthesameresult,whichisusedlike$(function(){/*...*/}).ThesetwoalternativewaystousejQuery.fn.readyhavebeenheavilycriticizedamongdevelopers,sincetheycommonlyleadtomisunderstandings.Thesecond,shorterversioninparticularcanleadtoconfusion,sinceitlookslikeanImmediatelyInvokedFunctionExpression(IIFE),apatternthatJavaScriptdevelopersuseheavilyandhavelearnedtorecognize.Infact,itonlydiffersbyonecharacter($)andasaresult,itsuseisnotsuggestedbeforeadiscussionwiththerestofyourdeveloperteam.

NoteThe$.fn.ready()methodisalsocharacterizedasamethodthatprovidesaneasywaytoimplementtheLazyInitialization/ExecutionPatterninourcode.Thecoreconceptofthispatternistopostponetheexecutionofapieceofcodeorloadaremoteresourceatalaterpointoftime.Forexample,wecanwaitforthepagetobefullyloadeduntilweaddourobserversorwaitforacertaineventtohappenbeforedownloadingawebresource.

DemonstrateasampleusecaseInordertoseetheObserverPatterninaction,wewillcreateanexampleshowcasingaskeletonimplementationofadashboard.Inourexample,theuserwillbeabletoaddinformationboxestohisdashboardrelatedtosomesampleitemsandcategoriesthatareavailableforselectionontheheader.

Ourexamplewillhavethreepredefinedcategoriesforouritems:Products,Sales,andAdvertisements.Eachofthesecategorieswillhaveaseriesofrelateditemsthatwillappearinthearearightbelowthecategoryselector.Theuserwillbeabletoselectthedesiredcategorybyusingadrop-downselectorandthiswillchangethevisibleselectionitemsofthedashboard.

Ourdashboardwillinitiallycontainahintinformationboxaboutthedashboardusage.Wheneverauserclicksononeofthecategoryitems,anewinformationboxwillappearinourthree-columnlayoutdashboard.Intheprecedingimage,theuserhasaddedtwonewinformationboxesforProductBandProductDbyclickingontheassociatedbuttons.

Theuserwillalsobeabletodismissanyoftheseinformationboxesbyclickingonaredclosebuttononthetop-rightofeachinformationbox.Intheprecedingimage,theuserdismissedtheProductDinformationbox,thenaddedinformationboxesfortheAdvertisement3andlaterthe1st,2nd,and3rdweekitemsoftheSalescategory.

Byjustreadingtheabovedescription,wecaneasilyisolatealltheuserinteractionsthatarerequiredfortheimplementationofourdashboard.WewillneedtoaddobserversforeachoneoftheseuserinteractionsandwritecodeinsidethecallbackfunctionsthatexecutetheappropriateDOMmanipulations.

Indetail,ourcodewillneedto:

ObservechangesdonetothecurrentlyselectedelementandrespondtosucheventbyhidingorrevealingtheappropriateitemsObservetheclicksoneachitembuttonandrespondbyaddinganewinformationboxObservetheclicksontheclosebuttonofeachinformationboxandrespondbyremovingitfromthepage

Nowlet’sproceedandreviewtheHTML,CSS,andJavaScriptcoderequiredfortheprecedingexample.Let’sstartwiththeHTMLcodeandforreference,let’ssaythatwesaveditinafilenamedDashboardExample.html,asfollows:

<!DOCTYPEhtml>

<html>

<head>

<title>DashboardExample</title>

<linkrel="stylesheet"type="text/css"href="dashboard-example.css">

</head>

<body>

<h1id="pageHeader">DashboardExample</h1>

<divclass="dashboardContainer">

<sectionclass="dashboardCategories">

<selectid="categoriesSelector">

<optionvalue="0"selected>Products</option>

<optionvalue="1">Sales</option>

<optionvalue="2">Advertisements</option>

</select>

<sectionclass="dashboardCategory">

<button>ProductA</button>

<button>ProductB</button>

<button>ProductC</button>

<button>ProductD</button>

<button>ProductE</button>

</section>

<sectionclass="dashboardCategoryhidden">

<button>1stweek</button>

<button>2ndweek</button>

<button>3rdweek</button>

<button>4thweek</button>

</section>

<sectionclass="dashboardCategoryhidden">

<button>Advertisement1</button>

<button>Advertisement2</button>

<button>Advertisement3</button>

</section>

<divclass="clear"></div>

</section>

<sectionclass="boxContainer">

<divclass="boxsizer">

<articleclass="box">

<headerclass="boxHeader">

Hint!

<buttonclass="boxCloseButton">&#10006;</button>

</header>

Pressthebuttonsabovetoaddinformationboxes…

</article>

</div>

</section>

<divclass="clear"></div>

</div>

<scripttype="text/javascript"src="jquery.js"></script>

<scripttype="text/javascript"src="dashboard-example.js">

</script>

</body>

</html>

IntheprecedingHTML,weplacedallourdashboard-relatedelementsinsidea<div>elementwiththedashboardContainerCSSclass.Thiswillenableustohaveacentricstartingpointtosearchforourdashboard’selementsandalsoscopeourCSS.Insideit,wedefinetwo<section>elementsinordertodividethedashboardintologicalareasusingsomeHTML5semanticelements.

Thefirst<section>withthedashboardCategoriesclassisusedtoholdthecategoriesselectorofourdashboard.Insideit,wehavea<select>elementwiththeIDcategoriesSelectorthatisusedtofilterthevisiblecategoryitemsandthreesubsectionswiththedashboardCategoryclassthatareusedtowrapthe<button>elementsthatwillpopulatethedashboardwithinformationboxeswhenclicked.Twoofthemalsohavethehiddenclasssothatonlythefirstoneisvisiblewhenthepageloadsbymatchingtheinitiallyselectedoption(<option>)ofthecategoryselector.Also,attheendofthefirstsection,wealsoaddeda<div>withtheclearclassthat,aswesawinthefirstchapter,willbeusedtoclearthefloated<button>elements.

Thesecond<section>withtheboxContainerclassisusedtoholdtheinformationboxesofourdashboard.Initially,itcontainsonlyonewithahintabouthowtousethedashboard.Weusea<div>elementwiththeboxsizerclasstosettheboxdimensionsandanHTML5<article>elementwiththeboxclasstoaddtherequiredborderpaddingandshadow,similartotheboxelementsfromthefirstchapter.

Eachinformationbox,besidesitscontent,alsocontainsa<header>elementwiththeboxHeaderclassanda<button>elementwiththeboxCloseButtonclassthat,whenclicked,removestheinformationboxthatcontainsit.Wealsousedthe&#10006;HTMLcharactercodeasthebutton’scontentinordertogetabetter-looking“x”markandavoid

usingaseparateimageforthatpurpose.

Lastly,sincetheinformationboxesarealsofloated,wealsoneeda<div>withtheclearclassattheendoftheboxContainer.

Inthe<head>oftheprecedingHTML,wealsoreferenceaCSSfilenamedasdashboard-example.csswiththefollowingcontent:

.dashboardCategories{

margin-bottom:10px;

}

.dashboardCategoriesselect,

.dashboardCategoriesbutton{

display:block;

width:200px;

padding:5px3px;

border:1pxsolid#333;

margin:3px5px;

border-radius:3px;

background-color:#FFF;

text-align:center;

box-shadow:01px1px#777;

cursor:pointer;

}

.dashboardCategoriesselect:hover,

.dashboardCategoriesbutton:hover{

background-color:#DDD;

}

.dashboardCategoriesbutton{

float:left;

}

.box{

padding:7px10px;

border:solid1px#333;

margin:5px3px;

box-shadow:01px2px#777;

}

.boxsizer{

float:left;

width:33.33%;

}

.boxHeader{

padding:3px10px;

margin:-7px-10px7px;

background-color:#AAA;

box-shadow:01px1px#999;

}

.boxCloseButton{

float:right;

height:20px;

width:20px;

padding:0;

border:1pxsolid#000;

border-radius:3px;

background-color:red;

font-weight:bold;

text-align:center;

color:#FFF;

cursor:pointer;

}

.clear{clear:both;}

.hidden{display:none;}

AsyoucanseeinourCSSfile,firstofallweaddsomespacebelowtheelementwiththedashboardCategoriesclassandalsodefinethesamestylingforthe<select>elementandthebuttonsinsideit.Inordertodifferentiateitfromthedefaultbrowserstyling,weaddsomepadding,aborderwithroundedcorners,adifferentbackgroundcolorwhenhoveringthemousepointer,andsomespaceinbetweenthem.Wealsodefinethatour<select>elementshouldbedisplayedaloneinitsrowasablockandthatthecategoryitembuttonsshouldfloatnexttoeachother.WeagainusetheboxsizerandboxCSSclasses,aswedidinChapter1,ARefresheronjQueryandtheCompositePattern;thefirstonetocreateathree-columnlayoutandthesecondonetoactuallyprovidethestylingofaninformationbox.WecontinuebydefiningtheboxHeaderclassthatisappliedtothe<header>elementsofourinformationboxes,anddefinesomepadding,agreybackgroundcolor,alightshadow,andalsosomenegativemarginssothatitcounterbalancestheeffectofthebox’spaddingsandplacesitselfnexttoitsborder.

Tocompletethestylingoftheinformationboxes,wealsodefinetheboxCloseButtonCSSclassthat(i)floatsthebox’sclosebuttonstotheupper-rightcornerinsidethebox<header>,(ii)definesa20pxwidthandheight,(iii)overridesthedefaultbrowser’s<button>stylingtozeropadding,and(iv)addsasingle-pixelblackborderwithroundedcornersandaredbackgroundcolor.Lastly,likeinChapter1,ARefresheronjQueryandtheCompositePatternwedefinetheclearutilityCSSclasstopreventtheelementfrombeingplacednexttothepreviousfloatingelementsandalsodefinethehiddenclassasaconvenientwayofhidingelementsofthepage.

InourHTMLfile,wereferencethejQuerylibraryitselfandalsoaJavaScriptfilenamedasdashboard-example.jsthatcontainsourdashboardimplementation.Followingthebestpracticesofcreatingperformantwebpages,wehaveplacedthemrightbeforethe</body>tag,inordertoavoiddelayingtheinitialpagerendering:

$(document).ready(function(){

$('#categoriesSelector').change(function(){

var$selector=$(this);

varselectedIndex=+$selector.val();

var$dashboardCategories=$('.dashboardCategory');

var$selectedItem=$dashboardCategories.eq(selectedIndex).show();

$dashboardCategories.not($selectedItem).hide();

});

functionsetupBoxCloseButton($box){

$box.find('.boxCloseButton').click(function(){

$(this).closest('.boxsizer').remove();

});

}

//maketheclosebuttonofthehintboxwork

setupBoxCloseButton($('.box'));

$('.dashboardCategorybutton').on('click',function(){

var$button=$(this);

varboxHtml='<divclass="boxsizer"><articleclass="box">'+

'<headerclass="boxHeader">'+

$button.text()+

'<buttonclass="boxCloseButton">&#10006;'+

'</button>'+

'</header>'+

'Informationboxregarding'+$button.text()+

'</article></div>';

$('.boxContainer').append(boxHtml);

setupBoxCloseButton($('.box:last-child'));

});

});

Wehaveplacedallourcodeinsidea$(document).ready()call,inordertodelayitsexecutionuntiltheDOMtreeofthepageisfullyloaded.Thiswouldbeabsolutelyrequiredifweplacedourcodeinthe<head>element,butitisalsoabestpracticethatisgoodtofollowinanycase.

WefirstaddanobserverforthechangeeventonthecategoriesSelectorelementusingthe`$.fn.change()`method,whichisactuallyashorthandmethodforthe$.fn.on('change',/*…*/)method.InjQuery,thevalueofthethiskeywordinsideafunctionthatisusedasanobserverholdsareferencetotheDOMelementthattheeventwasfired.ThisappliestoalljQuerymethodsthatregisterobservers,fromthecore$.fn.on()tothe$.fn.change()and$.fn.click()convenientmethods.Soweusethe$()functiontomakeajQueryobjectwiththe<select>elementandstoreitinthe$selectorvariable.Then,weuse$selector.val()toretrievethevalueoftheselected<option>andcastittoanumericvaluebyusingthe+operator.Rightafterthis,weretrievethe<section>elementsofdashboardCategoryandcachetheresulttothe$dashboardCategoriesvariable.Then,weproceedbyfindingandrevealingthecategorywhosepositionisequaltothevalueoftheselectedIndexvariableandalsostoretheresultingjQueryobjecttothe$selectedItemvariable.Finally,weareusingthe$selectedItemvariablewiththe$.fn.not()methodtoretrieveandhideallthecategoryelements,exceptfromtheonewejustrevealed.

Inthenextcodesection,wedefinethesetupBoxCloseButtonfunctionthatwillbeusedtoinitializethefunctionalityoftheclosebutton.ItexpectsajQueryobjectwiththeboxelementsasaparameter,andforeachofthem,searchestheirdescendantsforthe

boxCloseButtonCSSclassthatweuseontheclosebuttons.Using$.fn.click(),whichisaconvenientmethodfor$.fn.on('click',/*fn*/),weregisterananonymousfunctiontobeexecutedwheneveraclickeventisfiredthatusesthe$.fn.closest()methodtofindthefirstancestorelementwiththeboxsizerclassandremovesitfromthepage.Rightafterthis,wecallthisfunctiononcefortheboxelementsthatalreadyexistedinthepageatthetimewhenthepagewasloaded.Inthiscase,theboxelementwiththeusagehint.

NoteAnextrathingtokeepinmindwhenusingthe$.fn.closest()methodisthatitbeginstestingthegivenselectorfromthecurrentelementofthejQuerycollectionbeforeproceedingwithitsancestorelements.Formoreinformation,youcanvisititsdocumentationathttp://api.jquery.com/closest.

Inthefinalcodesection,weusethe$.fn.on()methodtoaddanobserverfortheclickeventoneachofthecategorybuttons.Inthiscase,insidetheanonymousobserverfunction,weusethethiskeyword,whichholdstheDOMelementofthe<button>thatwasclicked,andusethe$()methodtocreateajQueryobjectandcacheitsreferenceinthe$buttonvariable.Rightafterthis,weretrievethebutton’stextcontentusingthe$.fn.text()methodandalongwithit,constructtheHTMLcodefortheinformationbox.Fortheclosebutton,weusethe&#10006HTMLcharactercodethatwillberenderedasaprettier“X”icon.ThetemplatewecreatedisbasedontheHTMLcodeoftheinitiallyvisiblehintbox;fortheneedsofthischapter’sexample,weuseplainstringconcatenation.Lastly,weappendthegeneratedHTMLcodeforourboxtotheboxContainer,andsinceweexpectittobethelastelement,weusethe$()functiontofinditandprovideitasaparametertothesetupBoxCloseButton.

HowitiscomparedwitheventattributesBeforetheEventTarget.addEventListener()wasdefinedintheDOMLevel2Eventsspecification,theeventlistenerswereregisteredeitherbyusingtheeventattributesthatareavailableforHTMLelementsortheelementeventpropertiesthatareavailableforDOMnodes.

NoteFormoreinformationontheDOMLevel2Eventspecificationandeventattributes,youcanvisithttp://www.w3.org/TR/DOM-Level-2-Eventsandhttps://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Event_attributes,respectively.

TheeventattributesareasetofattributesthatareavailabletoHTMLelementsandprovideadeclarativewayofdefiningpiecesofJavaScriptcode(preferablyfunctioncalls)thatshouldbeexecutedwhenaspecificeventistriggeredonthatelement.Becauseoftheirdeclarativenatureandhowsimplytheycanbeused,thisisoftenthefirstwaythatnewdevelopersgetintroducedtoeventsinwebdevelopment.

Ifweusedeventattributesintheaboveexample,thentheHTMLcodefortheclosebuttonsintheinformationboxeswilllookasfollows:

<articleclass="box">

<headerclass="boxHeader">

Hint!

<buttononclick="closeInfoBox();"

class="boxCloseButton">&#10006;</button>

</header>

Pressthebuttonsabovetoaddinformationboxes…

</article>

Also,weshouldchangethetemplatethatisusedtocreatenewinformationboxesandexposethecloseInfoBoxfunctiononthewindowobject,inorderforittobeaccessiblefromtheHTMLeventattribute:

window.closeInfoBox=function(){

$(this).closest('.boxsizer').remove();

};

SomeofthedisadvantagesofusingeventattributesovertheObserverPatternare:

ItmakesithardertodefinemultipleseparateactionsthathavetobeexecutedwhenaneventfiresonanelementItmakestheHTMLcodeofthepagebiggerandlessreadableItisagainsttheseparationofconcernsprinciple,sinceitaddsJavaScriptcodeinsideourHTML,possiblymakingabughardertotrackandfixMostofthetime,itleadstothefunctionsbeingcalledintheeventattributegettingexposedtotheglobalwindowobject,thereby“polluting”theglobalnamespace

UsingtheelementeventpropertieswouldnotrequireanychangestoourHTML,keepingalltheimplementationinourJavaScriptfiles.Thechangesrequiredinour

setupBoxCloseButtonfunctionwillmakeitlookasfollows:

functionsetupBoxCloseButton($box){

var$closeButtons=$box.find('.boxCloseButton');

for(vari=0;i<$closeButtons.length;i++){

$closeButtons[i].onclick=function(){

this.onclick=null;

$(this).closest('.boxsizer').remove();

};

}

}

Notethat,forconvenience,wearestillusingjQueryforDOMmanipulations,buttheresultingcodestillhassomeoftheaforementioneddisadvantages.Moreimportantly,inordertoavoidmemoryleaks,wearealsorequiredtoremovethefunctionassignedtotheonclickpropertybeforeremovingtheelementfromthepage,ifitcontainsreferencestotheDOMelementthatitisappliedon.

Usingthetoolsthattoday’sbrowsersoffer,wecanevenmatchtheconveniencethatthedeclarativenatureofeventattributesoffers.Inthefollowingimage,youcanseehowtheFirefoxdevelopertoolsprovideuswithhelpfulfeedbackwhenweusethemtoinspectapageelementthathasaneventlistenerattached:

Asyoucanseeintheprecedingimage,alltheelementsthathaveobserversattachedalsohaveanevsignrightnexttothem,whichwhenclicked,displaysadialogshowingalltheeventlistenersthatarecurrentlyattached.Tomakeourdevelopingexperienceevenbetter,wecandirectlyseethefileandthelinethatthesehandlersweredefinedin.Moreover,wecanclickontheminordertoexpandandrevealtheircode,orclickonthesigninfrontofthemtonavigatetotheirsourceandaddbreakpoints.

OneofthebiggestbenefitsofusingtheObserverPatternovereventattributesisclearlyvisibleinthecasewhereweneedtotakemorethanoneactionwhenacertaineventhappens.Supposethatwealsoneedtoaddanewfeatureinourexampledashboard,whichwouldpreventauserfromaccidentallydouble-clickingacategoryitembuttonandaddingthesameinformationboxtwicetothedashboard.Thenewimplementationshouldideallybecompletelyindependentfromtheexistingone.UsingtheObserverPattern,allweneedtodoisaddthefollowingcodethatobservesforbuttonclicksanddisablesthatbuttonfor700milliseconds:

$(document).ready(function(){

$('.dashboardCategorybutton').on('click',function(){

var$button=$(this);

$button.prop('disabled',true);

setTimeout(function(){

$button.prop('disabled',false);

},700);

});

});

TheprecedingcodeisindeedcompletelyindependentfromthebasicimplementationandwecouldplaceitinsidethesameoradifferentJSfileandloadittoourpage.Thiswouldbemoredifficultwhenusingeventattributes,sinceitwouldrequireustodefinebothactionsatthesametimeinsidethesameeventhandlerfunction;asaresult,itwouldstronglycouplethetwoindependentactions.

AvoidmemoryleaksAswesawearlier,therearesomestrongadvantagesofusingtheObserverPatterntohandleeventsonawebpage.WhenusingtheEventTarget.addEventListener()methodtoaddanobservertoanelement,wealsoneedtokeepinmindthatinordertoavoidmemoryleaks,wealsohavetocalltheEventTarget.removeEventListener()methodbeforeremovingsuchelementsfromthepagesothattheobserversarealsoremoved.

NoteFormoreinformationonremovingeventlistenersfromelements,youcanvisithttps://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener,orforthejQueryequivalentmethod,visithttp://api.jquery.com/off/.

ThejQuerylibrarydevelopersunderstoodthatsuchanimplementationconcerncouldeasilybeforgottenornothandledproperly,therebymakingtheadoptionoftheObserverPatternlookmorecomplex,sotheydecidedtoencapsulatetheappropriatehandlinginsidethejQuery.eventimplementation.Asaresult,whenusinganyeventhandlingjQuerymethod,suchasthecore$.fn.on()oranyoftheconvenientmethodssuchas$.fn.click()or$.fn.change(),theobserverfunctionsaretrackedbyjQueryitselfandareproperlyunregisteredifwelaterdecidetoremovetheelementfromthepage.AswesawearlierintheimplementationofjQuery.event,jQuerystoresareferencetotheobserversofeachelementinaseparatemappingobject.EverytimeweauseajQuerymethodthatremovesDOMelementsfromthepage,itfirstmakessuretoremoveanyobserversattachedtothoseelementsoranyofthedescendantelements,bycheckingthemappingobject.Asaresult,theexamplecodeweusedearlierisnotcausingmemoryleakseventhoughwearenotusinganymethodthatexplicitlyremovestheobserversweaddtothecreatedelements.

TipBecarefulwhenmixingjQueryandplainDOMmanipulations

EventhoughalljQuerymethodskeepyousafefrommemoryleakscausedfromobserversthatareneverunregistered,keepinminditcan’tprotectyouifyouremoveelementsusingplainmethodsfromtheDOMAPI.IfmethodssuchasElement.remove()andElement.removeChild()areusedandtheremovedelementsortheirdescendantshaveobserversattached,thentheyarenotgoingtobeunregisteredautomatically.ThesameapplieswhenassigningtotheElement.innerHTMLproperty.

IntroducingtheDelegatedEventObserverPatternNowthatwehavelearnedsomeadvanceddetailsabouthowtousetheObserverPatternusingjQuery,wewillgetintroducedtoaspecialvariationofitthatfitsperfectlytothewebplatformandprovidessomeextrabenefits.TheDelegatedEventObserverPattern(orsimplyDelegateObserverPattern)isoftenusedinwebdevelopmentanditutilizesthebubblingfeaturethatmosteventsthatarefiredonDOMelementshave.Forexample,whenweclickonapageelement,theclickeventisimmediatelyfiredonit,andrightafterthisitalsofiresonallitsparentelementsuntilitreachestherootofourHTMLdocument.UsingaslightlydifferentoverloadedversionofthejQuery’s$.fn.onmethod,wecaneasilycreateandattachobserversonpageelementsfordelegatedeventsthatarefiredonspecificchildelements.

NoteTheterm“EventDelegation”describestheprogrammingpatternwherethehandlerofaneventisnotattacheddirectlytotheelementofinterest,butisinsteadattachedtooneofitsancestorelements.

HowitsimplifiesourcodeReimplementingourdashboardexampleusingtheDelegatedEventObserverPatternwillrequireustochangeonlythecodeoftheincludedJavaScriptfiletothefollowing:

$(document).ready(function(){

$('#categoriesSelector').change(function(){

var$selector=$(this);

varselectedIndex=+$selector.val();

var$dashboardCategories=$('.dashboardCategory');

var$selectedItem=$dashboardCategories.eq(selectedIndex).show();

$dashboardCategories.not($selectedItem).hide();

});

$('.dashboardCategories').on('click','button',function(){

var$button=$(this);

varboxHtml='<divclass="boxsizer"><articleclass="box">'+

'<headerclass="boxHeader">'+

$button.text()+

'<buttonclass="boxCloseButton">&#10006;'+

'</button>'+

'</header>'+

'Informationboxregarding'+$button.text()+

'</article></div>';

$('.boxContainer').append(boxHtml);

});

$('.boxContainer').on('click','.boxCloseButton',function(){

$(this).closest('.boxsizer').remove();

});

});

Themostobviousdifferenceisthatthenewimplementationisshorter.Thebenefitscomebydefiningjustoneobservertoacommonancestorelement,foreachactionthatappliestomorethanonepageelement.Forthisreason,weusethe$.fn.on(events,selector,handler)overloadvariationofthe$.fn.on()method.

Specifically,weaddanobservertothepageelementwiththedashboardCategoriesCSSclassandlistenfortheclickeventsthatoriginatefromanyofits<button>descendants.Similarly,weaddasingleobservertotheboxContainerelementthatwillbeexecutedwheneveraclickeventfiresonanyofitsdescendantsthatmatchthe.boxCloseButtonCSSselector.

Sincetheaboveobserversapplynotonlytotheelementsthatexistedinthepageatthemomenttheywereregistered,butalsotoanyelementthatisaddedatanylaterpointoftimeandmatchesthespecifiedCSSselector;weareabletodecouplethecodethathandlestheclicksontheclosebuttonsandplaceitinaseparateobserver,insteadofregisteringanewoneeverytimeanewinformationboxisadded.Asaresult,theobserverthataddsthenewinformationboxesinthedashboardissimplerandonlyhastodealwithcreatingtheHTMLoftheboxandinsertitintothedashboard,leadingtoagreaterseparationof

concerns.Moreover,wenolongerneedtohandletheregistrationoftheobserverfortheclosebuttonofthehintboxinaseparatepieceofcode.

ComparethememoryusagebenefitsWewillnowcomparethedifferenceinmemoryusagewhenusingthe$.fn.on()methodwiththesimpleandDelegatedEventObserverPatternvariation.ToachievethiswewillopenthetwoimplementationsofourdashboardexampleandcomparetheirmemoryusageonChrome.ToopenChrome’sdevelopertools,justpressF12andthennavigatetotheTimelinetab.Wepressthe“record”buttonintheChrome’sTimelinetabandthenpresseachcategoryitembutton10times,resultingintheadditionof120informationboxestoourdashboard.Afteraddingalltheboxes,weendupwith121openboxesintotal,sincethehintboxwillstillbeopenandthenstopthetimelinerecording.

TheresultsinthetimelineforourinitialObserverPatternimplementationwilllookasfollows:

RepeatingthesameprocessfortheDelegatedEventObserverPatternimplementationwillgiveasmoothertimeline,revealinglessobjectallocationsandGarbageCollections,asfollows:

Asyoucanseeintheprecedingimages,weendupwith1192pageelementsinbothcases,butinthefirstimplementationweareusing134eventlisteners,ascomparedtotheimplementationwitheventdelegationwhereweinitiallycreatedthreeeventlistenersandneveractuallyaddedanother.

Finally,asyoucanseefromthebluelineinthegraph,thememoryconsumptionofthedelegateversionstayedrelativelythesame,addinguptojustaround200KB.Ontheotherhand,intheoriginalimplementation,theheapsizeincreasedmorethanfivetimes,gainingmorethan1MBofincrease.

Addingsomanyelementsmaynotbeanactualusecase,butthedashboardwillprobablynotbetheonlydynamicpartofyourpage.Asaresult,inarelativelycomplexwebpage,wecouldgetsimilarimprovementsifwereimplementedeveryapplicablepartofitusing

theDelegatedEventObserverPatternvariant.

SummaryInthischapter,welearnedabouttheObserverPattern,howitcanmaketheHTMLcodeofourwebpagescleaner,andthewaythatdecouplesitfromourapplication’scode.WelearnedhowjQueryaddsaprotectionlayertoitsmethodsinordertoprotectusfromundetectedmemoryleaks,whichmayoccurbyaddingobserverstoelements,whennotusingthejQueryDOMmanipulationmethods.

WealsotriedtheDelegatedEventObserverPatternvariantandusedittorewriteourinitialexample.Wecomparedthetwoimplementationsandsawhowitsimplifieswritingcodethatappliestomanypageelementswhentheyaregeneratedafterthepagehasbeenloaded.Finally,wehadacomparisonregardingthememoryconsumptionoftheplainObserverPatternwithitsdelegatevariantandhighlightedhowitalsolessensthememoryconsumptionofourpagebyreducingtherequirednumberofattachedobservers.

NowthatwehavecompletedourintroductiononhowtheObserverPatternisusedtolistentouseractions,wecanmoveontothenextchapterwherewewilllearnaboutcustomeventsandthePublish/SubscribePatternandthewaytheycanleadtoamoredecoupledimplementation.

Chapter3.ThePublish/SubscribePatternInthischapter,wewillshowcasethePublish/SubscribePattern,adesignpatternquitesimilartotheObserverPatternbutwithamoredistinctrolethatisabetterfitformorecomplexusecases.WewillseehowitdiffersfromtheObserverPatternandhowjQueryadoptedsomeofitsconceptsandbroughtthemtoitsObserverPatternimplementation.

Later,wewillproceedandrewriteourpreviouschapter’sexampleusingthispattern.Wewillusethispattern’sbenefitstoaddsomeextrafeaturesandalsoreducethecouplingofourcodewiththeelementsofthewebpage.

Inthischapter,wewill:

IntroducethePublish/SubscribePatternLearnhowitdiffersandwhatadvantagesithasovertheObserverPatternLearnhowjQuerybringssomeofitsfeaturestoitsmethodsLearnhowtoemitcustomeventswithjQueryRewriteandextendtheexamplefromChapter2,TheObserverPattern,usingthispattern

IntroducingthePublish/SubscribePatternThePublish/SubscribePatternisaMessagingPatternwheretheemittersofthemessages,calledthepublishers,multicastmessagestoanumberofrecipients,calledthesubscribers,thathaveexpressedtheirinterestinreceivingsuchmessages.Thekeyconceptofthispattern,whichisalsocommonlyreferredtoasthePub/SubPatterninshort,istoprovideawaytoavoiddependenciesbetweenthepublishersandtheirsubscribers.

Anextraconceptofthispatternistheuseoftopicsthatareusedbythesubscribersinordertoexpressthattheyareonlyinterestedinmessagesofaspecifictype.Thisway,publishersfiltersubscribersbeforesendingamessageanddistributethatmessageonlytotheappropriateones,therebyreducingtheamountoftrafficandworkrequiredonbothsides.

Anothercommonvariantistouseacentral,application-wideobject,knownasthebroker,thatrelaysmessagesproducedbythepublisherstotherelevantsubscribers.Thebroker,inthiscase,actsasawell-knownmessagehandlertosendandsubscribetomessagetopics.Thisenablesus,insteadofcouplingdifferentapplicationpartstogether,toonlyreferencethebrokeritselfandalsothetopicthatourcomponentsareinterestedin.Eventhoughtopicsmightnotbeanabsoluterequirementinthefirstvariantofthispattern,thisvariantplaysanessentialroleinscalabilitysincetherewillcommonlyexistwaylessbrokers(ifnotjustone)thanpublishersandsubscribers.

Byfollowingasubscriptionscheme,thecodeofthepublisheriscompletelydecoupledfromthesubscribers,meaningthatthepublisherdoesnothavetoknowtheobjectsdependonthem.Asaresult,wedonotneedtohardcodetothepublishereachseparateactionthatshouldbeexecutedonthedifferentpartsofourapplication.Instead,thecomponentsofanapplication,andpossiblythird-partyextensions,subscribetobenotifiedonlyabouttopics/eventsthattheyneedtoknow.Insuchdistributedarchitecture,addinganewfeaturetoanexistingapplicationrequiresminimaltonochangestotheapplicationcomponentsitdependson.

HowitdiffersfromtheObserverPatternThemostbasicdifferenceisthat,bydefinition,thePub/SubPatternisaone-way-MessagingPatternthatcanalsopassamessage,unliketheObserverPatternthatjustdescribeshowtonotifytheobserversaboutaspecificstatechangeonthesubject.

Moreover,unliketheObserverPattern,thePub/SubPatternwithabrokerresultsinmorelooselycoupledcodeforthedifferentpartsofanimplementation.Thisisbecausetheobserversneedtoknowtheirsubjectthatisemittingtheevents;however,ontheotherhand,thepublishersandtheirsubscribersonlyneedtoknowthebrokerthatisused.

HowitisadoptedbyjQueryOnceagain,thejQuerylibraryprovidesuswithaconvenientwaytotakeadvantageofthePub/SubPatterninourcode.InsteadofextendingitsAPIbyaddingnewmethodsspecificallynamed“publish”and“subscribe”andintroducingnewconcepts,thedevelopersdecidedtoextendthejQuery.fn.on()andjQuery.fn.trigger()methodswiththeabilitytohandleandemitcustomevents.Thisway,jQuerycanbeusedtoimplementapublisher/subscribercommunicationschemeusingthealreadyknownconvenientmethodsitprovides.

CustomeventsinjQueryCustomeventsallowustousealmostanyuser-definedstringvalueasacommoneventthatwecanaddlistenersfor,andalsomanuallyfireitonpageelements.Asanextrabutapreciousfeature,customeventscanalsocarrysomeextradatatobedeliveredtothelistenersoftheevent.

ThejQuerylibraryaddeditsowncustomeventsimplementation,beforeitwasactuallyaddedtoanywebspecification.Thisway,itwasprovedhowusefultheycanbewhenusedinwebdevelopment.Aswesawinthepreviouschapter,injQuery,thereisaspecificpartoftheimplementationthathandlesboththecommonelementeventandalsocustomevents.ThejQuery.eventobjectholdsalltheinternalimplementationsrelatedtofiringandlisteningtoevents.Also,thejQuery.EventclassisadedicatedwrapperthatjQueryusesfortheneedsofboththecommonelementeventsanditscustomeventsimplementation.

ImplementingaPub/SubschemeusingcustomeventsInthepreviouschapter,wesawhowthejQuery.fn.on()methodcanbeusedtoaddeventlistenersonelements.Wealsosawthatitsimplementationismaintaininglistswiththeaddedhandlersandnotifyingthemwhenrequired.Moreover,theeventnameseemstohavethesamecoordinationpurpose,justlikethetopic.ThisimplementationsemanticsseemtomatchexactlywiththePub/SubPatternaswell.

ThejQuery.fn.trigger()methodactuallyusestheinternaljQuery.event.trigger()methodthatisusedtofireeventsinjQuery.Ititeratesovertheinternalhandlerslistandexecutesthemwiththerequestedeventalongwithanyextraparametersthatthecustomeventdefines.Onceagain,thisalsomatchestheoperationrequirementsofthePub/SubPattern.

Asaresult,jQuery.fn.trigger()andjQuery.fn.on()seemtomatchtheneedsofthePub/SubPatternandcanbeusedinsteadofseparate“publish”and“subscribe”methods,respectively.SincetheyarebothavailableonthejQuery.fnobject,wecanusethesemethodsonanyjQueryobject.ThisjQueryobjectwillactasanintermediateentitybetweenthepublishersandthesubscribers,inawaythatperfectlyalignswiththedefinitionofthebroker.

Agoodcommonpractice,whichisalsousedbyalotofjQueryplugins,istousetheoutermostpageelementthatholdstheimplementationoftheapplicationorthepluginasthebroker.Ontheotherhand,jQueryactuallyallowsustouseanyobjectasabroker,sinceallthatitactuallyneedsisatargettoemitanobserveforourcustomevents.Asaresult,wecouldevenuseanemptyobjectasourbrokersuchas$({}),incaseusingapageelementseemstoorestrictingornotcleanenoughaccordingtothePub/SubPattern.ThisisactuallywhatthejQueryTinyPub/Sublibrarydoes,alongwithsomemethodaliasing,sothatweactuallyusemethodsnamed“publish”and“subscribe”insteadofjQuery’s“on”and“trigger”.FormoreinformationonTiny,youcanvisititsrepositorypageathttps://github.com/cowboy/jquery-tiny-pubsub.

DemonstratingasampleusecaseInordertoseehowthePub/SubPatternisused,andmakeiteasytocompareitwiththeObserverPattern,wearegoingtorewritethedashboardexamplefromChapter2,TheObserverPattern,usingthispattern.Thiswillalsoclearlydemonstratehowthispatterncanhelpusdecoupletheindividualpartsofanimplementationandmakeitmoreextendableandscalable.

UsingPub/SubonthedashboardexampleFortheneedsofthisdemonstration,wewillusetheHTMLandCSSfilesexactlyaswesawtheminChapter2,TheObserverPattern.

Toapplythispattern,wewillonlyneedtochangethecodeintheJavaScriptfilewithournewimplementation.Inthefollowingcodesnippet,wecanseehowthecodewaschangedinordertoadapttothePublisher/SubscriberPattern:

$(document).ready(function(){

window.broker=$('.dashboardContainer');

$('#categoriesSelector').change(function(){

var$selector=$(this);

varmessage={categoryID:$selector.val()};

broker.trigger('dashboardCategorySelect',[message]);

});

broker.on('dashboardCategorySelect',function(event,message){

var$dashboardCategories=$('.dashboardCategory');

varselectedIndex=+message.categoryID;

var$selectedItem=$dashboardCategories.eq(selectedIndex).show();

$dashboardCategories.not($selectedItem).hide();

});

$('.dashboardCategory').on('click','button',function(){

var$button=$(this);

varmessage={categoryName:$button.text()};

broker.trigger('categoryItemOpen',[message]);

});

broker.on('categoryItemOpen',function(event,message){

varboxHtml='<divclass="boxsizer"><articleclass="box">'+

'<headerclass="boxHeader">'+

message.categoryName+

'<buttonclass="boxCloseButton">&#10006;'+

'</button>'+

'</header>'+

'Informationboxregarding'+message.categoryName+

'</article></div>';

$('.boxContainer').append(boxHtml);

});

$('.boxContainer').on('click','.boxCloseButton',function(){

varboxIndex=$(this).closest('.boxsizer').index();

varmessage={boxIndex:boxIndex};

broker.trigger('categoryItemClose',[message]);

});

broker.on('categoryItemClose',function(event,message){

$('.boxContainer.boxsizer').eq(message.boxIndex).remove();

});

});

Justlikeinourpreviousimplementation,weuse$(document).ready()inordertodelaytheexecutionofourcodeuntilthepagehasbeenfullyloaded.Firstofall,wedeclareourbrokerandassignittoanewvariableonthewindowobjectsothatitisgloballyavailableonthepage.Forourapplication’sbroker,weareusingajQueryobjectwiththeoutermostcontainerofourimplementation,whichinourcaseisthe<div>elementwiththedashboardContainerclass.

TipEventhoughusingglobalvariablesisgenerallyananti-pattern,westorethebrokerintoaglobalvariablesinceitisanimportantsynchronizationpointofthewholeapplicationandmustbeavailableforeverypieceofourimplementation,eventothosethatarestoredinseparate.jsfiles.AswewilldiscussinthenextchapterabouttheModulePattern,theprecedingcodecouldbeimprovedbystoringthebrokerasapropertyoftheapplication’snamespace.

Inordertoimplementthecategoryselector,wearefirstobservingthe<select>elementforthechangeevent.Whentheselectedcategorychanges,wecreateourmessageusingaplainJavaScriptobjectwiththevalueoftheselected<option>storedinthecategoryIDproperty.Then,wepublishitinthedashboardCategorySelecttopicusingthejQueryjQuery.fn.trigger()methodonourbroker.Thisway,wemovefromaUIelementeventtoamessagewithapplicationsemanticsthatcontainsalltherequiredinformation.Rightbelow,inoursubscriber’scode,weareusingthejQuery.fn.on()methodonourbrokerwiththedashboardCategorySelecttopicasaparameter(ourcustomevent),justlikewewoulddotolistenforasimpleDOMevent.ThesubscriberthenusesthecategoryIDfromthereceivedmessage,justlikewedidintheimplementationofthepreviouschapter,todisplaytheappropriatecategoryitems.

Followingthesameapproach,wesplitthecodethathandlesaddingandclosinginformationboxesinourdashboardinpublishersandsubscribers.Fortheneedsofthisdemonstration,themessageofthecategoryItemOpentopiccontainsjustthenameofthecategorywewanttoopen.However,inanapplicationwheretheboxcontentisretrievedfromaserver,wewouldprobablyuseacategoryitemIDinstead.Thesubscriberthenusesthecategoryitemnamefromthemessagetocreateandinserttherequestedinformation

box.

Similarly,themessageforthecategoryItemClosetopiccontainstheindexoftheboxthatwewantremoved.OurpublisherusesthejQuery.fn.closest()methodtotraversetheDOMandreachthechildelementsofourboxContainerelementandthenusesthejQuery.fn.index()methodtofinditspositionamongitssiblings.ThesubscriberthenusesjQuery.fn.eq()andtheboxIndexpropertyfromthereceivedmessagetofilterandremoveonlytherequestedinformationboxfromthedashboard.

TipInamorecomplexapplication,insteadoftheboxindex,wecanassociateeachinformationboxelementwithanewlyretrievedjQuery.guidusingamappingobject.Thiswillallowourpublishertousethatguidinthemessageinsteadofthe(DOM-related)elementindex.Thesubscriberwillthensearchthemappingobjectforthatguidinordertolocateandremovetheappropriatebox.

SincewearetryingtodemonstratetheadvantagesofthePub/SubPattern,thisimplementationchangewasnotintroducedinordertoeasethecomparisonwiththeObserverPatternandisinsteadleftasarecommendedexerciseforthereader.

Tosummarizetheabove,weusedthedashboardCategorySelect,categoryItemOpen,andcategoryItemClosetopicsasourapplication-leveleventsinordertodecouplethehandlingoftheuseractionsfromtheirorigin(theUIelement).Asaresult,wenowhavededicatedreusablepiecesofcodethatmanipulateourdashboard’scontent,whichisequivalenttoabstractingthemintoseparatefunctions.Thisallowsustoprogrammaticallypublishaseriesofmessagessothatwecan,forexample,removealltheexistinginformationboxesandaddallthecategoryitemsofthecurrentlyselectedcategory.Alternatively,evenbetter,makethedashboardshowalltheitemsofeachcategoryfor10secondsandthenmovetothenextone.

ExtendingtheimplementationInordertodemonstratethescalabilitythatthePub/SubPatternbringswithit,wewillextendourcurrentexamplebyaddingacounterwiththenumberofboxesthatarecurrentlyopeninthedashboard.

Forthecounterimplementation,wewillneedtoaddsomeextraHTMLtoourpageandalsocreateandreferenceanewJavaScriptfiletoholdthecounterimplementation:

...

</section>

<divstyle="margin-left:5px;">

Openboxes:

<outputid="dashboardItemCounter">1</output>

</div>

<sectionclass="boxContainer">

...

IntheHTMLpageoftheexample,wewillneedtoaddanextra<div>elementtoholdourcounterandsomedescriptiontext.Forourcounter,weareusingan<output>element,whichisasemanticHTML5elementidealtopresentresultsofuseractions.Thebrowserwilluseitjustlikeanormal<span>element,soitwillappearrightnexttoitsdescription.Also,sincethereisinitiallyahintboxopeninourdashboard,weusea1foritsinitialcontent:

$(document).ready(function(){

broker.on('categoryItemOpencategoryItemClose',function(event,

message){

var$counter=$('#dashboardItemCounter');

varcount=parseInt($counter.text());

if(event.type==='categoryItemOpen'){

$counter.text(count+1);

}elseif(event.type==='categoryItemClose'&&count>0){

$counter.text(count-1);

}

});

});

Forthecounterimplementationitself,allweneedtodoisaddanextrasubscribertothedashboard’sbroker,whichisgloballyavailabletootherJavaScriptfilesloadedinthepage,sincewehaveattachedittothewindowobject.Wearesimultaneouslysubscribingtotwotopics,bypassingthemspacedelimitedtothejQuery.fn.on()method.Rightafterthis,welocatethecounter<output>elementthathastheIDdashboardItemCounterandparseitstextcontentasanumber.Inordertodifferentiateouraction,basedonthetopicthatthemessagehasreceived,weusetheeventobjectthatjQuerypassesasthefirstparametertoouranonymousfunction,whichisoursubscriber.Specifically,weusethetypepropertyoftheeventobjectthatholdsthetopicnameofthemessagethatwasreceivedandbasedonitsvalue,wechangethecontentofthecounter.

NoteFormoreinformationontheeventobjectthatjQueryprovides,youcanvisithttp://api.jquery.com/category/events/event-object/.

Similarly,wecouldalsorewritethecodethatpreventsaccidentaldouble-clicksonthecategoryitembuttons.AllthatisneededistoaddanextrasubscriberforthecategoryItemOpentopicandusethecategoryNamepropertyofthemessagetolocatethepressedbutton.

UsinganyobjectasabrokerWhileinourexampleweusedtheoutermostcontainerelementofourdashboardforourbroker,itisalsocommontousethe$(document)objectasabroker.Usingtheapplication’scontainerelementisconsideredagoodsemanticpractice,whichalsoscopestheemittedevents.

Aswedescribedearlierinthischapter,jQueryactuallyallowsustouseanyobjectasabroker,evenanemptyone.Asaresult,wecouldinsteadusesomethingsuchaswindow.broker=$({});forourbroker,incasewepreferitoverusingapageelement.

Byusingnewlyconstructedemptyobjects,wecanalsoeasilycreateseveralbrokers,incasesuchathingwouldbepreferredforaspecificimplementation.Moreover,incaseacentralizedbrokerisnotpreferred,wecouldjustmakeeachpublisherthebrokerofitself,leadingtoanimplementationmorelikethefirst/basicvariantofthePub/SubPattern.

Sinceinmostcases,adeclaredvariableisusedtoaccesstheapplication’sbrokerwithinapage,thereislittledifferencebetweentheaboveapproaches.Justchoosetheonethatbettermatchesyourteam’staste,andincaseyouchangeyourmindatalaterpoint,allyouhavetodoisuseadifferentassignmentonyourbrokervariable.

UsingcustomeventnamespacingAsaclosingnoteforthischapter,wewillpresent,inshort,themechanismthatjQueryprovidesfornamespacingcustomevents.Themainbenefitofeventnamespacingisthatitallowsustousemorespecificeventnamesthatbetterdescribetheirpurpose,whilealsohelpingustoavoidconflictsbetweendifferentimplementationpartsandplugins.Italsoprovidesaconvenientwaytounbindalltheeventsofagivennamespacefromanytarget(elementorbroker).

Asimpleexampleimplementationwilllookasfollows:

varbroker=$({});

broker.on('close.dialog',function(event,message){

console.log(event.type,event.namespace);

});

broker.trigger('close.dialog',['messageEmitted']);

broker.off('.dialog');

//removesalleventhandlersofthe"dialog"namespace

Formoreinformation,youcanvisitthedocumentationpageathttp://docs.jquery.com/Namespaced_Eventsandthearticleathttps://css-tricks.com/namespaced-events-jquery/fromtheCSS-Trickswebsite.

SummaryInthischapter,wewereintroducedtothePublish/SubscribePattern.WesawitssimilaritieswiththeObserverPatternandalsolearneditsbenefitsbydoingacomparisonofthetwo.WeanalyzedhowthemoredistinctrolesandtheextrafeaturesthatthePublish/SubscribePatternoffersmakeitanidealpatternformorecomplexusecases.WesawhowjQuerydevelopersadoptedsomeofitsconceptsandbroughtthemtotheirObserverPatternimplementationascustomevents.Finally,werewrotetheexamplefromthepreviouschapterusingthePublish/SubscribePattern,addingsomeextrafeaturesandalsoachievinggreaterdecouplingbetweenthedifferentpartsandpageelementsofourapplication.

NowthatwehavecompletedourintroductiontohowthePublish/SubscribePatterncanbeusedasafirststeptodecouplethedifferentpartsofanimplementation,wecanmoveontothenextchapterwherewewillbeintroducedtotheModulePattern.Inthenextchapter,wewilllearnhowtoseparatethedifferentpartsofanimplementationintoindependentmodulesandhowtousenamespacingtoachievebettercodeorganizationanddefineastrictAPItoachievecommunicationbetweenthedifferentmodules.

Chapter4.DivideandConquerwiththeModulePatternInthischapter,wewillbeintroducedtotheconceptsofModulesandNamespacingandseehowtheycanleadtomorerobustimplementations.Wewillshowcasehowthesedesignprinciplescanbeusedinapplications,bydemonstratingsomeofthemostcommonlyuseddevelopmentpatternstocreateModulesinJavaScript.

Inthischapter,wewill:

ReviewtheconceptofModulesandNamespacingIntroducetheObjectLiteralPatternIntroducetheModulePatternanditsvariantsIntroducetheRevealingModulePatternanditsvariantsHaveasmalldiveintoES5StrictModeandES6ModulesExplainhowModulescanbeusedandbenefitjQueryapplications

ModulesandNamespacesThetwomainpracticesofthischapterareModulesandNamespaces,whichareusedtogetherinordertostructureandorganizeourcode.WewillfirstanalyzethemainconceptofModulesthatiscodeencapsulationandrightafterthis,wewillproceedtoNamespacing,whichisusedtologicallyorganizeanimplementation.

EncapsulatinginternalpartsofanimplementationWhiledevelopingalarge-scaleandcomplexwebapplication,theneedforawell-defined,structuredarchitecturebecomesclearfromthebeginning.Inordertoavoidcreatingaspaghetticodeimplementation,wheredifferentpartsofourcodecalleachotherinachaoticway,wehavetosplitourapplicationintosmall,self-containedparts.

Theseself-containedpiecesofcodecanbedefinedasModules.Todocumentthisarchitectureprinciple,ComputerSciencehasdefinedconceptssuchasSeparationofConcerns,wheretherole,operation,andtheexposedAPIofeachModuleshouldbestrictlydefinedandfocusedonprovidingagenericsolutiontoaspecificproblem.

NoteFormoreinformationonEncapsulationandSeparationofConcerns,youcanvisithttps://developer.mozilla.org/en-US/docs/Glossary/Encapsulationandhttp://aspiringcraftsman.com/2008/01/03/art-of-separation-of-concerns/.

AvoidingglobalvariableswithNamespacesInJavaScript,thewindowobjectisalsoknownastheGlobalNamespace,whereeachdeclaredvariableandfunctionidentifierisattachedbydefault.ANamespacecanbedefinedasanamingcontextwhereeachidentifierhastobeunique.ThemainconceptofNamespacingistoprovideawaytologicallygroupalltherelatedpiecesofadistinctandself-containedpartofanapplication.Inotherwords,itsuggeststhatwecreategroupswithrelatedfunctionsandvariablesandmakethemaccessibleunderthesameumbrellaidentifier.ThishelpstoavoidnamingcollisionsbetweendifferentpartsofanapplicationandotherJavaScriptlibrariesthatareused,sinceweonlyneedtokeepalltheidentifiersuniqueundereachdifferentNamespace.

AgoodexampleofNamespacingisthemathematicalfunctionsandconstantsthatJavaScriptprovides,whicharegroupedunderthebuilt-inJavaScriptobjectcalledMath.SinceJavaScriptprovidesmorethan40short-namedmathematicalidentifiers,suchasE,PI,andfloor(),inordertoavoidnamingconflictsandgroupingthemtogether,itwasdesignedtomakethemaccessibleaspropertiesoftheMathobjectthatactsastheNamespaceofthisbuilt-inlibrary.

WithoutproperNamespacing,eachfunctionandvariableneedstobeuniquelynamedthroughtheentireapplication,andcollisionscouldhappenbetweentheidentifiersofdifferentapplicationpartsorevenwiththoseofathird-partylibrarythatanapplicationuses.Finally,whileModulesprovideawaytoisolateeachindependentpartofyourapplication,NamespacingprovidesawaytostructureyourdifferentModulestowhatbecomesthearchitectureoftheapplication.

ThebenefitsofthesepatternsDesigninganapplicationarchitecturebasedonModulesandnamespacingleadstobettercodeorganizationandclearlyseparatedparts.Insucharchitectures,Modulesareusedtogrouptogetherpartsoftheimplementationthatarerelated,whileNamespacesconnectthemtoeachothertocreatetheapplicationstructure.

Thisarchitecturehelpstocoordinatelargedeveloperteams,enablingtheimplementationofindependentpartstotakeplaceinparallel.Itcanalsoshortenthedevelopmenttimeneededtoaddanewfunctionalitytotheexistingimplementation.Thisisbecausetheexistingpiecesthatareusedcanbelocatedeasilyandtheaddedimplementationhaslesschanceofconflictingwiththeexistingcode.

Theresultingcodestructuresarenotonlycleanlyseparated,butsinceeachModuleisdesignedtoachieveasinglegoal,thereisagoodchancethatitcanalsobeusedinothersimilarapplications.Asanaddedbenefit,sincetheroleofeachModuleisstrictlydefined,italsomakestracingtheoriginofabugaloteasierinalargecodebase.

ThewideacceptanceBoththecommunityandtheenterpriseworldrealizedthat,inordertohavemaintainable,largefrontendapplicationswritteninJavaScript,theyshouldendupwithasetofbestpracticesthatshouldbeincorporatedineverypartoftheirimplementations.

TheacceptanceandadoptionofModulesandNamespacinginJavaScriptimplementationsisclearlyvisibleinthebestpracticesandcodingstyleguidesthatthecommunityandenterpriseshavereleased.

Forexample,Google’sJavaScriptStyleGuide(availableathttps://google.github.io/styleguide/javascriptguide.xml#Naming)describesandsuggestsadoptingnamespacinginourimplementations:

ALWAYSprefixidentifiersintheglobalscopewithauniquepseudonamespacerelatedtotheprojectorlibrary.

Moreover,thejQueryJavaScriptStyleGuide(availableathttps://contribute.jquery.org/style-guide/js/#global-variables)suggestsusingglobalvariablessothat:

Eachprojectmayexposeatmostoneglobalvariable.

Anotherexampleofacceptanceamongthedevelopercommunity,comesfromtheMozillaDeveloperNetwork.Itsguideforobject-orientedJavaScript(availableathttps://developer.mozilla.org/en-US/docs/Web/JavaScript/Introduction_to_Object-Oriented_JavaScript#Namespace)alsosuggestsusingNamespaces,towraptheimplementationofourapplicationunderasingleexposedvariable,usingsomethingassimpleasfollows:

//globalnamespace

varMYAPP=MYAPP||{};

TheObjectLiteralPatternTheObjectLiteralPatternisprobablythesimplestwaytowrapalltherelatedpartsofanimplementationunderanumbrellaobjectthatworksasaModule.Thenameofthispatternaccuratelydescribesthewayitisused.ThedeveloperjustneedstodeclareavariableandassignanobjectwithalltherelatedpartsthatneedtobeencapsulatedintothisModule.

Let’sseehowwecancreateaModulethatprovidesuniqueintegerstoapage,inasimilarwayhowjquery.guiddoesit:

varsimpleguid={

guid:1,

init:function(){

this.guid=1;

},

increaseCounter:function(){

this.guid++;

//orsimpleguid.guid++;

},

getNext:function(){

varnextGuid=this.guid;

this.increaseCounter();

returnnextGuid;

}

};

Asseenabove,asimplerulethatyoucanfollowinordertoadoptthispatternistodefineallthevariablesandfunctionsthateachimplementationneedsaspropertiesofanobject.OurcodeisreusableanddoesnotpollutetheGlobalNamespace,otherthanjustdefiningasinglevariablenameforourModule,simpleguidinthiscase.

WecanaccesstheModulepropertiesinternally,eitherbyusingthethiskeyword,suchasthis.guid,orusingthefullnameoftheModulesuchassimpleguid.guid.InordertousetheaboveModuleinourcode,wejustneedtoaccessitspropertybyusingitsname.Forexample,callingthesimpleguid.getNext()methodwillreturntoourcodethenext-in-ordernumericguidandalsochangetheModule’sstatebyincreasingtheinternalcounter.

OneofthenegativesofthispatternisthatitdoesnotprovideanyprivacytotheinternalpartsoftheModule.AlltheinternalpartsoftheModulecanbeaccessedandbeoverriddenbyexternalcode,eventhoughweideallyprefertoonlyexposethesimpleguid.init()andsimpleguid.getNext()methods.Thereareseveralnamingconventionsthatdescribeprependingorappendinganunderscore(_)tothenamesofpropertiesthatareintendedonlyforinternaluse,butthistechnicallydoesn’tfixthisdisadvantage.

AnotherdisadvantageisthatwritingabigModuleusinganobjectliteralcaneasilygettiring.It’struethatJavaScriptdevelopersareusedtoendtheirvariablesandfunctiondefinitionswithsemicolons(;),andtryingtowriteabigModuleusingcommas(,)aftereachpropertycaneasilyleadtosyntacticerrors.

EventhoughthispatternmakesiteasytodeclarenestedNamespacesforaModule,itcanalsoleadtobigcodestructureswithbadreadabilityincaseweneedseverallevelsofnesting.Forexample,let’stakealookatthefollowingskeletonofaTodoapplication:

varmyTodoApp={

todos:[],

addTodo:function(todo){this.todos.push(todo);},

getTodos:function(){returnthis.todos;},

updateTodo:function(todo){/*...*/},

imports:{

fromGDrive:function(){/*...*/},

fromUrl:function(){/*...*/},

fromText:function(){/*...*/}

},

exports:{

gDrivePublicKey:'#wnanqAASnsmkkw',

toGDrive:function(){/*...*/},

toFile:function(){/*...*/},

},

share:{

toTwitter:function(todo){/*...*/}

}

};

Fortunately,thiscanbeeasilyfixedbysplittingtheobjectliteraltomultipleassignmentsforeachsubmodule(andpreferablytodifferentfiles)asfollows:

varmyTodoApp={

todos:[],

addTodo:function(todo){this.todos.push(todo);},

getTodos:function(){returnthis.todos;},

updateTodo:function(todo){/*...*/},

};

/*…*/

myTodoApp.exports={

gDrivePublicKey:'#wnanqAASnsmkkw',

toGDrive:function(){/*...*/},

toFile:function(){/*...*/},

};

/*...*/

TheModulePatternThekeyconceptofthebasicModulePatternistoprovideasimplefunction,class,orobjectthattherestoftheapplicationcanuse,throughawell-knownvariablename.ItenablesustoprovideaminimalAPIforaModule,byhidingthepartsoftheimplementationthatdonotneedtobeexposed.Thisway,wealsoavoidpollutingtheGlobalNamespacewithvariablesandutilityfunctionsthatareneededforinternalusebyourModule.

TheIIFEbuildingblockInthissubsection,wewillgetasmallintroductiontotheIIFEDesignPatternsinceit’sanintegralpartforallthevariantsoftheModulePatternthatwewillseeinthischapter.TheImmediatelyInvokedFunctionExpression(IIFE)isaverycommonlyusedDesignPatternamongJavaScriptdevelopersbecauseofthecleanwayinwhichitisolatesblocksofcode.IntheModulePattern,anIIFEisusedtowrapalltheimplementationinordertoavoidpollutingtheGlobalNamespaceandprovideprivacytothedeclarationstotheModuleitself.

EachIIFEcreatesaClosurewiththevariablesandfunctionsdeclaredinsideit.TheClosurethatiscreatedenablestheexposedfunctionoftheIIFEtokeepreferencestotherestofthedeclarationsoftheirenvironmentandaccessthemnormallywhenexecutedfromotherpartsofanimplementation.Asaresult,thenon-exposeddeclarationsoftheIIFEdonotleakoutsideit,butarekeptprivateandareaccessibleonlybythefunctionsthatarepartofthecreatedClosure.

NoteFormoreinformationonIIFEsandClosures,youcanvisithttps://developer.mozilla.org/en-US/docs/Glossary/IIFEandhttps://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures.

AnIIFEismostcommonlyusedasfollows:

(function(){

varx=7;

console.log(x);

//prints7

})();

Sincetheprecedingcodeconstructmightlookbizarreonfirstsight,let’sseethepiecesthatitiscomposedfrom.AnIIFEisalmostequivalenttodeclaringananonymousfunction,assigningittoavariable,andthenexecutingit,asshowninthefollowingcode:

vartmp=function(){

varx=7;

console.log(x);

};

tmp();

//or

(tmp)();

Intheprecedingcode,wedefineafunctionexpressionandexecuteitusingtmp().Since,inJavaScript,wecanuseparenthesesaroundanidentifierwithoutchangingitsmeaning,wecanalsoexecutethestoredfunctionwith(tmp)();.Thefinalstep,inordertoturntheprecedingcodeintoanIIFE,istoreplacethetmpvariablewiththeactualanonymousfunctiondeclaration.

Aswesawearlier,theonlydifferenceisthat,withanIIFE,wedoneedtodeclarea

variablejusttoholdthefunctionitself.Weonlycreateananonymousfunctionandinvokeitimmediatelyrightafterdefiningit.

SincethecreationofanIIFEcanbeachievedinseveralways,whichmightlooklikeanexerciseofJavaScript’srules,thecommunityofJavaScriptdevelopershasconcludedtotheabovecodestructureasapointofreferenceforthispattern.ThiswayofcreatinganIIFEisconsideredtohavebetterreadabilityandisusedbylargelibrariesandasaresultofitsadoption,developerscaneasilyrecognizeitinsidelargeJavaScriptimplementations.

Anexampleoftheless-widely-usedwaystocreateanIIFEisthefollowingcodestructure:

(function(){

//code

}());

ThesimpleIIFEModulePatternSincethereisnoactualnameforthispattern,itisrecognizedbythefactthatthedefinedModulereturnsasingleentity.Forreferenceonhowtocreateareusablelibraryusingthispattern,wewillrewritethesimpleguidModulethatwesawearlier.Theresultingimplementationwilllookasfollows:

varsimpleguid=(function(){

varsimpleguid={};

varguid;

simpleguid.init=function(){

guid=1;

};

simpleguid.increaseCounter=function(){

guid++;

};

simpleguid.getNext=function(){

varnextGuid=guid;

this.increaseCounter();

returnnextGuid;

};

simpleguid.init();

returnsimpleguid;

})();

ThispatternusesanIIFEtodefineanobjectthatactsastheModulecontainer,attachespropertiestoit,andlaterreturnsit.ThevariablesimpleguidinthefirstlineoftheprecedingcodeisusedastheNamespaceoftheModuleandisassignedwiththevaluethatisreturnedbytheIIFE.ThemethodsandpropertiesthataredefinedonthereturnedobjectaretheonlyexposedpartsoftheModulesandconstituteitspublicAPI.

Onceagain,thispatternallowsustousethethiskeyword,inordertoaccesstheexposedmethodsandpropertiesofourModule.Furthermore,italsoprovidestheflexibilitytoexecuteanyrequiredinitializationcodebeforecompletingtheModule’sdefinition.

UnliketheObjectLiteralPattern,theModulePatternenablesustocreateactualprivatemembersinourModules.VariablesdeclaredinsidetheIIFE,thatarenotattachedtothereturnvalue,suchastheguidvariable,actasprivatemembersandareonlyaccessibleinsidetheModulebyrestmembersofthecreatedClosure.

Lastly,incaseweneedtodefineanestedNamespace,allwehavetodoischangetheassignmentofthevaluereturnedbytheIIFE.Asanexampleofanapplicationstructuredwithsubmodules,let’sseehowwewilldefinetheexportingsubmodulefortheTodoapplicationskeletonthatwesawearlier:

varmyTodoApp=(function(){

varmyTodoApp={};

vartodos=[];

myTodoApp.addTodo=function(todo){

todos.push(todo);

};

myTodoApp.getTodos=function(){

returntodos;

};

returnmyTodoApp;

})();

myTodoApp.exports=(function(){

varexports={};

vargDrivePublicKey='#wnanqAASnsmkkw';

exports.toGDrive=function(){/*...*/};

exports.toFile=function(){/*...*/};

returnexports;

})();

Giventhatourapplication’sNamespacemyTodoApphasalreadybeendefinedearlier,theexportssubmodulecanbedefinedasasimplepropertyonit.AgoodpracticetofollowwillbetocreateonefileforeachoneoftheaboveModules,usingtheIIFEsasthelandmarkstosplityourcode.Awidelyusednamingconvention,whichisalsosuggestedbyGoogle’sJavaScriptStyleGuide,istouselowercasenamingforyourfilesandadddashestoseparatesubmodules.Forexample,byfollowingthisnamingconvention,theprecedingcodeshouldbedefinedintwofilesnamedasmytodoapp.jsandmytodoapp-exports.jsforeachModule,respectively.

HowitisusedbyjQueryTheModulePatternisusedwithinjQueryitself,inordertoisolatethesourcecodeoftheCSSselectorengine(Sizzle),whichpowersthe$()function,fromtherestofthejQuerysource.Fromthebeginning,SizzlewasabigpartofthejQuerysource,whichiscurrentlycountingabout2135linesofcode;since2009,ithasbeensplitintoaseparateprojectnamedSizzle,soitcanbemoreeasilymaintained,bedevelopedindependently,andbereusablebyotherlibraries:

varSizzle=(function(window){

/*179linesofcode*/

functionSizzle(selector,context,results,seed){

/*131linesofcode*/

}

/*

1804linesofcode,definingmethodslike:

Sizzle.attr

Sizzle.compile

Sizzle.contains

Sizzle.getText

Sizzle.matches

Sizzle.matchesSelector

Sizzle.select

*/

returnSizzle;

})(window);

jQuery.find=Sizzle;

SizzleisaddedtothejQuery’ssourceinsideanIIFE,whileitsmainfunctionisreturnedandassignedtojQuery.findforuse.

NoteFormoreinformationonSizzle,youcanvisithttps://github.com/jquery/sizzle.

TheNamespaceParameterModulevariantInthisvariant,insteadofreturninganobjectfromourIIFEandthenassigningittothevariablethatactsastheNamespaceoftheModule,wecreatetheNamespaceandpassitasaparametertotheIIFEitself:

(function(simpleguid){

varguid;

simpleguid.init=function(){

guid=1;

};

simpleguid.increaseCounter=function(){

guid++;

};

simpleguid.getNext=function(){

varnextGuid=guid;

this.increaseCounter();

returnnextGuid;

};

simpleguid.init();

})(window.simpleguid=window.simpleguid||{});

ThelastlineoftheModuledefinitiontestswhethertheModuleisalreadydefined;incaseitisnot,itinitializesittoanemptyobjectliteralandassignsittotheglobalobject(window).Inanycase,thesimpleguidparameterinthefirstlineoftheIIFEwillholdtheModule’sNamespace.

NoteTheaboveexpressionisalmostequivalenttowriting:

window.simpleguid=window.simpleguid!==undefined?window.simpleguid:

{};

UsingthelogicalORoperator(||)makestheexpressionbothshorterandmorereadable.Moreover,thisisapatternthatmostwebdevelopershavelearnedtoeasilyrecognize,anditappearsinalotofdevelopmentpatternsandbestpractices.

Onceagain,thispatternallowsustousethethiskeywordtoaccesspublicmembersfromwithintheexportedmethodsoftheModule.Atthesametime,itallowsustokeepsomefunctionsandvariablesprivate,whichwillbeaccessibleonlybyotherfunctionsoftheModule.

Eventhoughit’sconsideredagoodpracticetodefineeachModuletoitsownJSfile,thisvariantalsoallowsustosplittheimplementationoflargeModulestomorethanonefile.ThisbenefitcomesasaresultofcheckingwhethertheModuleisalreadydefined,beforeinitializingittoanemptyobject.Thismightbeusefulinsomecases,withtheonlylimitationbeingthateachpartialfileofaModulecanaccesstheprivatemembersdefinedinitsownIIFE.

Moreover,inordertoavoidrepetition,wecanuseasimpleridentifierfortheparameteroftheIIFEandwriteourModuleasfollows:

(function(namespace){

/*…*/

namespace.getNext=function(){

varnextGuid=guid;

this.increaseCounter();

returnnextGuid;

};

namespace.init();

})(window.simpleguid=window.simpleguid||{});

WhenitcomestoapplicationswithnestedNamespaces,thispatternmightstartfeelingalittleuncomfortabletoread.ThelastlineoftheModuledefinitionwillstarttogetlongerforeveryextralevelofnestednamespacingthatwedefine.Forexample,let’sseehowtheexportssubmoduleofourTodoapplicationwouldlook:

(function(exports){

vargDrivePublicKey='#wnanqAASnsmkkw';

exports.toGDrive=function(){/*...*/};

exports.toFile=function(){/*...*/};

})(myTodoApp.exports=myTodoApp.exports||{});

Asyoucansee,eachextralevelofthenestedNamespaceneedstobeaddedonbothsidesoftheassignmentthatispassedasaparametertotheIIFE.ForapplicationswithcomplexfeaturesthatleadtomultiplelevelsofnestedNamespaces,thiscouldleadtoModuledefinitionslookingsomethinglikethis:

(function(smallModule){

smallModule.method=function(){/*...*/};

returnsmallModule;

})(myApp.bigFeature.featurePart.smallModule=

myApp.bigFeature.featurePart.smallModule||{});

Moreover,ifwewanttoprovidethesamesafetyguaranties,asintheoriginalcodesample,thenwewouldneedtoaddsimilarsafechecksforeachNamespacelevel.Withthisinmind,theexportsModuleofourTodoapplicationthatwesawearlierwouldneedtohavethefollowingform:

(function(exports){

vargDrivePublicKey='#wnanqAASnsmkkw';

exports.toGDrive=function(){/*...*/};

exports.toFile=function(){/*...*/};

})((window.myTodoApp=window.myTodoApp||{},myTodoApp.exports=

myTodoApp.exports||{}));

Asseenintheprecedingcode,weusedthecommaoperator(,)toseparateeachnamespaceexistencecheckandwrappedthewholeexpressioninanextrapairofparenthesissothatthewholeexpressionisusedasthefirstparameteroftheIIFE.Usingthecommaoperator(,)tojoinexpressionswillleadthemtobeevaluatedinorderandpasstheresultofthelastevaluatedexpressionastheparameteroftheIIFE,andthatresultwillbeusedastheNamespaceoftheModule.Keepinmindthat,foreachextranestedNamespacelevel,weneedtoaddanextraexistencecheckexpressionusingthecommaoperator(,).

Adisadvantageofthispattern,especiallywhenusedfornestednamespacing,isthattheNamespacedefinitionoftheModuleisattheendofthefile.EventhoughitishighlyrecommendedtonameyourJSfilessothattheyproperlyrepresenttheModulesthattheycontain,forexample,mytodoapp.exports.js;nothavingtheNamespacenearthetopofthefilecansometimesbecounterproductiveormisleading.Aneasywork-aroundforthisproblemwouldbetodefinetheNamespacebeforetheIIFEandthenpassitasaparameter.Forexample,theprecedingcodeusingthistechniquewouldbetransformedtosomethingasfollows:

window.myTodoApp=window.myTodoApp||{};

myTodoApp.exports=myTodoApp.exports||{};

(function(exports){

vargDrivePublicKey='#wnanqAASnsmkkw';

exports.toGDrive=function(){/*...*/};

exports.toFile=function(){/*...*/};

})(myTodoApp.exports);

TheIIFE-containedModulevariantLikeinthepreviousvariantsoftheModulePattern,thisvariantdoesnotactuallyhaveaspecificvariantname,butisrecognizedbythewaythecodeisstructured.ThekeyconceptofthisvariantistomovealltheModule’scodeinsidetheIIFE:

(function(){

window.simpleguid=window.simpleguid||{};

varguid;

simpleguid.init=function(){

guid=1;

};

simpleguid.increaseCounter=function(){

guid++;

};

simpleguid.getNext=function(){

varnextGuid=guid;

this.increaseCounter();

returnnextGuid;

};

simpleguid.init();

})();

ThisvariantlooksverysimilartothepreviousoneandmainlydiffersinthewaythattheNamespaceiscreated.Firstofall,itkeepstheNamespacecheckandinitializationnearthetopoftheModule,likeaheading,makingourcodemorereadableregardlessofwhetherweuseaseparatefilefortheModuleornot.LikeothervariantsoftheModulePattern,itsupportsprivatemembersforourModulesandalsoallowsustousethethiskeywordtoaccesspublicmethodsandproperties,makingourcodelookmoreobject-oriented.

RegardingimplementationswithnestedNamespaces,thecodestructureoftheexportssubmoduleofourTodoapplicationskeletonwilllookasfollows:

(function(){

window.myTodoApp=window.myTodoApp||{};

myTodoApp.exports=myTodoApp.exports||{};

vargDrivePublicKey='#wnanqAASnsmkkw';

myTodoApp.exports.toGDrive=function(){/*...*/};

myTodoApp.exports.toFile=function(){/*...*/};

})();

Asseenintheprecedingcode,wealsoborrowedtheNamespacedefinitionchecksfromthepreviousvariantand,likewise,appliedittoeverylevelofnestednamespacing.Even

thoughthisisnotabsolutelynecessary,itbringsthebenefitsthatwediscussedearliersuchasenablingustosplitaModuledefinitionintoseveralfilesandevenresultsinamoreerror-tolerantimplementationregardingtheimportorderoftheapplication’sModules.

TheRevealingModulePatternTheRevealingModulePatternisavariantoftheModulePatternwithaknownandwidelyrecognizedname.WhatmakesthispatternspecialisthatitcombinesthebestpartsoftheObjectLiteralPatternandtheModulePattern.AllthemembersoftheModulearedeclaredinsideanIIFE,whichattheend,returnsanObjectLiteralcontainingonlythepublicmembersoftheModuleandisassignedtothevariablethatactsasourNamespace:

varsimpleguid=(function(){

varguid=1;

functioninit(){

guid=1;

}

functionincreaseCounter(){

guid++;

}

functiongetNext(){

varnextGuid=guid;

increaseCounter();

returnnextGuid;

}

return{

init:init,

getNext:getNext

};

})();

OneofthemainbenefitsofthispatternthatdifferentiatesitfromothervariantsisthatitallowsustowriteallthecodeofourModuleinsidetheIIFE,justlikewewouldiftheywouldbedeclaredontheGlobalNamespace.Moreover,thispatterndoesnotrequireanyvariationonthewaythatthepublicandprivatemembersaredeclared,makingthecodeoftheModulelookuniform.

SincethereturnedObjectLiteraldefinesthepubliclyavailablemembersoftheModule,itisalsoaconvenienteasywaytoinspectitspublicAPI,evenifitiswrittenbysomeoneelse.Moreover,incaseweneedtoexposeaprivatemethodonourModule’sAPI,allweneedtodoisaddanextrapropertytothereturnedObjectLiteralwithoutchanginganypartofitsdefinition.Additionally,theuseofanObjectLiteralenablesustochangetheexposedidentifiersfortheModule’sAPI,withoutchangingthenamesusedbytheModule’simplementationinternally.

Evenifthisisnotclearlyvisible,thethiskeywordcanbeusedforcallsbetweenthepublicmembersoftheModule.Unfortunately,usingthethiskeywordisdiscouragedforthispattern,sinceitbreakstheuniformityofthefunctiondeclarationsandcaneasilyleadtoerrors,especiallywhenchangingthevisibilityofapublicmethodtoprivate.

SincetheNamespacedefinitioniskeptoutsidethebodyoftheIIFE,thispatternclearlyseparatestheNamespacedefinitionfromtheactualimplementationoftheModule.UsingthispatterntodefineaModuleinanestedNamespacedoesnotaffecttheModule’simplementation,whichwillnotlookdifferentatanypointfromatop-levelNamespaceModule.RewritingtheexportssubmoduleofourTodoskeletonapplicationusingthispatternwillmakeitlooklikethis:

myTodoApp.exports=(function(){

vargDrivePublicKey='#wnanqAASnsmkkw';

functiontoGDrive(){/*...*/}

functiontoFile(){/*...*/}

return{

toGDrive:toGDrive,

toFile:toFile

};

})();

Asaresultofthisseparation,wehavelesscoderepetitionandwecaneasilychangetheNamespaceofaModulewithoutaffectingitsimplementationatall.

UsingES5StrictModeAsmallbutpreciousadditiontoalltheModulePatternsthatuseIIFEsastheirbasicbuildingblocks,istheuseofStrictModeforJavaScriptexecution.ThiswasstandardizedinthefiftheditionofJavaScript,andisanopt-inexecutionmodewithslightlydifferentsemantics,inordertopreventsomeofthecommonpitfallsofJavaScript,butalsohavingbackwardscompatibilityinmind.

Underthismode,theJavaScriptruntimeenginewillpreventyoufromaccidentallycreatingaglobalvariableandpollutingtheGlobalNamespace.Eveninnot-so-largeapplications,itisquitepossiblethatavardeclarationbeforetheinitialassignmentofavariablecanbemissing,automaticallypromotingthattoaglobalvariable.Topreventthiscase,strictmodethrowsanerrorincaseanassignmentisissuedtoanundeclaredvariable.ThefollowingimageshowtheerrorthatisthrownbyFirefoxandChromewhenaStrictModeviolationhappens.

Thismodecanbeenabledbyaddingthe"usestrict";or'usestrict';statementbeforeanyotherstatements.Eventhoughthiscanbeenabledontheglobalscope,itishighlyrecommendedthatyouenableitonlyinsidethescopeofafunction.Enablingitontheglobalscopemightmakethird-partylibrariesthatarenon-strict-modecompliantstopworkingormisbehave.Ontheotherhand,thebestplacetoenableStrictModeisinsidetheIIFEofaModule.TheStrictModewillberecursivelyappliedtoallnestedNamespaces,methods,andfunctionsofthatIIFE.

NoteFormoreinformationonJavaScript’sstrictexecutionmode,youcanvisithttps://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode.

IntroducingES6ModulesEventhoughJavaScriptinitiallyhadnobuilt-inpackagingandnamespacingsupportlikeotherprogramminglanguages,webdevelopersfilledthegapsbydefiningandadoptingsomedesignpatternsforthispurpose.ThesesoftwaredevelopmentpracticesworkedaroundthemissingfeaturesofJavaScriptandallowedlargeandscalableimplementationsofcomplexapplicationsonaprogramminglanguagethatsomeyearsagowasmostlyusedforformvalidation.

Thiswasuntilthe6thversionofJavaScript,commonlyreferredtoasES6,wasreleasedasastandardonJune2015andintroducedtheconceptofModulesaspartofthelanguage.

NoteES6isanabbreviationofECMAScript6thedition,whichisalsoreferredtoasHarmonyorECMAScript2015,whereECMAScriptisthetermthatisusedforthestandardizationprocessofJavaScript.Thespecificationcanbefoundathttp://www.ecma-international.org/ecma-262/6.0/index.html#sec-modules.

AsanexampleofES6Modules,wewillseeoneofthemanywaysinwhichthesimpleguidModulecanbewritten:

vares6simpleguid={};

exportdefaultes6simpleguid;

varguid;

es6simpleguid.init=function(){

guid=1;

};

es6simpleguid.increaseCounter=function(){

guid++;

};

es6simpleguid.getNext=function(){

varnextGuid=guid;

this.increaseCounter();

returnnextGuid;

};

es6simpleguid.init();

Ifwesavethisasafilenamedes6simpleguid.js,thenwecanimportanduseitinadifferentfilebysimplywritingthefollowingcode:

importes6simpleguidfrom'es6simpleguid';

console.log(es6simpleguid.getNext());

SinceES6ModulesarebydefaultinStrictMode,writingyourModulestodayusingyourpreferredModulePatternvariantwithStrictModeenabledwillmakeyourtransitiontoES6Moduleseasier.Someoftheabovepatternsrequireveryfewchangestoachievethis.

Forexample,intheIIFE-containedModulePatternvariant,allthatisneededisremovetheIIFEandthe"usestrict";statement,replacethecreationoftheModule’sNamespacewithavariable,andusetheexportkeywordonit.

Unfortunately,atthetimeofwritingthisbook,nobrowserhas100%supportforES6Modules.Asaresult,specialloadersortoolsthattranspileES6toES5arerequiredsothatwecanstartwritingourcodeusingthenewfeaturesofES6.

NoteFormoreinformation,youcanvisitES6Moduleloader’sdocumentationpageathttps://github.com/ModuleLoader/es6-module-loader,andBabeltranspiler(earlierknownasES6toES5)athttp://babeljs.io/.

UsingModulesinjQueryapplicationsInordertodemonstratehowtheModulePatterncanleadtoabetterapplicationstructure,wewillreimplementthedashboardexamplethatwesawinthepreviouschapters.Wewillincludeallthefunctionalitiesthatwehaveseenuntilnow,includingthecounteroftheopeninformationboxes.TheHTMLandCSScodeusedisexactlythesameasinthepreviouschapterand,asaresult,ourdashboardlooksexactlythesameasbefore:

Forthisdemonstration,wewillrefactorourJavaScriptcodeintofoursmallModulesusingthesimpleIIFE-containedModulevariant.ThedashboardModulewillactasthemainentryofcodeexecutionandalsoasthecentralcoordinationpointofthedashboardapplication.Thecategoriessubmodulewillberesponsiblefortheimplementationoftheupper-toppartofourdashboard.Thisincludescategoryselection,thepresentationofappropriatebuttons,andthehandlingofbuttonclicks.TheinformationBoxsubmodulewillberesponsibleforthemainpartofourdashboard.Itwillprovidemethodstocreateandremoveinformationboxesfromthedashboard.Finally,thecountersubmodulewillberesponsibleforkeepingthefieldwiththenumberofthecurrentlyopeninformationboxesup-to-date,respondingtotheuseractions.

AsinglechangethatweneedtomaketotheHTMLofthepageinordertosupportthismultimodulearchitectureislimitedtothewayinwhichtheJavaScriptfilesareincluded:

<scripttype="text/javascript"src="jquery.js"></script>

<scripttype="text/javascript"src="dashboard.js"></script>

<scripttype="text/javascript"src="dashboard.categories.js"></script>

<scripttype="text/javascript"src="dashboard.informationbox.js">

</script>

<scripttype="text/javascript"src="dashboard.counter.js"></script>

TipEvenifthismultifilestructuremakesthedevelopmentanddebuggingprocessesaloteasier,itisrecommendedthatwecombineallthesefilesbeforemovingourapplicationtoaproductionenvironment.Severaltoolsspecializedforthisjobexist;forexample,theverysimpleandeffectivegrunt-contrib-concatprojectthatisavailableathttps://github.com/gruntjs/grunt-contrib-concat.

ThemaindashboardmoduleTheresultingcodeforthedashboardmodulewilllookasfollows:

(function(){

'usestrict';

window.dashboard=window.dashboard||{};

dashboard.$container=null;

dashboard.init=function(){

dashboard.$container=$('.dashboardContainer');

dashboard.categories.init();

dashboard.informationBox.init();

dashboard.counter.init();

};

$(document).ready(dashboard.init);

})();

Aswealreadymentioned,thedashboardmodulewillbethecentralpointofourapplication.Sincethisisthestartingpointofexecutionforourapplication,itsmaindutyistodoalltherequiredinitializationsforitselfandeachsubmodule.Theinvocationoftheinit()methodiswrappedinsideacalltothe$(document).ready()methodsothatitsexecutionisdelayeduntiltheDOMtreeofthepageisfullyloaded.

Oneimportantthingtonoteisthat,duringtheinitialization,wedoaDOMtraversalinordertofindthecontainerelementofthedashboardandstoreittoapublicpropertyoftheModulenamed$container.ThiselementwillbeusedbyallthemethodsofthedashboardthatneedtoaccesstheDOMtree,inordertoscopetheircodeinsidethatcontainerelement,removingtheneedtoconstantlytraversethewholeDOMtreeusingcomplexselectors.KeepingreferencestokeyDOMelementsandreusingtheminthedifferentsubmodules,canmaketheapplicationsnappierandalsolessenthechanceofaccidentallyinterferingwiththerestofthepage;thus,leadingtolessbugsthatarealsoeasiertoresolve.

TipCacheelementsbutavoidmemoryleaks.

KeepinmindthatmaintainingreferencestoDOMelementsthatareconstantlyaddedandremovedfromthepageaddsextracomplexitytoourapplication.Thiscanevenleadtomemoryleaksincaseweareaccidentallykeepingareferencetoanelementthathasalreadybeenremovedfromthepage.Forsuchelements,suchastheinformationboxes,itmightbesaferandmoreeffectivetohavedelegatedhandlingfortheeventstriggeredonthemandtodoascopedDOMtraversalwhenneeded,inordertoretrieveajQueryobjectwithfreshreferencesoftheelements.

ThecategoriesmoduleLet’sproceedwiththecategoriessubmodule:

(function(){

'usestrict';

dashboard.categories=dashboard.categories||{};

dashboard.categories.init=function(){

dashboard.$container.find('#categoriesSelector').change(function()

{

var$selector=$(this);

varcategoryIndex=+$selector.val();

dashboard.categories.selectCategory(categoryIndex);

});

dashboard.$container.find('.dashboardCategories').on('click',

'button',function(){

var$button=$(this);

varitemName=$button.text();

dashboard.informationBox.openNew(itemName);

});

};

dashboard.categories.selectCategory=function(categoryIndex){

var$dashboardCategories=

dashboard.$container.find('.dashboardCategory');

var$selectedItem=$dashboardCategories.eq(categoryIndex).show();

$dashboardCategories.not($selectedItem).hide();

};

})();

Thissubmodule’sinitializationmethodusesthereferencetothe$containerelementthatthemainModuleprovidesandaddstwoobserverstothepage.Thefirsthandlesthechangeeventonthe<select>categoryandcallstheselectCategory()methodwiththenumericvalueoftheselectedcategory.TheselectCategory()methodofthissubmodulewillthenhandlerevealingtheappropriatecategoryitems,decouplingitfromtheeventhandlingcodeandmakingitareusablefunctionalityavailabletotheentireapplication.

Rightafterthis,wecreateasingleDelegatedEventObserverthathandlestheclickeventonthe<button>categoryitem.Itextractsthetextofthe<button>pressedandcallstheopenNew()methodoftheinformationBoxsubmodulethatcontainsalltheimplementationrelatedtoinformationboxes.Inanon-demogradeapplication,aparametertosuchamethodwouldprobablybeanidentifierinsteadofatextvaluethatwouldbeusedtoretrievemoredetailsfromaremoteserver.

TheinformationBoxmoduleTheinformationBoxsubmodulethatcontainstheimplementationpartsrelatedtothemainareaofourdashboardhasthefollowingform:

(function(){

'usestrict';

dashboard.informationBox=dashboard.informationBox||{};

var$boxContainer=null;

dashboard.informationBox.init=function(){

$boxContainer=dashboard.$container.find('.boxContainer');

$boxContainer.on('click','.boxCloseButton',function(){

var$button=$(this);

dashboard.informationBox.close($button);

});

};

dashboard.informationBox.openNew=function(itemName){

varboxHtml='<divclass="boxsizer"><articleclass="box">'+

'<headerclass="boxHeader">'+

itemName+

'<buttonclass="boxCloseButton">&#10006;'+

'</button>'+

'</header>'+

'Informationboxregarding'+itemName+

'</article></div>';

$boxContainer.append(boxHtml);

};

dashboard.informationBox.close=function($boxElement){

$boxElement.closest('.boxsizer').remove();

};

})();

Thefirstthingthatthissubmodule’sinitializationcodedoesisretrieveandstoreareferenceofthecontainerthatholdstheinformationboxestothe$boxContainervariable,usingthe$containerpropertyofthedashboardforscoping.

TheopenNew()methodisresponsibleforcreatingtheHTMLrequiredforanewinformationboxandaddingittothedashboardusingthe$boxContainervariable,whichactslikeaprivatememberoftheModule,andisusedforcachingthereferenceofthepreviouslyassignedDOMelement.Thisisagoodpracticethatcanimprovetheapplication’sperformance,sincethestoredelementisneverremovedfromthepageandisusedduringtheinitializationandtheopenNew()methodsoftheModule.Thisway,wenolongerneedtoexecuteslowDOMtraversalseverytimetheopenNew()methodiscalled.

Theclose()method,ontheotherhand,isresponsibleforremovinganexistinginformationboxfromthedashboard.ItreceivesajQuerycompositecollectionobjectasa

parameterrelatedtothetargetinformationbox,whichisbasedonthewaythatthe$.fn.closest()methodworks,andcaneitherbetheboxelementcontaineroranyofitsdescendants.

TipImplementationsofmethodsthatprovideflexibilityregardingthewaythattheycanbecalledcanmakethemusablebymorepartsofalargeapplication.Thenextlogicalstepforthismethod,whichisleftasanexercisetothereader,wouldbetomakeitacceptasaparameter,theindex,oranidentifieroftheinformationboxthatneedstobeclosed.

ThecountermoduleLastly,hereishowwerewrotethecounterimplementation,whichwesawinthepreviouschapter,asanindependentsubmodule:

(function(){

'usestrict';

dashboard.counter=dashboard.counter||{};

vardashboardItemCounter;

var$counter;

dashboard.counter.init=function(){

$counter=$('#dashboardItemCounter');

var$boxContainer=dashboard.$container.find('.boxContainer');

varinitialCount=$boxContainer.find('.boxsizer').length;

dashboard.counter.setValue(initialCount);

dashboard.$container.find('.dashboardCategories').on('click',

'button',function(){

dashboard.counter.setValue(dashboardItemCounter+1);

});

$boxContainer.on('click','.boxCloseButton',function(){

dashboard.counter.setValue(dashboardItemCounter-1);

});

};

dashboard.counter.setValue=function(value){

dashboardItemCounter=value;

$counter.text(dashboardItemCounter);

};

})();

Forthissubmodule,weareusingthe$countervariableasaprivatemembertocacheareferencetotheelementthatdisplaysthecount.AnotherprivatememberoftheModuleisthedashboardItemCountervariable,whichatanypointoftimewillholdthenumberofvisibleinformationboxesinthedashboard.KeepingsuchinformationonthemembersofourModulesreducesthetimesweneedtoreachtheDOMtreetoextractinformationonthestateoftheapplication,makingtheimplementationmoreefficient.

TipPreservingthestateoftheapplicationinthepropertiesofJavaScriptobjectsorModulesinsteadofreachingtheDOMtoextractthem,isaverygoodpracticethatmakestheapplication’sarchitecturemoreobject-oriented,andisalsoadoptedbymostofthemodernwebdevelopmentframeworks.

DuringtheinitializationoftheModule,wearegivinganinitialvaluetoourcountervariablesothatwearenolongerdependentontheinitialHTMLofthepageandhavea

morerobustimplementation.Moreover,weareattachingtwoDelegatedEventObservers,oneforclicksthatwillleadtothecreationofnewinformationboxesandanotheroneforclicksthatwillclosethem.

OverviewoftheimplementationWiththeabove,wecompletedtherewriteofthedashboardskeletonapplicationtoamodulararchitecture.Alltheavailableactionsareexposedaspublicmethodsofeachofoursubmodulesthatcanbeinvokedprogrammaticallyandthiswaytheyaredecoupledfromtheeventsthattriggerthem.

Agoodexerciseforthereaderwouldbetopromotethedecouplingevenfurther,byalsoadoptingthePublisher/SubscriberPatternintheaboveimplementation.ThefactthatthecodeisalreadystructuredintoModuleswillmakesuchchangealoteasiertoimplement.

Anotherpartthatcanbeimplementedinadifferentwayisthewayinwhichthesubmodulesareinitialized.InsteadofexplicitlyorchestratingtheinitializationofeachModuleinourmaindashboardModule,wecouldinsteadinitializeeachsubmoduleonitsownbywrappingtheinvocationoftheinit()methodina$(document).ready()callandissuingitsinitializationrightafteritsdeclaration.Ontheotherhand,nothavingacentralpointtocoordinatetheinitializationsandrelyingonpageeventscanfeellessdeterministic.AnotherwaytoimplementitwouldbelikethePublisher/SubscriberPattern,byexposingaregisterForInit()methodonourmainModule,whichwouldkeeptrackoftheModulesthathavebeenrequestedtobeinitializedusinganarray.

NoteFormorejQuerycodeorganizationtips,youcanvisithttp://learn.jquery.com/code-organization/concepts/.

SummaryInthischapter,welearnedtheconceptsofModulesandNamespacesandalsothebenefitsthatcomefromtheiradoptioninlargeapplications.Wehadanin-depthanalysisofthemostwidelyadoptedpatternsandcomparedtheirbenefitsandlimitations.WelearnedbyexamplehowtodevelopModulesusingtheObjectLiteralPattern,thevariantsoftheModulePattern,andtheRevealingModulePattern.

WecontinuedwithasmallintroductiontoES5’sStrictModeandsawhowitcanbenefittoday’sModules.ThenweproceededbylearningsomedetailsaboutthestandardizedbutnotyetwidelysupportedES6Modules.Lastly,wesawhowthearchitectureofthedashboardapplicationcanchangedramaticallyafterusingtheModulePatterninitsimplementation.

NowthatwehavecompletedourintroductiononhowtouseModulesandNamespaces,wecanmoveontothenextchapterwherewewillbeintroducedtothefacadepattern.Inthenextchapter,wewilllearnaboutthephilosophyoffacadesandtheuniformwaythattheydefinehowcodeabstractionsshouldbecreatedsothattheyareeasilyunderstandableandreusablebyotherdevelopers.

Chapter5.TheFacadePatternInthischapter,wewillshowcasetheFacadePattern,astructuraldesignpatternthattriestodefineauniformwayregardinghowdevelopersshouldcreateabstractionsintheircode.Initially,wewillusethispatterntowrapcomplexAPIsandexposesimpleronesthatfocusontheneedsofourapplication.WewillseehowjQueryembracestheconceptsofthispatterninitsimplementation,howitachievesencapsulatingcompleximplementationsthatareintegralpartsofthewebdeveloper’stool-beltintoeasy-to-useAPI’s,andhowthisplaysacriticalroleforitswideadoption.

Inthischapter,wewill:

IntroducetheFacadePatternDocumentitskeyconceptsandbenefitsSeehowjQueryusesitinitsimplementationWriteanexampleimplementationwhereFacadesareusedtocompletelyabstractanddecoupleathird-partylibrary

IntroducingtheFacadePatternTheFacadeisastructuralsoftwaredesignpatternthatdealswithhowabstractionsofthevariouspartsofanimplementationshouldbecreated.ThekeyconceptoftheFacadePatternistoabstractanexistingimplementationandprovideasimplifiedAPIthatbettermatchestheusecasesofthedevelopedapplication.AccordingtomostComputerSciencebibliographiesdescribingthispattern,aFacadeismostcommonlyimplementedasaspecializedclassthatisusedtosegmenttheimplementationofanapplicationintosmallerpiecesofcode,whileprovidinganinterfacethatcompletelyhidestheencapsulatedcomplexity.Inthewebdevelopmentworld,itisalsocommontouseplainobjectsorfunctionsfortheimplementationofaFacade,takingadvantageofthewayinwhichJavaScripttreatsfunctionsasobjects.

Inapplicationsthathaveamodularstructure,liketheexamplesofthepreviouschapter,itisalsocommontoimplementFacadesasseparatemoduleswiththeirownnamespace.Moreover,foralargerimplementationwithverycomplexparts,anapproachwithmultiplelevelsofFacadescanalsobefollowed.Onceagain,theFacadeswillbeimplementedasmodulesandsubmodules,havingthetop-levelFacadeorchestratingthemethodsofitssubmodules,whileprovidinganAPIthatcompletelyhidesthecomplexityoftheentiresubsystem.

ThebenefitsofthispatternMostofthetime,theFacadePatternisadoptedforimplementationpartsthathavearelativelyhighdegreeofcomplexityandareusedinseveralplacesofanapplication,whereinlargepiecesofcodecanbereplacedwithasimplecalltothecreatedFacade,leadingnotonlytolesscoderepetition,butalsohelpingustoincreasethereadabilityoftheimplementation.SincetheFacademethodsareusuallynamedbythehigher-levelapplicationconceptsthattheyencapsulate,theresultingcodeisalsoeasiertounderstand.ThesimplifiedAPIthataFacadeprovidesthroughitsconvenientmethods,leadstoanimplementationthatiseasiertouse,understand,andalsowriteunittestsfor.

Moreover,havingFacadestoabstractcompleximplementationsprovesitsusefulnessincaseswherethereisaneedtointroduceachangetothebusinesslogicoftheimplementation.IncaseaFacadehasawell-designedAPIwithapredictionforfuturerequirements,suchchangescanoftenrequiremodificationsjusttotheFacade’scode,leavingtherestoftheapplication’simplementationuntouchedandfollowingtheSeparationofConcernsprinciple.

Inthesamemanner,usingFacadestoabstracttheAPIofathird-partylibrarytobettermatchtheneedsofeachapplication,providesadegreeofdecouplingbetweenourcodeandtheusedlibrary.Incasethethird-partylibrarychangesitsAPIorneedstobereplacedwithanotherone,thedifferentmodulesoftheapplicationwillnotneedtoberewritten,sincetheimplementationchangeswouldbelimitedtothewrapperFacade.Inthiscase,allthatisneededistoprovideanequivalentimplementationusingthenewlibraryAPIwhilekeepingtheFacade’sAPIintact.

Asanexampleoforchestratingmethodcallsandusingsensibledefaultsforspecificusecases,takealookatthefollowingsampleimplementation:

functiondo(x,y){

varz=y-x/2;

varyy=Math.pow(y,2);

varb=3*Math.random();//addsomerandomnesstotheresult

vari=0;//forthiscase

returnLibraryA.doingMethod(x,z,i,yy,b);

}

HowitisadoptedbyjQueryAverylargepartofthejQueryimplementationisdedicatedtoprovidingsimpler,shorter,andmoreconvenient-to-usemethodsforthingsthatthedifferentJavaScriptAPIsalreadyallowustoachieve,butwithmorelinesofcodeandeffort.BytakingalookattheprovidedAPIsofjQuery,wecandistinguishsomegroupsofrelatedmethods.Thisgroupingcanalsobeseeninthewayinwhichthesourcecodeisstructured,placingmethodsforrelatedAPIsneartoeachother.

EvenifthewordFacadedoesnotappearinjQuery’ssourcecode,theuseofthispatterncanbewitnessedbythewayinwhichtherelatedmethodsaredefinedontheexposedjQueryobject.Mostofthetime,therelatedmethodsthatformagroupareimplementedanddefinedaspropertiesonanObjectLiteralandthenattachedtothejQueryobjectwithasinglecalltothe$.extend()orthe$.fn.extend()method.Asyoumightremember,fromthebeginningofthischapter,thismatchesalmostexactlywiththeimplementationthatComputerSciencecommonlyusestodescribehowaFacadeisimplemented,withtheexceptionthat,inJavaScript,wecancreateaplainobjectwithoutneedingtofirstdefineaclass.Asaresult,jQueryitselfcanbeseenasacollectionofFacades,whereeachoneindependentlyaddsgreatvaluetothelibrarywiththeAPIofconvenientmethodsthatitprovides.

NoteFormoreinformationon$.extend()and$.fn.extend(),youcanvisithttp://api.jquery.com/jQuery.extend/andhttp://api.jquery.com/jQuery.fn.extend/.

SomeoftheabstractedAPIgroupsthatarebigpartsofthejQueryimplementationandplayacriticalroletoitsadoptionareasfollows:

TheDOMTraversalAPITheAJAXAPITheDOMManipulationAPITheEffectsAPI

Also,agreatexampleofhowthispatterncanbeusedtoprovidesimplifiedAPIsisjQuery’sEventsAPI,whichprovidesavarietyofconvenientmethodsforthemostcommonusecasesthatareeasiertousethantherespectiveplainJavaScriptAPIs.

ThejQueryDOMTraversalAPIAtthetimethatjQuerywasreleased,webdeveloperscouldlocatespecificDOMelementsofapageonlybyusingtheverylimitedgetElementById()andgetElementsByTagName()methods,sinceothermethods,suchasgetElementsByClassName(),werenotwidelysupportedbytheexistingbrowsers.ThejQueryteamrealizedhowthewebdevelopmentcouldbeleveragediftherewasasimpleAPIthatwouldeasesuchDOMtraversals,whichwouldworkthesamewayacrossallbrowsers,beaseffectiveasthefamiliarCSSSelectors,anddidtheirbesttomakesuchanimplementationareality.

TheresultofthiseffortisthenowfamousjQueryDOMTraversalAPIthatisexposedthroughthe$()function,whichplayedaseriousroleinthestandardizationofthequerySelectorAll()methodaspartoftheLevel2SelectorAPI.TheimplementationunderthehoodusesthemethodsprovidedbytheDOMAPIandcountsabout2,135linesofcodeinjQueryv2.2.0,whileitisevenbiggerinthev1.xversionsthatneededtosupportolderbrowsersaswell.Aswesawinthischapter,becauseofitscomplexitythisimplementationisnowpartofaseparatestand-aloneprojectthatisnamedSizzle.

NoteFormoreinformationonSizzleandthequerySelectorAll()method,youcanvisithttps://github.com/jquery/sizzleandhttps://developer.mozilla.org/en-US/docs/Web/API/document/querySelectorAll.

Regardlessofitscompleximplementation,theexposedAPIsarequiteeasytouse,mostlyusingsimpleCSSSelectorsasstringparameters,makingitanexcellentexampleofhowaFacadecanbeusedtocompletelyhidethecomplexityofitsinnerworkingsandexposeaconvenientAPI.SinceSizzle’sAPIisstillquitecomplex,thejQuerylibraryactuallywrapsitwithitsownAPIactingasanextraFacadelevel:

//Line733

functionSizzle(selector,context,results,seed){/*...*/}

//Line2678

jQuery.find=Sizzle;

ThejQuerylibraryfirstkeepsareferenceofSizzletotheinternaljQuery.find()methodandthenusesittoimplementallitsexposedDOMTraversalmethods,whichworkonCompositeObjectssuchas$.fn.find():

//Line2769

jQuery.fn.extend({

find:function(selector){

/*15linesofcode*/

for(i=0;i<len;i++){

jQuery.find(selector,self[i],ret);

}

/*3linesofcode*/

returnret;

}

});

Finally,thefamous$()functioncanactuallybeinvokedinseveralways,butevenwhenitisinvokedwithaCSSSelectorasastringparameter,itactuallyhasanextralevelofhiddencomplexity:

//Line71

jQuery=function(selector,context){

returnnewjQuery.fn.init(selector,context);

};

//Line2825

rquickExpr=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,

//Line2735

init=jQuery.fn.init=function(selector,context,root){

/*12linesofcode*/

if(typeofselector==="string"){

if(/*...*/){

/*3linesofcode*/

}else{

match=rquickExpr.exec(selector);

}

//Matchhtmlormakesurenocontextisspecifiedfor#id

if(match&&(match[1]||!context)){

if(match[1]){

/*27linesofcode*/

//HANDLE:$(#id)

}else{

elem=document.getElementById(match[2]);

//Support:Blackberry4.6

//gEBIDreturnsnodesnolongerinthedocument(#6963)

if(elem&&elem.parentNode){

//InjecttheelementdirectlyintothejQueryobject

this.length=1;

this[0]=elem;

}

this.context=document;

this.selector=selector;

returnthis;

}

//HANDLE:$(expr,$(...))

}elseif(!context||context.jquery){

return(context||root).find(selector);

//HANDLE:$(expr,context)

//(whichisjustequivalentto:$(context).find(expr)

}else{

returnthis.constructor(context).find(selector);

}

}/*else…21linesofcode*/

};

Asyoucansee,intheprecedingcode,the$()isactuallycreatinganewobjectwith$.fn.init().Insteadofbeingjustanentrypointto$.fn.find()orjQuery.find(),itisactuallyaFacadethathidesalevelofoptimization.Specifically,itmakesjQueryfasterbyavoidinginvoking$.fn.find()andSizzle,whensimpleIDselectorsareusedbydirectlyinvokingthegetElementById()method.

ThepropertyaccessandmanipulationAPIAnotherveryinterestingabstractionthatfollowstheprinciplesoftheFacadePatternandcanbefoundinjQuery’ssource,isthe$.fn.prop()method.Likethe$.fn.attr(),$.fn.val(),$.fn.text(),and$.fn.html(),itbelongstoafamilyofmethodsthatischaracterizedbythefactthateachmethodisbothagetterandasetteroftherelatedsubject.Thedistinctionofthemethod’sexecutionmodeisdonebyinspectingthenumberofparametersthatarepassedduringitsinvocation.ThisconvenientAPIallowsustohavetorememberlessmethodsignaturesandmakethesettersdifferonlybyoneextraparameter.Forexample,$('#myCheckBox').prop('checked')willreturntrueorfalse,basedonthestateoftheselectedcheckbox.Ontheotherhand,$('#myCheckBox').prop('checked',true);willprogrammaticallycheckthatcheckboxforus.Inthesameconcept,$('button').prop('disabled',true);willdisableallthe<button>elementsonapage.

The$.fn.prop()methoddoesthejQueryCompositeObjecthandling,buttheactualimplementationoftheFacadeistheinternaljQuery.prop()method.AnextraconcernthataddscomplexitytotheFacade’simplementationisthefactthattherearesomeHTMLattributesthathavedifferentidentifiersforthecorrespondingpropertiesontheDOMelements:

jQuery.extend({

prop:function(elem,name,value){

/*8liesofcode*/

if(nType!==1||!jQuery.isXMLDoc(elem)){

//Fixnameandattachhooks

name=jQuery.propFix[name]||name;

hooks=jQuery.propHooks[name];

}

if(value!==undefined){

if(hooks&&"set"inhooks&&

(ret=hooks.set(elem,value,name))!==undefined){

returnret;

}

return(elem[name]=value);

}

if(hooks&&"get"inhooks&&(ret=hooks.get(elem,name))!==

null){

returnret;

}

returnelem[name];

},

propHooks:{

tabIndex:{

get:function(elem){

vartabindex=jQuery.find.attr(elem,"tabindex");

returntabindex?parseInt(tabindex,10):/*...*/;

}

}

},

propFix:{

"for":"htmlFor",

"class":"className"

}

});

ThefirsthighlightedcodeareaefficientlyresolvesthepropertytoattributeidentifiermismatchbyusingthepropFixandpropHooksobjectstodothematching.ThepropFixobjectactslikeasimpledictionarytomatchtheidentifiers,whilethepropHooksobjectholdsafunctionthatdoesthematchinginaless-hard-codedway,withprogrammatictesting.Thisisagenericimplementationthatcaneasilybeextendedbyaddingextrapropertiestothosetwoobjects.

Therestofthehighlightedareasareresponsibleforthegetter/settermodeofthemethod.Theoverallimplementationistoperformthefollowingtasks:

Checkwhetheravalueispassedasanargumentand,ifthepropertyfindsthattheassignmentissuccessful,dotheassignmentandreturnthevalue.Alternatively,iftherewasnovaluepassed,returnthevalueoftherequestedpropertyifitisretrievable.

UsingFacadesinourapplicationsInordertodemonstratehowfacadescanbeusedbothtoencapsulatecomplexity,helpingusenforcetheSeparationofConcernsprinciple,andalsoabstractthird-partylibraryAPIsintomoreconvenientmethodsthatareapplicationcentric,wearegoingtodemonstrateaverysimplelotteryapplication.Our“ElementLottery”applicationwillpopulateitscontainerwithsomeLotteryTicketelementsthatwillhaveauniqueIDandcontainarandomnumber.

Thewinningticketwillbepickedbyrandomlyselectingoneofthelotteryelements,basedonarandomindexamongthecreateduniqueIDs.Thewinningnumberwillthenbeannouncedtobethenumericcontentofthepickedelement.Let’sseethemodulesofourapplication:

(function(){

window.elementLottery=window.elementLottery||{};

varelementIDs;

var$lottery;

varticketCount=30;

elementLottery.init=function(){

elementIDs=[];

$lottery=$('#lottery').empty();

elementLottery.add(ticketCount);

$('#lotteryTicketButton').on('click',elementLottery.pick);

};

elementLottery.add=function(n){

for(vari=0;i<n;i++){

varid=this.uidProvider.get();

elementIDs.push(id);

$lottery.append(this.ticket.createHtml(id));

}

};

elementLottery.pick=function(){

varindex=Math.floor(Math.random()*elementIDs.length);

varresult=$lottery.find('#'+elementIDs[index]).text();

alert(result);

returnresult;

};

$(document).ready(elementLottery.init);

})();

ThemainelementLotterymoduleofourapplicationinitializeditselfrightafterthepagewasfullyloaded.Theaddmethodisusedtopopulatethelotterycontainerelementwithtickets.ItusestheuidProvidersubmoduletogenerateuniqueidentifiersfortheticketelements,keepstrackofthemontheelementIDsarray,usestheticketsubmoduletoconstructtheappropriateHTMLcode,andfinallyappendstheelementtothelottery.Thepickmethodisusedtorandomlyselectthewinningticketbyrandomlyselectingoneofthegeneratedidentifiers,retrievingthepageelementwiththatID,anddisplayingitscontentinsideanalertboxasthewinningresult.ThepickmethodistriggeredbyclickingonthebuttonthatwehaveaddedanObserverduringtheinitializationphase:

(function(){

elementLottery.ticket=elementLottery.ticket||{};

elementLottery.ticket.createHtml=function(id){

varticketNumber=Math.floor(Math.random()*1000*10);

return'<divid="'+id+'"class="ticket">'+ticketNumber+

'</div>';

};

})();

(function(){

elementLottery.uidProvider=elementLottery.uidProvider||{};

elementLottery.uidProvider.get=function(){

return'Lot'+simpleguid.getNext();

};

})();

TheticketsubmobuleactsasaFacadewithasinglemethodthatisusedtoencapsulatethegenerationofarandomnumberandthecreationoftheHTMLcodethatwillbeusedastheticket.Ontheotherhand,theuidProvidesubmoduleisaFacadethatprovidesasinglegetmethodthatencapsulatesthewayweusethesimpleguidmodulethatwesawinthepreviouschapters.Asaresult,wecaneasilychangethelibrarythatisusedtogenerateuniqueidentifiersandtheonlyplacethatwewillhavetomodifytheexistingimplementationwillbetheuidProvidesubmodule.Forexample,let’sseehowitwilllookifwedecidedtousethegreatnode-uuidlibrarythatgenerates128-bituniqueidentifiersasstringsofhexadecimalcharacters:

(function(){

elementLottery.uidProvider=elementLottery.uidProvider||{};

elementLottery.uidProvider.get=function(){

returnuuid.v4();

};

})();

NoteFormoreinformationonthenode-uuilibrary,youcanvisithttps://github.com/broofa/node-uuid.

SummaryInthischapter,welearnedwhataFacadeactuallyis.Welearneditsphilosophyandtheuniformwayinwhichitdefineshowcodeabstractionsshouldbecreatedsothattheyareeasilyunderstandableandreusablebyotherdevelopers.

Startingfromthesimplestusecasesofthispattern,welearnedhowtowrapacomplexAPIwithaFacadeandexposeasimpleronethatisfocusedontheneedsofourapplicationandisabettermatchtoitsspecificusecases.WealsosawhowjQueryembracestheconceptsofthispatterninitsimplementationandhowprovidingsimpleAPIsformorebasicweb-developingtechniques,suchasDOMTraversals,playedacriticalroleforitswideadoption.

NowthatwehavecompletedourintroductiontohowtheFacadePatterncanbeusedtodecoupleandabstractpartsofanimplementation,wecanmoveontothenextchapterwherewewillbeintroducedtotheBuilderandFactoryPatterns.Inthenextchapter,wewilllearnhowtousethesetwoCreationalDesignPatternstoabstracttheprocessofgeneratingandinitializingnewobjectsforspecificusecasesandanalyzehowtheiradoptioncanbenefitourimplementations.

Chapter6.TheBuilderandFactoryPatternsInthischapter,wewillshowcasetheBuilderandFactoryPatterns,twoofthemostcommonlyusedCreationalDesignPatterns.Thesetwodesignpatternshavesomesimilaritieswitheachother,sharesomecommongoals,andarededicatedtoeasingthecreationofcomplexresults.Wewillanalyzethebenefitsthattheiradoptioncanbringtoourimplementationsandalsothewaysinwhichtheydiffer.Finally,wewilllearnhowtousethemproperlyandchoosethemostappropriateoneforthedifferentusecasesofourimplementations.

Inthischapter,wewill:

IntroducetheFactoryPatternSeehowtheFactoryPatternisusedbyjQueryHaveanexampleoftheFactoryPatteninajQueryapplicationIntroducetheBuilderPatternComparetheBuilderandFactoryPatternsSeehowtheBuilderPatternisusedbyjQueryHaveanexampleoftheBuilderPatteninajQueryapplication

IntroducingtheFactoryPatternTheFactoryPatternispartofthegroupofCreationalPatternsandoverallitdescribesagenericwayforobjectcreationandinitialization.Itiscommonlyimplementedasanobjectorfunctionthatisusedtogenerateotherobjects.AccordingtothemajorityofComputerScienceresources,thereferenceimplementationoftheFactoryPatternisdescribedasaclassthatprovidesamethodthatreturnsnewlycreatedobjects.Thereturnedobjectsarecommonlytheinstancesofaspecificclassorsubclass,ortheyexposeasetofspecificcharacteristics.

ThekeyconceptoftheFactorypatternistoabstractthewayanobjectoragroupofrelatedobjectsarecreatedandinitializedforaspecificpurpose.Thepointofthisabstractionistoavoidcouplinganimplementationwithspecificclassesorthewaythateachobjectinstanceneedstobecreatedandconfigured.Theresultisanimplementationthatworksasanabstractwayforobjectcreationandinitialization,whichfollowstheconceptofSeparationofConcerns.

Theresultingimplementationsareonlybasedontheobjectmethodsandpropertiesthatarerequiredbytheiralgorithmorbusinesslogic.Suchanapproachcanbenefitthemodularityandextensibilityofanimplementation,byfollowingtheconceptofprogrammingoverObjectFeaturesandFunctionalityinsteadofObjectClasses.Thisgivesustheflexibilitytochangetheusedclasseswithanyotherobjectthatexposesthesamefunctionality.

HowitisadoptedbyjQueryAswehavealreadynotedintheearlierchapters,oneoftheearlygoalsofjQuerywastoprovideasolutionthatworkedthesameacrossallbrowsers.The1.12.xversionseriesofjQueryarefocusedonprovidingsupportforbrowsersasoldasInternetExplorer6(IE6),whilemaintainingthesameAPIwiththenewerv2.2.xversionsthatonlyfocusonmodernbrowsers.

Inordertohaveasimilarstructureandmaximizethecommoncodebetweenthetwoversions,thejQueryteamtriedtoabstractmostcompatibilitymechanismsinadifferentimplementationlayer.Suchadevelopmentpracticegreatlyimprovesthereadabilityofthecodeandreducesthecomplexityofthemainimplementation,encapsulatingitintodifferentsmallerpieces.

AgreatexampleofthisistheimplementationoftheAJAX-relatedmethodsthatjQueryprovides.Specifically,inthefollowingcode,youcanfindapartofit,asfoundinversion1.12.0ofjQuery:

//Createtherequestobject

//(ThisisstillattachedtoajaxSettingsforbackwardcompatibility)

jQuery.ajaxSettings.xhr=window.ActiveXObject!==undefined?

//Support:IE6-IE8

function(){

//XHRcannotaccesslocalfiles,alwaysuseActiveXforthatcase

if(this.isLocal){

returncreateActiveXHR();

}

//Support:IE9-11

if(document.documentMode>8){

returncreateStandardXHR();

}

//Support:IE<9

return/^(get|post|head|put|delete|options)$/i.test(this.type)&&

createStandardXHR()||createActiveXHR();

}:

//Forallotherbrowsers,usethestandardXMLHttpRequestobject

createStandardXHR;

//Functionstocreatexhrs

functioncreateStandardXHR(){

try{

returnnewwindow.XMLHttpRequest();

}catch(e){}

}

functioncreateActiveXHR(){

try{

returnnewwindow.ActiveXObject("Microsoft.XMLHTTP");

}catch(e){}

}

EverytimeanewAJAXrequestisissuedonjQuery,thejQuery.ajaxSettings.xhrmethodisusedasaFactorythatcreatesanewinstanceoftheappropriateXHRobjectbasedonthesupportofthecurrentbrowser.Lookinginmoredetail,wecanseethatthejQuery.ajaxSettings.xhrmethodorchestratestheuseoftwosmallerFactoryfunctions,witheachresponsibleforaspecificimplementationofAJAX.Moreover,wecanseethatitactuallytriestoavoidrunningthecompatibilitytestsoneverycallbydirectlywiringupitsreferencetothesmallercreateStandardXHRFactoryfunctionwhenappropriate.

UsingFactoriesinourapplicationsAsanexampleusecaseofFactories,wewillcreateadata-drivenformwhereouruserswillbeabletofillsomefieldsthataredynamicallycreatedandinsertedintothepage.Wewillassumetheexistenceofanarraycontainingobjectsthatdescribeeachformfieldthatneedstobepresented.OurFactorymethodwillencapsulatethewayinwhicheachformfieldneedstobeconstructed,andproperlyhandleeachspecificcase,basedonthecharacteristicsdefinedontherelatedobjects.

TheHTMLcodeforthispageisquitesimple:

<h1>DataDrivenForm</h1>

<form></form>

<scripttype="text/javascript"src="jquery.js"></script>

<scripttype="text/javascript"src="datadrivenform.js"></script>

Itonlycontainsan<h1>elementwiththepageheadingandanempty<form>elementthatwillhostthegeneratedfields.AsfortheCSSused,weonlystylethe<button>elementsinthesamewayaswedidinthepreviouschapters.

AsfortheJavaScriptimplementationoftheapplication,wecreateamoduleanddeclare

dataDrivenFormasthenamespaceofthisexample.Thismodulewillcontainthedatathatdescribesourform,theFactorymethodthatwillgeneratetheHTMLofeachformelementand,ofcourse,theinitializationcodethatwillcombinetheaforementionedpartstocreatetheresultingform:

(function(){

'usestrict';

window.dataDrivenForm=window.dataDrivenForm||{};

dataDrivenForm.formElementHTMLFactory=function(type,name,title){

if(!title||!title.length){

title=name;

}

vartopPart='<div><label><span>'+title+':</span><br/>';

varbottomPart='</label></div>';

if(type==='text'){

returntopPart+

'<inputtype="text"maxlength="200"name="'+name+'"/>'+

bottomPart;

}elseif(type==='email'){

returntopPart+

'<inputtype="email"requiredname="'+name+'"/>'+

bottomPart;

}elseif(type==='number'){

returntopPart+

'<inputtype="number"min="0"max="2147483647"'+'name="'+name+

'"/>'+

bottomPart;

}elseif(type==='date'){

returntopPart+

'<inputtype="date"min="1900-01-01"name="'+

name+'"/>'+

bottomPart;

}elseif(type==='textarea'){

returntopPart+

'<textareacols="30"rows="3"maxlength="800"name="'+name+'"

/>'+

bottomPart;

}elseif(type==='checkbox'){

return'<div><label><span>'+title+':</span>'+

'<inputtype="checkbox"name="'+name+'"/>'+

'</label></div>';

}elseif(type==='notice'){

return'<p>'+name+'</p>';

}elseif(type==='button'){

return'<buttonname="'+name+'">'+title+'!</button>';

}

};

})();

OurFactorymethodwillbeinvokedwiththreeparameters.Startingfromthemostimportantone,itacceptsthetypeandthenameoftheformfieldandalsothetitlethatwillbeusedasitsdescription.Sincemostformfieldssharesomecommoncharacteristics,

liketheirtitle,theFactorymethodtriestoabstracttheminordertohavelesscoderepetition.Asyoucansee,theFactorymethodalsocontainssomesensibleextraconfigurationforeachfieldtype,likethemaxlengthattributeofthetextfields,thatisspecificforthisusecase.

TheobjectstructurethatwillbeusedtorepresenteachformelementwillbeaplainJavaScriptobjectthathasatype,name,andtitleproperty.ThecollectionofobjectsthatdescribetheformfieldswillbegroupedinanarrayandbeavailableonthedataDrivenForm.partspropertyofourmodule.Inareal-worldapplication,thesefieldswouldcommonlyeitherberetrievedwithanAJAXrequestorbeinjectedintosomepartoftheHTMLofthepage.Inthefollowingcodesnippet,wecanseethedatathatwillbeusedtodrivethecreationofourform:

dataDrivenForm.parts=[{

type:'text',

name:'firstname',

title:'FirstName'

},{

type:'text',

name:'lastname',

title:'LastName'

},{

type:'email',

name:'email',

title:'e-mailaddress'

},{

type:'date',

name:'birthdate',

title:'Dateofbirth'

},{

type:'number',

name:'experience',

title:'Yearsofexperience'

},{

type:'textarea',

name:'summary',

title:'Summary'

},{

type:'checkbox',

name:'receivenotifications',

title:'Receivenotificatione-mails'

},{

type:'notice',

name:'Byusingthisformyouacceptthetermsofuse'

},{

type:'button',

name:'save'

},{

type:'button',

name:'submit'

}];

Finally,wedefineandimmediatelyinvokeaninitmethodforourmodule:

dataDrivenForm.init=function(){

for(vari=0;i<dataDrivenForm.parts.length;i++){

varpart=dataDrivenForm.parts[i];

varelementHTML=dataDrivenForm.formElementHTMLFactory(part.type,

part.name,part.title);

//checkiftheresultisnull,undefinedoremptystring

if(elementHTML&&elementHTML.length){

$('form').append(elementHTML);

}

}

};

$(document).ready(dataDrivenForm.init);

TheinitializationcodewaitsuntiltheDOMofthepageisfullyloadedandthenusestheFactorymethodtocreatetheformelementsandattachthemtothe<form>elementofourpage.AnextraconcernoftheprecedingcodeistochecktheresultoftheFactorymethodinvocationbeforeactuallystartingtouseit.

MostFactories,wheninvokedwithparametersforacasetheycan’thandle,returnnulloremptyobjects.Asaresult,it’sagoodcommonpractice,whenusingFactories,tocheckwhethertheresultofeachinvocationisactuallyvalid.

Asyoucansee,havingFactoriesthatacceptonlysimpleparameters(forexample,stringsandnumbers),inmanycases,leadstoanincreasednumberofparameters.Eventhoughtheseparametersmayonlybeusedinspecificcases,theAPIofourFactorystartstobeawkwardlylongandneedsproperdocumentationforeachspecialcaseinordertobeusable.

Ideally,aFactorymethodshouldacceptasfewargumentsaspossible,otherwiseitwillstartlookinglikeaFacadethatonlyprovidesadifferentAPI.Since,insomecases,usingasinglestringornumericargumentdoesnotsuffice,inordertoavoidusingahugenumberofparameters,wecanfollowapracticewheretheFactoryisdesignedtoacceptasingleobjectasitsparameter.

Forexample,inourcase,wecanjustpassthewholeobjectthatdescribestheformfieldasaparametertotheFactorymethod:

dataDrivenForm.formElementHTMLFactory=function(formElementDefinition){

vartopPart='<div><label><span>'+formElementDefinition.title+':

</span><br/>';

varbottomPart='</label></div>';

if(formElementDefinition.type==='text'){

returntopPart+

'<inputtype="text"maxlength="200"name="'

+formElementDefinition.name+'"/>'+

bottomPart;

}/*...*/

};

Thispracticeissuggestedforthefollowingcases:

WhenwecreategenericFactoriesthatarenotfocusedonspecificusecasesandweneedtoconfiguretheirresultsdifferentlyforeachspecificusecase.

Whentheconstructedobjectshavemanyoptionalconfigurationparametersthatlargelydiffer.Inthiscase,addingthemasseparateparameterstotheFactorymethodwouldleadtoinvocationsthathaveanumberofnullarguments,dependingonwhichexactargumentweareinterestedindefining.

Anotherpractice,especiallyinJavaScriptprogramming,istocreateaFactorymethodthatacceptsasimplestringornumericvalueasitsfirstargumentandoptionallyprovideacomplementaryobjectasasecondparameter.ThisenablesustohaveasimplegenericAPIthatcanbeuse-case-specificandalsogivesussomeextrapointsoffreedomtoconfiguresomespecialcases.Thisapproachisusedbythe$.ajax(url[,settings])methodthatallowsustogeneratesimpleGETrequestsbyjustprovidingaURLandalsoacceptsanoptionalsettingsparameterthatallowsustoconfigureanyaspectoftherequest.Changingtheaboveimplementationtousethisvariationisleftasanexerciseforthereader,inordertoexperimentandgetfamiliarwiththeuseofFactorymethods.

IntroducingtheBuilderPatternTheBuilderPatternispartofthegroupofCreationalPatternsandprovidesusawaytocreateobjectsthatrequirealotofconfigurationbeforetheyreachthepointwheretheycanbeused.TheBuilderPatternisoftenusedforobjectsthatacceptmanyoptionalparametersinordertodefinetheiroperation.Anothermatchingcaseisforthecreationofobjectswheretheirconfigurationneedstobedoneinseveralstepsorinaspecificorder.

ThecommonparadigmfortheBuilderPatternaccordingtoComputerScienceisthatthereisaBuilderObjectthatprovidesoneormoresettermethods(setA(...),setB(...))andasinglegenerationmethodthatconstructsandreturnsthenewlycreatedresultobject(getResult()).

Thispatternhastwoimportantconcepts.ThefirstoneisthattheBuilderObjectexposesanumberofmethodsasawaytoconfigurethedifferentpartsoftheobjectthatisunderconstruction.Duringtheconfigurationphase,theBuilderObjectpreservesaninternalstatethatreflectstheeffectsoftheinvocationsoftheprovidedsettermethods.Thiscanbebeneficialwhenusedtocreateobjectsthatacceptalargenumberofconfigurationparameters,solvingtheproblemofTelescopicConstructors.

NoteTelescopicConstructorsisananti-patternofobject-orientedprogrammingthatdescribesthesituationwhereaclassprovidesseveralconstructorsthattendtodifferonthenumber,thetype,andthecombinationoftheargumentsthattheyrequire.Objectclasseswithseveralparametersthatcanbeusedinmanydifferentcombinationscanoftenleadtoimplementationsfallingintothisanti-pattern.

Thesecondimportantconceptisthatitalsoprovidesagenerationmethodthatreturnstheactualconstructedobjectbasedontheprecedingconfiguration.Mostofthetime,theinstantiationoftherequestedobjectisdonelazilyandactuallytakesplaceatthemomentthatthismethodisinvoked.Insomecases,theBuilderObjectallowsustoinvokethegenerationmethodmorethanonce,allowingustogenerateseveralobjectswiththesameconfiguration.

HowitisadoptedbyjQuery’sAPITheBuilderPatterncanalsobefoundaspartoftheAPIthatjQueryexposes.Specifically,thejQuery$()functioncanalsobeusedtocreatenewDOMelementsbyinvokingitwithanHTMLstringasanargument.Asaresult,wecancreatenewDOMelementsandsettheirdifferentpartsasweneedthem,insteadofhavingtocreatetheexactHTMLstringthatisneededforthefinalresult:

var$input=$('<input/>');

$input.attr('type','number');

$input.attr('min','0');

$input.attr('max','100');

$input.prop('required',true);

$input.val(4);

$input.appendTo('form');

The$('<input/>')callreturnsaCompositeObjectcontaininganelementthatisnotattachedtotheDOMtreeofthepage.Thisunattachedelementisonlyanin-memoryobjectthatisneitherfullyconstructednorfullyfunctionaluntilweattachittothepage.Inthiscase,thisCompositeObjectactslikeaBuilderObjectInstancehavinganinternalstateofobjectsthatarenotyetfinalized.Rightafterthis,wedoaseriesofmanipulationsonitusingsomejQuerymethodsthatactlikethesettermethodsdescribedbytheBuilderPattern.

Finally,afterweapplyalltherequiredconfigurations,sothattheresultingobjectbehavesinthedesiredway,weinvokethe$.fn.appendTo()method.The$.fn.appendTo()methodworksasthegenerationmethodoftheBuilderPattern,byattachingthein-memoryelementofthe$inputvariabletotheDOMtreeofthepage,transformingitintoanactualattachedDOMelement.

Ofcourse,theaboveexamplecangetmorereadableandlessrepetitivebyutilizingtheFluentAPIthatjQueryprovidesforitsmethods,andalsocombinethe$.fn.attr()methodinvocations.Moreover,jQueryallowsustousealmostallitsmethodstodotraversalsandmanipulationsontheelementsthatareunderconstruction,justaswecanonnormalDOMelementCompositeObjects.Asaresult,theaboveexamplecangetalittlemorecompleteasfollows:

$('<input/>').attr({

'type':'number',

'min':'0',

'max':'100'

})

.prop('required',true)

.val(4)

.css('display','block')

.wrap('<label>')//wraptheinputwitha<label>

.parent()//traverseonelevelup,tothe<label>

.prepend('<span>Qty:#</span')

.appendTo('form');

Theresultwilllookasfollows:

Thecriteriathatallowustocategorizethisoverloadedwayofinvokingthe$()functionasanimplementationthatadoptstheBuilderPattern,isthefactthat:

Itreturnsanobjectwithaninternalstatecontainingpartiallyconstructedelements.Thecontainedelementsareonlyin-memoryobjectsthatarenotpartofthepage’sDOMtree.Itprovidesusmethodstomanipulateitsinternalstate.MostjQuerymethodscanbeusedforthispurpose.Itprovidesusmethod(s)togeneratethefinalresult.WecanusejQuerymethodssuchas$.fn.appendTo()and$.fn.insertAfter(),asawaytocompletetheconstructionoftheinternalelementsandmakethempartoftheDOMtreewithpropertiesthatreflecttheirearlierin-memoryrepresentation.

AswehavealreadyseeninChapter1,ARefresheronjQueryandtheCompositePattern,theprimarywaytousethe$()functionistoinvokeitwithaCSSselectorasastringparameterandinturnitwillretrievethematchingpageelementsandreturntheminaCompositeObject.Ontheotherhand,whenthe$()functiondetectsthatithasbeeninvokedwithastringparameterthatlookslikeapieceofHTML,itworksasaDOMelementBuilder.Thisoverloadedwayofinvokingthe$()functionbasesitsdetectionontheassumptionthattheprovidedHTMLcodestartsandendswiththeinequalitysymbols<and>:

init=jQuery.fn.init=function(selector,context){

/*11linesofcode*/

//HandleHTMLstrings

if(typeofselector==="string"){

if(selector[0]==="<"&&selector[selector.length-1]===">"

&&selector.length>=3){

//Assumethatstringsthatstartandendwith<>areHTML//and

skiptheregexcheck

match=[null,selector,null];

}/*...*/

//Matchhtmlormakesurenocontextisspecifiedfor#id

if(match&&(match[1]||!context)){

//HANDLE:$(html)->$(array)

if(match[1]){

/*4linesofcode*/

jQuery.merge(this,jQuery.parseHTML(match[1],/*...*/));

/*16linesofcode*/

returnthis;

}/*...*/

}/*...*/

}/*...*/

};

Aswecanseeintheprecedingcode,thisoverloadusesthejQuery.parseHTML()helpermethodthatultimatelyleadstoacallofthecreateDocumentFragment()method.ThecreatedDocumentFragmentisthenusedasahostoftheunderconstructiontreestructureofelements.AfterjQueryfinishesconvertingtheHTMLintoelements,theDocumentFragmentisdiscardedandonlyit’shostedelementsarereturned:

jQuery.parseHTML=function(data,context,keepScripts){

/*17linesofcode*/

//Singletag

if(parsed){

return[context.createElement(parsed[1])];

}

parsed=buildFragment([data],context,scripts);

/*5linesofcode*/

returnjQuery.merge([],parsed.childNodes);

};

ThisresultsinthecreationofanewjQueryCompositeObjectcontaininganin-memorytreestructureofelements.EventhoughtheseelementsarenotattachedtotheactualDOMtreeofthepage,wecanstilldotraversalsandmanipulationsonthemlikeanyotherjQueryCompositeObject.

NoteFormoreinformationonDocumentFragments,youcanvisit:https://developer.mozilla.org/en-US/docs/Web/API/Document/createDocumentFragment.

HowitisusedbyjQueryinternallyAnundoubtedlybigpartofjQueryisitsAJAX-relatedimplementation,whichaimstoprovideasimpleAPIforasynchronouscallsthatisalsoconfigurabletoalargedegree.UsingthejQuerySourceViewerandsearchingforjQuery.ajax,ordirectlysearchingjQuery’ssourcecodefor"ajax:",willbringustheaforementionedimplementation.Inordertomakeitsimplementationmorestraightforwardandalsoallowittobeconfigurable,jQueryinternallyusesaspecialobjectstructurethatactsasaBuilderObjectforthecreationandhandlingofeachAJAXrequest.Aswewillsee,thisisnotthemostcommonwayofusingaBuilderObject,butitisactuallyaspecialvariantwithsomemodificationsinordertofittherequirementsofthiscompleximplementation:

jqXHR={

readyState:0,

//Buildsheadershashtableifneeded

getResponseHeader:function(key){/*...*/},

//Rawstring

getAllResponseHeaders:function(){/*...*/},

//Cachestheheader

setRequestHeader:function(name,value){/*...*/},

//Overridesresponsecontent-typeheader

overrideMimeType:function(type){/*...*/},

//Status-dependentcallbacks

statusCode:function(map){/*...*/},

//Canceltherequest

abort:function(statusText){/*...*/}

};

ThemainmethodthatthejqXHRobjectexposestoconfigurethegeneratedasynchronousrequestisthesetRequestHeader()method.Theimplementationofthismethodisquitegeneric,enablingjQuerytosetallthedifferentHTTPheadersfortherequest,usingonlyonemethod.

Inordertoprovideanevengreaterdegreeofflexibilityandabstraction,jQueryinternallyusesaseparatetransportobjectasawrapperofthejqXHRobject.ThistransportobjecthandlesthepartofactuallysendingtheAJAXrequesttotheserver,workinglikeapartnerbuilderobjectthatcooperateswiththejqXHRobjectforthecreationofthefinalresult.Thisway,jQuerycanfetchScripts,XML,JSON,andJSONPresponsesfromthesameorcross-originservers,usingthesameAPIandoverallimplementation:

transport=inspectPrefiltersOrTransports(transports,s,options,jqXHR);

//Ifnotransport,weauto-abort

if(!transport){

done(-1,"NoTransport");

}else{

jqXHR.readyState=1;

/*12linesofcode*/

try{

state=1;

transport.send(requestHeaders,done);

}catch(e){/*7linesofcode*/}

}

AnotherspecialthingaboutthisimplementationoftheBuilderPatternisthatitshouldbeabletooperateinbothsynchronousandasynchronousmanner.Asaresult,thesend()methodofthetransportobjectthatactsastheresultgeneratormethodofthewrappedjqXHRobjectcan’tjustreturnaresultobject,butitisinsteadinvokedwithacallback.

Finally,aftertherequestiscomplete,jQueryusesthegetResponseHeader()methodtoretrievealltherequiredresponseheaders.Rightafterthis,theheadersareusedtoproperlyconvertthereceivedresponsethatisstoredintheresponseTextpropertyofthejqXHRobject.

HowtouseitinourapplicationsAsanexampleusecaseoftheBuilderPatterninaclient-sideapplicationthatusesjQuery,wewillcreateasimpledata-drivenmultiple-choicequiz.ThemainreasonthattheBuilderPatternisabettermatchforthiscase,ascomparedtotheFactoryPatternexamplethatwesawearlier,isthattheresultismorecomplexandhasmoredegreesofconfiguration.Eachquestionwillbegeneratedbasedonamodelobjectthatwillrepresentitsdesiredproperties.

Onceagain,therequiredHTMLisverysimple,containingjustan<h1>elementwiththeheaderofthepage,anempty<form>tag,andsomereferencestoourCSSandJavaScriptresources:

<h1>DataDrivenQuiz</h1>

<form></form>

<scripttype="text/javascript"src="jquery.js"></script>

<scripttype="text/javascript"src="datadrivenquiz.js"></script>

Besidesthecommon,simplestylesthatwehaveseeninthepreviouschapters,theCSSof

thisexampleadditionallydefines:

ul.unstyled>li{

margin:0;

padding:0;

list-style:none;

}

Fortheneedsofthisexample,wewillcreateamodulewithanewnamespacenameddataDrivenQuiz.Aswesawearlierinthischapter,wewillassumetheexistenceofanarraycontainingthemodelobjectsthatdescribeeachmultiple-choicequestionthatneedstobepresented.Eachofthesemodelobjectswillhave:

AtitlepropertythatwillholdthequestionAnoptionspropertythatwillbeanarraywiththeavailableanswerstochoosefromAnoptionalacceptsMultiplepropertytosignifywhetherweshoulduseradioorcheckboxes

ThearraywiththemodelobjectsthatdescribetheformquestionswillbeavailableatthedataDrivenQuiz.partspropertyofourmodule,whilekeepinginmindthatourimplementationcouldeasilybemodifiedtofetchthemodelswithanAJAXrequest:

dataDrivenQuiz.questions=[{

title:'WhichisthemostpreferredwaytowriteourJavaScriptcode?',

options:[

'inlinealongwithourHTML',

'flatinside*.jsfiles',

'insmallModules,oneper*.jsfile'

]

},{

title:'Whatdoesthe$()functionreturnswheninvokedwithaCSS

selector?',

options:[

'asingleelement',

'anarrayofelements',

'theHTMLoftheselectedelement',

'aCompositeObject'

]

},{

title:'WhichofthefollowingareDesignPatterns',

acceptsMultiple:true,

options:[

'GarbageCollector',

'Class',

'ObjectLiteral',

'Observer'

]

},{

title:'Howcangetaholdtothe<body>elementofapage?',

acceptsMultiple:true,

options:[

'document.body',

'document.getElementsByTagName(\'body\')[0]',

'$(\'body\')[0]',

'document.querySelector(\'body\')'

]

}];

TipDefiningthedatastructuresthatarerequiredtodescribeaproblem,beforestartingtheactualimplementation,allowsustofocusontheneedsoftheapplicationandgetanestimateofitsoverallcomplexity.

Giventheprecedingsampledata,let’snowproceedtotheimplementationofourBuilder:

functionMultipleChoiceBuilder(){

this.title='Untitled';

this.options=[];

}

dataDrivenQuiz.MultipleChoiceBuilder=MultipleChoiceBuilder;

MultipleChoiceBuilder.prototype.setTitle=function(title){

this.title=title;

returnthis;

};

MultipleChoiceBuilder.prototype.setAcceptsMultiple=

function(acceptsMultiple){

this.acceptsMultiple=acceptsMultiple;

returnthis;

};

MultipleChoiceBuilder.prototype.addOption=function(title){

this.options.push(title);

returnthis;

};

MultipleChoiceBuilder.prototype.getResult=function(){

var$header=$('<header>').text(this.title||'Untitled');

varquestionGuid='quizQuestion'+(jQuery.guid++);

var$optionsList=$('<ulclass="unstyled">');

for(vari=0;i<this.options.length;i++){

var$input=$('<input/>').attr({

'type':this.acceptsMultiple?'checkbox':'radio',

'value':i,

'name':questionGuid,

});

var$option=$('<li>');

$('<label>').append($input,$('<span>').text(this.options[i]))

.appendTo($option);

$optionsList.append($option);

}

return$('<article>').append($header,$optionsList);

};

UsingthePrototypicalObject-OrientedapproachofJavaScript,wefirstlydefinetheConstructorFunctionforourMultipleChoiceBuilderclass.WhentheConstructor

Functionisinvokedusingthenewoperator,itwillcreateanewinstanceoftheBuilderandinitializeitstitlepropertyto"Untitled"andtheoptionspropertytoanemptyarray.

Rightafterthis,wecompletethedefinitionoftheConstructorFunctionofourBuilder,weattachitasamemberofourmodule,andcontinuewiththedefinitionofitssettermethods.FollowingthePrototypicalClassparadigm,thesetTitle(),setAcceptsMultiple(),andaddOption()methodsaredefinedaspropertiesofourBuilder’sPrototypeandareusedtomodifytheinternalstateoftheunderconstructionelement.Additionally,inordertoenableustochainseveralinvocationsofthesemethods,whichresultsinamorereadableimplementation,allofthemendwiththereturnthis;statement.

WecompletetheimplementationoftheBuilderwiththegetResult()methodthathasthedutyofgatheringalltheparametersthatareappliedontheBuilderobjectinstanceandgeneratingtheresultingelementwrappedinsideajQueryCompositeObject.Initsfirstline,itcreatesaheaderofthequestion.Rightafterthis,itcreatesa<ul>elementwiththeunstyledCSSclasstoholdthepossibleanswerstothequestionandauniqueidentifierthatwillbeusedasthenameofthegenerated<input>ofthequestion.

Intheforloopthatfollows,wewill:

Createan<input/>elementforeachoptionofthequestionProperlysetitstypeasacheckboxoraradiobutton,basedonthevalueoftheacceptsMultiplepropertyUsetheforloop’siterationnumberasitsvalueSettheuniqueidentifierthatwegeneratedearlierforthequestionastheinput’snameinordertogrouptheanswersFinally,adda<label>withtheoption’stext,whichwrapsalloftheminsidean<li>,andappendittothequestion’s<ul>.

Lastly,theheaderandthelistofoptionsarewrappedinan<article>element,whichisthenreturnedasthefinalresultoftheBuilder.

Intheaboveimplementation,weusethe$.fn.text()methodtoassignthecontentofthequestion’sheaderanditsavailablechoicesinsteadofstringconcatenation,inordertoproperlyescapethe<and>charactersthatarefoundintheirdescriptions.Asanextranote,sincesomeoftheanswersalsocontainsinglequotes,weneedtoescapetheminthemodelobjectsusingabackslash(\').

Finally,inourmodule’simplementation,wedefineandimmediatelyinvoketheinitmethod:

dataDrivenQuiz.init=function(){

for(vari=0;i<dataDrivenQuiz.questions.length;i++){

varquestion=dataDrivenQuiz.questions[i];

varbuilder=newdataDrivenQuiz.MultipleChoiceBuilder();

builder.setTitle(question.title)

.setAcceptsMultiple(question.acceptsMultiple);

for(varj=0;j<question.options.length;j++){

builder.addOption(question.options[j]);

}

$('form').append(builder.getResult());

}

};

$(document).ready(dataDrivenQuiz.init);

TheexecutionoftheinitializationcodeisdelayeduntiltheDOMtreeofthepageisfullyloaded.Thentheinit()methoditeratesoverthemodelobjectsarrayandusestheBuildertocreateeachquestionandpopulatethe<form>elementofourpage.

Agoodexerciseforthereaderwouldbetoextendtheaboveimplementationinordertosupporttheclient-sideevaluationofthequiz.Firstly,thiswouldrequireyoutoextendthequestionobjectstocontaininformationaboutthevalidityofeachchoice.Then,itwouldbesuggestedthatyoucreateaBuilderthatwouldretrievetheanswersfromtheform,evaluatethem,andcreatearesultobjectwiththeuserchoicesandtheoverallsuccessonthequiz.

SummaryInthischapter,welearnedtheconceptsoftheBuilderandFactoryPatterns,twoofthemostcommonlyusedCreationalDesignPatterns.Weanalyzedtheircommongoals,theirdifferentapproachesonabstractingtheprocessofgeneratingandinitializingnewobjectsforspecificusecases,andhowtheiradoptioncanbenefitourimplementations.Finally,welearnedhowtousethemproperlyandhowtochoosethemostappropriateoneforthedifferentusecasesofanygivenimplementations.

NowthatwehavecompletedourintroductiontothemostimportantCreationalDesignPatterns,wecanmoveontothenextchapterwherewewillbeintroducedtothedevelopmentpatternsthatareusedtoprogramasynchronousandconcurrentprocedures.Inmoredetail,wewilllearnhowtoorchestratetheexecutionofasynchronousproceduresthatruneitherinorderorparalleltoeachother,byusingcallbacksandjQueryDeferredandPromisesAPIs.

Chapter7.AsynchronousControlFlowPatternsThischapterisdedicatedtodevelopmentpatternsthatareusedtoeasetheprogrammingofasynchronousandconcurrentprocedures.

Atfirst,wewillhavearefresheronhowCallbacksareusedinJavaScriptprogrammingandhowtheyareanintegralpartofwebdevelopment.Wewillthenproceedandidentifytheirbenefitsandlimitationswhenusedinlargeandcompleximplementations.

Rightafterthis,wewillbeintroducedtotheconceptofPromises.WewilllearnhowjQuery’sDeferredandPromiseAPIsworkandhowtheydifferfromES6Promises.WewillseewhereandhowtheyareusedinternallybyjQuerytosimplifyitsimplementationandleadtomorereadablecode.Wewillanalyzetheirbenefits,classifythebestmatchingusecases,andcomparethemwiththeclassicCallbackPattern.

Bytheendofthischapter,wewillbeabletousejQueryDeferredandPromisestoefficientlyorchestratetheexecutionofasynchronousproceduresthatruneitherinorderorparalleltoeachother.

Inthischapter,wewill:

HavearefresheronhowCallbacksareusedinJavaScriptprogrammingGetintroducedtotheconceptofPromisesLearnhowtousejQuery’sDeferredandPromiseAPIsComparejQueryPromiseswithES6PromisesLearnhowtoorchestrateasynchronoustasksusingPromises.

ProgrammingwithcallbacksACallbackcanbedefinedasafunctionthatispassedasaninvocationargumenttoanotherfunctionormethod(whichisreferredtoasaHigher-OrderFunction)andisexpectedtobeexecutedatsomelaterpointoftime.Inthisway,thepieceofcodethatwashandedourCallbackwilleventuallyinvokeit,propagatingtheresultsofanoperationoreventbacktothecontextthattheCallbackwasdefined.

Callbackscanbecharacterizedassynchronousorasynchronous,basedonthewaythattheinvokedmethodoperates.ACallbackischaracterizedassynchronouswhenitisexecutedbyablockingmethod.Ontheotherhand,JavaScriptdevelopersaremorefamiliarwithasynchronouscallbacks,alsocalleddeferredcallbacks,whicharesettobeexecutedafteranasynchronousprocedurefinishesorwhenaspecificeventoccurs(pageload,click,AJAXresponsearrival,andsoon).

CallbacksarewidelyusedinJavaScriptapplicationssincetheyareanintegralpartofmanycoreJavaScriptAPIssuchasAJAX.Moreover,JavaScriptimplementationsofthispatternarealmostwordforwordasdescribedbytheabovesimpledefinition.ThisisaresultofthewaythatJavaScripttreatsfunctionsasobjectsandallowsustostoreandpassmethodreferencesassimplevariables.

UsingsimplecallbacksinJavaScriptPerhapsoneofthesimplestexamplesofasynchronouscallbacksinJavaScriptisthesetTimeout()function.Thefollowingcodedemonstratesasimpleuseofit,whereweinvokesetTimeout()withthedoLater()functionasacallbackparameterand,after1000millisecondsofwaiting,thedoLater()callbackisinvoked:

varalertMessage='Onesecondpassed!';

functiondoLater(){

alert(alertMessage);

}

setTimeout(doLater,1000);

Asseeninthesimpleprecedingexample,thecallbackisexecutedinthecontextthatitwasdefined.Thecallbackstillhasaccesstothevariablesofthecontextthatitwasdefinedbycreatingaclosure.Eventhoughtheprecedingexampleusesanamedfunctiondefinedearlier,thesameappliesforanonymouscallbacks:

varalertMessage='Onesecondpassed!';

setTimeout(function(){

alert(alertMessage);

},1000);

Inmanycases,usinganonymouscallbacksisamoreconvenientwayofprogramming,sinceitresultsinshortercodeandalsoreducesthereadabilitynoise,whichisaresultofdefiningseveraldifferentnamedfunctionsthatareusedonlyonce.

SettingcallbacksasobjectpropertiesAsmallvariationoftheabovedefinitionalsoexists,wherethecallbackfunctionisassignedtoapropertyofanobjectinsteadofbeingpassedasanargumentofamethodinvocation.Thisiscommonlyusedincaseswherethereareseveraldifferentactionsthatneedtotakeplaceduringorafteramethodinvocationiscompleted:

varc=newCountdown();

c.onProgress=function(progressStatus){/*...*/};

c.onDone=function(result){/*...*/};

c.onError=function(error){/*...*/};

c.start();

Anotherusecaseoftheabovevariantistoaddhandlersonobjectsthathavealreadybeeninstantiatedandinitialized.Agoodexampleofthiscaseisthewaywesetuparesulthandlerforsimple(non-jQuery)AJAXcalls:

varr=newXMLHttpRequest();

r.open('GET','data.json',true);

r.onreadystatechange=function(){

if(r.readyState!=4||r.status!=200){

return;

}

alert(r.responseText);

};

r.send();

Intheprecedingcode,wesetananonymousfunctionontheonreadystatechangepropertyoftheXMLHttpRequestobject.Thisfunctionactsasacallbackandisinvokedeverytimethereisastatechangeontheongoingrequest.Insideourcallback,wecheckwhethertherequesthascompletedwithasuccessfulHTTPstatuscodeanddisplayanalertwiththeresponsebody.Likeinthisexample,whereweinitiatetheAJAXcallbyinvokingthesend()methodwithoutpassinganyarguments,itiscommonforAPIsthatusethisvarianttoleadtominimalwaysofinvokingtheirmethods.

UsingcallbacksinjQueryapplicationsPerhapsthemostcommonwayinwhichcallbacksareusedinjQueryapplicationsisforeventhandling.Thisislogicalsincethefirstthingthateveryinteractiveapplicationshoulddoishandleandrespondtouseractions.Aswesawinearlierchapters,oneofthemostconvenientwaystoattacheventhandlerstoelementsisbyusingjQuery’s$.fn.on()method.

AnothercommonplacewherecallbacksareusedinjQueryisforAJAXrequests,wherethe$.ajax()methodhasthecentralrole.Moreover,thejQuerylibraryalsoprovidesseveralotherconvenientmethodstomakeAJAXrequeststhatarefocusedonthemostcommonusecases.Sinceallthesemethodsareexecutedasynchronously,theyalsoacceptacallbackasaparameter,asawaytomaketheretrieveddataavailablebacktothecontextthatinitiatedtheAJAXrequest.Oneoftheseconvenientmethodsis$.getJSON(),whichisawrapperaround$.ajax(),andisusedasabettermatchingAPItoexecuteAJAXrequeststhatintendtoretrieveJSONresponses.

OtherwidelyusedjQueryAPIsacceptingcallbacksareasfollows:

Theeffects-relatedjQuerymethodssuchas$.animate()The$(document).ready()method

Let’snowcontinuebydemonstratingacodeexamplewherealltheabovemethodsareused.

$(document).ready(function(){

$('#fetchButton').on('click',function(){

$.getJSON('AjaxContent.json',function(json){

console.log('doneloadingnewcontent');

$('#newContent').css({'display':'none'})

.text(json.data)

.slideDown(function(){

console.log('donedisplayingnewcontent');

});

});

});

});

TheprecedingcodefirstlydelaysitsexecutionuntiltheDOMtreeofthepagehasbeenfullyloadedandthenaddsanObserverforclicksonthe<button>withIDfetchButtonbyusingthejQuery’s$.fn.on()method.Whenevertheclickeventisfired,theprovidedcallbackwillbeinvokedandinitiateanAJAXcalltofetchtheAjaxContent.jsonfile.Fortheneedsofthisexample,weareusingasimpleJSONfile,likethefollowing:

{"data":"I'mthetextcontentfetchedbyanAJAXcall!"}

WhentheresponseisreceivedandtheJSONisparsedsuccessfully,thecallbackisinvokedwiththeparsedobjectasaparameter.Finally,thecallbackitselflocatesthepageelementwiththeIDnewContentinthepage,hidesit,andthensetsthedatafieldoftheretrievedJSONasitstextcontent.Rightafterthis,weusethejQuery$.fn.slideDown()

methodthatmakesthenewlysetpagecontentappear,byprogressivelyincreasingitsheight.Finally,aftertheanimationiscomplete,wewritealogmessagetothebrowserconsole.

NoteFurtherdocumentationregardingjQuery’s$.ajax(),$.getJSON(),and$.fn.slideDown()methodscanbefoundathttp://api.jquery.com/jQuery.ajax/,http://api.jquery.com/jQuery.getJSON/,andhttp://api.jquery.com/slideDown/.

Keepinmindthatthe$.getJSON()methodmightnotworkinsomebrowserswhenthepageisloadedthroughthefilesystem,butworksasintendedwhenservedusinganywebserversuchasApache,IIS,ornginx.

WritingmethodsthatacceptcallbacksWhenwritingafunctionthatutilizesoneormoreasynchronousAPIs,thatalsodictatesthattheresultingfunctionwillbeasynchronousbydefinition.Inthatcase,itisobviousthatsimplyreturningaresultvalueisnotanoption,sincetheresultwillprobablybeavailableafterthefunctioninvocationhasalreadyfinished.

Theeasiestsolutionforasynchronousimplementationsistouseacallbackasaparameterofyourfunction,which,aswediscussedearlier,ishassle-freeinJavaScript.Asanexample,wewillcreateanasynchronousfunctionthatgeneratesarandomnumberofaspecifiedrange:

functiongetRandomNumberAsync(max,callbackFn){

varrunFor=1000+Math.random()*1000;

setTimeout(function(){

varresult=Math.random()*max;

callbackFn(result);

},runFor);

}

ThegetRandomNumberAsync()functionacceptsitsmaxargumentasthenumericupperboundforthegeneratedrandomnumberandalsoacallbackfunctionthatitwillinvokewiththegeneratedresult.ItusessetTimeout()toemulateanasynchronouscalculationthatrangesfrom1000to2000milliseconds.Forthegenerationoftheresult,itusestheMath.random()method,multiplyingitwiththemaximumallowedvalue,andfinallyinvokestheprovidedcallbackwithit.Asimplewaytoinvokethisfunctionwilllookasfollows:

getRandomNumberAsync(10,function(number){

console.log(number);//returnsanumberbetween0and10

});

EventhoughtheaboveexampleusessetTimeout()toemulateasynchronousprocessing,theimplementationprinciplesremainthesameregardlessoftheasynchronousAPI(s)thatisused.Forexample,wecanrewritetheabovefunctiontoretrieveitsresultthroughanAJAXcall:

functiongetRandomNumberWS(max,callbackFn,errorFn){

$.ajax({

url:'https://qrng.anu.edu.au/API/jsonI.php?length=1&type=uint16',

dataType:'json',

success:function(json){

varresult=json.data[0]/65535*max;

callbackFn(result);

},

error:errorFn

});

}

Theprecedingimplementationusesthe$.ajax()methodthatisinvokedwithanobjectparameter,enclosingalltheoptionsoftherequest.ExceptfortheURLfortherequest,theobjectalsodefinestheexpecteddataTypeoftheresultandthesuccessanderror

callbacks,whicharewiredwiththerespectiveparametersofourfunction.

Perhapstheonlyextraconcernthattheprecedingcodehastoresolveishowtohandleerrorsinsidethesuccesscallbacksothatthecallerofthefunctioncanbenotifiedincasesomethinggoeswrongduringthecreationoftheresult.Forexample,theAJAXrequestmightreturnanemptyobject.Addingproperhandlingforsuchcasesisleftasanexerciseforthereader,afterreadingtherestofthischapter.

NoteTheAustralianNationalUniversity(ANU)providesfree,trulyrandom,numberstothepublic,throughtheirRESTWebService.Formoreinformation,youcanvisithttp://qrng.anu.edu.au/API/api-demo.php.

OrchestratingcallbacksWewillnowcontinuebyanalyzingsomepatternsthatarecommonlyusedtocontroltheexecutionflowwhendealingwithasynchronousmethodsthatacceptcallbacks.

QueuinginorderexecutionAsourfirstexample,wewillcreateafunctionthatdemonstrateshowwecanqueuetheexecutionofseveralasynchronoustasks:

functiongetThreeRandomNumbers(callbackFn,errorFn){

varresults=[];

getRandomNumberAsync(10,function(number){

results.push(number);

getRandomNumberAsync(10,function(number){

results.push(number);

getRandomNumberWS(10,function(number){

results.push(number);

callbackFn(results);

},function(error){

errorFn(error);

});

});

});

}

Intheprecedingimplementation,ourfunctioncreatesaqueueofthreerandomnumbergenerations.ThefirsttworandomnumbersaregeneratedfromoursamplesetTimeout()implementationandthethirdisretrievedfromtheaforementionedwebservicethoughanAJAXcall.Inthisexample,allthenumbersaregatheredintheresultarray,whichispassedasaninvocationparametertothecallbackFnafteralltheasynchronoustaskshavecompleted.

TheprecedingimplementationisquitestraightforwardandjustappliesthesimpleprinciplesoftheCallbackPatternrepeatedly.Foreveryextraorqueuedasynchronoustask,wejustneedtonestitsinvocationinsidethecallbackofthetaskthatitdependson.Keepinmindthat,indifferentusecases,wemightonlycaretoreturntheresultofthefinaltaskandhavetheresultsoftheintermediatestepsbepropagatedasargumentsforeachsubsequentasynchronouscall.

AvoidingtheCallbackHellanti-pattern

Eventhoughwritingcodeasshownintheaboveexampleiseasy,whenappliedtolargeandcompleximplementations,itcanleadtobadreadability.Thetriangularshapethatiscreatedbythewhite-spacesinfrontofourcodeandthestackingofseveral});nearitsend,arethetwosignsthatourcodemightleadtoananti-patternknownasCallbackHell.

NoteFormoreinformation,youcanvisithttp://callbackhell.com/.

Awaytoavoidthisanti-patternistounfoldthenestedcallbacks,bycreatingseparatenamedfunctionsatthesamelevelwiththeasynchronoustaskthattheyareused.Afterapplyingthissimpletiptotheaboveexample,theresultingcodelooksalotcleaner:

functiongetThreeRandomNumbers(callbackFn,errorFn){

varresults=[];

getRandomNumberAsync(10,function(number){//task1

results.push(number);

task2();

});

functiontask2(){

getRandomNumberAsync(10,function(number){

results.push(number);

task3();

});

}

functiontask3(){

getRandomNumberWS(10,function(number){

results.push(number);

callbackFn(results);

},errorFn);

}

}

Asyoucansee,theresultingcodesurelydoesnotremindusofthecharacteristicsoftheCallbackHellanti-pattern.Ontheotherhand,itnowneedsmorelinesofcodeforitsimplementation,mostlyusedfortheadditionalfunctiondeclarationsfunctiontaskX(){}thatarenowrequired.

TipAmiddlegroundsolutionbetweentheabovetwoapproachesistoorganizetherelatedpartsofsuchasynchronousexecutionqueuesinsmallandmanageablefunctions.

RunningconcurrentlyEventhoughJavaScriptinwebbrowsersissingle-threaded,makingindependentasynchronoustasksrunconcurrentlycanmakeourapplicationsworkfaster.Asanexample,wewillrewritetheprecedingimplementationtofetchallthreerandomnumbersinparallel,whichcanmaketheresulttoberetrievedalotfasterthanbefore:

functiongetRandomNumbersConcurent(callbackFn,errorFn){

varresults=[];

varresultCount=0;

varn=3;

functiongatherResult(resultPos){

returnfunction(result){

results[resultPos]=result;

resultCount++;

if(resultCount===n){

callbackFn(results);

}

};

}

getRandomNumberAsync(10,gatherResult(0));

getRandomNumberAsync(10,gatherResult(1));

getRandomNumberWS(10,gatherResult(2),errorFn);

}

Intheprecedingcode,wedefinedthegatherResult()helperfunction,whichreturnsananonymousfunctionthatisusedasacallbackforourrandomnumbergenerators.ThereturnedcallbackfunctionusestheresultPosparameterastheindexofthearraywhereitwillstorethegeneratedorretrievedrandomnumber.Additionally,ittrackshowmanytimesithasbeeninvoked,asawaytoknowwhetherallthreeconcurrenttaskshaveended.Finally,rightafterthethirdandfinalinvocationofthecallback,thecallbackFnfunctionisinvokedwiththeresultsarrayasaparameter.

Anothergreatapplicationofthistechnique,otherthanAJAXcalls,istoaccessdatastoredinIndexedDB.Retrievingmanyvaluesfromthedatabaseconcurrentlycanleadtoperformancegains,sincethedataretrievalscanexecuteinparallelwithoutblockingeachother.

NoteFormoreinformationonIndexedDB,youcanvisithttps://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/Using_IndexedDB.

IntroducingtheconceptofPromisesPromises,alsoknownasFutures,aredescribedbyComputerScienceasspecializedobjectsthatareusedforsynchronizationofasynchronous,concurrent,orparallelprocedures.Theyarealsousedasproxiestopropagatetheresultofataskwhenitsgenerationcompletes.Thisway,aPromiseobjectislikeacontractwhereanoperationwilleventuallycompleteitsexecution,andanyonehavingareferencetothiscontractcandeclaretheirinteresttobenotifiedabouttheresult.

SincetheywereintroducedtoJavaScriptdevelopers,aspartofseverallibraries,theyrevolutionizedthewayweuseasynchronousfunctionsandcomposetheminimplementationwithcomplexsynchronizationschemes.Thisway,webdeveloperscancreatemoreflexible,scalable,andreadableimplementations,makingmethodinvocationswithcallbackslooklikeaprimitivepatternandeffectivelyeliminatingtheCallbackHellsituations.

OneofthekeyconceptsofPromisesisthatasynchronousmethodsreturnanobjectthatrepresentstheireventualresult.EveryPromisehasaninternalstatethatinitiallystartsasPending.Thisinternalstatecanchangeonlyonce,fromPendingtoeitherResolvedorRejected,byusingoneoftheresolve()orreject()methodsthateveryimplementationprovides.ThesemethodscanbeinvokedonlytochangethestateofaPendingPromise;inmostcases,theyareintendedtobeusedonlybytheoriginalcreatorofthePromiseobjectandnotbeavailabletoitsconsumers.Theresolve()methodcanbeinvokedwiththe

resultoftheoperationasasingleparameter,whilethereject()methodisusuallyinvokedwiththeErrorthatcausedthePromiseobjecttogetRejected.

AnotherkeyconceptofPromisesistheexistenceofathen()method,givingthemthecharacterizationofthe“thenable”,asageneraltermtodescribepromisesamongallthedifferentimplementations.EveryPromiseobjectexposesathen()methodthatisusedbyacallerinordertoprovidethefunction(s)thatwillbeinvokedwhenthePromiseissettled(ResolvedorRejected).Thethen()methodcanbeinvokedwithtwofunctionsasparameters,wherethefirstisinvokedincasethePromisegetsResolved,whilethesecondisinvokedwhenitisRejected.ThefirstargumentiscommonlyreferredtoastheonFulfilled()callback,whilethesecondisreferredtoastheonRejected().

EveryPromisepreservestwointernallistcontainingalltheonFulfilled()andonRejected()callbackfunctionsthatarepassedasargumentstothethen()method.Thethen()methodcanbeinvokedseveraltimesforeachPromise,addingnewentriestotheappropriateinternallist,asfarastherespectiveparameterisactuallyafunction.WhenaPromiseeventuallygetsResolvedorRejected,ititeratesovertheappropriatelistofcallbacksandinvokestheminorder.Moreover,fromthepointthataPromisegetssettledandafter,everyfurtherusageofthethen()methodhas,asaresult,theimmediateinvocationoftheappropriateprovidedcallback.

NoteBasedonitscharacteristics,aPromisecanbelikenedtoaBrokerfromthe

Publish/SubscribePatterntosomedegree.TheirkeydifferencesincludethefactsthatitcanonlybeusedforasinglePublishandthattheSubscribersgetnotifiedoftheresulteveniftheyexpressedtheirinterestafterthePublishtookplace.

UsingPromisesAswesaidearlier,theconceptofPromisesrevolutionizedprogrammingofasynchronoustasksinJavaScriptand,foralongtime,theywerethenewbigthingthateveryonewasenthusiasticabout.Atthattime,manyspecializedlibrariesappearedwhereeachoneprovidedanimplementationofPromiseswithslightdifferencestoeachother.Moreover,PromiseimplementationsbecameavailableaspartofutilitylibrariessuchasjQueryandwebframeworkssuchasAngularJSandEmberJS.Atthattime,the“CommonJSPromises/A”specificationmadeitsappearanceasareferencepointandwasthefirstattempttodefinehowPromisesshouldactuallyworkacrossallimplementations.

NoteFormoreinformationonthe“CommonJSPromises/A”specification,youcanvisithttp://wiki.commonjs.org/wiki/Promises/A.

UsingthejQueryPromiseAPIAPromise-basedAPIfirstappearedinthejQuerylibraryinv1.5,basedonthe“CommonJSPromises/A”design.ThisimplementationintroducedtheadditionalconceptoftheDeferredobject,whichworkslikeaPromiseFactory.TheDeferredobjectsexposeasupersetofthemethodsthatPromisesprovide,wheretheadditionalmethodscanbeusedtodomanipulationstothestateofitsinternalPromise.Additionally,theDeferredobjectexposesapromise()methodandreturnstheactualPromiseobject,whichdoesnotexposeanywaytomanipulateitsinternalstateandjustexposesobservationmethodssuchasthen().

Inotherwords:

OnlycodethathasareferencetoaDeferredobjectcanactuallychangetheinternalstateofitsPromise,byeitherresolvingorrejectingit.AnypieceofcodethathasareferencetoaPromiseobjectcan’tchangeitsstatebutjustobserveforitsstatetochange.

NoteFormoreinformationonjQuery’sDeferredobject,youcanvisithttp://api.jquery.com/jQuery.Deferred/.

AsasimpleexampleofjQuery’sDeferredobject,let’sseehowwecanrewritethegetRandomNumberAsync()functionthatwesawearlierinthischapter,tousePromisesinsteadofCallbacks:

functiongetRandomNumberAsync(max){

vard=$.Deferred();

varrunFor=1000+Math.random()*1000;

setTimeout(function(){

varresult=Math.random()*max;

d.resolve(result);

},runFor);

returnd.promise();

}

getRandomNumberAsync(10).then(function(number){

console.log(number);//returnsanumberbetween0and10

});

OurtargetistomakeanasynchronousfunctionthatreturnsaPromisethatiseventuallyresolvedtotheresultingrandomnumber.Atfirst,anewDeferredobjectiscreatedandthentherespectivePromiseobjectisreturned,byusingthepromise()methodoftheDeferred.Whentheasynchronousgenerationoftheresultiscomplete,ourmethodusestheresolve()methodoftheDeferredobjecttosetthefinalstateofthePromisethatwasreturnedearlier.

Thecallerofourfunctionusesthethen()methodofthereturnedPromise,toattachacallbackthatwillbeinvokedwiththeresultasaparameterassoonasthePromisegetsResolved.Moreover,asecondcallbackcanalsobepassedinordertogetnotifiedincasethePromisegetsRejected.Animportantthingtonoticeisthat,byfollowingtheabovepatternwherefunctionsalwaysreturnPromisesandnevertheactualDeferredobjects,wecanbesurethatonlythecreatoroftheDeferredobjectcanchangethestateofthePromise.

UsingPromises/A+Aftersometimeofhands-onexperimentationwithCommonJSPromises/A,thecommunityidentifiedsomeoftheirlimitationsandalsorecommendedsomewaystoimprovethem.TheresultwasthecreationofthePromises/A+specification,asawaytoimprovetheexistingspecificationandalsoasasecondattempttounifythevariousavailableimplementations.ThemostimportantpartsofthenewspecificationfocusedonhowchainingPromisesshouldwork,makingthemevenmoreusefulandconvenienttoworkwith.

NoteFormoreinformationonthePromises/A+specification,youcanvisithttps://promisesaplus.com/.

Finally,thePromises/A+specificationwaspublishedaspartofthe6thversionofJavaScript,commonlyreferredasES6,thatwasreleasedasastandardonJune,2015.Asaresult,Promises/A+startedtobeimplementednativelyinbrowsers,removingtheneedtousecustomthird-partylibrariesandpushingmostoftheexistinglibrariestoupgradetheirsemantics.Asofwritingofthisbook,nativePromises/A+compliantimplementationshavebeenavailableinmostmodernbrowsers,exceptforIE11,makingthemavailableout-of-the-boxtomorethan65%ofwebusers.

NoteFormoreinformationontheadoptionofA+Promisesinbrowsers,youcanvisithttp://caniuse.com/#feat=promises.

ArewriteofthegetRandomNumberAsync()functionusingthenownativelyimplementedES6A+Promiseswilllookasfollows:

functiongetRandomNumberAsync(max){

returnnewPromise(function(resolve,reject){

varrunFor=1000+Math.random()*1000;

setTimeout(function(){

varresult=Math.random()*max;

resolve(result);

},runFor);

});

}

getRandomNumberAsync(10).then(function(number){

console.log(number);//returnsanumberbetween0and10

});

Asyoucansee,ES6/A+PromisesarecreatedbyusingthePromiseconstructorfunctionwiththenewkeyword.Theconstructorisinvokedwithafunctionasaparameter,whichmakesaclosurethathasaccesstoboththevariablesofthecontextthatthePromiseiscreated,butalsogetsaccesstotheresolve()andreject()functionsasparameters,whichistheonlywaytochangethestateofthenewlycreatedPromise.AfterthesetTimeout()functionfiresitscallback,theresolve()functionisinvokedwiththegeneratedrandomnumberasaparameter,changingthestateofthePromiseobjecttoFulfilled.Finally,thecallerofourfunctionusesthethen()methodofthereturnedPromiseinexactlythesamewayaswesawintheearlierimplementationthatwasusingjQuery.

ComparingjQueryandA+PromisesWewillnowhaveanin-depthstep-by-stepanalysisofthecoreconceptsofthejQueryandA+PromiseAPIs,byalsodoingaside-by-sidecodecomparisonofthetwo.Thiscanbeagreatassettohave,sinceyouwillalsobeabletouseitasareferencewhiletheimplementationsofPromisesaregraduallyadaptingtotheES6A+specification.

Theneedtounderstandfromthebeginninghowthetwovariantsdifferseemsevengreater,sincethejQueryteamhasalreadyannouncedthatVersion3.0ofthelibrarywillhavePromises/A+compliantimplementation.Specifically,asofwritingthisbook,thefirstbetaversionisalreadyout,makingthetimethatthemigrationwillhappentoappearevencloser.

NoteFormoreinformationonjQueryv3.0A+Promisesimplementation,youcanvisithttp://blog.jquery.com/2016/01/14/jquery-3-0-beta-released/.

OneofthemostobviousdifferencesbetweenthetwoimplementationsisthewaythatnewPromisesarecreated.Aswesaw,jQueryusesthe$.Deferred()functionlikeafactoryofamorecomplexobjectthatprovidesdirectaccesstothestateofthePromiseandeventuallyextractstheactualPromiseusingaseparatemethod.Ontheotherhand,A+Promisesusethenewkeywordandafunctionasaparameter,whichwillbeinvokedbytheruntimewiththeresolve()andreject()functionsasparameters:

vard=$.Deferred();

setTimeout(function(){

d.resolve(7);

},2000);

varp=d.promise();//jQueryPromise

varp=newPromise(function(resolve,reject){//Promises/A+

setTimeout(function(){

resolve(7);

},2000);

});

Moreover,jQueryalsoprovidesanotherwaytocreatePromisesthatlookmorelikethewaythatA+Promiseswork.Inthiscase,$.Deferred()canbeinvokedwithafunctionasanargumentthatreceivestheDeferredobjectasaparameter:

vard=$.Deferred(function(deferred){

setTimeout(function(){

deferred.resolve(7);

},2000);

});

varp=d.promise();

Aswediscussedearlier,thesecondpossibleoutcomeofaPromiseistobeRejected,afeaturethatnicelypairswiththeclassicalexceptionsofJavaScriptinsynchronousprogramming.RejectingaPromiseiscommonlyusedforcaseswhereanerroroccursduringtheprocessingoftheresult,orinsituationswheretheresultisnotvalid.WhileES6Promisesprovideareject()functionasanargumenttothefunctionpassedtoitsconstructor,injQuery’simplementationareject()methodissimplyexposedontheDeferredobjectitself.

varp=$.Deferred(function(deferred){

deferred.reject(newError('Somethinghappened!'));

}).promise();

varp=newPromise(function(resolve,reject){

reject(newError('Somethinghappened!'));

});

Inboththeimplementations,theresultofaPromisecanberetrievedusingthethen()method,whichcanbeinvokedwithtwofunctionsasarguments,onetohandlethecasethatthePromisegetsFulfilledandoneforthecasewhereitisRejected:

p.then(function(result){//worksthesameinjQuery&ES6

console.log(result);

},function(error){

console.error('Anerroroccurred:',error);

});

BothimplementationsalsoprovideconvenientmethodstohandlethecasewherethePromisegetsRejected,butwithdifferentmethodnames.Insteadofusingp.then(null,fn),ES6Promisesprovidethecatch()methodthatnicelypairswiththetry…catchJavaScriptexpression,whilejQuery’simplementationprovides,forthesamepurpose,thefail()method:

p.fail(function(error){//jQuery

console.error(error);

});

p.catch(function(error){//ES6

console.error(error);

});

Moreover,asajQueryexclusivefeature,jQueryPromisesalsoexposeadone()andanalways()method.Thecallbacksprovidedtodone()areinvokedwhenthePromisegetsFulfilledandisequivalenttousingthethen()methodwithasingleparameter,whilethecallbacksofthealways()methodareinvokedwhenthepromisegetssettledinbothpossibleoutcomes.

NoteFormoreinformationondone()andalways(),youcanvisithttp://api.jquery.com/deferred.doneandhttp://api.jquery.com/deferred.always.

Finally,bothimplementationsprovideaneasywaytodirectlycreatePromisesthatarealreadyResolvedorRejected.Thiscanbeusefulasastartingvaluetoimplementcomplexsynchronizationschemesorasaneasywaytomakesynchronousfunctionstooperatelikeasynchronousones:

varpResolved=$.Deferred().resolve(7).promise();//jQuery

varpRejected=$.Deferred().reject(newError('Something

happened!')).promise();

varpResolved=Promise.resolve(7);//ES6

varpRejected=Promise.reject(newError('Somethinghappened!'));

AdvancedconceptsAnotherkeyconceptofPromisesthatmakesthemuniqueandgreatlyincreasestheirusefulnessistheabilitytoeasilycreatecompositionsofseveralPromisesthatinturnarePromisesthemselves.Compositionisavailableintwoforms,serialcompositionthatchainsPromisestogetherandparallelcompositionthatusesspecialmethodstojointheresolutionofconcurrentPromisesintoanewone.Aswesawearlierinthischapter,implementingsuchsynchronizationschemescanbehardtoimplementwiththetraditionalcallbackapproach.Promises,ontheotherhand,trytosolvethisprobleminamoreconvenientandreadableway.

ChainingPromisesEveryinvocationofthethen()methodreturnsanewPromise,whosebothfinalstatusandresultdependsonthePromisethatthethen()methodwascalledon,butisalsosubjecttothevaluereturnedbytheattachedcallbacks.Thisallowsustochaincallsofthethen()method,enablingustocomposePromisesbyseriallyjoiningthem.Thisway,wecaneasilyorchestratebothasynchronousandsynchronouscode,whereeachchainingsteppropagatesitsresulttothenextoneandallowsustoconstructthefinalresultinareadableanddeclarativeway.

Let’snowproceedtoanalyzingallthedifferentwaysthatchainingofcallstothethen()methodworks.SincewewillbefocusingontheconceptsofPromisecompositionbychaining,whichworksthesameasjQueryandES6Promises,let’ssupposethatthereisapvariablethatisholdingaPromiseobjectcreatedbyeitherofthefollowinglinesofcode:

varp=$.Deferred().resolve(7).promise();

//or

varp=Promise.resolve(7);

Thesimplestusecasethatdemonstratesthepowerofchainingiswhentheinvokedcallbackreturnsa(non-promise)value.ThenewlycreatedPromiseusesthereturnedvalueasitsresult,whilepreservingthesamestateasthePromisethatthethen()methodwascalledon:

p.then(function(x){//worksthesameinjQuery&ES6

console.log(x);//logs7

returnx*3;

}).then(function(x){

console.log(x);//logs21

});

Aspecialcasetohaveinmindisthatfunctionsthatdonotreturnanythingasaresultarehandledlikereturningundefined.ThisessentiallyremovestheresultvaluefromthenewlyreturnedPromise,whichnowonlypreservestheparentsettlementstatus:

p.then(function(x){//worksthesameinjQuery&ES6

console.log(x);//logs7

}).then(function(x){

console.log(x);//logsundefined

});

InthecasewheretheinvokedcallbackreturnsanotherPromise,itsstateandresultareusedforthePromisereturnedbythethen()method:

p.then(function(x){//forjQueryPromises

console.log(x);//logs7

vard2=$.Deferred();

setTimeout(function(){

d2.resolve(x*3);

},2000);

returnd2.promise();

}).then(function(x){

console.log(x);//logs21

});

p.then(function(x){//fortheA+Promises

console.log(x);//logs7

returnnewPromise(function(resolve){

setTimeout(function(){

resolve(x*3);

},2000);

});

}).then(function(x){

console.log(x);//logs21

});

TheprecedingcodesamplesdemonstratetheimplementationsforboththejQueryandA+Promises,andbothhaveequivalentresults.Inbothcases,7isloggedintotheconsolefromthefirstthen()methodinvocationandanewPromiseisthenreturnedthatwillbeResolvedatalatertimeusingsetTimeout().After2000milliseconds,thatsetTimeout()willfireitscallback,thereturnedPromisewillbeResolvedwith21asavalueand,atthatpoint,21willalsobeloggedintotheconsole.

OneextrathingtonoteisthecasewheretheoriginalPromisegetssettledandthereisnoappropriatecallbackprovidedtothechainedthen()method.Inthiscase,thenewlycreatedPromisesettlestothesamestateandresult,asthePromisewherethethen()methodwascalledon:

p.then(null,function(error){//worksthesameinjQuery&ES6

console.error('Anerrorhappened!');//doesnotrun,sincethepromise

isresolved

}).then(function(x){

console.log(x);//logs7

});

Intheprecedingexample,thecallbackwiththeconsole.errorstatementthatispassedasthesecondargumentofthethen()method,doesnotgetinvokedsincethePromiseisresolvedwith7asitsvalue.Asaresult,thecallbackofthechaineventuallyreceivesanewPromise,whichisalsoresolvedwith7asitsvalueandlogsthatintheconsole.SomethingtohaveinmindinordertodeeplyunderstandhowchainingofPromisesworks,isthatp!=p.then()inallcases.

HandlingthrownerrorsThefinalconceptofchainingdefinesthecasewhereexceptionsarethrownduringtheinvocationofathen()callback.ThePromise/A+specificationdefinedthatthenewlycreatedPromiseisRejectedandthatitsresultistheErrorthatwasthrown.Moreover,theRejectionwillbubblethroughtheentirechainofPromises,enablingustobenotifiedaboutanyerrorinthechainonlydefiningtheerrorhandlingonce,neartotheendofthechain.

Unfortunately,thisisnotconsistentintheimplementationofthelateststableversionofjQuery,whichasofthewritingofthisbookisv2.2.0:

$.Deferred().resolve().promise().then(function(){

thrownewError('Somethinghappened!');

//theexecutionstopshere

}).then(null,function(x){

console.log(x);//nothinggetsprinted

});

$.Deferred().resolve().promise().then(function(){

try{//thisisaworkaround

thrownewError('Somethinghappened!');

}catch(e){

return$.Deferred().reject(e).promise();

}

}).then(function(){

console.log('Success');//notprinted

}).then(null,function(x){//almostequivalentto.fail()

console.log(x);//logs'Somethinghappened!''

});

Promise.resolve().then(function(){

thrownewError('Somethinghappened!');

}).then(function(){

console.log('Success');//notprinted

}).then(null,function(x){//equivalentto.catch()

console.log(x);//logs'Somethinghappened!''

});

Inthefirstcase,theexceptionthatisthrownstopstheexecutionofthePromisechain.Theonlywayarounditisprobablyexplicitlyaddingatry…catchstatementinsidethecallbackthatispassedtothethen()method,asshowninthesecondcasethatisdemonstrated.

JoiningPromisesTheotherwayoforchestratingPromisesthatrunconcurrentlyisbycomposingthemtogether.Asanexample,let’ssupposetheexistenceoftwoPromises,p1andp2,thatgetresolvedwith7and11astheirvalues,after2000and3000milliseconds,respectively.SincethesetwoPromisesareexecutedconcurrently,thecomposedPromisewillonlyneed3000millisecondstogetResolved,asitisthegreaterofthetwodurations:

//jQuery

$.when(p1,p2).then(function(result1,result2){

console.log('p1',result1);//logs7

console.log('p2',result2);//logs11

//thiscanbeusedtomakeourcodelooklikeA+

varresults=arguments;

});

//A+

Promise.all([p1,p2]).then(function(results){

console.log('p1',results[0]);//logs7

console.log('p2',results[1]);//logs11

});

BothPromiseAPIsprovideaspecializedfunctionthatallowsustoeasilycreatePromisecompositionsandalsoretrievetheindividualresultsofthecomposition.AcomposedPromisegetsResolvedwhenallitspartsgetResolved,whileitgetsRejectedwhenanyoneofitspartsgetsRejected.Unfortunately,thetwoPromiseAPIsdiffer,notonlybythenameofthefunctions,butalsobythewaytheyareinvokedandthewaytheyprovidetheirresults.

ThejQueryimplementationprovidesthe$.when()methodthatcanbeinvokedwithanynumberofargumentsthatwewanttobecomposed.Byusingthethen()methodonacomposedjQueryPromise,wecangetnotifiedwhenthecompositiongetssettledasawholeandalsoaccesseachindividualresultasargumentsofourcallback.

Ontheotherhand,theA+PromisesspecificationprovidesusthePromise.all()methodthatisinvokedwithanarrayasitssingleparameterthatcontainsallthePromisesthatwewanttogetcomposed.ThereturnedcomposedPromisedoesnotdifferatallfromthePromisesthatwehaveseensofarandthecallbackofthethen()methodisinvokedwithanarrayasitsparameter,whichcontainsalltheresultsofthePromisesthatarepartofthecomposition.

HowjQueryusesPromisesAtthetimethatjQueryaddedanimplementationofPromisestoitsAPI,italsostartedtoexposeitthroughotherasynchronousmethodsofitsAPI.Perhapsthemostwell-knownexampleofthiskindisthemethodofthe$.ajax()familythatreturnsajqXHRobject,whichisaspecializedPromiseobjectthatalsoprovidessomeextramethodsrelatedtotheAJAXrequest.

NoteFormoreinformationonthejQuery’s$.ajax()methodandthejqXHRobject,youcanvisithttp://api.jquery.com/jQuery.ajax/#jqXHR.ThejQueryteamalsodecidedtochangetheimplementationofseveralinternalpartsofthelibrarytousePromises,inordertoimprovetheirimplementations.Firstofall,the$.ready()methodisimplementedusingPromisessothattheprovidedcallbacksfireevenifthepagehasalreadybeenloadedalongtimebeforeitsinvocation.Also,someofthecomplexanimationsthatjQueryprovidesusePromisesinternallyasthepreferredwaytosynchronizetheexecutionofthesequentialpartsoftheanimationqueue.

TransformingPromisestoothertypesDevelopingbyusingseveraldifferentJavaScriptlibrariesoftenmakesmanyPromiseimplementationsavailabletoourprojectsthatunfortunatelytendtohavedifferentlevelsofcompliancetothereferencePromisesspecification.ComposingPromisesreturnedbythemethodsofdifferentlibrariescanoftenleadtoproblemsthatarehardtotrackandresolve,asaresultoftheirimplementationinconsistencies.

Inordertoavoidconfusionsinsuchsituations,itisn’tconsideredagoodpracticetotransformallthePromisestoasingletypebeforeattemptingtocomposethem.ThesuggestedtypeforsuchsituationsisthePromises/A+specification,sincenotonlyisitwidelyacceptedbythecommunitybutitisalsopartofthenewlyreleasedversionofJavaScript(theES6languagespecification)thatisalreadynativelyimplementedinmanybrowsers.

TransformingtoPromises/A+Forexample,let’sseehowajQueryPromisecanbetransformedtoanA+Promisethatisavailableinmostrecentbrowsers:

varjqueryPromise=$.Deferred().resolve('IwillbeA+

compliant').promise();

varp=Promise.resolve(jqueryPromise);

p.then(function(result){

console.log(result);

});

Intheprecedingexample,thePromise.resolve()methoddetectsthatithasbeeninvokedwitha“thenable”andthatthenewlycreatedA+PromisethatisreturnedbindsitsstatusandresulttothoseoftheprovidedjQueryPromise.Thisisessentiallyequivalenttodoingsomethingasfollows:

varp=newPromise(function(resolve,reject){

jqueryPromise.then(resolve,reject);

});

Ofcourse,thisisnotlimitedtoPromisesthatarecreatedbydirectinvocationsofthe$.Deferred()method.TheabovetechniquecanalsobeusedtotransformPromisesthatarereturnedbyanyjQuerymethod.Forexample,thisishowitcanbeusedwiththe$.getJSON()method:

varaPlusAjaxPromise=Promise.resolve($.getJSON('AjaxContent.json'));

aPlusAjaxPromise.then(function(result){

console.log(result);

});

TransformingtojQueryPromisesEventhoughIwouldgenerallynotrecommendthis,itisalsopossibletotransformanyPromisetoajQueryvariant.ThenewlycreatedjQueryPromisereceivesalltheextrafunctionalitiesthatjQueryprovides,butthetransformationisnotasstraightforwardasthepreviousone:

varaPromise=Promise.resolve('IwillbeajQueryPromise');

varp=$.Deferred(function(deferred){

aPromise.then(function(result){

returndeferred.resolve(result);

},function(error){

returndeferred.reject(error);

});

}).promise();

p.then(function(result){

console.log(result);

});

YoushouldonlyusetheprecedingtechniqueincaseswhereyouneedtoextendabigwebapplicationthatisalreadyimplementedusingjQueryPromises.Ontheotherhand,youshouldalsoconsiderupgradingsuchimplementations,sincethejQueryteamhasalreadyannouncedthatVersion3.0ofthelibrarywillhavePromises/A+compliantimplementation.

NoteFormoreinformationonjQueryv3.0A+Promisesimplementation,youcanvisithttp://blog.jquery.com/2016/01/14/jquery-3-0-beta-released/.

SummarizingthebenefitsofPromisesOverall,thebenefitsofusingPromisesoverplainCallbacksinclude:

HavingaunifiedwaytohandletheresultofasynchronousinvocationsHavingpredictableinvocationparametersfortheusedcallbacksTheabilitytoattachmultiplehandlersforeachoutcomeofthePromiseTheguaranteethattheappropriateattachedhandlerswillexecuteevenifthePromisehasalreadybeenResolved(orRejected)Theabilitytochainasynchronousoperations,makingthemruninorderTheabilitytoeasilycreatecompositionsofasynchronousoperations,makingthemrunconcurrentlyTheconvenientwayofhandlingerrorsinPromisechains

UsingamethodthatreturnsaPromiseremovestheneedtodirectlypassfunctionsofonecontexttoanotherasaninvocationargumentandthequestionregardingwhichparametersareusedasthesuccessandtheerrorCallbacks.Moreover,wealreadyknowtosomedegreehowtoretrievetheresultofanyoperationthatreturnsaPromise,byusingthethen()method,evenbeforereadingthedocumentationaboutthemethod’sinvocationparameters.

Lessparametersoftenmeanslesscomplexity,smallerdocumentation,andlesssearchingeverytimewewanttodoamethodinvocation.Evenbetter,thereisagoodchancethattherewillonlybeasingleorafewparameters,makingtheinvocationmoresensibleandreadable.Theimplementationofasynchronousmethodsalsobecomeslesscomplex,sincethereisnolongertheneedtoacceptcallbackfunctionsasanextraargumentorhavingtoproperlyinvokethemwiththeresult.

SummaryInthischapter,weanalyzedthedevelopmentpatternsthatareusedtoprogramasynchronousandconcurrentprocedures.Wealsolearnedhowtousethemtoefficientlyorchestratetheexecutionofasynchronousproceduresthatruneitherinorderorparalleltoeachother.

Atfirst,wehadarefresheronhowCallbacksareusedinJavaScriptprogrammingandhowtheyareanintegralpartofwebdevelopment.Weanalyzedtheirbenefitsandlimitationswhenusedinlargeandcompleximplementations.

Rightafterthis,wewereintroducedtotheconceptsofPromises.WelearnedhowjQuery’sDeferredandPromiseAPIsworkandhowtheydifferfromES6Promises.WealsosawwhereandhowtheyareusedinternallybyjQueryitself,asanexampleofhowtheycanleadtomorereadablecodeandsimplifysuchcompleximplementations.

Inthenextchapter,wewillproceedtolearninghowtodesign,create,anduseMockObjectsandMockServicesinourapplications.WewillanalyzethecharacteristicsthataproperMockObjectshouldhaveandunderstandhowtheycanbeusedasrepresentativeusecasesandevenastestcasesforourcode.

Chapter8.MockObjectPatternInthischapterwewillshowcasetheMockObjectPattern,apatterntofacilitatethedevelopmentofapplicationswithoutactuallybeingpartofthefinalimplementation.Wewilllearnhowtodesign,createandusethisindustry-standarddesignpatterninordertocoordinateandcompletethedevelopmentofmulti-partjQueryapplicationsfaster.WewillanalyzethecharacteristicsthataproperMockObjectshouldhaveandunderstandhowtheycanbeusedasrepresentativeusecasesandevenastestcasesforourcode.

WewillseehowgoodapplicationarchitecturemakesiteasierforustouseMockObjects&Servicesbymatchingindividualpartsoftheapplication,andalsorealizethebenefitsofusingthemduringdevelopment.Bytheendofthischapter,wewillbeabletocreateMockObjects&Servicestoacceleratetheimplementationofourapplicationandalsotogetasenseoftheoverallfunctionalitylongbeforeallofitspartsarecompleted.

Inthischapter,weshall:

IntroducetheMockObjectandMockServicePatternsAnalyzethecharacteristicsthatMockObjects&ServicesshouldhaveUnderstandwhytheyfitbetterwithapplicationswithgoodarchitectureLearnhowtousetheminjQueryapplicationsasawaytodrivethedevelopmentandaccelerateit

IntroducingtheMockObjectPatternThekeyconceptoftheMockObjectPatternisincreatingandusingadummyobjectthatsimulatesthebehaviorofamorecomplexobjectthatis(orwillbe)partofanimplementation.TheMockObjectshouldhavethesameAPIastheactual(orreal)object,returnsimilarresultsusingthesamedatastructures,andalsooperateinasimilarmannerwithregardstohowitsmethodsalteritsexposedstate(theproperties).

MockObjectsareusuallycreatedduringtheearlydevelopmentphasesofanapplication.TheirprimaryusecaseistoenableustoproceedwiththedevelopmentofaModule,evenifitdependsonothersthathavenotyetbeenimplemented.MockObjectscanalsobedescribedasprototypesofthedataexchangedbetweenthedifferentpartsoftheimplementation,actinglikecontractsbetweenthedevelopersandeasingtheparalleldevelopmentofinterdependentmodules.

TipInthesamewaythattheprinciplesoftheModulePatterndecoupletheimplementationsofthedifferentpartsofanapplication,creatingandusingMockObjectsandMockServicesdecouplestheirdevelopment.

CreatingMockObjectsforeveryModulebeforestartingtheirimplementationclearlydefinesthedatastructuresandAPIsthatwillbeusedbytheapplication,removinganymisconceptionsandenablingustodetectinsufficienciesintheproposedAPIs.

TipDefiningthedatastructuresthatarerequiredtodescribeaproblembeforestartingtheactualimplementationallowsustofocusontheneedsoftheapplicationandgetanideaofitsoverallcomplexityandstructure.

YoucanalwaystestanypartofyourimplementationafteranycodechangebyusingtheMockObjectsthatwerecreatedfortheoriginalimplementation.YoucanbesurethattheoriginalusecasestillworksbyusingtheMockObjectsonthemodifiedmethods.Thisisveryusefulwhenthemodifiedimplementationisapartofausecaseinvolvingseveralstages.

MockObjectsareespeciallyusefulfortracingerrorsiftheimplementationofaModulehaschangedandcausedtherestoftheapplicationtomisbehave.ByusingtheexistingMockObjects,wecaneasilyidentifytheModulethatdivergedfromtheoriginalspecification.Moreover,thesameMockObjectscanbeusedasthebasisforhighqualitytestcasessincetheyoftencontainmorerealisticsampledata,somethingespeciallyusefulifyourteamisfollowingaTestDrivenDevelopment(TDD)paradigm.

NoteInTestDrivenDevelopment(TDD),thedeveloperfirstlydefinesatestcaseforausecaseoranewfeaturethatneedstobeaddedandthenproceedswithitsimplementationbytryingtosatisfythecreatedtestcase.Formoreinformation,youcanvisit:

https://www.packtpub.com/books/content/overview-tdd.

TheMockObjectPatterniscommonlyusedamongfrontendwebdeveloperstodecoupletheclient-sidedevelopmentfromthewebservicesthatthebackendwillexpose.Thathasledtowittycommentssuchas:

“Thewebservicewillalwaysbelate&changesuddenly,souseaMockinstead.”

Summarizingallofthis,themainreasonstocreateMockObjectsandServicesinclude:

Theactualobjectorserviceisnotyetimplemented.Theactualobjectisdifficulttosetupforaspecificusecase.Weneedtoemulatearareornon-deterministicbehavior.Theactualobjectbehavesinawaythatishardtoreproduce,suchasnetworkerrorsorUIevents.

UsingMockObjectsinjQueryapplicationsInordertodemonstratehowtheMockObjectPatterncanbeusedduringthedevelopmentofamulti-partapplication,wewillextendthedashboardexample,aswesawinChapter4,DivideandConquerwiththeModulePattern,inordertopresentthumbnailsofYouTubevideosfromwebdevelopingconferences.Thevideoreferencesaregroupedintofourpredefinedcategoriesandtherelatedbuttonswillbedisplayedbasedonthecurrentcategoryselection,asillustratedbelow:

ThechangesthatneedtobeintroducedtotheHTMLandtheCSSareminimal.TheonlyextraCSSthatisneededfortheaboveimplementation,whencomparedtotheexistingimplementationfromChapter4,DivideandConquerwiththeModulePattern,isrelatedtothewidthofthethumbnails:

.boximg{

width:100%;

}

ThechangeintheHTMLisintendedtoorganizethe<button>elementsofeachcategory.ThischangewillmakeourimplementationmorestraightforwardsincethecategoriesandtheiritemsarenolongerstaticallydefinedintheHTMLbutareinsteadcreateddynamically,drivenbytheavailabledata.

<!--…-->

<sectionclass="dashboardCategories">

<selectid="categoriesSelector"></select>

<divclass="dashboardCategoriesList"></div>

<divclass="clear"></div>

</section>

<!--…-->

IntheabovepieceofHTML,the<div>elementwiththedashboardCategoriesListCSSclass,willbeusedasacontainerforthegroupedbuttonsofthedifferentvideocategories.AftercoveringtheUIelements,let’snowmoveontotheanalysisoftheJavaScriptimplementation.

DefiningtheactualservicerequirementsThevideoreferencestobedisplayedinourdashboardcouldberetrievedfromvarioussources.Forexample,youcouldmakeadirectcalltoYouTube’sclient-sideAPIoranAJAXcalltoabackendwebservice.Inalloftheabovecases,itisconsideredagoodpracticetoabstractthisdataretrievalmechanismintoaseparatemodule,followingthecodestructuringrecommendationsofthepreviouschapters.

Forthisreason,weneedtoaddanextramoduletotheexistingimplementation.Thiswillbeaservice,responsibleforprovidingthemethodsthatwillallowustoretrievethemostrelevantvideosfromeachcategoryandloadinformationforeachvideoindividually.ThiswillbeachievedbyusingthesearchVideos()andgetVideo()methodsrespectively.

Aswehavealreadysaid,oneofthemostimportantphasesofeachimplementation,especiallyincaseofparalleldevelopment,istheanalysisanddefinitionofthedatastructurestobeused.SinceourdashboardwillbeusingtheYouTubeAPI,weneedtocreatesomesampledatawhichfollowitsdatastructurerules.AfterinspectingtheAPI,weendupwithasub-setofthefieldsthatarerequiredforourdashboard,andcanproceedtocreateaJSONobjectwithmockdatatodemonstratetheuseddatastructure:

{

"items":[{

"id":{"videoId":"UdQbBq3APAQ"},

"snippet":{

"title":"jQueryUIDevelopmentTutorial:jQueryUITooltip|

packtpub.com",

"thumbnails":{

"default":{"url":

"https://i.ytimg.com/vi/UdQbBq3APAQ/default.jpg"},

"medium":{"url":

"https://i.ytimg.com/vi/UdQbBq3APAQ/mqdefault.jpg"},

"high":{"url":"https://i.ytimg.com/vi/UdQbBq3APAQ/hqdefault.jpg"

}

}

}

}/*,...*/]

}

NoteFormoreinformationabouttheYouTubeAPI,youcanvisit:https://developers.google.com/youtube/v3/getting-started.

Ourserviceprovidestwocoremethods,oneforsearchingforvideosinaspecifiedcategoryandoneforretrievinginformationaboutaspecificvideo.Thestructureofthesampleobjectisusedforthesearchmethodtoretrieveasetofrelevantitems,whilethemethodforretrievinginformationforasinglevideousesthedatastructureofeachindividualitem.TheresultingimplementationforthevideoinformationretrievalisinaseparatemodulenamedvideoService,whichwillbeavailableonthedashboard.videoServicenamespace,andourHTMLwouldcontaina<script>referencelikethefollowing:

<scripttype="text/javascript"src="dashboard.videoservice.js"></script>

ImplementingaMockServiceChangingthe<script>referencesoftheserviceimplementationwiththeMockServiceandviceversashouldleaveuswithaworkingapplication,helpingusprogressandtesttherestoftheimplementationbeforetheactualimplementationofthevideoserviceisfinished.Asaresult,theMockServiceneedstousethesamedashboard.videoServicenamespace,butitsimplementationshouldbeinadifferentlynamedfilesuchasdashboard.videoservicemock.jsthatsimplyaddsthe“mock”suffix.

Aswehavealreadymentioned,itisagoodpracticetoplaceallourmockdataunderasinglevariable.Moreover,iftherearealotofMockedObjects,itiscommontoplacetheminadifferentfilealtogether,withanestednamespace.Inourcase,thefilewiththemockdataisnameddashboard.videoservicemock.mockdata.jsanditsnamespaceisdashboard.videoService.mockData,whileexposingthesearchesandvideospropertiesthatwillbeusedbythetwocoremethodsofourMockService.

EventhoughtheimplementationsofMockServicesshouldbesimple,theyhavetheirowncomplexitysincetheyneedtoprovidethesamemethodsasthetargetimplementations,acceptthesamearguments,andlookasiftheyareoperatingintheexactsameway.Forexample,inourcase,thevideoretrievalserviceneedstobeasynchronousanditsimplementationneedstoreturnPromises:

(function(){//dashboard.videoservicemock.js

'usestrict';

dashboard.videoService=dashboard.videoService||{};

dashboard.videoService.searchVideos=function(searchKeywords){

return$.Deferred(function(deferred){

varsearches=dashboard.videoService.mockData.searches;

for(vari=0;i<searches.length;i++){

if(searches[i].keywords===searchKeywords){

//returnthefirstmatchingsearchresults

deferred.resolve(searches[i].data);

return;

}

}

deferred.reject('Notfound!');

}).promise();

};

dashboard.videoService.getVideo=function(videoTitle){

return$.Deferred(function(deferred){

varvideos=dashboard.videoService.mockData.allVideos;

for(vari=0;i<videos.length;i++){

if(videos[i].snippet.title===videoTitle){

//returnthefirstmatchingitem

deferred.resolve(videos[i]);

return;

}

}

deferred.reject('Notfound!');

}).promise();

};

varvideoBaseUrl='https://www.youtube.com/watch?v=';

dashboard.videoService.getVideoUrl=function(videoId){

returnvideoBaseUrl+videoId;

};

})();

AsshownintheMockServiceimplementationabove,thesearchVideos()andgetVideo()methods,areiteratingoverthearrayswiththemockdataandreturnaPromisethatiseitherResolvedwithanappropriateMockObjectorRejectedwhensuchanobjectisnotfound.Finally,youcanseebelowthecodeforthesub-modulecontainingtheMockObjects,followingthedatastructurethatwedescribedearlier.NotethatwestoretheMockObjectsofallcategoriesintheallVideospropertyinordertomakesearchingwiththemockgetVideo()methodsimpler.

(function(){//dashboard.videoservicemock.mockdata.js

'usestrict';

dashboard.videoService.mockData=dashboard.videoService.mockData||

{};

dashboard.videoService.mockData.searches=[{

keywords:'jQueryconference',

data:{

"items":[/*...*/]

}

}/*,...*/];

varallVideos=[];

varsearches=dashboard.videoService.mockData.searches;

for(vari=0;i<searches.length;i++){

allVideos=allVideos.concat(searches[i].data.items);

}

dashboard.videoService.mockData.allVideos=allVideos;

})();

ExperimentingwiththeimplementationofsomeMockServiceswillgetyoufamiliarwiththeircommonimplementationpatternsinaveryshortperiodoftime.Beyondthat,youwillbeabletoeasilycreateMockObjectsandServices,helpingyoudesigntheAPIsofyourapplications,trythemoutbyusingthemocksandfinallysettleonthebestmatchingmethodsanddatastructuresforeachusecase.

TipUsingthejQueryMockjaxlibrary

TheMockjaxjQueryPluginlibrary(availableathttps://github.com/jakerella/jquery-mockjax)focusesonprovidingasimplewayofmockingorsimulatingAJAXrequestsandresponses.ThisreducesthecodeneededtofullyimplementyourownMockServices,ifallthatyouneedistointerceptanAJAXrequesttoawebserviceandreturnaMock

Objectinstead.

UsingtheMockServiceInordertoaddthefunctionalitythatwedescribedearliertotheexistingdashboardimplementation,weneedtointroducesomechangestothecategoriesandtheinformationBoxmodules,addingthecodethatwillconsumethemethodsofourservice.AsarepresentativeexampleofusingthenewlycreatedMockService,let’stakealookattheimplementationoftheopenNew()method,intheinformationBoxmodule:

dashboard.informationBox.openNew=function(itemName){

var$box=$('<divclass="boxsizer"><articleclass="box">'+

'<headerclass="boxHeader">'+

'<buttonclass="boxCloseButton">&#10006;</button>'+

itemName+

'</header>'+

'<divclass="boxContent">Loading…</div>'+

'</article></div>');

$boxContainer.append($box);

dashboard.videoService.getVideo(itemName).then(function(result){

var$a=$('<a>').attr('href',

dashboard.videoService.getVideoUrl(result.id.videoId));

$a.append($('<img/>').attr('src',

result.snippet.thumbnails.medium.url));

$box.find('.boxContent').empty().append($a);

}).fail(function(){

$buttonContainer.html('Anerroroccurred!');

});

};

ThismethodinitiallyopensanewinformationboxwithaLoading…labelasitscontentandusesthedashboard.videoService.getVideo()methodtoretrievethedetailsoftherequestedvideoasynchronously.Finally,whenthereturnedPromisegetsresolved,itreplacestheLoading…labelwithananchorcontainingthethumbnailofthevideo.

SummaryInthischapter,welearnedhowtodesign,createanduseMockObjectsandMockServicesinourapplications.WeanalyzedthecharacteristicsthatMockObjectsshouldhaveandunderstoodhowtheycanbeusedasrepresentativeusecases.WearenowabletouseMockObjects&Servicestoacceleratetheimplementationofourapplicationsandgetabettersenseofitsoverallfunctionality,longbeforeallofitsindividualpartsarecompleted.

Inthenextchapter,wewillbeintroducedtoclient-sidetemplatingandlearnhowtogeneratecomplexHTMLstructuresinthebrowserfromreadabletemplatesefficiently.WewillgetanintroductiontoUnderscore.jsandHandlebars.js,analyzetheirconventions,evaluatetheirfeaturesandfindwhichonebettersuitsourtaste.

Chapter9.Client-sideTemplatingThischapterwilldemonstratesomeofthemostwidelyusedlibrariestocreatecomplexHTMLtemplatesfaster,whilemakingourimplementationeasiertoreadandunderstandwhencomparedtotraditionalstringconcatenationtechniques.WewilllearninmoredetailhowtousetheUnderscore.jsandHandlebars.jstemplatinglibraries,getatasteoftheirconventions,evaluatetheirfeaturesandfindtheonethatbestsuitsourtaste.

Bytheendofthischapter,wewillbeabletogeneratecomplexHTMLstructuresinthebrowserefficientlybyusingreadabletemplatesandutilizingtheuniquecharacteristicsofeachtemplatinglibrary.

Inthischapter,wewill:

DiscussthebenefitsofusingaspecializedtemplatinglibraryIntroducethecurrenttrendsinclient-sidetemplating,specificallythetoprepresentativeofthefamiliesthatuse<%%>and{{}}astheirplaceholdersIntroduceUnderscore.jsasanexampleofthefamilyoftemplatingenginesthatuse<%%>placeholdersIntroduceHandlebars.jsasanexampleofthefamilyoftemplatingenginesthatusecurlybraces{{}}placeholders

IntroducingUnderscore.jsUnderscore.jsisaJavaScriptlibrarythatprovidesacollectionofutilitymethodsthathelpwebdevelopersworkmoreefficientlyandfocusontheactualimplementationoftheirapplicationratherthanbotheringwithrepetitivealgorithmicproblems.Underscore.jsis,bydefault,accessiblethroughthe“_”identifieroftheglobalnamespaceandthat’sexactlywhereitsnamecomesfrom.

NoteAswiththe$identifierinjQuery,theunderscore“_”identifiercanalsobeusedasavariablenameinJavaScript.

Oneoftheutilityfunctionsthatitprovidesisthe_.template()method,whichprovidesuswithaconvenientwayofinterpolatingspecificvaluesintoexistingtemplatestringsthatfollowaspecificformat.The_.template()methodrecognizesthreespecialplaceholdernotationsinsidetemplates,whichareusedtoadddynamiccharacteristics:

The<%=%>notationisusedasthesimplestwaytointerpolateavalueofavariableoranexpressioninatemplate.The<%-%>notationperformsHTMLescapingonavariableorexpressionandtheninterpolatesitinatemplate.The<%%>notationisusedtoexecuteanyvalidJavaScriptstatementaspartofthetemplategeneration.

The_.template()methodacceptsatemplatestringthatfollowsthesecharacteristicsandreturnsaplainJavaScriptfunction,commonlyreferredtoasthetemplatefunction,whichcanbeinvokedwithanobjectcontainingthevaluesthataregoingtobeinterpolatedinthetemplate.Theresultoftheinvocationofthetemplatefunctionisastringvalue,whichistheresultoftheinterpolationoftheprovidedvaluesinsidethetemplate:

vartemplateFn=_.template('<h1><%=title%></h1>');

varresultHtml=templateFn({

title:'Underscore.jsexample'

});

Asanexample,theabovecodereturns<h1>Underscore.jsexample</h1>andisequivalenttothefollowingshorthandinvocation:

varresultHtml=_.template('<h1><%=title%></h1>')({

title:'Underscore.jsexample'

});

NoteFormoreinformationaboutthe_.templatemethod,youcanreadthedocumentationat:http://underscorejs.org/#template.

WhatmakesUnderscore.jstemplatesveryflexibleisthe<%%>notation,whichallowsustoperformanymethodinvocationandis,forexample,usedastherecommendedwaytocreateloopsinatemplate.Ontheotherhand,overusingthisfeaturemayaddtoomuch

logictoyourtemplates,whichisaknownanti-patternfoundinmanyotherframeworks,violatingtheprincipleofSeparationofConcerns.

UsingUnderscore.jstemplatesinourapplicationsAsanexampleofusingUnderscore.jsfortemplating,wewillnowuseittorefactortheHTMLcodegenerationwhichtakesplaceinsomemodulesofthedashboardexample,aswesawinpreviouschapters.ThemodificationsrequiredtotheexistingimplementationarelimitedtothecategoriesandtheinformationBoxmodules,whichmanipulatetheDOMtreeofthepagebyaddingnewelements.

Thefirstplacethatsucharefactorcanbeappliedisintheinit()methodofthecategoriesmodule.Wecanmodifythecodethatcreatestheavailable<option>softhe<select>categorytolooklikethis:

varoptionTemplate=_.template('<optionvalue="<%=value%>"><%-title%>

</option>');

varoptionsHtmlArray=[];

for(vari=0;i<dashboard.categories.data.length;i++){

varcategoryInfo=dashboard.categories.data[i];

optionsHtmlArray.push(optionTemplate({

value:i,

title:categoryInfo.title

}));

}

$categoriesSelector.append(optionsHtmlArray.join(''));

Asyoucansee,weiterateoverthecategoriesofthedashboardinordertocreateandappendtheappropriate<option>elementstothe<select>categoryelement.Inourtemplate,weareusingthe<%=%>notationforthevalueattributeofthe<option>sinceweknowthatitwillholdanintegervaluethatdoesnotneedescaping.Ontheotherhand,weareusingthe<%-%>notationforthecontentpartofeach<option>inordertoescapethetitleofeachcategoryforthecaseitsvalueisnotanHTML-safestring.

Weareusingthe_.template()methodoutsidetheforloopinordertocreateasinglecompiledtemplatefunctionthatwillbereusedoneachiterationoftheforloop.Inthisway,thebrowsernotonlyexecutesthe_.template()methodjustonce,butalsooptimizesthegeneratedtemplatefunctionandmakesitrunfasteroneachsubsequentexecutioninsidetheforloop.Lastly,weareusingthejoin('')methodtocombinealltheHTMLstringsoftheoptionsHtmlArrayvariableandappend()theresulttotheDOMwithasingleoperation.

Analternativeandpossiblysimplerwaytoachievethesameresultisbycombiningthe<%%>notationandthe_.each()methodthatUnderscore.jsprovides,enablingustoimplementaloopinsidethetemplateitself.Inthisway,thetemplatewillberesponsiblefortheiterationovertheprovidedarrayofcategories,movingthecomplexityfromtheimplementationofthemoduleintothetemplate.

vartemplateSource=''.concat(

'<%_.each(categoryInfos,function(categoryInfo,i){%>',

'<optionvalue="<%=i%>"><%-categoryInfo.title%></option>',

'<%});%>');

varoptionsHtml=_.template(templateSource)({

categoryInfos:dashboard.categories.data

});

$categoriesSelector.append(optionsHtml);

Asyoucanseeintheabovecode,ourJavaScriptimplementationnolongercontainsaforloop,reducingitscomplexityandtherequirednesting.Thereisonlyasinglecalltothe_.template()method,whichnicelyabstractstheimplementationtoanoperationthatgeneratestheHTMLandrendersthe<option>elementsforallthecategories.YoucanalsoseehownicelythistechniquefitsinwiththeCompositelogicthatjQueryitselffollows,inwhichthemethodsaredesignedtooperateovercollectionsofelementsinsteadofsingleitems.

SeparatingHTMLtemplatesfromJavaScriptcodeEvenafterintroducingalloftheaboveimprovements,itsoonstartstobecomeobviousthatwritingtemplatesinbetweenyourapplicationlogicmightnotbethebestapproachtofollow.Assoonasyourapplicationbecomescomplexenough,orwhenyouneedtousetemplatesthataremorethanafewlineslong,theimplementationstartstofeelfragmentedbythemixoftheapplication’slogicandtheHTMLtemplates.

AcleanerapproachtothisproblemistostoreyourtemplatesalongsidetherestoftheHTMLcodeofyourpage.ThisisagoodsteptowardsbetterSeparationofConcernssinceitproperlyisolatesthepresentationfromtheapplicationlogic.

InordertoincludeHTMLtemplatesaspartofwebpagesinaninactiveform,weneedtouseahosttagthatwillpreventthemfrombeingrendered,butalsoallowustoretrieveitscontentprogrammaticallywhenneeded.Forthispurpose,wecanuse<script>tagsinsidethe<head>orthe<body>ofourpageandspecifyanytypeotherthanthecommontext/javascriptthatwenormallyuseforourJavaScriptcode.Theoperationprinciplebehindthisisthatbrowsersdonottrytoparse,executeorrenderthecontentof<script>tags,incasetheirtypeattributeisn’trecognized.Aftersomeexperimentation,thecommunityofUnderscore.jsusershaslargelyadoptedthispracticeandagreedtospecifytext/templateasthepreferredtypeforthese<script>tags,inanattempttomaketheseimplementationsmoreuniformamongdevelopers.

TipEventhoughUnderscore.jsisneitheropinionatednorcontainsanyimplementationspecifictothewaythatthetemplatesbecomeavailable,usingtext/template<script>tagsand/orAJAXrequestshavebeenvaluabletechniquesthatarewidelyusedandareconsideredbestpractices.

Asanexampleofacomplextemplatethatwouldbebeneficialtomoveintoa<script>tag,wewillrefactortotheopenNew()methodoftheinformationBoxmodule.Asyoucanseeinthecodebelow,theresulting<script>tagiscleanlyformattedandwenolongerneedtousestringconcatenationforthedefinitionofthemulti-linetemplate:

<scriptid="box-template"type="text/template">

<divclass="boxsizer">

<articleclass="box">

<headerclass="boxHeader">

<buttonclass="boxCloseButton">&#10006;</button>

<%-itemName%>

</header>

<divclass="boxContent">Loading…</div>

</article>

</div>

</script>

AgoodpracticewhenmovingHTMLtemplatesoutofourcodeistowriteanabstractedmechanismtoberesponsibleforretrievingthemandprovidingthecompiledtemplatefunction.Thisapproachnotonlydecouplestherestoftheimplementationfromthetemplateretrievalmechanismbutalsomakesitlessrepetitiveandcreatesacentralizedmethoddesignedtoprovidetemplatesfortherestoftheapplication.Moreover,aswecanseebelow,thisapproachalsoallowsustooptimizethewaythattemplatesareretrieved,propagatingthebenefitstoalltheplacesthattheyareused.

vartemplateCache={};

functiongetEmbeddedTemplate(templateName){

varcompiledTemplate=templateCache[templateName];

if(!compiledTemplate){

vartemplate=$('#'+templateName).html();

compiledTemplate=_.template(template);

templateCache[templateName]=compiledTemplate;

}

returncompiledTemplate;

}

dashboard.informationBox.openNew=function(itemName){

varboxCompiledTemplate=getEmbeddedTemplate('box-template');

varboxHtml=boxCompiledTemplate({

itemName:itemName

});

var$box=$(boxHtml).appendTo($boxContainer);

/*...*/

};

Asshownintheaboveimplementation,theopenNew()methodoftheinformationBoxmodulesimplyinvokesthegetEmbeddedTemplate()functionbypassingauniqueidentifierthatisassociatedwiththerequestedtemplateandusesthereturnedtemplatefunctiontogeneratethenewbox’sHTMLandfinallyappendittothepage.ThemostinterestingpartoftheimplementationisthegetEmbeddedTemplate()method,whichusesthetemplateCachevariableasadictionarytoholdallthepreviouslycompiledtemplatefunctions.

Thefirststepisalwaystocheckwhethertherequestedtemplateidentifierexistsinourtemplatecache.Ifnot,thentheDOMtreeofthepageissearchedforthe<script>tagwiththerelatedIDanditsHTMLcontentisusedtocreatethetemplatefunction,whichisthenstoredinthecacheandreturnedtothecaller.

KeepinmindthatitisagoodpracticetouseaspecificprefixorsuffixforalltheidentifiersofyourHTMLtemplatesinordertoavoidconflictswiththeIDsofotherpage

elements.Forthispurpose,intheaboveexampleweusedthe-templateasasuffixoftheidentifierofourboxtemplate.

Ideally,theimplementationofthetemplateprovidermethodshouldbeinaseparatemodulethatwillbeusedbyallthepartsofanapplicationbut,sinceinourdashboardthisisusedinonlyoneplace,wemettheneedsofourdemonstrationbysimplyusingafunction.

IntroducingHandlebars.jsHandlebars.js,orsimplyHandlebars,isaspecializedclient-sidetemplatinglibrarythatenableswebdeveloperstocreatesemantictemplateseffectively.UsingHandlebarsfortemplatingleadstothecreationoflogic-freetemplateswhichensuresthattheviewandthecodeareisolated,helpingpreservetheSeparationofConcernsprinciple.ItislargelycompatiblewithMustachetemplates,whichareatemplatinglanguagespecificationthathaveproventheireffectivenessovertimeandhavemanyimplementationsforallthemajorprogramminglanguages.Additionally,HandlebarsprovidesasetofextensionsontopoftheMustachetemplatespecification,suchashelpermethodsandpartials,asameansofextendingthetemplatingengineandcreatingmoreeffectivetemplates.

NoteYoucanseeallthedocumentationforHandlebarsat:http://handlebarsjs.com/.YoucangetmoreinformationaboutMustacheinJavaScriptat:https://github.com/janl/mustache.js/.

ThemaintemplatenotationthatHandlebarsprovidesisthedoublecurlybracessyntax{{}}.AsHandlebarswasdesignedtobeusedforHTMLtemplatesfromthebeginning,thisnotationalsoappliesHTMLescapingbydefault,loweringthechancesthatanon-escapedvaluecouldreachthetemplatecausingpotentialsecurityproblems.Ifanon-escapedinterpolationisrequiredforaspecificpartofatemplate,wecanusethetriplecurlybracesnotation{{{}}}.

Moreover,sinceHandlebarspreventsusfrominvokingmethodsdirectlyfromwithinatemplate,itprovidesuswiththeabilitytodefineandusehelpermethodsandblockexpressionsasawaytocovermorecomplexusecaseswhilealsohelpingtomaintainourtemplatesascleanandreadableaspossible.Thesetofbuilt-inhelpersincludesthe{{#if}}and{{#each}}helperswhichallowustoperformiterationsoverarraysandchangetheoutcomesofatemplatebasedonconditionsveryeasily.

ThecentralmethodoftheHandlebarslibraryistheHandlebars.compile()method,whichacceptsatemplatestringasaparameterandreturnsafunctionthatcanbeusedtogeneratestringvaluesthatfollowtheformoftheprovidedtemplate.Thisfunctioncanthenbeinvoked(asinUnderscore.js)withanobjectasaparameter,thepropertiesofwhichwillbeusedasacontextfortheevaluationofalltheHandlebarsexpressions(thecurlybracesnotations)thatweredefinedintheoriginaltemplate:

vartemplateFn=Handlebars.compile('<h1>!!!{{title}}!!!</h1>');

varresultHtml=templateFn({

title:'>Handlebarsexample<'

});

Asanexample,theabovecodereturns"<h1>!!!&gt;Handlebarsexample&lt;!!!</h1>",turningtheinterpolatedtitleintoasafeHTMLstring,butonewhichwouldotherwiserenderproperlywhenattachedtotheDOMtreeofapage.Ofcourse,thesameresultcanbeachievedwiththefollowingshorthandinvocation,ifwedon’tneedtokeepareferencetothecompiledtemplatefunctionforfutureuse:

varresultHtml=Handlebars.compile('<h1>!!!{{title}}!!!</h1>')({

title:'>Handlebarsexample<'

});

UsingHandlebars.jsinourapplicationsAsanexampleofusingHandlebars.jsfortemplatingandinordertodemonstrateitsdifferencesfromUnderscore.jstemplates,wewillnowuseittorefactorourdashboardexample,likewedidintheprevioussection.Likebefore,therefactoringislimitedtothecategoriesandtheinformationBoxmodules,whichmanipulatetheDOMtreeofthepagebyaddingnewelements.

Therefactoredimplementationoftheinit()methodofthecategoriesmoduleshouldlooklikethis:

varoptionTemplate=Handlebars.compile('<optionvalue="{{value}}">{{

title}}</option>');

varoptionsHtmlArray=[];

for(vari=0;i<dashboard.categories.data.length;i++){

varcategoryInfo=dashboard.categories.data[i];

optionsHtmlArray.push(optionTemplate({

value:i,

title:categoryInfo.title

}));

}

$categoriesSelector.append(optionsHtmlArray.join(''));

Firstofall,wehaveusedtheHandlebars.compile()methodwhichgeneratesandreturnsatemplatefunctionbasedontheprovidedtemplatestring.ThemaindifferencewiththeUnderscore.jsimplementationwesawintheprevioussection,isthatwenowusethedoublecurlybracesnotation{{}}tointerpolatevaluesinourtemplate.Apartfromthedifferentappearance,Handlebars.jsalsodoesHTMLstringescapingbydefaultinanattempttoeliminateHTMLinjectionsecurityholesbymakingescapingpartofitsprimaryusecase.

Aswedidearlierinthischapter,wewillcreatethetemplatefunctionoutsidetheforloopanduseittogeneratetheHTMLforeach<option>element.AllthegeneratedHTMLstringsaregatheredinanarrayandarefinallycombinedandattachedtotheDOMtreewithasingleoperation,usingthe$.append()method.

ThenextincrementalsteptoreducethecomplexityofourimplementationistoabstracttheiterationsawayfromourJavaScriptcodeusingtheloopingcapabilitiesofthetemplatingengineitself:

vartemplateSource=''.concat(

'{{#eachcategoryInfos}}',

'<optionvalue="{{@index}}">{{title}}</option>',

'{{/each}}');

varoptionsHtml=Handlebars.compile(templateSource)({

categoryInfos:dashboard.categories.data

});

$categoriesSelector.append(optionsHtml);

TheHandlebars.jslibraryallowsustoachievethatbyusingthespecial{{#each}}notation.Inbetweenthe{{#each}}and{{/each}},thecontextofthetemplateischangedtomatcheachindividualobjectoftheiteration,allowingtodirectlyaccessand

interpolatethe{{title}}ofeachobjectinthecategoryInfosarray.Moreover,inordertoaccesstheloopcounter,Handlebarsprovidesuswiththespecial@indexvariableaspartofthecontextoftheloop.

NoteForafulllistofallthespecialnotationsthatHandlebarsprovides,youcanreadthedocumentationat:http://handlebarsjs.com/reference.html

SeparatingHTMLtemplatesfromJavaScriptcodeLikemosttemplatingengines,HandlebarsalsoleadsustoisolateourtemplatesfromtheJavaScriptimplementationofourapplicationanddeliverthemtothebrowserbyincludingthemin<script>tags,insidetheHTMLofourpages.Moreover,Handlebarsisopinionatedandprefersthespecialtext/x-handlebars-templateasthetypeattributeforall<script>tagsthatcontainHandlebarstemplates.Forexample,hereishowthetemplateforthedashboard’sboxesshouldbedefinedaccordingtothelibraryrecommendations:

<scriptid="box-template"type="text/x-handlebars-template">

<divclass="boxsizer">

<articleclass="box">

<headerclass="boxHeader">

<buttonclass="boxCloseButton">&#10006;</button>

{{itemName}}

</header>

<divclass="boxContent">Loading…</div>

</article>

</div>

</script>

TipEventhoughourimplementationwouldstillworkifadifferenttypewasspecifiedforthe<script>tag,followingthelibrary’sguidelinescanobviouslymakeimplementationsmoreuniformamongdevelopers.

Aswedidearlierinthischapter,wewillfollowthebestpracticeofcreatingaseparatefunctiontoberesponsibleforprovidingthetemplateswherevertheyareneededintheapplication:

vartemplateCache={};

functiongetEmbeddedTemplate(templateName){

varcompiledTemplate=templateCache[templateName];

if(!compiledTemplate){

vartemplate=$('#'+templateName).html();

compiledTemplate=Handlebars.compile(template);

templateCache[templateName]=compiledTemplate;

}

returncompiledTemplate;

}

dashboard.informationBox.openNew=function(itemName){

varboxCompiledTemplate=getEmbeddedTemplate('box-template');

varboxHtml=boxCompiledTemplate({

itemName:itemName

});

var$box=$(boxHtml).appendTo($boxContainer);

/*...*/

};

Asyoucansee,theimplementationismostlythesameastheUndescore.jsexamplethatwesawearlierinthischapter.TheonlydifferenceisthatwearenowusingtheHandlebars.compile()methodtogeneratethecompiledtemplatefunctionsfromtheretrievedtemplates.

Pre-compilingtemplatesAnextrafeatureoftheHandlebarslibraryisthesupportfortemplatepre-compilation.Thisallowsustopre-generateallthetemplatefunctionswithasimpleterminalcommandandthenhaveourserverdelivertothemtothebrowser,insteadoftheactualtemplates.Inthisway,thebrowserwillbeabletousethepre-compiledtemplatesdirectly,removingtheneedforthecompilationofeachindividualtemplateandmakingtheexecutionofthelibraryandourapplicationfaster.

Inordertopre-compileourtemplates,wefirstneedtoplacetheminseparatefiles.TheHandlebarsdocumentationsuggestsusingthe.handlebarsextensionforourfilesbutwecanstillusethe.htmlextensionifitispreferred.Afterinstallingthecompilationtoolonourdevelopmentmachine(withnpminstallhandlebars-g),wecanissuethefollowingcommandinourterminaltocompileatemplate:

handlebarsbox-template.handlebars-fbox-template.js

Thiswillgeneratethebox-template.jsfilethatisactuallyamini-moduledefinitionthataddsthetemplatetoHandlebars.templates.ThegeneratedfilecanthenbecombinedandminifiedlikeregularJavaScriptfilesand,whenloadedbyabrowser,thetemplatefunctionwillbecomeavailablethroughtheHandlebars.templates['box-template']property.

NoteKeepinmindthatifthe.htmlextensionisbeingusedforthetemplates,thenthepre-compiledtemplatefunctionwillbeavailablethroughtheHandlebars.templates['box-template.html']property.

Asyoucansee,usingatemplateproviderfunctionassistswiththemigrationofanexistingapplicationtopre-compiledtemplatessinceitallowsustoencapsulatethewaythatthetemplatesareretrieved.Movingtopre-compiledtemplatesonlyrequireschangingthegetEmbeddedTemplate()tosomethinglikethis:

functiongetEmbeddedTemplate(templateName){

returnHandlebars.templates[templateName];

}

Note

Formoreinformationabouttemplatepre-compilationinHandlebars,readthedocumentationat:http://handlebarsjs.com/precompilation.html.

RetrievingHTMLtemplatesasynchronouslyThefinalsteptomasteringclient-sidetemplatingisadevelopmentpracticethatallowsustoloadtemplatesdynamicallyandusetheminawebpagethathasalreadybeenloaded.Thisapproachcanleadtomorescalableimplementationsthantheapproachofembeddingalltheavailabletemplatesas<script>tagsinsidetheHTMLsourceofeachpage.

Thekeyelementofthistechniqueistoloadeachtemplateonlywhenitisrequiredforthepresentationofawebpage,commonlyafterauseraction.Themainbenefitsofthisapproacharethat:

TheinitialpageloadtimeisreducedsincetheHTMLofthepageissmaller.Thegainsfromthereductionofthepagesizebecomeevengreaterifourapplicationhasalotoftemplatesthatareusedonlyundercertaincircumstances,forexample,afterspecificuserinteractions.Theuseronlydownloadsatemplateifitisactuallygoingtobeused.Inthisway,thesizeofthetotaldownloadedresourcesforeachpageloadcanbereduced.Subsequentrequestsforanalreadyloadedtemplatewillnotleadtoanextradownload,sincethebrowser’sHTTPcachingmechanismwillreturnthecachedresource.Additionally,sincethebrowsercacheisusedforallHTTPrequestsregardlessofthepagefromwhichtheyoriginate,usersonlyhavetodownloadtherequiredtemplateoncewhileusingourwebapplication.

Becauseofitsbenefitstouserexperienceanditsscalability,thistechniqueiswidelyusedbythemostpopularwebmailandsocialnetworkingwebsites,wherevariousHTMLtemplatesandJavaScriptmodulesareloadeddynamically,basedonuseractions.

NoteFormoreinformationonhowjQuerycanbeusedtoloadJavaScriptmodulesonapagedynamically,readthedocumentationforthe$.getScript()methodat:https://api.jquery.com/jQuery.getScript/.

AdoptingitinanexistingimplementationToillustratethistechnique,wewillchangetheUnderscore.jsandHandlebars.jsimplementationsoftheinformationBoxmodulesothatitfetchestheboxtemplateforourdashboardusinganAJAXrequest.

Let’sproceedbyanalyzingthenecessarychangesforourUnderscore.jsimplementation:

vartemplateCache={};

functiongetAjaxTemplate(templateName){

varcompiledTemplate=templateCache[templateName];

if(compiledTemplate){

return$.Deferred().resolve(compiledTemplate);

}

return$.ajax({

mimeType:'text/html',

url:templateName+'.html'

}).then(function(template){

templateCache[templateName]=_.template(template);

returntemplateCache[templateName];

});

}

Asyoucanseeintheabovecode,wehaveimplementedthegetAjaxTemplate()functionasawayofdecouplingthemechanismthatisresponsibleforfetchingthetemplatefromtheimplementationthatusesit.ThisimplementationhasalotincommonwiththegetEmbeddedTemplate()functionthatweusedearlier,themaindifferencebeingthatthegetAjaxTemplate()functionisasynchronousand,asaresult,returnsaPromise.

ThegetAjaxTemplate()functionfirstlycheckswhetherornottherequestedtemplatealreadyexistsinitscache,asanextraattempttoreduceHTTPrequeststotheserver.Ifthetemplateisfoundinthecache,thenitisreturnedaspartofaResolvedPromise,otherwiseweinitiateanAJAXrequestusingthe$.ajax()methodtoretrieveitfromtheserver.Likebefore,weneedtohaveaconventionregardingthenamingofthetemplateHTMLfilesandthepathusedtostorethemintheserver.Inourexample,wearelookinginthesamedirectoryasthewebpageitselfandjustappendingthe.htmlfileextension.Anextraconcerninsomecases,dependingonthewebserverused,isthedefinitionofthemimeTypeoftheresourceastext/html.

WhentheAJAXrequestcompletes,thethen()methodisexecutedwiththecontentofthetemplateasastringparameter,whichisusedtogeneratethecompiledtemplatefunction.OurimplementationfinallyreturnsthecompiledtemplatefunctionastheresultofthechainedPromise,rightafteraddingittoitscache.SincethegetAjaxTemplate()functionisasynchronous,wealsohadtochangetheimplementationoftheopenNew()methodandmoveallthecodeusingthereturnedtemplatefunctioninsideathen()callback.Apartfromthis,theimplementationhasremainedthesameandusesthetemplatefunctioninexactlythesamewayasbefore.

dashboard.informationBox.openNew=function(itemName){

vartemplatePromise=getAjaxTemplate('box-template');

templatePromise.then(function(boxCompiledTemplate){

varboxHtml=boxCompiledTemplate({

itemName:itemName

});

var$box=$(boxHtml).appendTo($boxContainer);box);

/*...*/

});

};

Whenre-implementingthegetAjaxTemplate()functiontouseHandlebars.js,theresultingcodeismostlythesameasbefore.TheonlydifferenceisintheinvocationoftheHandlebars.compile()methodinsteadoftheUndescore.jsequivalent.Thisisanaddedbenefitasmanyclient-sidetemplatingenginesinfluencedeachotherandhaveconvergedintoaverysimilarAPIregardingthewaythattheirtemplatefunctionsareused,largelybecauseofthepositiveuserfeedbackontheexistingimplementations.

functiongetAjaxTemplate(templateName){

/*…sameasbefore…*/

return$.ajax({/*…sameasbefore…*/}).then(function(template){

templateCache[templateName]=Handlebars.compile(template);

returntemplateCache[templateName];

});

}

NoteKeepinmindthatthe$.ajax()methodmightnotworkinsomebrowserswhenthepageisloadedthroughthefilesystem,butworksasintendedwhenservedusingawebserverlikeApache,IIS,ornginx.

ModerationisbestinallthingsEventhoughthistechniquereducestheoveralldownloadfootprintofeachwebpage,italsoinevitablyincreasesthenumberofHTTPrequestsmade.Moreover,thepracticeofloadingeverytemplatelazilycansometimesincreasethetimethattheuserwillhavetowaitifthetemplatesarerequiredfortheinitialrenderingofthepage.

Balancingthewaythatweloadourtemplatesbetweenlazyloadingandembeddingthemin<script>tagsusuallybringsthebestofbothworlds.Thishybridapproachisconsideredabestpracticebytheindustrysinceitallowsustomicromanageandfinetuneeachimplementationbasedonitsneeds.Accordingtothispractice,thetemplatesthatarerequiredforthepresentationofthemaincontentofapageareembeddedinitsHTML,whiletherestofthemaredeliveredlazilywhenneeded,takingadvantageofbrowsercaching.

Theimplementationofsuchatemplateproviderfunctionisleftasanexerciseforthereader.Asahint,suchmethodshavetobeasynchronoussince,whentherequestedtemplateisnotfoundembeddedinthe<script>tagofthepage,itwillhavetoproceedandmakeanAJAXrequesttoretrieveitfromtheserver.

TipKeepinmindthatitisgenerallypreferabletogeneratethecompleteinitialHTMLcontentofthepageontheserversideinsteadofusingclient-sidetemplating.ThisnotonlyleadstoasmallerloadingtimeoftheinitialpagecontentbutitalsopreventssituationsinwhichtheuserispresentedwithanemptypagewhenJavaScriptisunavailableoranerrorhasoccurred.

SummaryInthischapter,welearnedhowtousetwoofthemostcommonclient-sidetemplatinglibraries:Underscore.jsandHandlebars.js.WealsolearnedhowtheyallowustocreatecomplexHTMLtemplatesfasterwhilemakingourimplementationseasiertoreadandunderstand.Wethenwentontoanalyzetheirconventionsandevaluatetheirfeaturesandlearnedbyexamplehowtheycanbeeffectivelyandefficientlyusedinourimplementations.

Aftercompletingthischapter,wearenowabletogeneratecomplexHTMLstructuresinabrowserefficientlybyusingreadabletemplatesandutilizingtheuniquecharacteristicsofthetemplatinglibraries.

Inthenextchapter,wewilllearnhowtocreatejQueryPluginsasawaytoabstractpartsofourapplicationsintoreusableandextensibleimplementations.WewillintroducethemostwidelyusedpatternsfordevelopingjQueryPluginsandanalyzetheimplementationproblemsthateachofthemhelpstosolve.

Chapter10.PluginandWidgetDevelopmentPatternsThischapterfocusesonthedesignpatternsandbestpracticesusedwhenimplementingjQueryPlugins.WewilllearnherehowtoabstractpartsofanapplicationintoseparatejQueryPlugins,promotingtheSeparationofConcernsprincipleandcodereusability.

WewillfirstlyanalyzethesimplestwaysthatajQueryPlugincanbeimplemented,learnthevariousconventionsofjQueryPlugindevelopmentandthebasiccharacteristicsthateverypluginshouldsatisfyinordertofollowjQueryprinciples.Wewillthenproceedwithanintroductiontothemostwidelyuseddesignpatternsandanalyzethecharacteristicsandbenefitsofeachofthem.Bytheendofthischapter,wewillbeabletoimplementextensiblejQueryPluginsusingthedevelopmentpatternthatbestsuitseachusecase.

Inthischapterwewill:

IntroducethejQueryPluginAPIanditsconventionsAnalyzethecharacteristicsthatmakeanexcellentpluginLearnhowtocreateapluginbyextendingthe$.fnobjectLearnhowtoimplementgenericpluginsthatareextensibleinordertomakethemreusableinmoreusecasesLearnhowtoprovideoptionsandmethodstoyourpluginsIntroducethemostcommondesignpatternsforjQueryplugindevelopmentandanalyzethecommonimplementationproblemsthateachofthemhelpstosolve

IntroducingjQueryPluginsThekeyconceptofjQuerypluginsliesinextendingthejQueryAPIbymakingtheirfunctionalityaccessibleasamethodonjQueryCompositeCollectionObjects.AjQuerypluginissimplyafunctionthatisdefinedasanewmethodonthe$.fnobject,whichisthePrototypeObjectthateveryjQueryCollectionObjectinheritsfrom.

$.fn.simplePlugin101=function(arg1,arg2/*,...*/){

//Plugin'simplementation…

};

Bydefiningamethodonthe$.fnobject,weareactuallyextendingthecorejQueryAPIitself,sincethismakesthemethodavailableonallcreatedjQueryCollectionObjectsfromthatpointonwards.Asaresult,afterapluginhasbeenloadedinawebpage,itsfunctionalityisavailableasamethodoneveryobjectreturnedbythe$()function:

$('h1').simplePlugin101('test',1);

ThemainconventionofthejQuerypluginAPIisthatthejQueryCollectionObjectthatthepluginwasinvokedonismadeavailabletotheplugin’smethodasitsexecutioncontext.Inotherwords,wecanusethethisidentifierinthepluginmethod,asshownbelow:

$.fn.simplePlugin101=function(){

this.slideToggle();

//"this"isajQueryobjectwhereall

//jQuerymethodsareavailable

};

FollowingjQueryprinciplesOneofthegoalswhencreatingapluginistomakeitfeellikeapartofjQueryitself.Afterreadingthepreviouschapters,youshouldbefamiliarwithsomeoftheprinciplesthatalljQuerymethodsfollowandthecharacteristicsthatmakeitsapproachspecial.ImplementingapluginthatfollowstheseprinciplesmakesusersfeelmorecomfortablewithitsAPI,bemoreproductive,andmakefewerimplementationerrors,whichleadstoanincreaseintheplugin’spopularityandadoption.

TwoofthemostimportantcharacteristicsthatagreatjQuerypluginshouldhaveareasfollows:

ItshouldapplyonalltheelementsofthejQueryCollectionObjectitisinvokedonwheneverapplicableItshouldallowfurtherchainingofotherjQuerymethods

Let’snowmoveonandanalyzeeachoftheseprinciples.

WorkingonCompositeCollectionObjectsOneofthemostimportantfeaturesofjQuerymethodsisthattheyareappliedoneveryitemoftheCompositeCollectionObjectthattheyareinvokedon.Asanexample,the$.fn.addClass()methodaddsoneormoreCSSclassestoeveryitemofthecollectionafterindividuallycheckingwhethereachclasshasalreadybeendefinedoneachindividualelement.

Asaresult,ourjQuerypluginsshouldalsofollowthisprinciplebyoperatingoneveryelementofacollection,whensuchathingseemslogical.IfyouareusingonlyjQuerymethodsinyourplugin’simplementation,mostofthetime,yougetthisforfree.Ontheotherhand,animportantconsiderationtobearinmindisthatnotalljQuerymethodsoperateoneveryelementofacollectionobject.Methodslike$.fn.html(),$.fn.css()and$.fn.data()applyonalltheitemsofthecollectionwhenusedassettermethods,butoperateonlyonthefirstelementwhenusedasgetters.

Let’sseeanexampleimplementationofapluginthatuses$.fn.animate()tocreateashakeeffectonallitemsofajQueryobject:

$.fn.vibrate=function(){

this.each(function(i,element){

//specificallyhandleeveryelement

var$element=$(element);

if($element.css('position')==='static'){

$element.css({position:'relative'});

}

});

this.animate({left:'+=3'},30)

.animate({left:'-=6'},60)

.animate({left:'+=6'},60)

.animate({left:'-=3'},30);

returnthis;//allowfurtherchaining

};

Invokingthispluginwith$('button').vibrate();appliestheshakinganimationoneverymatchedelementofthepage.Toachievethat,thepluginchangestheleftCSSpropertyofallmatchedelementsusingthe$.fn.animate()method,whichconvenientlyoperatesoneveryelement.Ontheotherhand,sincethe$.fn.css()methodappliesonlyonthefirstelementofthecollectionwhenusedasagetter,wehadtoiterateoveralltheelementsusingthe$.fn.each()methodandensurethateachofthemwasnotstaticallypositioned,inwhichcasetheleftCSSpropertywouldnotaffectitsappearance.

Obviously,usingonlyjQuerymethodsisnotalwayssufficientfortheimplementationofaplugin.Inmostcases,anewpluginwillhavetouseatleastonenon-jQueryAPIforitsimplementation,requiringustoiterateovertheitemsofthecollectionandapplythelogicoftheplugintoeachofthemindividually.Thesameapproachshouldbeusedwheneachelementofthecollectionhastobehandledslightlydifferentlybasedonitsstate.

Asaresult,itisquitecommonforpluginstowrapalmostalloftheirimplementationsinsidea$.fn.each()invocation.Byrecognizingthecommonneedsthatarecoveredbyexplicititeration,thejQueryteamandmostjQuerypluginboilerplatesnowmakeitpartoftheirstandardpractice.

AllowingfurtherchainingIngeneral,whenyourplugin’scodedoesnotneedtoreturnanything,allthatyouhavetodotoenablefurtherchainingistoaddareturnthis;statementtoitslastline,aswesawinthepreviousexample.Makesurethatallthecodepathsreturnareferenceoftheinvocationcontext(this)oranotherrelevantjQuerycollectionobject,inthesamewaythat$.fn.parent()and$.fn.find()do.Alternatively,whenallyourcodeiswrappedinsideanotherjQuerymethod,suchas$.fn.each(),itiscommonpracticetosimplyreturntheresultofthatinvocation,asdemonstratedbelow:

$.fn.myLogPlugin=function(){

returnthis.each(function(i,element){

console.log($(element).text());

});

};

Keepinmindthat,ifyourcodemanipulatesthecollectionobjectthatitwasinvokedon,insteadofreturningthethisreference,youmightneedtoreturnthenewcollectionthatwastheresultofyourplugin’smanipulations.

NoteYoushouldavoidbasingyourplugin’simplementationonareturnvalueinordertoallowfurtherchaining.Insteadofdoingthat,itispreferabletoinitializethepluginonitsfirstinvocationandthenprovidesomeoverloadedwaystoinvokeit,asawayofreturningvalues.

Workingwith$.noConflict()Thefirststeptoimproveaplugin’simplementationistomakeitworkinenvironmentsthatdonothaveaccesstothe$identifier.AnexampleofthisiswhenawebpageusesthejQuery.noConflict()method,whichpreventsjQueryfromassigningitselftothe$globalidentifier(orwindow.$)andkeepsitavailableonlyonthejQuerynamespace(window.jQuery).

NoteThejQuery.noConflict()methodallowsustopreventjQueryfromconflictingwithotherlibrariesandimplementationsthatalsohappentousethe$variable.Formoreinformation,youcanvisitthejQuerydocumentationpageat:http://api.jquery.com/jQuery.noConflict/

Insuchcases,theplugindefinitionwouldthrowan$isnotdefinederrororevenworse;itmighttrytousethe$variablethatthedeveloperhasreservedtouseinanimplementation,leadingtoerrorsthatarehardtodebug.

Fortunately,thechangesrequiredtofixthisproblemareeasytoimplementanddonotaffectthefunctionalityoftheplugin.Allthatwehavetodoisrenamealloftheoccurrencesofthe$identifierinourpluginwithjQuery,asshownbelow:

jQuery.fn.simplePlugin101=function(arg1,arg2/*,...*/){

var$buttons=jQuery('button');

//...

};

WrappingwithanIIFEThenextbestpracticetofollowistowrapthedefinitionandimplementationofourpluginwithanIIFE.ThisnotonlymakesourpluginlookliketheModulePatternbutalsomakesourimplementationmorerobustbyaddingseveralotherbenefitstoit.

Firstofall,theIIFEpatternallowsustocreateanduseprivatevariablesandfunctionsinthecontextoftheplugin’sdefinition.Thesevariablesaresharedacrossalltheinstancesoftheplugininasimilarwaytohowstaticvariablesworkinotherprogramminglanguages,enablingustousethemassynchronizationpointsbetweentheplugininstances:

(function($){

varcallCounter=0;

functionutilityLogMethod(message){

if(window.console&&console.log){

console.log(message);

}

}

$.fn.simplePlugin101=function(arg1,arg2/*,...*/){

callCounter++;

utilityLogMethod(callCounter);

returnthis;

};

})(jQuery);

Otherwise,wewouldhavetousesomethinglike$.simplePlugin101._callCounteror$.simplePlugin101._utilityLogMethod()toemulateprivacy,whichisjustanamingconventionanddoesnotprovideanyactualprivacy.

Thesecondbenefit,asdemonstratedintheaboveexample,isthatitallowsustousethe$identifieragaintoaccessjQuerywithnoconcernsaboutconflicts.Inordertoachievethis,wearepassingthejQuerynamespacevariableasaninvocationparametertoourIIFEandusethe$identifiertonametherespectiveparameter.Inthisway,weeffectivelyaliasthejQuerynamespaceto$inthecontextcreatedbytheIIFE,enablingustousetheminimal$identifierinourimplementationtokeepourcodeslimandreadable,evenifjQuery.noConflict()isused.

Additionally,addingtheusestrict;statementonthetopofourIIFEhelpsustoeliminateanyleakingofvariablesintotheglobalnamespace.Forexample,thefollowingcodewouldthrowaReferenceError:assignmenttoundeclaredvariablexerrorduringtheinvocationoftheplugin’smethod,enablingustocatchthoseerrorsduringthedevelopmentphaseofthepluginhelpingproduceamorerobustfinalimplementation:

(function($){

'usestrict';

$.fn.leakingPlugin=function(){

x=0;//thereisno"varx"declaration,

//soanerroristhrownwhenexecuted

};

})(jQuery);

$('div').leakingPlugin();

NoteFormoreinformationaboutJavaScript’sstrictexecutionmode,youcanvisit:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode

Finally,thispattern,aswithallthenamespacealiasingpracticesthatuseIIFEs,canalsohelpincreasethegainswhenminifyingyourplugin’ssourcecode,whencomparedtoanimplementationthatreferencesthejQuerynamespacevariabledirectly.Inanattempttomaximizethebenefitsofthistechnique,it’salsocommontoaliasalltheglobalnamespacevariablesthatourpluginaccesses,asdemonstratedbelow:

(function($,window,document,undefined){

//Plugin'simplementation…

})(jQuery,window,document);

CreatingreusablepluginsAfteranalyzingthemostimportantaspectsofthedevelopmentofjQueryplugins,wearenowreadytoanalyzeanimplementationthatisusedforsomethingmorethanasimpledemonstration.Inordertocreateareallyusefulandreusableplugin,itmustbedesignedsuchthatitsoperationsarenotrestrictedbythedemandsofitsoriginalusecase.

Themostpopularplugins,likethemostusefuljQuerymethods,arethosethatprovideahighdegreeofconfigurationoftheirfunctionality.Creatingapluginthatisconfigurableaddsadegreeofflexibilitytoitsimplementation,whichenablesustomatchtheneedsofseveralotherusecasesthataregovernedbythesameoperationprinciples.

Aswesaidearlier,ajQuerypluginisjustafunctionattachedtothe$.fnobjectand,asaresult,wecanmakeitsimplementationmoreabstractandgenericinthesamewayaswithplainfunctionsofourmodules.Asinsimplefunctions,theeasiestwaytodifferentiatetheoperationofajQuerypluginisbyusinginvocationparameters.Apluginthatexposesalotofconfigurationparametershasgreatpotentialofbeingabletobematchtherequirementsofseveraldifferentusecases.

AcceptingconfigurationparametersIncontrasttohowweimplementfunctionsthatusuallyacceptuptofiveargumentsandstillhaveamanageableandrelativelycleanAPI,thispracticedoesnotworksowellwithjQueryplugins.InordertoexposeaclearAPIandmaintainahighlevelofusability,regardlessofthevariousconfigurationoptionsthatareexposed,mostjQuerypluginsprovideaminimalAPIthatacceptsuptothreeinvocationarguments.Thisisachievedbyusingdedicatedsettingobjectswithaspecificformat,asawayofencapsulatingmultipleoptionsandpassingthemasasingleargument.AnotherapproachistoexposeanAPIwithtwoparameters,wherethefirstoneisaregularvaluethatdefinestheoperationofthepluginandthesecondoneisusedtowrapthelessimportantconfigurationoptions.

Agreatexampleofbothofthesepracticesisthe$.ajax(settings)method,whichisinvokedwithasinglesettingsobjectasaparametertodefinehowitshouldoperate,butalsoexposesanotheroverloadedwaytobeinvokedwithtwoarguments.Thetwoargumentoverloadisinvokedwith$.ajax(url,settings),wherethefirstisthetargetURLfortheHTTPrequestandthesecondisanobjectwiththerestconfigurationoptions.Whatappliestobothofthemisthatthemethoditselfcontainsasetofsensibledefaultsthatareusedinsteadofanyconfigurationparameterthattheuserhasnotdefined.Moreover,thesecondoverloadalsodefinesthesecondparameterasoptionaland,ifthatwasnotprovidedduringitsinvocation,itbasesitsoperationonthedefaultsettings.

Adoptingthesettingsobjectpracticeinourpluginsnotonlybringsalltheaforementionedbenefits,butalsoallowsustoextendtheimplementationinamorescalableway,sincetheadditionofanextraconfigurationparameterhaslittleeffectontherestofitsAPI.Asanexampleofthis,wewillreimplementthe$.fn.vibratepluginthatwesawearlierinthischapterinamoregenericway,sothatasettingobjectwithdefaultvaluesisusedforitsconfiguration:

(function($){

$.fn.vibrate=function(options){

varopts=$.extend({},$.fn.vibrate.defaultOptions,options);

this.each(function(i,element){

var$element=$(element);

if($element.css('position')==='static'){

$element.css({position:'relative'});

}

});

for(vari=0,len=opts.loops*4;i<len;i++){

varanimationProperties={};

varmovement=(i%2)?'+=':'-=';

movement+=(i===0||i===len-1)?

opts.amplitude/2:

opts.amplitude;

vart=(i===0||i===len-1)?

opts.period/4:

opts.period/2;

animationProperties[opts.direction]=movement;

this.animate(animationProperties,t);

}

returnthis;

};

$.fn.vibrate.defaultOptions={

loops:2,

amplitude:8,

period:100,

direction:'left'

};

})(jQuery);

Incontrasttotheoriginalfixedimplementation,thisoneacceptsasingleobjectasaninvocationparameterwhichwrapsfourdifferentoptionsthatcanbeusedtodiversifytheoperationoftheplugin.Theoptionsobjectallowsustodiversifytheoperationofthepluginbyexposingfourcustomizationpoints:

ThenumberofloopsthattheshakeeffectshouldrunTheamplitudeoftheanimation,asameansofcontrollinghowmuchanelementshouldmoveawayfromitsoriginalpositionTheperiodofeachloop,asameansofcontrollinghowfastthemovementwillbeThedirectionoftheanimation,whichishorizontalwhenleftisusedorverticalwhentopisused

Byfollowingawidelyacceptedbestpractice,wehavedefinedallthedefaultvaluesfortheconfigurationoptionsasaseparateobject.Thispatternnotonlyallowsustogatheralltherelatedvaluesunderasingleobject,butalsoenablesustousethe$.extend()methodasaneffectivewayofcomposingallthedefinedoptionswiththedefaultvaluesoftheundefinedones.Wecanthusavoidcheckingexplicitlyfortheexistenceofeachindividualproperty,reducingthecomplexityandthesizeofourcode.

Inbrief,the$.extend()methodreturnstheobjectpassedasitsfirstargumentaftermergingthepropertiesofthesubsequentobjectstogetherintothefirstobject.Asaresult,thereturnedobjectwillcontainallthedefaultvaluesexceptthosethatweredefinedintheoptionsobjectthatwaspassedasaninvocationparameter.

NoteFormoreinformationaboutthe$.extend()helpermethod,youcanvisitthedocumentationpageat:http://api.jquery.com/jQuery.extend/

Moreover,insteadofusingasimplevariable,weareexposingthedefaultoptionsobjectasapropertyoftheplugin’sfunction,enablinguserstochangethemtobettersuittheirneeds.Asanexample,consideracaseinwhichasmoothanimationisrequiredfortheneedsofaspecificapplication.Bysetting$.fn.vibrate.defaultOptions.period=250,thedeveloperwouldcompletelyremovetheneedtospecifytheperiodoptionin

everyinvocationoftheplugin,whichwouldleadtoanimplementationwithlessrepetitivecode.

NoteThejQuerylibraryitselfadoptsthispracticefordefiningthedefaultconfigurationparametersofthe$.ajax()method.Becauseoftheincreasedcomplexityofthismethod,jQueryprovidesuswiththejQuery.ajaxSetup()methodasawayofsettingupthedefaultparametersforeveryAJAXrequest.

Finally,inordertocreateagenericvariantoftheoriginalimplementationandutilizetheaforementionedconfigurationoptions,wereplacedthefourfixedinvocationsofthe$.fn.animate()methodoftheoriginalimplementationwithaforloopthatutilizedtheloopsoption.Insidetheforloopitself,weconstructtheparametersforeachcallofthe$.fn.animate()methodandbrieflyalternatethedirectionoftheanimatedmovementoneachsubsequentexecutionoftheloop,andalsoensurethatthefirstandlastmovementshavehalfofthetimedurationandhalfoftheshiftofalloftheothersteps.

Thefinalimplementationcanbeconfiguredtoproducedifferentanimations,basedontheneedsofeachspecificusecase,rangingfromshorthorizontalanimationsthatareidealfornotifyingauseraboutaninvalidaction,toverticallonganimationsthatlooklikealevitationeffect.Theplugincanbeinvokedwithanycombinationoftheaforementionedoptions,usethedefaultvaluesformissingoptionsandevenoperatewithnoinvocationargument,asshownbelow:

//dothedefaultintenseanimationonabutton

//thatappearsdisabled,todesignateaninvalidaction

$('button.disabled').on('click',function(){

$(this).vibrate();

});

//doasmothershakeanimationtocatchtheuser's

//attentiononanimportantpartofthepage

$('.save-button').vibrate({loops:3,period:250});

//startalongrunninglevitationeffectontheheaderofthepage

$('h1').vibrate({direction:'top',loops:1000,period:5000});

WritingstatefuljQuerypluginsThepluginimplementationsthatwehavelookedatsofarwerestatelesssince,aftercompletingtheirexecution,theyreverttheirmanipulationsontheDOM’sstateanddon’tleaveallocatedobjectsinthebrowser’smemory.Asaresult,subsequentinvocationsofstatelesspluginsalwaysproducethesameresults.

Asyoucanprobablyguess,suchpluginshavelimitedapplicationssincetheycan’tbeusedtocreateaseriesofcomplexinteractionswiththeuserofthewebpage.Inordertoorchestratecomplexuserinteractions,apluginneedstopreserveaninternalstatewiththeactionstakenuptothatpointinordertochangeitsoperationmodeappropriatelyandhandlesubsequentinteractions.Comparingthecharacteristicofstatefulandstatelesspluginscouldbedefinedastheequivalenttocomparingplain(static)functionswithmethodsthatarepartofanobjectandcanoperateonitsstate.

Anotherpopularcategoryofplugins,inwhichhavinganinternalstateisessential,isthefamilyofpluginsthatmanipulatetheDOMtree.Thesepluginsusuallycreatecomplexelementstructuressuchasarichtexteditors,datepickersandcalendars,commonlybybuildingonauser-definedempty<div>element.

ImplementingastatefuljQueryPluginAsanexampleofthepatternsusedfortheimplementationofpluginsofthisfamily,wewillwriteagenericElementMutationObserverplugin.ThispluginwillprovideuswithaconvenientwayofaddingeventlistenersforchangestotheDOMtreethatoriginatefromanyoftheelementsthatthispluginwasinvokedon.Asawayofachievingthat,thefollowingimplementationusestheMutationObserverAPI,which,atthetimeofwriting,isimplementedbyallmodernbrowsersandisavailabletomorethan86%ofwebusers.

NoteFormoreinformationontheMutationObserver,youcanvisit:https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver

Let’snowproceedwiththeimplementationandanalyzethepracticesthatwereused:

(function($){

$.fn.mutationObserver=function(action){

returnthis.each(function(i,element){

var$element=$(element);

varinstance=$element.data('plugin_mutationObserver');

if(!instance){

varobserver=newMutationObserver(function(mutations){

mutations.forEach(function(mutation){

instance.callbacks.forEach(function(callbackFn){

callbackFn(mutation);

});

});

});

observer.observe(element,{

attributes:true,

childList:true,

characterData:true

});

instance={

observer:observer,

callbacks:[]

};

$element.data('plugin_mutationObserver',instance);

}

if(typeofaction==='function'){

instance.callbacks.push(action);

}

});

};

})(jQuery);

Firstly,wedefineourplugininsideanIIFE,asrecommendedearlierinthischapter.Rightafterthedeclarationofthepluginonthe$.fnobject,weusethe$.fn.each()methodasa

directapproachtoensurethatthefunctionalityofourpluginisappliedtoeveryitemofthejQueryCollectionObjectthatitwasinvokedon.

Twoofthemainissuesthatstatefulpluginimplementationshaveisthelackofamechanismtopreservetheinternalstateofeachinstantiationofthepluginandawayofavoidingbeinginitializedmanytimesonthesamepageelement.Inordertosolvebothoftheseproblems,weneedtousesomethinglikeahashtableinwhichthekeyistheelementitselfandthevalueisanobjectwiththestateoftheplugin’sinstance.

Fortunately,thisismoreorlesshowthe$.fn.data()methodworksbyassociatingDOMelementsandJavaScriptobjectvaluesusingspecificstringkeys.Byusingthe$.fn.data()methodandtheplugin’snameasanassociationkey,weareabletostoreandretrievethestateobjectofourpluginveryeasily.

TipUsingthe$.fn.data()methodforthisusecaseisconsideredabestpracticeandisusedbymoststatefulpluginimplementationsandboilerplatessinceitisarobustpartofjQuerythatenablesustoreducethesizeofourplugin’simplementation.

Ifanexistingstateobjectisnotfoundthenwecanassumethatthepluginisnotyetinitializedonthatspecificelementandstartitsinitializationrightaway.ThestateobjectofthispluginwillcontaintheinstanceoftheactiveMutationObserverresponsiblefortrackingthechangesthathappenontheobservedDOMelement,andanarraywithallthecallbacksthathavesubscribedtoittogetnotificationsaboutchanges.

AftercreatinganewMutationObserverinstance,weconfigureittolookforthreespecifictypesofDOMchangesandinstructittoinvokeallthecallbacksoftheplugin’sstateobjectwheneversuchDOMchangesoccur.Finally,wecreatethestateobjectitselftoholdtheobserverandtheassociatedcallbacksandusethe$.fn.data()methodasasetterandassociateitwiththepageelement.

Afterensuringthatthepluginisinstantiatedandinitializedontheprovidedelement,wecheckwhetherthepluginisinvokedwithafunctionasaparameterand,ifso,weaddittothelistoftheplugin’scallbacks.

TipKeepinmindthatusingasingleMutationObserverinstanceperelementandhavingitnotifyaboutDOMchangesbyiteratingoveranarrayofcallbacksgreatlyreducesthememoryrequirementsoftheimplementation,justlikewhenweareusingasingledelegateobserver.

AnexampleofusingournewlyimplementedplugintoobserveforchangesofaspecificDOMelementwouldlooklikethis:

$('.container').mutationObserver(function(mutation){

console.log('SomethingchangedontheDOMtree!');

});

DestroyingaplugininstanceAnextraconsiderationthatastatefulpluginhastotakeintoaccountisofferingthedeveloperawaytoreversethechangesthatitintroducedtothestateofthepage.ThemostcommonandsimpleAPIforachievingthisistoinvokethepluginwiththedestroyliteralasitsfirstparameter.Let’sproceedwiththerequiredimplementationchanges:

(function($){

$.fn.mutationObserver=function(action){

returnthis.each(function(i,element){

var$element=$(element);

varinstance=$element.data('plugin_mutationObserver');

if(action==='destroy'&&instance){

instance.observer.disconnect();

instance.observer=null;

$element.removeData('plugin_mutationObserver');

return;

}

if(!instance){

/*...*/

}

});

};

})(jQuery);

Inordertoadaptourimplementationtotheaboverequirement,allwehadtodowastocheckwhetherthepluginwasinvokedwiththedestroystringvalueasitsfirstparameter,rightafterretrievingtheplugin’sstateobject.Ifwefindthatthepluginhasalreadybeeninstantiatedonthespecifiedelementandthatthedestroystringvaluehasbeenused,wecanproceedtostoptheMutationObserveritselfandcleartheassociationthat$.fn.data()createdbyusingthe$.fn.removeData()method.Finally,attheendoftheifstatementweaddedareturnstatementsince,aftercompletingthedestructionoftheplugininstance,wenolongerneedtoexecuteanyothercode.Anexampleofdestroyingaplugininstancewiththisimplementationwouldlooklikethis:

$('.container').mutationObserver('destroy');

ImplementinggetterandsettermethodsByusingthesametechniquethatwedemonstratedearlierfortheimplementationofthedestroymethodofourplugin,wecanprovideseveralotheroverloadedwaystoinvokeourpluginthatworklikenormalmethods.ThispatternisnotonlyusedbyplainjQueryplugins,butisalsoadoptedbymorecomplexpluginarchitectures,aswithjQuery-UI.

Ontheotherhand,wemightendupwithapluginimplementationthatresultsinalargenumberofinvocationoverloads,whichissomethingthatwouldmakeitdifficulttouseanddocument.AwaytoworkaroundthisistocombinethegetterandsettermethodsofyourAPIintomulti-purposemethods.ThisnotonlyreducestheAPIsurfaceofyourpluginsothatadeveloperhastorememberfewermethodnamesbutitalsoincreasestheproductivitysincethesamepatternisusedinmanyjQuerymethodslike$.fn.html(),$.fn.css(),$.fn.prop(),$.fn.val(),and$.fn.data().

Asademonstrationofthis,let’sseehowwecanaddanewmethodtoourMutationObserverpluginthatworksbothasagetterandasetterfortheregisteredcallbacks:

(function($){

$.fn.mutationObserver=function(action,callbackFn){

varresult=this;

this.each(function(i,element){

var$element=$(element);

varinstance=$element.data('plugin_mutationObserver');

/*...*/

if(typeofaction==='function'){

instance.callbacks.push(action);

}elseif(action==='callbacks'){

if(callbackFn&&callbackFn.length>=0){

//usedasasetter

instance.callbacks=callbackFn;

}else{

//usedasagetterforthefirstelement

result=instance.callbacks;

returnfalse;//breakthe$.fn.each()iteration

}

}

});

returnresult;

};

})(jQuery);

Asshownintheabovecode,wehavecreatedanoverloadedinvocationmethodwhichusesthecallbacksstringvalueasthefirstargumentoftheplugininvocation.ThisgetterandsettermethodallowsustoretrieveoroverwriteallofthecallbacksthatareregisteredontheMutationObserverandworksinadditiontothepre-existingmethodsforinvokingtheplugin,byusingafunctionparameterandthedestroymethod.

Thegetterandsetterimplementationisbasedontheassumptionthat,whentryingtousethecallbacksmethodasagetter,youdon’tneedtopassanyextraparametersand,whentryingtouseitasasetter,youwillpassanextraarrayasaninvocationparameter.Inordertosupportthegettervariant,whichpreventsfurtherchainingandonlyoperatesonthefirstelementofthecompositecollection,wehadtodeclareandusetheresultvariablewhichisinitializedtothevalueofthethisidentifier.Ifthecallbacksgetterisused,weassignthecallbacksofthefirstelementofthecollectiontotheresultvariableandbreakoutofthe$.fn.each()iterationbyreturningfalsetofinishtheexecutionoftheplugin’smethod.

Hereisanexampleusecaseforournewlyimplementedgetterandsettermethod:

//retrievethecallbacks

varoldCallbacks=$('.container').mutationObserver('callbacks');

//clearthem

$('.container').mutationObserver('callbacks',[]);

//addanewone

$('.container').mutationObserver(function(){

console.log('Printedonlyonce');

//restoretheoldcallbacks

$('.container').mutationObserver('callbacks',oldCallbacks);

});

TipKeepinmindthatinvocationoverloadsthatpreventfurtherchainingbyreturningnon-jQueryobjectresultsshouldbewelldocumentedsincethistechniqueconflictswiththechainingprinciplethateveryoneexpectstowork.

UsingourplugininourDashboardapplicationAftercompletingourmutationObserverplugin,letsnowseehowwecanuseitfortheimplementationofthecountersub-modulethatweusedinourDashboard’simplementationinpreviouschapters:

(function(){

'usestrict';

dashboard.counter=dashboard.counter||{};

var$counter;

dashboard.counter.init=function(){

$counter=$('#dashboardItemCounter');

var$boxContainer=dashboard.$container

.find('.boxContainer');

$boxContainer.mutationObserver(function(mutation){

dashboard.counter.setValue($boxContainer.children().length);

});

};

dashboard.counter.setValue=function(value){

$counter.text(value);

};

})();

Asyoucanseeintheaboveimplementation,ourpluginabstractsnicelyandreplacestheoldimplementationbyprovidingageneric,flexibleandreusableAPI.Insteadoflisteningforclickeventsonthedifferentbuttonsofthepage,theimplementationisnowusingthemutationObserverpluginandobservestheboxContainerelementfortheadditionsorremovalsofchildelements.Moreover,thisimplementationchangedoesnotaffectthefunctionalityofthecountermodulewhichappearstoworkinthesamewaysinceallthechangesareencapsulatedinthemodule.

UsingthejQueryPluginBoilerplateThejQueryBoilerplateproject,whichisavailableathttps://github.com/jquery-boilerplate/jquery-patterns,offersseveraltemplatesthatcanbeusedasstartingpointsfortheimplementationofrobustandextensibleplugins.Thesetemplatesincorporatealotofbestpracticesanddesignpatternssuchasthoseanalyzedearlierinthischapter.Eachofthetemplatespacksanumberofbestpracticesthatworkwelltogether,inanattempttoprovidegoodstartingpointsthatbettermatchthevarioususecases.

Perhapsthemostwidelyusedtemplateisjquery.basic.plugin-boilerplatefromAdamSontagandAddyOsmani,whicheventhoughitischaracterizedasagenerictemplateforbeginnersandabove,successfullycoversmostaspectsofjQueryplugindevelopment.WhatmakesthistemplateuniqueistheObject-Orientedapproachthatitfollowswhichispresentedinsuchawaythatithelpsyouwritebetterstructuredcode,withoutmakingithardertointroducecustomizationsontheimplementation.Let’sproceedandanalyzeitssourcecode:

/*!

*jQuerylightweightpluginboilerplate

*Originalauthor:@ajpiano

*Furtherchanges,comments:@addyosmani

*LicensedundertheMITlicense

*/

;(function($,window,document,undefined){

varpluginName="defaultPluginName",

defaults={

propertyName:"value"

};

functionPlugin(element,options){

this.element=element;

this.options=$.extend({},defaults,options);

this._defaults=defaults;

this._name=pluginName;

this.init();

}

Plugin.prototype={

init:function(){/*Placeinitializationlogichere*/},

yourOtherFunction:function(options){/*somelogic*/}

};

//Areallylightweightpluginwrapperaroundtheconstructor,

//preventingagainstmultipleinstantiations

$.fn[pluginName]=function(options){

returnthis.each(function(){

if(!$.data(this,"plugin_"+pluginName)){

$.data(this,"plugin_"+pluginName,

newPlugin(this,options));

}

});

};

})(jQuery,window,document);

Thesemi-colonrightbeforetheIIFEistheretoavoiderrorsincaseofunfortunatescriptconcatenation(andpossiblyminification)withafilethatmightbemissinganendingsemi-colon.Rightbelow,theboilerplateusesthepluginNamevariableasaDRYwayofnamingourpluginandusingitsnameforanyothercase.Asanaddedbenefit,allthatwehavetodoifweneedtorenameourpluginischangethevalueofthisvariableandrenamethe.jsfileofourpluginaccordingly.

Followingthebestpracticesthatwesawearlier,avariableisusedtoholdthedefaultoptionsofthepluginand,aswecanseeafewlineslater,itismergedwiththeuser-providedoptionsusingthe$.extend()method.Keepinmindthat,ifwewanttoexposethedefaultoptions,allthatwehavetodoisdefineitaspartoftheplugin’snamespace:$.fn[pluginName].defaultOptions=defaults;

Theactualplugindefinitioncanbefoundneartheendofthisboilerplatecode.Followingthealreadydiscussedbestpractices,ititeratesovertheitemsofthecollectionusing$.fn.each()andreturnsitsresult,whichisequivalenttoreturningthis.Itthenensuresthatapluginstateinstanceexistsforeachitemofthecollectionbyusingthe$.data()methodandtheprefixedpluginnameasanassociationkey.

ThePluginconstructorfunctionisusedforthecreationoftheplugin’sstateobjectwhich,afterstoringtheDOMelementandthefinalpluginoptionsaspropertiesoftheobject,invokestheinit()methodofitsprototype.Theinit()methodisthesuggestedplacetodefineourinitializationcode,forexample,itcouldinstantiateanewMutationObserveraswedidearlierinthischapter.

AddingmethodstoyourpluginBydefault,everymethodthatisdefinedaspartoftheprototypeisonlyavailableforinternaluse.Ontheotherhand,wecaneasilyextendtheaboveimplementationtomakeamethodavailabletoallourusers,asshownbelow:

$.fn[pluginName]=function(options,extraParam){

returnthis.each(function(){

varinstance=$.data(this,"plugin_"+pluginName);

if(!instance){

instance=newPlugin(this,options);

$.data(this,"plugin_"+pluginName,instance);

}elseif(options==='yourOtherFunction'){

instance.yourOtherFunction(this,extraParam);

}

});

};

OneguidelinetofollowwhenworkingwiththisboilerplateistoextendyourpluginbyaddingextramethodstothePlugin‘sprototype.Additionally,trytokeepanymodificationstotheplugin’sdefinitionassmallaspossible,ideallysinglelinemethodinvocations.

Inordertomaketheimplementationmorescalable,withregardstohowthepluginmethodsareinvokedandifwewanttoaddanabstractapproachformethodsthatareintendedforinternalorprivateusebytheplugin,wecanintroducethefollowingchanges:

$.fn[pluginName]=function(options){

varrestArgs=Array.prototype.slice.call(arguments,1);

returnthis.each(function(){

varinstance=$.data(this,"plugin_"+pluginName);

if(!instance){

instance=newPlugin(this,options);

$.data(this,"plugin_"+pluginName,instance);

}elseif(typeofoptions==='string'&&//methodname

options[0]!=='_'&&//protectprivatemethods

typeofinstance[options]==='function'){

instance[options].apply(instance,restArgs);

}

});

};

Intheaboveimplementation,weusedthefirstargumenttoidentifythemethodthatneedstobeinvokedandtheninvokeditwiththerestarguments.Wealsoaddedachecktopreventtheinvocationofmethodsthatstartwithanunderscorewhich,accordingtocommonconventions,areintendedtobeforinternalorprivateuse.Asaresult,inordertoaddanextramethodtoyourplugin’spublicAPI,wejustneedtodeclareitinthePlugin.prototypethatwesawearlier.

NoteAnothergreatwaytoimplementyourpluginwhenyouarealreadyusingjQuery-UIinyourapplicationistousethe$.widget()methodwhichisalsoknownasjQuery-UI

WidgetFactory.Itsimplementationabstractsseveralpartsoftheboilerplatecodethatwesawinthischapterandhelpscreatecomplexandrobustplugins.Formoreinformation,youcanreadthedocumentationat:http://api.jqueryui.com/jQuery.widget/

ChoosinganameLastly,afterlearningthebestpracticesthatweneedtocreateajQueryplugin,let’ssaysomethingaboutthenamingconventionsandwheretopublishyournewandshinyplugin.

Asyouhaveprobablyalreadyseen,mostjQuerypluginsusethefollowingnamingconvention:jQuery-myPluginNamefortheirprojectsitesandrepositoriesandstoretheirimplementationsinafilenamedjquery.mypluginname.js.Aftersettlingonsomeprospectivenamesforyourplugin,takeamomentandsearchthewebtoverifythatthereisnooneelsewiththesameprojectname.ThejQuerydocumentationsuggestssearchingforpluginsonNPMandrefiningyourresultsbyusingthejquery-pluginkeyword.Thisisobviouslythebestwaytopublishyourpluginsothatitcanbeeasilyfoundbyothers.

NoteFormoreinformationaboutNPM,youcanvisit:https://www.npmjs.com/

AnotherpopularplaceforsearchingandhostingJavaScriptlibrariesisGitHub.Youcanfinditsrepositorysearchpageathttps://github.com/search?l=JavaScript,whereitfiltersthesearchresultstoincludeonlyJavaScriptprojectsandsearchesforexistingpluginsandalreadyusedprojectnames.SinceinourcasewearefocusingonjQueryplugins,youwillgetbetterresultsbysearchingforprojectnamesthatfollowtheaforementionednamingconvention,jQuery-myPluginName.

NoteUntilrecently,developerscouldsearchforexistingpluginsandregisteranewoneattheofficialjQueryPluginRegistry(http://plugins.jquery.com/).Unfortunately,ithasbeendiscontinuedandnowonlyallowssearchingforolderpluginswithnonewsubmissions.

SummaryInthischapterwelearnedhowjQuerycanbeextendedbyimplementingandusingplugins.WefirstsawanexampleofthesimplestwaythatajQueryplugincanbeimplementedandanalyzedthecharacteristicsthatmakeagreatplugin,andonewhichfollowstheprinciplesofthejQuerylibrary.

WewerethenintroducedtothemostcommondevelopmentpatternsinthedevelopercommunityforcreatingjQueryPlugins.Weanalyzedtheimplementationproblemsthateachofthemsolvesandtheusecasesthatareabettermatchforthem.

Aftercompletingthischapter,wearenowabletoabstractpartsofourapplicationsintoreusableandextensiblejQuerypluginsthatarestructuredusingthedevelopmentpatternthatbestmatcheseachusecase.

Inthenextchapter,wewillpresentseveraloptimizationtechniquesthatcanbeusedtoimprovetheperformanceofourjQueryapplications,especiallywhentheybecomelargeandcomplex.WewilldiscusssimplepracticessuchasusingCDNstoloadthird-partylibrariesandcontinuewithmoreadvancedsubjectssuchaslazyloadingthemodulesofanimplementation.

Chapter11.OptimizationPatternsThischapterpresentsseveraloptimizationtechniquesthatcanbeusedtoimprovetheperformanceofjQueryapplications,especiallywhentheybecomelargeandcomplex.

WewillstartwithsimplepracticeslikebundlingandminifyingourJavaScriptfilesanddiscussthebenefitsofusingCDNstoloadthird-partylibraries.WewillthenmoveontoanalyzesomesimplepatternsforwritingefficientJavaScriptcodeandlearnhowtowriteefficientCSSselectorsinordertoimprovethepage’srenderingspeedandDOMtraversalsusingjQuery.

WewillthenstudyjQuery-specificpracticessuchasthecachingofjQueryCompositeCollectionObjects,howtominimizeDOMmanipulations,andhaveareminderoftheDelegateObserverPatternasagoodexampleoftheFlyweightPattern.Lastly,wewillgetanintroductiontotheadvancedtechniqueofLazyLoadingandhaveademonstrationofhowtoloadthedifferentmodulesofanimplementationprogressively,basedonuseractions.

Bytheendofthischapter,wewillbeabletoapplythemostcommonoptimizationpatternstoourimplementationsandusethischapterasachecklistofbestpracticesandperformancetipsbeforemovingtheapplicationtoaproductionenvironment.

Inthischapter,weshall:

LearnthebenefitsofbundlingandminifyingourJavaScriptfilesLearnhowtoloadthird-partylibrariesthroughtheCDNserverLearnsomesimpleJavaScriptperformancetipsLearnhowtooptimizeourjQuerycodeIntroducetheFlyweightpatternandshowcasesomeexamplesofitLearnhowtolazyloadpartsofourapplicationwhenrequiredbyauseraction

PlacingscriptsneartheendofthepageThefirsttipformakingyourpage’sinitialrenderingfasteristogatheralltherequiredJavaScriptfilesandplacetheir<script>tagsneartheendofthepage,preferablyjustbeforetheclosing</body>tag.Thischangewillhaveagreatimpactonthetimeneededfortheinitialrenderingofthepage,especiallyforuserswithlowspeedconnectionssuchasmobileusers.Ifyouarealreadyusingthe$(document).ready()methodforallinitializationpurposesthatrelatetotheDOM,movingthe<script>tagsaroundshouldnotaffectthefunctionalityofyourimplementationatall.

Themainreasonforthisisthat,eventhoughbrowsersdownloadthepage’sHTMLandotherresources(CSS,images,andsoon)inparallel,whena<script>tagisencountered,thebrowserpauseseverythingelseuntilitisdownloadedandexecuted.Inordertoworkaroundthislimitationofthespecification,attributeslikedeferandasyncfromHTLM5havebeenintroducedaspartsofthe<script>tagspecificationbutunfortunatelyhaveonlystartedtobeadoptedbysomebrowsersrecently.Asaresult,thispracticeisstillwidelyusedtoobtaingoodpageloadingspeedsevenonolderbrowsers.

NoteFormoreinformationaboutthe<script>tagyoucanvisit:https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script

BundlingandminifyingresourcesThefirstplacetolookwhentryingtomakeapageloadfasterisforwaystoreducethenumberandtotalsizeofHTTPrequests.Thebenefitscomefromthefactthatthebrowserdownloadsthecontentinlargerchunksinsteadofspendingtimewaitingforalotofsmallround-tripstotheservertocomplete.Thisisespeciallybeneficialforuserswithlowspeedconnectionssuchasmobileusers.

Resourceconcatenationisasimpleconceptthatdoesnotneedanyintroduction.Thiscanbedonemanuallybutitispreferabletoautomatethistaskwithabundlingscriptorintroduceabuildstepforyourproject.Dependingonyourdevelopmentenvironment,therearedifferentbundlingsolutionstochoosefrom.Ifyouareusinggruntorgulpaspartofyourdevelopmentstack,youcanusesolutionslikegrunt-contrib-concat(https://github.com/gruntjs/grunt-contrib-concat)andgulp-concat(https://github.com/contra/gulp-concat)respectively.

MinifyingJavaScriptfilesisamorecomplexprocedurewhichincludesaseriesofcodetransformationsthatareappliedtothetargetsourcecode,rangingfromsomethingassimpleaswhitespaceremovaltomorecomplextaskslikevariablerenaming.PopularsolutionsforminifyingJavaScriptinclude:

YUICompressoravailableathttp://yui.github.io/yuicompressor/Google’sClosureCompileravailableathttps://developers.google.com/closure/compiler/UglifyJSavailableathttps://github.com/mishoo/UglifyJS2

Onceagain,varioussolutionsexistthatintegratetheabovelibrariesnicelywithyourpreferreddevelopmentenvironmentandmakeminificationasimpletask.Examplesofintegrationsforgruntandgulpincludegrunt-contrib-uglify(https://github.com/gruntjs/grunt-contrib-uglify)andgulp-uglify(https://github.com/terinjokes/gulp-uglify)respectively.

Asafinalword,keepinmindthatyourcodeshouldbeasreadableandaslogicallystructuredaspossible.BundlingandminifyingyourJavaScriptandCSSfilesismosteffectivelydoneasabuildstepofyourdevelopmentanddeploymentprocedures.

UsingIIFEparametersApartfromhelpingtoavoidpollutingtheglobalnamespace,usingIIFEstowrapyourimplementationcanalsobebeneficialforthesizeofyourminifiedJavaScriptfiles.Let’stakealookatthefollowingcodeinwhichthejQuery,thewindow,andthedocumentvariablesarepassedasinvocationparameterstothemodule’sIIFE.

(function($,window,document,undefined){

if(window.myModule===undefined){

window.myModule={};

}

myModule.init=function(){/*...*/};

$(document).ready(myModule.init);

})(jQuery,window,document);

Wesawasimilarpatterninthepreviouschapter,aspartofthesuggestedtemplateforcreatingjQueryplugins.Eventhoughthevariablealiasingdoesnotaffectthefunctionalityoftheimplementation,itallowsthecodeminifierstoapplyvariablerenaminginmoreplacesthanbefore,resultingincodelikethefollowing:

(function(b,a,c,d){

a.myModule===d&&(a.myModule={});

myModule.init=function(){/*...*/};

b(c).ready(myModule.init);

})(jQuery,window,document);

Asyoucanseeintheabovecode,alltheinvocationparametersoftheIIFEwererenamedbytheminifiertosingleletteridentifiers,whichincreasesthegainsoftheminificationespeciallyiftheoriginalidentifiersareusedinseveralplaces.

TipAsanaddedbenefit,aliasingalsoprotectsourmodulesfromthecasethattheoriginalvariablesgetaccidentallyassignedadifferentvalue.Forexample,whenIIFEparametersarenotused,anassignmentlike$={}orundefined=7fromwithinadifferentmodulewouldbreakalltheimplementation.

UsingCDNsInsteadofservingalloftheJavaScriptandCSSfilesofthethird-partylibrariesfromyourwebserver,youshouldconsiderusingaContentDeliveryNetwork(CDN).UsingaCDNtoservethestaticfilesofthelibrariesthatareusedbyyourwebsitecanmakeitloadfastersince:

CDNshavehighspeedconnectionsandseveralcachinglevels.CDNshavemanygeographicallydistributedserversthatcandelivertherequestedfilesfastersincetheyareclosertotheenduser.CDNshelpparallelizeresourcerequests,sincemostbrowserscanonlydownloaduptofourresourcesconcurrentlyfromanyspecificdomain.

Moreover,ifauserhasstaticresourcescachedfromapreviousvisittoanotherwebsitethatusesthesameCDN,heorshewillnothavetodownloadthemagain,reducingthetimethatyoursiteneedstoload.

BelowisalistwiththemostwidelyusedCDNsforJavaScriptlibrarieswhichyoucanuseinyourimplementations:

https://code.jquery.com/https://developers.google.com/speed/libraries/https://cdnjs.com/http://www.jsdelivr.com/

UsingJSDelivrAPIAnewcomertotheCDNworldisJSDelivr,whichisgainingpopularitybecauseofitsuniquefeatures.Beyondsimplyservingexistingstaticfiles,JSDelivrprovidesanAPI(https://github.com/jsdelivr/api)thatallowsustocreateandusecustombundleswiththeresourcesthatweneedtoload,helpingustominimizetheHTTPrequeststhatoursiteneeds.Moreover,itsAPIallowsustotargetlibrarieswithdifferentlevelsofspecificity(major,minor,orbugfixreleases)andevenallowsustoloadonlyspecificpartsofalibrary.

Asanexample,takealookatthefollowingURL,whichallowsustoloadthemostrecentbugfixreleasesofjQueryv1.11.xwithasinglerequestaswellassomepartsofjQuery-UIv1.10.xandBootstrapv3.3.x:http://cdn.jsdelivr.net/g/jquery@1.11,jquery.ui@1.10(jquery.ui.core.min.js+jquery.ui.widget.min.js+jquery.ui.mouse.min.js+jquery.ui.sortable.min.js),bootstrap@3.3

OptimizingcommonJavaScriptcodeInthissection,wewillanalyzesomeperformancetipsthatarenotjQuery-specificandcanbeappliedtomostJavaScriptimplementations.

WritingbetterforloopsWheniteratingovertheitemsofanarrayoranarray-likecollectionwithaforloop,asimplewaytoimprovetheperformanceoftheiterationistoavoidaccessingthelengthpropertyoneveryloop.Thiscaneasilybedonebystoringtheiterationlengthtoaseparatevariable,declaredjustbeforethelooporevenalongwithit,asshownbelow:

for(vari=0,len=myArray.length;i<len;i++){

varitem=myArray[i];

/*...*/

}

Moreover,ifweneedtoiterateovertheitemsofanarraythatdoesnotcontainfalsyvalues,wecanuseanevenbetterpatternwhichiscommonlyappliedforiteratingoverarraysthatcontainobjects:

varobjects=[{},{},{}];

for(vari=0,item;item=objects[i];i++){

console.log(item);

}

Inthiscase,insteadofrelyingonthelengthpropertyofthearray,weexploitthefactthataccesstoanout-of-boundspositionofthearrayreturnsundefinedwhichisfalsyandstopstheiteration.AnothersamplecasethatthistrickcanbeusediniswheniteratingoverNodeListsorjQueryCompositeCollectionObjectsasshownbelow:

varanchors=$('a');//ordocument.getElementsByTagName('a');

for(vari=0,anchor;anchor=anchors[i];i++){

console.log(anchor.href);

}

NoteFormoreinformationaboutthetruthyandfalsyJavaScriptvalues,visit:https://developer.mozilla.org/en-US/docs/Glossary/Truthyandhttps://developer.mozilla.org/en-US/docs/Glossary/Falsy

WritingperformantCSSselectorsEventhoughSizzle(jQuery’sselectorengine)hidesthecomplexityofDOMtraversalsbasedoncomplexCSSselectors,weshouldhaveanideaofhowourselectorsareperforming.UnderstandinghowCSSselectorsarematchedagainsttheelementsoftheDOMhelpsuswritemoreefficientselectorswhichperformbetterwhenusedwithjQuery.

ThekeycharacteristicofefficientCSSselectorsisspecificity.Accordingtothis,IDandClassselectorsarealwaysmoreefficientthanselectorswithmanyresultslikedivand*.WhenwritingcomplexCSSselectors,keepinmindthattheyareevaluatedfromtherighttotheleftandthataselectorgetsrejectedafterrecursivelytestingitagainsteveryparentelementuntiltherootoftheDOM.

Asaresult,trytobeasspecificaspossiblewiththerightmostselectorinordertocutdownthematchedelementsasquicklyaspossibleduringtheexecutionoftheselector.

//initiallymatchesalltheanchorsofthepage

//andthenremovesthosethatarenotchildrenofthecontainer

$('.containera');

//performsbetter,sinceitmatchesfewerelements

//inthefirststepoftheselector'sevaluation

$('.container.mySpecialLinks');

TheotherperformancetipisusingtheChildSelector(“parent>child”)whereverapplicable,inanefforttoeliminatetherecursionoverallthehierarchyoftheDOMtree.Agreatexamplewherethiscanbeappliedisincaseswherethetargetelementscanbefoundataspecificdescendantlevelofacommonancestorelement:

//initiallymatchesallthediv'softhepage,whichisbad

$('.containerdiv');

//alotfasterthanthepreviousone,

//sinceitavoidstherecursiveclasschecks

//untilreachingtherootoftheDOMtree

$('.container>div');

//bestofall,butcan'tbeusedalways

$('.container>.specialDivs');

TipThesametipscanalsobeappliedtoCSSselectorsthatareusedforstylingpages.EventhoughbrowsershavebeentryingtooptimizeanygivenCSSselector,thetipsdescribedabovecangreatlyreducethetimethatisrequiredtorenderawebpage.

NoteFormoreinformationonjQueryCSSselectorperformance,youcanvisit:http://learn.jquery.com/performance/optimize-selectors/

WritingefficientjQuerycodeLet’snowproceedandanalyzethemostimportantjQuery-specificperformancetips.Formoreinformationaboutthemostup-to-dateperformancetipsonjQuery,keepaneyeontherelevantpageforjQuery’sLearningCenter:http://learn.jquery.com/performance

MinimizingDOMtraversalsSincejQuerymadeDOMtraversalssosimple,manywebdevelopersoverusedthe$()functioneverywhere,eveninsubsequentlinesofcode,makingtheirimplementationsslowerbyexecutingunnecessarycode.OneofthemainreasonsthatthecomplexityoftheoperationissooftenoverlookedistheelegantandminimalisticsyntaxthatjQueryuses.DespitethefactthatJavaScriptbrowserenginesbecamemanytimesfasterinthelastfewyears,withperformancecomparabletomanycompiledlanguages,theDOMAPIisstilloneoftheirslowestcomponentsand,asaresult,developershavetominimizetheirinteractionswithit.

CachingjQueryobjectsStoringtheresultofthe$()functiontoalocalvariableandsubsequentlyusingittooperateontheretrievedelementsisthesimplestwayofeliminatingunnecessaryexecutionsofthesameDOMtraversals.

var$element=$('.boxHeader');

if($element.css('position')==='static'){

$element.css({position:'relative'});

}

$element.height('40px');

$element.wrapInner('<b>');

Inthepreviouschapters,weevensuggestedstoringCompositeCollectionObjectsofimportantpageelementsaspropertiesofourmodulesandreusingthemeverywhereinourapplication:

dashboard.$container=null;

dashboard.init=function(){

dashboard.$container=$('.dashboardContainer');

};

TipCachingretrievedelementsonmodulesisaverygoodpracticewhentheelementsarenotgoingtoberemovedfromthepage.Keepinmindthat,whendealingwithelementswithshorterlifespans,inordertoavoidmemoryleaks,youhavetoeitherensurethatyouclearalltheirreferenceswhentheyareremovedfromthepageorhaveafreshreferenceretrievedwhenrequiredandcacheitonlyinsideyourfunctions.

ScopingelementtraversalsInsteadofwritingcomplexCSSselectorsforyourtraversalslike:

$('.dashboardContainer.dashboardCategories');

YoucaninsteadhavethesameresultinamoreefficientwaybyusinganalreadyretriedancestorelementtoscopetheDOMtraversal.Thisway,youarenotonlyusingsimplerCSSselectorsthatarefastertomatchagainstpageelements,butyouarealsoreducingthenumberofelementsthathavetobechecked.Moreover,theresultingimplementationshavelesscoderepetitions(areDRYer)andtheCSSselectorsusedaresimpleandasa

resultmorereadable.

var$container=$('.dashboardContainer');

$container.find('.dashboardCategories');

Additionally,thispracticeworksevenbetterwithmodule-widecachedelementslikethoseweusedinthepreviouschapters:

$boxContainer=dashboard.$container.find('.boxContainer');

ChainingjQuerymethodsOneofthecharacteristicsofalljQueryAPIsisthattheyareFluentinterfaceimplementationsthatenableustochainseveralmethodinvocationsonasingleCompositeCollectionObject.

$('.boxContent').html('')

.append('<ahref="#">')

.height('40px')

.wrapInner('<b>');

Aswediscussedinpreviouschapters,chainingallowsustoreducethenumberofusedvariablesandleadstomorereadableimplementationswithfewercoderepetitions.

Don’toverdoitKeepinmindthatjQueryalsoprovidesthe$.fn.end()method(http://api.jquery.com/end/)asawayofmovingbackfromachainedtraversal.

$('.box')

.filter(':even')

.find('.boxHeader')

.css('background-color','#0F0')

.end()

.end()//undothefilterandfindtraversals

.filter(':odd')//appliedontheinitial.boxresults

.find('.boxHeader')

.css('background-color','#F00');

Eventhoughthisisahandymethodinmanycases,youshouldavoidoverusingitsinceitcandamagethereadabilityandperformanceofyourcode.Inmanycases,usingcachedelementcollectionsinsteadof$.fn.end()resultsinfasterandmorereadableimplementations.

ImprovingDOMmanipulationsAswesaidearlier,theextensiveuseoftheDOMAPIisoneofthemostcommonthingsthatmakesanapplicationslower,especiallywhenusedtomanipulatethestateoftheDOMtree.Inthissection,wewillshowcasesometipstoimproveperformancewhenmanipulatingtheDOMtree.

CreatingDOMelementsThemostefficientwaytocreateDOMelementsistoconstructaHTMLstringandappendittotheDOMtreeusingthe$.fn.html()method.Additionally,sincethisistoolimitinginsomeusecases,youcanalsousethe$.fn.append()and$.fn.prepend()methods,whichareslightlyslowerbutmaybeabettermatchforyourimplementation.Ideally,ifmultipleelementsneedtobecreated,youshouldtrytominimizetheinvocationofthesemethodsbycreatingaHTMLstringthatdefinesalltheelementsandtheninsertingitintotheDOMtree,asshownbelow:

varfinalHtml='';

for(vari=0,len=questions.length;i<len;i++){

varquestion=questions[i];

finalHtml+='<div><label><span>'+question.title+':</span>'+

'<inputtype="checkbox"name="'+question.name+'"/>'+

'</label></div>';

}

$('form').html(finalHtml);

Anotherwaytoachievethesameresult,isbyusinganarraytostoretheHTMLforeachintermediateelementandthenjointhemrightbeforetheinsertiontotheDOMtree:

varparts=[];

for(vari=0,len=questions.length;i<len;i++){

varquestion=questions[i];

parts.push('<div><label><span>'+question.title+':</span>'+

'<inputtype="checkbox"name="'+question.name+'"/>'+

'</label></div>');

}

$('form').html(parts.join(''));

NoteThisisacommonlyusedpatternsince,untilrecently,itperformedbetterthanconcatenatingtheintermediateresultswith“+=”.

StylingandanimatingWheneverpossible,useCSSclassesforyourstylingmanipulationsbyutilizingthe$.fn.addClass()and$.fn.removeClass()methodsinsteadofmanuallymanipulatingthestyleoftheelementswiththe$.fn.css()method.That’sespeciallyusefulwhenyouneedtostylealargenumberofelementssincethisisthemainpurposeofCSSclassesandbrowsershavealreadyspentyearsoptimizingit.

Tip

Asanextraoptimizationsteptominimizethenumberofmanipulatedelements,youcanapplyCSSclassesonasinglecommonancestorelementanduseadescendantCSSselectortoapplyyourstyling,asdemonstratedhere:https://developer.mozilla.org/en-US/docs/Web/CSS/Descendant_selectors

Whenyoustillneedtousethe$.fn.css()method,forexample,whenyourimplementationneedstobeimperative,usetheinvocationoverloadthatacceptsobjectparameters:http://api.jquery.com/css/#css-properties.Inthisway,therequiredmethodinvocationsareminimizedwhenapplyingmultiplestylesonelementsandyourcodeisbetterorganized.

Moreover,avoidmixingmethodsthatmanipulatetheDOMwithmethodsthatreadfromtheDOMsincethiswillforceareflowofthepagesothatthebrowsercancalculatethenewpositionsofthepageelements.

Insteadofdoingsomethinglikethis:

$('h1').css('padding-left','2%');

$('h1').css('padding-right','2%');

$('h1').append('<b>!!</b>');

varh1OuterWidth=$('h1').outerWidth();

$('h1').css('margin-top','5%');

$('body').prepend('<b>--!!--</b>');

varh1Offset=$('h1').offset();

Prefergroupingthenon-conflictingmanipulationstogetherlikethis:

$('h1').css({

'padding-left':'2%',

'padding-right':'2%',

'margin-top':'5%'

}).append('<b>!!</b>');

$('body').prepend('<b>--!!--</b>');

varh1OuterWidth=$('h1').outerWidth();

varh1Offset=$('h1').offset();

Thebrowsercanthusskipsomere-renderingsofthepage,resultinginfewerpausesoftheexecutionofyourcode.

NoteFormoreinformationaboutreflows,visitthefollowingpage:https://developers.google.com/speed/articles/reflow

Lastly,notethatalljQuery-generatedanimationsinv1.xandv2.xareimplementedusingthesetTimeout()function.Thisisgoingtochangeinv3.xofjQuerywhichplanstousetherequestAnimationFrame()function,whichisabettermatchforcreatingimperativeanimations.Untilthen,youcanusethejQuery-requestAnimationFrameplugin(https://github.com/gnarf/jquery-requestAnimationFrame)whichmonkey-patchesjQuerytousetherequestAnimationFrame()functionforitsanimationswhenitisavailable.

ManipulatingdetachedelementsAnotherwaytoavoidunnecessaryrepaintsofthepagewhilemanipulatingDOMelementsistodetachtheelementfromthepageandre-attachitaftercompletingyourmanipulations.Workingwithadetachedin-memoryelementismuchfasteranddoesnotcausereflowsonthepage.

Inordertoachievethat,weusethe$.fn.detach()methodwhich,incontrastto$.fn.remove(),preservesalleventhandlersandjQuerydataonthedetachedelement.

var$h1=$('#pageHeader');

var$h1Cont=$h1.parent();

$h1.detach();

$h1.css({

'padding-left':'2%',

'padding-right':'2%',

'margin-top':'5%'

}).append('<b>!!</b>');

$h1Cont.append($h1);

Additionally,tobeabletoplacethemanipulatedelementbackintoitsoriginalposition,wecancreateandinsertahiddenplaceholderelementintotheDOM.Thisemptyandhiddenelementdoesnotaffecttherenderingofthepageandisremovedaftertheoriginalitemisplacedbackintoitsoriginalposition.

var$h1PlaceHolder=$('<divstyle="display:none;"></div>');

var$h1=$('#pageHeader');

$h1PlaceHolder.insertAfter($h1);

$h1.detach();

$h1.css({

'padding-left':'2%',

'padding-right':'2%',

'margin-top':'5%'

}).append('<b>!!</b>');

$h1.insertAfter($h1PlaceHolder);

$h1PlaceHolder.remove();

$h1PlaceHolder=null;

NoteFormoreinformationaboutthe$.fn.detach()method,youcanreadthedocumentationat:http://api.jquery.com/detach/

IntroducingtheFlyweightPatternAccordingtoComputerScience,aFlyweightisanobjectthatisusedasameansofreducingthememoryconsumptionofanimplementationbyprovidingfunctionalityand/ordatathataresharedwithotherobjectinstances.ThePrototypesofJavaScriptconstructorfunctionscanbecharacterizedasFlyweightssinceeveryobjectinstancecanuseallofthe

methodsandpropertiesthataredefinedinitsprototypeuntilitoverwritesthem.Ontheotherhand,classicalFlyweightsareseparateobjectsfromtheobjectfamilythattheyareusedwithandoftenholdtheshareddataandfunctionalityinspecialdatastructures.

UsingDelegateObserversAgreatexampleofFlyweightsinjQueryapplicationsisDelegateObserverswhich,aswesawintheDashboardexampleinChapter2,TheObserverPattern,cangreatlyreducethememorydemandsofanimplementationbyworkingasacentralizedeventhandlerforalargegroupofelements.Inthisway,wecanavoidthecostofsettingupseparateobserversandeventhandlersforeveryelementandusethebrowser’seventbubblingmechanismtoobserveforthemonasinglecommonancestorelementandfiltertheirorigin.

$boxContainer.on('click','.boxCloseButton',function(){

var$button=$(this);

dashboard.informationBox.close($button);

});

NoteTheactualFlyweightobjectistheeventhandleralongwiththecallbackthatisattachedtotheancestorelement.

Using$.noop()ThejQuerylibraryoffersthe$.noop()methodwhichisactuallyanemptyfunctionthatcanbesharedamongimplementations.Usingemptyfunctionsasdefaultcallbackvaluessimplifiesandimprovesthereadabilityofanimplementationbyreducingthenumberofifstatements.ThisishandyforjQuerypluginsthatalreadyencapsulatecomplexfunctionality.

functiondoLater(callbackFn){

setTimeout(function(){

if(callbackFn){

callbackFn();

}

},500);

}

//with$.noop()

functiondoLater(callbackFn){

callbackFn=callbackFn||$.noop();

setTimeout(function(){

callbackFn();

},500);

}

Insuchsituations,wheretheimplementationrequirementsorthepersonaltasteofthedeveloperhasledtousingemptyfunctions,the$.noop()methodisusefulasawaytolowermemoryconsumptionbysharingasingleemptyfunctioninstanceamongallthedifferentpartsofanimplementation.Anaddedbenefitofusingthe$.noop()methodforeverypartofanimplementationisthatwecanalsocheckwhetherapassedfunctionreferenceistheemptyfunctionbysimplycheckingcallbackFn===$.noop().

NoteFormoreinformation,youcanfindthedocumentationat:http://api.jquery.com/jQuery.noop/

Usingthe$.singlepluginAnothersimpleexampleoftheFlyweightpatterninjQueryapplicationsisthejQuery.singlepluginasdescribedbyJamesPadolseyinhisarticle,76bytesforfasterjQuery,whichtriestoeliminatethecreationofnewjQueryobjectswheneverweneedtoapplyjQuerymethodsonasinglepageelement.TheimplementationisquitesmallandcreatesasinglejQuerycompositecollectionobjectthatisreturnedoneveryinvocationofthejQuery.single()method,containingthepageelementthatwasusedasanargument.

jQuery.single=(function(){

varcollection=jQuery([1]);

//Fillwith1item,tomakesurelength===1

returnfunction(element){

collection[0]=element;//Givecollectiontheelement:

returncollection;//Returnthecollection:

};

}());

ThejQuery.singlepluginisusefulwhenusedinobserverslike$.fn.on()anditerationswithmethodslike$.each().

$boxContainer.on('click','.boxCloseButton',function(){

//var$button=$(this);

var$button=$.single(this);

//thisisnotcreatinganynewobject

dashboard.informationBox.close($button);

});

ThebenefitsofusingthejQuery.singleplugincomefromthefactthatwearecreatingfewerobjectsand,asaresult,thebrowser’sGarbageCollectorwillalsohavelessworktodowhenfreeingupthememoryofshortlivedobjects.

Asasidenote,keepinmindthesideeffectsofhavingasinglejQueryobjectreturnedbyeveryinvocationofthe$.single()methodandthefactthatthelastinvocationargumentwillbestoreduntilthenextinvocationofthemethod:

varbuttons=document.getElementsByTagName('button');

var$btn0=$.single(buttons[0]);

var$btn1=$.single(buttons[1]);

$btn0===$btn1//thisistrue

Additionally,incasethatyouusesomethinglike$btn1.remove()thentheelementwillnotbefreeduntilthenextinvocationofthe$.single()methodwhichwillremoveitfromtheplugin’sinternalcollectionobject.

AnothersimilarbutmoreextensivepluginisthejQuery.flypluginwhichcanbeinvokedwitharraysandjQueryobjectsasparameters.

NoteFormoreinformationaboutjQuery.singleandjQuery.fly,youcanvisitthefollowingURLs:http://james.padolsey.com/javascript/76-bytes-for-faster-jquery/andhttps://github.com/matjaz/jquery.fly.

Ontheotherhand,thejQueryimplementationthathandlestheinvocationofthe$()methodwithasinglepageelementisnotcomplexatallandonlycreatesasinglesimpleobject.

jQuery=function(selector,context){

returnnewjQuery.fn.init(selector,context);

};

/*...*/init=jQuery.fn.init=function(selector,context,root){

/*...else*/

if(selector.nodeType){

this.context=this[0]=selector;

this.length=1;

returnthis;

}/*...*/

};

Moreover,theJavaScriptenginesofmodernbrowsershavealreadybecomequiteefficientwhendealingwithshort-livedobjectssincesuchobjectsarecommonlypassedaroundanapplicationasmethodinvocationparameters.

LazyLoadingModulesFinally,wewillgetanintroductiontotheadvancedtechniqueofLazyLoadingModules.Thekeyconceptofthispracticeisthat,duringthepageload,thebrowserwillonlydownloadandexecutethosemodulesthatarerequiredfortheinitialrenderingofthepagewhiletherestoftheapplicationmodulesarerequestedafterthepageisfullyloadedandisrequiredtorespondtoauseraction.RequireJS(http://requirejs.org/)isapopularJavaScriptlibrarythatisusedasamoduleloaderbut,forsimplecases,wecanachievethesameresultwithjQuery.

Asanexampleofthis,wewilluseittolazyloadtheinformationBoxmoduleoftheDashboardexamplethatwesawinpreviouschapters,afterthefirstclickoftheuserontheDashboard’s<button>.WewillabstracttheimplementationthatisresponsiblefordownloadingandexecutingJavaScriptfilesintoagenericandreusablemodulenamedmoduleUtils:

(function(){

'usestrict';

dashboard.moduleUtils=dashboard.moduleUtils||{};

dashboard.moduleUtils.getModule=function(namespaceString){

varparts=namespaceString.split('.');

varresult=parts.reduce(function(crnt,next){

returncrnt&&crnt[next];

},window);

returnresult;

};

varongoingModuleRequests={};

dashboard.moduleUtils.ensureLoaded=function(namespaceString){

varexistingNamespace=this.getModule(namespaceString);

if(existingNamespace){

return$.Deferred().resolve(existingNamespace);

}

if(ongoingModuleRequests[namespaceString]){

returnongoingModuleRequests[namespaceString];

}

varmodulePromise=$.getScript(namespaceString.toLowerCase()+

'.js')

.always(function(){

ongoingModuleRequests[namespaceString]=null;

}).then(function(){

returndashboard.moduleUtils.getModule(namespaceString);

});

ongoingModuleRequests[namespaceString]=modulePromise;

returnmodulePromise;

};

})();

ThegetModule()methodacceptsthemodule’snamespaceasastringparameterandreturnseithertheModule’sSingletonObjectitselforafalsyvalueifthemoduleisnotalreadyloaded.ThisisdonewiththeArray.reduce()methodwhichisusedtoiterateoverthedifferentpartsofthenamespacestring,usingthedot(.)asadelimiterandevaluatingeachpartonthepreviousobjectcontext,startingwithwindow.

NoteFormoreinformationabouttheArray.reduce()method,youcanvisit:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce

ensureLoaded()istheprimarymethodofthemoduleUtilsmoduleandisresponsibleforretrievingandexecutingmodulesthatarenotalreadyloaded.ItfirstusesthegetModule()methodtocheckwhethertherequestedmodulehasalreadybeenloadedand,ifso,returnsitsnamespaceobjectasaResolvedPromise.

Thenextstep,ifamodulehasnotyetbeenloaded,istochecktheongoingModuleRequestsobjecttoverifywhethertherequestedmoduleisnotalreadybeingdownloaded.Inordertodothat,theongoingModuleRequestsobjectusesthemodule’snamespacestringasapropertyandstoresthePromisesoftheAJAXrequeststhatareusedtoretrievethe.jsfilesfromtheserver.IfaPromiseobjectisavailablethenwecaninferthattheAJAXrequestisstillongoingand,insteadofstartinganewone,wereturntheexistingPromise.

Finally,whennoneoftheabovereturnsaresult,weusethelowercasemodulefilenamingconventionthatwediscussedinpreviouschaptersandusejQuery’s$.getScript()methodtoinitiateanAJAXrequesttoretrievetherequestedmodulefile.ThePromisecreatedfortheAJAXrequestisassignedastotheappropriatepropertyoftheongoingModuleRequestsobjectandisthenreturnedtothecallerofthemethod.When,atalaterpointintime,thePromiseisFulfilled,were-evaluatethemoduleandreturnitasthefinalresultofthereturnedPromise.Moreover,regardlessoftheresultoftheAJAXrequest,thePromiseisalsoremovedfromtheongoingModuleRequestsobjectinordertokeeptheimplementationreusableincaseofanetworkfailureandalsofreeupthememorythatwasallocatedfortherequest.

NoteKeepinmindthatthe$.getScript()methodmightnotworkinsomebrowserswhenthepageisloadedthroughthefilesystem,butdoesworkasintendedwhenservedusingawebserverlikeApache,IISornginx.Formoreinformationabout$.getScript(),youcanvisit:http://api.jquery.com/jQuery.getScript/

TheonlychangethatweintroducedtotheexistingimplementationoftheinformationBoxmoduleforthisdemonstrationwastomakeitself-initializableinanattempttoreducethecomplexityoftheensureLoaded()method.

(function(){

'usestrict';

dashboard.informationBox=dashboard.informationBox||{};

var$boxContainer=null;

dashboard.informationBox.init=function(){/*…*/};

$(document).ready(dashboard.informationBox.init);

/*...*/

})();

Finally,wealsohadtochangetheimplementationofthecategoriesmodulesothatitwouldusetheensureLoaded()methodbeforeusingtheinformationBoxmodule.Asyoucanseebelow,wehadtorefactorthecodehandlingtheclickeventonthedashboard’s<button>sincetheensureLoaded()methodreturnsaPromiseasaresult:

//indashboard.categories.init

dashboard.$container.find('.dashboardCategories').on('click','button',

function(){

var$button=$(this);

varitemName=$button.text();

varp=dashboard.moduleUtils.ensureLoaded('dashboard.informationBox');

p.then(function(){

dashboard.informationBox.openNew(itemName);

});

});

SummaryInthischapter,welearnedseveraloptimizationtechniquesthatcanbeusedtoimprovetheperformanceofjQueryapplications,especiallywhentheybecomelargeandcomplex.

WestartedwithsimplepracticeslikebundlingandminifyingourJavaScriptfilesanddiscussedthebenefitsofusingCDNstoloadthird-partylibraries.WethenwentontoanalyzesomesimplepatternstowritingefficientJavaScriptcodeandlearnedhowtowriteefficientCSSselectorstoimprovethepage’srenderingspeedandDOMtraversalsusingjQuery.

WecontinuedwithjQuery-specificpracticessuchascachingofjQueryCompositeCollectionObjects,howtominimizeDOMmanipulations,andhadareminderoftheDelegateObserverpattern,asagoodexampleoftheFlyweightPattern.Lastly,wegotanintroductiontotheadvancedtechniqueofLazyLoadingandsawademonstrationofhowtoloadthevariousmodulesofanimplementationprogressively,basedonuseractions.

Aftercompletingthischapter,wearenowabletoapplythemostcommonoptimizationpatternstoourimplementationsandusethischapterasachecklistofbestpracticesandperformancetipsbeforemovinganapplicationtoaproductionenvironment.

IndexA

$.ajax()method/AcceptingconfigurationparametersaddEventListener()methods

URL/IntroducingtheObserverPatternapplications

developing,withCompositePattern/UsingtheCompositePatterntodevelopapplicationsUnderscore.jstemplates,using/UsingUnderscore.jstemplatesinourapplicationsHandlebars.jstemplates,using/UsingHandlebars.jsinourapplications

Array.reduce()methodreference/LazyLoadingModules

attachEvent()methodURL/IntroducingtheObserverPattern

AustralianNationalUniversity(ANU)/Writingmethodsthatacceptcallbacks

BBabeltranspiler

URL/IntroducingES6Modulesbroker/IntroducingthePublish/SubscribePatternBuilderPattern

about/IntroducingtheBuilderPatternadopting,byjQuery/HowitisadoptedbyjQuery’sAPIusing,byjQueryinternally/HowitisusedbyjQueryinternallyusing,inapplications/Howtouseitinourapplications

CCallbackHell

URL/AvoidingtheCallbackHellanti-patterncallbacks

about/Programmingwithcallbacksprogrammingwith/Programmingwithcallbackssimplecallbacks,usinginJavaScript/UsingsimplecallbacksinJavaScriptsetting,asobjectproperties/Settingcallbacksasobjectpropertiesusing,injQueryapplications/UsingcallbacksinjQueryapplicationsmethods,writing/Writingmethodsthatacceptcallbacks

callbacks,orchestratingabout/Orchestratingcallbacksqueuing,inorderexecution/QueuinginorderexecutionCallbackHellanti-pattern,avoiding/AvoidingtheCallbackHellanti-patternrunningconcurrently/Runningconcurrently

categoriesmodule/ThecategoriesmoduleCDNs

about/UsingCDNsusing/UsingCDNsJSDelivrAPI,using/UsingJSDelivrAPI

closureabout/IntroducingtheObserverPatternURL/IntroducingtheObserverPattern

ClosureCompilerreference/Bundlingandminifyingresources

closuresURL/TheIIFEbuildingblock

commonJavaScriptcodeoptimizing/OptimizingcommonJavaScriptcodeforloops,writingfor/Writingbetterforloops

CompositePatternabout/TheCompositePatternusing,byjQuery/HowtheCompositePatternisusedbyjQuerycomparing,withplainDOMAPIbenefits/ComparingthebenefitsovertheplainDOMAPIused,fordevelopingapplications/UsingtheCompositePatterntodevelopapplicationssampleusecase/AsampleusecaseCollectionImplementation/TheCompositeCollectionImplementationexampleexecution/Anexampleexecutionalternativeimplementations/Alternativeimplementationspairing,withIteratorPattern/HowitpairswiththeCompositePattern

configurationparameters

accepting/AcceptingconfigurationparametersContentDeliveryNetwork(CDN)/UsingCDNscountermodule/ThecountermoduleCSSSelectors/ThejQueryDOMTraversalAPIcustomeventnamespacing

using/UsingcustomeventnamespacingURL/Usingcustomeventnamespacing

customeventsinjQuery/CustomeventsinjQueryused,forimplementingPublish/SubscribePattern/ImplementingaPub/Subschemeusingcustomevents

DDashboardapplication

reusableplugins,using/UsingourplugininourDashboardapplicationdashboardexample

Publish/SubscribePattern,using/UsingPub/Subonthedashboardexampledashboardmodule/Themaindashboardmoduledeferredobserver/Thecategoriesmoduledeferredobservers/ThecountermoduleDelegatedEventObserverPattern

about/IntroducingtheDelegatedEventObserverPatternused,forsimplifyingcode/Howitsimplifiesourcodememoryusagebenefits,comparing/Comparethememoryusagebenefits

DelegateObserversusing/UsingDelegateObservers

descendantCSSselectorreference/Stylingandanimating

DocumentFragmentabout/HowitisadoptedbyjQuery’sAPI

DocumentFragmentsreference/HowitisadoptedbyjQuery’sAPI

DocumentObjectMode(DOM)URL/jQueryandDOMscriptingmanipulating,withjQuery/ManipulatingtheDOMusingjQuery

DOMAPI/ThejQueryDOMTraversalAPIDOMLevel2Eventspecification

URL/HowitiscomparedwitheventattributesDOMmanipulations,improving

about/ImprovingDOMmanipulationsDOMelements,creating/CreatingDOMelementsanimating/Stylingandanimatingstyling/Stylingandanimatingdetachedelements,manipulating/ManipulatingdetachedelementsFlyweightPattern/IntroducingtheFlyweightPattern

DOMscriptingandjQuery/jQueryandDOMscripting

DOMtraversals,minimizingabout/MinimizingDOMtraversalsjQueryobjects,caching/CachingjQueryobjectselementtraversals,scoping/ScopingelementtraversalsjQuerymethods,chaining/ChainingjQuerymethods

E$.extend()helpermethod

URL/AcceptingconfigurationparametersefficientjQuerycode

writing/WritingefficientjQuerycodeDOMtraversals,minimizing/MinimizingDOMtraversalsoveruse,avoiding/Don’toverdoitDOMmanipulations,improving/ImprovingDOMmanipulationsDelegateObservers,using/UsingDelegateObservers$.noop(),using/Using$.noop()$.singleplugin,using/Usingthe$.singleplugin

EncapsulationURL/Encapsulatinginternalpartsofanimplementation

ES5StrictModeusing/UsingES5StrictMode

ES6modulesabout/IntroducingES6ModulesURL/IntroducingES6Modules

eventattributesURL/Howitiscomparedwitheventattributes

eventlistenersremoving,URL/Avoidmemoryleaks

eventobjectURL/Extendingtheimplementation

F$.fn.addClass()method/WorkingonCompositeCollectionObjects$.fn.closest()method

URL/Demonstrateasampleusecase$.fn.data()method/ImplementingastatefuljQueryPlugin$.fn.end()method

reference/Don’toverdoit$.fn.ready()method/Thedocument-readyobserver

URL/Thedocument-readyobserverFacadePattern

about/IntroducingtheFacadePatternbenefits/Thebenefitsofthispatternadopting,byjQuery/HowitisadoptedbyjQuery

Facadesusing,inapplications/UsingFacadesinourapplications

Factoriesusing,inapplications/UsingFactoriesinourapplications

FactoryPatternabout/IntroducingtheFactoryPatternkeyconcept/IntroducingtheFactoryPatternadopting,byjQuery/HowitisadoptedbyjQuery

falsyJavaScriptvaluereference/Writingbetterforloops

FlyweightPatternabout/IntroducingtheFlyweightPattern

functionData()URL/ThejQueryonmethod

G$.getScript()method

URL/RetrievingHTMLtemplatesasynchronouslyreference/LazyLoadingModules

genericiteratorfunction/HowtheIteratorPatternisusedbyjQuerygettermethod

implementing/ImplementinggetterandsettermethodsgetValuesmethod/TheCompositeCollectionImplementationglobalnamespace/AvoidingglobalvariableswithNamespaces,TheRevealingModulePatternGoogle’sJavaScriptStyleGuide

URL/Thewideacceptancegrunt-contrib-concatproject

URL/UsingModulesinjQueryapplications

HHandlebars.js

about/IntroducingHandlebars.jsURL/IntroducingHandlebars.js,UsingHandlebars.jsinourapplications,Pre-compilingtemplatesusing,inapplications/UsingHandlebars.jsinourapplicationstemplatepre-compilation,URL/Pre-compilingtemplates

HTMLtemplatesseparating,fromJavaScriptcode/SeparatingHTMLtemplatesfromJavaScriptcode,SeparatingHTMLtemplatesfromJavaScriptcoderetrievingasynchronously/RetrievingHTMLtemplatesasynchronouslyadopting,inexistingimplementation/Adoptingitinanexistingimplementationmoderation/Moderationisbestinallthings

IIIFE

used,forwrappingjQueryPlugin/WrappingwithanIIFEabout/WrappingwithanIIFE

IIFE-containedmodulevariant/TheIIFE-containedModulevariantIIFEparameters

using/UsingIIFEparametersImmediatelyInvokedFunctionExpression(IIFE)/Thedocument-readyobserver

about/TheIIFEbuildingblockURL/TheIIFEbuildingblock

incrementmethod/TheCompositeCollectionImplementationIndexedDB

URL/RunningconcurrentlyinformationBoxmodule/TheinformationBoxmoduleIteratorPattern

about/TheIteratorPatternusing,byjQuery/HowtheIteratorPatternisusedbyjQuerypairing,withCompositePattern/HowitpairswiththeCompositePatternusing/Wherecanitbeused

JJavaScript

Prototype-basedprogrammingmodel,URL/TheCompositeCollectionImplementation

JavaScriptcodeHTMLtemplates,separating/SeparatingHTMLtemplatesfromJavaScriptcode,SeparatingHTMLtemplatesfromJavaScriptcode

JavaScriptlibrariesURL/Choosinganame

jQueryandDOMscripting/jQueryandDOMscriptingURL/jQueryandDOMscripting,ManipulatingtheDOMusingjQuery,HowtheIteratorPatternisusedbyjQueryused,formanipulatingDOM/ManipulatingtheDOMusingjQueryMethodChaining/MethodChainingandFluentInterfacesFluentInterfaces/MethodChainingandFluentInterfacesCompositePattern,using/HowtheCompositePatternisusedbyjQueryGitHubpage,URL/HowtheCompositePatternisusedbyjQueryIteratorPattern,using/HowtheIteratorPatternisusedbyjQueryObserverPattern,using/HowitisusedbyjQueryPublish/SubscribePattern/HowitisadoptedbyjQuerycustomevents/CustomeventsinjQuerycodeorganization,URL/Overviewoftheimplementation

jQuery-requestAnimationFramepluginreference/Stylingandanimating

jQuery-UIWidgetFactoryURL/Addingmethodstoyourplugin

jQuery.ajaxSetup()method/AcceptingconfigurationparametersjQuery.guid/ThejQueryonmethodjQuery.noConflict()method

URL/Workingwith$.noConflict()jQueryapplications

modules,using/UsingModulesinjQueryapplicationsMockObjectPattern,using/UsingMockObjectsinjQueryapplications

jQueryequivalentmethodURL/Avoidmemoryleaks

jQueryimplementationabout/HowitisadoptedbyjQueryjQueryDOMTraversalAPI/ThejQueryDOMTraversalAPIpropertyaccessandmanipulationAPI/ThepropertyaccessandmanipulationAPI

jQueryJavaScriptStyleGuideURL/Thewideacceptance

jQueryonmethod/ThejQueryonmethodjQueryPlugin

about/IntroducingjQueryPluginsprinciples,following/FollowingjQueryprinciplescharacteristics/FollowingjQueryprinciples$.noConflict(),workingwith/Workingwith$.noConflict()wrapping,withIIFE/WrappingwithanIIFEnamingconventions/Choosinganame

jQueryPluginBoilerplateURL/UsingthejQueryPluginBoilerplateusing/UsingthejQueryPluginBoilerplatemethods,adding/Addingmethodstoyourplugin

jQueryPluginRegistryURL/Choosinganame

jQueryprinciplesfollowing/FollowingjQueryprinciplesCompositeCollectionObjects,workingon/WorkingonCompositeCollectionObjectsfurtherchaining/Allowingfurtherchaining

jQueryPromisestransforming,to/TransformingtojQueryPromises

jQuerysourceviewerURL/ThejQueryonmethod

jQuerySourceViewerURL/HowtheCompositePatternisusedbyjQuery

jQueryv3.0A+Promisesimplementationreference/ComparingjQueryandA+Promises

JSDelivrAPIusing/UsingJSDelivrAPIreference/UsingJSDelivrAPI

LLazyLoadingModules

about/LazyLoadingModulesLevel2SelectorAPI/ThejQueryDOMTraversalAPI

Mmemoryusagebenefits

comparing/ComparethememoryusagebenefitsMethodChaining/MethodChainingandFluentInterfacesMockjaxjQueryPluginlibrary

reference/ImplementingaMockServiceusing/ImplementingaMockService

MockObjectPatternabout/IntroducingtheMockObjectPatternusing,injQueryapplications/UsingMockObjectsinjQueryapplicationsactualservicerequirements,defining/DefiningtheactualservicerequirementsMockService,implementing/ImplementingaMockServiceMockService,using/UsingtheMockService

ModulePatternabout/TheModulePatternImmediatelyInvokedFunctionExpression(IIFE)buildingblock/TheIIFEbuildingblocksimpleIIFEModulePattern/ThesimpleIIFEModulePatternusing,injQuery/HowitisusedbyjQuerynamespaceparametermodulevariant/TheNamespaceParameterModulevariantIIFE-containedmodulevariant/TheIIFE-containedModulevariant

modulesabout/ModulesandNamespaces,Encapsulatinginternalpartsofanimplementationinternalpartimplementation,encapsulating/Encapsulatinginternalpartsofanimplementationacceptance/Thewideacceptanceusing,injQueryapplications/UsingModulesinjQueryapplications

modules,jQueryapplicationsusing/UsingModulesinjQueryapplicationsdashboardmodule/Themaindashboardmodulecategoriesmodule/ThecategoriesmoduleinformationBoxmodule/TheinformationBoxmodulecountermodule/Thecountermoduleimplementation,overview/Overviewoftheimplementation

MustacheURL/IntroducingHandlebars.js

MutationObserverURL/ImplementingastatefuljQueryPlugin

N$.noConflict()

working/Workingwith$.noConflict()$.noop()

using/Using$.noop()reference/Using$.noop()

namespacedeventsURL/Usingcustomeventnamespacing

namespaceparametermodulevariant/TheNamespaceParameterModulevariantnamespaces

about/ModulesandNamespaces,AvoidingglobalvariableswithNamespacesinternalpartimplementation,encapsulating/Encapsulatinginternalpartsofanimplementationglobalvariables,avoiding/AvoidingglobalvariableswithNamespacesbenefits/Thebenefitsofthesepatternsacceptance/Thewideacceptance

namespacing/AvoidingglobalvariableswithNamespacesnamingconventions,jQueryPlugin

selecting/ChoosinganameNPM

URL/Choosinganame

Oobject-orientedJavaScript

URL/ThewideacceptanceObjectLiteral/HowitisadoptedbyjQueryObjectLiteralPattern/TheObjectLiteralPattern,ThesimpleIIFEModulePattern,TheRevealingModulePatternObserverPattern

about/IntroducingtheObserverPatternURL/IntroducingtheObserverPatternusing,injQuery/HowitisusedbyjQueryjQuery.fn.on()method/ThejQueryonmethoddocument-readyobserver/Thedocument-readyobserversampleusecase,demonstrating/Demonstrateasampleusecasecomparing,witheventattributes/Howitiscomparedwitheventattributeseventattributes,comparingwith/Howitiscomparedwitheventattributesmemoryleaks,avoiding/AvoidmemoryleaksandPublish/SubscribePattern,differentiatingbetween/HowitdiffersfromtheObserverPattern

Ppatterns

benefits/ThebenefitsofthesepatternsperformancetipsonjQuery

reference/WritingefficientjQuerycodeperformantCSSselectors

writing/WritingperformantCSSselectorsplaceholdernotations,_.tempate()method

<%=%>notation/IntroducingUnderscore.js<%-%>/IntroducingUnderscore.js<%%>notation/IntroducingUnderscore.js

Promisesabout/IntroducingtheconceptofPromisesusing/UsingPromisesjQueryPromiseAPI,using/UsingthejQueryPromiseAPIadvancedconcepts/Advancedconceptsjoining/JoiningPromisesusing,byjQuery/HowjQueryusesPromisestransforming,toothertypes/TransformingPromisestoothertypesbenefits/SummarizingthebenefitsofPromises

Promises,chainingabout/ChainingPromisesthrownerrors,handling/Handlingthrownerrors

Promises/A+using/UsingPromises/A+reference/UsingPromises/A+comparing,withjQuery/ComparingjQueryandA+Promisestransforming,to/TransformingtoPromises/A+

Publish/SubscribePatternabout/IntroducingthePublish/SubscribePatternandObserverPattern,differentiatingbetween/HowitdiffersfromtheObserverPatternusing,byjQuery/HowitisadoptedbyjQueryimplementing,withcustomevents/ImplementingaPub/Subschemeusingcustomeventssampleusecase/Demonstratingasampleusecaseusing,ondashboardexample/UsingPub/Subonthedashboardexampleimplementation,extending/Extendingtheimplementation

publishers/IntroducingthePublish/SubscribePattern

RRequireJS

URL/LazyLoadingModulesresources

bundling/Bundlingandminifyingresourcesminifying/Bundlingandminifyingresources

reusablepluginscreating/Creatingreusablepluginsconfigurationparameters,accepting/AcceptingconfigurationparametersstatefuljQueryPlugins,writing/WritingstatefuljQuerypluginsstatefuljQueryPlugin,implementing/ImplementingastatefuljQueryPlugininstance,destroying/Destroyingaplugininstancegettermethod,implementing/Implementinggetterandsettermethodssettermethods,implementing/Implementinggetterandsettermethodsusing,inDashboardapplication/UsingourplugininourDashboardapplication

RevealingModulePatternabout/TheRevealingModulePattern

S$.singleplugin

using/Usingthe$.singleplugin<script>tag/Placingscriptsneartheendofthepagesampleusecase,ObserverPattern

demonstrating/Demonstrateasampleusecasesampleusecase,Publish/SubscribePattern

demonstrating/Demonstratingasampleusecaseobject,usingasbroker/Usinganyobjectasabroker

scriptsplacing,nearendofpage/Placingscriptsneartheendofthepage

SeparationofConcernsabout/EncapsulatinginternalpartsofanimplementationURL/Encapsulatinginternalpartsofanimplementation

SeparationofConcernsprinciple/Thebenefitsofthispatternsettermethod

implementing/Implementinggetterandsettermethodssimplecallback

defining/IntroducingtheObserverPatternsimpleIIFEModulePattern

about/ThesimpleIIFEModulePatternSingleResponsibilityprinciple

URL/TheIteratorPatternSizzle/ThejQueryDOMTraversalAPI,WritingperformantCSSselectors

URL/HowitisusedbyjQueryreference/ThejQueryDOMTraversalAPI

specifiedNodeTypesURL/HowtheCompositePatternisusedbyjQuery

statefuljQueryPluginswriting/WritingstatefuljQuerypluginsimplementing/ImplementingastatefuljQueryPlugininstance,destroying/Destroyingaplugininstance

strictexecutionmodeURL/WrappingwithanIIFE

strictmode,JavaScriptURL/UsingES5StrictMode

subscribers/IntroducingthePublish/SubscribePattern

TTiny

URL/ImplementingaPub/SubschemeusingcustomeventstruthyJavaScriptvalue

reference/Writingbetterforloops

UUglifyJS

reference/BundlingandminifyingresourcesUnderscore.js

about/IntroducingUnderscore.jsusing,inapplications/UsingUnderscore.jstemplatesinourapplicationsHTMLtemplates,separatingfromJavaScriptcode/SeparatingHTMLtemplatesfromJavaScriptcode,SeparatingHTMLtemplatesfromJavaScriptcodetemplates,pre-compiling/Pre-compilingtemplates

Vvariable

namingconventions/ManipulatingtheDOMusingjQuery

YYUICompressor

reference/Bundlingandminifyingresources

top related