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. 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>
  2. 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>
  3. 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>
  4. 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>
  5. 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.
  6. 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.
  7. 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.
  8. 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.
  9. 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.
  10. 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.
  11. 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
  12. 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
  13. 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
  14. 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
  15. 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
  16. 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
  17. 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
  18. 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>
  19. 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
  20. 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
  21. 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
  22. 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
  23. 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
  24. 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
  25. 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
  26. 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
  27. 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
  28. 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
  29. 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
  30. 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
  31. 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
  32. 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
  33. 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
  34. What inside ? note note note note note length =

    5 0 1 2 3 4 index id 1 3 5 6 9
  35. 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
  36. 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);
  37. 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
  38. 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);
  39. 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
  40. 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
  41. 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
  42. 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'
  43. 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
  44. 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
  45. 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 ...
  46. 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
  47. Manage events var Switch = Backbone.View.extend({ events: { 'click #switch':

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

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

    'toggleMusic' }, toggleMusic: function (e) { this.model.set('music', $(e.target).prop('checked')); } }); callback
  50. 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); } });
  51. 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); } });
  52. 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); } });
  53. 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
  54. 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
  55. 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
  56. 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
  57. switchView statusView config new Switch({ el: '#input', model: config })

    new Status({ el: '#output', model: config }) new Config()
  58. 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
  59. 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>
  60. 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
  61. 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
  62. 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
  63. 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>
  64. 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 () {} });
  65. 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' !!!
  66. 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/#
  67. 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
  68. 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
  69. 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)
  70. 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
  71. Starting route $(function () { var router = new App.Router();

    Backbone.history.start(); }); not 'Backbone.History'
  72. 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 ! !
  73. 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
  74. 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.
  75. 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
  76. 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
  77. 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