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

The Backbone.js Refactor

The Backbone.js Refactor

Present at Confoo.ca

Daniel Cousineau

March 01, 2013
Tweet

More Decks by Daniel Cousineau

Other Decks in Programming

Transcript

  1. +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
  2. +Functional Programming??? Daniel Cousineau // follow me : @dcousineau or

    http://dcousineau.com function input output global state global state global state X X X
  3. +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
  4. +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]
  5. +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
  6. +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"]
  7. +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}
  8. +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"]
  9. +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"}
  10. +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"
  11. +backbone.js Daniel Cousineau // follow me : @dcousineau or http://dcousineau.com

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

    ‣Center of a Backbone Application ‣Provides •Event notification of changes •Ajax support
  13. +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
  14. +collection Daniel Cousineau // follow me : @dcousineau or http://dcousineau.com

    ‣“Collection of models” ‣Convenience event delegation
  15. +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
  16. +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
  17. +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>
  18. +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: '/'});
  19. Daniel Cousineau // follow me : @dcousineau or http://dcousineau.com Because

    you’re not lucky enough right now to start a project from scratch.
  20. +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>
  21. +looking conceptually Daniel Cousineau // follow me : @dcousineau or

    http://dcousineau.com scaffolding DOM Management Event Management State Management Data Synchronization
  22. 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
  23. 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
  24. 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
  25. 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
  26. 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
  27. 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
  28. 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
  29. 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
  30. 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
  31. 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
  32. +Looking Conceptually Daniel Cousineau // follow me : @dcousineau or

    http://dcousineau.com widgets Modules Full Suite
  33. 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>
  34. 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>
  35. 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);
  36. Q& A Put your questions Daniel Cousineau // follow me

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

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