Chapter 1: Introduction

"Get your truth out of the DOM":
- don’t lose data inside the DOM, when should instead abstract data into a model
- provides client-side app structure
- models represent data structure: the domain specific-specific knowledge and data of an application.
- views hook up models to the DOM: constitute user interface in an application.  Views observe models but don’t directly communicate with them.  Responsibility of the controller merged into the view.
- synchronises data to/from the server

Chapter 2: Fundamentals

MV*

Backbone.js is an MV* framework, in that there is no specific controller.  Instead, the View addresses the controller responsibility (has controller logic) and the router helps manage application state.  Backbone.Event is a fundamental Backbone component that is mixed into both Backbone.Model and Backbone.View, providing both rich event management capability.

A Single Page Application (SPA) client side application like Backbone.  The SPA could be a be a simple HTML file, or it could be a view contracted by a server-side MVC implementation.

Chapter 3: Backbone Basics

Models

A model is a data container.  To create a model class (capitalise first letter of each word when declaring a class):

var TodoItem = Backbone.Model.extend({

  defaults: {
    title: '',
    completed: false
  },

  initialize: function(){
    console.log(‘This model has been initialised.’);
    this.on(‘change’, function(){
      console.log(‘ - Values for this model have changed.’);
    });
  }

  urlRoot: ‘/todos’
});

- initialize() method is called when a new instance model is created.  Use is optional.
- call defaults in the model to set data where complete set not provided by the user.

Create concrete instance of the model with attributes:

var todoItem = new TodoItem({  
  id: 1
});  

model.get() provides access to a model’s attributes:
model.get(‘description’);

model.set() sets a hash containing one or more attributes on the model:
model.set({status: ‘complete’}, {silent:true});
todoItem.set({description: ‘Pick up cookies.’});

Models expose an .attributes attributes, which represents an internal hash containing the state of the model.  Setting values through the .attributes attribute on a model bypasses triggers bound to the model.  Passing [silent:true} silences trigger events entirely.

If you want to receive a notification when a Backbone model changes, can bind a listener to the model for its change event.  A convenient place to add listeners is on initialize() function.

Listen for an event on a model:

todoItem.on(‘event-name’, function(){  
  alert(‘event-name happened!’);
});

Run the event:
todoItem.trigger(‘event-name’);

Special events
Listen to changes on our model, and call the doThing function:
var doThing = function(){ .. some stuff .. }

todoItem.on(‘change’, doThing);

Built-in events: change, change:, destroy, sync, error, all

Model.validate() allows checking the attribute values for a model prior to setting them. By default validation occurs when the model is persisted via the save() method of when set() is called if {validate:true} is passed as an argument.

Model.validate = function(attrs) {  
  if (!attrs.name) {
    return ‘I need a name’;
  }
};

Views

Views in Backbone don’t contain HTML markup, they contain logic behind the presentation of the model’s data to the user.  This is done using JS templating (for example Underscore mircotemplates, Mustache, Handlebars, haml-js, eco etc).  A view’s render() method can be bound a model’s change() event, enabling the view to instantly reflect model changes without requiring a full page refresh.

To create a view class:

var TodoView = Backbone.View.extend({  
  tagName:  'li',
  // Cache the template function for a single item.
  todoTpl: _.template( "An example template" ),

  events: {
    'dblclick label': 'edit',
    'keypress .edit': 'updateOnEnter',
    'blur .edit':   'close'
  },

  initialize: function (options) {
    // In Backbone 1.1.0, if you want to access passed options in
    // your view, you will need to save them as follows:
    this.options = options || {};
  },
  // Re-render the title of the todo item.
  render: function() {
    this.$el.html( this.todoTpl( this.model.attributes ) );
    this.input = this.$('.edit');
    return this;
  },
  edit: function() {
    // executed when todo label is double clicked
  },
  close: function() {
    // executed when todo loses focus
  },
  updateOnEnter: function( e ) {
    // executed on each keypress when in todo edit mode,
    // but we'll wait for enter to get in action
  }
});

Create a new view instance:
var todoView = new TodoView();

