Slide 1

Slide 1 text

Head First Backbone.js Jace Ju

Slide 2

Slide 2 text

Why Bacbkone.js ?

Slide 3

Slide 3 text

Spaghetti code Output Input Global Variables Business Logic

Slide 4

Slide 4 text

Organized code Model Output Input Business Logic

Slide 5

Slide 5 text

Separation of concerns Router Model View Url and Pages Data and Status DOM and Template

Slide 6

Slide 6 text

How to start ?

Slide 7

Slide 7 text

Load these scripts underscore.js jQuery Backbone.js Your JavaScript

Slide 8

Slide 8 text

Load these scripts underscore.js jQuery Backbone.js Your JavaScript

Slide 9

Slide 9 text

Load these scripts underscore.js jQuery Backbone.js Your JavaScript

Slide 10

Slide 10 text

Load these scripts underscore.js jQuery Backbone.js Your JavaScript

Slide 11

Slide 11 text

Basic Classes Sync View Event History extend use Router Model Collection

Slide 12

Slide 12 text

Basic flow Find out input UI and output UI, Define model and business logic, Define view elements, Define events, Define router (optional), Initialize all objects and run.

Slide 13

Slide 13 text

Basic flow Find out input UI and output UI, Define model and business logic, Define view elements, Define events, Define router (optional), Initialize all objects and run.

Slide 14

Slide 14 text

Basic flow Find out input UI and output UI, Define model and business logic, Define view elements, Define events, Define router (optional), Initialize all objects and run.

Slide 15

Slide 15 text

Basic flow Find out input UI and output UI, Define model and business logic, Define view elements, Define events, Define router (optional), Initialize all objects and run.

Slide 16

Slide 16 text

Basic flow Find out input UI and output UI, Define model and business logic, Define view elements, Define events, Define router (optional), Initialize all objects and run.

Slide 17

Slide 17 text

Basic flow Find out input UI and output UI, Define model and business logic, Define view elements, Define events, Define router (optional), Initialize all objects and run.

Slide 18

Slide 18 text

Event first

Slide 19

Slide 19 text

var target = {}; _.extend(target, Backbone.Events, { }); var init = function () {}; target.on('init', init); target.on('start', function () {}); target.trigger('init'); target.trigger('init start'); Bind event and trigger may not be in the order

Slide 20

Slide 20 text

var context = { command: 'this is context' }; var execute = function () { console.log(this.command); }; target.on('run execute', execute, context); target.trigger('run execute'); Multi-events different events, same callback

Slide 21

Slide 21 text

var context1 = { command: 'this is context1' }; var context2 = { command: 'this is context2' }; var execute = function () { console.log(this.command); }; target.on('run execute', execute, context1); target.on('run execute', execute, context2); target.on('run execute', function () { console.log(this.command); }, context2); target.on('test', function () {}); target.off('run execute', execute, context1); target.off('run execute', execute); target.off('run execute'); target.off(); Unbind events

Slide 22

Slide 22 text

var context1 = { command: 'this is context1' }; var context2 = { command: 'this is context2' }; var execute = function () { console.log(this.command); }; target.on('run execute', execute, context1); target.on('run execute', execute, context2); target.on('run execute', function () { console.log(this.command); }, context2); target.on('test', function () {}); target.off('run execute', execute, context1); target.off('run execute', execute); target.off('run execute'); target.off(); Unbind events

Slide 23

Slide 23 text

var context1 = { command: 'this is context1' }; var context2 = { command: 'this is context2' }; var execute = function () { console.log(this.command); }; target.on('run execute', execute, context1); target.on('run execute', execute, context2); target.on('run execute', function () { console.log(this.command); }, context2); target.on('test', function () {}); target.off('run execute', execute, context1); target.off('run execute', execute); target.off('run execute'); target.off(); Unbind events

Slide 24

Slide 24 text

var context1 = { command: 'this is context1' }; var context2 = { command: 'this is context2' }; var execute = function () { console.log(this.command); }; target.on('run execute', execute, context1); target.on('run execute', execute, context2); target.on('run execute', function () { console.log(this.command); }, context2); target.on('test', function () {}); target.off('run execute', execute, context1); target.off('run execute', execute); target.off('run execute'); target.off(); Unbind events

Slide 25

Slide 25 text

var context1 = { command: 'this is context1' }; var context2 = { command: 'this is context2' }; var execute = function () { console.log(this.command); }; target.on('run execute', execute, context1); target.on('run execute', execute, context2); target.on('run execute', function () { console.log(this.command); }, context2); target.on('test', function () {}); target.off('run execute', execute, context1); target.off('run execute', execute); target.off('run execute'); target.off(); Unbind events

Slide 26

Slide 26 text

Just model

Slide 27

Slide 27 text

How to share status ?
Turn on music.
Music: off

Slide 28

Slide 28 text

var music = false; // after DOM is ready $('#switch').on('click', function () { music = $(this).prop('checked'); var status = music ? 'on' : 'off'; $('#status') .removeClass('on off') .addClass(status) .text(status); }); Share status the old way global variable

Slide 29

Slide 29 text

var music = false; // after DOM is ready $('#switch').on('click', function () { music = $(this).prop('checked'); var status = music ? 'on' : 'off'; $('#status') .removeClass('on off') .addClass(status) .text(status); }); Share status the old way Input logic

Slide 30

Slide 30 text

var music = false; // after DOM is ready $('#switch').on('click', function () { music = $(this).prop('checked'); var status = music ? 'on' : 'off'; $('#status') .removeClass('on off') .addClass(status) .text(status); }); Share status the old way Output logic

Slide 31

Slide 31 text

var Config = Backbone.Model.extend({ defaults: { music: false } }); var config = new Config(); $('#switch').on('click', function () { config.set('music', $(this).prop('checked')); }); config.on('change', function () { var music = config.get('music'); var status = music ? 'on' : 'off'; $('#status') .removeClass('on off') .addClass(status) .text(status); }); Share status the new way model

Slide 32

Slide 32 text

var Config = Backbone.Model.extend({ defaults: { music: false } }); var config = new Config(); $('#switch').on('click', function () { config.set('music', $(this).prop('checked')); }); config.on('change', function () { var music = config.get('music'); var status = music ? 'on' : 'off'; $('#status') .removeClass('on off') .addClass(status) .text(status); }); Share status the new way trigger change

Slide 33

Slide 33 text

var Config = Backbone.Model.extend({ defaults: { music: false } }); var config = new Config(); $('#switch').on('click', function () { config.set('music', $(this).prop('checked')); }); config.on('change', function () { var music = config.get('music'); var status = music ? 'on' : 'off'; $('#status') .removeClass('on off') .addClass(status) .text(status); }); output logic Share status the new way

Slide 34

Slide 34 text

Change attributes var Box = Backbone.Model.extend({ defaults: { width: 100, height: 100 } }); var box = new Box(); box.on("change", function () { ... }, box) .on("change:width", function () { ... }, box); box.set("width", 400); box.set("height", 300); define model

Slide 35

Slide 35 text

Change attributes var Box = Backbone.Model.extend({ defaults: { width: 100, height: 100 } }); var box = new Box(); box.on("change", function () { ... }, box) .on("change:width", function () { ... }, box); box.set("width", 400); box.set("height", 300); define events

Slide 36

Slide 36 text

Change attributes var Box = Backbone.Model.extend({ defaults: { width: 100, height: 100 } }); var box = new Box(); box.on("change", function () { ... }, box) .on("change:width", function () { ... }, box); box.set("width", 400); box.set("height", 300); trigger change and change:width

Slide 37

Slide 37 text

Change attributes var Box = Backbone.Model.extend({ defaults: { width: 100, height: 100 } }); var box = new Box(); box.on("change", function () { ... }, box) .on("change:width", function () { ... }, box); box.set("width", 400); box.set("height", 300); trigger change

Slide 38

Slide 38 text

var Box = Backbone.Model.extend({ defaults: { width: 100, height: 100 }, validate: function(attrs) { if (attrs.width < 1) { return "Width cannot be less than 1"; } if (attrs.height < 1) { return "Height cannot be less than 1"; } } }); var box = new Box(); box.on("error", function(model, error) { alert(error); }); box.set('width', -1); Validate attributes add validate method

Slide 39

Slide 39 text

var Box = Backbone.Model.extend({ defaults: { width: 100, height: 100 }, validate: function(attrs) { if (attrs.width < 1) { return "Width cannot be less than 1"; } if (attrs.height < 1) { return "Height cannot be less than 1"; } } }); var box = new Box(); box.on("error", function(model, error) { alert(error); }); box.set('width', -1); Validate attributes trigger error

Slide 40

Slide 40 text

More models

Slide 41

Slide 41 text

Use Collection var Note = Backbone.Model.extend({ defaults: { title: '', content: '' }, urlRoot: '/note' }); var Notes = Backbone.Collection.extend({ model: Note }); var notes = new Notes([ { id: 1, title: 'note 1', content: 'This is note 1.' }, { id: 3, title: 'note 3', content: 'This is note 3.' }, { id: 5, title: 'note 5', content: 'This is note 5.' }, { id: 6, title: 'note 6', content: 'This is note 6.' }, { id: 9, title: 'note 9', content: 'This is note 9.' } ]); define model first

Slide 42

Slide 42 text

Use Collection var Note = Backbone.Model.extend({ defaults: { title: '', content: '' }, urlRoot: '/note' }); var Notes = Backbone.Collection.extend({ model: Note }); var notes = new Notes([ { id: 1, title: 'note 1', content: 'This is note 1.' }, { id: 3, title: 'note 3', content: 'This is note 3.' }, { id: 5, title: 'note 5', content: 'This is note 5.' }, { id: 6, title: 'note 6', content: 'This is note 6.' }, { id: 9, title: 'note 9', content: 'This is note 9.' } ]); define collection and set model class

Slide 43

Slide 43 text

Use Collection var Note = Backbone.Model.extend({ defaults: { title: '', content: '' }, urlRoot: '/note' }); var Notes = Backbone.Collection.extend({ model: Note }); var notes = new Notes([ { id: 1, title: 'note 1', content: 'This is note 1.' }, { id: 3, title: 'note 3', content: 'This is note 3.' }, { id: 5, title: 'note 5', content: 'This is note 5.' }, { id: 6, title: 'note 6', content: 'This is note 6.' }, { id: 9, title: 'note 9', content: 'This is note 9.' } ]); initialize data

Slide 44

Slide 44 text

What inside ? note note note note note length = 5 0 1 2 3 4 index id 1 3 5 6 9

Slide 45

Slide 45 text

Get model by id note 0 1 2 3 4 get(id = 3) index id 1 3 5 6 9 var note = notes.get(3); note note note note

Slide 46

Slide 46 text

note Get model by index note note note note at(index = 3) 0 1 2 3 4 index id 1 3 5 6 9 var note = notes.at(3);

Slide 47

Slide 47 text

note Remove model note note note note note remove 0 1 2 3 4 index id 1 3 5 6 9 var note = notes.at(2); notes.remove(note); // or notes.remove({ id: 5 }); length = 4

Slide 48

Slide 48 text

Add model note note note note note add length = 5 0 1 2 3 4 index id 1 3 6 9 10 var note = new Note({ id: 10, title: 'note 10', content: '...' }); notes.add(note);

Slide 49

Slide 49 text

RESTful sync

Slide 50

Slide 50 text

Create var note = new Note(); note.on('change', function () { console.log('change: ' + this.id); console.log(note.url()); }); note.on('sync', function () { console.log('sync: ' + this.id); }); note.save({ title: 'note 10', content: 'This is content.' }); Backbone.sync trigger 'sync' trigger 'change' POST http://localhost/note note.url() → http://localhost/note/10

Slide 51

Slide 51 text

Backbone.sync trigger 'sync' trigger 'change' Create var note = new Note(); note.on('change', function () { console.log('change: ' + this.id); console.log(note.url()); }); note.on('sync', function () { console.log('sync: ' + this.id); }); note.save({ title: 'note 10', content: 'This is content.' }, { wait: true }); waiting for sync completed

Slide 52

Slide 52 text

Load var note = new Note({ id: 10 }); note.fetch({ success: function (model, response) {}, error: function (model, response) {} }); GET http://localhost/note/10 { "id":10, "title":"note 10", "content":"This is note 10." } trigger 'change' Backbone.sync

Slide 53

Slide 53 text

Update note.save({ content: 'New Content Here.' }, { wait: true }); PUT http://localhost/note/10 { "id":10, "title":"note 10", "content":"New Content Here." } Backbone.sync trigger 'sync' trigger 'change'

Slide 54

Slide 54 text

Destroy note.on('destroy', function () {}); note.on('change', function () {}); note.on('sync', function () {}); note.destroy(); DELETE http://localhost/note/10 Backbone.sync trigger 'sync' trigger 'destroy' won’t invoke

Slide 55

Slide 55 text

Backbone.sync trigger 'sync' trigger 'add' Create by Collection var Notes = Backbone.Collection.extend({ model: Note, url: '/note' }); var notes = new Notes(); notes.on('add', function () {}); notes.on('sync', function () {}); var note = notes.create({ 'title': 'note 11', 'content': 'This is note 11.' }, { wait: true }); POST http://localhost/note

Slide 56

Slide 56 text

Load into Collection notes.on('add', function () {}); notes.fetch({ add: true }); GET http://localhost/note Backbone.sync trigger 'add' trigger 'add' by length of collection ...

Slide 57

Slide 57 text

Customize Backbone.sync Backbone.sync = function(method, model, options) { var response; switch (method) { case "create": /* ... */ break; case "read": /* ... */ break; case "update": /* ... */ break; case "delete": /* ... */ break; } if (response) { options.success(model, response); } else { options.error(model, response); } }; Delegate to customize persistence strategy. Example: http://documentcloud.github.com/backbone/docs/backbone-localstorage.html

Slide 58

Slide 58 text

To see is to believe

Slide 59

Slide 59 text

What is Backbone.View ? Input & Output DOM events handling Model binding Not Template

Slide 60

Slide 60 text

What is Backbone.View ? Input & Output DOM events handling Model binding Not Template

Slide 61

Slide 61 text

What is Backbone.View ? Input & Output DOM events handling Model binding Not Template

Slide 62

Slide 62 text

What is Backbone.View ? Input & Output DOM events handling Model binding Not Template

Slide 63

Slide 63 text

Manage events var Switch = Backbone.View.extend({ events: { 'click #switch': 'toggleMusic' }, toggleMusic: function (e) { this.model.set('music', $(e.target).prop('checked')); } }); event

Slide 64

Slide 64 text

Manage events var Switch = Backbone.View.extend({ events: { 'click #switch': 'toggleMusic' }, toggleMusic: function (e) { this.model.set('music', $(e.target).prop('checked')); } }); child element

Slide 65

Slide 65 text

Manage events var Switch = Backbone.View.extend({ events: { 'click #switch': 'toggleMusic' }, toggleMusic: function (e) { this.model.set('music', $(e.target).prop('checked')); } }); callback

Slide 66

Slide 66 text

Render var Status = Backbone.View.extend({ initialize: function () { this.model.on('change', this.render, this); }, render: function () { var music = this.model.get('music'); var status = music ? 'on' : 'off'; $('#status', this.el) .removeClass('on off') .addClass(status) .text(status); } });

Slide 67

Slide 67 text

Render var Status = Backbone.View.extend({ initialize: function () { this.model.on('change', this.render, this); }, render: function () { var music = this.model.get('music'); var status = music ? 'on' : 'off'; $('#status', this.el) .removeClass('on off') .addClass(status) .text(status); } });

