Вы находитесь на странице: 1из 70

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 CONTENTS
Copyright Preface Chapter 1: The Setup Chapter 2: Test Driven Development Chapter 3: Backbone Events Chapter 4: Views Chapter 5: Models Chapter 6: Collections Chapter 7: Sync Chapter 8: Routing Chapter 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, 2013 ISBN: 978-1-939902-01-6

PREFACE
Let'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. It provides abstractions to manage your data, handle single-page navigation, and manage rendering and events for components on your pages. Backbone gives you a robust, yet flexible 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 to Backbone, or perhaps haven't created complex client-side JavaScript applications. Basic jQuery 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 then continue with gaining a full understanding of Backbone. WHY BACKBONE? Backbone brings structure to your code. It is an incredibly flexible library and is used in widgets like Disqus, large content apps like USA Today and project management tools like 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 the building 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 fluid user experience, compared to re-rendering every page and interaction on the server. Modern browsers have fast JavaScript engines and users are accustomed to JavaScriptbased 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. Backbone isn't a replacement for jQuery--it actually requires jQuery (or Zepto). Backbone brings modularity and best practices to your jQuery-based application. jQuery.ajax() is used by Backbone Sync to interact with REST APIs, for example, and jQuery's delegated events 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 is commonly used in server-side frameworks, for example: Ruby on Rails, Django, Zend Framework and ASP.NET MVC. The pattern encourages a clear separation between your data and how users interact with that data, while encouraging code to be split up into these logical pieces. Backbone doesn't follow a strict MVC pattern, and is often referred to as a Model-View-Star (MV*) framework. You will find a more in-depth discussion to these principles in the Chapter 5: Models chapter.

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. It can be easily integrated into an existing page, and you can choose to only use certain components of the library (Views without Models or Collections, for example). Although there are many frameworks that seem to be faster to get started with, Backbone's lack of surprises, clear documentation, and speed and flexibility make it a good fit for all types of applications. Backbone also has a large community of users who have produced all manner of plugins to extend Backbone. If there is a particular feature from framework x that you wish was in Backbone, the chances are that it will already be implemented as a plugin. This bare bones, modular approach to web development, yields great returns in the long run when compared to being locked into a monolithic framework. The Backbone documentation has many examples of applications built with Backbone and some more answers to the question, "Why Backbone?" WHY THIS BOOK? While there are many online resources for learning Backbone, we felt that there was space for a full guide to the library. This book incorporates best practices and the techniques from our combined experience of developing many Backbone applications. Some of the resources on the web advocate inefficient or inelegant solutions and there are few that cover the whole library. In this book we aim to provide a complete guide to Backbone 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, you should feel comfortable starting a Backbone project from scratch on your own. The tricky thing is understanding what solutions make sense for which problems, and while we do our best to point out gotchas and explain "best practices," we guarantee you will encounter problems that we haven't yet. Nothing outweighs experience. While writing this book we developed an example application called Hubbub to illustrate the various features of the library. Don't worry, it's not another "Todo App," rather it is a GitHub 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 application as you go through the book. ABOUT THE AUTHORS

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

Aidan Feldman is a Backbone.js contributor and developer at Jux. He is also a faculty member at both General Assembly, and NYU School of Continuing and Professional Studies, He is an organizer of Hacker Hours, which offers free office hours for programming 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 led the 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 thank his co-authors. It was a pleasure working with such a talented group of guys, and the book sprint writing format was a unique experience. He would also like to thank his wife Kristin for giving him time to write, and all the Backbone.js contributors who make the project great. And finally, he would like to thank to all the readers! You can find Phil at PhilFreo.com or on Twitter @philfreo. He can also be found on GitHub: github.com/philfreo.

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

CHAPTER 1: THE SETUP


In this chapter we will be going over how to download our sample project and setup a development environment so you can get started writing code. If you're already comfortable with git, jQuery, Underscore.js, and debugging JavaScript in the browser, you may want to skip parts of this chapter. YOUR EDITOR When building a reasonably complex site, you will be spending most of your time in a code editor, so it's important you find one that works well for you. Some developers like a full-fledged IDE like WebStorm, while others use a lighter-weight GUI like Sublime Text or Textmate, and yet others stick to a terminal-based editor like Vim or Emacs. We promise, though, that Notepad/TextEdit won't cut it, and we do recommend you choose an editor that works for you. In case you are interested: authors Aidan and Casey use Sublime Text, Phil and Tim use Vim and Dave uses IntelliJ. HUBBUB Over the course of this book, we're going to walk you through building a sample one-page application with Backbone, called "Hubbub." The project is an organizer for GitHub issues, known to some as a Kanban board. The basic functionalty is this: you can search for code repositories hosted on GitHub (either owned by you or someone else), add them to your board, then categorize each issue as "ToDo", "In Progress" or "Done". You can check 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 to Chapter 2: Test Driven Development. GIT Depending upon your development background, you may or may not have used a version control system. They are helpful for individuals to keep track of changes within their project over time, and they become essential when working in a team environment. Git is currently the most popular version control system, and that's what we'll be using throughout the Hubbub project. GitHub is a site to host and share Git repos. Before we even started writing the book, we built Hubbub collaboratively, but remotely, by saving our changes in Git and synchronizing them through GitHub (which allowed us to keep track of the changes over time). Git has a feature called "branches," which allows you to have the same project in multiple contexts at once. We have a working version of the application in the complete branch, but we created a separate version (as the boilerplate branch) that includes just the basics you 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 now have your own personal version of Hubbub! Next, install Git. While you'll eventually want to get comfortable with Git on the command line--and here's a great interactive tutorial for doing so --using GitHub's desktop application provides a nice GUI that masks a lot of the nitty-gritty. Use whichever you feel comfortable with. Let's get the Hubbub project onto your computer:

Command Line Install Official Installers

GitHub Desktop Mac or Windows Near the top left of your Hubbub fork page (https://github.com/ USERNAME/hubbub), click (or Windows). After choosing a folder to save the project in, you'll now have "Hubbub" listed under "My Repositories". On the "Branches" tab, click the "+" next to "boilerplate". Name that branch "master". Next to your new "master" branch, click "Publish."

Clone Hubbub (make sure to replace your GitHub USERNAME)

git clone git@github.com: USERNAME/hubbub.git cd hubbub

Create your "master" branch

git checkout -b master

Push your new branch up to GitHub

git push -u origin master

The internals of Git are complex, but when you're working on a project by yourself on a single computer, there are really only a few commands you need to know. Inside the project folder, you'll see a README.md file. Open that up in your editor. Add your name just 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 View what's git status been changed Commit the changes
git add . git commit -am "added my name to the README" git status

GitHub Desktop Open the "Changes" tab. Type "added my name to the README" in the "Commit summary" field and press "Commit." Click "Sync" Use descriptive commit messages to make your repo history easy to follow.

Send the changes git push to GitHub


BOILERPLATE

Open the index.html file at the base of the Hubbub project directory. This acts as the skeleton for our one-page application. We include a simple stylesheet (which includes Twitter Bootstrap) in the <head>, just to provide a baseline of styling. Backbone has two dependencies: jQuery for doing DOM manipulation, and Underscore 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 browsers in exchange for a much smaller filesize. Lo-Dash is a fully-compatible replacement for Underscore that emphasizes performance and customizability. If you want to support old browsers (e.g. Internet Explorer 6), you should also include json2.js. We also included marked.js, which we will use to parse the Markdown content of GitHub Issues. You'll notice that <body> is fairly empty, and it will remain that way, because we'll use JavaScript (via Backbone and templates) to create the markup. More about that is covered in Chapter 4: Views. JQUERY jQuery is the most popular library in use today, in JavaScript or otherwise. jQuery normalizes browser inconsistencies for common operations like accessing elements in the DOM, 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. Running the $() function with a selector returns a jQuery object, which refers to a set of zero or more elements on the page. If you save this object to a variable, it's a common convention to 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 it directly later in Chapter 4: Views. UNDERSCORE Jeremy Ashkenas created Backbone when he was part of DocumentCloud, but he also wrote a more general-purpose "utility-belt library" called Underscore, which Backbone depends upon. Underscore is meant to provide functions that are commonly used in JavaScript applications. If you're coming from another programming language, you can think of Underscore as the "standard library" JavaScript never had. Some methods like _.each() and _.size() normalize methods available between Arrays, Arguments and Objects, while providing a "shim" in some places where the method is only natively available 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');

// 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 feeling really ambitious, you can also check out the well-annotated source code. THE BROWSER While we know you use a web browser all the time, doing serious client-side development requires a deep understanding of the browser as both a platform and a tool. We highly recommend using Google Chrome or Mozilla Firefox, as they have the strongest developer tools. Open up the completed Hubbub site, then fire up the developer tools: Chrome Install the developer tools (preinstalled) Click the Chrome Click the Firebug menu ( ) on the Open them far right end of the button ( ) at the up toolbar. Under Tools, far right of your toolbar. click Developer Tools. See all of Click the Network the tab, then refresh the downloaded page. assets It's useful to check if a particular file is being loaded, Click the Net tab, then how big it is, etc. Click an refresh the page. individual row to get more details. This view allows you to see the rendered markup, and selecting an element allows you to play with the CSS and see the changes live. Firebug Install Firebug Tips

Inspect the DOM

Click the Elements tab.

Click the HTML tab.

Open the primary JavaScript file

Click the Sources Click the Script tab, tab, then click the tiny then the dropdown on the left of the secondary bar of the arrow icon ( ) in debugger, and select the top left and select index.js. index.js.

While many JavaScript developers simply use console.log() to debug, being able to stop 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. You will see a marker appear on (or next to) the number. Refresh the page, and you should see that line highlighted. This means the page has been paused, and this line is about to run.

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 a list of what methods have been executed to lead to the current line. You can think of these as JavaScript "breadcrumbs.") Chrome Firebug Continue Step Into Step Over Step Out Resume execution of the code, until it hits the next breakpoint, or indefinitely If the current line has a function call, jump into that function If the current line has a function call, execute it and stop on the next line Finish execution of the current function, and stop when it returns to the calling function