Log reference to a DOM element that corresponds to the view instance:
console.log(todoView.el);

El

The central property of a view is el.  El is a reference to a DOM element, and all views must have one.  Views can use el to compose their element’s content  and insert it into the DOM all at once.  A new element can be created for the view and subsequently added to the DOM, or a reference can be made to an element that already exists in the page.

To create a new element for the view, set any combination of tagName, id and className.  This will create a new element and a reference to it will be available at the el property.  Div is the default, but could be p, li, header, section etc

var NewView = Backbone.View.extend({  
  tagName: ‘li’,
  id: ’todo-view’,
  className: ’todo’
});

var newView = new SimpleView();  
console.log(newView.el); // logs <li id="todo-view" class="todo"></li>  

When declaring views, can define options, el, tagName, id and className as functions if you want their values to be determined at runtime.

View logic often needs jQuery functions on the el element: Backbone defines $el property and $() function.  view.$el property is equivalent to $(view.el) and view.$(selector) is equivalent to $(view.el).find(selector).

el is a DOM element, so if using jQuery
todoView.$el.html(); //best since the el’s may be dynamic
$(todoView.el).html(); //ok too
$(‘#todo-view’).html(); // jQuery version

setElement applies existing Backbone view to a different DOM element.  setElement will create a cached $el reference, moving the delegated events for a view from the old element to the new one.

The el property represents the markup portion of the view that will be rendered.  To get the view to actually render to the page, need to add the view as a new element or append it to an existing element.

Render

render() is an optional function that defines the logic for rendering a template.  The _.template method in Underscore compiles JS templates into functions that can be evaluated for rendering.  A common Backbone convention is to return 'this’ at the end of render(). This enables its use as a subview.

Events

In Backbone, views are responsible for responding to any user interaction, so events are defined inside of view. The events hash allows us to attach event listeners to either el-relative custom selectors, or directly to el if no selector is provided. Uses " ": "".  Views can have multiple events.

var TodoView = Backbone.View.extend({  
  events: {
    "click h3": "alertStatus" // can listen to all jQuery events if using jQuery
  },

  alertStatus: function(e){
    alert(‘Hey you clicked the h3!’);
  }

});

The selector (h3) is scoped to inside the delegate using jQuery’s .delegate() (so not every h3 on the page).  And ‘this’ always refers to the current view object within callback functions.

Events: change, click, dblclick, focus, focusing, focus out, hover, keydown, keypress, load, mousedown, mouseenter, mouseleave, mousemove, mouseout, mouseover, mouseup, ready, resize, scroll, select, unload.

Can bind methods using _.bind(this.viewEvent, this)

Collections (of data)

Collections are sets of models, and are created by extending Backbone.Collection.  Normally, when creating a collection will want to define a property specifying the type of model the collection will contain, along with any instance properties required.

var Todo = Backbone.Model.extend({  
  defaults: {
    title: '',
    completed: false
  }
});

var TodosCollection = Backbone.Collection.extend({  
  model: Todo
});

var myTodo = new Todo({title:'Read the whole book', id: 2});  

Pass array of models on collection instantiation:

var todos = new TodosCollection([myTodo]);  
console.log("Collection size: " \+ todos.length); // Collection size: 1  

add() and remove() edit collections and accept both individual models and lists of models.
todoList.add(todoItem1);
todoList.remove(todoItem1);

Collection.get() retrieves a model from a collection, by accepting a single id: todoList.get(1); // todoItem1 todoList.length; // get number of models
todoList.at(0); // get model instance at index 0

Backbone uniquely identifies models using id, cid and idAttribute properties.  id is an unique identifier that is either an integer or a string.  cid (client ID) is automatically generated by Backbone with the model is created.  Either can be used to retrieve.  The idAttribute is the identifying attribute of the model returned from the server (i.e. the id in the database).

Can listen for add and remove events which occur when models are added or removed from a collection.

TodosCollection.on("add", function(todo) {  
  console.log("A model has been added");
});

once() method ensures that a callback only fires once when a notification arrives.

Collection.set() takes an array of models and performs necessary add, remove and change operations required to update the collection.

Collection.reset() replaces the entire content of the collection.  With no argument will reset entirely.  No add / remove events are fired, instead a ‘reset’ event is fired.

var todos = [  
  {description: ‘Pick up milk’, status: ‘incomplete’},
  {description: ‘Get car wash’, status: ‘incomplete’},
];
todoList.reset(todos);  

update() method is available for collections.

Many Underscore utilities are directly available on collections.
forEach():

todoList.forEach(function(todoItem){  
  alert(todoItem.get(‘description’)); // will get description for each model item in the collection
});

sortBy(): sort a collection on a specific attribute
map() creates a new collection by mapping each value in a list through a transformation function.  Map() will loop through and build a new array, based off the return item for that function.

var count = 1;  
console.log(Todos.map(function(model){  
  return count++ + ". " + model.get(‘rite’);
}));

min()/max(): retrieve item with the min or max value of an attribute.
pluck(): extract a specific attribute
filter(): filter a collection, say by an array of model IDs.  Filter will only return items that return true to the function.

todoList.filter(function(todoItem){  
  return todoItem.get(’status’) === ‘incomplete';
}); 

indexOf(): return the item at a particular index within a collection
any(): confirm if any of the values in a collection pass an iterator truth test
size() returns the size of a collection.  Equivalent to Collection.lenth.
isEmpty() will determine whether a collection is empty
groupBy() will group a collection into group like items
pick() will extract a set of attributes from a model
omit(); will extract all attributes from a model except those listed
keys() and values() get lists of attribute names and values
pairs() gets a list of attributes as [key, value] pairs
invert() creates an object in which the values are keys and the attributes are values.

Backbone also supports Underscore’s chain() method.  A chain is a sequence of method calls on the same object that are performed in a single statement.

RESTful Persistence

Fetching models from server

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 collections’ url property (which may be a function). Once retrieved a set() will be executed to update the collection.

var Todo = Backbone.Model.extend({  
  defaults: {
    title: '',
    completed: false
  }
});

var TodosCollection = Backbone.Collection.extend({  
  model: Todo,
  url: '/todos'
});

var todos = new TodosCollection();

todos.fetch(); // sends HTTP GET to /todos to populate collection from server  

Saving models to server

Updates to models are performed individually using the model’s save() method.
todoItem.save(); constructs a URL by appending model’s ID to collection’s URL and sends HTTP PUT to server.
Collections.create() can create a new model and add to the collection, and send to the server in single method call.
todos.create({title: ‘Try out code samples’});

Deleting models from the server

A model can be removed from the containing collection by calling destroy() method.  Unlike Collection.remove() which only removes a model from a collection, Model.destory() will also send an HTTP DELETE to the collection’s URL.

todoItem.destroy(); sends HTTP DELETE to url and removes from collection.

Various options are available to these RESTful API methods.

Get
todoItem.get(‘description');
Get JSON from model
todoItem.toJSON();

Events

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 with a specific event occurs.  Backbone.Events is mixed into the other Backbone classes.  Documentation recommends namespacing event names using colons if use many e.g. ‘dance:tap’, ‘dance:break’.

on(), off() and trigger()

Backbone.Events can give any object the ability to bind and trigger custom events.

var ourObject = {};  
// Mixin
_.extend(ourObject, Backbone.Events);  
// Add a custom event
ourObject.on('dance', function(msg){  
  console.log('We triggered ' + msg);
});
// Trigger the custom event
ourObject.trigger('dance', 'our event');  

off() method removes callback function that were previously bound to an object.  In publish/Subscribe comparison is it unsubscribe.  move removes all callbacks for the event we pass an event name.

trigger triggers a callback for a specified event.  trigger can pass multiple arguments to the callback function.

var ourObject = {};  
// Mixin
_.extend(ourObject, Backbone.Events);  
function doAction (action, duration) {  
  console.log("We are " \+ action + ' for ' + duration );
}
// Add event listeners
ourObject.on("dance", doAction);  
ourObject.on("jump", doAction);  
ourObject.on("skip", doAction);  
// Passing multiple arguments to single event
ourObject.trigger("dance", 'dancing', "5 minutes");  
// Passing multiple arguments to multiple events
ourObject.trigger("dance jump skip", 'on fire', "15 minutes");  

listenTo() and stopListening()

While on() and off() add callbacks directly to an observed object, listenTo() tells an object to listen for event on another object.  stopListening() can be call after on the listener to tell it to stop listening for events.

Events and Views

There are two types of events you can listen for within a view: DOM events and events triggered using the Event API.  DOM events can be bound to using the View’s events property or using jQuery.on().  Event API events are bound as described in this section.  See page 60 of Backbone book: impact particularly regarding ‘this’.

<div id="todo">  
  <input type='checkbox' />
</div>  
var View = Backbone.View.extend({  
  el: '#todo',

  // bind to DOM event using events property
  events: {
    'click [type="checkbox"]': 'clicked',
  },

  initialize: function () {
    // bind to DOM event using jQuery
    this.$el.click(this.jqueryClicked);
    // bind to API event
    this.on('apiEvent', this.callback);
  },

  // 'this' is view
  clicked: function(event) {
    console.log("events handler for " \+ this.el.outerHTML);
    this.trigger('apiEvent', event.type);
  },

  // 'this' is handling DOM element
  jqueryClicked: function(event) {
    console.log("jQuery handler for " \+ this.outerHTML);
  },

  callback: function(eventType) {
    console.log("event type was " \+ eventType);
  }
});

var view = new View();  

Routers

Routers provide a way to connect URLs (either hash fragments ‘/#about' or real) to parts of your application.  The job of the router is to map URLs to actions.

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.

'route': 'mappedFunction'

var TodoRouter = Backbone.Router.extend({  
  /* define the route and function maps for this router */

  routes: {
    "about" : "showAbout",

    /* Sample usage: <http://example.com/#about> */

    "todo/:id" : "getTodo",

    /* This is an example of using a ":param" variable which allows us to match
    any of the components between two URL slashes */

    /* Sample usage: <http://example.com/#todo/5> */

    "search/:query" : "searchTodos",

    /* We can also define multiple routes that are bound to the same map function,
    in this case searchTodos(). Note below how we're optionally passing in a
    reference to a page number if one is supplied */

    /* Sample usage: <http://example.com/#search/job> */

    "search/:query/p:page" : "searchTodos",

    /* As we can see, URLs may contain as many ":param"s as we wish */

    /* Sample usage: <http://example.com/#search/job/p1> */

    "todos/:id/download/*documentPath" : "downloadDocument",

    /* This is an example of using a *splat. Splats are able to match any number of
    URL components and can be combined with ":param"s*/

    /* Sample usage: <http://example.com/#todos/5/download/files/Meeting_schedule.doc> */

    /* If you wish to use splats for anything beyond default routing, it's probably a good
    idea to leave them at the end of a URL otherwise you may need to apply regular
    expression parsing on your fragment */

    "*other"    : "defaultRoute",

    /* This is a default route that also uses a *splat. Consider the
    default route a wildcard for URLs that are either not matched or where
    the user has incorrectly typed in a route path manually */

    /* Sample usage: <http://example.com/#> <anything> */

    "optional(/:item)": "optionalItem",

    "named/optional/(y:z)": "namedOptionalItem"

    /* Router URLs also support optional parts via parentheses, without having
    to use a regex.  */

    },

    showAbout: function(){

    },

    getTodo: function(id){
      /* Note that the id matched in the above route will be passed to this function */
      console.log("You are trying to reach todo " \+ id);
    },

    searchTodos: function(query, page){
      var page_number = page || 1;
      console.log("Page number: " \+ page_number + " of the results for todos containing the word: " \+ query);
    },

    downloadDocument: function(id, path){

    },

    defaultRoute: function(other){
      console.log('Invalid. You attempted to reach:' + other);
    }
});

/* Now that we have a router setup, we need to instantiate it */

var myTodoRouter = new TodoRouter();  
Backbone.history.start({pushState: true});  

Ideally recommend keeping routes to one single router.

Backbone.history

To save history in the browser can either use a hash fragment (/#mark) which creates a new URL, or otherwise HTML5 API called "pushState".  It's recommended to use HTML5 pushState.

Backbone.history.start();  // Start history with pushState off

Backbone.history.start({pushState: true}); // Start history with pushState on

Sync API

Backbone.emulateHTTP = false; // set to true if server cannot handle HTTP PUT or HTTP DELETE
Backbone.emulateJSON = false; // set to true if server cannot handle application/json requests

Can override the sync method globally as Backbone.sync or finer level by adding a sync function to a Backbone collection or individual model.

Collection Views

Collection View is 1 to many model <—> view because the collection view delegates to the model views to render the HTML.  It doesn’t render any HTML itself.

var TodoListView = Backbone.View.extend({});  
var todoListView = new TodoListView({collection: todoList});

render: function(){  
  this.collection.forEach(this.addOne, this);  // this adds the context of the view
}

addOne: function(todoItem){  
  var todoView = new TodoView({model: todoItem}):
  this.$el.append(todoView.render().el);
} 

Chapter 7: Common Problems

Solutions to common problems:
- working with nested views
- managing models in nested views
- rendering a parent view from a child view
- disposing view hierarchies
- rendering view hierarchies
- working with nested models or collections
- better model property validation
- avoiding conflicts with multiple backbone versions
- building model and view hierarchies
- event aggregators and mediators

Chapter 8: Modular Development

A modular application means its composed of a set of highly decouple, distinct pieces of functionality stored in modules.  Current iteration of ECMA-262 (JavaScript) doesn’t provide a means to import modules, but this may be arriving in the newer version of JavasScript ES6 modules proposal.

AMD = asynchronous module definition

RequireJS

RequireJS is a popular script loader that loads multiple script files, define modules with or without dependencies, and load in non script dependences such as text files.

Browsers are limited to between 4 and 8 parallel HTTP requests at a time (each script requires an HTTP request).  RequireJS loads scripts asynchronously which makes the load process nonblocking.

Dependency management by ordering the script tag is not effective.  RequireJS loads code on as-needed basis: rather than load all at page load, it dynamically loads modules when the code is required.

RequireJS implements AMD which defines a method for writing modular code and managing dependencies.

Key concepts when using with a script loader are define() method for defining modules and require() method for loading dependencies. 

define() is used to define named or unnamed modules

A module ID has been omitted here to make the module anonymous.

define(['foo', 'bar'],  
  // module definition function
  // dependencies (foo and bar) are mapped to function parameters

  function ( foo, bar ) {
    // return a value that defines the module export
    // (i.e the functionality we want to expose for consumption)
    // create your module here
    var myModule = {
      doStuff: function(){
        console.log('Yay! Stuff');
      }
    }
  return myModule;
});

Alternate syntax declaring dependencies as local variables using require()

A module ID has been omitted here to make the module anonymous.

define(function(require){  
  // module definition function
  // dependencies (foo and bar) are defined as local vars
  var foo = require('foo'), bar = require('bar');

  // return a value that defines the module export
  // (i.e the functionality we want to expose for consumption)

  // create your module here
  var myModule = {
    doStuff: function(){
      console.log('Yay! Stuff');
    }
  }
  return myModule;
});
// Consider 'foo' and 'bar' are two external modules
// In this example, the 'exports' from the two modules loaded are passed as
// function arguments to the callback (foo and bar)
// so that they can similarly be accessed
require( ['foo', 'bar'], function ( foo, bar ) {  
  // rest of your code here
  foo.doSomething();
});

Wrapping models, views and other components with AMD

define(['underscore', 'backbone'], function(_, Backbone) {  
  var myModel = Backbone.Model.extend({

    defaults: {
      content: 'hello world',
    },

    initialize: function() {

    },

    clear: function() {
      this.destroy();
      this.view.remove();
    }

  });

  return myModel;

});