Slide 68

Slide 68 text

Render var Status = Backbone.View.extend({ initialize: function () { this.model.on('change', this.render, this); }, render: function () { var music = this.model.get('music'); var status = music ? 'on' : 'off'; $('#status', this.el) .removeClass('on off') .addClass(status) .text(status); } });

Slide 69

Slide 69 text

Put all together $(function () { var config = new Config(); var switchView = new Switch({ el: '#input', model: config }); var statusView = new Status({ el: '#output', model: config }); }); after DOM ready

Slide 70

Slide 70 text

Put all together $(function () { var config = new Config(); var switchView = new Switch({ el: '#input', model: config }); var statusView = new Status({ el: '#output', model: config }); }); model

Slide 71

Slide 71 text

Put all together $(function () { var config = new Config(); var switchView = new Switch({ el: '#input', model: config }); var statusView = new Status({ el: '#output', model: config }); }); input

Slide 72

Slide 72 text

Put all together $(function () { var config = new Config(); var switchView = new Switch({ el: '#input', model: config }); var statusView = new Status({ el: '#output', model: config }); }); output

Slide 73

Slide 73 text

switchView statusView config new Switch({ el: '#input', model: config }) new Status({ el: '#output', model: config }) new Config()

Slide 74

Slide 74 text

events: { 'click #switch': 'toggleMusic' }, toggleMusic: function (e) { ... } switchView statusView config

Slide 75

Slide 75 text

switchView statusView config initialize: function () { this.model.on( 'change', this.render, this); }, render: function () { ... }

Slide 76

Slide 76 text

Template <article> <h1><%= title %></h1> <p><%= content %></p> </article> var NoteView = Backbone.View.extend({ tagName: 'li', template: _.template($('#note-template').html()), render: function () { this.$el.html(this.template(this.model.toJSON())); return this; } }); template

Slide 77

Slide 77 text

