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

Embracing the client with Backbone.js

Embracing the client with Backbone.js

Talk on MVC and Backbone given to the Dallas HTML5 Users Group.

Avatar for Nicholas Spencer

Nicholas Spencer

December 18, 2012
Tweet

Other Decks in Programming

Transcript

  1. Who’s this guy? • Grew up on JavaScript & LAMP

    • Objective-C, OS X & iOS • Placethings • BA of Arts and Technology from UT Dallas • Match.com on Facebook Wednesday, December 19, 12 Introduce my awesomeself Started playing with javascript in middle school (like inline onClick crap) Began developing web applications in high school Started working for a web shop directly after graduating high school. That was fun... but not for me so I went back to school @ UTD.
  2. What’s all this about? Wednesday, December 19, 12 Why are

    you here tonight? Who’s built some client side stuff in javascript? AJAXy stuff? More client heavy apps? Full JavaScript applications? Why should you want to use Backbone.js or go client side at all? ‘cause JavaScript is cool and you can debate semicolons, bro. Maintainable JavaScript.
  3. Production Code /* who calls this? */ photoCallback = function

    (data) { if (data.Payload == "") { $('#photoalbum').append('<li class="photo">No picture available</li>'); } else { $.each(data.Payload, function (index, value) { var pc = window.location.protocol; if (pc.indexOf('https:') != -1) { var imgUrl = '<img src="https://securethumbnails.match.com/thumbnails/' + value.PhotoUrl + '" />'; } else { var imgUrl = '<img src="http://thumbnails.match.com/thumbnails/' + value.PhotoUrl + '" />'; } $('#photoalbum').append('<li class="photo">' + imgUrl + '</li>'); }); var images = $('#photoalbum li img'); var nimages = images.length; images.load(function () { nimages--; if (nimages == 0) { $("#photoalbum").css("display", "block"); $('#photoalbum').jcarousel(); } }); } }; Wednesday, December 19, 12
  4. Production Code $('#reg1Form').live('submit', function (e) { var tracking = '';

    var buttonText = $(this).find('.goButton').html(); $(this).find('.goButton').html('loading...'); $(this).find('.goButton').attr('disabled', 'disabled'); store.get('s_modalaction', function (ok, val) { if (val) { s_modalaction = jQuery.parseJSON(val) } else { s_modalaction = '' } }); if ($('#reg1Modal #gender').length) { var gender = $('#reg1Modal #gender').val(); } else { store.get('s_regGender', function (ok, val) { s_regGender = val }); var gender = s_regGender; } if ($('#reg1Modal #seeking').length) { var seeking = $('#reg1Modal #seeking').val(); } else { store.get('s_regSeeking', function (ok, val) { s_regSeeking = val }); if (s_regSeeking == 'male') { var seeking = 1; } else { var seeking = 2; } } if ($('#country').val() == 'US') { var country = 'United-States'; } else { var country = $('#country').val(); } ///It keeps going Wednesday, December 19, 12
  5. Production Code $('button.winksrecvd').live('click', function (e) { ///this action will take

    you to search once it is completed. For now it just takes you Home. top.location = '//apps.facebook.com/' + appData.uri + '/Winks.aspx?fid=R'; e.preventDefault(); }); $('button.favorites').live('click', function (e) { ///this action will take you to search once it is completed. For now it just takes you Home. top.location = '//apps.facebook.com/' + appData.uri + '/Favorites.aspx?fid=S'; e.preventDefault(); }); $('button.subRedirect').live('click', function (e) { ///this action will take you to search once it is completed. For now it just takes you Home. top.location = '//apps.facebook.com/' + appData.uri + '/SubRedirect.aspx'; e.preventDefault(); }); $('button.search').live('click', function (e) { top.location = '//apps.facebook.com/' + appData.uri + '/Search.aspx'; e.preventDefault(); }); $('button.matches').live('click', function (e) { top.location = '//apps.facebook.com/' + appData.uri + '/Matches.aspx'; e.preventDefault(); }); $('a.viewProfile').live('click', function (e) { store.get('m_userid', function (ok, val) { m_userid = val }); top.location = '//apps.facebook.com/' + appData.uri + '/Profile.aspx?uid=' + m_userid; e.preventDefault(); }); $('#sub1form').live('submit', function (e) { modalClick('sub2', 670, 700, 40, 80); Wednesday, December 19, 12
  6. It works (until your application actually does anything) Wednesday, December

    19, 12 As your application grows, this sort of approach becomes unwieldy
  7. It works (until your application actually does anything and you

    have to introduce your code base to your new hire) Wednesday, December 19, 12 That dear poor soul that has to come work on the application after you. >>
  8. True Story • 1 File • 2723 Lines of Code

    • 254 Functions Wednesday, December 19, 12 This slide is based on a true story.
  9. “Photograph of once-beautiful forest tragically burning down amidst terrible roaring

    fires, presumably full of flaming animals” Wednesday, December 19, 12 >>
  10. Software Design Patterns Wednesday, December 19, 12 All software development

    is about using patterns to get a job done. The use of software design patterns in your application typical indicates that some thought was put into your solution. The previous two code snippets are still used in our backbone applications but in a more appropriate fashion. Tonight we’re going to focus on... >>
  11. MVC Model 2 Controller Model View Browser HTTP Request /

    Response Wednesday, December 19, 12 MVC for HTTP request/response applications Implemented by JSP Similarly seen in most web frameworks
  12. MVC Model View Controller Users Wednesday, December 19, 12 Traditional

    MVC originally implemented by smalltalk-80. (1970s) Perhaps originally more low level than we need today. Controllers should relay user interaction from input devices to the model. (Input Controller)
  13. MVC Model View Controller Users Wednesday, December 19, 12 More

    realistic modern implementation of MVC. User interacts with the view which propagates down to the controller.
  14. MVP, MVVM Model View Presenter, ViewModel Users Wednesday, December 19,

    12 Re-interpretations of MVC It’s all about separation of concerns
  15. MV* Model View Binding,Dispatching,* User Wednesday, December 19, 12 Re-interpretations

    of MVC It’s all about separation of concerns This helps split up code into different files (units) Separate files (with separate areas of functionality) means a more navigable code base Helps create testable units of functionality
  16. MV* Model View * User View * View * Wednesday,

    December 19, 12 A change on the model is propagated to all representations of that model
  17. MV* Model View * User View * View * and

    now for something completely different! Wednesday, December 19, 12 This gets really interesting. Present stale data from local storage, fetch new data from server, re- render on changes. Websockets!
  18. Backbone.js Wednesday, December 19, 12 Written for DocumentCloud, Document searching

    tool written for reports and editors. Originally a nonprofit funded by a Knight News Foundation grant. Now a part of the nonprofit group Investigative Reporters and Editors.
  19. for more: http://backbonejs.org/#examples Wednesday, December 19, 12 It’s since been

    adopted by different industries. new hulu linkedin mobile gilt mobile, gilt live new foursquare new usatoday Go checkout http://backbonejs.org/#examples
  20. Backbone.js •Version 0.9.9 •56 kb (6.3 kb) •1533 Lines of

    Code Wednesday, December 19, 12 read the annotated source! it’s pretty cool.
  21. Backbone.js • Required Dependencies • Underscore • Lo-Dash (lodash backbone)

    • Optional Dependencies • json2.js • Jquery • Zepto • Ender • + packages Wednesday, December 19, 12
  22. Backbone.Model var User = Backbone.Model.extend({ defaults: { firstName: "John", lastName:

    "Doe" }, initialize: function() { //setup code }, isAnonymous: function() { //sorry to all the real John Does out there return (this.get("firstName")==="John"&&this.get("lastName")==="Doe") } }); Wednesday, December 19, 12
  23. Backbone.Model var User = Backbone.Model.extend({ defaults: { firstName: "John", lastName:

    "Doe" }, initialize: function() { //setup code }, isAnonymous: function() { //sorry to all the real John Does out there return (this.get("firstName")==="John"&&this.get("lastName")==="Doe") } }); var user = new User({ firstName: "Nicholas", lastName: "Spencer" }); Wednesday, December 19, 12
  24. Backbone.Model var User = Backbone.Model.extend({ defaults: { firstName: "John", lastName:

    "Doe" }, initialize: function() { //setup code }, isAnonymous: function() { //sorry to all the real John Does out there return (this.get("firstName")==="John"&&this.get("lastName")==="Doe") } }); var user = new User({ firstName: "Nicholas", lastName: "Spencer" }); user.isAnonymous(); // false Wednesday, December 19, 12
  25. var Blarg = Backbone.Model.extend( //instance {yack:function(){return "yack";}}, //class {yick:function(){return "yick";}}

    ); Blarg.yick(); //”yick” var blarg = new Blarg; blarg.yack(); //”yack” Backbone’s extend pattern • Takes two object literals • instance properties • class properties • Copies them onto the new • prototype chain • namespace Wednesday, December 19, 12 The object literal that you pass in gets copied to the prototype chain
  26. Backbone.Model • initialize called at the end of Backbone.Model’s constructor

    • new up instances with optional attributes and options arguments • Maintains an internal attributes object accessed through get() & set() • Handles data validation on set() • Triggers change events Wednesday, December 19, 12
  27. Backbone.Collection var Coach = Backbone.Model.extend({ /* ... */ title: function()

    { return "Coach " + this.get("firstName") + " " + this.get("lastName"); } }); Wednesday, December 19, 12
  28. Backbone.Collection var Coach = Backbone.Model.extend({ /* ... */ title: function()

    { return "Coach " + this.get("firstName") + " " + this.get("lastName"); } }); var Coaches = Backbone.Collection.extend({ model: Coach, comparator: function(coachA,coachB) { var a = coachA.get("totalWins"); var b = coachB.get("totalWins"); return a > b ? -1 : a < b ? 1 : 0; }, firstPlace: function() { return this.at(0); } }); Wednesday, December 19, 12
  29. Backbone.Collection var Coach = Backbone.Model.extend({ /* ... */ title: function()

    { return "Coach " + this.get("firstName") + " " + this.get("lastName"); } }); var Coaches = Backbone.Collection.extend({ model: Coach, comparator: function(coachA,coachB) { var a = coachA.get("totalWins"); var b = coachB.get("totalWins"); return a > b ? -1 : a < b ? 1 : 0; }, firstPlace: function() { return this.at(0); } }); var coaches = new Coaches([ { firstName: "Nicholas", lastName: "Spencer", totalWins: 45 }, { firstName: "Quincy", lastName: "Magoo", totalWins: 30 }, { firstName: "John", lastName: "McGuirk", totalWins: 73 } ]); Wednesday, December 19, 12
  30. Backbone.Collection var Coach = Backbone.Model.extend({ /* ... */ title: function()

    { return "Coach " + this.get("firstName") + " " + this.get("lastName"); } }); var Coaches = Backbone.Collection.extend({ model: Coach, comparator: function(coachA,coachB) { var a = coachA.get("totalWins"); var b = coachB.get("totalWins"); return a > b ? -1 : a < b ? 1 : 0; }, firstPlace: function() { return this.at(0); } }); var coaches = new Coaches([ { firstName: "Nicholas", lastName: "Spencer", totalWins: 45 }, { firstName: "Quincy", lastName: "Magoo", totalWins: 30 }, { firstName: "John", lastName: "McGuirk", totalWins: 73 } ]); coaches.firstPlace().title(); // "Coach John McGuirk" Wednesday, December 19, 12
  31. Backbone.Collection • initialize called at the end of Backbone.Collection’s constructor

    • new up instances with optional models and options arguments • Maintains an internal models array accessed through add(), remove(), get(), at(), push(), pop() and many more including collection functions from underscore • Triggers change events after validation from the individual models Wednesday, December 19, 12
  32. Backbone.Router var Router = Backbone.Router.extend({ routes: { "":"login", "messages":"dashboard", "messages/:folder":"messages",

    "message/:id":"message" }, login: function() { /* init view */ }, dashboard: function() { /* init view with model */ }, messages: function(folder) { /* init view with model */ }, message: function(id) { /* init view with model */ }, }) Backbone.history.start(); //don’t forget! Wednesday, December 19, 12
  33. Backbone.Router • Uses the HTML5 History API pushState to handle

    URL changes • Falls back to hashChange events • Falls back to hash polling • String or Regex pattern matching Wednesday, December 19, 12
  34. Backbone.Router • pushState requires server configuration • Backbone.history.start() //don’t forget!

    • Your single-page app is now bookmarkable and shareable! • Triggers events as the routes change Wednesday, December 19, 12 Views are next
  35. Backbone.View • extends can be used to setup the DOM

    node • id - HTML node id • className - CSS class name • tagName - HTML node name (div by default) Wednesday, December 19, 12 initialization on next slide
  36. Backbone.View • initialize consumes a number of options • id

    - HTML node id • className - CSS class name • attribuets - HTML node attributes • tagName - HTML node name • el - Existing HTML node • model - Backbone model instance • collection - Backbone collection instance • cached on view.options Wednesday, December 19, 12
  37. Backbone.View var ListView = Backbone.View.extend({ tagName: "ul", initialize: function() {

    this.listenTo(this.collection,"add remove sort",this.render); }, render: function() { this.$el.html(); this.collection.each(_renderItem,this); return this; }, _renderItem: function(item) { var itemView = new ItemView({ model: item }); this.listenTo(itemView, "selected", this.setSelected) this.$el.append(itemView.render().$el.html()); }, setSelected: function(item) { item.set({"selected":true}); this.collection.save(); } }); Wednesday, December 19, 12
  38. Backbone.View var ListView = Backbone.View.extend({ tagName: "ul", initialize: function() {

    this.listenTo(this.collection,"add remove sort",this.render); }, render: function() { this.$el.html(); this.collection.each(_renderItem,this); return this; }, _renderItem: function(item) { var itemView = new ItemView({ model: item }); this.listenTo(itemView, "selected", this.setSelected) this.$el.append(itemView.render().$el.html()); }, setSelected: function(item) { item.set({"selected":true}); this.collection.save(); } }); var ItemView = Backbone.View.extend({ tagName: "li", initialize: function() { this.listenTo(this.model,"change",this.render) }, render: function() { this.$el.html(model.get("fullName")); return this; }, events: { "click": "select" }, select: function(e) { this.trigger("selected", this.model); } }); Wednesday, December 19, 12
  39. Backbone.View • view.$el - Cached $(view.el) • view.$() - Shorthand

    for view.$el.find() • events - DOM events are delegated to view.$el • {“load img”:”imageLoaded”} • view.el = foo; • use view.setElement() instead Wednesday, December 19, 12
  40. Backbone.View • view.render() • a non-op function by default •

    used to build up view.el’s DOM • templates • direct DOM manipulation • little bit of both • return this; • Be mindful about event delegation Wednesday, December 19, 12
  41. Backbone.View • DOM shouldn’t hold state • data- attributes are

    cool! • Good views present state • Model holds state • view.isOpen() • driven by the model; not jQuery • Unit testing • mock models vs mock DOM Wednesday, December 19, 12
  42. Backbone.Events • Models, Collections, Routers and Views copy the Backbone.Events

    API to their prototype chains • Backbone does as well for a central eventing bus • Events can be namespaced • “change:firstName” Wednesday, December 19, 12
  43. Backbone.Events var User = Backbone.Model.extend({ defaults: { firstName: "John", lastName:

    "Doe" }, initialize: function() { //setup code this.on("change:firstName change:lastName", this._setFirstName, this); this._setFirstName(); }, _setFirstName: function() { this.set("fullName",this.get("firstName") + " " + this.get("lastName")); } }); Wednesday, December 19, 12
  44. Backbone.Events var User = Backbone.Model.extend({ defaults: { firstName: "John", lastName:

    "Doe" }, initialize: function() { //setup code this.on("change:firstName change:lastName", this._setFirstName, this); this._setFirstName(); }, _setFirstName: function() { this.set("fullName",this.get("firstName") + " " + this.get("lastName")); } }); var user = new User({ firstName: "Nicholas", lastName: "Spencer" }); user.get("fullName"); //Nicholas Spencer Wednesday, December 19, 12
  45. Backbone.Events var User = Backbone.Model.extend({ defaults: { firstName: "John", lastName:

    "Doe" }, initialize: function() { //setup code this.on("change:firstName change:lastName", this._setFirstName, this); this._setFirstName(); }, _setFirstName: function() { this.set("fullName",this.get("firstName") + " " + this.get("lastName")); } }); var user = new User({ firstName: "Nicholas", lastName: "Spencer" }); user.get("fullName"); //Nicholas Spencer user.set({"firstName": "Nico"}); user.get("fullName"); //Nico Spencer Wednesday, December 19, 12
  46. Backbone.sync • Out of the box function to handle CRUD

    operations with a RESTful service • .save([attributes],[options]) - PUT, POST the model • .fetch([options]) - GET the model • .destroy([options]) - DELETE the model • .create(attributes, [options]) - POST to the collection Wednesday, December 19, 12
  47. Backbone.sync • Define the URLs • collection.url() - modifies model.url()

    • model.urlRoot() - modifies model.url() • model.url() • “/[collection.url]/[model.id]” • “/[model.urlRoot]/[model.id]” • extended custom function or string Wednesday, December 19, 12
  48. Backbone.sync • Customize the payload • model.parse() • model.toJSON() •

    collection.parse() • collection.toJSON() Wednesday, December 19, 12
  49. Backbone.sync • Customize the behavior • Override Backbone.sync() • model.sync()

    • collection.sync() • Backbone.ajax() • LocalStorage, WebSocket adapters Wednesday, December 19, 12
  50. So now what? Wednesday, December 19, 12 look at others

    knockout.js ember.js batman.js angular.js todosjs
  51. So now what? • Dive in! (read that annotated source)

    • Make it your own • Try overloading constructors Wednesday, December 19, 12
  52. So now what? • Other frameworks • Knockout • Ember.js

    • Angular.js • Batman.js • http://addyosmani.github.com/ todomvc/ Wednesday, December 19, 12