KEMBAR78
Javascript MVC & Backbone Tips & Tricks | PDF
MVC Frameworks
 in Javascript
   Hjörtur Hilmarsson
       @hjortureh
Agenda


• Why MVC in Javascript ?
• Backbone & Spine
• Backbone fundamentals
• Backbone Tips & Tricks
Why MVC ?
“The world web
  is changed”
Evolution of web apps
Help!
Contact Us
Markup

<form>

!   <!-- Name input -->
!   <input id="name" name="name" type="text" placeholder="What is your name?" required />

!   <!-- Email input -->
!   <input id="email" name="email" type="email" placeholder="What is your email?" required />

!   <!-- Message input -->
!   <textarea id="message" name="message" placeholder="Hello!" required ></textarea>

!   <!--Send button -->
!   <input id="submit" name="submit" type="submit" value="Send" />

!   <!-- Message label -->
!   <span id="message" ></span>

</form>
Javascript - Old style

$("form").submit(function( e ) {
!   !    !   !
!   e.preventDefault();

!     // get values
      var $form = $(this);
      var data = {
          name: $form.find("[name=name]").val(),
          email: $form.find("[name=email]").val(),
          message: $form.find("[name=message]").val()
      };

      // ajax request
      $.ajax({
          type: "post",
          url: "/enquiry",
          contentType: "application/json",
          dataType: "json",
          data: data,
          success: function() {
               $form.find("#message").text("Message posted").fadeIn();
          },
          error: function() {
               $form.find("#message").text("Sorry, there was an error").fadeIn();
          }
      });
});
Controller - MVC style

$("form").submit(function( e ) {
!   !    !   !
!   e.preventDefault();

!     // get values
!     var $form = $(this);
!     var data = {
!     !   name: $form.find("[name=name]").val(),
!     !   email: $form.find("[name=email]").val(),
!     !   message: $form.find("[name=message]").val()
!     };

!     // model
!     var enquiry = new Enquiry( data );
!
!     enquiry.save(
!     !   function() {
!     !   !    $form.find("#message").text("Message posted");
!     !   },
!     !   function() {
!     !   !    $form.find("#message").text("Sorry, there was an error");
!     !   }
!     );
});
Model - MVC style

// constructor
var Enquiry = function( data ) {
!   this.data = data;
};

// save method
Enquiry.prototype.save = function( success, error ) {

!    // ajax request
!    $.ajax({
!    !   type: "post",
!    !   url: "/enquiry",
!    !   contentType: "application/json",
!    !   dataType: "json",
!    !   data: this.data,
!    !   success: success,
!    !   error: error
!    });

};
Backbone.js controller view
var   ContactUs = Backbone.View.extend({
!
!     // local variables
!     el: $("form").get(0),
!     events: { "submit": "submit" }
!     model: new Enquiry,

!     // constructor
!     initialize: function() {
!     !   this.model.bind("create", create, this );!
!     !   this.model.bind("error", error, this );!
!     },

!     // submit event
!     submit: function( e ) {
!     !   e.preventDefault();
!     !
!     !   var data = {
!     !   !    name: this.$("[name=name]").val(),
!     !   !    email: this.$("[name=email]").val(),
!     !   !    message: this.$("[name=message]").val()
!     !   };

!     !    this.model.save();
!     },

!     // success callback
!     create: function() {
!     !   this.$("#message").text("Message posted");
!     },

!     // error callback
!     error: function() {
!     !   this.$("#message").text("Sorry, there was an error");
!     }

});
Backbone.js model

 var Enquiry = Backbone.Model.extend({});
MVC Benefits

Structure
Classes, inheritance, common patterns.

Modular
Communication via events, lousily coupled & testable components.

Common services
Back and forward history, clients-side url resources, utilities.

Persistence layers
RESTful sync, local storage, web sockets and more.

Community
Patterns,  mixins, conferences and more.
Challenges


• Going out of the box
• Nested models
• Complex ajax requests
• Understanding the limitations
• Its still hard
Challenges




TodoMVC - http://addyosmani.github.com/todomvc/
To mvc, or not to mvc ?

Use for one page apps

Use for complex client-side UIs & crud


Use not only for UI sugar

Use not for just rendering HTML

