Learning Backbone

Tutsplus

## Installation
My way of doing this. 

Create a dir and index.php. Then follow my [blog post](http://www.cecilieo.com/techblog/using-bash-script-on-sublime-text-2/). 


## Data Types Primer

In main.js

	var Person = function(config) {
		this.name = config.name;
		this.age = config.age;
		this.occupation = config.occupation;
	};
	
	Person.prototype.work = function() {
		return this.name + ' is working.';
	};
	
Person.prototype.work is added for common function.	

In Chrome console. Check if all are loaded.

	Backbone
	$ // for jquery
	_ // underscore
	person = new Person({name:'Shinokada', age:55,occupation:'Teacher')};
	person //check it also __prototype__
	person.name
	person.age
	person.occupation
	person.work()


## Your First Backbone Model

Creating a backbone object.

	var Person = Backbone.Model.extend({
	
    });
    
Check it in console. 

	var person = new Person;
	person
	
Setting default.

	var Person = Backbone.Model.extend({
		defaults: {
			name: 'John Doe',
			age: 30,
			occupation: 'worker'
		},
		work: function() {
			return this.get('name') + ' is working.';
		}
	});

### Using object.get('attribute')
You can find these under attribute.

	var person = new Person;
	person
	// see under attribute.
	person.get('name')
	person.get('age')//backbone is keeping track of data.
	person.work()
	
Override it. In Chrome console

	var person = new Person({name:'Shin Okada', age:55});
	person // to see attribute
	
This will only override name and age. toJSON method can access the object. In Chrome.

	person.toJSON()	

Setting properties. In Chrome.

	person.set('occupation', 'Web developer');
	person.toJSON()
	
	person.set({occupation:'intructor'});// Passing an object. This will work as well.

## Validating-Models	
Adding validation rules.

	person.set('age',-50); // this will work and add age -50.
	
Add validate function

	validate: function(attrs) {
		console.log(attrs);
	},

Backbone pass all the object properties to attrs in the validate method. This validate will be called only when you are setting. 

	var person = new Person({name:"Shin Okada", age: 55});
	//This will not call validate.
	
	//But actually it calls validate and outputs the object.
	person.get('name');// this won't call it either.
	person.set('age', -10); // this will call the validate and console the object at the moment.

Adding validation.

	validate: function(attrs) {
		if ( attrs.age < 0 ) {
			return 'Age must be positive, stupid.';
		}
	},
	
In Chrome
	
	var person = new Person({name:"Shin Okada", age:55});
	// undefined
	person.set('age',-20);
	//false. This only returns boolean, does not return Age…
	// Fails the validation and only false will be returned.
	
How to fetch the error message. Create a listener.

	person.on('error', function(model, error){
		console.log(error);
	});	
	//listen for error event
	person.set ('age',-70);
	// This will display the error message as well.
	
	person.toJSON();
	//This shows that the previous one did not updated the object. Validation worked.
	
Adding validation to name as well.

	if ( ! attrs.name ) {
			return 'Every person must have a name, fool.';
		}	
	
In Chrome,
	
	person.set('name','');
	//will return false	
	
	
## Views


	var Person = Backbone.Model.extend({
	defaults: {
		name: 'John Doe',
		age: 30,
		occupation: 'worker'
	}
	});

	var PersonView = Backbone.View.extend({
	
	});
	
In Chrome

	var personView = new PersonView;
	personView
	// see under d to find $el and el
	personView.el
	<div></div>
	peronView.$el
	// element is wrapped with jQuery
	[<div></div>]

If you want to override the tagname to li instead of div.

	var PersonView = Backbone.View.extend({
		tagName: 'li',
	});

Console in Chrome

	var personView = new PersonView;
	peronView.el
	//<li></li>
	
You can add class name to this li.

	className: 'person'
	
Repeat it in console.

Or adding id.

	idName : 'myid'
	
Constructor method, initialize: which automatically run.

	initialize: function() {
		console.log('Hi there');
	}
	
In console

	var personView = new PersonView;
	// Hi there.


### render method

Create an instance of model and add it to view.

	var person = new Person;
	var personView = new PersonView({ model: person });

So far we got the following codes.

	// main.js
	var Person = Backbone.Model.extend({
		defaults: {
			name: 'John Doe',
			age: 30,
			occupation: 'worker'
		}
	});
	
	var PersonView = Backbone.View.extend({
		tagName: 'li',
	
		initialize: function() {
			console.log(this.model);
		},
	
		render: function() {
			
		}
	});
	
	var person = new Person;
	var personView = new PersonView({ model: person });

When you reload the page it will initialize and show the model.

Change to this.

		initialize: function() {
			
		},
	
		render: function() {
			this.$el.html( this.model.get('name') );// when you add render(), it will add this.model name to el ellement.
		}
		
In console,

	personView
	personView.el // only li tag
	personView.render(); // to render 
	personView.el // this will show name
	
Add this.render(); to initialize.

And in console,

	personView.el// will display name
	$(document.body).html(personView.el);//to add to body using jquery.
	
Adding more to render becomes crowed and it is anti-patter,

	// anti-pattern, no good
		this.$el.html( this.model.get('name') + ' (' + this.model.get('age') + ') - ' + this.model.get('occupation') );
	
## Inline-Templates


	var PersonView = Backbone.View.extend({
		tagName: 'li',
	
		template: _.template("<strong><%= name %></strong> (<%= age %>) - <%= occupation %>"),
		//function is stored in template:
		// underscorejs template
		// <%= name %> to render variables
		
		initialize: function() {
			this.render();
		},
	
		render: function() {
			this.$el.html( this.template(this.model.toJSON()) );
			// call it as function and pass the date to use.
		}
	});

In console

	personView.el
	$(document.body).append(personView.el);



## External-Templates

Add the following to index.html

	<script id="personTemplate" type="text/template">
	<strong><%= name %></strong> (<%= age %>) - <%= occupation %>
	</script>

Since there is no javascript, you have to add type="text/template. This will prevent executing as a javascript.

In main.js you need to fetch the above.

	// method one
	template: _.template( $('#personTemplate').html() ),
	
	// method two
	template: '#personTemplate',

	render: function(){
		var template = _.template( $(this.template) );
		this.$el.html( template );
	}

### Final

console 
	
	personView.el
	
main.js

	var Person = Backbone.Model.extend({
		defaults: {
			name: 'John Doe',
			age: 30,
			occupation: 'worker'
		}
	});
	
	var PersonView = Backbone.View.extend({
		tagName: 'li',
	
		template: _.template( $('#personTemplate').html() ),
	
		initialize: function() {
			this.render();
		},
	
		render: function() {
			this.$el.html( this.template(this.model.toJSON()) );
		}
	});
	
	var person = new Person;
	var personView = new PersonView({ model: person });
	
	// Bleh. This gets messy quickly.
	var person2 = new Person({ name: 'Jeffrey Way', age: 27 });
	var personView2 = new PersonView({ model: person2 });
	

## Collections

When you have more than one person/object, you need to use collections. Adding Backbone.Collection.extend().

	// A List of People
	var PeopleCollection = Backbone.Collection.extend({
	model: Person //pass the name of model
	});

In console

	new PeopleCollection
	
Add a new Collection in main.js add and console.log it.

	var peopleCollection = new PeopleCollection;
	peopleCollection.add(person);

	console.log(peopleCollection);

In console, it shows length one, which is one model.

	peopleCollection.toJSON();
	
In main.js Add one more person2.

	var person = new Person;
	var personView = new PersonView({model: person});
	
	var peopleCollection = new PeopleCollection;
	peopleCollection.add(person);
	
	var person2 = new Person({name:'Shin Okada', age : 55});
	var personView2 = new PersonView({model: person2});
	
	peopleCollection.add(person2);
	
	console.log(peopleCollection);
	
In console
	
	peopleCollection.toJSON();//this will show two objects.
	
You can clean it up by followings. You can add array of object as following.

	
	var peopleCollection = new PeopleCollection([
		{
			name: 'Jeffrey Way',
			age: 27
		},
		{
			name: 'John Doe',
			age: 50,
			occupation: 'web designer'
		},
		{
			name: 'Sally Doe',
			age: 29,
			occupation: 'graphic designer'
		}
	]);
	
	console.log(peopleCollection);
	
In console,

	peopleCollection.toJSON();	

	var model = peopleCollection.at(0);
	model
	// and see under attributes
	model.get('name');
	// return the name
	model.set('occupation', 'web developer');
	model.toJSON();
	// to see added item
	peopleCollection.toJSON();
	// to see updated items.
	
	jeff=peopleCollection.getByCid('c0');
	JSON.stringify(jeff);
	
	//if jeff has id:123
	jeff=peopleCollection.get(123);
	JSON.stringify(jeff);

	
	
### Final Code
main.js

	// Person Model
	var Person = Backbone.Model.extend({
		defaults: {
			name: 'John Doe',
			age: 30,
			occupation: 'worker'
		}
	});
	
	// A List of People
	var PeopleCollection = Backbone.Collection.extend({
		model: Person
	});
	
	// The View for a Person
	var PersonView = Backbone.View.extend({
		tagName: 'li',
	
		template: _.template( $('#personTemplate').html() ),
	
		initialize: function() {
			this.render();
		},
	
		render: function() {
			this.$el.html( this.template(this.model.toJSON()) );
		}
	});
	
	var peopleCollection = new PeopleCollection([
		{
			name: 'Jeffrey Way',
			age: 27
		},
		{
			name: 'John Doe',
			age: 50,
			occupation: 'web designer'
		},
		{
			name: 'Sally Doe',
			age: 29,
			occupation: 'graphic designer'
		}
	]);
	
	console.log(peopleCollection);
	



	
## Collection-Views
When you have more than one colleciton view, you need to use collecion view. We need a view to render all the person view.

	// View for all people
	var PeopleView = Backbone.View.extend({
	tagName: 'ul',

	initialize: function(){
		console.log(this.collection);
	},

	render: function() {
		// filter through all items in a collection
		// for each, create a new PersonView
		// append to root element
	}
	});

Next we need to access to peopleCollection in PeopleView. We create a collection by var peopleCollection before. Add this at the end.

	var peopleView = new PeopleView({ collection: peopleCollection });
	
This will console an object with length of 3.

Next take out the previous initialize and add the following.

	render: function(){
		console.log(this.collection);
	}
	
In console, peopleView.render() and see under \__proto__, and \__proto__ to find out all the methods and each() method. Then change render() again.

	render: function(){
		console.log(this.collection.each);
	}

This will display the each method.

Change the render to the followings.

	this.collection.each(function(person)){//model or person
		console.log(person);
	}
	
In console, 

	peopleView.render();
	
You need to watch this. There are more to it.	


### Final Code

	// Person Model
	var Person = Backbone.Model.extend({
		defaults: {
			name: 'John Doe',
			age: 30,
			occupation: 'worker'
		}
	});
	
	// A List of People
	var PeopleCollection = Backbone.Collection.extend({
		model: Person
	});
	
	
	// View for all people
	var PeopleView = Backbone.View.extend({
		tagName: 'ul',
	
		render: function() {
			// filter through all items in a collection
			// for each, create a new PersonView
			// append to root element
			
			this.collection.each(function(person) {
				var personView = new PersonView({ model: person });
				this.$el.append(personView.render().el);
			}, this);
	
			return this;
		}
	});
	
	// The View for a Person
	var PersonView = Backbone.View.extend({
		tagName: 'li',
	
		template: _.template( $('#personTemplate').html() ),
	
		render: function() {
			this.$el.html( this.template(this.model.toJSON()) );
			return this;
		}
	});
	
	var peopleCollection = new PeopleCollection([
		{
			name: 'Jeffrey Way',
			age: 27
		},
		{
			name: 'John Doe',
			age: 50,
			occupation: 'web designer'
		},
		{
			name: 'Sally Doe',
			age: 29,
			occupation: 'graphic designer'
		}
	]);
	
	var peopleView = new PeopleView({ collection: peopleCollection });
	$(document.body).append(peopleView.render().el);