Lastly, the Console tab allows you to execute arbitrary JavaScript on the page, and if you'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 to keep this window open during development. Now that we've introduced the projects, tools and libraries you'll be using, let's get into some specifics about Backbone.

10

CHAPTER 2: TEST DRIVEN DEVELOPMENT


Imagine you are in charge of building an airplane. Would you simply create all of the various airplane parts, put them together, and then hope that the plane flies? Of course not! You'd set up strict testing requirements and quality assurance guidelines for each part individually 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 writing some JavaScript, loading it into the browser, and manually trying out the feature to see if it runs without error and does what you expect. If it works, you're happy and move on. If it doesn't then you keep trying new things until it does. THE PROBLEM The problem with this typical manual testing approach is that a feature that works properly today may accidentally break when you add or change more code later. Often times you may 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 increasingly difficult 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 tweak the feature? Or perhaps even more importantly: do you trust that all of your current and future teammates will also remember to properly test all of those edge cases? THE SOLUTION The solution is to add in programmatic testing. Unit tests are little pieces of code that test a specific part of your application code. Rather than manually clicking around in your application to test some code, you write test code, to test it for you. The tests are usually kept in source control alongside your application code itself, and should run (making sure all 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 testing into 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, rather than after, so that your tests actually help you complete your task rather than being a chore. There are entire books written on Test-driven Development, but the basic idea is simple: 1. Write new tests that fail initially, but that demonstrate how a new feature or bug fix should 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 much more 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 and what its effect is, a unit test can explicitly prove what code does. It's the difference between describing what should happen and showing exactly what does happen. 11

Consider this approach in your own programming as well, especially when you're trying to fix 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 we expect them to. A lot of times we discover that a part of our application that we thought worked 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 provides opportunities to "give back" to the Backbone project by creating a Pull Request on GitHub showing exactly how to reproduce the problem, and ideally a way to fix the bug itself. And the best part is, by contributing a unit test with the use case you care about, you can feel more confident knowing that this behavior won't accidentally change in a future version of the project. Written tests also help when working with a team, or even when asking for help online on StackOverflow, in mailing lists, or in GitHub Issues. When you can write a straightforward unit test showing what a problem is, then you've given others a reproducible way to see the problem and understand it in as little code as possible. We groan every time we see a question online with way too much (irrelevant) information and no easy way to reproduce a version of the problem. But when you see somebody show up with a unit test demonstrating their exact problem, it becomes much easier to a) fix a bug in the underlying code they are relying on, or b) see what they are doing wrong themselves. WRITING TESTS We are using QUnit as a testing framework in this book and in the example code. We've chosen QUnit because it's very popular and powerful, and because the tests for Backbone.js itself use QUnit. A test can be very simple. Here's a test that simply demonstrates that JavaScript allows you 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 <head> ... <!-- load the generic test file --> <script src="generic.js"></script> </head>

Then, just open test/index.html in your browser to run it. As you can see, testing can be easy. QUnit just assumes that you wrap your test functions in a function called test() and give them a name, and that you use special assertion functions like equal(), 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 example that will show both the basics of how a model can work to store data and an example of another test. Add this directly under the last test:
// test/generic.js test("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 your own application's code, rather than testing JavaScript Arrays or the basics of Backbone. RUNNING TESTS JavaScript tests can be run very easily from the browser. In our project, just open test/index.html in your web browser and you'll see a nicely formatted page running the tests. You can switch to the complete git branch to see the tests for the completed Hubbub project, 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 this figure:

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

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

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 up test/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 of the book). You can also practice writing your own tests. To start, write out the "Length of an 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 few best practices to get you started: Make each test independent from others. Testing frameworks have a way to explicitly define what set-up work a test needs (see how in QUnit), so make sure you're not expecting one test to do anything that another test requires in order to pass. 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 to writing integration tests that test your overall application, the most common type of test should be testing one specific module of code and focusing only on that one piece. 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 question about the specifics of how Backbone.js works in some detailed case where the documentation 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.js test("slice", 2, function() { var col = new Backbone.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 TIPS Once you're comfortable with writing and running tests for your application, you'll want to learn about more advanced testing methods. Here are a few resources to help. (But don't worry 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 they pass) from the command line. This allows you to easily integrate with continuous integration frameworks. You can use a service like CircleCi or Travis CI in such a way that every time you commit new code, it is automatically tested, and then deployed to a production server if the tests pass. Mocks, Spies, and Stubs: Sinon.JS is also helpful in testing (with QUnit or by itself) in many ways, but especially in testing code that relies on communicating with a server via AJAX/XMLHttpRequest. You can mock out server responses with a fake server to test that your response handling code is correct, and you can test that the AJAX requests your code is generating is correct. See an example here of using these concepts to test how a Backbone Model syncs with a server. Other Testing Frameworks: Our project and Backbone.js itself uses QUnit as a testing framework, but you may want to check out alternative testing frameworks like Jasmine and Mocha.
CONCLUSION

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

15

CHAPTER 3: BACKBONE EVENTS


Now that we have covered how to test our code, let's get started by looking at Backbone events. With functions as first class members, JavaScript is a great language for taking full advantage of events. If you've ever responded to a user click or keyboard event with some JavaScript code that was waiting for it, you've already been using events. An event is simply 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. Even better, this API has been extended onto all Model, Collection , View, and Router instances, as well as the Backbone object itself. This section will go over the Events API and then give some specific examples of how Hubbub uses events. API The Backbone.Events object comes equipped with seven methods that allow you to invoke a specific function when an event occurs, trigger an event, or stop waiting for an event to occur. We will go over the signature and purpose of each method along with an example 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 you want 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 callback to 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. If not 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, you may wish to save a model every time it changes. Without Events , you'll have to remember to call model.save() after every model.set(attributes) . That's not fun, 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 will be invoked on all of that object's triggers with the name of the event fired as the first argument. 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 Collection s 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 the context 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 from the object as soon as it is called. It gives you an easy way to bind to an event for just one occurrence, and then forget about it. A custom event like a modal view 'close' is a good use case for once , because generally a modal view will be remove d once it is closed, so further 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's important to turn off callbacks you aren't going to be using anymore, since lingering callbacks (zombie events) can lead to memory leaks. Here's an example of a memory leak using 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 knows nothing about view's removal and will continue firing callback with view 's context every time 'event' is triggered. How can we fix this problem? Use off to reverse the effect 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 no arguments are passed, every event listener is destroyed. Turning off the special case 'all' event works the same way. To turn off a callback listening 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 job and puts it in the hands of the listening object, rather than the triggering one. The difference between on and listenTo is best seen in an example. These two methods of binding an object to another object's event are functionally the same:
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 the stopListening section. As a general rule, use listenTo when the context of your callback should be the object you're listeningTo with. This is often the case for views where you want the view to listen to a model or collection, but stop listening when 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 of the objects the listening object has been listening to. This comes in handy when it comes time 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 case modelA , modelB, and modelC), all bindings can be undone with a single stopListening:
view.stopListening();

stopListening is automatically called by a view when it is remove d, so normally in the 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 arguments listenTo (or listenToOnce) receives to turn off callbacks accordingly.
TRIGGER(NAME[, ARGUMENTS...])

Now that you have all your callback s ready to listen, trigger gives you the power to be heard. Internally, Backbone triggers about a dozen built-in events on instances of Models, 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 a lot. 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 the listening callback functions. What's great about the Events API, though, is that you are free to trigger whatever kind of event you want to listen to. Maybe you want to do something before a model is saved. You could 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 its callback 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 EXAMPLES If you'd like to see more examples of on, off, trigger, listenTo and the other event methods check out the Backbone events test file. There are plenty of assertions there that will help you understand events better and also may give you some ideas to help your testing.
EXTENSIBILITY

The Backbone.Events object was designed to be extendable onto any object. For example, 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 if you don't need the other features Backbone provides.
BONUS FEATURES

Backbone's Events API is sprinkled with some nice syntactic sugar to make your life easier. The first one we'll go over is method chaining. This allows you to bind or trigger an event and continue calling methods on the binding object in that same expression. We'll use 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 callback s. So far we've only used the standard single name to single callback style, but there are a couple other ways you can 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 are triggered:
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 corresponding callbacks all at once. This is possible when you pass an event map as the first argument of the function:
obj.on({ eventA: callbackA, eventB: callbackB });