Use not for inflexible backends
Web Apps
Backbone & Spine
• Created 2010 by Jeremy Ashkenas
• File size 5.4k
• Depends on Underscore.js ( 4k )
• Very popular
http://blog.fogcreek.com/the-trello-tech-stack/
https://engineering.linkedin.com/mobile/linkedin-ipad-using-local-storage-snappy-mobile-apps
Spine


• Inspired by Backbone
• Written in CoffeeScript by Alex McCaw
• File size 7k
• Introduced async UI concept
Text



http://hjortureh.tumblr.com/post/22117245794/spine-js-vs-backbone-js
Fundamentals
Modules

• Events
• Models
• Collections
• Views
• Routes
• History
Events
Events



• Consists of on, off & trigger methods
• All Backbone modules can trigger events
• All Javascript object can be extended with
  the Backbone events module
Event example


Event triggered inside User class when name is changed


  this.trigger("change:name", "Mr Hilmarsson");




Bind to a name change event

  user.on("change:name", function( name ) {
  !   alert( "Name changed to " + name );
  });
Models
Models


• Wrapper for JSON & syncing via JSON
• RESTful by default. Overwrite sync
  method to change persistence logic.
• Communicates via events ( create, change,
  destroy, sync, error, add , remove )
• Can handle validation
Model


var Todo = Backbone.Model.extend({

      defaults: {
         done: false
      },

      toggle: function() {
         this.save({done: !this.get("done")});
      },

      clear: function() {
        this.destroy();
      }

});
TodoMVC - example




   http://addyosmani.github.com/todomvc/architecture-examples/backbone/index.html
Collections
Collections


• List of models
• Fires events for collection and the models
• Keeps models sorted
• Includes many utility methods
Collection

var TodoList = Backbone.Collection.extend({

      model: Todo,

      done: function() {
         return this.filter(function(todo){ return todo.get('done'); });
      },

      remaining: function() {
         return this.without.apply(this, this.done() );
      },

      comparator: function(todo) {
        return todo.get('order');
      }

});
Views
Views

• Bridge the gap between the HTML and
  models
• DOM element ( this.el ) represents the
  context
• Uses jQuery / Zepto / ender for DOM
  manipulation
• Listens for UI events & model events
• Use render method to create view
Organizing views




       1:1
       View    Model
Todo view

