SlideShare a Scribd company logo
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({});
Javascript MVC & Backbone Tips & Tricks
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/
Javascript MVC & Backbone Tips & Tricks
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
Javascript MVC & Backbone Tips & Tricks
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

More Related Content

What's hot (20)

PDF
jQuery: Events, Animation, Ajax
Constantin Titarenko
 
PDF
jQuery Loves Developers - Oredev 2009
Remy Sharp
 
PDF
jQuery for beginners
Siva Arunachalam
 
PDF
Angular JS blog tutorial
Claude Tech
 
PPTX
jQuery Presentation
Rod Johnson
 
PPTX
Getting the Most Out of jQuery Widgets
velveeta_512
 
PDF
jQuery in the [Aol.] Enterprise
Dave Artz
 
PDF
jQuery Essentials
Bedis ElAchèche
 
PDF
Sane Async Patterns
TrevorBurnham
 
PPTX
Jquery Complete Presentation along with Javascript Basics
EPAM Systems
 
PDF
AnkaraJUG Kasım 2012 - PrimeFaces
Ankara JUG
 
KEY
The go-start webframework (GTUG Vienna 27.03.2012)
ungerik
 
PDF
Jquery In Rails
shen liu
 
PDF
AngularJS vs. Ember.js vs. Backbone.js
Mark
 
PDF
BPM-2 Introduction to Advanced Workflows
Alfresco Software
 
PPTX
Unobtrusive javascript with jQuery
Angel Ruiz
 
PDF
Stack Overflow Austin - jQuery for Developers
Jonathan Sharp
 
PPTX
SharePoint and jQuery Essentials
Mark Rackley
 
PDF
BPM-1 Introduction to Advanced Workflows
Alfresco Software
 
PDF
BPM-3 Advanced Workflow Deep Dive
Alfresco Software
 
jQuery: Events, Animation, Ajax
Constantin Titarenko
 
jQuery Loves Developers - Oredev 2009
Remy Sharp
 
jQuery for beginners
Siva Arunachalam
 
Angular JS blog tutorial
Claude Tech
 
jQuery Presentation
Rod Johnson
 
Getting the Most Out of jQuery Widgets
velveeta_512
 
jQuery in the [Aol.] Enterprise
Dave Artz
 
jQuery Essentials
Bedis ElAchèche
 
Sane Async Patterns
TrevorBurnham
 
Jquery Complete Presentation along with Javascript Basics
EPAM Systems
 
AnkaraJUG Kasım 2012 - PrimeFaces
Ankara JUG
 
The go-start webframework (GTUG Vienna 27.03.2012)
ungerik
 
Jquery In Rails
shen liu
 
AngularJS vs. Ember.js vs. Backbone.js
Mark
 
BPM-2 Introduction to Advanced Workflows
Alfresco Software
 
Unobtrusive javascript with jQuery
Angel Ruiz
 
Stack Overflow Austin - jQuery for Developers
Jonathan Sharp
 
SharePoint and jQuery Essentials
Mark Rackley
 
BPM-1 Introduction to Advanced Workflows
Alfresco Software
 
BPM-3 Advanced Workflow Deep Dive
Alfresco Software
 

Similar to Javascript MVC & Backbone Tips & Tricks (20)

PDF
Viking academy backbone.js
Bert Wijnants
 
PDF
Understanding backbonejs
Nick Lee
 
PPT
Backbone.js
Knoldus Inc.
 
ODP
Javascript frameworks: Backbone.js
Soós Gábor
 
PDF
Backbone.js — Introduction to client-side JavaScript MVC
pootsbook
 
PDF
Introduction to Backbone.js for Rails developers
AoteaStudios
 
PPT
Backbone js
Knoldus Inc.
 
PPTX
Backbone the Good Parts
Renan Carvalho
 
PPTX
Backbone.js
VO Tho
 
PDF
Backbone.js
Ivano Malavolta
 
PDF
MVC on the Server and on the Client: How to Integrate Spring MVC and Backbone...
jaxconf
 
PDF
Backbone js
Rohan Chandane
 
PDF
Javascript Application Architecture with Backbone.JS
Min Ming Lo
 
PPTX
Planbox Backbone MVC
Acquisio
 
KEY
MVC on the server and on the client
Sebastiano Armeli
 
PPTX
Taming that client side mess with Backbone.js
Jarod Ferguson
 
PDF
Introduction to Backbone.js
Jonathan Weiss
 
PDF
Backbone js in action
Usha Guduri
 
PPTX
BackboneJS Training - Giving Backbone to your applications
Joseph Khan
 
PDF
Developing maintainable Cordova applications
Ivano Malavolta
 
Viking academy backbone.js
Bert Wijnants
 
Understanding backbonejs
Nick Lee
 
Backbone.js
Knoldus Inc.
 
Javascript frameworks: Backbone.js
Soós Gábor
 
Backbone.js — Introduction to client-side JavaScript MVC
pootsbook
 
Introduction to Backbone.js for Rails developers
AoteaStudios
 
Backbone js
Knoldus Inc.
 
Backbone the Good Parts
Renan Carvalho
 
Backbone.js
VO Tho
 
Backbone.js
Ivano Malavolta
 
MVC on the Server and on the Client: How to Integrate Spring MVC and Backbone...
jaxconf
 
Backbone js
Rohan Chandane
 
Javascript Application Architecture with Backbone.JS
Min Ming Lo
 
Planbox Backbone MVC
Acquisio
 
MVC on the server and on the client
Sebastiano Armeli
 
Taming that client side mess with Backbone.js
Jarod Ferguson
 
Introduction to Backbone.js
Jonathan Weiss
 
Backbone js in action
Usha Guduri
 
BackboneJS Training - Giving Backbone to your applications
Joseph Khan
 
Developing maintainable Cordova applications
Ivano Malavolta
 
Ad

Recently uploaded (20)

PDF
The 2025 InfraRed Report - Redpoint Ventures
Razin Mustafiz
 
DOCX
Python coding for beginners !! Start now!#
Rajni Bhardwaj Grover
 
PDF
Transforming Utility Networks: Large-scale Data Migrations with FME
Safe Software
 
PDF
Agentic AI lifecycle for Enterprise Hyper-Automation
Debmalya Biswas
 
PPTX
Agentforce World Tour Toronto '25 - MCP with MuleSoft
Alexandra N. Martinez
 
PDF
Newgen 2022-Forrester Newgen TEI_13 05 2022-The-Total-Economic-Impact-Newgen-...
darshakparmar
 
PDF
Staying Human in a Machine- Accelerated World
Catalin Jora
 
DOCX
Cryptography Quiz: test your knowledge of this important security concept.
Rajni Bhardwaj Grover
 
PDF
“NPU IP Hardware Shaped Through Software and Use-case Analysis,” a Presentati...
Edge AI and Vision Alliance
 
PDF
Bitcoin for Millennials podcast with Bram, Power Laws of Bitcoin
Stephen Perrenod
 
PDF
Peak of Data & AI Encore AI-Enhanced Workflows for the Real World
Safe Software
 
PDF
LOOPS in C Programming Language - Technology
RishabhDwivedi43
 
PDF
Future-Proof or Fall Behind? 10 Tech Trends You Can’t Afford to Ignore in 2025
DIGITALCONFEX
 
PDF
“Voice Interfaces on a Budget: Building Real-time Speech Recognition on Low-c...
Edge AI and Vision Alliance
 
PPTX
From Sci-Fi to Reality: Exploring AI Evolution
Svetlana Meissner
 
PPTX
Q2 FY26 Tableau User Group Leader Quarterly Call
lward7
 
PDF
Transcript: Book industry state of the nation 2025 - Tech Forum 2025
BookNet Canada
 
PPTX
AI Penetration Testing Essentials: A Cybersecurity Guide for 2025
defencerabbit Team
 
PDF
Automating Feature Enrichment and Station Creation in Natural Gas Utility Net...
Safe Software
 
PDF
The Rise of AI and IoT in Mobile App Tech.pdf
IMG Global Infotech
 
The 2025 InfraRed Report - Redpoint Ventures
Razin Mustafiz
 
Python coding for beginners !! Start now!#
Rajni Bhardwaj Grover
 
Transforming Utility Networks: Large-scale Data Migrations with FME
Safe Software
 
Agentic AI lifecycle for Enterprise Hyper-Automation
Debmalya Biswas
 
Agentforce World Tour Toronto '25 - MCP with MuleSoft
Alexandra N. Martinez
 
