PageRenderTime 66ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/chapters/03-internals.md

https://github.com/bicccio/backbone-fundamentals
Markdown | 2021 lines | 1464 code | 557 blank | 0 comment | 0 complexity | 8978f4dca727f1f343e0e038990a4437 MD5 | raw file
  1. # Backbone Basics
  2. In this section, you'll learn the essentials of Backbone's models, views, collections, events, and routers. This isn't by any means a replacement for the official documentation, but it will help you understand many of the core concepts behind Backbone before you start building applications using it.
  3. ### Getting set up
  4. Before we dive into more code examples, let's define some boilerplate markup you can use to specify the dependencies Backbone requires. This boilerplate can be reused in many ways with little to no alteration and will allow you to run code from examples with ease.
  5. You can paste the following into your text editor of choice, replacing the commented line between the script tags with the JavaScript from any given example:
  6. ```html
  7. <!DOCTYPE HTML>
  8. <html>
  9. <head>
  10. <meta charset="UTF-8">
  11. <title>Title</title>
  12. </head>
  13. <body>
  14. <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
  15. <script src="http://documentcloud.github.com/underscore/underscore-min.js"></script>
  16. <script src="http://documentcloud.github.com/backbone/backbone-min.js"></script>
  17. <script>
  18. // Your code goes here
  19. </script>
  20. </body>
  21. </html>
  22. ```
  23. You can then save and run the file in your browser of choice, such as Chrome or Firefox. Alternatively, if you prefer working with an online code editor, [jsFiddle](http://jsfiddle.net/jnf8B/) and [jsBin](http://jsbin.com/iwiwox/1/edit) versions of this boilerplate are also available.
  24. Most examples can also be run directly from within the console in your browser's developer tools, assuming you've loaded the boilerplate HTML page so that Backbone and its dependencies are available for use.
  25. For Chrome, you can open up the DevTools via the Chrome menu in the top right hand corner: select "Tools > Developer Tools" or alternatively use the Control + Shift + I shortcut on Windows/Linux or Command + Option + I on Mac.
  26. ![](img/devtools.png)
  27. Next, switch to the Console tab, from where you can enter in and run any piece of JavaScript code by hitting the return key. You can also use the Console as a multi-line editor using the Shift + Enter shortcut on Windows, or Ctrl + Enter shortcut on Mac to move from the end of one line to the start of another.
  28. ## Models
  29. Backbone models contain data for an application as well as the logic around this data. For example, we can use a model to represent the concept of a todo item including its attributes like title (todo content) and completed (current state of the todo).
  30. Models can be created by extending `Backbone.Model` as follows:
  31. ```javascript
  32. var Todo = Backbone.Model.extend({});
  33. // We can then create our own concrete instance of a (Todo) model
  34. // with no values at all:
  35. var todo1 = new Todo();
  36. // Following logs: {}
  37. console.log(JSON.stringify(todo1));
  38. // or with some arbitrary data:
  39. var todo2 = new Todo({
  40. title: 'Check the attributes of both model instances in the console.',
  41. completed: true
  42. });
  43. // Following logs: {"title":"Check the attributes of both model instances in the console.","completed":true}
  44. console.log(JSON.stringify(todo2));
  45. ```
  46. #### Initialization
  47. The `initialize()` method is called when a new instance of a model is created. Its use is optional; however you'll see why it's good practice to use it below.
  48. ```javascript
  49. var Todo = Backbone.Model.extend({
  50. initialize: function(){
  51. console.log('This model has been initialized.');
  52. }
  53. });
  54. var myTodo = new Todo();
  55. // Logs: This model has been initialized.
  56. ```
  57. **Default values**
  58. There are times when you want your model to have a set of default values (e.g., in a scenario where a complete set of data isn't provided by the user). This can be set using a property called `defaults` in your model.
  59. ```javascript
  60. var Todo = Backbone.Model.extend({
  61. // Default todo attribute values
  62. defaults: {
  63. title: '',
  64. completed: false
  65. }
  66. });
  67. // Now we can create our concrete instance of the model
  68. // with default values as follows:
  69. var todo1 = new Todo();
  70. // Following logs: {"title":"","completed":false}
  71. console.log(JSON.stringify(todo1));
  72. // Or we could instantiate it with some of the attributes (e.g., with custom title):
  73. var todo2 = new Todo({
  74. title: 'Check attributes of the logged models in the console.'
  75. });
  76. // Following logs: {"title":"Check attributes of the logged models in the console.","completed":false}
  77. console.log(JSON.stringify(todo2));
  78. // Or override all of the default attributes:
  79. var todo3 = new Todo({
  80. title: 'This todo is done, so take no action on this one.',
  81. completed: true
  82. });
  83. // Following logs: {"title":"This todo is done, so take no action on this one.","completed":true}
  84. console.log(JSON.stringify(todo3));
  85. ```
  86. #### Getters & Setters
  87. **Model.get()**
  88. `Model.get()` provides easy access to a model's attributes.
  89. ```javascript
  90. var Todo = Backbone.Model.extend({
  91. // Default todo attribute values
  92. defaults: {
  93. title: '',
  94. completed: false
  95. }
  96. });
  97. var todo1 = new Todo();
  98. console.log(todo1.get('title')); // empty string
  99. console.log(todo1.get('completed')); // false
  100. var todo2 = new Todo({
  101. title: "Retrieved with model's get() method.",
  102. completed: true
  103. });
  104. console.log(todo2.get('title')); // Retrieved with model's get() method.
  105. console.log(todo2.get('completed')); // true
  106. ```
  107. If you need to read or clone all of a model's data attributes, use its `toJSON()` method. This method returns a copy of the attributes as an object (not a JSON string despite its name). (When `JSON.stringify()` is passed an object with a `toJSON()` method, it stringifies the return value of `toJSON()` instead of the original object. The examples in the previous section took advantage of this feature when they called `JSON.stringify()` to log model instances.)
  108. ```javascript
  109. var Todo = Backbone.Model.extend({
  110. // Default todo attribute values
  111. defaults: {
  112. title: '',
  113. completed: false
  114. }
  115. });
  116. var todo1 = new Todo();
  117. var todo1Attributes = todo1.toJSON();
  118. // Following logs: {"title":"","completed":false}
  119. console.log(todo1Attributes);
  120. var todo2 = new Todo({
  121. title: "Try these examples and check results in console.",
  122. completed: true
  123. });
  124. // logs: {"title":"Try these examples and check results in console.","completed":true}
  125. console.log(todo2.toJSON());
  126. ```
  127. **Model.set()**
  128. `Model.set()` sets a hash containing one or more attributes on the model. When any of these attributes alter the state of the model, a "change" event is triggered on it. Change events for each attribute are also triggered and can be bound to (e.g. `change:name`, `change:age`).
  129. ```javascript
  130. var Todo = Backbone.Model.extend({
  131. // Default todo attribute values
  132. defaults: {
  133. title: '',
  134. completed: false
  135. }
  136. });
  137. // Setting the value of attributes via instantiation
  138. var myTodo = new Todo({
  139. title: "Set through instantiation."
  140. });
  141. console.log('Todo title: ' + myTodo.get('title')); // Todo title: Set through instantiation.
  142. console.log('Completed: ' + myTodo.get('completed')); // Completed: false
  143. // Set single attribute value at a time through Model.set():
  144. myTodo.set("title", "Title attribute set through Model.set().");
  145. console.log('Todo title: ' + myTodo.get('title')); // Todo title: Title attribute set through Model.set().
  146. console.log('Completed: ' + myTodo.get('completed')); // Completed: false
  147. // Set map of attributes through Model.set():
  148. myTodo.set({
  149. title: "Both attributes set through Model.set().",
  150. completed: true
  151. });
  152. console.log('Todo title: ' + myTodo.get('title')); // Todo title: Both attributes set through Model.set().
  153. console.log('Completed: ' + myTodo.get('completed')); // Completed: true
  154. ```
  155. **Direct access**
  156. Models expose an `.attributes` attribute which represents an internal hash containing the state of that model. This is generally in the form of a JSON object similar to the model data you might find on the server but can take other forms.
  157. Setting values through the `.attributes` attribute on a model bypasses triggers bound to the model.
  158. Passing `{silent:true}` on change doesn't delay individual `"change:attr"` events. Instead they are silenced entirely:
  159. ```javascript
  160. var Person = new Backbone.Model();
  161. Person.set({name: 'Jeremy'}, {silent: true});
  162. console.log(!Person.hasChanged(0));
  163. // true
  164. console.log(!Person.hasChanged(''));
  165. // true
  166. ```
  167. Remember where possible it is best practice to use `Model.set()`, or direct instantiation as explained earlier.
  168. #### Listening for changes to your model
  169. If you want to receive a notification when a Backbone model changes you can bind a listener to the model for its change event. A convenient place to add listeners is in the `initialize()` function as shown below:
  170. ```javascript
  171. var Todo = Backbone.Model.extend({
  172. // Default todo attribute values
  173. defaults: {
  174. title: '',
  175. completed: false
  176. },
  177. initialize: function(){
  178. console.log('This model has been initialized.');
  179. this.on('change', function(){
  180. console.log('- Values for this model have changed.');
  181. });
  182. }
  183. });
  184. var myTodo = new Todo();
  185. myTodo.set('title', 'The listener is triggered whenever an attribute value changes.');
  186. console.log('Title has changed: ' + myTodo.get('title'));
  187. myTodo.set('completed', true);
  188. console.log('Completed has changed: ' + myTodo.get('completed'));
  189. myTodo.set({
  190. title: 'Changing more than one attribute at the same time only triggers the listener once.',
  191. completed: true
  192. });
  193. // Above logs:
  194. // This model has been initialized.
  195. // - Values for this model have changed.
  196. // Title has changed: The listener is triggered whenever an attribute value changes.
  197. // - Values for this model have changed.
  198. // Completed has changed: true
  199. // - Values for this model have changed.
  200. ```
  201. You can also listen for changes to individual attributes in a Backbone model. In the following example, we log a message whenever a specific attribute (the title of our Todo model) is altered.
  202. ```javascript
  203. var Todo = Backbone.Model.extend({
  204. // Default todo attribute values
  205. defaults: {
  206. title: '',
  207. completed: false
  208. },
  209. initialize: function(){
  210. console.log('This model has been initialized.');
  211. this.on('change:title', function(){
  212. console.log('Title value for this model has changed.');
  213. });
  214. },
  215. setTitle: function(newTitle){
  216. this.set({ title: newTitle });
  217. }
  218. });
  219. var myTodo = new Todo();
  220. // Both of the following changes trigger the listener:
  221. myTodo.set('title', 'Check what\'s logged.');
  222. myTodo.setTitle('Go fishing on Sunday.');
  223. // But, this change type is not observed, so no listener is triggered:
  224. myTodo.set('completed', true);
  225. console.log('Todo set as completed: ' + myTodo.get('completed'));
  226. // Above logs:
  227. // This model has been initialized.
  228. // Title value for this model has changed.
  229. // Title value for this model has changed.
  230. // Todo set as completed: true
  231. ```
  232. #### Validation
  233. Backbone supports model validation through `model.validate()`, which allows checking the attribute values for a model prior to setting them. By default, validation occurs when the model is persisted using the `save()` method or when `set()` is called if `{validate:true}` is passed as an argument.
  234. ```javascript
  235. var Person = new Backbone.Model({name: 'Jeremy'});
  236. // Validate the model name
  237. Person.validate = function(attrs) {
  238. if (!attrs.name) {
  239. return 'I need your name';
  240. }
  241. };
  242. // Change the name
  243. Person.set({name: 'Samuel'});
  244. console.log(Person.get('name'));
  245. // 'Samuel'
  246. // Remove the name attribute, force validation
  247. Person.unset('name', {validate: true});
  248. // false
  249. ```
  250. Above, we also use the `unset()` method, which removes an attribute by deleting it from the internal model attributes hash.
  251. Validation functions can be as simple or complex as necessary. If the attributes provided are valid, nothing should be returned from `.validate()`. If they are invalid, an error value should be returned instead.
  252. Should an error be returned:
  253. * An `invalid` event will triggered, setting the `validationError` property on the model with the value which is returned by this method.
  254. * `.save()` will not continue and the attributes of the model will not be modified on the server.
  255. A more complete validation example can be seen below:
  256. ```javascript
  257. var Todo = Backbone.Model.extend({
  258. defaults: {
  259. completed: false
  260. },
  261. validate: function(attribs){
  262. if(attribs.title === undefined){
  263. return "Remember to set a title for your todo.";
  264. }
  265. },
  266. initialize: function(){
  267. console.log('This model has been initialized.');
  268. this.on("invalid", function(model, error){
  269. console.log(error);
  270. });
  271. }
  272. });
  273. var myTodo = new Todo();
  274. myTodo.set('completed', true, {validate: true}); // logs: Remember to set a title for your todo.
  275. console.log('completed: ' + myTodo.get('completed')); // completed: false
  276. ```
  277. **Note**: the `attributes` object passed to the `validate` function represents what the attributes would be after completing the current `set()` or `save()`. This object is distinct from the current attributes of the model and from the parameters passed to the operation. Since it is created by shallow copy, it is not possible to change any Number, String, or Boolean attribute of the input within the function, but it *is* possible to change attributes in nested objects.
  278. An example of this (by @fivetanley) is available [here](http://jsfiddle.net/2NdDY/7/).
  279. ## Views
  280. Views in Backbone don't contain the HTML markup for your application; they contain the logic behind the presentation of the model's data to the user. This is usually achieved using JavaScript templating (e.g., Underscore Microtemplates, Mustache, jQuery-tmpl, etc.). A view's `render()` method can be bound to a model's `change()` event, enabling the view to instantly reflect model changes without requiring a full page refresh.
  281. #### Creating new views
  282. Creating a new view is relatively straightforward and similar to creating new models. To create a new View, simply extend `Backbone.View`. We introduced the sample TodoView below in the previous chapter; now let's take a closer look at how it works:
  283. ```javascript
  284. var TodoView = Backbone.View.extend({
  285. tagName: 'li',
  286. // Cache the template function for a single item.
  287. todoTpl: _.template( "An example template" ),
  288. events: {
  289. 'dblclick label': 'edit',
  290. 'keypress .edit': 'updateOnEnter',
  291. 'blur .edit': 'close'
  292. },
  293. // Re-render the titles of the todo item.
  294. render: function() {
  295. this.$el.html( this.todoTpl( this.model.toJSON() ) );
  296. this.input = this.$('.edit');
  297. return this;
  298. },
  299. edit: function() {
  300. // executed when todo label is double clicked
  301. },
  302. close: function() {
  303. // executed when todo loses focus
  304. },
  305. updateOnEnter: function( e ) {
  306. // executed on each keypress when in todo edit mode,
  307. // but we'll wait for enter to get in action
  308. }
  309. });
  310. var todoView = new TodoView();
  311. // log reference to a DOM element that corresponds to the view instance
  312. console.log(todoView.el); // logs <li></li>
  313. ```
  314. #### What is `el`?
  315. The central property of a view is `el` (the value logged in the last statement of the example). What is `el` and how is it defined?
  316. `el` is basically a reference to a DOM element and all views must have one. Views can use `el` to compose their element's content and then insert it into the DOM all at once, which makes for faster rendering because the browser performs the minimum required number of reflows and repaints.
  317. There are two ways to associate a DOM element with a view: a new element can be created for the view and subsequently added to the DOM or a reference can be made to an element which already exists in the page.
  318. If you want to create a new element for your view, set any combination of the following properties on the view: `tagName`, `id`, and `className`. A new element will be created for you by the framework and a reference to it will be available at the `el` property. If nothing is specified `tagName` defaults to `div`.
  319. In the example above, `tagName` is set to 'li', resulting in creation of an li element. The following example creates a ul element with id and class attributes:
  320. ```javascript
  321. var TodosView = Backbone.View.extend({
  322. tagName: 'ul', // required, but defaults to 'div' if not set
  323. className: 'container', // optional, you can assign multiple classes to this property like so: 'container homepage'
  324. id: 'todos', // optional
  325. });
  326. var todosView = new TodosView();
  327. console.log(todosView.el); // logs <ul id="todos" class="container"></ul>
  328. ```
  329. The above code creates the DOM element below but doesn't append it to the DOM.
  330. ```html
  331. <ul id="todos" class="container"></ul>
  332. ```
  333. If the element already exists in the page, you can set `el` as a CSS selector that matches the element.
  334. ```javascript
  335. el: '#footer'
  336. ```
  337. Alternatively, you can set `el` to an existing element when creating the view:
  338. ```javascript
  339. var todosView = new TodosView({el: $('#footer')});
  340. ```
  341. Note: When declaring a View, `options`, `el`, `tagName`, `id` and `className` may be defined as functions, if you want their values to be determined at runtime.
  342. **$el and $()**
  343. View logic often needs to invoke jQuery or Zepto functions on the `el` element and elements nested within it. Backbone makes it easy to do so by defining the `$el` property and `$()` function. The `view.$el` property is equivalent to `$(view.el)` and `view.$(selector)` is equivalent to `$(view.el).find(selector)`. In our TodosView example's render method, we see `this.$el` used to set the HTML of the element and `this.$()` used to find subelements of class 'edit'.
  344. **setElement**
  345. If you need to apply an existing Backbone view to a different DOM element `setElement` can be used for this purpose. Overriding this.el needs to both change the DOM reference and re-bind events to the new element (and unbind from the old).
  346. `setElement` will create a cached `$el` reference for you, moving the delegated events for a view from the old element to the new one.
  347. ```javascript
  348. // We create two DOM elements representing buttons
  349. // which could easily be containers or something else
  350. var button1 = $('<button></button>');
  351. var button2 = $('<button></button>');
  352. // Define a new view
  353. var View = Backbone.View.extend({
  354. events: {
  355. click: function(e) {
  356. console.log(view.el === e.target);
  357. }
  358. }
  359. });
  360. // Create a new instance of the view, applying it
  361. // to button1
  362. var view = new View({el: button1});
  363. // Apply the view to button2 using setElement
  364. view.setElement(button2);
  365. button1.trigger('click');
  366. button2.trigger('click'); // returns true
  367. ```
  368. The "el" property represents the markup portion of the view that will be rendered; to get the view to actually render to the page, you need to add it as a new element or append it to an existing element.
  369. ```javascript
  370. // We can also provide raw markup to setElement
  371. // as follows (just to demonstrate it can be done):
  372. var view = new Backbone.View;
  373. view.setElement('<p><a><b>test</b></a></p>');
  374. view.$('a b').html(); // outputs "test"
  375. ```
  376. **Understanding `render()`**
  377. `render()` is an optional function that defines the logic for rendering a template. We'll use Underscore's micro-templating in these examples, but remember you can use other templating frameworks if you prefer. Our example will reference the following HTML markup:
  378. ```html
  379. <!doctype html>
  380. <html lang="en">
  381. <head>
  382. <meta charset="utf-8">
  383. <title></title>
  384. <meta name="description" content="">
  385. </head>
  386. <body>
  387. <div id="todo">
  388. </div>
  389. <script type="text/template" id="item-template">
  390. <div>
  391. <input id="todo_complete" type="checkbox" <%= completed ? 'checked="checked"' : '' %>>
  392. <%= title %>
  393. </div>
  394. </script>
  395. <script src="underscore-min.js"></script>
  396. <script src="backbone-min.js"></script>
  397. <script src="jquery-min.js"></script>
  398. <script src="example.js"></script>
  399. </body>
  400. </html>
  401. ```
  402. The `_.template` method in Underscore compiles JavaScript templates into functions which can be evaluated for rendering. In the TodoView, I'm passing the markup from the template with id `item-template` to `_.template()` to be compiled and stored in the todoTpl property when the view is created.
  403. The `render()` method uses this template by passing it the `toJSON()` encoding of the attributes of the model associated with the view. The template returns its markup after using the model's title and completed flag to evaluate the expressions containing them. I then set this markup as the HTML content of the `el` DOM element using the `$el` property.
  404. Presto! This populates the template, giving you a data-complete set of markup in just a few short lines of code.
  405. A common Backbone convention is to return `this` at the end of `render()`. This is useful for a number of reasons, including:
  406. * Making views easily reusable in other parent views.
  407. * Creating a list of elements without rendering and painting each of them individually, only to be drawn once the entire list is populated.
  408. Let's try to implement the latter of these. The `render` method of a simple ListView which doesn't use an ItemView for each item could be written:
  409. ```javascript
  410. var ListView = Backbone.View.extend({
  411. render: function(){
  412. this.$el.html(this.model.toJSON());
  413. }
  414. });
  415. ```
  416. Simple enough. Let's now assume a decision is made to construct the items using an ItemView to provide enhanced behaviour to our list. The ItemView could be written:
  417. ```javascript
  418. var ItemView = Backbone.View.extend({
  419. events: {},
  420. render: function(){
  421. this.$el.html(this.model.toJSON());
  422. return this;
  423. }
  424. });
  425. ```
  426. Note the usage of `return this;` at the end of `render`. This common pattern enables us to reuse the view as a sub-view. We can also use it to pre-render the view prior to rendering. Using this requires that we make a change to our ListView's `render` method as follows:
  427. ```javascript
  428. var ListView = Backbone.View.extend({
  429. render: function(){
  430. // Assume our model exposes the items we will
  431. // display in our list
  432. var items = this.model.get('items');
  433. // Loop through each our items using the Underscore
  434. // _.each iterator
  435. _.each(items, function(item){
  436. // Create a new instance of the ItemView, passing
  437. // it a specific model item
  438. var itemView = new ItemView({ model: item });
  439. // The itemView's DOM element is appended after it
  440. // has been rendered. Here, the 'return this' is helpful
  441. // as the itemView renders its model. Later, we ask for
  442. // its output ("el")
  443. this.$el.append( itemView.render().el );
  444. }, this);
  445. }
  446. });
  447. ```
  448. **The `events` hash**
  449. The Backbone `events` hash allows us to attach event listeners to either `el`-relative custom selectors, or directly to `el` if no selector is provided. An event takes the form of a key-value pair `'eventName selector': 'callbackFunction'` and a number of DOM event-types are supported, including `click`, `submit`, `mouseover`, `dblclick` and more.
  450. ```javascript
  451. // A sample view
  452. var TodoView = Backbone.View.extend({
  453. tagName: 'li',
  454. // with an events hash containing DOM events
  455. // specific to an item:
  456. events: {
  457. 'click .toggle': 'toggleCompleted',
  458. 'dblclick label': 'edit',
  459. 'click .destroy': 'clear',
  460. 'blur .edit': 'close'
  461. },
  462. ```
  463. What isn't instantly obvious is that while Backbone uses jQuery's `.delegate()` underneath, it goes further by extending it so that `this` always refers to the current view object within callback functions. The only thing to really keep in mind is that any string callback supplied to the events attribute must have a corresponding function with the same name within the scope of your view.
  464. The declarative, delegated jQuery events means that you don't have to worry about whether a particular element has been rendered to the DOM yet or not. Usually with jQuery you have to worry about "presence or absence in the DOM" all the time when binding events.
  465. In our TodoView example, the edit callback is invoked when the user double-clicks a label element within the `el` element, updateOnEnter is called for each keypress in an element with class 'edit', and close executes when an element with class 'edit' loses focus. Each of these callback functions can use `this` to refer to the TodoView object.
  466. Note that you can also bind methods yourself using `_.bind(this.viewEvent, this)`, which is effectively what the value in each event's key-value pair is doing. Below we use `_.bind` to re-render our view when a model changes.
  467. ```javascript
  468. var TodoView = Backbone.View.extend({
  469. initialize: function() {
  470. this.model.bind('change', _.bind(this.render, this));
  471. }
  472. });
  473. ```
  474. `_.bind` only works on one method at a time, but supports currying and as it returns the bound function means that you can use `_.bind` on an anonymous function.
  475. ## Collections
  476. Collections are sets of Models and are created by extending `Backbone.Collection`.
  477. Normally, when creating a collection you'll also want to define a property specifying the type of model that your collection will contain, along with any instance properties required.
  478. In the following example, we create a TodoCollection that will contain our Todo models:
  479. ```javascript
  480. var Todo = Backbone.Model.extend({
  481. defaults: {
  482. title: '',
  483. completed: false
  484. }
  485. });
  486. var TodosCollection = Backbone.Collection.extend({
  487. model: Todo
  488. });
  489. var myTodo = new Todo({title:'Read the whole book', id: 2});
  490. // pass array of models on collection instantiation
  491. var todos = new TodosCollection([myTodo]);
  492. console.log("Collection size: " + todos.length); // Collection size: 1
  493. ```
  494. #### Adding and Removing Models
  495. The preceding example populated the collection using an array of models when it was instantiated. After a collection has been created, models can be added and removed using the `add()` and `remove()` methods:
  496. ```javascript
  497. var Todo = Backbone.Model.extend({
  498. defaults: {
  499. title: '',
  500. completed: false
  501. }
  502. });
  503. var TodosCollection = Backbone.Collection.extend({
  504. model: Todo,
  505. });
  506. var a = new Todo({ title: 'Go to Jamaica.'}),
  507. b = new Todo({ title: 'Go to China.'}),
  508. c = new Todo({ title: 'Go to Disneyland.'});
  509. var todos = new TodosCollection([a,b]);
  510. console.log("Collection size: " + todos.length);
  511. // Logs: Collection size: 2
  512. todos.add(c);
  513. console.log("Collection size: " + todos.length);
  514. // Logs: Collection size: 3
  515. todos.remove([a,b]);
  516. console.log("Collection size: " + todos.length);
  517. // Logs: Collection size: 1
  518. todos.remove(c);
  519. console.log("Collection size: " + todos.length);
  520. // Logs: Collection size: 0
  521. ```
  522. Note that `add()` and `remove()` accept both individual models and lists of models.
  523. Also note that when using `add()` on a collection, passing `{merge: true}` causes duplicate models to have their attributes merged in to the existing models, instead of being ignored.
  524. ```javascript
  525. var items = new Backbone.Collection;
  526. items.add([{ id : 1, name: "Dog" , age: 3}, { id : 2, name: "cat" , age: 2}]);
  527. items.add([{ id : 1, name: "Bear" }], {merge: true });
  528. items.add([{ id : 2, name: "lion" }]); // merge: false
  529. console.log(JSON.stringify(items.toJSON()));
  530. // [{"id":1,"name":"Bear","age":3},{"id":2,"name":"cat","age":2}]
  531. ```
  532. #### Retrieving Models
  533. There are a few different ways to retrieve a model from a collection. The most straight-forward is to use `Collection.get()` which accepts a single id as follows:
  534. ```javascript
  535. var myTodo = new Todo({title:'Read the whole book', id: 2});
  536. // pass array of models on collection instantiation
  537. var todos = new TodosCollection([myTodo]);
  538. var todo2 = todos.get(2);
  539. // Models, as objects, are passed by reference
  540. console.log(todo2 === myTodo); // true
  541. ```
  542. In client-server applications, collections contain models obtained from the server. Anytime you're exchanging data between the client and a server, you will need a way to uniquely identify models. In Backbone, this is done using the `id`, `cid`, and `idAttribute` properties.
  543. Each model in Backbone has an `id`, which is a unique identifier that is either an integer or string (e.g., a UUID). Models also have a `cid` (client id) which is automatically generated by Backbone when the model is created. Either identifier can be used to retrieve a model from a collection.
  544. The main difference between them is that the `cid` is generated by Backbone; it is helpful when you don't have a true id - this may be the case if your model has yet to be saved to the server or you aren't saving it to a database.
  545. The `idAttribute` is the identifying attribute of the model returned from the server (i.e., the `id` in your database). This tells Backbone which data field from the server should be used to populate the `id` property (think of it as a mapper). By default, it assumes `id`, but this can be customized as needed. For instance, if your server sets a unique attribute on your model named "userId" then you would set `idAttribute` to "userId" in your model definition.
  546. The value of a model's idAttribute should be set by the server when the model is saved. After this point you shouldn't need to set it manually, unless further control is required.
  547. Internally, `Backbone.Collection` contains an array of models enumerated by their `id` property, if the model instances happen to have one. When `collection.get(id)` is called, this array is checked for existence of the model instance with the corresponding `id`.
  548. ```javascript
  549. // extends the previous example
  550. var todoCid = todos.get(todo2.cid);
  551. // As mentioned in previous example,
  552. // models are passed by reference
  553. console.log(todoCid === myTodo); // true
  554. ```
  555. #### Listening for events
  556. As collections represent a group of items, we can listen for `add` and `remove` events which occur when models are added to or removed from a collection. Here's an example:
  557. ```javascript
  558. var TodosCollection = new Backbone.Collection();
  559. TodosCollection.on("add", function(todo) {
  560. console.log("I should " + todo.get("title") + ". Have I done it before? " + (todo.get("completed") ? 'Yeah!': 'No.' ));
  561. });
  562. TodosCollection.add([
  563. { title: 'go to Jamaica', completed: false },
  564. { title: 'go to China', completed: false },
  565. { title: 'go to Disneyland', completed: true }
  566. ]);
  567. // The above logs:
  568. // I should go to Jamaica. Have I done it before? No.
  569. // I should go to China. Have I done it before? No.
  570. // I should go to Disneyland. Have I done it before? Yeah!
  571. ```
  572. In addition, we're also able to bind to a `change` event to listen for changes to any of the models in the collection.
  573. ```javascript
  574. var TodosCollection = new Backbone.Collection();
  575. // log a message if a model in the collection changes
  576. TodosCollection.on("change:title", function(model) {
  577. console.log("Changed my mind! I should " + model.get('title'));
  578. });
  579. TodosCollection.add([
  580. { title: 'go to Jamaica.', completed: false, id: 3 },
  581. ]);
  582. var myTodo = TodosCollection.get(3);
  583. myTodo.set('title', 'go fishing');
  584. // Logs: Changed my mind! I should go fishing
  585. ```
  586. jQuery-style event maps of the form `obj.on({click: action})` can also be used. These can be clearer than needing three separate calls to `.on` and should align better with the events hash used in Views:
  587. ```javascript
  588. var Todo = Backbone.Model.extend({
  589. defaults: {
  590. title: '',
  591. completed: false
  592. }
  593. });
  594. var myTodo = new Todo();
  595. myTodo.set({title: 'Buy some cookies', completed: true});
  596. myTodo.on({
  597. 'change:title' : titleChanged,
  598. 'change:completed' : stateChanged
  599. });
  600. function titleChanged(){
  601. console.log('The title was changed!');
  602. }
  603. function stateChanged(){
  604. console.log('The state was changed!');
  605. }
  606. myTodo.set({title: 'Get the groceries'});
  607. // The title was changed!
  608. ```
  609. Backbone events also support a [once()](http://backbonejs.org/#Events-once) method, which ensures that a callback only fires one time when a notification arrives. It is similar to Node's [once](http://nodejs.org/api/events.html#events_emitter_once_event_listener), or jQuery's [one](http://api.jquery.com/one/). This is particularly useful for when you want to say "the next time something happens, do this".
  610. ```javascript
  611. // Define an object with two counters
  612. var TodoCounter = { counterA: 0, counterB: 0 };
  613. // Mix in Backbone Events
  614. _.extend(TodoCounter, Backbone.Events);
  615. // Increment counterA, triggering an event
  616. var incrA = function(){
  617. TodoCounter.counterA += 1;
  618. TodoCounter.trigger('event');
  619. };
  620. // Increment counterB
  621. var incrB = function(){
  622. TodoCounter.counterB += 1;
  623. };
  624. // Use once rather than having to explicitly unbind
  625. // our event listener
  626. TodoCounter.once('event', incrA);
  627. TodoCounter.once('event', incrB);
  628. // Trigger the event once again
  629. TodoCounter.trigger('event');
  630. // Check out output
  631. console.log(TodoCounter.counterA === 1); // true
  632. console.log(TodoCounter.counterB === 1); // true
  633. ```
  634. `counterA` and `counterB` should only have been incremented once.
  635. #### Resetting/Refreshing Collections
  636. Rather than adding or removing models individually, you might want to update an entire collection at once. `Collection.set()` takes an array of models and performs the necessary add, remove, and change operations required to update the collection.
  637. ```javascript
  638. var TodosCollection = new Backbone.Collection();
  639. TodosCollection.add([
  640. { id: 1, title: 'go to Jamaica.', completed: false },
  641. { id: 2, title: 'go to China.', completed: false },
  642. { id: 3, title: 'go to Disneyland.', completed: true }
  643. ]);
  644. // we can listen for add/change/remove events
  645. TodosCollection.on("add", function(model) {
  646. console.log("Added " + model.get('title'));
  647. });
  648. TodosCollection.on("remove", function(model) {
  649. console.log("Removed " + model.get('title'));
  650. });
  651. TodosCollection.on("change:completed", function(model) {
  652. console.log("Completed " + model.get('title'));
  653. });
  654. TodosCollection.set([
  655. { id: 1, title: 'go to Jamaica.', completed: true },
  656. { id: 2, title: 'go to China.', completed: false },
  657. { id: 4, title: 'go to Disney World.', completed: false }
  658. ]);
  659. // Above logs:
  660. // Removed go to Disneyland.
  661. // Completed go to Jamaica.
  662. // Added go to Disney World.
  663. ```
  664. If you need to simply replace the entire content of the collection then `Collection.reset()` can be used:
  665. ```javascript
  666. var TodosCollection = new Backbone.Collection();
  667. // we can listen for reset events
  668. TodosCollection.on("reset", function() {
  669. console.log("Collection reset.");
  670. });
  671. TodosCollection.add([
  672. { title: 'go to Jamaica.', completed: false },
  673. { title: 'go to China.', completed: false },
  674. { title: 'go to Disneyland.', completed: true }
  675. ]);
  676. console.log('Collection size: ' + TodosCollection.length); // Collection size: 3
  677. TodosCollection.reset([
  678. { title: 'go to Cuba.', completed: false }
  679. ]);
  680. // Above logs 'Collection reset.'
  681. console.log('Collection size: ' + TodosCollection.length); // Collection size: 1
  682. ```
  683. Another useful tip is to use `reset` with no arguments to clear out a collection completely. This is handy when dynamically loading a new page of results where you want to blank out the current page of results.
  684. ```javascript
  685. myCollection.reset();
  686. ```
  687. Note that using `Collection.reset()` doesn't fire any `add` or `remove` events. A `reset` event is fired instead as shown in the previous example. The reason you might want to use this is to perform super-optimized rendering in extreme cases where individual events are too expensive.
  688. Also note that listening to a [reset](http://backbonejs.org/#Collection-reset) event, the list of previous models is available in `options.previousModels`, for convenience.
  689. ```javascript
  690. var Todo = new Backbone.Model();
  691. var Todos = new Backbone.Collection([Todo])
  692. .on('reset', function(Todos, options) {
  693. console.log(options.previousModels);
  694. console.log([Todo]);
  695. console.log(options.previousModels[0] === Todo); // true
  696. });
  697. Todos.reset([]);
  698. ```
  699. An `update()` method is available for Collections (which is also available as an option to fetch) for "smart" updating of sets of models. This method attempts to perform smart updating of a collection using a specified list of models. When a model in this list isn't present in the collection, it is added. If it is, its attributes will be merged. Models which are present in the collection but not in the list are removed.
  700. ```javascript
  701. var theBeatles = new Collection(['john', 'paul', 'george', 'ringo']);
  702. theBeatles.update(['john', 'paul', 'george', 'pete']);
  703. // Fires a `remove` event for 'ringo', and an `add` event for 'pete'.
  704. // Updates any of john, paul and georges's attributes that may have
  705. // changed over the years.
  706. ```
  707. #### Underscore utility functions
  708. Backbone takes full advantage of its hard dependency on Underscore by making many of its utilities directly available on collections:
  709. **`forEach`: iterate over collections**
  710. ```javascript
  711. var Todos = new Backbone.Collection();
  712. Todos.add([
  713. { title: 'go to Belgium.', completed: false },
  714. { title: 'go to China.', completed: false },
  715. { title: 'go to Austria.', completed: true }
  716. ]);
  717. // iterate over models in the collection
  718. Todos.forEach(function(model){
  719. console.log(model.get('title'));
  720. });
  721. // Above logs:
  722. // go to Belgium.
  723. // go to China.
  724. // go to Austria.
  725. ```
  726. **`sortBy()`: sort a collection on a specific attribute**
  727. ```javascript
  728. // sort collection
  729. var sortedByAlphabet = Todos.sortBy(function (todo) {
  730. return todo.get("title").toLowerCase();
  731. });
  732. console.log("- Now sorted: ");
  733. sortedByAlphabet.forEach(function(model){
  734. console.log(model.get('title'));
  735. });
  736. // Above logs:
  737. // go to Austria.
  738. // go to Belgium.
  739. // go to China.
  740. ```
  741. **`map()`: iterate through a collection, mapping each value through a transformation function**
  742. ```javascript
  743. var count = 1;
  744. console.log(Todos.map(function(model){
  745. return count++ + ". " + model.get('title');
  746. }));
  747. // Above logs:
  748. //1. go to Belgium.
  749. //2. go to China.
  750. //3. go to Austria.
  751. ```
  752. **`min()`/`max()`: retrieve item with the min or max value of an attribute**
  753. ```javascript
  754. Todos.max(function(model){
  755. return model.id;
  756. }).id;
  757. Todos.min(function(model){
  758. return model.id;
  759. }).id;
  760. ```
  761. **`pluck()`: extract a specific attribute**
  762. ```javascript
  763. var captions = Todos.pluck('caption');
  764. // returns list of captions
  765. ```
  766. **`filter()`: filter a collection**
  767. *Filter by an array of model IDs*
  768. ```javascript
  769. var Todos = Backbone.Collection.extend({
  770. model: Todo,
  771. filterById: function(ids){
  772. return this.models.filter(
  773. function(c) {
  774. return _.contains(ids, c.id);
  775. })
  776. }
  777. });
  778. ```
  779. **`indexOf()`: return the item at a particular index within a collection**
  780. ```javascript
  781. var People = new Backbone.Collection;
  782. People.comparator = function(a, b) {
  783. return a.get('name') < b.get('name') ? -1 : 1;
  784. };
  785. var tom = new Backbone.Model({name: 'Tom'});
  786. var rob = new Backbone.Model({name: 'Rob'});
  787. var tim = new Backbone.Model({name: 'Tim'});
  788. People.add(tom);
  789. People.add(rob);
  790. People.add(tim);
  791. console.log(People.indexOf(rob) === 0); // true
  792. console.log(People.indexOf(tim) === 1); // true
  793. console.log(People.indexOf(tom) === 2); // true
  794. ```
  795. **`any()`: Confirm if any of the values in a collection pass an iterator truth test**
  796. ```javascript
  797. Todos.any(function(model){
  798. return model.id === 100;
  799. });
  800. // or
  801. Todos.some(function(model){
  802. return model.id === 100;
  803. });
  804. ```
  805. **`size()`: return the size of a collection**
  806. ```javascript
  807. Todos.size();
  808. // equivalent to
  809. Todos.length;
  810. ```
  811. **`isEmpty()`: determine whether a collection is empty**
  812. ```javascript
  813. var isEmpty = Todos.isEmpty();
  814. ```
  815. **`groupBy()`: group a collection into groups of like items**
  816. ```javascript
  817. var Todos = new Backbone.Collection();
  818. Todos.add([
  819. { title: 'go to Belgium.', completed: false },
  820. { title: 'go to China.', completed: false },
  821. { title: 'go to Austria.', completed: true }
  822. ]);
  823. // create groups of completed and incomplete models
  824. var byCompleted = Todos.groupBy('completed');
  825. var completed = new Backbone.Collection(byCompleted[true]);
  826. console.log(completed.pluck('title'));
  827. // logs: ["go to Austria."]
  828. ```
  829. In addition, several of the Underscore operations on objects are available as methods on Models.
  830. **`pick()`: extract a set of attributes from a model**
  831. ```javascript
  832. var Todo = Backbone.Model.extend({
  833. defaults: {
  834. title: '',
  835. completed: false
  836. }
  837. });
  838. var todo = new Todo({title: 'go to Austria.'});
  839. console.log(todo.pick('title'));
  840. // logs {title: "go to Austria"}
  841. ```
  842. **`omit()`: extract all attributes from a model except those listed**
  843. ```javascript
  844. var todo = new Todo({title: 'go to Austria.'});
  845. console.log(todo.omit('title'));
  846. // logs {completed: false}
  847. ```
  848. **`keys()` and `values()`: get lists of attribute names and values**
  849. ```javascript
  850. var todo = new Todo({title: 'go to Austria.'});
  851. console.log(todo.keys());
  852. // logs: ["title", "completed"]
  853. console.log(todo.values());
  854. //logs: ["go to Austria.", false]
  855. ```
  856. **`pairs()`: get list of attributes as [key, value] pairs**
  857. ```javascript
  858. var todo = new Todo({title: 'go to Austria.'});
  859. var pairs = todo.pairs();
  860. console.log(pairs[0]);
  861. // logs: ["title", "go to Austria."]
  862. console.log(pairs[1]);
  863. // logs: ["completed", false]
  864. ```
  865. **`invert()`: create object in which the values are keys and the attributes are values**
  866. ```javascript
  867. var todo = new Todo({title: 'go to Austria.'});
  868. console.log(todo.invert());
  869. // logs: {go to Austria.: "title", false: "completed"}
  870. ```
  871. The complete list of what Underscore can do can be found in its official [docs](http://documentcloud.github.com/underscore/).
  872. #### Chainable API
  873. Speaking of utility methods, another bit of sugar in Backbone is its support for Underscores `chain()` method. Chaining is a common idiom in object-oriented languages; a chain is a sequence of method calls on the same object that are performed in a single statement. While Backbone makes Underscore's array manipulation operations available as methods of Collection objects, they cannot be directly chained since they return arrays rather than the original Collection.
  874. Fortunately, the inclusion of Underscore's `chain()` method enables you to chain calls to these methods on Collections.
  875. The `chain()` method returns an object that has all of the Underscore array operations attached as methods which return that object. The chain ends with a call to the `value()` method which simply returns the resulting array value. In case you havent seen it before, the chainable API looks like this:
  876. ```javascript
  877. var collection = new Backbone.Collection([
  878. { name: 'Tim', age: 5 },
  879. { name: 'Ida', age: 26 },
  880. { name: 'Rob', age: 55 }
  881. ]);
  882. var filteredNames = collection.chain() // start chain, returns wrapper around collection's models
  883. .filter(function(item) { return item.get('age') > 10; }) // returns wrapped array excluding Tim
  884. .map(function(item) { return item.get('name'); }) // returns wrapped array containing remaining names
  885. .value(); // terminates the chain and returns the resulting array
  886. console.log(filteredNames); // logs: ['Ida', 'Rob']
  887. ```
  888. Some of the Backbone-specific methods do return `this`, which means they can be chained as well:
  889. ```javascript
  890. var collection = new Backbone.Collection();
  891. collection
  892. .add({ name: 'John', age: 23 })
  893. .add({ name: 'Harry', age: 33 })
  894. .add({ name: 'Steve', age: 41 });
  895. var names = collection.pluck('name');
  896. console.log(names); // logs: ['John', 'Harry', 'Steve']
  897. ```
  898. ## RESTful Persistence
  899. Thus far, all of our example data has been created in the browser. For most single page applications, the models are derived from a data set residing on a server. This is an area in which Backbone dramatically simplifies the code you need to write to perform RESTful synchronization with a server through a simple API on its models and collections.
  900. **Fetching models from the server**
  901. `Collections.fetch()` retrieves a set of models from the server in the form of a JSON array by sending an HTTP GET request to the URL specified by the collection's `url` property (which may be a function). When this data is received, a `set()` will be executed to update the collection.
  902. ```javascript
  903. var Todo = Backbone.Model.extend({
  904. defaults: {
  905. title: '',
  906. completed: false
  907. }
  908. });
  909. var TodosCollection = Backbone.Collection.extend({
  910. model: Todo,
  911. url: '/todos'
  912. });
  913. var todos = new TodosCollection();
  914. todos.fetch(); // sends HTTP GET to /todos
  915. ```
  916. **Saving models to the server**
  917. While Backbone can retrieve an entire collection of models from the server at once, updates to models are performed individually using the model's `save()` method. When `save()` is called on a model that was fetched from the server, it constructs a URL by appending the model's id to the collection's URL and sends an HTTP PUT to the server. If the model is a new instance that was created in the browser (i.e., it doesn't have an id) then an HTTP POST is sent to the collection's URL. `Collections.create()` can be used to create a new model, add it to the collection, and send it to the server in a single method call.
  918. ```javascript
  919. var Todo = Backbone.Model.extend({
  920. defaults: {
  921. title: '',
  922. completed: false
  923. }
  924. });
  925. var TodosCollection = Backbone.Collection.extend({
  926. model: Todo,
  927. url: '/todos'
  928. });
  929. var todos = new TodosCollection();
  930. todos.fetch();
  931. var todo2 = todos.get(2);
  932. todo2.set('title', 'go fishing');
  933. todo2.save(); // sends HTTP PUT to /todos/2
  934. todos.create({title: 'Try out code samples'}); // sends HTTP POST to /todos and adds to collection
  935. ```
  936. As mentioned earlier, a model's `validate()` method is called automatically by `save()` and will trigger an `invalid` event on the model if validation fails.
  937. **Deleting models from the server**
  938. A model can be removed from the containing collection and the server by calling its `destroy()` method. Unlike `Collection.remove()` which only removes a model from a collection, `Model.destroy()` will also send an HTTP DELETE to the collection's URL.
  939. ```javascript
  940. var Todo = Backbone.Model.extend({
  941. defaults: {
  942. title: '',
  943. completed: false
  944. }
  945. });
  946. var TodosCollection = Backbone.Collection.extend({
  947. model: Todo,
  948. url: '/todos'
  949. });
  950. var todos = new TodosCollection();
  951. todos.fetch();
  952. var todo2 = todos.get(2);
  953. todo2.destroy(); // sends HTTP DELETE to /todos/2 and removes from collection
  954. ```
  955. Calling `destroy` on a Model will return `false` if the model `isNew`:
  956. ```javascript
  957. var Todo = new Backbone.Model();
  958. console.log(Todo.destroy());
  959. // false
  960. ```
  961. **Options**
  962. Each RESTful API method accepts a variety of options. Most importantly, all methods accept success and error callbacks which can be used to customize the handling of server responses.
  963. Specifying the `{patch: true}` option to `Model.save()` will cause it to use HTTP PATCH to send only the changed attributes (i.e partial updates) to the server instead of the entire model i.e `model.save(attrs, {patch: true})`:
  964. ```javascript
  965. // Save partial using PATCH
  966. model.clear().set({id: 1, a: 1, b: 2, c: 3, d: 4});
  967. model.save();
  968. model.save({b: 2, d: 4}, {patch: true});
  969. console.log(this.syncArgs.method);
  970. // 'patch'
  971. ```
  972. Similarly, passing the `{reset: true}` option to `Collection.fetch()` will result in the collection being updated using `reset()` rather than `set()`.
  973. See the Backbone.js documentation for full descriptions of the supported options.
  974. ## Events
  975. Events are a basic inversion of control. Instead of having one function call another by name, the second function is registered as a handler to be called when a specific event occurs.
  976. The part of your application that has to know how to call the other part of your app has been inverted. This is the core thing that makes it possible for your business logic to not have to know about how your user interface works and is the most powerful thing about the Backbone Events system.
  977. Mastering events is one of the quickest ways to become more productive with Backbone, so let's take a closer look at Backbone's event model.
  978. `Backbone.Events` is mixed into the other Backbone "classes", including:
  979. * Backbone
  980. * Backbone.Model
  981. * Backbone.Collection
  982. * Backbone.Router
  983. * Backbone.History
  984. * Backbone.View
  985. Note that `Backbone.Events` is mixed into the `Backbone` object. Since `Backbone` is globally visible, it can be used as a simple event bus:
  986. ```javascript
  987. Backbone.on('event', function() {console.log('Handled Backbone event');});
  988. Backbone.trigger('event'); // logs: Handled Backbone event
  989. ```
  990. #### on(), off(), and trigger()
  991. `Backbone.Events` can give any object the ability to bind and trigger custom events. We can mix this module into any object easily and there isn't a requirement for events to be declared before being bound to a callback handler.
  992. Example:
  993. ```javascript
  994. var ourObject = {};
  995. // Mixin
  996. _.extend(ourObject, Backbone.Events);
  997. // Add a custom event
  998. ourObject.on('dance', function(msg){
  999. console.log('We triggered ' + msg);
  1000. });
  1001. // Trigger the custom event
  1002. ourObject.trigger('dance', 'our event');
  1003. ```
  1004. If you're familiar with jQuery custom events or the concept of Publish/Subscribe, `Backbone.Events` provides a system that is very similar with `on` being analogous to `subscribe` and `trigger` being similar to `publish`.
  1005. `on` binds a callback function to an object, as we've done with `dance` in the above example. The callback is invoked whenever the event is triggered.
  1006. The official Backbone.js documentation recommends namespacing event names using colons if you end up using quite a few of these on your page. e.g.:
  1007. ```javascript
  1008. var ourObject = {};
  1009. // Mixin
  1010. _.extend(ourObject, Backbone.Events);
  1011. function dancing (msg) { console.log("We started " + msg); }
  1012. // Add namespaced custom events
  1013. ourObject.on("dance:tap", dancing);
  1014. ourObject.on("dance:break", dancing);
  1015. // Trigger the custom events
  1016. ourObject.trigger("dance:tap", "tap dancing. Yeah!");
  1017. ourObject.trigger("dance:break", "break dancing. Yeah!");
  1018. // This one triggers nothing as no listener listens for it
  1019. ourObject.trigger("dance", "break dancing. Yeah!");
  1020. ```
  1021. A special `all` event is made available in case you would like notifications for every event that occurs on the object (e.g., if you would like to screen events in a single location). The `all` event can be used as follows:
  1022. ```javascript
  1023. var ourObject = {};
  1024. // Mixin
  1025. _.extend(ourObject, Backbone.Events);
  1026. function dancing (msg) { console.log("We started " + msg); }
  1027. ourObject.on("all", function(eventName){
  1028. console.log("The name of the event passed was " + eventName);
  1029. });
  1030. // This time each event will be caught with a catch 'all' event listener
  1031. ourObject.trigger("dance:tap", "tap dancing. Yeah!");
  1032. ourObject.trigger("dance:break", "break dancing. Yeah!");
  1033. ourObject.trigger("dance", "break dancing. Yeah!");
  1034. ```
  1035. `off` removes callback functions that were previously bound to an object. Going back to our Publish/Subscribe comparison, think of it as an `unsubscribe` for custom events.
  1036. To remove the `dance` event we previously bound to `ourObject`, we would simply do:
  1037. ```javascript
  1038. var ourObject = {};
  1039. // Mixin
  1040. _.extend(ourObject, Backbone.Events);
  1041. function dancing (msg) { console.log("We " + msg); }
  1042. // Add namespaced custom events
  1043. ourObject.on("dance:tap", dancing);
  1044. ourObject.on("dance:break", dancing);
  1045. // Trigger the custom events. Each will be caught and acted upon.
  1046. ourObject.trigger("dance:tap", "started tap dancing. Yeah!");
  1047. ourObject.trigger("dance:break", "started break dancing. Yeah!");
  1048. // Removes event bound to the object
  1049. ourObject.off("dance:tap");
  1050. // Trigger the custom events again, but one is logged.
  1051. ourObject.trigger("dance:tap", "stopped tap dancing."); // won't be logged as it's not listened for
  1052. ourObject.trigger("dance:break", "break dancing. Yeah!");
  1053. ```
  1054. To remove all callbacks for the event we pass an event name (e.g., `move`) to the `off()` method on the object the event is bound to. If we wish to remove a specific callback, we can pass that callback as the second parameter:
  1055. ```javascript
  1056. var ourObject = {};
  1057. // Mixin
  1058. _.extend(ourObject, Backbone.Events);
  1059. function dancing (msg) { console.log("We are dancing. " + msg); }
  1060. function jumping (msg) { console.log("We are jumping. " + msg); }
  1061. // Add two listeners to the same event
  1062. ourObject.on("move", dancing);
  1063. ourObject.on("move", jumping);
  1064. // Trigger the events. Both listeners are called.
  1065. ourObject.trigger("move", "Yeah!");
  1066. // Removes specified listener
  1067. ourObject.off("move", dancing);
  1068. // Trigger the events again. One listener left.
  1069. ourObject.trigger("move", "Yeah, jump, jump!");
  1070. ```
  1071. Finally, as we have seen in our previous examples, `trigger` triggers a callback for a specified event (or a space-separated list of events). e.g.:
  1072. ```javascript
  1073. var ourObject = {};
  1074. // Mixin
  1075. _.extend(ourObject, Backbone.Events);
  1076. function doAction (msg) { console.log("We are " + msg); }
  1077. // Add event listeners
  1078. ourObject.on("dance", doAction);
  1079. ourObject.on("jump", doAction);
  1080. ourObject.on("skip", doAction);
  1081. // Single event
  1082. ourObject.trigger("dance", 'just dancing.');
  1083. // Multiple events
  1084. ourObject.trigger("dance jump skip", 'very tired from so much action.');
  1085. ```
  1086. `trigger` can pass multiple arguments to the callback function:
  1087. ```javascript
  1088. var ourObject = {};
  1089. // Mixin
  1090. _.extend(ourObject, Backbone.Events);
  1091. function doAction (action, duration) {
  1092. console.log("We are " + action + ' for ' + duration );
  1093. }
  1094. // Add event listeners
  1095. ourObject.on("dance", doAction);
  1096. ourObject.on("jump", doAction);
  1097. ourObject.on("skip", doAction);
  1098. // Passing multiple arguments to single event
  1099. ourObject.trigger("dance", 'dancing', "5 minutes");
  1100. // Passing multiple arguments to multiple events
  1101. ourObject.trigger("dance jump skip", 'on fire', "15 minutes");
  1102. ```
  1103. #### listenTo() and stopListening()
  1104. While `on()` and `off()` add callbacks directly to an observed object, `listenTo()` tells an object to listen for events on another object, allowing the listener to keep track of the events for which it is listening. `stopListening()` can subsequently be called on the listener to tell it to stop listening for events:
  1105. ```javascript
  1106. var a = _.extend({}, Backbone.Events);
  1107. var b = _.extend({}, Backbone.Events);
  1108. var c = _.extend({}, Backbone.Events);
  1109. // add listeners to A for events on B and C
  1110. a.listenTo(b, 'anything', function(event){ console.log("anything happened"); });
  1111. a.listenTo(c, 'everything', function(event){ console.log("everything happened"); });
  1112. // trigger an event
  1113. b.trigger('anything'); // logs: anything happened
  1114. // stop listening
  1115. a.stopListening();
  1116. // A does not receive these events
  1117. b.trigger('anything');
  1118. c.trigger('everything');
  1119. ```
  1120. `stopListening()` can also be used to selectively stop listening based on the event, model, or callback handler.
  1121. If you use `on` and `off` and remove views and their corresponding models at the same time, there are generally no problems. But a problem arises when you remove a view that had registered to be notified about events on a model, but you don't remove the model or call `off` to remove the view's event handler. Since the model has a reference to the view's callback function, the JavaScript garbage collector cannot remove the view from memory. This is called a "ghost view" and is a form of memory leak which is common since the models generally tend to outlive the corresponding views during an application's lifecycle. For details on the topic and a solution, check this [excellent article](http://lostechies.com/derickbailey/2011/09/15/zombies-run-managing-page-transitions-in-backbone-apps/) by Derick Bailey.
  1122. Practically, every `on` called on an object also requires an `off` to be called in order for the garbage collector to do its job. `listenTo()` changes that, allowing Views to bind to Model notifications and unbind from all of them with just one call - `stopListening()`.
  1123. The default implementation of `View.remove()` makes a call to `stopListening()`, ensuring that any listeners bound using `listenTo()` are unbound before the view is destroyed.
  1124. ```javascript
  1125. var view = new Backbone.View();
  1126. var b = _.extend({}, Backbone.Events);
  1127. view.listenTo(b, 'all', function(){ console.log(true); });
  1128. b.trigger('anything'); // logs: true
  1129. view.listenTo(b, 'all', function(){ console.log(false); });
  1130. view.remove(); // stopListening() implicitly called
  1131. b.trigger('anything'); // does not log anything
  1132. ```
  1133. #### Events and Views
  1134. Within a View, there are two types of events you can listen for: DOM events and events triggered using the Event API. It is important to understand the differences in how views bind to these events and the context in which their callbacks are invoked.
  1135. DOM events can be bound to using the View's `events` property or using `jQuery.on()`. Within callbacks bound using the `events` property, `this` refers to the View object; whereas any callbacks bound directly using jQuery will have `this` set to the handling DOM element by jQuery. All DOM event callbacks are passed an `event` object by jQuery. See `delegateEvents()` in the Backbone documentation for additional details.
  1136. Event API events are bound as described in this section. If the event is bound using `on()` on the observed object, a context parameter can be passed as the third argument. If the event is bound using `listenTo()` then within the callback `this` refers to the listener. The arguments passed to Event API callbacks depends on the type of event. See the Catalog of Events in the Backbone documentation for details.
  1137. The following example illustrates these differences:
  1138. ```html
  1139. <div id="todo">
  1140. <input type='checkbox' />
  1141. </div>
  1142. ```
  1143. ```javascript
  1144. var View = Backbone.View.extend({
  1145. el: '#todo',
  1146. // bind to DOM event using events property
  1147. events: {
  1148. 'click [type="checkbox"]': 'clicked',
  1149. },
  1150. initialize: function () {
  1151. // bind to DOM event using jQuery
  1152. this.$el.click(this.jqueryClicked);
  1153. // bind to API event
  1154. this.on('apiEvent', this.callback);
  1155. },
  1156. // 'this' is view
  1157. clicked: function(event) {
  1158. console.log("events handler for " + this.el.outerHTML);
  1159. this.trigger('apiEvent', event.type);
  1160. },
  1161. // 'this' is handling DOM element
  1162. jqueryClicked: function(event) {
  1163. console.log("jQuery handler for " + this.outerHTML);
  1164. },
  1165. callback: function(eventType) {
  1166. console.log("event type was " + eventType);
  1167. }
  1168. });
  1169. var view = new View();
  1170. ```
  1171. ## Routers
  1172. In Backbone, routers provide a way for you to connect URLs (either hash fragments, or
  1173. real) to parts of your application. Any piece of your application that you want
  1174. to be bookmarkable, shareable, and back-button-able, needs a URL.
  1175. Some examples of routes using the hash mark may be seen below:
  1176. ```javascript
  1177. http://example.com/#about
  1178. http://example.com/#search/seasonal-horns/page2
  1179. ```
  1180. An application will usually have at least one route mapping a URL route to a function that determines what happens when a user reaches that route. This relationship is defined as follows:
  1181. ```javascript
  1182. 'route' : 'mappedFunction'
  1183. ```
  1184. Let's define our first router by extending `Backbone.Router`. For the purposes of this guide, we're going to continue pretending we're creating a complex todo application (something like a personal organizer/planner) that requires a complex TodoRouter.
  1185. Note the inline comments in the code example below as they continue our lesson on routers.
  1186. ```javascript
  1187. var TodoRouter = Backbone.Router.extend({
  1188. /* define the route and function maps for this router */
  1189. routes: {
  1190. "about" : "showAbout",
  1191. /* Sample usage: http://example.com/#about */
  1192. "todo/:id" : "getTodo",
  1193. /* This is an example of using a ":param" variable which allows us to match
  1194. any of the components between two URL slashes */
  1195. /* Sample usage: http://example.com/#todo/5 */
  1196. "search/:query" : "searchTodos",
  1197. /* We can also define multiple routes that are bound to the same map function,
  1198. in this case searchTodos(). Note below how we're optionally passing in a
  1199. reference to a page number if one is supplied */
  1200. /* Sample usage: http://example.com/#search/job */
  1201. "search/:query/p:page" : "searchTodos",
  1202. /* As we can see, URLs may contain as many ":param"s as we wish */
  1203. /* Sample usage: http://example.com/#search/job/p1 */
  1204. "todos/:id/download/*documentPath" : "downloadDocument",
  1205. /* This is an example of using a *splat. Splats are able to match any number of
  1206. URL components and can be combined with ":param"s*/
  1207. /* Sample usage: http://example.com/#todos/5/download/files/Meeting_schedule.doc */
  1208. /* If you wish to use splats for anything beyond default routing, it's probably a good
  1209. idea to leave them at the end of a URL otherwise you may need to apply regular
  1210. expression parsing on your fragment */
  1211. "*other" : "defaultRoute"
  1212. /* This is a default route that also uses a *splat. Consider the
  1213. default route a wildcard for URLs that are either not matched or where
  1214. the user has incorrectly typed in a route path manually */
  1215. /* Sample usage: http://example.com/# <anything> */,
  1216. "optional(/:item)": "optionalItem",
  1217. "named/optional/(y:z)": "namedOptionalItem"
  1218. /* Router URLs also support optional parts via parentheses, without having
  1219. to use a regex. */
  1220. },
  1221. showAbout: function(){
  1222. },
  1223. getTodo: function(id){
  1224. /*
  1225. Note that the id matched in the above route will be passed to this function
  1226. */
  1227. console.log("You are trying to reach todo " + id);
  1228. },
  1229. searchTodos: function(query, page){
  1230. var page_number = page || 1;
  1231. console.log("Page number: " + page_number + " of the results for todos containing the word: " + query);
  1232. },
  1233. downloadDocument: function(id, path){
  1234. },
  1235. defaultRoute: function(other){
  1236. console.log('Invalid. You attempted to reach:' + other);
  1237. }
  1238. });
  1239. /* Now that we have a router setup, we need to instantiate it */
  1240. var myTodoRouter = new TodoRouter();
  1241. ```
  1242. Backbone offers an opt-in for HTML5 pushState support via `window.history.pushState`. This permits you to define routes such as http://backbonejs.org/just/an/example. This will be supported with automatic degradation when a user's browser doesn't support pushState. Note that it is vastly preferred if you're capable of also supporting pushState on the server side, although it is a little more difficult to implement.
  1243. **Is there a limit to the number of routers I should be using?**
  1244. Andrew de Andrade has pointed out that DocumentCloud, the creators of Backbone, usually only use a single router in most of their applications. You're very likely to not require more than one or two routers in your own projects; the majority of your application routing can be kept organized in a single router without it getting unwieldy.
  1245. #### Backbone.history
  1246. Next, we need to initialize `Backbone.history` as it handles `hashchange` events in our application. This will automatically handle routes that have been defined and trigger callbacks when they've been accessed.
  1247. The `Backbone.history.start()` method will simply tell Backbone that it's okay to begin monitoring all `hashchange` events as follows:
  1248. ```javascript
  1249. var TodoRouter = Backbone.Router.extend({
  1250. /* define the route and function maps for this router */
  1251. routes: {
  1252. "about" : "showAbout",
  1253. "search/:query" : "searchTodos",
  1254. "search/:query/p:page" : "searchTodos"
  1255. },
  1256. showAbout: function(){},
  1257. searchTodos: function(query, page){
  1258. var page_number = page || 1;
  1259. console.log("Page number: " + page_number + " of the results for todos containing the word: " + query);
  1260. }
  1261. });
  1262. var myTodoRouter = new TodoRouter();
  1263. Backbone.history.start();
  1264. // Go to and check console:
  1265. // http://localhost/#search/job/p3 logs: Page number: 3 of the results for todos containing the word: job
  1266. // http://localhost/#search/job logs: Page number: 1 of the results for todos containing the word: job
  1267. // etc.
  1268. ```
  1269. Note: To run the last example, you'll need to create a local development environment and test project, which we will cover later on in the book.
  1270. If you would like to update the URL to reflect the application state at a particular point, you can use the router's `.navigate()` method. By default, it simply updates your URL fragment without triggering the `hashchange` event:
  1271. ```javascript
  1272. // Let's imagine we would like a specific fragment (edit) once a user opens a single todo
  1273. var TodoRouter = Backbone.Router.extend({
  1274. routes: {
  1275. "todo/:id": "viewTodo",
  1276. "todo/:id/edit": "editTodo"
  1277. // ... other routes
  1278. },
  1279. viewTodo: function(id){
  1280. console.log("View todo requested.");
  1281. this.navigate("todo/" + id + '/edit'); // updates the fragment for us, but doesn't trigger the route
  1282. },
  1283. editTodo: function(id) {
  1284. console.log("Edit todo opened.");
  1285. }
  1286. });
  1287. var myTodoRouter = new TodoRouter();
  1288. Backbone.history.start();
  1289. // Go to: http://localhost/#todo/4
  1290. //
  1291. // URL is updated to: http://localhost/#todo/4/edit
  1292. // but editTodo() function is not invoked even though location we end up is mapped to it.
  1293. //
  1294. // logs: View todo requested.
  1295. ```
  1296. It is also possible for `Router.navigate()` to trigger the route along with updating the URL fragment by passing the `trigger:true` option.
  1297. Note: This usage is discouraged. The recommended usage is the one described above which creates a bookmarkable URL when your application transitions to a specific state.
  1298. ```javascript
  1299. var TodoRouter = Backbone.Router.extend({
  1300. routes: {
  1301. "todo/:id": "viewTodo",
  1302. "todo/:id/edit": "editTodo"
  1303. // ... other routes
  1304. },
  1305. viewTodo: function(id){
  1306. console.log("View todo requested.");
  1307. this.navigate("todo/" + id + '/edit', {trigger: true}); // updates the fragment and triggers the route as well
  1308. },
  1309. editTodo: function(id) {
  1310. console.log("Edit todo opened.");
  1311. }
  1312. });
  1313. var myTodoRouter = new TodoRouter();
  1314. Backbone.history.start();
  1315. // Go to: http://localhost/#todo/4
  1316. //
  1317. // URL is updated to: http://localhost/#todo/4/edit
  1318. // and this time editTodo() function is invoked.
  1319. //
  1320. // logs:
  1321. // View todo requested.
  1322. // Edit todo opened.
  1323. ```
  1324. A "route" event is also triggered on the router in addition to being fired on Backbone.history.
  1325. ```javascript
  1326. Backbone.history.on('route', onRoute);
  1327. // Trigger 'route' event on router instance."
  1328. router.on('route', function(name, args) {
  1329. console.log(name === 'routeEvent');
  1330. });
  1331. location.replace('http://example.com#route-event/x');
  1332. Backbone.history.checkUrl();
  1333. ```
  1334. ## Backbones Sync API
  1335. We previously discussed how Backbone supports RESTful persistence via its `fetch()` and `create()` methods on Collections and `save()`, and `delete()` methods on Models. Now we are going to take a closer look at Backbone's sync method which underlies these operations.
  1336. The Backbone.sync method is an integral part of Backbone.js. It assumes a jQuery-like `$.ajax()` method, so HTTP parameters are organized based on jQuerys API. Since some legacy servers may not support JSON-formatted requests and HTTP PUT and DELETE operations, Backbone can be configured to emulate these capabilities using the two configuration variables shown below with their default values:
  1337. ```javascript
  1338. Backbone.emulateHTTP = false; // set to true if server cannot handle HTTP PUT or HTTP DELETE
  1339. Backbone.emulateJSON = false; // set to true if server cannot handle application/json requests
  1340. ```
  1341. The inline Backbone.emulateHTTP option should be set to true if extended HTTP methods are not supported by the server. The Backbone.emulateJSON option should be set to true if the server does not understand the MIME type for JSON.
  1342. ```javascript
  1343. // Create a new library collection
  1344. var Library = Backbone.Collection.extend({
  1345. url : function() { return '/library'; }
  1346. });
  1347. // Define attributes for our model
  1348. var attrs = {
  1349. title : "The Tempest",
  1350. author : "Bill Shakespeare",
  1351. length : 123
  1352. };
  1353. // Create a new Library instance
  1354. var library = new Library;
  1355. // Create a new instance of a model within our collection
  1356. library.create(attrs, {wait: false});
  1357. // Update with just emulateHTTP
  1358. library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'}, {
  1359. emulateHTTP: true
  1360. });
  1361. // Check the ajaxSettings being used for our request
  1362. console.log(this.ajaxSettings.url === '/library/2-the-tempest'); // true
  1363. console.log(this.ajaxSettings.type === 'POST'); // true
  1364. console.log(this.ajaxSettings.contentType === 'application/json'); // true
  1365. // Parse the data for the request to confirm it is as expected
  1366. var data = JSON.parse(this.ajaxSettings.data);
  1367. console.log(data.id === '2-the-tempest'); // true
  1368. console.log(data.author === 'Tim Shakespeare'); // true
  1369. console.log(data.length === 123); // true
  1370. ```
  1371. Similarly, we could just update using `emulateJSON`:
  1372. ```javascript
  1373. library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'}, {
  1374. emulateJSON: true
  1375. });
  1376. console.log(this.ajaxSettings.url === '/library/2-the-tempest'); // true
  1377. console.log(this.ajaxSettings.type === 'PUT'); // true
  1378. console.log(this.ajaxSettings.contentType ==='application/x-www-form-urlencoded'); // true
  1379. var data = JSON.parse(this.ajaxSettings.data.model);
  1380. console.log(data.id === '2-the-tempest');
  1381. console.log(data.author ==='Tim Shakespeare');
  1382. console.log(data.length === 123);
  1383. ```
  1384. `Backbone.sync` is called every time Backbone tries to read, save, or delete models. It uses jQuery or Zepto's `$.ajax()` implementations to make these RESTful requests, however this can be overridden as per your needs.
  1385. **Overriding Backbone.sync**
  1386. The `sync` function may be overridden globally as Backbone.sync, or at a finer-grained level, by adding a sync function to a Backbone collection or to an individual model.
  1387. Since all persistence is handled by the Backbone.sync function, an alternative persistence layer can be used by simply overriding Backbone.sync with a function that has the same signature:
  1388. ```javascript
  1389. Backbone.sync = function(method, model, options) {
  1390. };
  1391. ```
  1392. The methodMap below is used by the standard sync implementation to map the method parameter to an HTTP operation and illustrates the type of action required by each method argument:
  1393. ```javascript
  1394. var methodMap = {
  1395. 'create': 'POST',
  1396. 'update': 'PUT',
  1397. 'patch': 'PATCH',
  1398. 'delete': 'DELETE',
  1399. 'read': 'GET'
  1400. };
  1401. ```
  1402. If we wanted to replace the standard `sync` implementation with one that simply logged the calls to sync, we could do this:
  1403. ```javascript
  1404. var id_counter = 1;
  1405. Backbone.sync = function(method, model) {
  1406. console.log("I've been passed " + method + " with " + JSON.stringify(model));
  1407. if(method === 'create'){ model.set('id', id_counter++); }
  1408. };
  1409. ```
  1410. Note that we assign a unique id to any created models.
  1411. The Backbone.sync method is intended to be overridden to support other persistence backends. The built-in method is tailored to a certain breed of RESTful JSON APIs - Backbone was originally extracted from a Ruby on Rails application, which uses HTTP methods like PUT in the same way.
  1412. The sync method is called with three parameters:
  1413. * method: One of create, update, patch, delete, or read
  1414. * model: The Backbone model object
  1415. * options: May include success and error methods
  1416. Implementing a new sync method can use the following pattern:
  1417. ```javascript
  1418. Backbone.sync = function(method, model, options) {
  1419. function success(result) {
  1420. // Handle successful results from MyAPI
  1421. if (options.success) {
  1422. options.success(result);
  1423. }
  1424. }
  1425. function error(result) {
  1426. // Handle error results from MyAPI
  1427. if (options.error) {
  1428. options.error(result);
  1429. }
  1430. }
  1431. options || (options = {});
  1432. switch (method) {
  1433. case 'create':
  1434. return MyAPI.create(model, success, error);
  1435. case 'update':
  1436. return MyAPI.update(model, success, error);
  1437. case 'patch':
  1438. return MyAPI.patch(model, success, error);
  1439. case 'delete':
  1440. return MyAPI.destroy(model, success, error);
  1441. case 'read':
  1442. if (model.attributes[model.idAttribute]) {
  1443. return MyAPI.find(model, success, error);
  1444. } else {
  1445. return MyAPI.findAll(model, success, error);
  1446. }
  1447. }
  1448. };
  1449. ```
  1450. This pattern delegates API calls to a new object (MyAPI), which could be a Backbone-style class that supports events. This can be safely tested separately, and potentially used with libraries other than Backbone.
  1451. There are quite a few sync implementations out there. The following examples are all available on GitHub:
  1452. * Backbone localStorage: persists to the browser's local storage
  1453. * Backbone offline: supports working offline
  1454. * Backbone Redis: uses Redis key-value store
  1455. * backbone-parse: integrates Backbone with Parse.com
  1456. * backbone-websql: stores data to WebSQL
  1457. * Backbone Caching Sync: uses local storage as cache for other sync implementations
  1458. ## Dependencies
  1459. The official Backbone.js [documentation](http://backbonejs.org/) states:
  1460. >Backbone's only hard dependency is either Underscore.js ( >= 1.4.3) or Lo-Dash. For RESTful persistence, history support via Backbone.Router and DOM manipulation with Backbone.View, include json2.js, and either jQuery ( >= 1.7.0) or Zepto.
  1461. What this translates to is that if you require working with anything beyond models, you will need to include a DOM manipulation library such as jQuery or Zepto. Underscore is primarily used for its utility methods (which Backbone relies upon heavily) and json2.js for legacy browser JSON support if Backbone.sync is used.
  1462. ## Summary
  1463. In this chapter we have introduced you to the components you will be using to build applications with Backbone: Models, Views, Collections, and Routers. We've also explored the Events mix-in that Backbone uses to enhance all components with publish-subscribe capabilities and seen how it can be used with arbitrary objects. Finally, we saw how Backbone leverages the Underscore.js and jQuery/Zepto APIs to add rich manipulation and persistence features to Backbone Collections and Models.
  1464. Backbone has many operations and options beyond those we have covered here and is always evolving, so be sure to visit the official [documentation](http://backbonejs.org/) for more details and the latest features. In the next chapter you will start to get your hands dirty as we walk you through implementation of your first Backbone application.