var TodoView = Backbone.View.extend({

    tagName:     "li",

    template: _.template($('#item-template').html()),

    events: {
       "click .check"                : "toggleDone"
    },

    initialize: function() {
      _.bindAll(this, 'render' );

         this.model.bind('change', this.render );
    },

    render: function() {
       $(this.el).html(this.template(this.model.toJSON()));
       return this;
    },

    toggleDone: function() {
      this.model.toggle();
    }

    ...
}
Template



<script type="text/template" id="item-template">

  <div class="todo <%= done ? 'done' : '' %>">
    <div class="display">
      <input class="check" type="checkbox" <%= done ? 'checked="checked"' : '' %> />
      <label class="todo-content"><%= content %></label>
      <span class="todo-destroy"></span>
    </div>
    <div class="edit">
      <input class="todo-input" type="text" value="<%= content %>" />
    </div>
  </div>

</script>
App view

var AppView = Backbone.View.extend({

!   el: $("#todoapp"),

!   !     initialize: function() {
!   !     _.bindAll(this, 'addOne', 'addAll', 'render' );

!   !     Todos.on('add',     this.addOne);
!   !     Todos.on('reset',   this.addAll);

!   !     Todos.fetch();
!   },

!   addOne: function(todo) {
!   !   var view = new TodoView({model: todo});
!   !   this.$("#todo-list").append(view.render().el);
!   },

!   addAll: function() {
!   !   Todos.each(this.addOne);
!   }

!   ...

}
Router & History
Router & History


• Provides a way to map URL resources
• Enables client-side back & forward
  navigation
• Use Hash-change by default. Supports
  push state ( History API )  
Be Careful!



• Its stateful !
• Its not easy
• Don’t set navigate trigger to true
Router


APP.Router = Backbone.Router.extend({

  routes: {
     "new": "newNote",
     ":id": "editNote",
     "": "home"
  },

  home: function() {
     APP.appView.home();
  },

  newNote: function() {
     APP.appView.newNote();
  },

  editNote: function( id ) {
    APP.appView.editNote( id );
  }

});
History - example


Start listening for hash-change events

  // Start the history
  Backbone.history.start();




 Use html5 history API

  // Start the history
  Backbone.history.start({pushState: true});
Demo
Backbone tips & tricks
Tips & Tricks
•   Tip #1 - Bootstrapping data
•   Tip #2 - Async user interfaces
•   Tip #3 - Nested models
•   Tip #4 - Custom ajax requests
•   Tip #5 - Zombies to heaven
•   Tip #6 - The toolbox
•   Tip #7 - Test, test, test
•   Tip #8 - CoffeeScript
•   Tip #9 - Remember the basics
•   Tip #10 - Bonus points
Tip #1
Bootstrapping data
Bootstrapping data



• Using fetch extends waiting time
• Possible to bootstrap the most important
  data when the page is rendered
• No loading spinners !
Bootstrapping Data

The code

 // Current user
 APP.currentUser = new APP.Models.User(<%= @current_user.to_json.html_safe %>);

 // Notes
 APP.notes.reset(<%= @notes.to_json.html_safe %>);




After render
 // Current user
 APP.currentUser = new APP.Models.User({
   id: 1, username: "hjortureh",
   name: "Hjortur Hilmarsson",
   avatar: "avatar.gif"
 });

 // Notes
 APP.notes.reset([
   { id: 1, text: "Note 1" },
   { id: 1, text: "Note 2" },
   { id: 1, text: "Note 3" }
 ]);
Demo
Twitter demo
Tip #2
Async User Interfaces
Importance of speed
     Amazon 
     100 ms of extra load time caused a 1% drop in
     sales (source: Greg Linden, Amazon).


     Google
     500 ms of extra load time caused 20% fewer
     searches (source: Marrissa Mayer, Google).


     Yahoo! 
     400 ms of extra load time caused a 5–9%
     increase in the number of people who clicked
     “back” before the page even loaded (source:
     Nicole Sullivan, Yahoo!).


     37 Signals - Basecamp
     500 ms increase in speed on basecamp.com
     resulted in 5% improvement in conversion rate.
Importance of speed
Async user interfaces


• Models are optimistic by default
• UI is updated before server response
• Use cid as a unique identifier on the client
• No loading spinners !
Demo
Tip #3
Nested Models
Question

   Has many




Answers
Nested models


• Nested models are common
• No official way of doing it
• Overwrite parse after ajax request

• Overwrite toJSON before ajax request

• Backbone-relational mixin could help
Nested models
 var Question = Backbone.Model.extend({


   initialize: function() {

        // collection instance
        this.answers = new Answers;

   },


   parse: function(resp, xhr) {

        // fill nested model
        if( _.isArray( resp.answers ) ) {
            this.answers.reset( resp.answers );
        }

        return resp;

   },


   toJSON: function() {

        // send nested models
        return $.extend(
           this.attributes(), {
             answers: this.answers.toJSON()
           }
        );
   }

 });
Tip #4
Custom ajax requests
Custom ajax request



• Sometimes RESTful methods are not
  enough
• Example: Sorting tasks in to-do list
Sorting - Custom request



saveOrder: function() {
    !
!   var ids = this.pluck("id");
!
!   window.$.ajax({
!   !    url: "/tasks/reorder",
!   !    data: {
!   !    !   ids: ids
!   !    },
!   !    type: "POST",
!   !    dataType: "json",
!   !    complete: function() {
!   !    !   // Handle response
!   !    }
!   });
!
}
Tip #5
Send zombies to heaven
Zombies to heaven



• Its not enough to remove views from the
  DOM
• Events must be released so you don’t have
  zombies walking around
Zombies to heaven

// same as this.$el.remove();
this.remove();

// remove all models bindings
// made by this view
this.model.off( null, null, this );

// unbind events that are
// set on this view
this.off();
Tip #6
Use the toolbox
Use the toolbox


• Underscore has some wonderful methods
• isFunction, isObject, isString, isNumber,
  isDate & more.
• Underscore: http://
  documentcloud.github.com/underscore
Underscore
Line 865 from the Backbone.js code.


  // Underscore methods that we want to implement on the Collection.
  var methods = ['forEach', 'each', 'map', 'reduce', 'reduceRight', 'find',
      'detect', 'filter', 'select', 'reject', 'every', 'all', 'some', 'any',
      'include', 'contains', 'invoke', 'max', 'min', 'sortBy', 'sortedIndex',
      'toArray', 'size', 'first', 'initial', 'rest', 'last', 'without', 'indexOf',
      'shuffle', 'lastIndexOf', 'isEmpty', 'groupBy'];

  // Mix in each Underscore method as a proxy to `Collection#models`.
  _.each(methods, function(method) {
      Collection.prototype[method] = function() {
         return _[method].apply(_, [this.models].concat(_.toArray(arguments)));
      };
  });
Tip #7
Test, test, test
Testing


• Recommend Jasmine for testing

• Recommend Sinon to fake the server

• jQuery-jasmine to test views

• Use setDomLibrary method to fake jQuery
Jasmine with fake server & spy
 it('Should sync correctly', function () {

       // mockup data
       var note = new APP.Models.Note({ text: "Buy some eggs" });

       // fake server
       this.server = sinon.fakeServer.create();

       // fake response
       this.server.respondWith( "POST", "/notes",
          [ 200,
            {"Content-Type": "application/json"},
            '{ "id": 1, "text": "Remember the milk" }' ]
       );

       // spy on sync event
       var spy = sinon.spy();
       note.on("sync", spy );

       // save model
       note.save();

       // server repsonse
       this.server.respond();

       // assert
       expect( spy ).toHaveBeenCalledOnce();
       expect( spy ).toHaveBeenCalledWith( note );
       expect( note.get("text") ).toEqual( "Remember the milk" );

       // restore fake server
       this.server.restore();

 });
Demo
Tip #8
CoffeeScript
CoffeeScript


• Advanced programing language
• Compiles to javascript
• Same creator of Backbone and
  CoffeeScript
• Integrates well with Backbone
Coffee Script example
Extending Backbone module


  class TodoList extends Backbone.View




Double arrow to bind to the context
Use @ instead of this
Last line is the return value, returns this

 _.bindAll( this, 'render' )

 render: =>
   @$el.html( @template( @.model.toJSON() ))
   @




Need to call super on parent constructors

 initialize: ->
 !    super
Tip #9
The Basics
The basics


• The basics still apply with MVC in place
• Minimize ajax requests
• Keep your views thin & models fat
• Understanding Javascript is the key
Tip #10
 Bonus
Bonus points



• Read the documentation
• Read the source code
• Just do it !
Tack så mycket
  Hjörtur Elvar Hilmarsson
        @hjortureh

Javascript MVC & Backbone Tips & Tricks

  • 1.
    MVC Frameworks inJavascript Hjörtur Hilmarsson @hjortureh
  • 2.
    Agenda • Why MVCin Javascript ? • Backbone & Spine • Backbone fundamentals • Backbone Tips & Tricks
  • 3.
  • 4.
    “The world web is changed”
  • 5.
  • 6.
  • 7.
  • 8.
    Markup <form> ! <!-- Name input --> ! <input id="name" name="name" type="text" placeholder="What is your name?" required /> ! <!-- Email input --> ! <input id="email" name="email" type="email" placeholder="What is your email?" required /> ! <!-- Message input --> ! <textarea id="message" name="message" placeholder="Hello!" required ></textarea> ! <!--Send button --> ! <input id="submit" name="submit" type="submit" value="Send" /> ! <!-- Message label --> ! <span id="message" ></span> </form>
  • 9.
    Javascript - Oldstyle $("form").submit(function( e ) { ! ! ! ! ! e.preventDefault(); ! // get values var $form = $(this); var data = { name: $form.find("[name=name]").val(), email: $form.find("[name=email]").val(), message: $form.find("[name=message]").val() }; // ajax request $.ajax({ type: "post", url: "/enquiry", contentType: "application/json", dataType: "json", data: data, success: function() { $form.find("#message").text("Message posted").fadeIn(); }, error: function() { $form.find("#message").text("Sorry, there was an error").fadeIn(); } }); });
  • 10.
    Controller - MVCstyle $("form").submit(function( e ) { ! ! ! ! ! e.preventDefault(); ! // get values ! var $form = $(this); ! var data = { ! ! name: $form.find("[name=name]").val(), ! ! email: $form.find("[name=email]").val(), ! ! message: $form.find("[name=message]").val() ! }; ! // model ! var enquiry = new Enquiry( data ); ! ! enquiry.save( ! ! function() { ! ! ! $form.find("#message").text("Message posted"); ! ! }, ! ! function() { ! ! ! $form.find("#message").text("Sorry, there was an error"); ! ! } ! ); });
  • 11.
    Model - MVCstyle // constructor var Enquiry = function( data ) { ! this.data = data; }; // save method Enquiry.prototype.save = function( success, error ) { ! // ajax request ! $.ajax({ ! ! type: "post", ! ! url: "/enquiry", ! ! contentType: "application/json", ! ! dataType: "json", ! ! data: this.data, ! ! success: success, ! ! error: error ! }); };
  • 12.
    Backbone.js controller view var ContactUs = Backbone.View.extend({ ! ! // local variables ! el: $("form").get(0), ! events: { "submit": "submit" } ! model: new Enquiry, ! // constructor ! initialize: function() { ! ! this.model.bind("create", create, this );! ! ! this.model.bind("error", error, this );! ! }, ! // submit event ! submit: function( e ) { ! ! e.preventDefault(); ! ! ! ! var data = { ! ! ! name: this.$("[name=name]").val(), ! ! ! email: this.$("[name=email]").val(), ! ! ! message: this.$("[name=message]").val() ! ! }; ! ! this.model.save(); ! }, ! // success callback ! create: function() { ! ! this.$("#message").text("Message posted"); ! }, ! // error callback ! error: function() { ! ! this.$("#message").text("Sorry, there was an error"); ! } });
  • 13.
    Backbone.js model varEnquiry = Backbone.Model.extend({});
  • 15.
    MVC Benefits Structure Classes, inheritance,common patterns. Modular Communication via events, lousily coupled & testable components. Common services Back and forward history, clients-side url resources, utilities. Persistence layers RESTful sync, local storage, web sockets and more. Community Patterns,  mixins, conferences and more.
  • 16.
    Challenges • Going outof the box • Nested models • Complex ajax requests • Understanding the limitations • Its still hard
  • 17.
  • 18.
    To mvc, ornot to mvc ? Use for one page apps Use for complex client-side UIs & crud Use not only for UI sugar Use not for just rendering HTML Use not for inflexible backends
  • 19.
  • 20.
  • 21.
    • Created 2010by Jeremy Ashkenas • File size 5.4k • Depends on Underscore.js ( 4k ) • Very popular
  • 22.
  • 24.
  • 25.
    Spine • Inspired byBackbone • Written in CoffeeScript by Alex McCaw • File size 7k • Introduced async UI concept
  • 26.
  • 27.
  • 28.
    Modules • Events • Models •Collections • Views • Routes • History
  • 29.
  • 30.
    Events • Consists ofon, off & trigger methods • All Backbone modules can trigger events • All Javascript object can be extended with the Backbone events module
  • 31.
    Event example Event triggeredinside User class when name is changed this.trigger("change:name", "Mr Hilmarsson"); Bind to a name change event user.on("change:name", function( name ) { ! alert( "Name changed to " + name ); });
  • 32.
  • 33.
    Models • Wrapper forJSON & syncing via JSON • RESTful by default. Overwrite sync method to change persistence logic. • Communicates via events ( create, change, destroy, sync, error, add , remove ) • Can handle validation
  • 34.
    Model var Todo =Backbone.Model.extend({ defaults: { done: false }, toggle: function() { this.save({done: !this.get("done")}); }, clear: function() { this.destroy(); } });
  • 35.
    TodoMVC - example http://addyosmani.github.com/todomvc/architecture-examples/backbone/index.html
  • 36.
  • 37.
    Collections • List ofmodels • Fires events for collection and the models • Keeps models sorted • Includes many utility methods
  • 38.
    Collection var TodoList =Backbone.Collection.extend({ model: Todo, done: function() { return this.filter(function(todo){ return todo.get('done'); }); }, remaining: function() { return this.without.apply(this, this.done() ); }, comparator: function(todo) { return todo.get('order'); } });
  • 39.
  • 40.
    Views • Bridge thegap between the HTML and models • DOM element ( this.el ) represents the context • Uses jQuery / Zepto / ender for DOM manipulation • Listens for UI events & model events • Use render method to create view
  • 41.
    Organizing views 1:1 View Model
  • 42.
    Todo view var TodoView= Backbone.View.extend({ tagName: "li", template: _.template($('#item-template').html()), events: { "click .check" : "toggleDone" }, initialize: function() { _.bindAll(this, 'render' ); this.model.bind('change', this.render ); }, render: function() { $(this.el).html(this.template(this.model.toJSON())); return this; }, toggleDone: function() { this.model.toggle(); } ... }
  • 43.
    Template <script type="text/template" id="item-template"> <div class="todo <%= done ? 'done' : '' %>"> <div class="display"> <input class="check" type="checkbox" <%= done ? 'checked="checked"' : '' %> /> <label class="todo-content"><%= content %></label> <span class="todo-destroy"></span> </div> <div class="edit"> <input class="todo-input" type="text" value="<%= content %>" /> </div> </div> </script>
  • 44.
    App view var AppView= Backbone.View.extend({ ! el: $("#todoapp"), ! ! initialize: function() { ! ! _.bindAll(this, 'addOne', 'addAll', 'render' ); ! ! Todos.on('add', this.addOne); ! ! Todos.on('reset', this.addAll); ! ! Todos.fetch(); ! }, ! addOne: function(todo) { ! ! var view = new TodoView({model: todo}); ! ! this.$("#todo-list").append(view.render().el); ! }, ! addAll: function() { ! ! Todos.each(this.addOne); ! } ! ... }
  • 45.
  • 46.
    Router & History •Provides a way to map URL resources • Enables client-side back & forward navigation • Use Hash-change by default. Supports push state ( History API )  
  • 47.
    Be Careful! • Itsstateful ! • Its not easy • Don’t set navigate trigger to true
  • 48.
    Router APP.Router = Backbone.Router.extend({ routes: { "new": "newNote", ":id": "editNote", "": "home" }, home: function() { APP.appView.home(); }, newNote: function() { APP.appView.newNote(); }, editNote: function( id ) { APP.appView.editNote( id ); } });
  • 49.
    History - example Startlistening for hash-change events // Start the history Backbone.history.start(); Use html5 history API // Start the history Backbone.history.start({pushState: true});
  • 50.
  • 51.
  • 52.
    Tips & Tricks • Tip #1 - Bootstrapping data • Tip #2 - Async user interfaces • Tip #3 - Nested models • Tip #4 - Custom ajax requests • Tip #5 - Zombies to heaven • Tip #6 - The toolbox • Tip #7 - Test, test, test • Tip #8 - CoffeeScript • Tip #9 - Remember the basics • Tip #10 - Bonus points
  • 53.
  • 54.
    Bootstrapping data • Usingfetch extends waiting time • Possible to bootstrap the most important data when the page is rendered • No loading spinners !
  • 55.
    Bootstrapping Data The code // Current user APP.currentUser = new APP.Models.User(<%= @current_user.to_json.html_safe %>); // Notes APP.notes.reset(<%= @notes.to_json.html_safe %>); After render // Current user APP.currentUser = new APP.Models.User({ id: 1, username: "hjortureh", name: "Hjortur Hilmarsson", avatar: "avatar.gif" }); // Notes APP.notes.reset([ { id: 1, text: "Note 1" }, { id: 1, text: "Note 2" }, { id: 1, text: "Note 3" } ]);
  • 56.
  • 57.
  • 58.
  • 59.
    Importance of speed Amazon  100 ms of extra load time caused a 1% drop in sales (source: Greg Linden, Amazon). Google 500 ms of extra load time caused 20% fewer searches (source: Marrissa Mayer, Google). Yahoo!  400 ms of extra load time caused a 5–9% increase in the number of people who clicked “back” before the page even loaded (source: Nicole Sullivan, Yahoo!). 37 Signals - Basecamp 500 ms increase in speed on basecamp.com resulted in 5% improvement in conversion rate.
  • 60.
  • 61.
    Async user interfaces •Models are optimistic by default • UI is updated before server response • Use cid as a unique identifier on the client • No loading spinners !
  • 62.
  • 63.
  • 64.
    Question Has many Answers
  • 65.
    Nested models • Nestedmodels are common • No official way of doing it • Overwrite parse after ajax request • Overwrite toJSON before ajax request • Backbone-relational mixin could help
  • 66.
    Nested models varQuestion = Backbone.Model.extend({ initialize: function() { // collection instance this.answers = new Answers; }, parse: function(resp, xhr) { // fill nested model if( _.isArray( resp.answers ) ) { this.answers.reset( resp.answers ); } return resp; }, toJSON: function() { // send nested models return $.extend( this.attributes(), { answers: this.answers.toJSON() } ); } });
  • 67.
  • 68.
    Custom ajax request •Sometimes RESTful methods are not enough • Example: Sorting tasks in to-do list
  • 70.
    Sorting - Customrequest saveOrder: function() { ! ! var ids = this.pluck("id"); ! ! window.$.ajax({ ! ! url: "/tasks/reorder", ! ! data: { ! ! ! ids: ids ! ! }, ! ! type: "POST", ! ! dataType: "json", ! ! complete: function() { ! ! ! // Handle response ! ! } ! }); ! }
  • 71.
  • 72.
    Zombies to heaven •Its not enough to remove views from the DOM • Events must be released so you don’t have zombies walking around
  • 73.
    Zombies to heaven //same as this.$el.remove(); this.remove(); // remove all models bindings // made by this view this.model.off( null, null, this ); // unbind events that are // set on this view this.off();
  • 74.
  • 75.
    Use the toolbox •Underscore has some wonderful methods • isFunction, isObject, isString, isNumber, isDate & more. • Underscore: http:// documentcloud.github.com/underscore
  • 76.
    Underscore Line 865 fromthe Backbone.js code. // Underscore methods that we want to implement on the Collection. var methods = ['forEach', 'each', 'map', 'reduce', 'reduceRight', 'find', 'detect', 'filter', 'select', 'reject', 'every', 'all', 'some', 'any', 'include', 'contains', 'invoke', 'max', 'min', 'sortBy', 'sortedIndex', 'toArray', 'size', 'first', 'initial', 'rest', 'last', 'without', 'indexOf', 'shuffle', 'lastIndexOf', 'isEmpty', 'groupBy']; // Mix in each Underscore method as a proxy to `Collection#models`. _.each(methods, function(method) { Collection.prototype[method] = function() { return _[method].apply(_, [this.models].concat(_.toArray(arguments))); }; });
  • 77.
  • 78.
    Testing • Recommend Jasminefor testing • Recommend Sinon to fake the server • jQuery-jasmine to test views • Use setDomLibrary method to fake jQuery
  • 79.
    Jasmine with fakeserver & spy it('Should sync correctly', function () { // mockup data var note = new APP.Models.Note({ text: "Buy some eggs" }); // fake server this.server = sinon.fakeServer.create(); // fake response this.server.respondWith( "POST", "/notes", [ 200, {"Content-Type": "application/json"}, '{ "id": 1, "text": "Remember the milk" }' ] ); // spy on sync event var spy = sinon.spy(); note.on("sync", spy ); // save model note.save(); // server repsonse this.server.respond(); // assert expect( spy ).toHaveBeenCalledOnce(); expect( spy ).toHaveBeenCalledWith( note ); expect( note.get("text") ).toEqual( "Remember the milk" ); // restore fake server this.server.restore(); });
  • 80.
  • 81.
  • 82.
    CoffeeScript • Advanced programinglanguage • Compiles to javascript • Same creator of Backbone and CoffeeScript • Integrates well with Backbone
  • 83.
    Coffee Script example ExtendingBackbone module class TodoList extends Backbone.View Double arrow to bind to the context Use @ instead of this Last line is the return value, returns this _.bindAll( this, 'render' ) render: => @$el.html( @template( @.model.toJSON() )) @ Need to call super on parent constructors initialize: -> ! super
  • 84.
  • 85.
    The basics • Thebasics still apply with MVC in place • Minimize ajax requests • Keep your views thin & models fat • Understanding Javascript is the key
  • 86.
  • 87.
    Bonus points • Readthe documentation • Read the source code • Just do it !
  • 88.
    Tack så mycket Hjörtur Elvar Hilmarsson @hjortureh