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

The Backbone.js Refactor

The Backbone.js Refactor

Present at Confoo.ca

6696617169722009ed1ec8c52496c6da?s=128

Daniel Cousineau

March 01, 2013
Tweet

Transcript

  1. Daniel Cousineau // follow me : @dcousineau or http://dcousineau.com The

    Backbone.js Refactor
  2. Daniel Cousineau Developer at SplashMedia @dcousineau

  3. intro Daniel Cousineau // follow me : @dcousineau or http://dcousineau.com

  4. Daniel Cousineau // follow me : @dcousineau or http://dcousineau.com underscorejs.org

  5. Daniel Cousineau // follow me : @dcousineau or http://dcousineau.com Functional,

    self-contained micro-library
  6. +Functional Programming??? Daniel Cousineau // follow me : @dcousineau or

    http://dcousineau.com Functions are mappings between input and output • They have no side-effects • Input A always results in output b
  7. +Functional Programming??? Daniel Cousineau // follow me : @dcousineau or

    http://dcousineau.com function input output global state global state global state X X X
  8. +underscore.js Daniel Cousineau // follow me : @dcousineau or http://dcousineau.com

    Collections •each •map •reduce •reduceRight •find •filter •reject •all •any •include •invoke •pluck •max •min •sortBy •groupBy •sortedIndex •shuffle •toArray •size Arrays •first •initial •last •rest •compact •flatten •without •union •intersection •difference •uniq •zip •indexOf •lastIndexOf •range Functions •bind •bindAll •memoize •delay •defer •throttle •debounce •once •after •wrap •compose Objects •keys •values •functions •extend •pick •defaults •clone •tap •has •isEqual •isEmpty •isElement •isArray •isObject •isArguments •isFunction •isString •isNumber •isFinite •isBoolean •isDate •isRegExp •isNaN •isNull •isUndefined Utility •noConflict •identity •times •mixin •uniqueId •escape •result •template Chaining •chain •value
  9. +map Daniel Cousineau // follow me : @dcousineau or http://dcousineau.com

    Maps function to each element in the input collection var inp = [1, 2, 3] , out = _.map(inp, function(n){ return n*2; }); //out = [2, 4, 6]
  10. +reduce Daniel Cousineau // follow me : @dcousineau or http://dcousineau.com

    Reduces collection to a single value. mem is the initial state, each successive iteration must be returned var inp = [1, 2, 3]; _(inp).reduce(function(mem, n){ return mem + n; }); //Iter 0: mem = 1 | n = 2 //Iter 1: mem = 3 | n = 3 //Returns: 6
  11. +pluck Daniel Cousineau // follow me : @dcousineau or http://dcousineau.com

    Iterates over a collection and extracts the values for the input key (assumes all elements in the collection are objects/arrays) var stooges = [ {name: 'moe', age: 40} , {name: 'larry', age: 50} , {name: 'curly', age: 60} ]; _.pluck(stooges, 'name'); //Returns ["moe", "larry", "curly"]
  12. +min/max Daniel Cousineau // follow me : @dcousineau or http://dcousineau.com

    Returns the max item in a collection. If second argument (iterator) provided, will use to produce the value to be compared var stooges = [ {name: 'moe', age: 40} , {name: 'larry', age: 50} , {name: 'curly', age: 60} ]; _.max(stooges, function(s){ return s.age; }); //Returns {name: 'curly', age: 60}
  13. +keys Daniel Cousineau // follow me : @dcousineau or http://dcousineau.com

    Returns the keys from a dictionary as an array _.keys({ one: 1, two: 2, three: 3 }); //Returns ["one", "two", "three"]
  14. +defaults Daniel Cousineau // follow me : @dcousineau or http://dcousineau.com

    Maps a duplicate input dictionary on top of a predefined “default” dictionary var iceCream = { flavor: "chocolate" }; _.defaults(iceCream, { flavor: "vanilla" , sprinkles: "lots" }); //Returns {flavor : "chocolate", sprinkles : "lots"}
  15. +chaining Daniel Cousineau // follow me : @dcousineau or http://dcousineau.com

    When chain is initiated, method return a self-reference back to underscore but with the value attached [similar to opening with _(val)]. The chain continues until the value is extracted using .value() var stooges = [ {name: 'curly', age : 25}, {name: 'moe', age : 21}, {name: 'larry', age : 23} ]; var youngest = _.chain(stooges) .sortBy(function(s){ return s.age; }) .map(function(s){ return s.name + ' is ' + s.age; }) .first() .value(); //Returns "moe is 21"
  16. Daniel Cousineau // follow me : @dcousineau or http://dcousineau.com backbonejs.org

  17. Daniel Cousineau // follow me : @dcousineau or http://dcousineau.com micro-application

    framework
  18. +backbone.js Daniel Cousineau // follow me : @dcousineau or http://dcousineau.com

    •builds on jquery/Zepto and underscore.js •provides • views • models • collections • router
  19. +Model Daniel Cousineau // follow me : @dcousineau or http://dcousineau.com

    ‣Center of a Backbone Application ‣Provides •Event notification of changes •Ajax support
  20. +model Daniel Cousineau // follow me : @dcousineau or http://dcousineau.com

    var OurModel = Backbone.Model.extend({ defaults: { foo: 'value' , bar: 1 } }); var instance = new OurModel(); instance.on('change:foo', function(modelobj, foo) { console.log('foo is now ' + foo); }); instance.set('foo', 'bat'); //foo is now bat
  21. +collection Daniel Cousineau // follow me : @dcousineau or http://dcousineau.com

    ‣“Collection of models” ‣Convenience event delegation
  22. +collection Daniel Cousineau // follow me : @dcousineau or http://dcousineau.com

    var OurModelCollection = Backbone.Collection.extend({ model: OurModel }); var inst1 = new OurModel() , inst2 = new OurModel() , coll = new OurModelCollection(); coll.on('add', function(modelobj) { console.log('Added model ' + modelobj.cid); }); coll.on('change:bar', function(modelobj, bar) { console.log('Bar for ' + modelobj.cid + ' is now ' + bar); }); coll.add(inst1); coll.add(inst2); inst1.set('bar', 'baz'); //Added model c4 //Added model c5 //Bar for c4 is now baz
  23. +view Daniel Cousineau // follow me : @dcousineau or http://dcousineau.com

    ‣the ‘other center’ of a backbone application ‣provides •abstracted event binding •parent dom element management
  24. +view Daniel Cousineau // follow me : @dcousineau or http://dcousineau.com

    var OurView = Backbone.View.extend({ className: 'our-view' , initialize: function() { this.model.on('change:foo', this.updateFoo, this); } , render: function() { this.updateFoo(); return this; } , updateFoo: function() { this.$el.html(this.model.get('foo')); } }); var modelInst = new OurModel({foo: 'bar'}); , viewInst = new OurView({model: modelInst}); $('#our-container').append(viewInst.render().el); modelInst.set('foo', 'baz'); //<div class="our-view">bar</div> //<div class="our-view">baz</div>
  25. +router Daniel Cousineau // follow me : @dcousineau or http://dcousineau.com

    ‣uri matching and view scaffolding
  26. +router Daniel Cousineau // follow me : @dcousineau or http://dcousineau.com

    namespace.MessageCollectionRouter = Backbone.Router.extend({ routes: { "page/:page": "viewPage" } , initialize: function(options){ this.collection = options.collection; } , viewPage: function(page) { this.collection.setPage(page); } , setUrlToPage: function(page) { this.navigate('page/' + page, {trigger:false}); } }); Backbone.history.start({pushState: true, root: '/'});
  27. refactor Daniel Cousineau // follow me : @dcousineau or http://dcousineau.com

  28. Daniel Cousineau // follow me : @dcousineau or http://dcousineau.com Because

    you’re not lucky enough right now to start a project from scratch.
  29. +first steps Daniel Cousineau // follow me : @dcousineau or

    http://dcousineau.com <script src="./scripts/jquery-1.9.1.js"></script> <script src="./scripts/underscore.js"></script> <script src="./scripts/backbone.js"></script> <script src="./scripts/jquery-1.9.1.js"></script>
  30. +looking conceptually Daniel Cousineau // follow me : @dcousineau or

    http://dcousineau.com scaffolding DOM Management Event Management State Management Data Synchronization
  31. Daniel Cousineau // follow me : @dcousineau or http://dcousineau.com var

    defaults = {/* ... */}; var methods = { init: function(options) { if (!$(this).is('textarea')) { $.error('Poster must be applied to a textarea'); return undefined; } options = _.defaults(options, defaults); var poster = new Poster(this, options); $(this).data('_poster', poster); return this; } }; $.fn.splash_poster = function(method) { //jQuery Plugin Boilerplate }; function Poster($el, options) { this.$el = $el; this.init(); } Poster.prototype.init = function() { var that = this; this.$el.on('change', jQuery.proxy(this.changeHandler, this)); this.$el.on('keyup', jQuery.proxy(this.changeHandler, this)); }; Poster.prototype.changeHandler = function(e) { //Check character counts, identify urls }; jQuery
  32. Daniel Cousineau // follow me : @dcousineau or http://dcousineau.com var

    defaults = {/* ... */}; var methods = { init: function(options) { if (!$(this).is('textarea')) { $.error('Poster must be applied to a textarea'); return undefined; } options = _.defaults(options, defaults); var poster = new Poster(this, options); $(this).data('_poster', poster); return this; } }; $.fn.splash_poster = function(method) { //jQuery Plugin Boilerplate }; function Poster($el, options) { this.$el = $el; this.init(); } Poster.prototype.init = function() { var that = this; this.$el.on('change', jQuery.proxy(this.changeHandler, this)); this.$el.on('keyup', jQuery.proxy(this.changeHandler, this)); }; Poster.prototype.changeHandler = function(e) { //Check character counts, identify urls }; scaffolding
  33. Daniel Cousineau // follow me : @dcousineau or http://dcousineau.com var

    defaults = {/* ... */}; var methods = { init: function(options) { if (!$(this).is('textarea')) { $.error('Poster must be applied to a textarea'); return undefined; } options = _.defaults(options, defaults); var poster = new Poster(this, options); $(this).data('_poster', poster); return this; } }; $.fn.splash_poster = function(method) { //jQuery Plugin Boilerplate }; function Poster($el, options) { this.$el = $el; this.init(); } Poster.prototype.init = function() { var that = this; this.$el.on('change', jQuery.proxy(this.changeHandler, this)); this.$el.on('keyup', jQuery.proxy(this.changeHandler, this)); }; Poster.prototype.changeHandler = function(e) { //Check character counts, identify urls }; DOM Management
  34. Daniel Cousineau // follow me : @dcousineau or http://dcousineau.com var

    defaults = {/* ... */}; var methods = { init: function(options) { if (!$(this).is('textarea')) { $.error('Poster must be applied to a textarea'); return undefined; } options = _.defaults(options, defaults); var poster = new Poster(this, options); $(this).data('_poster', poster); return this; } }; $.fn.splash_poster = function(method) { //jQuery Plugin Boilerplate }; function Poster($el, options) { this.$el = $el; this.init(); } Poster.prototype.init = function() { var that = this; this.$el.on('change', jQuery.proxy(this.changeHandler, this)); this.$el.on('keyup', jQuery.proxy(this.changeHandler, this)); }; Poster.prototype.changeHandler = function(e) { //Check character counts, identify urls }; Event Management
  35. Daniel Cousineau // follow me : @dcousineau or http://dcousineau.com var

    defaults = {/* ... */}; var methods = { init: function(options) { if (!$(this).is('textarea')) { $.error('Poster must be applied to a textarea'); return undefined; } options = _.defaults(options, defaults); var poster = new Poster(this, options); $(this).data('_poster', poster); return this; } }; $.fn.splash_poster = function(method) { //jQuery Plugin Boilerplate }; function Poster($el, options) { this.$el = $el; this.init(); } Poster.prototype.init = function() { var that = this; this.$el.on('change', jQuery.proxy(this.changeHandler, this)); this.$el.on('keyup', jQuery.proxy(this.changeHandler, this)); }; Poster.prototype.changeHandler = function(e) { //Check character counts, identify urls }; State Management
  36. var Poster = Backbone.View.extend({ tagName: 'form' , events: { "change

    textarea.message": "eChangeBody" , "keyup textarea.message": "eChangeBody" } , initialize: function() { this.render(); this.setModel(this.model); } , setModel: function(model) { this.model = model; this.model.on('change:body', _.bind(this.handleUpdateBody, this)); this.handleUpdateBody(this.model, this.model.get('body')); } , handleUpdateBody: function(model, body) { this.$body.val(body).change(); } , eChangeBody: function(e) { //Check character counts, identify urls } , render: function() { this.$el.append(this._renderBody()); this.$body = this.$('textarea.message'); return this; } }); Backbone.js
  37. Daniel Cousineau // follow me : @dcousineau or http://dcousineau.com var

    Poster = Backbone.View.extend({ tagName: 'form' , events: { "change textarea.message": "eChangeBody" , "keyup textarea.message": "eChangeBody" } , initialize: function() { this.render(); this.setModel(this.model); } , setModel: function(model) { this.model = model; this.model.on('change:body', _.bind(this.handleUpdateBody, this)); this.handleUpdateBody(this.model, this.model.get('body')); } , handleUpdateBody: function(model, body) { this.$body.val(body).change(); } , eChangeBody: function(e) { //Check character counts, identify urls } , render: function() { this.$el.append(this._renderBody()); this.$body = this.$('textarea.message'); return this; } }); scaffolding
  38. Daniel Cousineau // follow me : @dcousineau or http://dcousineau.com var

    Poster = Backbone.View.extend({ tagName: 'form' , events: { "change textarea.message": "eChangeBody" , "keyup textarea.message": "eChangeBody" } , initialize: function() { this.render(); this.setModel(this.model); } , setModel: function(model) { this.model = model; this.model.on('change:body', _.bind(this.handleUpdateBody, this)); this.handleUpdateBody(this.model, this.model.get('body')); } , handleUpdateBody: function(model, body) { this.$body.val(body).change(); } , eChangeBody: function(e) { //Check character counts, identify urls } , render: function() { this.$el.append(this._renderBody()); this.$body = this.$('textarea.message'); return this; } }); dom management
  39. Daniel Cousineau // follow me : @dcousineau or http://dcousineau.com var

    Poster = Backbone.View.extend({ tagName: 'form' , events: { "change textarea.message": "eChangeBody" , "keyup textarea.message": "eChangeBody" } , initialize: function() { this.render(); this.setModel(this.model); } , setModel: function(model) { this.model = model; this.model.on('change:body', _.bind(this.handleUpdateBody, this)); this.handleUpdateBody(this.model, this.model.get('body')); } , handleUpdateBody: function(model, body) { this.$body.val(body).change(); } , eChangeBody: function(e) { //Check character counts, identify urls } , render: function() { this.$el.append(this._renderBody()); this.$body = this.$('textarea.message'); return this; } }); event management
  40. Daniel Cousineau // follow me : @dcousineau or http://dcousineau.com var

    Poster = Backbone.View.extend({ tagName: 'form' , events: { "change textarea.message": "eChangeBody" , "keyup textarea.message": "eChangeBody" } , initialize: function() { this.render(); this.setModel(this.model); } , setModel: function(model) { this.model = model; this.model.on('change:body', _.bind(this.handleUpdateBody, this)); this.handleUpdateBody(this.model, this.model.get('body')); } , handleUpdateBody: function(model, body) { this.$body.val(body).change(); } , eChangeBody: function(e) { //Check character counts, identify urls, update model } , render: function() { this.$el.append(this._renderBody()); this.$body = this.$('textarea.message'); return this; } }); state management
  41. +Looking Conceptually Daniel Cousineau // follow me : @dcousineau or

    http://dcousineau.com widgets Modules Full Suite
  42. Daniel Cousineau // follow me : @dcousineau or http://dcousineau.com <html>

    <!-- ... --> <div id="widget"> <ul> <li> <span class="item" data-id="1"> Item 1 <a href="/url/del">Delete</a> </span> </li> <!-- ... --> </ul> </div> <!-- ... --> <script> $('#widget .item a').click(function(e){ var $this = $(this) , $parent = $this.parent() , id = $parent.data('id'); $.ajax(/* … */); }); //... </script> </body> </html>
  43. Daniel Cousineau // follow me : @dcousineau or http://dcousineau.com <html>

    <!-- ... --> <div id="widget"> <ul> <li> <span class="item" data-id="1"> Item 1 <a href="/url/del">Delete</a> </span> </li> <!-- ... --> </ul> </div> <!-- ... --> <script src="./post.js"></script> <script> $(function(){ var view = new Foo.Bar({el: $('#widget')}).render(); }); </script> </body> </html>
  44. Daniel Cousineau // follow me : @dcousineau or http://dcousineau.com (function(namespace,

    $){ namespace.Bar = Backbone.View.extend({ events: { 'click #widget .item a': 'clickItemDelete' } , initialize: function() { } , render: function() { //... return this; } , clickItemDelete: function(e) { var $this = $(e.currentTarget) , $parent = $this.parent() , id = $parent.data('id'); $.ajax(/* … */); } }); })(window.Foo = window.Foo || {}, jQuery);
  45. +Backend Daniel Cousineau // follow me : @dcousineau or http://dcousineau.com

    reuse Backend refactor Backend
  46. case study Daniel Cousineau // follow me : @dcousineau or

    http://dcousineau.com
  47. +Case Study Daniel Cousineau // follow me : @dcousineau or

    http://dcousineau.com
  48. +Headline Daniel Cousineau // follow me : @dcousineau or http://dcousineau.com

  49. Q& A Put your questions Daniel Cousineau // follow me

    : @dcousineau or http://dcousineau.com
  50. THANKS. FOR YOUR ATTENTION Daniel Cousineau // follow me :

    @dcousineau or http://dcousineau.com http://joind.in/7993