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

Vertebrae: A Backbone Grabbag

Jake Trent
October 12, 2012

Vertebrae: A Backbone Grabbag

After a time in Backbone, these various bits may help you avoid pitfalls and make some nicer Backbone code.

Jake Trent

October 12, 2012
Tweet

More Decks by Jake Trent

Other Decks in Technology

Transcript

  1. MAKE VIEWS REUSABLE - Pass in the “el” attribute var

    MyView = Backbone.View.extend({ }); ! new MyView({ el: ‘#putHere’ }); var MyView = Backbone.View.extend({
 el: ‘#putHere’ }); new MyView();
  2. MAKE VIEWS REUSABLE - Create the container element dynamically var

    MyView = Backbone.View.extend({ tagName: ‘div’ }); ! $(‘#putHere’).html(new MyView({ el: ‘#putHere’ })); var MyView = Backbone.View.extend({
 el: ‘#putHere’ }); new MyView();
  3. ENSURE DOM ELEMENT EXISTS - If DOM is changing, use

    string ‘.selector’ instead of direct reference $(‘.selector’) var MyView = Backbone.View.extend({ }); ! new MyView({ el: ‘#putHere’ }); var MyView = Backbone.View.extend({ }); ! new MyView({ el: $(‘#putHere’) });
  4. ENSURE DOM ELEMENT EXISTS - Backbone will lazy evaluate the

    string ‘el’ // Ensure that the View has a DOM element to render into. // If `this.el` is a string, pass it through `$()`, take the first // matching element, and re-assign it to `el`. Otherwise, create // an element from the `id`, `className` and `tagName` proeprties. _ensureElement : function() { if (!this.el) { var attrs = this.attributes || {}; if (this.id) attrs.id = this.id; if (this.className) attrs['class'] = this.className; this.el = this.make(this.tagName, attrs); } else if (_.isString(this.el)) { this.el = $(this.el).get(0); } } (Backbone source)
  5. CLEANUP YOUR EVENTS - Manually remove events when closing views

    Backbone.View.prototype.close = function(){ this.remove(); // only for views that use tagName this.unbind(); if (this.onClose){ this.onClose(); } }
  6. CLEANUP YOUR EVENTS - Implement an ‘onClose’ function Backbone.View.extend({ onClose:

    function () { this.undelegateEvents(); // if not removing this.collection.off(); // for all event-bound state } });
  7. CLEANUP YOUR EVENTS - this.model.off() only removes .on() events Backbone.View.extend({

    initialize: function () { this.model.fetch({ success: render error: renderError }); } }); Backbone.View.extend({ initialize: function () { this.model.on(‘change’, this.render); this.model.on(‘error’, this.renderError); } });
  8. INHERITANCE - Classic OOP subclassing of your own classes var

    MajesticCreature = Backbone.Model.extend({}); ! var Liger = MajesticCreature.extend({});
  9. INHERITANCE - Extend properties (eg, add additional events) var Place

    = Backbone.View.extend({ events: { ‘ride .scooter’: ‘rideScooterThruPlace’ } }); ! var SecretPlace = Place.extend({ events: _.extend({ ‘fightFor .chips’: ‘beginChipBrawl’ }, Place.prototype.events) });
  10. INHERITANCE - Put different subtypes in a collection // If

    Liger and Nessie extend MajesticCreature ! var Creatures = Backbone.Collection.extend({ model: MajesticCreature, parse: function (res) { var self = this; _.each(res, function (creature) { switch (creature.type) { case ‘skilled-in-magic’: self.add(new Liger(creature)); case ‘underwater-ally’: self.add(new Nessie(creature)); } }); } });
  11. DEVELOP WITHOUT A BACKEND - Store test data.json files locally

    - At the path of the eventual backend service var RipOff = Backbone.Model.extend({ url: ‘/that/place/’ }); ! var theDojoExperience = new RipOff(); theDojoExperience.fetch();
  12. BIND IN INITIALIZE - Guaranteed to run once per object

    var MyView = Backbone.View.extend({ initialize: function () { this.model.on(‘change’, ‘goAndDo’); },
 render: function () { // multiple-run safe } }); var huckabee = new MyView(); huckabee.render(); //ok Huckabee.render(); //ok, just monotonous var MyView = Backbone.View.extend({
 render: function () { this.model.on(‘change’, ‘goAndDo’); } }); var huckabee = new MyView(); huckabee.render(); //ok Huckabee.render(); //bad – goAndDo x 2
  13. SCOPE EVENTS WITH SUBVIEWS - Easily get to model in

    question var ListView = Backbone.View.extend({ // handles fetching collection render: function () { // instantiate model-level views // they ea. render, append to DOM } }); var ModelInListView = Backbone.View.extend({ events: { ‘click .edit’: ‘editModel’ }, editModel: function() { this.model.doEdityThings(); } }); var ListView = Backbone.View.extend({ // handles fetching collection events: { ‘click .model .edit’: ‘editModel’ } render: function () { // render each model in collection in this el }, editModel: function (evt) { var indx = this.$(‘.model).index($ (evt.currentTarget)); this.collection.at(indx).doEdityThings(); } });
  14. COMMUNICATE BETWEEN OBJECTS - Commit to not using globals -

    Unless it’s really global; and then, don’t clutter
  15. COMMUNICATE BETWEEN OBJECTS - Pass reference var QuestionView = Backbone.View.extend({

    initialize: function () { this.answerSheet = this.options.answerSheet; } answerQuestion: function () { var answer = determineAnswer(); this.answerSheet.recordAnswer(answer); } }); ! var AnswerSheet = Backbone.Model.extend({}); ! var myAnswers = new AnswerSheet(); var newQuestion = new Question({ answerSheet: myAnswers });
  16. COMMUNICATE BETWEEN OBJECTS - Talk over global Backbone.Events var QuestionView

    = Backbone.View.extend({ initialize: function () { this.answerSheet = this.options.answerSheet; } answerQuestion: function () { var answer = determineAnswer(); Backbone.Events.trigger(‘answered’, answer); } }); ! var AnswerSheet = Backbone.Model.extend({ initialize: function () { Backbone.Events.on(‘answered’, this.recordAnswer); } }); ! var myAnswers = new AnswerSheet(); var newQuestion = new Question();
  17. MODEL NEWNESS - Model has isNew() function - Not magic:

    isNew: function() { return this.id == null; }
  18. MODEL NEWNESS - Controls HTTP method when calling model.save() -

    isNew(): HTTP POST /model/url/ HTTP PUT /model/url/id - !isNew():
  19. REMEMBER WHO YOU ARE - Manage the fact that ‘this’

    changes - Pass context in event binding Backbone.View.extend({ initialize: function () { this.model.on(‘change’, this.render, this); } render: function () { this.model.getStuffAndMakeHtml() ; // #ftw } }); Backbone.View.extend({ initialize: function () { this.model.on(‘change’, this.render); } render: function () { this.model.getStuffAndMakeHtml() ; // #fail } });
  20. REMEMBER WHO YOU ARE - Capture the context once for

    all your functions Backbone.View.extend({ initialize: function () { _.bindAll(this); this.model.on(‘change’, this.render); } render: function () { this.model.getStuffAndMakeHtml(); // #ftw } });
  21. ORGANIZE YOUR FILES - Use an AMD loader, like RequireJS

    define(function () { return Backbone.Model.extend({}); }); define([‘Luchador’], function (Luchador) { return Backbone.Collection.extend({ model: Luchador }); }); Luchador.js Luchadors.js
  22. MINIMIZE DOM OPERATIONS - They’re expensive. Use a disconnected DOM

    fragment Backbone.View.extend({ render: function () { this.$el.html(‘’); var frag = document.createDocumentFragment(); _(this.collection.models_.each(function (model) { frag.appendChild(createHtml(model)); }); this.$el.html(frag); } }); Backbone.View.extend({ render: function () { var self = this; _(this.collection.models_.each(function (model) { self.$el.append(createHtml(model)); }); } });
  23. BIND IN INITALIZE - This is the verbage - Everyone

    can read this, right? vertebrae = -> alert “super awesome”
  24. jaketrent.com @jaketrent Henry Gray http://en.wikipedia.org/wiki/File:Gray94.png http://www.bartleby.com/107/illus111.html Public Domain ! WebTreats,

    etc. http://webtreatsetc.deviantart.com/art/9-Blue-Striped-Patterns-131303428 Free for personal/commercial ! Jeremy Ashkenas https://github.com/documentcloud/backbone/blob/master/LICENSE MIT license ! Dave Geddes, Jason LuBean, and Matt Werny For their topic suggestions