Template <article> <h1><%= title %></h1> <p><%= content %></p> </article> var NoteView = Backbone.View.extend({ tagName: 'li', template: _.template($('#note-template').html()), render: function () { this.$el.html(this.template(this.model.toJSON())); return this; } }); View
  • note 5

    This is note 5.

  • Slide 78

    Slide 78 text

    HTML Container
    var NotesView = Backbone.View.extend({ initialize: function () { this.render.apply(this); }, render: function () { this.collection.forEach(function (model) { var noteView = new NoteView({ model: model, }); this.$el.append(noteView.render().el); }, this); } }); HTML

    Slide 79

    Slide 79 text

    HTML Container
    var NotesView = Backbone.View.extend({ initialize: function () { this.render.apply(this); }, render: function () { this.collection.forEach(function (model) { var noteView = new NoteView({ model: model, }); this.$el.append(noteView.render().el); }, this); } }); View

    Slide 80

    Slide 80 text

    HTML Container
    var NotesView = Backbone.View.extend({ initialize: function () { this.render.apply(this); }, render: function () { this.collection.forEach(function (model) { var noteView = new NoteView({ model: model, }); this.$el.append(noteView.render().el); }, this); } }); render each note

    Slide 81

    Slide 81 text

    Template Engines Underscore.js template http://documentcloud.github.com/underscore/#template JavaScript Micro-Templating http://ejohn.org/blog/javascript-micro-templating/ jQuery Templates Plugin https://github.com/jquery/jquery-tmpl EJS http://embeddedjs.com/

    Slide 82

    Slide 82 text

    Route the requests

    Slide 83

    Slide 83 text

    Single page app ...

    Slide 84

    Slide 84 text

    Single page app var App = {}; App.Router = Backbone.Router.extend({ routes: { '': 'index', 'page/:page': 'page', 'help': 'help', '*error': 'notFound' }, index: function () {}, page: function (page) {}, help: function () {}, notFound: function () {} });

    Slide 85

    Slide 85 text

    Single page app var App = {}; App.Router = Backbone.Router.extend({ routes: { '': 'index', 'page/:page': 'page', 'help': 'help', '*error': 'notFound' }, index: function () {}, page: function (page) {}, help: function () {}, notFound: function () {} }); not 'routers' !!!

    Slide 86

    Slide 86 text

    Single page app var App = {}; App.Router = Backbone.Router.extend({ routes: { '': 'index', 'page/:page': 'page', 'help': 'help', '*error': 'notFound' }, index: function () {}, page: function (page) {}, help: function () {}, notFound: function () {} }); http://localhost/ http://localhost/#

    Slide 87

    Slide 87 text

    Single page app var App = {}; App.Router = Backbone.Router.extend({ routes: { '': 'index', 'page/:page': 'page', 'help': 'help', '*error': 'notFound' }, index: function () {}, page: function (page) {}, help: function () {}, notFound: function () {} }); http://localhost/#page/1 http://localhost/#page/2 http://localhost/#page/3

    Slide 88

    Slide 88 text

    Single page app var App = {}; App.Router = Backbone.Router.extend({ routes: { '': 'index', 'page/:page': 'page', 'help': 'help', '*error': 'notFound' }, index: function () {}, page: function (page) {}, help: function () {}, notFound: function () {} }); http://localhost/#help

    Slide 89

    Slide 89 text

    Single page app var App = {}; App.Router = Backbone.Router.extend({ routes: { '': 'index', 'page/:page': 'page', 'help': 'help', '*error': 'notFound' }, index: function () {}, page: function (page) {}, help: function () {}, notFound: function () {} }); other url hash (404)

    Slide 90

    Slide 90 text

    Single page app var App = {}; App.Router = Backbone.Router.extend({ routes: { '': 'index', 'page/:page': 'page', 'help': 'help', '*error': 'notFound' }, index: function () {}, page: function (page) {}, help: function () {}, notFound: function () {} }); put the models and views here

    Slide 91

    Slide 91 text

    Starting route $(function () { var router = new App.Router(); Backbone.history.start(); }); not 'Backbone.History'

    Slide 92

    Slide 92 text

    Push state $(function () { var router = new App.Router(); Backbone.history.start({ pushState: true }); }); Only HTML5 support, Use iframe in old IE relocate http://localhost/#page/1 ! http://localhost/#help ! http://localhost/page/1 http://localhost/help ! !

    Slide 93

    Slide 93 text

    What happens ? $(function () { var router = new App.Router(); Backbone.history.start({ pushState: true }); }); relocate http://localhost/#page/1 ! http://localhost/#help ! http://localhost/page/1 http://localhost/help ! ! Will not change url until reload

    Slide 94

    Slide 94 text

    What happens ? $(function () { var router = new App.Router(); Backbone.history.start({ pushState: true }); }); relocate http://localhost/#page/1 ! http://localhost/#help ! http://localhost/page/1 http://localhost/help ! ! Should be render same result when you reload the page.

    Slide 95

    Slide 95 text

    What happens ? $(function () { var router = new App.Router(); Backbone.history.start({ pushState: true }); }); relocate http://localhost/#page/1 ! http://localhost/#help ! http://localhost/page/1 http://localhost/help ! ! http://localhost/page/1#page/2 but ... trigger nothing

    Slide 96

    Slide 96 text

    Navigate url var usePushState = true; Backbone.history.start({ pushState: usePushState }); if (usePushState) { $('a[href^="#"]').on('click', function (e) { e.preventDefault(); router.navigate($(this).attr('href'), true); }); } don’t use prop method

    Slide 97

    Slide 97 text

    Pushstate in sub directory Backbone.history.start({ pushState: true, root: '/subdir/' }); use root when your app in sub directory Example: http://localhost/subdir/#page/2

    Slide 98

    Slide 98 text

    DEMO http://jaceju.github.com/Head-First-Backbone.js

    Slide 99

    Slide 99 text

    Be fun

    Slide 100

    Slide 100 text

    Discuss