ScriptJunkieMicrosoftLearn

UpgradetoMicrosoftEdgetotakeadvantageofthelatestfeatures,securityupdates,andtechnicalsupport.

AddyOsmani|August4,2011

InPart1,I'llbecoveringacompleterun-downofBackbone0.5.2'smodels,views,collectionsandroutersbutalsotakingyouthroughoptionsforcorrectlynamespacingyourBackboneapplication.I'llalsogiveyousometipsincludingwhatscaffoldingtoolthatcansavetimesettingupyourinitialapplication,theidealnumberofrouterstouseandmore.

We'llthenbuildatestablewireframeofourapplicationinjQueryMobilebeforewecompletebuildingitinPart2.

DevelopersthathaveattemptedtoworkwithBackbone.jsandjQueryMobileinthepastwillbeawarethatbothsolutionshavetheirownapproachestohandingrouting.

Unfortunately,thereareoftenanumberofhacksandworkaroundsrequiredtogetthemfunctioninginunison.I'veexperienceddevelopersgoingsofarastoeditthejQueryMobilesourcetoachievethiswhichisactuallyunnecessary.InPart2,you'llfindouthowtousesome(currentlyundocumented)jQueryMobilefeaturestosolvethisissuewithoutresortingtosuchlengths,soyoucanspendmoretimeworkingonyouractualapplicationlogic.

I'mhopefultherewillbesomethinginthetutorialtobenefitdevelopersofallskill-levels,soonthatnote,let'sgetstarted!

Backboneismaintainedbyanumberofcontributors,mostnotably:JeremyAshkenas,creatorofCoffeeScript,DoccoandUnderscore.js.AsJeremyisabelieverindetaileddocumentation,there'salevelofcomfortinknowingyou'reunlikelytorunintoissueswhichareeithernotexplainedintheofficialdocsorwhichcan'tbenaileddownwithsomeassistancefromthe#documentcloudIRCchannel.Istronglyrecommendusingthelatterifyoufindyourselfgettingstuck.

Backbone'smainbenefits,regardlessofyourtargetplatformordevice,includehelping:

Inways,therealquestioniswhyyoushouldconsiderapplyingtheMVC-patterntoyourJavaScriptprojectsandtheonewordanswerissimplystructure.

IfoptingtousejQuery,zeptooranotherqSA-basedselectionlibrarytoproduceanon-trivialapplicationitcanbecomeveryeasytoproduceanunwieldyamountofcode;thatis-unlessyouhaveaplanforhowyou'regoingtostructureandorganizeyourapplication.Separatingconcernsintomodels,viewsandcontrollers(orrouters)isonewayofsolvingthis.

RememberthatifyouhaveexperiencewithstructuringapplicationsusingtheMVVM(Model-ViewViewModel)pattern,modulesorotherwise,thesearealsoequallyasvalidbutdorequireyoutoknowwhatyou'redoing.Formostsingle-pageapplications,IfindthattheMVCpatternworkswellsoBackboneisaperfectfitforourneeds.

Inthissection,you'lllearntheessentialsaboutBackbone'smodels,views,collectionsandrouters.Whilstthisisn'tmeantasareplacementfortheofficialdocumentation,itwillhelpyouunderstandmanyofthecoreconceptsbehindBackbonebeforewebuildmobileapplicationswithit.Iwillalsobecoveringsometipsoneffectivenamespacing.

Backbonemodelscontaininteractivedataforanapplicationaswellasthelogicaroundthisdata.Forexample,wecanuseamodeltorepresenttheconceptofaphotoobjectincludingitsattributesliketags,titlesandalocation.

Modelsarequitestraight-forwardtocreateandcanbeconstructedbyextendingBackbone.Modelasfollows:

Photo=Backbone.Model.extend({defaults:{src:'placeholder.jpg',title:'animageplaceholder,coordinates:[0,0]},initialize:function(){this.bind("change:src",function(){varsrc=this.get("src");console.log('Imagesourceupdatedto'+src)});},changeSrc:function(source){this.set({src:source});}});varsomePhoto=newPhoto({src:"test.jpg",title:"testing"});InitializationTheinitialize()methodiscalledwhencreatinganewinstanceofamodel.It'sconsideredoptional,howeverwe'llbereviewingsomereasonsitmightcomeinusefulveryshortly.

Photo=newBackbone.Model.extend({initialize:function(){console.log('thismodelhasbeeninitialized');}});/*Wecanthencreateourowninstanceofaphotoasfollows:*/varmyPhoto=newPhoto;Getters&SettersModel.get()

Model.get()provideseasyaccesstoamodel'sattributes.Attributesbelowwhicharepassedthroughtothemodeloninstantiationaretheninstantlyavailableforretrieval.

varmyPhoto=newPhoto({title:"Myawesomephoto",src:"boston.jpg",location:"Boston",tags:['thebiggame','vacation']}),title=myPhoto.get("title"),//myawesomephotolocation=myPhoto.get("location"),//Bostontags=myPhoto.get("tags"),//['thebiggame','vacation']photoSrc=myPhoto.get("src");//boston.jpgAlternatively,ifyouwishtodirectlyaccessalloftheattributesinanmodel'sinstancedirectly,youcanachievethisasfollows:

varmyAttributes=myPhoto.attributes;console.log(myAttributes);Note:ItisbestpracticetouseModel.set()ordirectinstantiationtosetthevaluesofamodel'sattributes.AccessingModel.attributesdirectlyisfineforreadingorcloningdata,butideallyshouldn'tbeusedtoforattributemanipulation.

Finally,ifyouwouldliketocopyamodel'sattributesforJSONstringification(e.g.forserializationpriortobeingpassedtoaview),thiscanbeachievedusingModel.toJSON():

varmyAttributes=myPhoto.toJSON();console.log(myAttributes);/*thisreturns{title:"Myawesomephoto",src:"boston.jpg",location:"Boston",tags:['thebiggame','vacation']}*/Model.set()

Model.set()allowsustopassparametersintoaninstanceofourmodel.Attributescaneitherbesetduringinitializationorlateron.

Photo=newBackbone.Model.extend({initialize:function(){console.log('thismodelhasbeeninitialized');}});/*Settingthevalueofattributesviainstantiation*/varmyPhoto=newPhoto({title:'Myawesomephoto',location:'Boston'});varmyPhoto2=newPhoto();/*SettingthevalueofattributesthroughModel.set()*/myPhoto2.set({title:'VacationinFlorida',location:'Florida'});DefaultvaluesTherearetimeswhenyouwantyourmodeltohaveasetofdefaultvalues(e.g.inscenariowhereacompletesetofdataisn'tprovidedbytheuser).Thiscanbesetusingapropertycalled'defaults'inyourmodel.

Photo=newBackbone.Model.extend({defaults:{title:'Anotherphoto!',tags:['untagged'],location:'home',src:'placeholder.jpg'},initialize:function(){}});varmyPhoto=newPhoto({location:"Boston",tags:['thebiggame','vacation']}),title=myPhoto.get("title"),//Anotherphoto!location=myPhoto.get("location"),//Bostontags=myPhoto.get("tags"),//['thebiggame','vacation']photoSrc=myPhoto.get("src");//placeholder.jpgListeningforchangestoyourmodelAnyandalloftheattributesinaBackbonemodelcanhavelistenersboundtothemwhichdetectwhentheirvalueschange.Thiscanbeeasilyaddedtotheinitialize()functionasfollows:

this.bind('change',function(){console.log('valuesforthismodelhavechanged');});Inthefollowingexample,wecanalsologamessagewheneveraspecificattribute(thetitleofourPhotomodel)isaltered.

Photo=newBackbone.Model.extend({defaults:{title:'Anotherphoto!',tags:['untagged'],location:'home',src:'placeholder.jpg'},initialize:function(){console.log('thismodelhasbeeninitialized');this.bind("change:title",function(){vartitle=this.get("title");console.log("Mytitlehasbeenchangedto.."+title);});},setTitle:function(newTitle){this.set({title:newTitle});}});varmyPhoto=newPhoto({title"Fishingatthelake",src:"fishing.jpg")});myPhoto.setTitle('Fishingatsea');//logsmytitlehasbeenchangedto..FishingatseaValidationBackbonesupportsmodelvalidationthroughModel.validate(),whichallowscheckingtheattributevaluesforamodelpriortothembeingset.

Itsupportsincludingascomplexortersevalidationrulesagainstattributesandisquitestraight-forwardtouse.Iftheattributesprovidedarevalid,nothingshouldbereturnedfrom.validate(),howeveriftheyareinvalidacustomerrorcanbereturnedinstead.

Abasicexampleforvalidationcanbeseenbelow:

Photo=newBackbone.Model.extend({validate:function(attribs){if(attribs.src=="placeholder.jpg"){return"Remembertosetasourceforyourimage!";}},initialize:function(){console.log('thismodelhasbeeninitialized');this.bind("error",function(model,error){console.log(error);});}});varmyPhoto=newPhoto();myPhoto.set({title:"Onthebeach"});CollectionsCollectionsarebasicallysetsofmodelsandcanbeeasilycreatedbyextendingBackbone.Collection.

Normally,whencreatingacollectionyou'llalsowanttopassthroughapropertyspecifyingthemodelthatyourcollectionwillcontainaswellasanyinstancepropertiesrequired.

Inthefollowingexample,we'recreatingaPhotoCollectioncontainingthePhotomodelswepreviouslydefined.

PhotoCollection=Backbone.Collection.extend({model:Photo});GettersandSettersThereareafewdifferentoptionsforretrievingamodelfromacollection.Themoststraight-forwardisusingCollection.get()whichacceptsasingleidasfollows:

varskiingEpicness=PhotoCollection.get(2);Sometimesyoumayalsowantgetamodelbasedonsomethingcalledtheclientid.Thisisanidthatisinternallyassignedautomaticallywhencreatingmodelsthathavenotyetbeensaved,shouldyouneedtoreferencethem.Youcanfindoutwhatamodel'sclientidisbyaccessingits.cidproperty.

varmySkiingCrash=PhotoCollection.getByCid(456);BackboneCollectionsdon'thavesettersassuch,butdosupportaddingnewmodelsvia.add()andremovingmodelsvia.remove().

vara=newBackbone.Model({title:'myvacation'}),b=newBackbone.Model({title:'myholiday'});varphotoCollection=newBackbone.Collection([a,b]);photoCollection.remove([a,b]);ListeningforeventsAscollectionsrepresentagroupofitems,we'realsoabletolistenforaddandremoveeventsforwhennewmodelsareaddedorremovedfromthecollection.Here'sanexample:

PhotoCollection=newBackbone.Collection;PhotoCollection.bind("add",function(photo){console.log("Iliked"+photo.get("title")+'itsthisone,right'+photo.src);});PhotoCollection.add([{title:"MytriptoBali",src:"bali-trip.jpg"},{title:"Theflighthome",src:"long-flight-oofta.jpg"},{title:"Uploadingpix",src:"too-many-pics.jpg"}]);Inaddition,we'reabletobinda'change'eventtolistenforchangestomodelsinthecollection.

PhotoCollection.bind("change:title",function(){console.log('therehavebeenupdatesmadetothiscollectionstitles');});FetchingmodelsfromtheserverCollections.fetch()providesyouwithasimplewaytofetchadefaultsetofmodelsfromtheserverintheformofaJSONarray.Whenthisdatareturns,thecurrentcollectionwillrefresh.

varPhotoCollection=newBackbone.Collection;PhotoCollection.url='/photos';PhotoCollection.fetch();Underthecovers,Backbone.syncisthefunctioncalledeverytimeBackbonetriestoread(orsave)modelstotheserver.ItusesjQueryorZepto'sajaximplementationstomaketheseRESTfulrequests,howeverthiscanbeoverriddenasperyourneeds.

Intheabovefetchexampleifwewishtologaneventwhen.sync()getscalled,wecansimplyachievethisasfollows:

Backbone.sync=function(method,model){console.log("I'vebeenpassed"+method+"with"+JSON.stringify(model));};Resetting/RefreshingCollectionsRatherthanaddingorremovingmodelsindividually,youoccasionallywishtoupdateanentirecollectionatonce.Collection.reset()allowsustoreplaceanentirecollectionwithnewmodelsasfollows:

PhotoCollection.reset([{title:"MytriptoScotland",src:"scotland-trip.jpg"},{title:"TheflightfromScotland",src:"long-flight.jpg"},{title:"Latestsnapoflock-ness",src:"lockness.jpg"}]);UnderscoreutilityfunctionsAsBackbonerequiresUnderscoreasaharddependency,we'reabletousemanyoftheutilitiesithastooffertoaidwithourapplicationdevelopment.Here'sanexampleofhowUnderscore'ssortBy()methodcanbeusedtosortacollectionofphotosbasedonaparticularattribute.

Note:ArouterwillusuallyhaveatleastoneURLroutedefinedaswellasafunctionthatmapswhathappenswhenyoureachthatparticularroute.Thistypeofkey/valuepairmayresemble:

"/route":"mappedFunction"

LetusnowdefineourfirstcontrollerbyextendingBackbone.Router.Forthepurposesofthisguide,we'regoingtocontinuepretendingwe'recreatingaphotogalleryapplicationthatrequiresaGalleryController.

Notetheinlinecommentsinthecodeexamplebelowastheycontinuetherestofthelessononrouters.

Next,weneedtoinitializeBackbone.historyasithandleshashchangeeventsinourapplication.Thiswillautomaticallyhandleroutesthathavebeendefinedandtriggercallbackswhenthey'vebeenaccessed.

TheBackbone.history.start()methodwillsimplytellBackbonethatit'sOKtobeginmonitoringallhashchangeeventsasfollows:

Backbone.history.start();Controller.saveLocation()Asanaside,ifyouwouldliketosaveapplicationstatetotheURLataparticularpointyoucanusethe.saveLocation()methodtoachievethis.ItsimplyupdatesyourURLfragmentwithouttheneedtotriggerthehashchangeevent.

/*Letsimaginewewouldlikeaspecificfragmentforwhenauserzoomsintoaphoto*/zoomPhoto:function(factor){this.zoom(factor);//imaginethiszoomsintotheimagethis.saveLocation("zoom/"+factor);//updatesthefragmentforus}ViewsViewsinBackbonedon'tcontainthemarkupforyourapplication,butratheraretheretosupportmodelsbydefininghowtheyshouldbevisuallyrepresentedtotheuser.ThisisusuallyachievedusingJavaScripttemplating(e.g.Mustache,jQuerytmpletc).Whenamodelupdates,ratherthantheentirepageneedingtoberefreshed,wecansimplybindaview'srender()functiontoamodel'schange()event,allowingtheviewtoalwaysbeuptodate.

Similartotheprevioussections,creatinganewviewisrelativelystraight-forward.WesimplyextendBackbone.View.Here'sanexampleofaonepossibleimplementationofthis,whichI'llexplainshortly:

varPhotoSearch=Backbone.View.extend({el:$('#results'),render:function(event){varcompiled_template=_.template($("#results-template").html());this.el.html(compiled_template(this.model.toJSON()));returnthis;//recommendedasthisenablescallstobechained.},events:{"submit#searchForm':"search","click.reset':"reset","click.advanced":'switchContext"},search:function(event){//executedwhenaform'#searchForm'hasbeensubmitted},reset:function(event){//executedwhenanelementwithclass"go"hasbeenclicked.},//etc});Whatis'el'elisbasicallyareferencetoaDOMelementandallviewsmusthaveone,howeverBackboneallowsyoutospecifythisinfourdifferentways.Youcaneitherdirectlyuseanid,atagName,classNameorifyoudon'tstateanythingelwillsimplydefaulttoaplaindivelementwithoutanyidorclass.Herearesomequickexamplesofhowthesemaybeused:

el:$('#results')//selectbasedonIDorjQueryselector.tagName:'li'//selectbasedonaspecifictag.HereelitselfwillbederivedfromthetagNameclassName:'items'//similartotheabove,thiswillalsoresultinelbeingderivedfromitel:''//defaultstoadivwithoutanid,nameorclass.Understandingrender()render()isafunctionthatshouldalwaysbeoverriddentodefinehowyouwouldlikeatemplatetoberendered.BackboneallowsyoutouseanyJavaScripttemplatingsolutionofyourchoiceforthisbutforthepurposesofthisexample,we'lloptforunderscore'smicro-templating.

The_.templatemethodinunderscorecompilesJavaScripttemplatesintofunctionswhichcanbeevaluatedforrendering.Here,I'mpassingthemarkupfromatemplatewithid'results-template'tobecompiled.Next,Isetthehtmlforel(ourDOMelementforthisview)theoutputofprocessingaJSONversionofthemodelassociatedwiththeviewthroughthecompiledtemplate.

Presto!Thispopulatesthetemplate,givingyouadata-completesetofmarkupinjustafewshortlinesofcode.

TheBackboneeventsattributeallowsustoattacheventlistenerstoeithercustomselectorsorelifnoselectorisprovided.Aneventtakestheform{"eventNameselector":"callbackFunction"}andanumberofevent-typesaresupported,including'click','submit','mouseover','dblclick'andmore.

Whatisn'tinstantlyobviousisthatunderthebonnet,BackboneusesjQuery's.delegate()toprovideinstantsupportforeventdelegationbutgoesalittlefurther,extendingitsothat'this'alwaysreferstothecurrentviewobject.Theonlythingtoreallykeepinmindisthatanystringcallbacksuppliedtotheeventsattributemusthaveacorrespondingfunctionwiththesamenamewithinthescopeofyourviewotherwiseyoumayincurexceptions.

WhenlearninghowtouseBackbone,oneimportantareathatthatisverycommonlyoverlookedintutorialsisnamespacing.IfyoualreadyhaveexperiencewithhowtonamespaceinJavaScript,thefollowingsectionwillprovidesomeadviceonhowtospecificallyapplyconceptsyouknowtoBackbone,howeverIwillalsobecoveringexplanationsforbeginnerstoensureeveryoneisonthesamepage.

Thebasicideaaroundnamespacingistoavoidcollisionswithotherobjectsorvariablesintheglobalnamespace.They'reimportantasit'sbesttosafeguardyourcodefrombreakingintheeventofanotherscriptonthepageusingthesamevariablenamesasyouare.Asagood'citizen'oftheglobalnamespace,it'salsoimperativethatyoudoyourbesttosimilarlynotpreventotherdeveloper'sscriptsexecutingduetothesameissues.

Inthissectionwe'llbetakingalookshortlyatsomeexamplesofhowyoucannamespaceyourmodels,views,routersandothercomponentsspecifically.Thepatternswe'llbeexaminingare:

OnepopularpatternfornamespacinginJavaScriptisoptingforasingleglobalvariableasyourprimaryobjectofreference.Askeletonimplementationofthiswherewereturnanobjectwithfunctionsandpropertiescanbefoundbelow:

varmyApplication=(function(){function(){...},return{...}})();whichyou'relikelytohaveseenbefore.ABackbone-specificexamplewhichmaybemoreusefulis:

varmyViews=(function(){return{PhotoView:Backbone.View.extend({..}),GalleryView:Backbone.View.extend({..}),AboutView:Backbone.View.extend({..});//etc.};})();Herewecanreturnasetofviewsorevenanentirecollectionofmodels,viewsandroutersdependingonhowyoudecidetostructureyourapplication.Althoughthisworksforcertainsituations,thebiggestchallengewiththesingleglobalvariablepatternisensuringthatnooneelsehasusedthesameglobalvariablenameasyouhaveinthepage.

Onesolutiontothisproblem,asmentionedbyPeterMichaux,istouseprefixnamespacing.It'sasimpleconceptatheart,buttheideaisyouselectabasicprefixnamespaceyouwishtouse(inthisexample,myApplication_)andthendefineanymethods,variablesorotherobjectsaftertheprefix.

varmyApplication_photoView=Backbone.View.extend({}),myApplication_galleryView=Backbone.View.extend({});Thisiseffectivefromtheperspectiveoftryingtolowerthechancesofaparticularvariableexistingintheglobalscope,butrememberthatauniquelynamedobjectcanhavethesameeffect.Thisaside,thebiggestissuewiththepatternisthatitcanresultinalargenumberofglobalobjectsonceyourapplicationstartstogrow.

Note:Thereareseveralothervariationsonthesingleglobalvariablepatternoutinthewild,howeverhavingreviewedquiteafew,IfelttheseappliedbesttoBackbone.

Objectliteralshavetheadvantageofnotpollutingtheglobalnamespacebutassistinorganizingcodeandparameterslogically.They'rebeneficialifyouwishtocreateeasily-readablestructuresthatcanbeexpandedtosupportdeepnesting.Unlikesimpleglobalvariables,objectliteralsoftenalsotakeintoaccounttestsfortheexistenceofavariablebythesamenamesothechancesofcollisionoccurringaresignificantlyreduced.

Thecodeattheverytopofthenextsampledemonstratesthedifferentwaysinwhichyoucanchecktoseeifanamespacealreadyexistsbeforedefiningit.IcommonlyuseOption3.

/*Doesn'tcheckforexistenceofmyApplication*/varmyApplication={};/*Doescheckforexistence.Ifalreadydefined,weusethatinstance.Option1:if(!MyApplication)MyApplication={};Option2:varmyApplication=myApplication=myApplication||{}Option3:varmyApplication=myApplication||{};Wecanthenpopulateourobjectliteraltosupportmodels,viewsandcollections(oranydata,really):*/varmyApplication={models:{},views:{pages:{}},collections:{}};Onecanalsooptforaddingpropertiesdirectlytothenamespace(suchasyourviews,inthefollowingexample):

varmyGalleryViews=myGalleryViews||{};myGalleryViews.photoView=Backbone.View.extend({});myGalleryViews.galleryView=Backbone.View.extend({});Thebenefitofthispatternisthatyou'reabletoeasilyencapsulateallofyourmodels,views,routersetc.inawaythatclearlyseparatesthemandprovidesasolidfoundationforextendingyourcode.

Thispatternhasanumberofusefulapplications.It'softenofbenefittodecouplethedefaultconfigurationforyourapplicationintoasingleareathatcanbeeasilymodifiedwithouttheneedtosearchthroughyourentirecodebasejusttoalterthem-objectliteralsworkgreatforthispurpose.Here'sanexampleofahypotheticalobjectliteralforconfiguration:

varmyConfig={language:'english',defaults:{enableGeolocation:true,enableSharing:false,maxPhotos:20},theme:{skin:'a',toolbars:{index:'ui-navigation-toolbar',pages:'ui-custom-toolbar'}}}NotethattherearereallyonlyminorsyntacticaldifferencesbetweentheobjectliteralpatternandastandardJSONdataset.IfforanyreasonyouwishtouseJSONforstoringyourconfigurationsinstead(e.g.forsimplerstoragewhensendingtotheback-end),feelfreeto.

Anextensionoftheobjectliteralpatternisnestednamespacing.It'sanothercommonpatternusedthatoffersalowerriskofcollisionduetothefactthatevenifanamespacealreadyexists,it'sunlikelythesamenestedchildrendo.

Doesthislookfamiliar

YAHOO.util.Dom.getElementsByClassName('test');Yahoo'sYUIusesthenestedobjectnamespacingpatternregularlyandevenDocumentCloud(thecreatorsofBackbone)usethenestednamespacingpatternintheirmainapplications.AsampleimplementationofnestednamespacingwithBackbonemaylooklikethis:

vargalleryApp=galleryApp||{};/*performsimilarcheckfornestedchildren*/galleryApp.routers=galleryApp.routers||{};galleryApp.model=galleryApp.model||{};galleryApp.model.special=galleryApp.model.special||{};/*routers*/galleryApp.routers.Workspace=Backbone.Router.extend({});galleryApp.routers.PhotoSearch=Backbone.Router.extend({});/*models*/galleryApp.model.Photo=Backbone.Model.extend({});galleryApp.model.Comment=Backbone.Model.extend({});/*specialmodels*/galleryApp.model.special.Admin=Backbone.Model.extend({});Thisisbothreadable,organizedandisarelativelysafewayofnamespacingyourBackboneapplicationinasimilarfashiontowhatyoumaybeusedtoinotherlanguages.

Theonlyrealcaveathoweveristhatitrequiresyourbrowser'sJavaScriptenginefirstlocatingthegalleryAppobjectandthendiggingdownuntilitgetstothefunctionyouactuallywishtouse.

Thiscanmeananincreasedamountofworktoperformlookups,howeverdeveloperssuchasJuriyZaytsev(kangax)havepreviouslytestedandfoundtheperformancedifferencesbetweensingleobjectnamespacingvsthe'nested'approachtobequitenegligible.

Reviewingthenamespacepatternsabove,theoptionthatIwouldpersonallyusewithBackboneisnestedobjectnamespacingwiththeobjectliteralpattern.

Singleglobalvariablesmayworkfineforapplicationsthatarerelativelytrivial,however,largercodebasesrequiringbothnamespacesanddeepsub-namespacesrequireasuccinctsolutionthatpromotesreadabilityandscales.IfeelthispatternachievesalloftheseobjectiveswellandisaperfectcompanionforBackbonedevelopment.

ItworksverywellwithBackbone,Underscore,jQueryandCoffeeScriptandisevenusedbycompaniessuchasRedBullandJimBean.Youmayhavetoupdateanythirdpartydependencies(e.g.latestjQueryorZepto)whenusingit,butotherthanthatitshouldbefairlystabletouserightoutofthebox.

Brunchcaneasilybeinstalledviathenodejspackagemanagerandtakesjustlittletonotimetogetstartedwith.IfyouhappentouseVimorTextmateasyoureditorofchoice,youmaybehappytoknowthattherearealsoBrunchbundlesavailableforboth.

AsThomasDavishaspreviouslynoted,Backbone.js'sMVCisalooseinterpretationoftraditionalMVC,somethingcommontomanyclient-sideMVCsolutions.Backbone'sviewsarewhatcouldbeconsideredawrapperfortemplatingsolutionssuchastheMustache.jsandBackbone.ViewistheequivalentofacontrollerintraditionalMVC.Backbone.Modelishoweverthesameasaclassical'model'.

WhilstBackboneisnottheonlyclient-sideMVCsolutionthatcouldusesomeimprovementsinit'snamingconventions,Backbone.Controllerwasprobablythemostcentralsourceofsomeconfusionbuthasbeenrenamedarouterinmorerecentversions.Thiswon'tpreventyoufromusingBackboneeffectively,howeverthisisbeingpointedoutjusttohelpavoidanyconfusionifforanyreasonyouopttouseanolderversionoftheframework.

TheofficialBackbonedocsdoattempttoclarifythattheirroutersaren'treallytheCinMVC,butit'simportanttounderstandwherethesefitratherthanconsideringclient-sideMVCa1:1equivalenttothepatternyou'veprobablyseeninserver-sidedevelopment.

AndrewdeAndradehaspointedoutthatDocumentCloudthemselvesusuallyonlyuseasinglecontrollerinmostoftheirapplications.You'reverylikelytonotrequiremorethanoneortworoutersinyourownprojectsasthemajorityofyourapplicationroutingcanbekeptorganizedinasinglecontrollerwithoutitgettingunwieldy.

BackbonecanbeusedforbuildingbothtrivialandcomplexapplicationsasdemonstratedbythemanyexamplesAshkenashasbeenreferencingintheBackbonedocumentation.AswithanyMVCframeworkhowever,it'simportanttodedicatetimetowardsplanningoutwhatmodelsandviewsyourapplicationreallyneeds.Divingstraightintodevelopmentwithoutdoingthiscanresultineitherspaghetticodeoralargerefactorlateronandit'sbesttoavoidthiswherepossible.

Attheendoftheday,thekeytobuildinglargeapplicationsisnottobuildlargeapplicationsinthefirstplace.IfyouhoweverfindBackbonedoesn'tcutitforyourrequirementsIstronglyrecommendcheckingoutJavaScriptMVCorSproutCoreasthesebothofferalittlemorethanBackboneoutofthebox.DojoandDojoMobilemayalsobeofinterestasthesehavealsobeenusedtobuildsignificantlycomplexappsbyotherdevelopers.

OneofthegreatthingsaboutjQueryMobileisthatitallowsyoutoeasilycreatefunctionalmock-upsofyourintendedmobileUIsimplyusingmark-upanddataattributes.SimilartoDojoMobile,jQueryMobilecomeswithalargesuiteofpre-themedcomponentsandUIwidgetsthatcanbeusedtostructureacompleteinterfacewithoutneedingtotouchagreatdealofCSSduringtheprototypingphase.

ThismeansthatratherthanoptingtowireframeyourapplicationusingpencilsketchesoratoollikeBalsamiq/MockFlow,thesameamountoftimecanbeinvestedincreatingamock-upwhichcanberendertestedcross-platformandcross-devicepriortocodinganyreallogicforyourwebapp.Wecantheneasilywritethemodels,viewlogicandroutersforourapplicationandsimplytiethemtotheprototypetocompletetheproject.

We'regoingtobebuildingacompletemobilephotosearchapplicationthatusestheFlickrAPIandprovidesviewsforresults,individualphotos,optionsandsharing-allwhichwillbebookmarkable.InPart2ofthetutorial,we'reactuallygoingtowritetheBackbone.jscodethatpowerstherestoftheappbutfornow,it'simperativethatwegettheprototyperight.Sothatwehavesomethingtoreferenceeasily,I'vedecidedtocalltheapplicationFlickly.

Therealityishoweverthatwedon'talwayshavethescopetocreateper-deviceexperiences,sotoday'sapplicationwillattempttooptimizeforthedevicesorbrowsersmostlikelytoaccessit.It'sessentialthatthecontenttheapplicationexposesisreadilyavailabletoallusers,regardlessofwhatbrowsertheyareaccessingitfromsothisprinciplewillbekeptinmindduringthedesignphase.We'llbeaimingtocreateasolutionthat'sresponsivefromthestartandjQueryMobilesimplifiesalotoftheactualheavyliftinghereasitsupportsresponsivelayoutsoutofthebox(includingbrowsersthatdon'tsupportmediaqueries).

Let'simaginethathypothetically,ouranalyticsstatethat50%oftheuserswhocometoFlickraccessitviatheiPhone,10%viatheiPadandtheother40%viathedesktop.Ifwe'regoingtobuildawebapplicationcateredtomobile,itwouldthusmakesensetoensureiPhoneusersgetanoptimizedexperiencewithiPadandotherdevices(Blackberryetc.)gettinganexperiencethatlooksacceptable.

WithjQueryMobile,thismayjustmeanviewsbeingstretchedorwidenedalittlemore,buttheoverallapplicationwillstillremainfullyusable.

Inmyopinion,landing/firstscreenofamobileapplicationshouldfocusonencouragingengagementwithyourapporservice'sprimaryfunction.Keepitverysimple.WithFlickr,thisisrelativelystraight-forward-search.Oncetheprimaryfunctionhasbeenidentified,avoidthetemptationtoover-clutteryourUIforthesakeofmakingitlookmoreadvancedthanithasto.FlickrofferanumberofadvancedsearchoptionsthroughtheirAPIandIinitiallythoughtincludingsomeoftheseoptionsonthesamepagewouldmaketheuserfeelmoreempowered.

Theproblemwiththisassumptionisthatthemajorityofusersjustwantaneasywaytosearchforphotosusingkeywords.That'sallthefirstscreenneedstooffer-asearchbox.Iendedupmovingalloftheadvancedsearchoptionstoanoptionspage,butasit'sjustaclickaway,advanceduserslikelywon'tmindthesmallamountofextraworkrequiredtousethosefeatures.Afixed-positionnavigationtoolbarthatappearsontheverybottomeachviewensuresthatgettingtowhereyouneedtoseldomtakesmorethanatouch.

Thenextconcernwashowtodisplaysearchresults.Inmyfirstdraftoftheapplication,IthoughtthatmimickingFlickr'sdesktopexperience(theNxMimagegridfoundinresults)wouldgiveuserssomethingfamiliarthatthey'dbecomfortablewith.Wrongagain.Onamobiledevice,you'reusuallyconstrainedbylimiteddimensionsandshouldattempttoofferanexperiencethatworksbestforthisscenarioratherthanassumingonethatworkswellelsewhere.It'sadifferentparadigm.Thegridonmobilesufferedfromanumberofissueslikelackofreadablemeta-dataanddifficultyselecting(touching)imagesasthethumbnailswerequitesmall.

WhatIoptedforinsteadwasajQueryMobileListViewwhichwascreatedspecificallyforthepurposesofdisplayingalistofthumbnailswithadescription.Eachthumbnailisgivenit'sownrowonthescreenwithmeta-datapositionedtotherightandit'sextremelyeasytonavigatearound.ThejQueryMobiledesignteamreallythoughtthisthroughanditshows.Byusingheader-positionedpreviousandnextbuttons,Iwasthenabletogiveusersaneasywaytonavigatethroughpaginatedresultswithoutlimitingtheirexperienceinanyway.Asmentioned,eachoftheseresultpagescanbebookmarkedandmaintaininformationaboutthesearchtypeselected,pagenumberandkeywordschosen.

Anotherimportantviewwasthatforasinglephoto.TheFlickrAPIhasdistinctcallsrequiredforbothsearchresultsandsinglephotos(withfullmeta-data)soitmadesensetoofferaviewthatcouldprovidealloftheavailableinformationaboutanimagetotheuseriftheyclickedonaparticularsearchresult.Inthiscase,thedesktopexperiencewasn'toverlycomplex-displayalargeimagewithcaptionsbeneathit.PortingthisideaovertojQueryMobile,alargerimagewasdisplayedatthetopofaphotoviewwithaninlinelist(resemblinga1-columntable)forthemeta-data.Itwasclean,easytoreadandworkedwellonalldevices.

Ichosenottoaccessthecommentssectionofthereturneddata-setasinmyopinion,thiswouldhavelettofurtherviewsforuserprofiles,userpagesanditemsagain,outsidethescopeofthisproject.Togivetheuseranopportunitytoviewaricherexperienceiftheysochoose,eachsinglephotopagealsoincludesalinktoit'scorrespondingpageonFlickr.Thiswaytheuserdoesn'tfeeloverlylockedintotheapplicationiftheywanttoaccessacompleteviewoftheoriginaldata(withcommentsandthecompletesiteexperience).A'share'pagewithlinkstoposttheitemtoFacebookandTwitterwasalsoadded.

Anumberofotherpageswerecreatedasapartoftheapplication.Theseincludedabout,sourceandhelp.Unliketheotherviewsoftheapplicationthatwererequiredagreatdealofthought,eachofthesepagessimplyusedvariationsofthejQueryMobilelistoptionstorenderdatainareadable,rowformat.Youcanseescreenshotsofsomeofthesebelow,includingsamplesofhowtheotherpagesthatweremockedupmightlookinthefinalapplication.

Somedevelopersmayfindthedescriptionsofmythoughtprocessaroundthemobileapplicationminimalist,butIbelievethisisparamounttocreatinganexperiencethatemphasizesusabilityandappropriatenessfortheintendedaudience'sdeviceofchoiceoverotherfactors.It'sawayofthinkingthathasworkedformobileapplicationsI'vecreatedinthepast,howeverI'malwaysopentoconsideringotherapproachesifaccompaniedbyasoundargument:)

That'sitforPart1ofthistutorial.Ihopeit'scomeinuseful.InPart2,we'regoingtojumprightintosomeheavyBackbone.jsdevelopment,usingtheconceptsthathavebeenintroducedinthisfirstparttocreateafullyfunctionalmobilewebapplicationthatwillworkcross-browserandcross-device.Seeyouagainsoon!

AddyOsmaniisaUserInterfaceDeveloperfromLondon,England.Anavidblogger,hehasapassionforencouragingbestpracticesinclient-sidedevelopment,inparticularwithrespecttoJavaScriptandjQuery.HeenjoysevangelizingthelatteroftheseandisonthejQueryBugtriage,APIandfront-endteams.AddyworksatAOLwherehe'saJavaScriptdeveloperontheirEuropeanProducts&Innovationteam.

THE END
1.开源项目推荐:BlueRetroBlueRetro 是一个开源项目,旨在为各种复古游戏主机提供一个多玩家的蓝牙控制器适配器。该项目使用 C 和 Python 作为主要编程语言,通过硬件和软件的结合,实现蓝牙控制器与复古游戏主机的无缝连接。 项目基础介绍 BlueRetro 项目是一个开源硬件和软件结合的项目,适用于流行的 ESP32 芯片。它支持多种游戏主机,包括但不https://blog.csdn.net/gitblog_00364/article/details/144099485
2.StoryCreator,龙族至宝的Mod制作工具现在,让我们为你带来一个全新的可能——《龙族至宝:广茂之绿》的Mod制作工具 Story Creator。 通过它,你可以亲手编织属于自己的故事,让那些遗憾在你的笔下化为完美的结局。你将不再是故事的旁观者,而是创造者,将心中的梦想变为现实。 不仅如此,你还可以将自己精心打造的Mod分享给更多志同道合的梦友。让我们一起https://www.bilibili.com/read/cv39875258
3.节奏盒子sprunki芥末模组:想玩的可以去好游快爆伍分硬币 3730粉丝1视频 关注 节奏盒子sprunki-芥末模组:想玩的可以去好游快爆 1415次播放2024-11-30发布 相关推荐 评论0打开App观看 02:39 Italian President Mattarella visits the Summer 0次播放前天10:51 30:00 两场飓风搅动美国大选,救灾还是党争,这是一个问题 156次播放3天前 02:51 https://m.ixigua.com/video/7442859505903206951
4.手游模组添加全攻略,从入门到精通模组,就是游戏中的一种扩展内容,它可以增加新的游戏元素、改变游戏玩法甚至优化游戏体验,与电脑游戏模组类似,手游模组也是由玩家或者第三方开发者制作的,用于增强游戏体验的附加内容。 手游模组添加的准备工作 在添加手游模组前,我们需要做好以下准备工作: http://m.meifushop.com/mfjx/41511.html
5.RecipeBrowser模组Recipe Browser的详情 显示名称 合成表(Recipe Browser) 内部名称 RecipeBrowser 版本 v0.8.9.1 作者 jopojelly 文件 下载491.4 KB重新同步(Origin,Mirror 1,Mirror 7,Mirror 8,Mirror 9,Mirror 10) 更新时间 2 年前 描述 Use the "Toggle Recipe Browser" hotkey to show/hide the recipe browser. (http://mirror.sgkoi.dev/Mods/Details/RecipeBrowser
6.RecipeBrowser模组在创意工坊搜不到啊terraria吧9回复贴,共1页 <返回terraria吧Recipe Browser模组在创意工坊搜不到啊 只看楼主收藏回复 Death丶孤獨 窥视之眼 7 在哪能下载? 送TA礼物 来自Android客户端1楼2022-05-27 23:26回复 Japioca 小吧主 12 镜像站:网页链接 来自Android客户端2楼2022-05-27 23:27 收起回复 海https://tieba.baidu.com/p/7852538582
7.《泰拉瑞亚》辅助mod有哪些?辅助mod推荐一、Pinyin Search Support for Ricipe Browser(合成表拼音搜索) 让合成表(Recipe Browser)Mod支持拼音及拼音首字母搜索。比如查找“土块”,可以输入“tukuai”或者“tk”来搜索,输入“uk“也是可以的(tukuai的一部分)不用切换输入法,不用输入中文。真的非常好用 http://youxi120.com/showinfo-3-620-0.html
8.解放双手增加乐趣泰拉瑞亚灾厄进阶mod推荐三、recipe browser 和向导一个功能,主要用于合成查询,闲时还可以看看生物图鉴,搭配快捷键食用。 可能会有朋友觉得和魔法储存里的合成功能有些冲突,但我觉得配合快捷键这个更方便一些,另外还有额外的生物图鉴也是不错的选择。 需要什么一目了然 四、catalyst mod灾劫和灾劫汉化 https://api.xiaoheihe.cn/v3/bbs/app/api/web/share?link_id=107170229
9.泰拉瑞亚官方服务器有哪些模组游戏懂哥的动态另一个非常受欢迎的模组是"Calamity Mod",它增加了大量的新物品、敌人和挑战。你可以在泰拉瑞亚Mod浏览器中搜索并下载安装"Calamity Mod"模组。 还有一个很方便的模组是"Recipe Browser",它可以让你方便地查看和搜索配方。你可以在泰拉瑞亚Mod浏览器中搜索并下载"Recipe Browser"模组进行安装。 https://www.taptap.cn/moment/507634321910137027