0
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 ...
Javascript - Old style$("form").submit(function( e ) {!   !    !   !!   e.preventDefault();!     // get values      var $f...
Controller - MVC style$("form").submit(function( e ) {!   !    !   !!   e.preventDefault();!     // get values!     var $f...
Model - MVC style// constructorvar Enquiry = function( data ) {!   this.data = data;};// save methodEnquiry.prototype.save...
Backbone.js controller viewvar   ContactUs = Backbone.View.extend({!!     // local variables!     el: $("form").get(0),!  ...
Backbone.js model var Enquiry = Backbone.Model.extend({});
MVC BenefitsStructureClasses, inheritance, common patterns.ModularCommunication via events, lousily coupled & testable com...
Challenges• Going out of the box• Nested models• Complex ajax requests• Understanding the limitations• Its still hard
ChallengesTodoMVC - http://addyosmani.github.com/todomvc/
To mvc, or not to mvc ?Use for one page appsUse for complex client-side UIs & crudUse not only for UI sugarUse not for jus...
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
Texthttp://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 exten...
Event exampleEvent triggered inside User class when name is changed  this.trigger("change:name", "Mr Hilmarsson");Bind to ...
Models
Models• Wrapper for JSON & syncing via JSON• RESTful by default. Overwrite sync  method to change persistence logic.• Comm...
Modelvar Todo = Backbone.Model.extend({      defaults: {         done: false      },      toggle: function() {         thi...
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
Collectionvar TodoList = Backbone.Collection.extend({      model: Todo,      done: function() {         return this.filter...
Views
Views• Bridge the gap between the HTML and  models• DOM element ( this.el ) represents the  context• Uses jQuery / Zepto /...
Organizing views       1:1       View    Model
Todo viewvar TodoView = Backbone.View.extend({    tagName:     "li",    template: _.template($(#item-template).html()),   ...
Template<script type="text/template" id="item-template">  <div class="todo <%= done ? done :  %>">    <div class="display"...
App viewvar AppView = Backbone.View.extend({!   el: $("#todoapp"),!   !     initialize: function() {!   !     _.bindAll(th...
Router & History
Router & History• Provides a way to map URL resources• Enables client-side back & forward  navigation• Use Hash-change by ...
Be Careful!• Its stateful !• Its not easy• Don’t set navigate trigger to true
RouterAPP.Router = Backbone.Router.extend({  routes: {     "new": "newNote",     ":id": "editNote",     "": "home"  },  ho...
History - exampleStart listening for hash-change events  // Start the history  Backbone.history.start(); Use html5 history...
Demo
Backbone tips & tricks
Tips & Tricks•   Tip #1 - Bootstrapping data•   Tip #2 - Async user interfaces•   Tip #3 - Nested models•   Tip #4 - Custo...
Tip #1Bootstrapping data
Bootstrapping data• Using fetch extends waiting time• Possible to bootstrap the most important  data when the page is rend...
Bootstrapping DataThe code // Current user APP.currentUser = new APP.Models.User(<%= @current_user.to_json.html_safe %>); ...
Demo
Twitter demo
Tip #2Async User Interfaces
Importance of speed     Amazon      100 ms of extra load time caused a 1% drop in     sales (source: Greg Linden, Amazon)....
Importance of speed
Async user interfaces• Models are optimistic by default• UI is updated before server response• Use cid as a unique identif...
Demo
Tip #3Nested Models
Question   Has manyAnswers
Nested models• Nested models are common• No official way of doing it• Overwrite parse after ajax request• Overwrite toJSON...
Nested models var Question = Backbone.Model.extend({   initialize: function() {        // collection instance        this....
Tip #4Custom ajax requests
Custom ajax request• Sometimes RESTful methods are not  enough• Example: Sorting tasks in to-do list
Sorting - Custom requestsaveOrder: function() {    !!   var ids = this.pluck("id");!!   window.$.ajax({!   !    url: "/tas...
Tip #5Send 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 walkin...
Zombies to heaven// same as this.$el.remove();this.remove();// remove all models bindings// made by this viewthis.model.of...
Tip #6Use the toolbox
Use the toolbox• Underscore has some wonderful methods• isFunction, isObject, isString, isNumber,  isDate & more.• Undersc...
UnderscoreLine 865 from the Backbone.js code.  // Underscore methods that we want to implement on the Collection.  var met...
Tip #7Test, test, test
Testing• Recommend Jasmine for testing• Recommend Sinon to fake the server• jQuery-jasmine to test views• Use setDomLibrar...
Jasmine with fake server & spy it(Should sync correctly, function () {       // mockup data       var note = new APP.Model...
Demo
Tip #8CoffeeScript
CoffeeScript• Advanced programing language• Compiles to javascript• Same creator of Backbone and  CoffeeScript• Integrates...
Coffee Script exampleExtending Backbone module  class TodoList extends Backbone.ViewDouble arrow to bind to the contextUse...
Tip #9The Basics
The basics• The basics still apply with MVC in place• Minimize ajax requests• Keep your views thin & models fat• Understan...
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
Javascript MVC & Backbone Tips & Tricks
Javascript MVC & Backbone Tips & Tricks
Upcoming SlideShare
Loading in...5
×

Javascript MVC & Backbone Tips & Tricks

6,034

Published on

Published in: Technology
0 Comments
7 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
6,034
On Slideshare
0
From Embeds
0
Number of Embeds
0
Actions
Shares
0
Downloads
122
Comments
0
Likes
7
Embeds 0
No embeds

No notes for slide

Transcript of "Javascript MVC & Backbone Tips & Tricks"

  1. 1. MVC Frameworks in Javascript Hjörtur Hilmarsson @hjortureh
  2. 2. Agenda• Why MVC in Javascript ?• Backbone & Spine• Backbone fundamentals• Backbone Tips & Tricks
  3. 3. Why MVC ?
  4. 4. “The world web is changed”
  5. 5. Evolution of web apps
  6. 6. Help!
  7. 7. Contact Us
  8. 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. 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. 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. 11. Model - MVC style// constructorvar Enquiry = function( data ) {! this.data = data;};// save methodEnquiry.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. 12. Backbone.js controller viewvar 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. 13. Backbone.js model var Enquiry = Backbone.Model.extend({});
  14. 14. MVC BenefitsStructureClasses, inheritance, common patterns.ModularCommunication via events, lousily coupled & testable components.Common servicesBack and forward history, clients-side url resources, utilities.Persistence layersRESTful sync, local storage, web sockets and more.CommunityPatterns,  mixins, conferences and more.
  15. 15. Challenges• Going out of the box• Nested models• Complex ajax requests• Understanding the limitations• Its still hard
  16. 16. ChallengesTodoMVC - http://addyosmani.github.com/todomvc/
  17. 17. To mvc, or not to mvc ?Use for one page appsUse for complex client-side UIs & crudUse not only for UI sugarUse not for just rendering HTMLUse not for inflexible backends
  18. 18. Web Apps
  19. 19. Backbone & Spine
  20. 20. • Created 2010 by Jeremy Ashkenas• File size 5.4k• Depends on Underscore.js ( 4k )• Very popular
  21. 21. http://blog.fogcreek.com/the-trello-tech-stack/
  22. 22. https://engineering.linkedin.com/mobile/linkedin-ipad-using-local-storage-snappy-mobile-apps
  23. 23. Spine• Inspired by Backbone• Written in CoffeeScript by Alex McCaw• File size 7k• Introduced async UI concept
  24. 24. Texthttp://hjortureh.tumblr.com/post/22117245794/spine-js-vs-backbone-js
  25. 25. Fundamentals
  26. 26. Modules• Events• Models• Collections• Views• Routes• History
  27. 27. Events
  28. 28. Events• Consists of on, off & trigger methods• All Backbone modules can trigger events• All Javascript object can be extended with the Backbone events module
  29. 29. Event exampleEvent 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 ); });
  30. 30. Models
  31. 31. 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
  32. 32. Modelvar Todo = Backbone.Model.extend({ defaults: { done: false }, toggle: function() { this.save({done: !this.get("done")}); }, clear: function() { this.destroy(); }});
  33. 33. TodoMVC - example http://addyosmani.github.com/todomvc/architecture-examples/backbone/index.html
  34. 34. Collections
  35. 35. Collections• List of models• Fires events for collection and the models• Keeps models sorted• Includes many utility methods
  36. 36. Collectionvar 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); }});
  37. 37. Views
  38. 38. 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
  39. 39. Organizing views 1:1 View Model
  40. 40. Todo viewvar 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(); } ...}
  41. 41. 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>
  42. 42. App viewvar 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);! }! ...}
  43. 43. Router & History
  44. 44. 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 )  
  45. 45. Be Careful!• Its stateful !• Its not easy• Don’t set navigate trigger to true
  46. 46. RouterAPP.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 ); }});
  47. 47. History - exampleStart listening for hash-change events // Start the history Backbone.history.start(); Use html5 history API // Start the history Backbone.history.start({pushState: true});
  48. 48. Demo
  49. 49. Backbone tips & tricks
  50. 50. 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
  51. 51. Tip #1Bootstrapping data
  52. 52. Bootstrapping data• Using fetch extends waiting time• Possible to bootstrap the most important data when the page is rendered• No loading spinners !
  53. 53. Bootstrapping DataThe 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" } ]);
  54. 54. Demo
  55. 55. Twitter demo
  56. 56. Tip #2Async User Interfaces
  57. 57. 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.
  58. 58. Importance of speed
  59. 59. 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 !
  60. 60. Demo
  61. 61. Tip #3Nested Models
  62. 62. Question Has manyAnswers
  63. 63. 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
  64. 64. 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() } ); } });
  65. 65. Tip #4Custom ajax requests
  66. 66. Custom ajax request• Sometimes RESTful methods are not enough• Example: Sorting tasks in to-do list
  67. 67. Sorting - Custom requestsaveOrder: function() { !! var ids = this.pluck("id");!! window.$.ajax({! ! url: "/tasks/reorder",! ! data: {! ! ! ids: ids! ! },! ! type: "POST",! ! dataType: "json",! ! complete: function() {! ! ! // Handle response! ! }! });!}
  68. 68. Tip #5Send zombies to heaven
  69. 69. Zombies to heaven• Its not enough to remove views from the DOM• Events must be released so you don’t have zombies walking around
  70. 70. Zombies to heaven// same as this.$el.remove();this.remove();// remove all models bindings// made by this viewthis.model.off( null, null, this );// unbind events that are// set on this viewthis.off();
  71. 71. Tip #6Use the toolbox
  72. 72. Use the toolbox• Underscore has some wonderful methods• isFunction, isObject, isString, isNumber, isDate & more.• Underscore: http:// documentcloud.github.com/underscore
  73. 73. UnderscoreLine 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))); }; });
  74. 74. Tip #7Test, test, test
  75. 75. Testing• Recommend Jasmine for testing• Recommend Sinon to fake the server• jQuery-jasmine to test views• Use setDomLibrary method to fake jQuery
  76. 76. 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(); });
  77. 77. Demo
  78. 78. Tip #8CoffeeScript
  79. 79. CoffeeScript• Advanced programing language• Compiles to javascript• Same creator of Backbone and CoffeeScript• Integrates well with Backbone
  80. 80. Coffee Script exampleExtending Backbone module class TodoList extends Backbone.ViewDouble arrow to bind to the contextUse @ instead of thisLast 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
  81. 81. Tip #9The Basics
  82. 82. The basics• The basics still apply with MVC in place• Minimize ajax requests• Keep your views thin & models fat• Understanding Javascript is the key
  83. 83. Tip #10 Bonus
  84. 84. Bonus points• Read the documentation• Read the source code• Just do it !
  85. 85. Tack så mycket Hjörtur Elvar Hilmarsson @hjortureh
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×