developing a backbone.js edge

Upload: istvan-bodnar

Post on 31-Oct-2015

589 views

Category:

Documents


0 download

DESCRIPTION

Applications of an event-based javascript framework

TRANSCRIPT

  • Developing a Backbone.js Edge:Applications of an Event-Based JavaScript Framework

    Casey Foster, Aidan Feldman, David Tonge, Phil Freo and Tim Branyen

    BLEEDINGedgePRESS

  • DEVELOPING A BACKBONE.JS EDGE

  • Published : 2013-04-09 License : CC BY

  • TABLE OF CONTENTSCopyrightPrefaceChapter 1: The SetupChapter 2: Test Driven DevelopmentChapter 3: Backbone EventsChapter 4: ViewsChapter 5: ModelsChapter 6: CollectionsChapter 7: SyncChapter 8: RoutingChapter 9: Modules, Build Tools, & Preparing for Production

  • DEVELOPING A BACKBONE.JS EDGE

    By Casey Foster, Aidan Feldman, David Tonge, Phil Freo and Tim Branyen

    Bleeding Edge Press, 2013ISBN: 978-1-939902-01-6

    1

  • PREFACELet's cover the big picture before jumping into the first chapter...WHAT IS BACKBONE?Backbone is a JavaScript library designed to give structure to your web applications. Itprovides abstractions to manage your data, handle single-page navigation, and managerendering and events for components on your pages. Backbone gives you a robust, yetflexible framework to build your client-side JavaScript applications.WHO IS THIS BOOK FOR?This book is for beginner to intermediate JavaScript developers who are new toBackbone, or perhaps haven't created complex client-side JavaScript applications. BasicjQuery knowledge will be beneficial, but is not essential.This book is Backbone 1.0 compliant.In Chapter 1: The Setup we cover some basic programming best-practices, and thencontinue with gaining a full understanding of Backbone.WHY BACKBONE?Backbone brings structure to your code. It is an incredibly flexible library and is used inwidgets like Disqus, large content apps like USA Today and project management toolslike Trello. There is a clear trend away from static websites to dynamic web applications.From traditional software to HTML5 applications in the browser, Backbone gives you thebuilding blocks to develop these applications. WHY CAN'T I JUST DO EVERYTHING ON THE SERVER?

    The short answer is: because it's slow. Using Backbone allows you to provide a more fluiduser experience, compared to re-rendering every page and interaction on the server.Modern browsers have fast JavaScript engines and users are accustomed to JavaScript-based applications such as Gmail and Google Docs. WHY CAN'T I JUST USE JQUERY?

    jQuery alone is not enough. It provides a lot of functionality, but no structure. Backboneisn't a replacement for jQuery--it actually requires jQuery (or Zepto). Backbone bringsmodularity and best practices to your jQuery-based application. jQuery.ajax() isused by Backbone Sync to interact with REST APIs, for example, and jQuery's delegatedevents are used in Backbone Views to provide efficient, declarative event binding.WHAT IS MVC & MV*?

    MVC is an acronym for the Model, View, Controller design pattern. This pattern iscommonly used in server-side frameworks, for example: Ruby on Rails, Django, ZendFramework and ASP.NET MVC. The pattern encourages a clear separation between yourdata and how users interact with that data, while encouraging code to be split up intothese logical pieces. Backbone doesn't follow a strict MVC pattern, and is often referred toas a Model-View-Star (MV*) framework.You will find a more in-depth discussion to these principles in the Chapter 5: Modelschapter.

    2

  • WHY BACKBONE RATHER THAN FRAMEWORK X?

    Backbone doesn't force you into a particular coding style or paradigm. There is no "magic"happening below the surface: the source code is clear, readable and well commented. Backbone is also "lightweight" in the sense that it doesn't require a ton of buy-in to use. Itcan be easily integrated into an existing page, and you can choose to only use certaincomponents of the library (Views without Models or Collections, for example). Althoughthere are many frameworks that seem to be faster to get started with, Backbone's lack ofsurprises, clear documentation, and speed and flexibility make it a good fit for all types ofapplications. Backbone also has a large community of users who have produced all manner of pluginsto extend Backbone. If there is a particular feature from framework x that you wish was inBackbone, the chances are that it will already be implemented as a plugin. This barebones, modular approach to web development, yields great returns in the long run whencompared to being locked into a monolithic framework.The Backbone documentation has many examples of applications built with Backboneand some more answers to the question, "Why Backbone?"WHY THIS BOOK?While there are many online resources for learning Backbone, we felt that there wasspace for a full guide to the library. This book incorporates best practices and thetechniques from our combined experience of developing many Backbone applications.Some of the resources on the web advocate inefficient or inelegant solutions and there arefew that cover the whole library. In this book we aim to provide a complete guide toBackbone and equip you to start using the library straight away.We will cover all aspects of the Backbone library, so by the time you finish this book, youshould feel comfortable starting a Backbone project from scratch on your own. The trickything is understanding what solutions make sense for which problems, and while we doour best to point out gotchas and explain "best practices," we guarantee you willencounter problems that we haven't yet. Nothing outweighs experience.While writing this book we developed an example application called Hubbub to illustratethe various features of the library. Don't worry, it's not another "Todo App," rather it is aGitHub issue organizer. The application is more complex than most "example"applications and uses a real API (GitHub's). You'll have a chance to build the applicationas you go through the book.ABOUT THE AUTHORS

    Casey Foster is a Backbone.js contributor and web developer specializing in client andserver-side JavaScript. He holds a degree in Computer Science from California StateUniversity, Fullerton. He would like to thank his wife Lacey and puppy Gunner for all theirsupport in everything he does. He can be found on GitHub: github.com/caseywebdev.

    3

  • Aidan Feldman is a Backbone.js contributor and developer at Jux. He is also a facultymember at both General Assembly, and NYU School of Continuing and ProfessionalStudies, He is an organizer of Hacker Hours, which offers free office hours forprogramming help. His open-sorcery can be found on GitHub: github.com/afeld.

    Dave Tonge is a Backbone.js contributor and web developer at Simple Creativity.GitHub: github.com/davidgtonge.

    Phil Freo is a Backbone.js contributor working on Close.io at Elastic. He previously ledthe engineering team at Quizlet, building study tools that millions of students relied upon.He has also interned at Google and Yahoo! as a software engineer. He would like to thankhis co-authors. It was a pleasure working with such a talented group of guys, and the booksprint writing format was a unique experience. He would also like to thank his wife Kristinfor giving him time to write, and all the Backbone.js contributors who make the projectgreat. And finally, he would like to thank to all the readers! You can find Phil atPhilFreo.com or on Twitter @philfreo. He can also be found onGitHub: github.com/philfreo.

    4

  • Tim Branyen is a Backbone.js contributor and writes JavaScript at Matchbox. He is also aweekend Skydiver and maintainer of several open source projects. He can be found onGitHub: github.com/tbranyen.

    5

  • CHAPTER 1: THE SETUPIn this chapter we will be going over how to download our sample project and setup adevelopment environment so you can get started writing code. If you're alreadycomfortable with git, jQuery, Underscore.js, and debugging JavaScript in the browser, youmay want to skip parts of this chapter.YOUR EDITORWhen building a reasonably complex site, you will be spending most of your time in acode editor, so it's important you find one that works well for you. Some developers like afull-fledged IDE like WebStorm, while others use a lighter-weight GUI like SublimeText or Textmate, and yet others stick to a terminal-based editor like Vim or Emacs. Wepromise, though, that Notepad/TextEdit won't cut it, and we do recommend you choose aneditor that works for you.In case you are interested: authors Aidan and Casey use Sublime Text, Phil and Tim useVim and Dave uses IntelliJ.HUBBUBOver the course of this book, we're going to walk you through building a sample one-pageapplication with Backbone, called "Hubbub." The project is an organizer for GitHubissues, known to some as a Kanban board. The basic functionalty is this: you can searchfor code repositories hosted on GitHub (either owned by you or someone else), add themto your board, then categorize each issue as "ToDo", "In Progress" or "Done". You cancheck out the finished product here.Let's get you set up. If you are familiar with all the topics in this chapter, feel free to jump toChapter 2: Test Driven Development.GITDepending upon your development background, you may or may not have used a versioncontrol system. They are helpful for individuals to keep track of changes within theirproject over time, and they become essential when working in a team environment. Git iscurrently the most popular version control system, and that's what we'll be usingthroughout the Hubbub project.GitHub is a site to host and share Git repos. Before we even started writing the book, webuilt Hubbub collaboratively, but remotely, by saving our changes in Git and synchronizingthem through GitHub (which allowed us to keep track of the changes over time). Git has afeature called "branches," which allows you to have the same project in multiple contextsat once. We have a working version of the application in the complete branch, but wecreated a separate version (as the boilerplate branch) that includes just the basicsyou need to build Hubbub from scratch.To get your own personal copy ("fork") of the Hubbub project:

    1. Create an account on GitHub if you don't have one already.

    2. Visit the Hubbub repo and click in the upper right window. You nowhave your own personal version of Hubbub!

    Next, install Git. While you'll eventually want to get comfortable with Git on the commandline--and here's a great interactive tutorial for doing so --using GitHub's desktopapplication provides a nice GUI that masks a lot of the nitty-gritty. Use whichever you feelcomfortable with. Let's get the Hubbub project onto your computer:

    6

  • Command Line GitHub DesktopInstall Official Installers Mac or Windows

    Clone Hubbub (make sure to replaceyour GitHub USERNAME)

    git [email protected]:USERNAME/hubbub.gitcd hubbub

    Near the top left of yourHubbubfork page(https://github.com/USERNAME/hubbub), click

    (orWindows).After choosing a folder tosave the project in, you'llnowhave "Hubbub" listed under"My Repositories".

    Create your "master" branch git checkout -bmasterOn the "Branches" tab,click the "+" next to "boilerplate". Name that branch "master".

    Push your new branch up to GitHub git push -u originmasterNext to your new "master" branch, click"Publish."

    The internals of Git are complex, but when you're working on a project by yourself on asingle computer, there are really only a few commands you need to know. Inside theproject folder, you'll see a README.md file. Open that up in your editor. Add your namejust below the title:Hubbub======by SO-AND-SO.

    Every time you make a standalone modification to one or more files, you want to "commit"those changes to say that you've accomplished that step.

    Command Line GitHub Desktop Viewwhat'sbeenchanged

    git status Open the "Changes" tab.

    Committhechanges

    git add .git commit -am"added my nameto the README"git status

    Type "added my name tothe README" in the "Commitsummary" field and press"Commit."

    Use descriptive commitmessages to make yourrepo history easy tofollow.

    Send thechangesto GitHub

    git push Click "Sync"

    BOILERPLATE

    Open the index.html file at the base of the Hubbub project directory. This acts as theskeleton for our one-page application. We include a simple stylesheet (which includesTwitter Bootstrap) in the , just to provide a baseline of styling.Backbone has two dependencies: jQuery for doing DOM manipulation, andUnderscore for utility functions. We will discuss each in a moment.

    7

  • NOTE: Backbone also accepts a couple of alternatives for it's dependencies. Zepto can be used in place of jQuery, which drops support for older browsersin exchange for a much smaller filesize. Lo-Dash is a fully-compatiblereplacement for Underscore that emphasizes performance and customizability.If you want to support old browsers (e.g. Internet Explorer 6), you should alsoinclude json2.js.

    We also included marked.js, which we will use to parse the Markdown content of GitHubIssues. You'll notice that is fairly empty, and it will remain that way, because we'lluse JavaScript (via Backbone and templates) to create the markup. More about that iscovered in Chapter 4: Views.JQUERYjQuery is the most popular library in use today, in JavaScript or otherwise. jQuerynormalizes browser inconsistencies for common operations like accessing elements in theDOM, doing requests to a server via AJAX, crafting animations, and so on. For example:// when they hover over the menu button$('.menuButton').hover(function(){ // slide the menu down while fading it in $('.menu').slideDown('fast').fadeIn('slow');});

    At their simplest, jQuery DOM operations consist of a selector passed to the $() - a.k.a.jQuery() - function, with one or more method calls chained together after it. Runningthe $() function with a selector returns a jQuery object, which refers to a set of zero ormore elements on the page. If you save this object to a variable, it's a common conventionto prefix that variable name with a dollar-sign:var $currentItem = $('.navBar .current');$currentItem.hide();

    jQuery is used throughout the Backbone.View class internally, and we will be using itdirectly later in Chapter 4: Views.UNDERSCOREJeremy Ashkenas created Backbone when he was part of DocumentCloud, but he alsowrote a more general-purpose "utility-belt library" called Underscore, which Backbonedepends upon. Underscore is meant to provide functions that are commonly used inJavaScript applications. If you're coming from another programming language, you canthink of Underscore as the "standard library" JavaScript never had. Some methods like_.each() and _.size() normalize methods available between Arrays, Arguments andObjects, while providing a "shim" in some places where the method is only nativelyavailable in newer browsers:var lastNames = _.pluck([ {first: 'Arnold', last: 'Schwarzenegger'},

    {first: 'Wesley', last: 'Snipes'}, {first: 'Sylvester', last: 'Stallone'}], 'last');

    // find the longest last name_.max(lastNames, function return lastName.length;});=> 'Schwarzenegger'

    Others like _.debounce() come in handy for doing more complex time-based operations:var $button = $('.myButton');

    8

  • // wait 100ms after they stop clicking$button.on('click', _.debounce(function () { console.log('Ok ok, we get it...');}, 100));

    // simulate a trigger-happy user$button.click().click().click();

    Take some time to look through the documentation at what is included. If you're feelingreally ambitious, you can also check out the well-annotated source code.THE BROWSERWhile we know you use a web browser all the time, doing serious client-side developmentrequires a deep understanding of the browser as both a platform and a tool. We highlyrecommend using Google Chrome or Mozilla Firefox, as they have the strongestdeveloper tools. Open up the completed Hubbub site, then fire up the developer tools:

    Chrome Firebug Tips Install thedevelopertools

    (preinstalled) Install Firebug

    Open themup

    Click the Chromemenu ( ) on thefar right end of thetoolbar. Under Tools,click DeveloperTools.

    Click the Firebugbutton ( ) at thefar right of your toolbar.

    See all ofthedownloadedassets

    Click the Networktab, then refresh thepage.

    Click the Net tab, thenrefresh the page.

    It's useful to check if aparticular file is being loaded,how big it is, etc. Click anindividual row to get moredetails.

    Inspect theDOM

    Click the Elementstab. Click the HTML tab.

    This view allows you to seethe rendered markup, andselecting an element allowsyou to play with the CSS andsee the changes live.

    Open theprimaryJavaScriptfile

    Click the Sourcestab, then click the tiny

    arrow icon ( ) inthe top left and selectindex.js.

    Click the Script tab,then the dropdown onthe left of thesecondary bar of thedebugger, and selectindex.js.

    While many JavaScript developers simply use console.log() to debug, being able tostop execution of the code and inspect the state of your application is very useful. To start,click the line number next to Backbone.History.start() to set a breakpoint. Youwill see a marker appear on (or next to) the number. Refresh the page, and you shouldsee that line highlighted. This means the page has been paused, and this line is about torun.

    9

  • You can hover over variables to see their values, and jump around the Call Stack ("Stack"in Firebug) by clicking the different function names in the right sidebar. (A "call stack" is alist of what methods have been executed to lead to the current line. You can think of theseas JavaScript "breadcrumbs.")

    Chrome Firebug Continue

    Resume execution of the code, until it hits the nextbreakpoint, or indefinitely

    Step Into If the current line has a function call, jump into that functionStepOver

    If the current line has a function call, execute it and stop onthe next line

    Step Out Finish execution of the current function, and stop when itreturns to the calling function

    Lastly, the Console tab allows you to execute arbitrary JavaScript on the page, and ifyou're paused in the debugger, it executes in the current context. Try some jQuery: $('body').css('background-color', 'yellow');

    The Console will also display any JavaScript errors that occur in the page, so it's useful tokeep this window open during development.Now that we've introduced the projects, tools and libraries you'll be using, let's get intosome specifics about Backbone.

    10

  • CHAPTER 2: TEST DRIVEN DEVELOPMENTImagine you are in charge of building an airplane. Would you simply create all of thevarious airplane parts, put them together, and then hope that the plane flies? Of coursenot! You'd set up strict testing requirements and quality assurance guidelines for each partindividually as well as the integration of the parts. Think about software in the same way:each part must be tested, both separately, and together, to know if it works properly.Most people start out by testing their code manually. This approach usually means writingsome JavaScript, loading it into the browser, and manually trying out the feature to see if itruns without error and does what you expect. If it works, you're happy and move on. If itdoesn't then you keep trying new things until it does.THE PROBLEMThe problem with this typical manual testing approach is that a feature that works properlytoday may accidentally break when you add or change more code later. Often times youmay break something that you didn't even know was related to what you were working on.The larger an application gets, the more things can go wrong, and it becomes increasinglydifficult to manually test all of the parts of your application.When you originally develop a feature, you may know all the little edge cases to test out.But can you guarantee you'll remember in six or twelve months when you need to tweakthe feature? Or perhaps even more importantly: do you trust that all of your current andfuture teammates will also remember to properly test all of those edge cases? THE SOLUTIONThe solution is to add in programmatic testing. Unit tests are little pieces of code that test aspecific part of your application code. Rather than manually clicking around in yourapplication to test some code, you write test code, to test it for you. The tests are usuallykept in source control alongside your application code itself, and should run (making sureall tests pass) before a version of the software is considered ready for production.OUR APPROACH

    You'll notice we sprinkle testing throughout the entire book, rather than dumping testinginto a chapter at the end. The reason for this is twofold:

    1. Test-driven Development (TDD)

    It makes more sense to write testing code before or during writing functional code, ratherthan after, so that your tests actually help you complete your task rather than being achore. There are entire books written on Test-driven Development, but the basic idea issimple:

    1. Write new tests that fail initially, but that demonstrate how a new feature or bug fixshould work once completed.

    2. Write your code so that all tests (existing and new) pass.3. Rinse and repeat!

    If you can get in a habit of testing in this manner, your project's codebase will be muchmore robust and unlikely to break in future releases.

    2. Clarity and Understanding

    Rather than show you some Backbone.js code and then try to explain how it works andwhat its effect is, a unit test can explicitly prove what code does. It's the difference betweendescribing what should happen and showing exactly what does happen.

    11

  • Consider this approach in your own programming as well, especially when you're trying tofix a hard-to-track-down bug. When it seems like everything should be working but isn't,what we like to do is start writing tests to prove that certain parts are behaving as weexpect them to. A lot of times we discover that a part of our application that we thoughtworked properly doesn't.At other times, you may discover that an underlying framework (such as Backbone.js)either has a bug or doesn't work exactly how you thought it did. This providesopportunities to "give back" to the Backbone project by creating a Pull Request on GitHubshowing exactly how to reproduce the problem, and ideally a way to fix the bug itself. Andthe best part is, by contributing a unit test with the use case you care about, you can feelmore confident knowing that this behavior won't accidentally change in a future version ofthe project.Written tests also help when working with a team, or even when asking for help online onStackOverflow, in mailing lists, or in GitHub Issues. When you can write a straightforwardunit test showing what a problem is, then you've given others a reproducible way to seethe problem and understand it in as little code as possible. We groan every time we see aquestion online with way too much (irrelevant) information and no easy way to reproducea version of the problem. But when you see somebody show up with a unit testdemonstrating their exact problem, it becomes much easier to a) fix a bug in theunderlying code they are relying on, or b) see what they are doing wrong themselves.WRITING TESTSWe are using QUnit as a testing framework in this book and in the example code. We'vechosen QUnit because it's very popular and powerful, and because the tests forBackbone.js itself use QUnit. A test can be very simple. Here's a test that simply demonstrates that JavaScript allowsyou to get the length of a list of numbers. Let's make a file to hold our general tests.// test/generic.js window.jQuery(function(){ module('Generic'); test("Length of an array", function() { var myList = [1, 3, 5, 7]; equal(myList.length, 4, 'An array should have a .length attribute'); });});

    // test/index.html

    ...

    Then, just open test/index.html in your browser to run it. As you can see, testingcan be easy. QUnit just assumes that you wrap your test functions in a function calledtest() and give them a name, and that you use special assertion functions likeequal(), notEqual(), and ok() to check if code is working properly.You'll learn more about Backbone models in Chapter 5: Models, but here's an examplethat will show both the basics of how a model can work to store data and an example ofanother test. Add this directly under the last test:// test/generic.jstest("A Backbone Model should store data", function() {

    12

  • // Create an instance of a generic Backbone Model. var model = new Backbone.Model();

    // Store some data in it. model.set('first_name', 'John'); model.set('last_name', 'Smith');

    // Test that we can retrieve the data from it correctly. equal(model.get('first_name'), 'John', 'can retrieve first_name correctly'); equal(model.get('last_name'), 'Smith', 'can retrieve last_name correctly');});

    Once you understand the basics of testing, you'll want to be writing unit tests that test yourown application's code, rather than testing JavaScript Arrays or the basics of Backbone.RUNNING TESTSJavaScript tests can be run very easily from the browser. In our project, just opentest/index.html in your web browser and you'll see a nicely formatted page running thetests. You can switch to the complete git branch to see the tests for the completed Hubbubproject, or just view them online here.If all of the tests passed, you'll see a green strip at the top of the page, as shown in thisfigure:

    If any tests are failing, you'll see a red strip with details about the failing test, as shown13

  • If any tests are failing, you'll see a red strip with details about the failing test, as shownhere:

    As you write code for your application, be sure to check your tests frequently.To get more comfortable with reading and writing JavaScript unit tests, just open uptest/index.js in a code editor. You can read through some of the existing Hubbub tests(don't worry if they don't make a lot of sense yet; they will make more sense by the end ofthe book). You can also practice writing your own tests. To start, write out the "Length ofan array" test above. Then re-run the tests in your web browser to see your new test pass.BEST PRACTICES

    There are an abundance of books on how to write good software tests, but here are a fewbest practices to get you started:

    Make each test independent from others. Testing frameworks have a way toexplicitly define what set-up work a test needs (see how in QUnit), so make sureyou're not expecting one test to do anything that another test requires in order topass. This means your unit tests should be able to be run in any order.A unit test should test as little code as possible. While there are advantages towriting integration tests that test your overall application, the most common type oftest should be testing one specific module of code and focusing only on that onepiece.A test should be as short as possible and have one purpose. Define one purpose for

    14

  • your test and don't add in a bunch of unrelated assertions. BACKBONE'S OWN TESTS

    A great way to learn Backbone.js in more depth is to look at the tests for the library itself.They can be viewed on GitHub, and run on the Backbone site. If you have a questionabout the specifics of how Backbone.js works in some detailed case where thedocumentation isn't clear, just write a test to prove to yourself one way or another.Here's one little example. How does Backbone's Collection#slice method work?We could read the documentation of course, but here is a test for it:// test/generic.jstest("slice", 2, function() { var col = newBackbone.Collection([{a: 'a'}, {b: 'b'}, {c: 'c'}]); var array = col.slice(1, 3); equal(array.length, 2); equal(array[0].get('b'), 'b');});

    ADVANCED TESTING TIPSOnce you're comfortable with writing and running tests for your application, you'll want tolearn about more advanced testing methods. Here are a few resources to help. (But don'tworry about these yet, if you're just starting out with testing.)Running Tests: You can use a tool like PhantomJS to run your tests (and see if theypass) from the command line. This allows you to easily integrate with continuousintegration frameworks. You can use a service like CircleCi or Travis CI in such a way thatevery time you commit new code, it is automatically tested, and then deployed to aproduction server if the tests pass.Mocks, Spies, and Stubs: Sinon.JS is also helpful in testing (with QUnit or by itself) inmany ways, but especially in testing code that relies on communicating with a server viaAJAX/XMLHttpRequest. You can mock out server responses with a fake server to test thatyour response handling code is correct, and you can test that the AJAX requests yourcode is generating is correct. See an example here of using these concepts to test how aBackbone Model syncs with a server.Other Testing Frameworks: Our project and Backbone.js itself uses QUnit as a testingframework, but you may want to check out alternative testing frameworkslike Jasmine and Mocha. CONCLUSION

    The most important unit test in your application is its very first one. Once you're past thefear that a lot of beginners have regarding testing and have a few example tests for yourapplication in place, the rest is relatively easy. If your experience is anything like ours hasbeen, it will make your life much easier later to help keep things from accidentallybreaking. In the next chapter it is time to learn about Events in Backbone.

    15

  • CHAPTER 3: BACKBONE EVENTSNow that we have covered how to test our code, let's get started by looking at Backboneevents. With functions as first class members, JavaScript is a great language for taking fulladvantage of events. If you've ever responded to a user click or keyboard event with someJavaScript code that was waiting for it, you've already been using events. An event issimply a way of letting another part of the program know that something has happened.Lucky for us, Backbone provides a sleek, fast API for binding callbacks to events. Evenbetter, this API has been extended onto all Model, Collection, View, and Routerinstances, as well as the Backbone object itself.This section will go over the Events API and then give some specific examples of howHubbub uses events.APIThe Backbone.Events object comes equipped with seven methods that allow you toinvoke a specific function when an event occurs, trigger an event, or stop waiting for anevent to occur. We will go over the signature and purpose of each method along with anexample of when it's most appropriately used.ON(NAME, CALLBACK[, CONTEXT])

    The on method is used to bind a callback to an event. In other words, you're saying youwant a function (callback, with an optional context) to be invoked when (on) an event(name) is triggered. on is best used when you want the context of your callbackto be the object triggering the event.Here are the arguments for on:

    name - The name of the event to listen to.callback - The function to invoke when the name event is triggered.context (optional) - The context the callback should be called with. In other words,context refers to the object that will be represented by this inside the function. Ifnot supplied, the default context will be the object that triggered the event.

    As an example, if you're using localStorage for persistance like we do in Hubbub, youmay wish to save a model every time it changes. Without Events, you'll have toremember to call model.save() after every model.set(attributes). That's notfun, and Backbone is all about fun!Instead, open index.html in your browser and try this in your Console: var model = new Backbone.Model();model.on('change', function () { console.log("time to save the model!");});model.set('name', 'Gunner'); // "time to save the model!"

    See how well it reads? When model changes, save it.The name 'all' is a special-cased event. When you bind a callback to 'all' it willbe invoked on all of that object's triggers with the name of the event fired as the firstargument. For example:model.on('all', function (name, a, b, c) { // On the first trigger, name is 'event', a === 1, b === 2, c === 3 // On the second trigger, name is 'event2', a === 4, b === 5, c === 6 console.log(arguments);});model.trigger('event', 1, 2, 3);model.trigger('event2', 4, 5, 6);

    16

  • The 'all' event is used internally in Collections to proxy Model events.You could use on() for all of your event bindings, but you might get bit by memory leaks,which we'll explain soon. As a general rule, on() is best used when you want thecontext of callback to be the object you're binding to and triggering from.ONCE(NAME, CALLBACK[, CONTEXT])

    once works identically to on, except that the event will be automatically unbound fromthe object as soon as it is called. It gives you an easy way to bind to an event for just oneoccurrence, and then forget about it. A custom event like a modal view 'close' is a gooduse case for once, because generally a modal view will be removed once it is closed, sofurther listening would be pointless.model.once('resolve', function () { alert('Tears of joy, our issue has been resolved!');}); model.trigger('resolve'); // alerted!model.trigger('resolve'); // no alert.

    OFF([NAME][, CALLBACK][, CONTEXT])

    What do you do when you need a callback to stop firing? Easy, just turn it off. It'simportant to turn off callbacks you aren't going to be using anymore, since lingeringcallbacks (zombie events) can lead to memory leaks. Here's an example of a memory leakusing on:model.on('eventName', callback, view);view.remove();

    Now it's clear that we're done with view since we've removed it. However, model knowsnothing about view's removal and will continue firing callback with view's contextevery time 'event' is triggered. How can we fix this problem? Use off to reverse theeffect of on:model.off('eventName', callback, view);

    Now the callback has been removed and the potential memory leak has been thwarted.off can take any combination (or none) of the same arguments you feed to on. If noarguments are passed, every event listener is destroyed.Turning off the special case 'all' event works the same way. To turn off a callbacklistening to all events, simply:obj.off('all', callback);

    LISTENTO(OBJ, NAME, CALLBACK)

    listenTo is what can be referred to as an inversion-of-control method. It takes on's joband puts it in the hands of the listening object, rather than the triggering one. Thedifference between on and listenTo is best seen in an example.These two methods of binding an object to another object's event are functionally thesame:objA.on('eventName', callback, objB);objB.listenTo(objA, 'eventName', callback);

    The difference is when it comes time to turn off these callbacks. More on that in thestopListening section. As a general rule, use listenTo when the context of yourcallback should be the object you're listeningTo with. This is often the case forviews where you want the view to listen to a model or collection, but stop listeningwhen the view is destroyed to prevent memory leaks.

    17

  • LISTENTOONCE(OBJ, NAME, CALLBACK)

    As you would expect, listenToOnce is to listenTo as once is to on.STOPLISTENING([OBJ][, NAME][, CALLBACK])

    stopListening is what makes listenTo shine. Internally, listenTo keeps track ofthe objects the listening object has been listening to. This comes in handy when it comestime to unbind events. Here is a common case with views:modelA.on('change', view.renderA, view);modelB.on('change', view.renderB, view);modelC.on('change', view.renderC, view);

    Now the only way to turn off these views would be like so:modelA.off('change', view.renderA, view);modelB.off('change', view.renderB, view);modelC.off('change', view.renderC, view);

    Ugh...tedious. Let's try the same thing with listenTo and stopListening:view.listenTo(modelA, 'change', view.render);view.listenTo(modelB, 'change', view.render);view.listenTo(modelC, 'change', view.render);

    Now, since listenTo has been keeping track of objects being listened to (in this casemodelA, modelB, and modelC), all bindings can be undone with a singlestopListening:view.stopListening();

    stopListening is automatically called by a view when it is removed, so normally inthe case of views you don't have to worry about doing this yourself.stopListening can also be passed any combination (or none) of the same argumentslistenTo (or listenToOnce) receives to turn off callbacks accordingly.TRIGGER(NAME[, ARGUMENTS...])

    Now that you have all your callbacks ready to listen, trigger gives you the power tobe heard. Internally, Backbone triggers about a dozen built-in events on instances ofModels, Collections, and Routers. You saw one of them in the first on example,'change'. If you look through the Backbone.js source you'll notice trigger pops up alot.The arguments trigger takes look like this:

    name - The name of the event to trigger.arguments... - Any additional arguments will be passed as arguments to thelistening callback functions.

    What's great about the Events API, though, is that you are free to trigger whatever kind ofevent you want to listen to. Maybe you want to do something before a model is saved. Youcould do something like this:collection.trigger('sort:before', collection, 'hello');collection.sort();

    Now any object that was listening for model's 'sort:before' event will have itscallback invoked with the collection and 'hello' arguments.A more advanced way of doing this would be to override the sort method to trigger

    18

  • 'before-sort'.// Create a closure so we can use a local variable to store the original// `sort` method.(function () { var sort = Backbone.Collection.prototype.sort; Backbone.Collection.prototype.sort = function (options) { if (!options.silent) this.trigger('sort:before', this, options); return sort.apply(this, arguments); };})();

    Now that you have that set up, you can forget worrying about triggering 'sort:before'before you sort, and just do it.collection.on('sort:before', beforeCallback);collection.on('sort', afterCallback);collection.sort(); // `beforeCallback` invoked then `afterCallback` invoked

    MORE EXAMPLESIf you'd like to see more examples of on, off, trigger, listenTo and the other eventmethods check out the Backbone events test file. There are plenty of assertions there thatwill help you understand events better and also may give you some ideas to help yourtesting.EXTENSIBILITY

    The Backbone.Events object was designed to be extendable onto any object. Forexample, using _.extend() you can do something like this:var objA = _.extend({ name: 'Gunner', cheer: function () { alert(this.name + " cheers on `objB`'s dancing!"); } }, Backbone.Events);var objB = _.extend({ dance: function () { this.trigger('dance'); }}, Backbone.Events);objA.listenTo(objB, 'dance', objA.cheer);objB.dance();

    You can even extract just the Events code from backbone.js for your own project ifyou don't need the other features Backbone provides.BONUS FEATURES

    Backbone's Events API is sprinkled with some nice syntactic sugar to make your lifeeasier. The first one we'll go over is method chaining. This allows you to bind or trigger anevent and continue calling methods on the binding object in that same expression. We'lluse our earlier on example as, well, an example:model.on('change', function () { this.save();});model.set('name', 'Gunner'); // `model` is saved!

    You have a few options for binding names to callbacks. So far we've only used thestandard single name to single callback style, but there are a couple other ways youcan use any of the Event methods to be more terse.

    19

  • Sometimes you want a certain callback to be fired when either of two events aretriggered:obj.on('eventA eventB', callback);obj.trigger('eventA'); // `callback` fired!obj.trigger('eventB'); // `callback` fired!

    It's also not uncommon to want to bind multiple events with multiple correspondingcallbacks all at once. This is possible when you pass an event map as the firstargument of the function:obj.on({ eventA: callbackA, eventB: callbackB});

    The same rule with space delimited events also applies to event maps, so you can reallyget creative:obj.on({ 'eventA eventB': callback1, 'eventA eventC': callback2});obj.trigger('eventA eventB eventC');// This will invoke, in this order:// `callback1`, `callback2`, `callback1`, `callback2`

    And the granddaddy of Events API examples:obj.on({ 'eventA eventB': callback1, 'eventA eventC': callback2});obj.trigger({ eventA: 'hello', 'eventB eventC': null});// And the result is, in this order:// callback1('hello'), callback2('hello'), callback1(null), callback2(null)

    This is a contrived example, but nevertheless, the features are there for your consumption.Keep in mind this API is consistent throughout the events. Use a space delimited string tobind multiple events to a callback, or get even fancier and pass in an event map tolistenTo an object with multiple events and multiple callbacks.EVENTS IN HUBBUBIf you browse the Hubbub source, you'll notice we use events extensively. Rather thanpoint out each individual place events are used (which would take its own book), we'llfocus on a distinct block of code in the Board model's initialize method.The first event we hook onto is 'change'. You can probably already tell from reading it,but the board (this) will save on 'change's.this.on('change', function () { this.save(); });

    The next part is a bit trickier. We use listenTo here because we want the callback'scontext to be our board (this). Then we pass an event map of event names as thekeys and corresponding callbacks as the values.this.listenTo(this.repos, {

    We've given board an issues Collection that we'd like to store all issues in. It's

    20

  • easier to filter the issues on the kanban when we can pull them all from the samecollection. To do this, we'll want to listen for new repos being added to the reposcollection, and in turn add that repo's issues to our master issues collection. But that'snot all. We also want to set up another hook for this new repo that ensures any newissues that are added to it, as we have added to our master issues collection. Finally,save the board so we can remember the newly added repo: add: function (repo) { this.issues.add(repo.issues.models); this.issues.listenTo(repo.issues, 'add', this.issues.add); this.save(); },

    Here is where stopListening comes into play to counter our listenTo in the addevent. When a repo is removed, we need to tell the board's issues to stop waiting forthe repo to add new issues. Obviously if the repo is being removed from our board,that case is no longer relevent. If the repo has been destroyed, failing tostopListening will result in a repo object that can never be de-referenced, eventhough it's no longer used (memory leak). And lastly, as with add, be sure to save theboard so that the removed repo no longer shows on our kanban: remove: function (repo) { this.issues.stopListening(repo.issues, 'add', this.issues.add); this.save(); }});

    The last two callbacks we hook up are to automatically save (to localStorage) newrepos and issues when they are added or changed. For example, when you change thestate of an issue from "doing" to "done," you immediately save the issue's new statewhen that 'change' event is triggered.this.repos.on('add change', function (repo) { repo.save(); });this.issues.on('add change', function (issue) { issue.save(); });

    As a credit to Backbone, we didn't find the need to trigger any custom events inHubbub. This isn't the case for all applications, and larger applications can actually makegreat use out of this feature.CONCLUSION

    Backbone's event system is a powerful tool that you will come to know and love as youbegin writing Backbone applications. Here are a few things to remember as you dive inwith Events:

    Use the right tool for the job.In general, on is best used when you need the context of the callback to be theobject that is triggering the event. listenTo is best used when you need thecontext of the callback to be the object that is listening to the event.Don't leak precious memory.When a browser page is refreshed, the JavaScript memory from the previous page iswiped. In a single-page Backbone application, you don't have this convenience somemory management becomes important. If you use on, once, listenTo, orlistenToOnce, you have added a callback to an internal array that will staythere until (a) the object is dereferenced in JavaScript or (b) you call off orstopListening accordingly. Keep this information in mind and clean up yourevents. A memory leak may not be noticable at first, but too much wasted memorywill eventually have noticable, detrimental effects to your application, especially if it'slong-running.

    21

  • Learn the events that Backbone triggers by default.As mentioned earlier, there are only about a dozen built-in events and they will beextremely helpful in establishing a logical flow for your application.

    In the next chapter we will examine Views in Backbone, and how we used them in ourHubbub application.

    22

  • CHAPTER 4: VIEWSA View encapsulates a visual region of a web application and constrains all plugins,events, interactivity, and logic inside to this region. Views allow you to take advantage ofreusable components and provide a more modular infrastructure.Views are often managed within a Router or a parent View and can either be long livedlike a persistant shopping cart in the header, for example, or short lived like the items inthe cart as you expand it.PURPOSEWhen working with regions of a web application, it is often desirable to isolate andcontextualize logic and rendering. Imagine a shopping cart web application that has aninterface that positions navigation at the top, categories on the left, and the shopping carton the right. The shopping cart is composed of details about each item.You can think of breaking down this application into the following views:

    Each view represents a unique or repeatable region in the application. By representingour application in this manner, we are able to adopt patterns and best practices that haveexisted in application development for decades. Since Views are visual representations ofdata, there is no querying the DOM to determine what the state is. The idea here is thatyou do logic and calculations (shopping cart totals, position, etc.) within the View andrender that.Views should not know anything about business logic, and instead focus solely onpresentation. They should remain independent and not depend upon any other views tofunction. Views should also not leak any events outside of a specific region.Backbone intentionally keeps views minimalistic, since there are many opinionated waysof using events, and developers have diverged to embracing different methods. Thefollowing section describes in detail how to create powerful views from scratch, but it alsoprovides insight on refactoring using the LayoutManager plugin (as well asrecommendations for other view management plugins).BASIC USAGEExtending and initializing Views is surprisingly simple, but there are inconsistencies inviews that are not prevalent in Models, Collections, or Routers that you will need to beaware of. These inconsistencies are most prevalent with assigning properties and figuringout how to access them. There are also properties that change from their original value toa new value. We'll talk about these as they come up in examples.

    23

  • You will find that Views are not as fleshed out as the other classes, in terms of functionalityand opinions. Views rely heavily on the DOM Library.EXTENDING A VIEW

    As with other Backbone classes, you are encouraged to extend the classes into your owncustom implementations. This is a useful practice when you will create more than oneinstance of the same view.We define our custom view by extending from Backbone.View:var UserView = Backbone.View.extend(properties, [classProperties]);

    This will create a unique class that is a derivation of a view. Neither the properties orclassProperties object are required, but its as rare to see a view without propertiesas a view having classProperties.Examine the Hubbub IssueHolderView as a good View example, and write thefollowing:var IssueHolderView = Backbone.View.extend({ className: 'issue-list', initialize: function(options) { this.options.collection = new app.Issue.Collection(); this.options.collection.setFilter(app.board.issues, options.testKey, options.testValue); }, template: _.template($('#js-issue-list-template').html()), render: function () { this.$el.html(this.template({ title: this.options.title })); this.listView = new app.IssueListView(_.extend({}, this.options, { modelView: app.ItemView, el: this.$('ul') })); return this; }});

    CREATING A VIEW INSTANCE

    A definition of a View is only a prototype that is later used to initialize an instance. Theproperties defined in the properties object will be available on every created instance.For example:// Create an instance of the previously extended View.var issueHolder = new IssueHolderView();// Render the viewissueHolder.render();

    EXAMINING THE VIEW INSTANCE

    Once you've created an instance of a View, you can use your debugger in the browser toinspect the structure. At their essence, view instances are simply wrappers around a DOMNode/jQuery collection. Views must always have a root element to allow eventdelegation, provide a location to put template contents, and give you an end point to attachthe View into the page Document.To inspect, open the Elements/DOM tab of your Developer Tools (see Chapter 1: TheSetup). Every View instance will have a cid property that uniquely identifies it on theclient and is used for event delegation. This is automatically handled by Backbone.

    24

  • SPECIAL PROPERTIES

    Backbone Views have a list of special properties that will be hijacked and purposed. Beaware that you should not try to assign arbitrary meaning to the following: model,collection, el, id, attributes, className, tagName, or events.

    The model and collection properties are designed to have Backbone.Modeland Backbone.Collection instances assigned to them for the View to render a visualrepresentation of their value.The el, id, attributes, className, and tagName properties will control howyour View's internal element is generated if you dont pass one upon instantiation.The events property is a declarative way of assigning DOM events based off eventtypes, selectors, and method names.Technically, any property that is not one of these special identifiers, will end up inthe options object described later on. We will see how this is not respected in allcases and should not always be relied upon.

    el

    The el property is always an element after you create your instance. During the definitionyou can pass the el property a jQuery collection, a selector, or a DOM Node. Backbonewill be smart enough to detect the type and normalize into a single DOM element.The $el property will always be the jQuery sidekick of el, saving you numerous calls to$(this.el).If el is not passed the element will becreated for you.

    options

    Any key passed during instantiation that is not in the list of special keys will be put in theoptions object. This is inconsistent from how Models, Collections, and Routers handleoptions that are only available during constructor and initialize methods.A good way to patch initialize to override your list of instance properties is by using someunderscore methods to filter values from the options object like this:var MyView = Backbone.View.extend({ template: _.template("Hello world!"), initialize : function(options) { _.extend(this, _.pick(options, ['template', 'whitelistedkey'])); }});

    When you pass options to the View, they end up in options unless they are special,since all properties defined on a View are on the instance.TEMPLATINGWhile not explicitly part of the Backbone.View API, templates are fundamental to agood abstraction of logic and presentation. The term can apply to many varied types andrepresentations of the string contents.Consider: function() { return "Hello world!"; }and: Hello world! They both represent "Hello world!", but in different ways. Therefore when talking about templates, we mean anything that is or can produce the HTML contents.

    25

  • INLINE

    Typically the template property on a View is a function that accepts an object that is theninterpolated into text and returned as a string.In the following example, a template is assigned to the View inline. This means that thetemplate exists within the JavaScript source code:// Defining a new view that renders "Hello world!".var IssueModalView = Backbone.View.extend({ // Compile a String template into a reusable Function. template: _.template("")});

    This approach may be seen as less than ideal, since you have: no syntax highlighting,very long lines, it blurs the lines of logic and presentation (potentially making it harder fordesigners), and it is not trivial to pre-compile these templates for production.SCRIPT TAG HACK

    A better way of handling templates is to place them all within your HTML markup, inside of tags that have a non-JavaScript type attribute on them whichwill cause the browser to not execute the contents. This is a hack, which has gainedmainstream endorsement, especially for example applications. This allows developers toquickly define templates outside of their JavaScript, but they can access them very easily.To update the above example to use this new practice, we need to break out the templateand then reference it within the View definition.First write the markup:

    And then modify the View:var MyView = Backbone.View.extend({ // Fetch the text contents of the template from the script tag. template: _.template($(".my-template").html())});

    This approach isn't ideal since it's a lot of repetitive code. Ideally we can just provide theselector and deal with the actual compilation of the template function inside the renderfunction when we need it.This is a DRY-er (Don't Repeat Yourself) way of representing the same code above.var MyView = Backbone.View.extend({ // Assign the selector to fetch the template contents from. template: ".my-template", // This method can be used by the render method to fetch the template, // before using it. It's only job is to return a compiled template. fetchTemplate: function() { return _.template($(this.template).html()); }});

    For the example application that accompanies this book we chose this implementation oftemplate organization, since it is the easiest way to consume and modify templates. It isgreat for getting started and does not require any dependencies to run.This is still not a perfect solution. There is missing syntax highlighting, your HTML is26

  • bloated, and it can be confusing since it is a browser hack (storing templates in script tagsmeant for JavaScript), and it's not easy to pre-compile these templates.How to pre-compile templates is explained in Chapter 9: Modules, Build Tools, &Preparing for Production. For the purposes of this chapter, pre-compiling templates can beunderstood as a method of compiling the string to a function before the application runs toavoid extraneous processing. This is problematic for the above two solutions since theraw templates exist in the markup and source.WORKING WITH DATA

    Once your template has been set up to accept data and return string markup, you will needto come up with a way to provide data to the template. This will almost always be trivial.INSTANCES OR PRIMITIVES

    Depending on your template engine, you may wish to work with the JavaScript objectinstances themselves. You may want flat valid JSON objects instead, for engines likeMustache and Handlebars.Here is an example of instances being used in underscore templates: // Passing the datathis.template({ searchResults: this.collection });

    You can see how you are writing JavaScript directly into the template. ThesearchResults will often be data from a Backbone.Collection, and you may wantto use the toJSON method which will flatten all its Backbone.Model instances down tosimple JavaScript objects:_.template({ searchResults: this.collection.toJSON() });

    CREATING A REUSABLE FUNCTION

    A common implementation to keep your view methods small is to have a serializingmethod to format a View's Model or Collection into native a JavaScript object or array.An implementation may look something like this:var MyView = Backbone.View.extend({ // Use a method, so you can get at instance properties. data: function() { return { active: this.isActive }; }});// Create a new instance.var myViewInstance = new MyView();// Mark it as active.myViewInstance.isActive = true;// Check and ensure the data is correct.console.log(myViewInstance.data()); // => { active: true }

    This function can now return any kind of data that will then be passed to the template. Thisis discussed in the next section on rendering.

    27

  • RENDERINGOnce you've fetched your template and have it as a function that will return markup andthe data that will be passed to the function, you can put the two together and inject into theView's element.This is typically done with a render function. Backbone actually provides a no-op render method by default. All it does is return this, it's implied that you bringyour own render logic and that you maintain returning this for chainability:Backbone.View.extend({ render: function() { // Using the previously defined fetch method. var template = this.fetch(); // Using the previously defined data method. var data = this.data(); // Generate the markup. var markup = template(data); // Insert into this element. this.$el.html(markup); // Allow for chaining. return this; }});

    EVENTSThe main value proposition of Backbone.View is helping you with events. Both DOMevents react to clicks and user interaction, as well as data changes coming from the Modelneed to be rendered to the user.DOM EVENTS

    DOM events are declarative events that are scoped to the View's #el property. They areset in the events property. This can either be an object or a function returning an objectof events. Events are described by "eventType selector" : callback.Let's examine the two ways of declaring views, taken from Hubbub modal views. Start bywriting the following:var ModalView = Backbone.View.extend({ events: { // you can use any jQuery selectors here 'click .modal-mask': 'close', 'click .modal' : 'stopPropagation', // note that an element needs focus for this to work 'keydown': 'keydown' }});var WelcomeModalView = ModalView.extend({ events: function(){ return _.extend({}, ModalView.prototype.events,{ 'click .exit': 'close', 'change .js-toggle-show-welcome': 'toggleShowWelcome' }); }});

    The context of all callbacks will be the View, which makes it easy to call upon other Viewmethods.DATA DRIVEN VIEWS

    You can easily set up a View to automatically re-render whenever relevant data changeswith events. These are normally set up in the initialize method.

    28

  • Consider our ItemView that will re-render upon a change to its models attribute isActive:var ItemView = Backbone.View.extend({ tagName: "li", template: _.template($('#js-issue-item-template').html()), initialize: function () { this.listenTo(this.model.repo, 'change:isActive', this.toggle); }});

    CLEAN UP CONSIDERATIONS

    If you create and tear down a lot of views, there is a chance you need to manually ensureproper cleanup once a view is removed. JavaScript garbage collection frees up variablesas they aren't referenced from anywhere, and ideally when you tear down a view, youneed to ensure that the view does not leave any references to external objects that in timewill make that object into a ghost object.Backbone.View provides a remove method you need to call on your view to safely tearit down, and if you have used listenTo/listenToOnce in order to be data driven,then those callbacks to your model/collection will be safely removed. If; however, youuse on or your event bindings to external objects, you need to override the removemethod and ensure cleanup is done. As a rule of thumb, you should stick to usinglistenTo unless you know what you are doing.

    External considerations

    It may be a good idea to add in a callable cleanup function. That way you can removereferences to non-Backbone related objects:Backbone.View.extend({ remove: function() { this.stopListening(); // Remove from some external dependency. removeFromSomethingElse(this); // This will call up the prototype chain and trigger the `__super__` // `remove` method. return Backbone.View.prototype.remove.apply(this, arguments); }});

    NESTING VIEWSWhen working with layouts, or Views that render collections, you often wish to maintain arelationship between the child Views. This can make it easier to maintain them and renderout dynamic lists.It's useful to create a views object or array that contains all of the nested Views. Thefollowing examples will layer on a ListView that can be extended and reused. This isused in the Hubbub application under views/main.js.var ListView = Backbone.View.extend({ initialize: function() { this.views = {}; } });

    The power of managing views like this is that propagating downwards becomes reallyeasy. Consider this very useful remove override:Backbone.View.extend({ remove : function() { _.invoke(this.views, 'remove'); Backbone.View.prototype.remove.call(this); }});

    29

  • APPENDING INTO PARENT

    You can use the jQuery append method to insert rendered sub Views elements. In theListView abstraction a method is added called addModel and this creates an instanceof a passed View named modelView, which assigns that to the internal Views object. Itis then rendered and appended into the parent list.Keep in mind that DOM operations are probably the most expensive part of your code, soyou need to make sure you don't have repeating DOM updates. That means eitherinserting DOM content or injecting CSS styles causing repaints.

    One of the first optimizations you want to make is to avoid the working, but naive,implementation below when it comes to View's rendering lists with many items. If only twoto three items are rendered at the maximum, then this approach is okay: var ListView = Backbone.View.extend({ initialize: function() { this.views = {}; this.listenTo(this.collection, { add: this.addModel }); },

    addModel: function(model) { this.views[model.cid] = new this.options.modelView({ collection: this.collection, model: model }); this.$el.append(this.views[model.cid].render().el); }});

    WHEN TO CREATE CHILD VIEWS

    Using child Views has some pros and some cons.Pros:

    Can pass a distinct model and make re-render easyEasier to keep all state out of the DOMMore modular codeEasier to make interchangeable modules

    Cons:N extra objects has higher memory and a CPU footprintEvent listeners are expensive, if each view handles its own event delegation thingscan get slowIt's hard to write an efficient render method for the parent View

    For small lists (up to 5 items at a time) a full blown view is faster to develop and easier tomaintain and for big lists (like a data grid with 10000 rows) you need to forget aboutcreating a RowView. For list sizes a hybrid approach can be considered. You can createlight weight views but handle the events in the parent view in order to not create extraDOM event listeners, then have your parent view call on the appropriate child view.PLUGINSYou may find that you tire having to maintain your own View logic for common tasks likeinsert nested Views, View cleanup, and template loading.

    30

  • This is a good time to investigate View plugins, which are designed to make your timeworking with Views more efficient.AVAILABLE OPTIONS

    There are several options available. The popular plugins at the time of writing this bookare: Marionette, Chaplin, and LayoutManager. These are more like frameworks built ontop of Backbone really. You can look into the complete list of plugins and the status ofthem at: https://github.com/documentcloud/backbone/wiki/Extensions%2C-Plugins%2C-Resources.CONCLUSION

    Now that you have your feet wet with Views, let's go on to the next chapter and learn howto use Models in Backbone.

    31

  • CHAPTER 5: MODELSBackbone's documentation clearly and succinctly explains what Models are:

    "Models are the heart of any JavaScript application, containing the interactivedata as well as a large part of the logic surrounding it: conversions, validations,computed properties, and access control. You extend Backbone.Model withyour domain-specific methods, and Model provides a basic set of functionalityfor managing changes."

    Before we look in more detail at how Backbone Models work, it's important to understandwhy they are needed. Backbone follows the MV* architecture of separating your data(models) from how it is displayed and interacted with (views). While you could build asmall JavaScript application without following any architecture, it is likely that it would bebrittle and hard to maintain. There are many great resources on the MVC / MV*architecture available online, and you may well have used an MVC framework on theserver, e.g. Ruby on Rails, Zend Framework, MVC ASP.Net, etc. The key principle to take from this is that your data and its relevant logic should beseparated from your views--how you display that data to the user.Lets take a traditional HTML page that we could convert into an application. In a standardpage, data is often displayed using a table: IDTitleCategory 43Fix welcome viewDoing 15Add testsToDo 20Update readneDone

    This is fine if all we want to do is display the static data. If you want to interactively edit,filter or sort this data, however, we need to use JavaScript. Using a DOM manipulationlibrary such as jQuery, we could select and allow the user to interact with the data. Wecould implement a method to sort the table, by looping through all the rows, detachingthem and inserting them in a sorted order. To find the right order we would need to extractthe relevant data from the HTML, sort it in JavaScript and then rearrange the associatedrows. If we wanted to allow the user to edit the data, we'd need to start adding clickhandlers and perhaps modal dialogs to enter the new data in. To save the data, we needto use AJAX requests. We are likely to end up with a JavaScript file full of functions, withlogic about our data scattered throughout. We'd probably still be using the HTML table asthe main representation of our data. We'd parse the data each time we need to update,change or sort the table. While this application may work, the code is likely to be a mess ofunmaintainable spaghetti. This is where Backbone comes to the rescue. Backbone provides a clear separation ofconcerns between the different parts of your application. Backbone Models contain yourdata and relevant logic (e.g. validation), while Backbone Views contain the display anduser interaction code. Part of the core philosophy of Backbone is to not store data in theDOM. In fact the first step of creating a maintainable client-side application is to move thedata out of the DOM. Instead we can use JavaScript objects (Backbone Models) to holdour data and we use Backbone Views to output this data to HTML. Using this approachwe can ensure that there is a single authoritative place that the data exists in your app.There could be multiple views of this data, and when the data is updated, all of theseviews can be updated to show the changes. Where data is changed after an AJAX requestfrom a server, or after a user input, there is one place that the change is made--the model.32

  • Now that we've had a look at why we need Models, let's look at how they work inBackbone.MODEL METHODSBackbone Models come with many useful methods for working with interactive data.Before we look at adding our own custom methods, let's go through and look at the built-inmethods.In current versions of JavaScript, there is no way of knowing when an object's propertyhas been changed (there is a proposal for this functionality with "Object.observe" inECMAScript 6 - "Harmony," however). Let's take the following example:var issue = {id:12, title:"Fix welcome view", category:"doing"}; issue.category = "done";

    There is no way for any view to know that the issue's category has changed. To getaround this problem, Backbone Models have get() and set() methods to access theirdata. The same example with a Backbone Model would look like this:var issue = new Backbone Model({id:12, title:"Fix welcome view", category:"doing"});

    issue.set("category","done"); //this changes the category attribute and triggers a changeeventWhen the issue's category is changed, any view displaying that issue can be notified ofthe change via the change event, and update itself accordingly. Events aren't the onlybenefit we get from using the set() method; Backbone Models can also run validationmethods each time data is changed. Before we look at set() in more detail, let's look athow we can retrieve data from Backbone Models.DATA RETRIEVAL METHODS

    Let's first look at the data retrieval methods.

    Get

    As we illustrated earlier, get accepts a key and returns the associated value from themodel's attributes. e.g. model.get('category'). get is the simplest and probably the most-often used of a Model's methods. While at first itmay seem a bit clunky compared to accessing an object's attributes directly, it offers fargreater flexibility. Later in this chapter we'll look at using the get method to retrievecomputed properties as well as just the raw data.

    Escape

    This HTML escapes the attribute before returning it. This is necessary if you are allowinguser-generated content to be inserted into your page as it will help prevent cross sitescripting (XSS).

    Has

    This utility method checks to see if the model contains a value for this key. This methodcan be useful in checking if a model has a property even if that property's value is false.For example:test('Model#has', function() {

    33

  • var model = new Backbone.Model({ read: false }); ok(model.has('read'), 'Property exists'); ok(!model.get('read'), '... but is not truthy');});

    This test will pass, showing that the "read" property exists on the model, even though it'svalue is false.

    isNew

    Backbone Models are often representations of rows or documents in a database on yourserver. Backbone assumes that if the data is from your sever then it will have some sort ofunique id attribute. By default this attribute's name is id, however it can be set to be adifferent key in your model class definition. If a model doesn't have an id value then it isassumed to be new and that it hasn't yet been saved to the server.

    toJSON

    This method returns a copy of the models properties, ready to be turned into a JSONstring. If you pass a model into JSON.stringify then interally the toJSON method willbe called and a correct JSON string will be generated of the model's attributes. Sometimesthis method is also used when sending data to the templates. It's important to note that thismethod uses "underscore clone" under the hood and therefore returns a "shallow copied"clone of the attributes. Any nested objects or arrays will be copied by reference, notduplicated. DATA MANIPULATION METHODS

    Now lets take a look at editing the data in your models.

    Set

    This method allows you to add or edit the data in your model. You can call it in one of twoways, and they have the same effect.model.set("key","value", options);model.set({key:"value"}, options);

    You can also update multiple properties at the same time:model.set({key:"value", key2:"value2"}, options);

    The last argument: "options", is optional. If you don't want any change events to be firedyou can pass in {silent:true} as an option.If you pass in {validate:true} as an option, then any data you pass into "set" willfirst be validated. If the validation fails then the method will return false and no data will bechanged on the model.

    Unset

    This method removes a key and its associated value from a model.

    Save

    You can call save without any arguments to persist the data already in your model, forexample:model.save();

    34

  • You can also pass in attributes in the same manner as with "set." For example:model.save({key,value}, options);

    The save method will use the Sync method that you've defined to save the model's data tothe server. Please read Chapter 7: Sync for more details on how this works.You can pass success and failure callbacks in the options object. These will in turn bepassed to the sync method--in the default AJAX based sync they will be sent tojQuery.ajax.

    Fetch

    This uses the sync method to get the latest data from the server. If the model is part of acollection, then the url endpoint is derived from the collection. If the model is not part of acollection then you can define a "urlRoot" property on the model to define the URL forsync operations.

    Destroy

    This attempts to delete the data from the server, in the default "sync" implementation, andan HTTP "DELETE" request is sent to the server.DEFINING MODEL CLASSESAlong with Views, Collections and Routers, Models have an extend method to enable youto create your own model classes. In the Hubbub application we use three different models: Board, Issue and Repo. Eachof the issues in the application will be represented by an Issue model. Let's build thismodel first.// models/issue.js(function (window) { 'use strict'; // Model code to go here})(this);

    All of the modules in Hubbub are defined within an "Immediately Invoked FunctionExpression." This ensures that variables are kept from polluting the global namespace.Before we define the model we need to first grab some local references to the global "app"and "Backbone" objects. We are using the "app" object to store all of our model, collectionand view methods. There will be models and collections for issues and these will both bestored in app.Issue.var app = window.app; var Backbone = window.Backbone; var Issue = app.Issue = app.Issue || {};

    Now that we've got references to our global objects we can define the model:Issue.Model = Backbone.Model.extend({ defaults: { category: 'default' },

    initialize: function () { this.repo = this.collection.repo; },

    url: function () { return this.urlRoot() + '/' + this.get('number'); },

    35

  • urlRoot: function () { return this.repo.url() + '/issues'; } });

    To understand this code we need to understand the structure of the appplication:Each issue will be part of a collection of issuesEach collection of issues will be associated with a repo modelIt is not possible to have an issue that is not part of a collection and therefore notassociated with a repo

    With this structure in mind we can have a closer look at the four instance properties for theissue model:

    Defaults: here we ensure that if an issue doesn't have a category attribute, then it isgiven one with the value of "default."Initialize: this method is called when the model is instantiated. In the appplication,issues will always be part of a collection of issues that belong to a specific repo, sowe give the issue model a reference to its parent repo.Url and urlRoot these methods define the url for the issue when syncing with theGithub API.

    There are a few other Model properties that we could have set when defining the model:

    parse

    This is an optional method which can be used to clean incoming data from the server. Forexample:parse: function(response) { return response.data; }

    This is useful when dealing with third party APIs, over which you have no control.

    idAttribute

    By default this is set as "id," if you use another key name for your "primary key" then youcan set it here. For example MongoDB and CouchDB often use "_id". Backbone uses thepresence of an id in a model to determine whether the model is "new" or not. A new modelis assumed to contain data that has yet to be synced to the server.

    validate

    We'll look at this in a bit more detail, but essentially this optional function validates anychanges made to your data.INSTANTIATING MODELS

    Once you have defined your Model classes you can create model instances by callingnew Model. The Model constructor accepts two arguments: attributes and options.Attributes is the data for your model, while options can contain:

    collection: the collection the model belongs toparse: true if you want the initial attributes to be run through the parse methodsilent: true if you don't want any change events to be fired when setting the initialdata

    36

  • VALIDATIONIt's possible to define a "validate" function on a model so that data attributes will only besaved to the model (or saved to the server) if the validate function passes. A validatefunction passes if it returns a false value, and fails if it returns anything else, such as astring. For example, we could say that the name attribute of a repository is required, likethis:// models/repo.jsRepo = app.Repo = app.Repo || {};Repo.Model = Backbone.Model.extend({ validate: function(attributes, options) { if (!attributes.name) { return 'Repo must have a name attribute'; } }});

    Then it would be impossible to save this model without giving it a name first. Add this testdemonstrating how validate works, given the above model:// test/hubbub.jswindow.jQuery(function () { module('Hubbub'); test('repo name is required', function () { var repo = new app.Repo.Model();

    // validate doesn't get checked in set() by default // (it only gets checked in Model#save() by default) repo.set('foo', 'bar'); equal(repo.get('foo'), 'bar');

    // setting with the "validate" option shouldn't work here // because validation will vail without a name attribute repo.set('foo', 'baz', { validate: true }); notEqual(repo.get('foo'), 'baz');

    // but if we set a name attribute, validation will pass repo.set({ 'name': 'test_name', 'foo': 'baz' }, { validate: true }); equal(repo.get('foo'), 'baz'); });});// test/index.html

    ...

    CUSTOM METHODS AND COMPUTED PROPERTIESIt's common to have some piece of data that you frequently want to access from a modelthat is actually comprised of two or more other attributes on the model. The canonicalexample of wanting a computed field like this is wanting a User model to have a"full_name" attribute such that it always returns the model's "first_name" and "last_name"attributes appended together with a space.We'll walk through a similar example from our Hubbub application where we want toeasily access a repository owner's username with the repository name when persistingour "board" to local storage. Backbone leaves you with a few valid options to do this. We'll show each of the options,using tests.

    37

  • OPTION A: CUSTOM METHOD

    The easiest way to make this work is by just creating a new method on your model. Here'sa snippet of the "toBoard" method from our completed Repo Model in Hubbub:// models/repo.jstoBoard: function () { var attrs = _.pick(this.attributes, 'id', 'name'); attrs.owner = {login: this.get('owner').login}; return attrs; }

    This custom method extracts the only data that we need in order to identify the repo: its id,name and owner's login name. Let's add a test showing the proper behavior:// test/hubbub.jstest('repos toBoard method products correct data', function () { var repo = new app.Repo.Model({ id: 1, name: 'hubbub', full_name:'backstopmedia/hubbub', owner: {login: 'backstopmedia'} }); deepEqual(repo.toBoard(), { id: 1, name: 'hubbub', owner: {login: 'backstopmedia'} }); });

    OPTION B: OVERRIDE MODEL#GET

    Sometimes you want to have computed properties available in a similar manner to actualproperties. In this case, you can override the Model's get method. If Github didn't returnthe "full_name" of a repo and we wanted to create it as a computed property, then wecould do this:Repo.Model = Backbone.Model.extend({ get: function(attr) { if (attr === 'full_name') { return this.get('owner').login + '/' + this.get('name'); } return Backbone.Model.prototype.get.apply(this, arguments); }});

    Then we could simply call repo.get('full_name') like you would for any "real"attribute. Though if you wanted this attribute to also show up when you call Repo model'stoJSON(), you would have to add it in there also.We don't generally recommend using this approach though, because it can be less clearwhat is going on, and can get messy in larger projects.OPTION C: USE A PLUGIN

    Backbone.Compute and Backbone.Mutators are two plugins to help facilitate easier waysto use computed properties on models. Check out their READMEs for examples.RELATIONSIn simple applications like Hubbub it made sense to manually link up all the relationsbetween models (and collections) in their initialize functions. An example of that can beseen in our Repo model, where we instantiate a Collection of Issues belonging to thatRepo:38

  • // models/repo.jsRepo.Model = Backbone.Model.extend({ initialize: function () { this.issues = new app.Issue.Collection(); this.issues.repo = this; });

    The first line in initialize creates the connection from the Repo to the Issues collection. Thesecond line creates a connection from the Issues collection back to its parent Repo.For managing complex relations, check out the Backbone-Relational plugin, which has alot of features and allows you to define relations in a declarative syntax.GOTCHAS

    One thing to watch out for is instantiating multiple instances of the same model. If youinstantiate two copies of an "Issue" model with the same ID, and even the same data, it'svery possible for them to get out of sync with each other. They are treated as twocompletely different objects, and if you call "set()" or "fetch()" on one, the other won't beupdated. There are cases where this is fine and expected, but other times this may lead toconfusing bugs.You can get around this by being sure to pass around references to existing models ratherthan creating new ones everywhere. Another good approach is to use a shared Collectioninstance that gets passed around as a global "store" for retrieving model instances.MODELS IN HUBBUBWe've already created our issue model and parts of our repo model. Let's finish off therepo model and add the main "board" model also.Here's the full repo model:// models/repo.jsRepo.Model = Backbone.Model.extend({ initialize: function () { this.issues = new app.Issue.Collection(); this.issues.repo = this;

    // When this repo is destroyed, destroy its issues too. this.on('destroy', function () { _.invoke(this.issues.models.slice(), 'destroy'); }); },

    url: function () { return app.apiRoot + '/repos/' + this.get('full_name'); },

    urlRoot: function () { return app.apiRoot + '/users/' + this.get('owner').login + '/repos'; },

    toBoard: function () { var attrs = _.pick(this.attributes, 'id', 'name'); attrs.owner = {login: this.get('owner').login}; return attrs; } });

    Again, we have the defaults, initialize, url and urlRoot methods. The initialize methodcreates a new issues collection for each repo. It also sets up an event handler for whenthe destroy event is triggered on itself. This event is triggered when the repo is destroyed(removed from the board and removed from local storage). When this happens any issues

    39

  • from the repo are also destroyed. Rather than looping through each of the issue models,we simply use underscore's invoke method to destroy all the issue models in thecollection. We've already looked at the toBoard custom method.Now lets build the main "board" model. This is a single model that will store which reposwe are tracking on our board. We also use it to store whether we should keep showing thewelcome modal view.var Board = app.Board = app.Board || {};

    Board.Model = Backbone.Model.extend({ defaults: { showWelcome: true },

    urlRoot: '/boards',

    initialize: function () { this.repos = new app.Repo.Collection(); this.issues = new app.Issue.Collection();

    this.on('change', function () { this.save(); }); var addIssue = function (issue) {this.add(issue)};

    // Save the board when a repo is added or removed. this.listenTo(this.repos, { add: function (repo) { this.issues.add(repo.issues.models); this.issues.listenTo(repo.issues, 'add', addIssue); this.save(); }, remove: function (repo) { this.issues.stopListening(repo.issues, 'add', addIssue); this.save(); } });

    // Save repos and issues when they are added or changed. this.repos.on('add change', function (repo) { repo.save(); }); this.issues.on('add change', function (issue) { issue.save(); }); },

    parse: function (res) { this.repos.set(res.repos);

    // **Don't** fetch on the collection (i.e. repos.fetch()), but // individually as the repos collection spans many owners and the // issues collection spans many repos. this.repos.invoke('fetch'); _.invoke(_.pluck(this.repos.models, 'issues'), 'fetch'); delete res.repos; return res; },

    toJSON: function () { var attrs = _.clone(this.attributes); attrs.repos = this.repos.invoke('toBoard'); return attrs; } });

    There's quite a lot happening here, so let's go through each property.DEFAULTS

    By default the welcome box should be shown.

    40

  • URLROOT

    This gives our sync method the key to save the board data. If you open up your consoleand type "localstorage," you will see see all your board data stored with the key "/boards."INITIALIZE

    When the board is first instantiated an issue collection and a repo collection are bothcreated. We then set up some event handlers, to ensure that the board is saved whenever changesare made or when repos are added or removed.

    Event handlers are also added to the repo and issue collection's change events to ensurethat the underlying repos and issues are saved.PARSE AND TOJSON

    These methods work in tandem. The toJSON method defines what data should bepersisted to local storage when the board is saved--any board properties plus the resuts ofthe "toBoard" method for each of the repos.The parse method is called when this data is retrieved from local storage. It updates therepo collection (created in "initialize") with the repo metadata saved to local storage. Itthen ensures that these repos and any associated issues are fetched (i.e. populated withdata, also from local storage). The parse method returns the input "res" without the repo'sattribute. This means any other attributes on the board model can be saved and retrievedsuccessfully. CONCLUSION

    Use instances of Backbone.Model to represent your business objects and use them tostore data and call their appropriate methods (e.g., fetch(), destroy()) to sync with aserver or other data source. By using Models to represent your data object, you have aconsistent internal API that other parts of your application can rely on for storing andretrieving data.We've built the three models used in the Hubbub application. To get a good idea of howthey work, try accessing models in the console. For example, with the finished applicationloaded and a few repos already added, try the following:app.board.issues.at(0); // This will return the first issue modelapp.board.repos.at(0).toBoard(); // Gets the first repo model and runs the "toBoard" methodapp.board.toJSON(); // See what data is being saved to local storage for your board

    Next up we will learn all about Collections in Backbone.

    41

  • CHAPTER 6: COLLECTIONSMore often than not your client-side application will be dealing with sets of data rather thansingle items of data, for example: users, pages, products, documents, etc. We have l