• Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Be the first to comment
No Downloads

Views

Total Views
5,717
On Slideshare
0
From Embeds
0
Number of Embeds
0

Actions

Shares
Downloads
115
Comments
0
Likes
7

Embeds 0

No embeds

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
    No notes for slide

Transcript

  • 1. MVC Frameworks in Javascript Hjörtur Hilmarsson @hjortureh
  • 2. Agenda• Why MVC in Javascript ?• Backbone & Spine• Backbone fundamentals• Backbone Tips & Tricks
  • 3. Why MVC ?
  • 4. “The world web is changed”
  • 5. Evolution of web apps
  • 6. Help!
  • 7. Contact Us
  • 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// 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. 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. Backbone.js model var Enquiry = Backbone.Model.extend({});
  • 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. Challenges• Going out of the box• Nested models• Complex ajax requests• Understanding the limitations• Its still hard
  • 16. ChallengesTodoMVC - http://addyosmani.github.com/todomvc/
  • 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. Web Apps
  • 19. Backbone & Spine
  • 20. • Created 2010 by Jeremy Ashkenas• File size 5.4k• Depends on Underscore.js ( 4k )• Very popular
  • 21. http://blog.fogcreek.com/the-trello-tech-stack/
  • 22. https://engineering.linkedin.com/mobile/linkedin-ipad-using-local-storage-snappy-mobile-apps
  • 23. Spine• Inspired by Backbone• Written in CoffeeScript by Alex McCaw• File size 7k• Introduced async UI concept
  • 24. Texthttp://hjortureh.tumblr.com/post/22117245794/spine-js-vs-backbone-js
  • 25. Fundamentals
  • 26. Modules• Events• Models• Collections• Views• Routes• History
  • 27. Events
  • 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. 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. Models
  • 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. Modelvar Todo = Backbone.Model.extend({ defaults: { done: false }, toggle: function() { this.save({done: !this.get("done")}); }, clear: function() { this.destroy(); }});
  • 33. TodoMVC - example http://addyosmani.github.com/todomvc/architecture-examples/backbone/index.html
  • 34. Collections
  • 35. Collections• List of models• Fires events for collection and the models• Keeps models sorted• Includes many utility methods
  • 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. Views
  • 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. Organizing views 1:1 View Model
  • 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. 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. 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. Router & History
  • 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. Be Careful!• Its stateful !• Its not easy• Don’t set navigate trigger to true
  • 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. 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. Demo
  • 49. Backbone tips & tricks
  • 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. Tip #1Bootstrapping data
  • 52. Bootstrapping data• Using fetch extends waiting time• Possible to bootstrap the most important data when the page is rendered• No loading spinners !
  • 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. Demo
  • 55. Twitter demo
  • 56. Tip #2Async User Interfaces
  • 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. Importance of speed
  • 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. Demo
  • 61. Tip #3Nested Models
  • 62. Question Has manyAnswers
  • 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. 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. Tip #4Custom ajax requests
  • 66. Custom ajax request• Sometimes RESTful methods are not enough• Example: Sorting tasks in to-do list
  • 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. Tip #5Send zombies to heaven
  • 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. 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. Tip #6Use the toolbox
  • 72. Use the toolbox• Underscore has some wonderful methods• isFunction, isObject, isString, isNumber, isDate & more.• Underscore: http:// documentcloud.github.com/underscore
  • 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. Tip #7Test, test, test
  • 75. Testing• Recommend Jasmine for testing• Recommend Sinon to fake the server• jQuery-jasmine to test views• Use setDomLibrary method to fake jQuery
  • 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. Demo
  • 78. Tip #8CoffeeScript
  • 79. CoffeeScript• Advanced programing language• Compiles to javascript• Same creator of Backbone and CoffeeScript• Integrates well with Backbone
  • 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. Tip #9The Basics
  • 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. Tip #10 Bonus
  • 84. Bonus points• Read the documentation• Read the source code• Just do it !
  • 85. Tack så mycket Hjörtur Elvar Hilmarsson @hjortureh