Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Head First Backbone.js

Head First Backbone.js

大澤木小鐵

March 15, 2012
Tweet

More Decks by 大澤木小鐵

Other Decks in Programming

Transcript

  1. Head First Backbone.js Jace Ju

  2. Why Bacbkone.js ?

  3. Spaghetti code Output Input Global Variables Business Logic

  4. Organized code Model Output Input Business Logic

  5. Separation of concerns Router Model View Url and Pages Data

    and Status DOM and Template
  6. How to start ?

  7. Load these scripts underscore.js jQuery Backbone.js Your JavaScript <script src="/lib/underscore.js"></script>

    <script src="/lib/jquery.js"></script> <script src="/lib/backbone.js"></script> <script src="/js/your_app.js"></script>
  8. Load these scripts underscore.js jQuery Backbone.js Your JavaScript <script src="/lib/underscore.js"></script>

    <script src="/lib/jquery.js"></script> <script src="/lib/backbone.js"></script> <script src="/js/your_app.js"></script>
  9. Load these scripts underscore.js jQuery Backbone.js Your JavaScript <script src="/lib/underscore.js"></script>

    <script src="/lib/jquery.js"></script> <script src="/lib/backbone.js"></script> <script src="/js/your_app.js"></script>
  10. Load these scripts underscore.js jQuery Backbone.js Your JavaScript <script src="/lib/underscore.js"></script>

    <script src="/lib/jquery.js"></script> <script src="/lib/backbone.js"></script> <script src="/js/your_app.js"></script>
  11. Basic Classes Sync View Event History extend use Router Model

    Collection
  12. 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.
  13. 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.
  14. 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.
  15. 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.
  16. 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.
  17. 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.
  18. Event first

  19. 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
  20. 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
  21. 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
  22. 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
  23. 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
  24. 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
  25. 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
  26. Just model

  27. How to share status ? <div class="input"> <input type="checkbox" id="switch"

    value="on" /> <label for="switch">Turn on music.</label> </div> <div class="output"> Music: <span id="status" class="off">off</span> </div>
  28. 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
  29. 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
  30. 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
  31. 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
  32. 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
  33. 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
  34. 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
  35. 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
  36. 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
  37. 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
  38. 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
  39. 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
  40. More models

  41. 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
  42. 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
  43. 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
  44. What inside ? note note note note note length =

    5 0 1 2 3 4 index id 1 3 5 6 9
  45. 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
  46. 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);
  47. 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
  48. 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);
  49. RESTful sync

  50. 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
  51. 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
  52. 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
  53. 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'
  54. 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
  55. 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
  56. 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 ...
  57. 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
  58. To see is to believe

  59. What is Backbone.View ? Input & Output DOM events handling

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

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

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

    Model binding Not Template
  63. Manage events var Switch = Backbone.View.extend({ events: { 'click #switch':

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

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

    'toggleMusic' }, toggleMusic: function (e) { this.model.set('music', $(e.target).prop('checked')); } }); callback
  66. 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); } });
  67. 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); } });
  68. 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); } });
  69. 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
  70. 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
  71. 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
  72. 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
  73. switchView statusView config new Switch({ el: '#input', model: config })

    new Status({ el: '#output', model: config }) new Config()
  74. events: { 'click #switch': 'toggleMusic' }, toggleMusic: function (e) {

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

    this); }, render: function () { ... }
  76. Template <script type="text/template" id="note-template"> <article> <h1><%= title %></h1> <p><%= content

    %></p> </article> </script> 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
  77. Template <script type="text/template" id="note-template"> <article> <h1><%= title %></h1> <p><%= content

    %></p> </article> </script> 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 <li> <article> <h1>note 5</h1> <p>This is note 5.</p> </article> </li>
  78. HTML Container <ul id="notes"> </ul> 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
  79. HTML Container <ul id="notes"> </ul> 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
  80. HTML Container <ul id="notes"> </ul> 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
  81. 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/
  82. Route the requests

  83. Single page app <article> ... <ul> <li><a href="#page/1">Page 1</a></li> <li><a

    href="#page/2">Page 2</a></li> <li><a href="#page/3">Page 3</a></li> </ul> </article> <footer> <ul> <li><a href="#help">Help</a></li> <li><a href="#">Home</a></li> </ul> </footer>
  84. 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 () {} });
  85. 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' !!!
  86. 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/#
  87. 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
  88. 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
  89. 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)
  90. 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
  91. Starting route $(function () { var router = new App.Router();

    Backbone.history.start(); }); not 'Backbone.History'
  92. 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 ! !
  93. 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
  94. 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.
  95. 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
  96. 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
  97. 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
  98. DEMO http://jaceju.github.com/Head-First-Backbone.js

  99. Be fun

  100. Discuss