The same rule with space delimited events also applies to event maps, so you can really get 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 to bind multiple events to a callback , or get even fancier and pass in an event map to listenTo an object with multiple events and multiple callbacks. EVENTS IN HUBBUB If you browse the Hubbub source, you'll notice we use events extensively. Rather than point out each individual place events are used (which would take its own book), we'll focus 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's context to be our board (this). Then we pass an event map of event names as the keys and corresponding callbacks as the values.
this.listenTo(this.repos, {

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

easier to filter the issues on the kanban when we can pull them all from the same collection. To do this, we'll want to listen for new repos being add ed to the repos collection, and in turn add that repo 's issues to our master issues collection. But that's not all. We also want to set up another hook for this new repo that ensures any new issues 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 add event. When a repo is removed, we need to tell the board's issues to stop waiting for the 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 to stopListening will result in a repo object that can never be de-referenced, even though it's no longer used (memory leak). And lastly, as with add , be sure to save the board 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 ) new repos and issues when they are added or changed. For example, when you change the state of an issue from "doing" to "done," you immediately save the issue's new state when 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 in Hubbub. This isn't the case for all applications, and larger applications can actually make great use out of this feature.
CONCLUSION

Backbone's event system is a powerful tool that you will come to know and love as you begin writing Backbone applications. Here are a few things to remember as you dive in with Events: Use the right tool for the job. In general, on is best used when you need the context of the callback to be the object that is trigger ing the event. listenTo is best used when you need the context 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 is wiped. In a single-page Backbone application, you don't have this convenience so memory management becomes important. If you use on , once , listenTo , or listenToOnce, you have added a callback to an internal array that will stay there until (a) the object is dereferenced in JavaScript or (b) you call off or stopListening accordingly. Keep this information in mind and clean up your events. A memory leak may not be noticable at first, but too much wasted memory will eventually have noticable, detrimental effects to your application, especially if it's long-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 be extremely 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 our Hubbub application.

22

CHAPTER 4: VIEWS
A 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 of reusable components and provide a more modular infrastructure. Views are often managed within a Router or a parent View and can either be long lived like a persistant shopping cart in the header, for example, or short lived like the items in the cart as you expand it. PURPOSE When working with regions of a web application, it is often desirable to isolate and contextualize logic and rendering. Imagine a shopping cart web application that has an interface that positions navigation at the top, categories on the left, and the shopping cart on 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 representing our application in this manner, we are able to adopt patterns and best practices that have existed in application development for decades. Since Views are visual representations of data, there is no querying the DOM to determine what the state is. The idea here is that you do logic and calculations (shopping cart totals, position, etc.) within the View and render that. Views should not know anything about business logic, and instead focus solely on presentation. They should remain independent and not depend upon any other views to function. Views should also not leak any events outside of a specific region. Backbone intentionally keeps views minimalistic, since there are many opinionated ways of using events, and developers have diverged to embracing different methods. The following section describes in detail how to create powerful views from scratch, but it also provides insight on refactoring using the LayoutManager plugin (as well as recommendations for other view management plugins). BASIC USAGE Extending and initializing Views is surprisingly simple, but there are inconsistencies in views that are not prevalent in Models, Collections, or Routers that you will need to be aware of. These inconsistencies are most prevalent with assigning properties and figuring out how to access them. There are also properties that change from their original value to a 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 functionality and 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 own custom implementations. This is a useful practice when you will create more than one instance 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 or classProperties object are required, but its as rare to see a view without properties as a view having classProperties. Examine the Hubbub IssueHolderView as a good View example, and write the following:
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. The properties 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 view issueHolder.render(); EXAMINING THE VIEW INSTANCE

Once you've created an instance of a View, you can use your debugger in the browser to inspect the structure. At their essence, view instances are simply wrappers around a DOM Node/jQuery collection. Views must always have a root element to allow event delegation, provide a location to put template contents, and give you an end point to attach the View into the page Document. To inspect, open the Elements/DOM tab of your Developer Tools (see Chapter 1: The Setup). Every View instance will have a cid property that uniquely identifies it on the client 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. Be aware 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.Model and Backbone.Collection instances assigned to them for the View to render a visual representation of their value. The el, id, attributes , className , and tagName properties will control how your 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 event types, selectors, and method names. Technically, any property that is not one of these special identifiers, will end up in the options object described later on. We will see how this is not respected in all cases and should not always be relied upon. el The el property is always an element after you create your instance. During the definition you can pass the el property a jQuery collection, a selector, or a DOM Node. Backbone will 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 <tagName class="className" id="id" attributes> will be created for you. options Any key passed during instantiation that is not in the list of special keys will be put in the options object. This is inconsistent from how Models, Collections, and Routers handle options 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 some underscore 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. TEMPLATING While not explicitly part of the Backbone.View API, templates are fundamental to a good abstraction of logic and presentation. The term can apply to many varied types and representations 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 then interpolated into text and returned as a string. In the following example, a template is assigned to the View inline. This means that the template 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("<%= echo %>") });

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 for designers), 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 <script></script> tags that have a non-JavaScript type attribute on them which will cause the browser to not execute the contents. This is a hack, which has gained mainstream endorsement, especially for example applications. This allows developers to quickly 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 template and then reference it within the View definition. First write the markup:
<head> <script type="template" class="my-template"> <%= echo %> </script> </head>

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 the selector and deal with the actual compilation of the template function inside the render function 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 of template organization, since it is the easiest way to consume and modify templates. It is great 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 is 26

bloated, and it can be confusing since it is a browser hack (storing templates in script tags meant 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 be understood as a method of compiling the string to a function before the application runs to avoid extraneous processing. This is problematic for the above two solutions since the raw 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 need to 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 object instances themselves. You may want flat valid JSON objects instead, for engines like Mustache and Handlebars. Here is an example of instances being used in underscore templates:
<% searchResults.each(function(result) { %> <li> <a class='js-result' data-id='<%= result.id %>'> <%- result.get('title') %></a> </li> <% }); %> // Passing the data this.template({ searchResults: this.collection });

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

A common implementation to keep your view methods small is to have a serializing method 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. This is discussed in the next section on rendering.

27

RENDERING Once you've fetched your template and have it as a function that will return markup and the data that will be passed to the function, you can put the two together and inject into the View's element. This is typically done with a render function. Backbone actually provides a noop render method by default. All it does is return this, it's implied that you bring your 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; } });

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

DOM events are declarative events that are scoped to the View's #el property. They are set in the events property. This can either be an object or a function returning an object of events. Events are described by "eventType selector" : callback. Let's examine the two ways of declaring views, taken from Hubbub modal views. Start by writing 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 <input> 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 View methods.
DATA DRIVEN VIEWS

You can easily set up a View to automatically re-render whenever relevant data changes with 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 ensure proper cleanup once a view is removed. JavaScript garbage collection frees up variables as they aren't referenced from anywhere, and ideally when you tear down a view, you need to ensure that the view does not leave any references to external objects that in time will make that object into a ghost object. Backbone.View provides a remove method you need to call on your view to safely tear it 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, you use on or your event bindings to external objects, you need to override the remove method and ensure cleanup is done. As a rule of thumb, you should stick to using listenTo 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 remove references 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 VIEWS When working with layouts, or Views that render collections, you often wish to maintain a relationship between the child Views. This can make it easier to maintain them and render out dynamic lists. It's useful to create a views object or array that contains all of the nested Views. The following examples will layer on a ListView that can be extended and reused. This is used 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 really easy. 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 the ListView abstraction a method is added called addModel and this creates an instance of a passed View named modelView, which assigns that to the internal Views object. It is then rendered and appended into the parent list. Keep in mind that DOM operations are probably the most expensive part of your code, so you need to make sure you don't have repeating DOM updates. That means either inserting 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 two to 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 easy Easier to keep all state out of the DOM More modular code Easier to make interchangeable modules Cons: N extra objects has higher memory and a CPU footprint Event listeners are expensive, if each view handles its own event delegation things can get slow It'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 to maintain and for big lists (like a data grid with 10000 rows) you need to forget about creating a RowView. For list sizes a hybrid approach can be considered. You can create light weight views but handle the events in the parent view in order to not create extra DOM event listeners, then have your parent view call on the appropriate child view. PLUGINS You may find that you tire having to maintain your own View logic for common tasks like insert nested Views, View cleanup, and template loading.

30

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

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

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

31

CHAPTER 5: MODELS
Backbone's documentation clearly and succinctly explains what Models are: "Models are the heart of any JavaScript application, containing the interactive data as well as a large part of the logic surrounding it: conversions, validations, computed properties, and access control. You extend Backbone.Model with your domain-specific methods, and Model provides a basic set of functionality for managing changes." Before we look in more detail at how Backbone Models work, it's important to understand why 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 a small JavaScript application without following any architecture, it is likely that it would be brittle 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 the server, 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 be separated 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 standard page, data is often displayed using a table:
<table> <thead> <tr><th>ID</th><th>Title</th><th>Category</th></tr> </thead> <tbody> <tr><td>43</td><td>Fix welcome view</td><td>Doing</td></tr> <tr><td>15</td><td>Add tests</td><td>ToDo</td></tr> <tr><td>20</td><td>Update readne</td><td>Done</td></tr> </tbody> </table>

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 manipulation library such as jQuery, we could select and allow the user to interact with the data. We could implement a method to sort the table, by looping through all the rows, detaching them and inserting them in a sorted order. To find the right order we would need to extract the relevant data from the HTML, sort it in JavaScript and then rearrange the associated rows. If we wanted to allow the user to edit the data, we'd need to start adding click handlers and perhaps modal dialogs to enter the new data in. To save the data, we need to use AJAX requests. We are likely to end up with a JavaScript file full of functions, with logic about our data scattered throughout. We'd probably still be using the HTML table as the 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 of unmaintainable spaghetti. This is where Backbone comes to the rescue. Backbone provides a clear separation of concerns between the different parts of your application. Backbone Models contain your data and relevant logic (e.g. validation), while Backbone Views contain the display and user interaction code. Part of the core philosophy of Backbone is to not store data in the DOM. In fact the first step of creating a maintainable client-side application is to move the data out of the DOM. Instead we can use JavaScript objects (Backbone Models) to hold our data and we use Backbone Views to output this data to HTML. Using this approach we 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 these views can be updated to show the changes. Where data is changed after an AJAX request from 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 in Backbone. MODEL METHODS Backbone 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-in methods. In current versions of JavaScript, there is no way of knowing when an object's property has been changed (there is a proposal for this functionality with "Object.observe" in ECMAScript 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 get around this problem, Backbone Models have get() and set() methods to access their data. 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 change event When the issue's category is changed, any view displaying that issue can be notified of the change via the change event, and update itself accordingly. Events aren't the only benefit we get from using the set() method; Backbone Models can also run validation methods each time data is changed. Before we look at set() in more detail, let's look at how 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 the model'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 it may seem a bit clunky compared to accessing an object's attributes directly, it offers far greater flexibility. Later in this chapter we'll look at using the get method to retrieve computed properties as well as just the raw data. Escape This HTML escapes the attribute before returning it. This is necessary if you are allowing user-generated content to be inserted into your page as it will help prevent cross site scripting (XSS). Has This utility method checks to see if the model contains a value for this key. This method can 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's value is false. isNew Backbone Models are often representations of rows or documents in a database on your server. Backbone assumes that if the data is from your sever then it will have some sort of unique id attribute. By default this attribute's name is id, however it can be set to be a different key in your model class definition. If a model doesn't have an id value then it is assumed 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 JSON string. If you pass a model into JSON.stringify then interally the toJSON method will be called and a correct JSON string will be generated of the model's attributes. Sometimes this method is also used when sending data to the templates. It's important to note that this method 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, not duplicated.
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 two ways, 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 fired you can pass in {silent:true} as an option. If you pass in {validate:true} as an option, then any data you pass into "set " will first be validated. If the validation fails then the method will return false and no data will be changed 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, for example:
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 to the 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 be passed to the sync method--in the default AJAX based sync they will be sent to jQuery.ajax. Fetch This uses the sync method to get the latest data from the server. If the model is part of a collection, then the url endpoint is derived from the collection. If the model is not part of a collection then you can define a "urlRoot" property on the model to define the URL for sync operations. Destroy This attempts to delete the data from the server, in the default "sync" implementation, and an HTTP "DELETE" request is sent to the server. DEFINING MODEL CLASSES Along with Views, Collections and Routers, Models have an extend method to enable you to create your own model classes. In the Hubbub application we use three different models: Board , Issue and Repo. Each of the issues in the application will be represented by an Issue model. Let's build this model 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 Function Expression." 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, collection and view methods. There will be models and collections for issues and these will both be stored 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 issues Each collection of issues will be associated with a repo model It is not possible to have an issue that is not part of a collection and therefore not associated with a repo With this structure in mind we can have a closer look at the four instance properties for the issue model: Defaults: here we ensure that if an issue doesn't have a category attribute, then it is given 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, so we give the issue model a reference to its parent repo. Url and urlRoot these methods define the url for the issue when syncing with the Github 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. For example:
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 you can set it here. For example MongoDB and CouchDB often use "_id". Backbone uses the presence of an id in a model to determine whether the model is "new" or not. A new model is 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 any changes made to your data.
INSTANTIATING MODELS

Once you have defined your Model classes you can create model instances by calling new 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 to parse: true if you want the initial attributes to be run through the parse method silent: true if you don't want any change events to be fired when setting the initial data 36

VALIDATION It's possible to define a "validate" function on a model so that data attributes will only be saved to the model (or saved to the server) if the validate function passes. A validate function passes if it returns a false value, and fails if it returns anything else, such as a string. For example, we could say that the name attribute of a repository is required, like this:
// models/repo.js Repo = 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 test demonstrating how validate works, given the above model:
// test/hubbub.js window.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 <head> ... <script src="hubbub.js"></script> </head>

CUSTOM METHODS AND COMPUTED PROPERTIES It's common to have some piece of data that you frequently want to access from a model that is actually comprised of two or more other attributes on the model. The canonical example 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 to easily access a repository owner's username with the repository name when persisting our "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's a snippet of the "toBoard" method from our completed Repo Model in Hubbub:
// models/repo.js toBoard: 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.js test('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 actual properties. In this case, you can override the Model's get method. If Github didn't return the "full_name" of a repo and we wanted to create it as a computed property, then we could 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's toJSON(), you would have to add it in there also. We don't generally recommend using this approach though, because it can be less clear what 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 ways to use computed properties on models. Check out their READMEs for examples. RELATIONS In simple applications like Hubbub it made sense to manually link up all the relations between models (and collections) in their initialize functions. An example of that can be seen in our Repo model, where we instantiate a Collection of Issues belonging to that Repo: 38

// models/repo.js Repo.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. The second 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 a lot 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 you instantiate two copies of an "Issue" model with the same ID, and even the same data, it's very possible for them to get out of sync with each other. They are treated as two completely different objects, and if you call "set()" or "fetch()" on one, the other won't be updated. There are cases where this is fine and expected, but other times this may lead to confusing bugs. You can get around this by being sure to pass around references to existing models rather than creating new ones everywhere. Another good approach is to use a shared Collection instance that gets passed around as a global "store" for retrieving model instances. MODELS IN HUBBUB We've already created our issue model and parts of our repo model. Let's finish off the repo model and add the main "board" model also. Here's the full repo model:
// models/repo.js Repo.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 method creates a new issues collection for each repo. It also sets up an event handler for when the 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 the collection. 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 repos we are tracking on our board. We also use it to store whether we should keep showing the welcome 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 console and 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 both created. We then set up some event handlers, to ensure that the board is saved whenever changes are made or when repos are added or removed. Event handlers are also added to the repo and issue collection's change events to ensure that the underlying repos and issues are saved.
PARSE AND TOJSON

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

Use instances of Backbone.Model to represent your business objects and use them to store data and call their appropriate methods (e.g., fetch() , destroy()) to sync with a server or other data source. By using Models to represent your data object, you have a consistent internal API that other parts of your application can rely on for storing and retrieving data. We've built the three models used in the Hubbub application. To get a good idea of how they work, try accessing models in the console. For example, with the finished application loaded and a few repos already added, try the following:
app.board.issues.at(0); // This will return the first issue model app.board.repos.at(0).toBoard(); // Gets the first repo model and runs the "toBoard" method app.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: COLLECTIONS
More often than not your client-side application will be dealing with sets of data rather than single items of data, for example: users, pages, products, documents, etc. We have looked at how Backbone represents individual items of data as Models. In this chapter we will look at how to work with multiple items of data through Backbone Collections. From the Backbone Documentation: "Collections are ordered sets of models." Using Collections you can easily sort and manipulate your data without fetching it afresh each time from the server. Take for example an application for managing the users of a blogging system. You may want to be able to see which users have authored the most articles, which users have written the most comments, or who the newest users are. In a traditional server-based application, each time you wanted a different view of the data you would have to load a new page from the server. This is not very fast, and not really necessary as the data hasn't changed, just the order in which it is displayed. If the application were built with Backbone, then there would be a collection of user models. Sorting the users would be a simple case of calling:
users.sortBy(function(user){ return user.get('number_of_posts'); });

This would sort the models by the "number_of_posts" field. You could easily resort the data by another field:
users.sortBy(function(user){ return user.get('date_joined'); });

Once the data has been sorted, it can be re-rendered on the page. Backbone Collections inherit many methods from Underscore (see Chapter 1: The Setup for more information), which make it easy to sort, group, and filter your Models. DEFINING A COLLECTION Collection Classes are defined in a simliar way to Models. They can be extended either from the base Backbone.Collection or from another Collection that you've defined. Here is a simple Collection Class for Issues:
// collections/issue.js (function (window) { 'use strict'; var app = window.app; var Backbone = window.Backbone; var Issue = app.Issue = app.Issue || {}; Issue.Collection = Backbone.Collection.extend({ model: Issue.Model, comparator: function (issue) { return -1 * Date.parse(issue.get('created_at')); }, url: '/issues' }); })(this);

Let's look at the three properties that we've defined. Comparator - this is a function that is invoked every time a model is added to the collection. It keeps the collection ordered correctly. In this collection, issues are sorted by the date they've been created--newest first. If we had three issues, all which were created on 2013-01-01 and I added an issue that was created on 201301-10, the new issue would be added to the front of the collection rather than the end. Model - while collections can be polymorphic and contain different types of models, standard practice is to have different collection classes for different model classes. By referencing the model class in the collection class, we have an easy way to add new models to the collection from raw data sources (either a REST api or a html 42

form). URL - Backbone works very well with REST APIs. The URL property allows you to define the url to "GET" the remote data from the server. It can also be used to define the urls to which individual models send "GET," "POST," "PATCH," "PUT" and "DELETE" commands to the server. For example, you could instantiate a new collection from the above class like this:
collection = new Issue.Collection();

To get data from the server you could call collection.fetch();. This would issue an AJAX GET request to /issues. The expected response would be an array of data items. When these items are returned from the server they would be converted into Issue.Model s and added to your collection (ordered by date created). If you made a change to a model in the collection and that model had an ID of 10, then the PUT request to the server would be sent to /issues/10. Backbone is quite flexible when it comes to working with legacy APIs or services that are outside your control. You can define a parse method to extract the actual array of data from your web service's response. For example if you received responses like this:
{"message": "Success", "date": "2013-01-10", "items": [ {"id": 1, "title": "First Issue"}, {"id": 2, "title": "Second Issue"} ]}

You could write a parse method as follows:


Issue.Collection = Backbone.Collection.extend({ model: Issue.Model, url: '/issues', parse: function(response){ return response.items; } });

This method ensures that the collection will receive the array of data rather than response metadata. You can define any custom methods on your collections in the same manner as you can with models. After defining and creating your collections, you need to be able to add and remove models from it, whether in response to an internal event, user input, or an update from the server. ADDING AND REMOVING MODELS Let's take a look at how you can add or remove models from collections. Backbone provides a variety of flexible methods to achieve this. It's important to remember that a model may be in several different collections at the same time. Collections usually contain a filtered subset of the data in your database, so adding and removing models from a collection is not equivalent to adding and removing data from your database. This is an important distinction between models and collections. Models usually have a direct mapping to the data in your database. You would normally persist changes in your model data to the server.
ADDING MODELS

You can add existing Backbone Models or raw objects to Collections. Raw objects will first be converted to models using the model constructor defined in the collection class (or the standard Backbone.Model if no model constructor is defined). There are three basic methods for adding models: add: adds a model or an array of models to the collection. push: adds a model to the end of the collection. 43

unshift: adds a model to the beginning of the collection. These three methods operate completely independent of your server; however, often you want to add a model to a collection and at the same time save that model to your server. Perhaps you have a commenting system and someone is adding a comment. You want the new comment to immmediatly be in your comments collection and then asynchronously saved to the server. To do this you would use collection.create. If you are syncing to a server with a REST API, then this method works as follows: Creates a model with the raw data - (triggers "change" events on the model) Add this model to the collection - (triggers an "add" event on the collection) Issues a POST request to the server to add the model data to your database (triggers a "request" event on the model) If the request is successful then trigger a "sync" event on the model If the request is successful then update the model with the response from the server (normally the server will return a unique id for the row / document) - (triggers "change" event on the model) If the request is unsuccesful, then an "error" event is triggered The above flow is an example of how Backbone helps you create ultra fast client-side applications. We can assume that most of your application's interactions with the server will be succesful. Therefore, there is no need to make the user wait while persisting data to the server. If there is an error then this can be handled by listening for the error events and notifying the user accordingly. If you would prefer to wait for the server's response before adding the model to the collection, then you can pass in {wait:true} as an option. Often you may have existing data in your collection, but you want to update it with the latest data from the server. Backbone provides two methods for this: reset - this replaces all of the models in the collection with the fresh models. update - this intelligently updates the collection based on the fresh data. New models are added, existing models are changed and any models that aren't in the fresh data are removed. Both reset and update can be called internally by the collections sync method. The default behaviour is to use reset, so each time you call fetch all models in your collection will be replaced. If {update:true} is passed as an option to the fetch method, the update method is used. As an example to keep the data in your collection up to date, you could set up a setInterval to fetch the data every one minute.
setInterval(function(){ issues.fetch({update:true}); }, 1 * 60 * 1000);

This code will run the fetch method every minute to get data from your server. We've passed update:true as an option, so every minute when the data is retrieved from the server it will be passed to the collection's update method. This method iterates through the data items and tries to match each item with existing models in the collection: If a match is found, then the model is updated with the fresh data from the server. "Change" events will be triggered on the model for any attributes that have changed. If no match is found, then the data is added as a new model and an "add" event is triggered. If there are models in the collection that haven't been matched to the incoming data they will be removed triggering a "remove" event. This allows you to efficiently update your views with only data that has been changed, rather than re-rendering your views every time data is fetched from the server. 44

In the Hubbub example appplication, we are working with the public GitHub API, which limits requests to sixty per hour. Because of this we haven't implemented any automatic fetching. Rather, fresh data is only fetched from the server when the user clicks the refresh icon for a repo. Now let's take a look at the various remove methods that Backbone provides.
REMOVE METHODS

These methods remove models from the collection (but don't destroy the model, or the model data). Again, remember that collections aren't an exact mapping to tables in your database. You can have multiple collections of the same data ordered and filtered in different ways. There are three removal methods: remove: removes the passed in model from the collection. pop: removes the last model in the collection and returns it. shift: removes the first model in the collection and returns it. All of the above methods will trigger a "remove" event on the collection. In some ways you can view collections as arrays of models. All the usual array operators are available: push, pop, shift, unshift, length, as well as many utility functions for filtering and manipulating the collection's models. We've looked at how to get models in and out of collections, now lets take a look at sorting and filtering models once they are within a collection. SORTING AND FILTERING As we've already mentioned, Backbone Collections inherit a variery of useful filtering and sorting methods from Underscore. We're going to illustrate the power and simplicity of these methods through a series of tests. As explained in the testing chapter, tests are an important part of keeping your application bug-free and maintainable. Collections are quite easy to test because you are dealing with data rather than user interaction. In our Hubbub application we need to be able to filter issues by their category and by which repo they belong to. Lets look at some examples of how to do this with some test data.
// test/generic.js var testData = [ {"id": 10338616, "title": "Comparator and fat arrow", "number": 2195, "repoId": 952189, "category": "doing", "comments": 5, "created_at": "2013-01-26T14:35:16Z"}, {"id": 10341232, "title": "trigger calls unbinded event handlers", "number": 2198, "repoId": 952189, "category": "todo", "comments": 21, "created_at": "2013-01-26T18:26:53Z"}, {"id": 10339785, "title": "All Code refactoring", "number": 2196, "repoId": 952189, "category": "doing", "comments": 1, "created_at": "2013-01-26T16:29:40Z"}, {"id": 10172489, "title": "Reverting changes from #2003 and 1f3f45252f", "number": 2173, "repoId": 952189, "category": "done", "comments": 3, "created_at": "2013-01-21T21:36:58Z"} ]; var issues = new app.Issue.Collection(testData);

The above code creates a new Issues Collection with the four sample issues from the testData array. The following are all available in the test suite: http://backstopmedia.github.com/hubbub/test/index.html To create the tests as you read along, create a file named "generic.js" in the test directory (these are generic Backbone tests rather than Hubub specific ones). Set up the test file with the following code: 45

window.jQuery(function () { var module = window.module; var _ = window._; var app = window.app; var ok = window.ok; var test = window.test; var equal = window.equal; var deepEqual = window.deepEqual; module("generic");

Then add the test data, and start adding the tests below.
TESTING SORTBY AND COMPARATOR // test/generic.js test('sort by title', function () { var issues = new app.Issue.Collection(testData); equal(issues.at(0).get("title"), "trigger calls unbinded event handlers"); // This is the first item before sorting var sorted = issues.sortBy("title"); equal(sorted[0].get("title"), "All Code refactoring"); // After sorting this item is first });

This test creates a new Issue Collection with the test data. We first ensure that the models are correctly ordered by "created_at" when they are added. If no comparator had been set then the first models would be the first item in the array. When a collection is instantiated any models added to it are kept in property named models . While it is possible to access this array of models directly its usually preferable to use the at method. So in the above test issues.at(0) is equivalent to issues.models[0]. sortBy is a method inherited from underscore.js. It accepts either an iterator function or a property key. For this simple sort, we can simply pass in "title", and a new array of models will be returned ordered by each model's title. Its important to note that this method and others like it don't affect the collection itself, they rather return a new array of filtered or sorted models.
TESTING SORTBY WITH AN ITERATOR

Here is another test with an iterator function rather than a property key. This time the models are ordered by the length of their titles.
// test/generic.js test('sort by longest title', function () { var issues = new app.Issue.Collection(testData); var sorted = issues.sortBy(function(issue) { return -1 * issue.get("title").length; }); equal(sorted[0].get("title"), "Reverting changes from #2003 and 1f3f45252f"); // After sorting this item is first });

As you can see it is fairly easy to sort the models in your collection. You could also create more complex sortBy interators to sort by two or three properties.

46

FILTERING COLLECTIONS

As well as sorting, Backbone Collections have several useful methods for filtering models. In our Hubbub application we need a collection that contains issue models that have the category: "done." For simple filters like this, we can use the where method. This accepts a hash of keys and values to test models against, e.g. issues.where({category:"done"}); This would return all models with a property of category that is equal to "done." For more complex queries we can use the following underscore methods. find: returns the first model that passes a test. filter: returns all models that pass a test. reject: returns all models that fail a test. The above three methods all take an iterator function, for example:
// test/generic.js test('filter by title contains "event"', function () { var issues = new app.Issue.Collection(testData); var filtered = issues.filter(function(issue) { return issue.get("title").indexOf("event") !== -1; }); equal(filtered.length, 1); // Only one model found equal(filtered[0].id, 10341232); // Confirm correct model is returned });

The function that is passed into the filter method is called with each model in the collection and should return the result of a test. If we had used reject rather than filter then the other three models would have been returned instead. You can use these functions to quickly and powerfully filter your collections. For example as the user is typing into a search box, you could filter a collection according to the text they are entering and update the results view instantly. If you are doing a lot of complex filtering on your collections then there are some Backbone plugins that abstract a lot of the logic away for you and provide a query API similar to MongoDB: https://github.com/bevry/query-engine https://github.com/davidgtonge/backbone_query.
GROUPING COLLECTIONS

Sometimes you need to group the models in a collection according to a model property. There are two methods you can use for this. groupBy - Groups the models into different arrays based on the return value of an iterator. countBy - The same as groupBy, but returns the number of models rather than an array of models. countBy is similar to groupBy but deals with a common use case, simply adding up how many models there are in each group. These methods could be used to find out how many issues there are in each of the categories:
// test/generic.js test('collection: groupBy & countBy', function () { var issues = new app.Issue.Collection(testData); var iterator = function(issue) { return issue.get("category"); }; var grouped = issues.groupBy(iterator); var counts = issues.countBy(iterator); deepEqual({ done:1, doing:2, todo:1 }, counts); equal(counts.doing, grouped.doing.length); });

47

Along with filtering, sorting, and grouping our collections, we need to be able to access the data in our collections, either to display to the user, or to use in our application logic. Let's take a look at some of the Underscore methods that can help with this. RETRIEVING DATA FROM COLLECTIONS Sometimes you need to retrieve specific values from each Model in a Collection, at other times you may want to check if a Collection meets certain criteria and perform an action accordingly. The next three methods don't return any specific data, rather they return true or false depending on if the collection passes a test.
METHODS RETURNING A BOOLEAN

These methods return a boolean: every or all: returns true if all the models pass a test some or any: returns true if any of the models pass a test include or contains: returns true if the collection contains a passed in model Again to illustrate these methods, let's run some tests on our sample data. In the following code we're going to check if our collection contains models with various category values.
// test/generic.js test('collection: some', function () { var issues = new app.Issue.Collection(testData); var hasToDoIssues = issues.some(function(issue) { return issue.get("category") === "todo"; }); var hasRejectedIssues = issues.some(function(issue) { return issue.get("category") === "rejected"; }); equal(hasToDoIssues, true); // This variable should be true as there are issues with a todo category equal(hasRejectedIssues, false); // This variable should be false as there are no issues with a rejected category });

You can see that "hasToDoIssues" is true because there is at least one issue with the category of "todo." "hasRejectedIssues" on the other hand is false, as our collection doesn't contain any models with the category of "rejected". Now let's look at getting data from each model in our collection.
METHODS RETURNING AN ARRAY

The next two methods iterate through all the models in a collection and return the requested properties as an array. map - returns a new array of the results of passing each model through an iterator pluck - returns an array of "plucked" values from a specific model property Pluck is a simpler version of map and often provides a succinct way of getting the data you require. Here is a test demonstrating the use of map and pluck to generate an array of issue numbers.
// test/generic.js test('map & pluck', function () { var issues = new app.Issue.Collection(testData); var mapped = issues.map(function(issue) { return issue.get("number"); }); var plucked = issues.pluck("number"); equal(plucked.toString(), mapped.toString()); // Mapped and plucked arrays should be the same equal(plucked.length, 4); // Should have 4 values equal(plucked[0], issues.at(0).get("number")); // First value should be equal to the number property of the

48

first model });

You can see that we used both methods to achieve the same goal. "Map" is more powerful however, you could perform various calculations in the iterator function and return the result of those, rather than a staight property value. The next set of methods return either a single value or a single model from a collection.
METHODS RETURNING A SINGLE VALUE

These methods are especially useful if your models contain properties with numerical values: reduce - iterates through all the models to return a single value. max - returns the model with the highest value returned from the passed in function. min - returns the model with the lowest value returned from the passed in function. You could use reduce to add up the total number of comments of all the issues in a collection:
// test/generic.js test('collection: reduce', function () { var issues = new app.Issue.Collection(testData); var reduceIterator = function(sum, issue) { return sum + issue.get("comments"); } var numberOfComments = issues.reduce(reduceIterator, 0); // We pass an iterator function and the starting "memo" value equal(numberOfComments, 30); // The total number of comments from all the issues. });

This method works by iterating through all of the models in the collection and adding the "comments" value to the result of the last iteration. We pass in 0 to the method, so that the first iteration has something to add to. Each iteration should return the adjusted value ready for the next iteration. max and min could be used to find the issue with the most or least comments:
// test/generic.js test('collection: max & min', function () { var issues = new app.Issue.Collection(testData); var iterator = function(issue) { return issue.get("comments"); }; var mostCommentsIssue = issues.max(iterator); // We can use the same iterator for both the min and max methods var leastCommentsIssue = issues.min(iterator); equal(mostCommentsIssue.id, 10341232); equal(leastCommentsIssue.id, 10339785); }); METHODS OPERATING ON ALL OF THE MODELS IN A COLLECTION

Sometimes you don't need to extract any data from your collection, rather you want to operate on all of the models within that collection. Backbone provides two methods for this: each - this is the most basic of the underscore iterators, it simply calls the passed in function on each model in the collection. invoke - this method calls the passed in method name on each model in the collection. The invoke method can help keep your code terse where you need to call the same function on all models. For example this code: issues.invoke('fetch'); will call fetch on all the models in the collection. This is clearer to read and less verbose than: issues.each(function(model) { model.fetch(); });. 49

Learning to use the underscore methods with your collections can hopefully keep your code clean and stable. Another important principle in keeping your application lean and maintainable is the correct usage of events. As we've discovered earlier, Backbone provides a powerful events system, lets take a look at some of the Collection specific events that are built into Backbone. EVENTS Events are an important part of Backbone Collections. Each collection has access to the same event methods as Models, Views and Routers. For an explanation of the various event methods (on, off, etc.) please refer to Chapter 3: Backbone Events. Events are a flexible way of strucuturing your app and help a lot with keeping your code modular. For example 1 or 20 views could listen to events on a collection, without you needing to change any of the code for that collection. You can trigger any custom events on your collections, but the following events are built into Backbone: add: when a model is added to the collection. remove: when a model is removed from the collection. reset: when the collections models have been replaced. sort: when the collection has been re-sorted. destroy: when a model in the collection is destroyed. request: when the collection has started a request to the server. sync: when a collection has been successfully synced with the server. Any event that is triggered on a model in the collection is also triggered on the collection itself. For example if the "category" attribute of a model is changed then "change:category" would be fired on that model and on the collection that it belongs to. In the Hubbub application, we use many of these events to ensure that collections stay in sync with views and with other collections. To illustrate how events work lets look at the setFilter method in the issue collection class. We use this method to create filtered collections for the different issue categories. The idea is that there is a "master" collection that contains all of the issues. There are then separate "filtered" collections that contain only a subset of those issues based on category. If the issue's categories weren't going to change, we could simply use the where method described above to filter the issues and add them to the appropriate collection. However we want the filtered collections to automatically stay updated when a model's category changes and when models are added or removed from the master collection:
// collections/issue.js setFilter: function(parent, testKey, testValue) { var self = this; var onAdd = function(model) { // only add to this collection if the model passes the filter if (model.get(testKey) === testValue) { self.add(model); } }; // If there are any existing models in the collection run them // through the add parent.each(onAdd); this.listenTo(parent, { // When a model is added to the parent, add it to this // collection if it matches the test key / value add: onAdd,

50

// When a model is removed from the parent, remove it from this // collection. // This method is a "no op" if the collection doesn't contain the model. remove: self.remove }); // Listen to change events on the models this.listenTo(parent, 'change:' + testKey, function(model, value) { if (value === testValue) { // If the new value matches the test value then add the model self.add(model); } else { // If the value doesn't match the test value then remove the model self.remove(model); } }); }

The method accepts a "master" (parent) collection and a key and value to test models against. Let's test the code to ensure that it works as planned:
// test/hubbub.js test('collection: setFilter method', function () { var issues = new app.Issue.Collection(testData); var done = new app.Issue.Collection(); done.setFilter(issues, "category", "done"); equal(done.length, 1);

// Contains just the one model with a done category issues.get(10172489).set("category","doing"); equal(done.length, 0); // Contains no models as the one model with a 'done' key had its category changed to doing issues.invoke("set", "category", "done"); equal(done.length, 4); // All 4 models are now, in the done collection }); You can see that issue models are automatically added to and removed from the "done" collection as the models' category attribute is changed. Once collections and views are set up to listen to the correct events, then simply changing an attribute on a model will cause everything to be updated correctly. CLASS METHODS Like Models, Collections allow you to define class-level properties/methods. Let's start by defining a Collection for our list of repositories:
// collections/repo.js (function (window) { 'use strict'; var app = window.app; var Backbone = window.Backbone; var Repo = app.Repo = app.Repo || {}; Repo.Collection = Backbone.Collection.extend({ model: Repo.Model, // sort by owner login name, then repo name comparator: 'full_name' }); })(this);

In Hubbub, we want to keep track of the list of repos the user has added to their board. Therefore, let's define a Repo Collection as a property on a Board:
// models/board.js initialize: function () { this.repos = new app.Repo.Collection(); // ... }

51

In this case, we are instantiating a Collection the normal way: with the new keyword. In Hubbub, we want our users to be able to search not only by a full Github repo path ("login/repoName"), but also to choose from all repos by a particular Github user. Let's use a factory method! A Factory method is a design pattern where objects ("products") are created through a proxy method. That function can also handle any special setup on or related to the new instance.
// test/hubbub.js test('app.Repo.Collection.withOwner() should set the `url`', function () var repos = app.Repo.Collection.withOwner('bob'); equal(_.result(repos, 'url'), 'https://api.github.com/users/bob/repos'); });

Now, let's define withOwner() as a method on the class, which creates and returns a specialized app.Repo.Collection for us:
// collections/repo.js Repo.Collection = Backbone.Collection.extend( // instance methods { // ... }, // class methods { withOwner: function (login) { var repos = new Repo.Collection(); repos.url = app.apiRoot + '/users/' + login + '/repos'; return repos; } } );

In this case, the factory method always returns an app.Repo.Collection, but you can imagine how it could create different kinds of classes, based on parameters provided, etc.
CONCLUSION

Backbone Collections allow you to easily group, sort and filter your models. It's important to see them as far more than an array of rows from your database. Rather collections are dynamic and live, they respond to and trigger their own events. They also often contain the URLs for syncing the client and server data. In the next chapter we will cover the use of persistence in Backbone with Sync, so that every page reload doesn't start your application over from scratch.

52

CHAPTER 7: SYNC
One of the most important parts of a web application is its ability to persist data. Without this persistence, every page reload would start the application over from scratch and, well, that's not very interesting. Backbone handles persistence agnostically through its sync method. This gives developers the flexibility to use AJAX, localStorage, web sockets, and/or any other data transfer protocol by simply overriding sync . This idea of encapsulating the data transfer method while not altering any of Backbone's core Model or Collection methods makes setting up client-to-server communication a breeze. Here is a figure representing this:

It's a good idea get a solid understanding of the sync algorithm before trying to override it. Failure to do so will leave your models and collections hanging high and dry in the data department. The best way to get a grasp of how sync works is to browse through the annotated source code, but we'll go over the major points you'll need to remember when writing your own sync method. After the basics, we can dive into Hubbub's sync method that uses both AJAX and localStorage for persistence. OVERVIEW The first thing to note about sync is its signature: (method, model, options) . These three arguments will give you all you need to create a request for the server. The first argument, method, will always be a CRUD string ('create' , 'read' , 'update' , or 'delete'). 'patch' was added in Backbone 0.9.9, but browser and server support for 'patch' is still a bit lacking, so we'll focus on the main four. The second argument, model, can be a bit misleading, but it will either be an instance of a Model (as the variable name implies) or an instance of a Collection. model is the instance we're going to be creating, reading, updating, or removing. The final argument is options . As with most Backbone methods, an options object is provided to pass along helpful information for tailoring your application. Common options for sync are success and error, which are callbacks that need to be invoked accordingly, if they exist. The next important piece of sync is data serialization. This is the process of taking a model (or collection ) with all of its attributes and methods and turning it into something the server can understand. The default method of serialization in Backbone is with JSON (JavaScript Object Notation). Calling a model 's toJSON method will extract this server-ready JSON. For simple models, the default toJSON will work just fine. For more complex attribute structures like those with nested models and collections, toJSON will most likely need to be overridden to format the data to the server's liking. 53

Once the serialized data is attained, it's time to act on the CRUD method . This part of sync will be where most of the customization is necessary as it is where the actual server request will be sent. This example will use AJAX because it is currently the most common. AJAX requests are composed of a few key properties, namely the method, url, data, and callbacks. Fortunately, the CRUD methods map very well to HTTP's request methods: CREATE POST READ GET UPDATE PUT DELETE DELETE The url for the request is also simple with Backbone. It can be attained by using the Underscore result method and applying it to the passed in model . result takes an object and a property string and returns the value of the property or, if the property is a function, the value of the function. For our case, this would look like: var url = _.result(model, 'url'); With the method and url set, the AJAX request is already half-way complete. The next thing we need is the request data, which just so happens to be the same data we serialized earlier with toJSON. Piece of cake. The final step is to bind the options.success and options.error callbacks to the request and send it off. Backbone sets the user's AJAX library of choice to Backbone.ajax, so all you have to do is invoke Backbone.ajax(options) . The default sync implementation also triggers a few events on the passed in model : 'request' - when the request is sent 'sync' - when the request succeeds 'error' - when the request fails These events can be helpful for telling views when to show or hide loading spinners, for example. HUBBUB'S SYNC Now that the basics of sync are covered, we can look at how Hubbub overrides sync to work for GitHub's API through AJAX as well as for the browser's localStorage. You'll want to take a look at the sync.js file in the root of the Hubbub repository as you read this section. The first decision we had to make was how to distinguish AJAX requests from localStorage. Initially, it was tempting to store a flag (something like 'localStorage: true') at the model level, but that quickly became a convenience that wouldn't work. The reason is that we wanted to be able to use one model to fetch from both AJAX and localStorage endpoints, not just one persistence method per model. The goal was to be able to fetch a repository or issue from GitHub, then save it locally, and then fetch it locally. It was especially important that we be able to cache results locally due to GitHub's relatively low API request limit of sixty per hour. The solution was to use sync's handy options argument. We decided to pass remote flag and use that to branch our sync logic into two paths: a standard AJAX request with the default sync method when remote is true, and a customized localStorage request when it's not. The following sections give a line-by-line explanation of our custom sync method.

54

AJAX

Obviously rewriting the default sync method for use in our AJAX case would be a pain, so we store the default sync in a local variable in our closure.
var sync = Backbone.sync;

By doing this, we can reassign Backbone.sync and still reuse the default sync Backbone provides for AJAX. Inside our sync function, this is the conditional we use to make the AJAX or localStorage decision.
if (method === 'read' && options.remote) { options.data = _.extend({per_page: 100}, options.data); return sync.apply(this, arguments); }

You can see that in the case of a 'read' method when the options.remote flag is truthy, we will set our default per_page option for GitHub and proxy the arguments to the original sync we stored earlier. You already know that the AJAX portion of our sync method works because it's the same as the original Backbone.sync, so now we'll focus on at the localStorage side of it.
LOCALSTORAGE

localStorage is essentially one big key-value store, saved in the client's browser, that persists across browsing sessions. It can't directly store complex JavaScript objects, but it can store strings. Fortunately our awesome serialization tactics (model.toJSON() and JSON.stringify(data) ) have already given us the ability to turn complex JavaScript objects into simple strings. We'll look at this in more detail later in the function. It's important to note that some older browsers do not support localStorage , but there are polyfills out there that can solve that problem. We didn't bother polluting Hubbub with extra code for the sake of a terser sample project (sorry old browsers). Overall localStorage is a great medium for storing data that doesn't need to persist on a server somewhere. It can hold more data than cookies and also doesn't come with the added overhead on every HTTP request. Setup window.localStorage is kind of a long variable name to type, so we store this behemoth as ls. res will hold the result we send to the options.success callback.
var ls = window.localStorage var res;

In an effort to reuse as many resources as we could for both the GitHub and localStorage API, we chose to use each model's urlRoot and a key. This provides a unique location for data to be stored for each type of model (user, repo, issue, etc.).
var url = _.result(model, 'urlRoot') || _.result(model, 'url');

The first thing to do before any CRUD operations can take place is to retrieve what already exists in localStorage. Here we get the string from localStorage and parse it into a real JavaScript object.
var models = ls.getItem(url); models = models ? JSON.parse(models) : {};

The next step is to act on the passed in method . The easiest way to branch logic based on a single value is through a switch statement, as you see here.
switch (method) {

55

CREATE and UPDATE The first case we handle is 'create' and 'update'. Since these two methods will take almost identical actions, we group them together.
case 'create': case 'update':

First we set the response to the result of the model's serialization method, toJSON . In general, our response will be what the server sends back, but in this case there is no server and we already know what the response should be.
res = model.toJSON(options);

This next bit only applies to the create case. In Backbone, the 'create' method is used when a model returns true for its isNew method. Unless you override it, isNew will simply check the existance of the model's id. The logic being if it has an id, it must exist and therefore we're 'update'-ing it, otherwise we must 'create' it. Normally an id is assigned to a model through that model's corresponding record in the database. Since we don't have a remote database, it's our job to assign the unique id manually. To do this, we simply start by assuming an available id (stored in our available variable) of 1. We then iterate through the existing models and increase our available ID if it has already been taken. Note the unary + operator placed before the id in the assignment. This operator is handy for converting non-numbers to numbers. Since the keys of our localStorage data are stored as strings, we want to convert them to numbers before assigning them as an id. Comparing numbers as strings would yield undesirable results, for example '2' > '11' is true, but 2 > 11 is false. Finally, assign the res ID to the available ID.
if (method === 'create') { var available = 1; for (var id in models) if (available <= id) available = +id + 1; res.id = available; }

The last thing we need to do before saving is update the models object with our model's data.
models[res.id] = res;

Now we override the old models with the modifed models using localStorage.setItem. Remember to JSON.stringify the data that's being saved, otherwise it is will be stored by the data's toString method. Try saving an empty object into localStorage and you'll see something like '[object Object]' is really what is stored.
ls.setItem(url, JSON.stringify(models)); break;

READ 'read' is the next case we handle, and it's probably the simplest. All we have to do in this case is make sure we return the right response. When the model passed to sync is actually a Model, we return the model with the requested model's id, or if it doesn't exists, an empty object. If the model isn't a Model, it must be a Collection, so return an array of all the models object values using Underscore's values method.
case 'read':

56

res = model instanceof Backbone.Model ? models[model.id] || {} : _.values(models); break;

DELETE The final CRUD method to handle is 'delete'. The response data for 'delete' isn't used because the model 's destroy method is just looking for a success or error callback, so we set it to an empty object. Then delete the model from the models object using its id as the key, and finally save it to localStorage with localStorage.setItem .
case 'delete': res = {}; delete models[model.id]; ls.setItem(url, JSON.stringify(models)); }

Callbacks The very last step in our sync function is to invoke the options.success or options.error callback. In our case, there is no possibility for an error like connection interruption or unauthorized use, so we'll always invoke options.success .
options.success(res);

And that's it! This is just one of the many ways sync can be leveraged to store data in multiple locations with multiple transports.
CONCLUSION

To recap, sync is the data bridge between the browser and persistent storage. The beauty lies in the fact that you can choose whatever persistent storage method you want and then easily tailor Backbone to work seemlessly with it. You should now have a good idea on how you could write your own AJAX, localStorage , or hybrid sync method. If you're looking for a challenge, try writing a sync method that works with web sockets. In the next chapter we will look at URL state and how Backbone.router manages that for us.

57

CHAPTER 8: ROUTING
While Models and Collections exist to keep track of the state of your data, you can think of URLs as keeping track of the state of navigation. In traditional web development, you would create separate HTML files, where they might be called index.html , about.html , books.html , etc. Navigating between pages caused the URL to change because it was actually loading a separate HTML file, and thus the URL would explicitly tell you where to access that resource. This concept is part of Representational State Transfer, or REST. HTML dynamically generated by a server can work in the same way: mysite.com/users/123 refers to a specific user page. In short: if you send a URL to a friend, you want them to basically see what you are seeing. In a single-page application, you are creating and removing views programatically, so you need to explicitly tell the browser when the navigational state has changed, and you need to be able to take a particular state representation (a URL) and render the correct thing on the screen. This is where the Backbone.Router comes in. BACKBONE.ROUTER
In Backbone, we use the Router class to manage (and sometimes trigger) these changes in navigational state. First, create a subclass:
// router.js (function () { 'use strict'; var app = window.app; var Backbone = window.Backbone; app.Router = Backbone.Router.extend({ }); })(); // index.html <!-- Add after "Router goes here." --> <!-- Note that Routers will typically be included after Models/Collections/Views. --> <script src='router.js'></script>

We put router.js in the top-level the directory, because in Hubbub (and for most single-page apps), you'll only have a single Router defined. Likewise, it will only be instantiated once, from your application entry-point. Add this next:
// index.js init: function () { // ... app.router = new app.Router(); Backbone.History.start(); }

Backbone.History is a singleton that manages setting/handling of URL changes, which it then delegates to the Router when there's a match. The routes are defined as a map in a Backbone.Router subclass, where particular "routing strings" (relative URLs) are connected to particular "actions" (methods). In our case, we have two navigational states: viewing the lists of Issues (which we will refer to as the "manage" action), and viewing a particular Issue. Set these up in our Router :
// router.js app.Router = Backbone.Router.extend({ routes: { // the base URL - "managing" the lists of issues '': 'manage', // an alias for the root path 'issues': 'manage', // viewing a particular issue

58

'issues/:id': 'issue' }, manage: function () { console.log('homepage'); }, issue: function (issueId) { // the :id matched from our route string will be passed in // as a parameter console.log('issue ' + issueId); } });

In your browser, open the Console and refresh the page - you should see "homepage" printed out. Add "#issues/6" to the end of the URL (so it will now be ".../hubbub/index.html#issues/6") and press Enter. You should now see "issue 6" in the Console. The Router worked! By default, Backbone uses hash-based navigation, though you can configure it to use pushState for newer browsers if you prefer... see the Backbone.history.start() documentation for more info. REFLECTING STATE Now that we have changes to the URL triggerering route actions, we need to update our view(s) to reflect the new state. Add the following:
// router.js app.Router = Backbone.Router.extend({ // ... initialize: function (options) { // pass in the main view this.mainView = options.mainView; }, manage: function () { this.mainView.closeModals(); }, issue: function (issueId) { this.mainView.showIssueModal(issueId); } );

Give it a try! Refresh the page and click on a particular Issue you should now not only see the modal appear, but the URL will change to reflect it. Huzzah! We now have URL changes triggering View changes via the Router . Notice that we have a very small amount of logic in our Router . We recommend having the actions call only a single method, because then it's easy to trigger that change of state from either the Router or elsewhere in the app. Another advantage of having Backbone update URLs is that your browser will keep track of history. Click the Back button to return to the "manage" view.
CONCLUSION

Now you see the power of Backbone.router , and the important role it plays tracking the state of navigation with your URLs in Backbone. In the next and final chapter of this book, we will cover topics such as modules, build tools, and how to prepare for production.

59

CHAPTER 9: MODULES, BUILD TOOLS, AND PREPARING FOR PRODUCTION


The code in the Hubbub project was organized in a very simple way using vanilla JavaScript without any special tools. This was done so that it was easy to get started with the project and understand how everything works. As you work on a real-world project, there are a variety of tools out there to help setup your project, organize your code, and build your project to be optimized for production. We'll walk you through a few of the important things to remember and some useful tools to help make it all manageable. Before we do that, though, let's look at fetching asynchronously and how to use Backbone.LayoutManager . FETCHING ASYNCHRONOUSLY
Up to now, all examples have been synchronous operations. This means we have not had to work around any I/O from network/file loading, timers, or anything else that may cause asynchronous behavior. This is desirable when working without an HTTP server serving your markup. Since browsers forbid asynchronous requests unless your application is served from a web server, it's less than ideal to teach this method in examples. The benefits of loading asynchronously through a method like AJAX is that you are able to break down templates into separate files with respective syntax highlighting, and thus you can easily precompile with existing build tools. An example of this fetching implementation could look something like:
var MyView = Backbone.View.extend({ // Assign the file path to fetch the template contents from. template: "templates/my-template.html", // Since this method is now asynchronous, pass a callback to know // when it's done. fetchTemplate: function(done) { // Use jQuery to asynchronously fetch the template. $.get(this.template, function(contents) { // Compile and send the template to the callback function. done(_.template(contents)); }, "text"); } }); // Create an instance to test fetch on. var myView = new MyView(); // To access the template function now you would need to pass a // callback to fetchTemplate. myView.fetchTemplate(function(template) { // Render the template. template({ some: "data" }); });

This should be enough to help get you started with the idea of fetching asynchronously with Backbone.
BACKBONE.LAYOUTMANAGER

We didn't cover this in Chapter 4: View , but let's take a quick look at an example of how a plugin View could look different from a typical Backbone.View:
var MyView = Backbone.Layout.extend({ template: "#someTemplate", // Tell the View what data to provide to your template. serialize: function() { return { model: this.model }; }, // Automatically have render defined for you, so just bind it to any event

60

// that should affect the View. initialize: function() { this.listenTo(this.model, "change", this.render); }, // Declaratively assign nested Views. views: { // Each line is a selector that exists within #someTemplate and a // View instance that will be rendered and inserted into the element // found by the selector. "header": new HeaderView(), "section": new ContentView(), "footer": new FooterView() }, // Can attach jQuery plugins after rendering is complete. afterRender: function() { this.$el.someJqueryPlugin(); } });

This is just enough to get your feet wet, but we felt it was important to show you a plugin View in Backbone. Now let's take a look at what is required with using Backbone on a production server. DIFFERENT GOALS IN DEVELOPMENT VS. PRODUCTION While of course you want your application to generally behave the same way on your production server as it did from your local development server, there are some pretty significant differences in goals to keep in mind. Let's look at the main differences and discuss how modules and build tools can add harmony to an otherwise conflicting situation.
DEVELOPMENT MODE

While you're writing code for your application, things should be setup in such a way that makes you, as a developer, the most efficient are: Code should be logically separated into smaller files that are more managable than one giant file with everything in it. This will help you find the code you need, allow you to focus on one part at a time, and it will also make it easier for other teammates to work on the project at the same time. There should be as little delay as possible between writing code and being able to test it (both running unit tests, and running your application in a browser). Make your code as readable (understandable) as possible by including plenty of whitespace and comments. Keep all required assets on your local development machine so that you can work on your application even without an Internet connection. This means not relying on a public CDN for jQuery, for example.
PRODUCTION

When code is running on your production server; however, things should be setup in such a way that provides the best experience for end users, which means doing whatever you can to improve performance. Some examples are: Files should be as small as possible (so they are downloaded faster). To remove every byte possible from your application, there should be no whitespace or comments. This is called minification. There should be as few downloads as possible, because there is overhead for each additional HTTP request, and because browsers have limits on the number of parallel connections it will make per hostname. Combining multiple source files into one file is called concatenation. Your code should be set to be cached by browsers (when it hasn't changed), so that a user's browser won't have to redownload the assets every time they visit your web application. Use a Content Delivery Network (CDN) for your static assets (JavaScript, CSS) so 61

they can be downloaded as quickly as possible. For the most commonly used libraries like jQuery, consider using a globally shared CDN (e.g., Google's Hosted Libraries) so that even your first-time visitors won't have to download a new copy of jQuery, most likely. Now let's discuss a few tools to make all of this easier. REQUIREJS RequireJS is an excellent way to split your code into AMD modules (separate files), define dependencies between them, and then load your multiple JavaScript modules/files asynchronously without anything breaking. In our Hubbub example, in index.html, you'll see we have a bunch of <script> tags. Browsers are forced to download and execute these files sequentially, which can be bad for performance. Using RequireJS (or another script loading tool) can allow multiple JavaScript files to be downloaded in parallel and then run in the correct order based on the defined dependencies needed for each module. In the example below, we'll define two AMD modules and show how one can show a dependency on the other. In models/issue.js:
define(['backbone'], function(Backbone) { var Issue = Backbone.Model.extend({ defaults: { name: 'Untitled' } // remainder of Issue Model definition here... }); } return Issue; });

In views/issue.js:
define(['backbone', 'models/issue'], function(Backbone, Issue) { var IssueView = Backbone.View.extend({ render: function() { this.$el.html('Name:' + this.model.get('name')); return this; } // remainder of Issue View definition here... }); } return IssueView; }); OPTIMIZING REQUIREJS PROJECTS WITH R.JS

Above we said that in development we wanted separate files for each logical section of code. RequireJS helps make this possible. But we also said that in production there should be as few files as possible. There's a tool, r.js, to help with this! If you've got Node installed on your computer, you can run this command before deploying your code to production:
$ node r.js -o baseUrl=. name=main out=main-built.js

If you want to learn more about RequireJS, r.js, and build optimizations, see RequireJS's Optimization page.

62

GRUNT
Grunt is a task-based command line build tool for JavaScript projects, that will help make easy many of the best practices described above. Here's an example "gruntfile" to configure grunt with a Backbone project. With grunt installed and configured, you could simply run: $ grunt release and Grunt will, based on the gruntfile linked above: lint your code, checking for common programming mistakes and enforcing best practices precompile JavaScript templates used by Backbone Views. concatenate each RequireJS/AMD module (using r.js) into one file minify that file, making it as small as possible by removing whitespace and comments, shortening variable names, etc. By integrating a tool like grunt and having a "build" process, you can get the best of both worlds (easy to work with development environment, fast application in production). If you use grunt, be sure to check out grunt-contrib and all the plugins listed on gruntjs.com. BACKBONE-BOILERPLATE TO GET STARTED WITH BACKBONE, REQUIREJS, AND GRUNT If you want to get started with using Backbone with RequireJS, we highly recommend checking out Backbone-Boilerplate. It nicely lays out: How your files and directories can be laid out Examples of RequireJS with Backbone.js classes An example RequireJS config file An example Grunt file already configured How to asynchronously fetch and compile templates for development
BRUNCH.IO

An alternative to Grunt is Brunch, an "application assembler" that comes with many Backbone based "skeletons". Like Grunt, it automates linting, concatenating, precompiling and minifying. It is a flexible build tool and works with RequireJS or a simpler CommonJS module syntax. It's configuration files are less verbose than Grunt's, and it's easy to get started with. One of the advantages of using BackboneJS in your project is that there is a large community and many excellent tools to choose from.
YEOMAN

Yeoman is a Google project that incorporates Grunt, but adds the following additional functionality: Package management via bower Built-in headless testing with PhantomJS Advanced scaffolding and generators
OTHER BUILD TOOLS

If you prefer standalone tools over Grunt, some other great tools include UglifyJS, Closure Compiler, YUI Compressor, and the Asset Pipeline in Ruby on Rails.

63

CONCLUSION

All of the different tools out there can be overwhelming when beginning your Backbone development adventures (especially when you're already trying to learn a new language or framework), so don't feel like you have to use them all at once. Sometimes it's best to work on a small project and structure to get things done quickly and simply (like our Hubbub app). Once you're comfortable with Backbone, you'll see the value some of these tools can provide. And if you sweat the details of performance, it can make a big difference in your user's happiness!

64

MADE WITH BOOKTYPE

Visit http://www.booktype.org

Вам также может понравиться