Newgen 2022-Forrester Newgen TEI_13 05 2022-The-Total-Economic-Impact-Newgen-...
darshakparmar
 
Staying Human in a Machine- Accelerated World
Catalin Jora
 
Cryptography Quiz: test your knowledge of this important security concept.
Rajni Bhardwaj Grover
 
“NPU IP Hardware Shaped Through Software and Use-case Analysis,” a Presentati...
Edge AI and Vision Alliance
 
Bitcoin for Millennials podcast with Bram, Power Laws of Bitcoin
Stephen Perrenod
 
Peak of Data & AI Encore AI-Enhanced Workflows for the Real World
Safe Software
 
LOOPS in C Programming Language - Technology
RishabhDwivedi43
 
Future-Proof or Fall Behind? 10 Tech Trends You Can’t Afford to Ignore in 2025
DIGITALCONFEX
 
“Voice Interfaces on a Budget: Building Real-time Speech Recognition on Low-c...
Edge AI and Vision Alliance
 
From Sci-Fi to Reality: Exploring AI Evolution
Svetlana Meissner
 
Q2 FY26 Tableau User Group Leader Quarterly Call
lward7
 
Transcript: Book industry state of the nation 2025 - Tech Forum 2025
BookNet Canada
 
AI Penetration Testing Essentials: A Cybersecurity Guide for 2025
defencerabbit Team
 
Automating Feature Enrichment and Station Creation in Natural Gas Utility Net...
Safe Software
 
The Rise of AI and IoT in Mobile App Tech.pdf
IMG Global Infotech
 
Ad

Javascript MVC & Backbone Tips & Tricks

  • 1. MVC Frameworks in Javascript Hjörtur Hilmarsson @hjortureh
  • 2. Agenda • Why MVC in Javascript ? • Backbone & Spine • Backbone fundamentals • Backbone Tips & Tricks
  • 4. “The world web is changed”
  • 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 - 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(); } }); });
  • 10. 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"); ! ! } ! ); });
  • 11. 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 ! }); };
  • 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 var Enquiry = 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 out of the box • Nested models • Complex ajax requests • Understanding the limitations • Its still hard
  • 18. 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
  • 21. • Created 2010 by Jeremy Ashkenas • File size 5.4k • Depends on Underscore.js ( 4k ) • Very popular
  • 25. Spine • Inspired by Backbone • Written in CoffeeScript by Alex McCaw • File size 7k • Introduced async UI concept
  • 28. Modules • Events • Models • Collections • Views • Routes • History
  • 30. Events • Consists of on, off & trigger methods • All Backbone modules can trigger events • All Javascript object can be extended with the Backbone events module
  • 31. 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 ); });
  • 33. 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
  • 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
  • 37. Collections • List of models • 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. Views
  • 40. 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
  • 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); ! } ! ... }
  • 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! • Its stateful ! • 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 Start listening for hash-change events // Start the history Backbone.history.start(); Use html5 history API // Start the history Backbone.history.start({pushState: true});
  • 50. Demo
  • 51. Backbone tips & tricks
  • 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
  • 54. Bootstrapping data • Using fetch 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. Demo
  • 58. Tip #2 Async User Interfaces
  • 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.
  • 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. Demo
  • 64. Question Has many Answers
  • 65. 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
  • 66. 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() } ); } });
  • 67. Tip #4 Custom ajax requests
  • 68. Custom ajax request • Sometimes RESTful methods are not enough • Example: Sorting tasks in to-do list
  • 70. 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 ! ! } ! }); ! }
  • 71. Tip #5 Send zombies to heaven
  • 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. Tip #6 Use the toolbox
  • 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 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))); }; });
  • 78. Testing • Recommend Jasmine for testing • Recommend Sinon to fake the server • jQuery-jasmine to test views • Use setDomLibrary method to fake jQuery
  • 79. 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(); });
  • 80. Demo
  • 82. CoffeeScript • Advanced programing language • Compiles to javascript • Same creator of Backbone and CoffeeScript • Integrates well with Backbone
  • 83. 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
  • 85. The basics • The basics still apply with MVC in place • Minimize ajax requests • Keep your views thin & models fat • Understanding Javascript is the key
  • 87. Bonus points • Read the documentation • Read the source code • Just do it !
  • 88. Tack så mycket Hjörtur Elvar Hilmarsson @hjortureh