Backbone.js

Models

Their purpose is to hold all the attributes of a logical piece of information.

Basic Interactions

Hints

Computed Values: Backbone really wants all of the attributes persisted to the database. If you have a read-only, computed attribute, then make it a function of the model. To pass the computed value to a view, you can merge the attributes with the function. For example, _.extend({'duration': this.model.computedField()}, this.model.toJSON())

Validation: Validation can happen in a few locations:

The concept of optimistic changes to a collection makes Backbone validation problematic. If validation fails, the view is already updated with bad data, so you will then need to back-out those changes if the “invalid” event gets fired. This seems like a lot of back and forth and possibility for conflicts.It is probably best to perform validatation right before the save, which is the default behavior of Backbone (vs. manually validating during a set({key:value},{validate:true})).

Collections

Collections are a list of models. You can fill a collection using fetch or reset.

Basic Interactions

Disconnected Collections

Most of the time the models will get loaded from the database via Collection.fetch() or some other means, but it is also possible to simply work with transient data that exists only in memory. Simply shove models into the collection when you create it. Ex:

var myCollection = new App.MyCollection([
	{id: 1, name: 'alice'},
	{id: 2, name: 'bob'},
	{id: 3, name: 'craig'}
]);

Views

Are responsible for DOM display and interaction. Data will be passed in. Keyboard and mouse events are tied to that DOM, and listeners are created to respond to other actions in the app.

Your goal is to have views automatically update themselves when a model is created, updated, or destroyed. This is achieved by listening to events that get fired by the model attached to the view. For example:

app.MyView = Backbone.View.extend({
	initialize: function() {
		//this.model is set when the view is instantiated (it is passed in).
		this.listenTo(this.model, 'change', this.render);
		this.listenTo(this.model, 'destroy', this.remove);
	},
	...

Basic Interactions

Events

Most of your event hooks ups are going to happen inside a view (and most inside your main AppView).

Use the .on() syntax when binding a callback to this object's events.

Use the this.listenTo('otherObject', event, callback) syntax when working with a different object.

Built-In Events

Model Collection Description
invalid invalid A model’s validation failed on the server
change change A model's attributes have changed
destroy destroy A model has been removed from the server
  add A model was added to collection
  remove A model has been removed from collection
  update Any number of models have been added or removed from the collection
  reset Collection was cleared out (any probably rebuilt)
  sort Has been resorted
request request Started a request to the server
sync sync  
error error Request to server failed

 

Learning Resources

There is a fair amount of documentation for Backbone, a lot of it complete crap. I wouldn’t call these resources great, but they are best I’ve come across so far:

  1. Developing Backbone.js Applications by Addy Osmani
  2. Pragmatic Backbone — A series of Backbone opinions

This tutorial from Christophe Coenraets is perfect if you want to learn how to use Backbone with a PHP/MySQL backend using the Slim Framework for PHP.

and building my own REST interface with help from this:

These are quite good, too.

Here are more lists of resources:

Technology Articles

RESTful Web Services article from IBM DeveloperWorks.

REST Set-up with Slim/PHP/MySQL for Backbone

MySQL

Obviously, you need your database up and running. The MAMP on OSX connection is:

function getConnection() {
  $dbhost="localhost";
  $dbuser="root";
  $dbpass="root";
  $dbname="PxPro"; //Your database name here
  $dbh = new PDO("mysql:host=$dbhost;dbname=$dbname", $dbuser, $dbpass); 
  $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
  return $dbh;
}

The above function is actually located in Slim’s index.php page. Which is to say, make sure the correct connection info is specified in Slim’s index.php page.

Backbone makes the assumption that every table has a primary key column called "id". You can tell it to use another similar type of column (auto-increment) with idAttribute.

Slim

Put the Slim directory inside folder api. That same folder will contain Slim’s index.php file and a .htaccess file. The .htaccess file is a critical bit of magic that allows Slim to work. It tells Apache to rewrite the URL to Slim’s index.php if the file requested is not found (which, of course, is why those files had better not exist!).

Backbone

When setting the url and urlRoot properties, keep in mind that it is a relative address to the api folder from your application's HTML page. Also, include the name of the Slim route (in this case, /airplane). That same route must exist in Slim’s index.php file and there should not be an actual directory/file called airplane inside the api folder because Apache's rewrite rule states “if no file with that name exists, then call Slim’s index.php”. This is what we want; Slim’s index.php should be processing all data requests.

App.Plane = Backbone.Model.extend({
     urlRoot : "../api/airplane"
 });
 
App.Planes = Backbone.Collection.extend({
     model : App.Plane,
     url : "../api/airplane"
 });

Another way to think of this is, urlRoot and url are mapping data access functions to a physical location on the web server. If we were NOT using Slim (say you are writing your own REST interface from scratch) then you very well may have actual files located there. e.g. http://myDomain.com/api/airplane/index.php

Backbone Routes vs. Slim Routes

These strings can look identical, therefore it is important (and confusing!) to understand that they are completely separate and serve different purposes.

Backbone Routes

Backbone Routes are requested from the web server as a URL to your webpage (say, http://myDomain.com/index.html) plus a fragment identifier (say, #/plane/15).

http://myDomain.com/index.html#/plane/15

In Backbone, you would define the above route as:

var AppRouter = Backbone.Router.extend({
  routes:{
    "plane/:id" : function(id) {
      //read a specific airplane
    }
  }
});

Don't make this more complicated than it deserves - all you are doing is requesting your index page. Tthe page is loaded by your browser, your Javascript code starts running, and it will hit the line:

var app = new AppRouter();

which then inspects the fragment identifier (#/model/15), and finally kicks off the function().

Slim Routes

The reason I am using Slim in this project is because it's little trick of magic is to execute a PHP function you tell it to, depending on the URL requested by your users (or more likely, Backbone). For example, if Backbone requests from the webserver http://myDomain.com/api/airplane/15, Apache will, of course try to find the default file (index.html) at that location. Namely, http://myDomain.com/api/airplane/15/index.html. This file does not, and should not exist. Why? Because we are tricking Apache into responding with Slim’s index.php file through the use of Apache's RewriteEngine.

So, we are requesting (via the HTTP GET method) http://myDomain.com/api/airplane/15. Instead, http://myDomain.com/api/index.php is called. That file contains and executes:

$app = new Slim();
$app->get('/airplane/:id', 'getSpecificAirplane');
$app->run();
 
function getSpecificAirplane($id) {
 	$sql = "select * FROM modelAirplanes Where planeId=$id ORDER BY name";
 	try {
 		$db = getConnection();
 		$stmt = $db->query($sql); 
 		$rst = $stmt->fetchAll(PDO::FETCH_OBJ);
 		$db = null;
 		echo json_encode($rst);
 	} catch(PDOException $e) {
 		echo '{"error":{"text":'. $e->getMessage() .'}}'; 
 	}
}

When PHP hits $app->run(); it will inspect the URL that was actually called, try to find a match (line two above), and then call the appropriate function (getSpecificAirplane in this case).

Key properties of Backbone objects

Views    
Properties    
  el The DOM object that is the target of the view. Can be passed as initialization option.
  $el Automatic jQuery reference to el
  events

A map of listeners to functions. Ex:

events:{'keypress #myTextbox':'saveOnEnter'}

  model Can be passed as initialization option.
  collection Can be passed as initialization option.
Methods    
  en  
Collection    
  create Convenience to create a new instance of a model within a collection. Equivalent to instantiating a model with a hash of attributes, saving the model to the server, and adding the model to the set after being successfully created.

Common Patterns

Views listen to their model’s “change” event, then re-render themselves.

Views listen to changes in their editable fields, and then call the save method on the model. i.e.

app.MyView = Backbone.View.extend({

initialize: function(){
	this.model.on('change', this.render, this);
},
events: {
	'click #saveBtn' : 'saveRec'
},
saveRec: function(){
	var value = this.input.val().trim();
	if(value) {
		this.model.save({title: value});
	}
}

});

In the background, the save() function delegates to Backbone.sync, which is the component in charge of making RESTful requests, using the jQuery function $.ajax() by default. Because REST style architecture is involved, each Create, Read, Update, or Delete (CRUD) action is associated with a different type of HTTP request (POST, GET, PUT, DELETE). The first time the model object is saved, a POST request is used and an identifier ID is created. For subsequent attempts to send the object to the server, a PUT request is used.

When a model needs to be retrieved from the server, a Read action is requested and an Ajax GET request is used. This type of request uses the fetch() method. To determine the location on the server where the model data is pushed or pulled from: