Migrating MVC to the  Front-end using    Backbone JS         Martin Drapeau     CTO & Co-founder Planbox            Feb 20...
AGILE PROJECT MANAGEMENTBased onSCRUM              Martin Drapeau Feb 2012
AGILE PROJECT MANAGEMENTCloud SAAStrying to solvea bigproblem...               Martin Drapeau Feb 2012
Source: www.solutionsiq.comMartin Drapeau Feb 2012
DEMO TIME!   Martin Drapeau Feb 2012
AGILE PROJECT MANAGEMENT   Built on LAMP         Martin Drapeau Feb 2012
AGILE PROJECT MANAGEMENTWe usepopularframeworks                Martin Drapeau Feb 2012
AGILE PROJECT MANAGEMENTSingle PageApplicationVery dynamic                  Martin Drapeau Feb 2012
AGILE PROJECT MANAGEMENT                     Client     http://www.planbox.com/plan                                       ...
The Problem    Martin Drapeau Feb 2012
MVC in the Back-endHTML      • HTML generated in PHP   HTMLHTML      • AJAX to update page            fragments          •...
MVC in the Back-end        ABC    Change of A requires update of B and C.    Why does the back-end have to do that?       ...
MVC in the Back-end          does not scale for         Dynamic Web AppsWHY?• Where do I write that code? PHP or JS?• Can ...
The Solution    Martin Drapeau Feb 2012
Move MVC to Front-end Let’s write our code in Javascript!              Martin Drapeau Feb 2012
Ground RulesBack-end• Becomes a REST API. Talks JSON.Front-end• Handles UI interactions.• Replicates data models.• Replica...
Choose a FrameworkRequirements:• Lightweight - fast to load & mobile friendly• Extendible• jQuery Friendly• Open Source• C...
Contenders (back in Nov 2010)      • SproutCore             A widget framework, too big and constraining!      • Javascrip...
Backbone JS   Martin Drapeau Feb 2012
Backbone.js gives structure to web applicationsby providing models with key-value binding and  custom events, collections ...
Backbone’s Take on MVC• Data and presentation are decoupled:    – “stop tying your data to the DOM”• Models use the Pub/Su...
Model – attributes and eventsvar story = new Backbone.Model({    id:1,    name:"As a coder I want MVC in the front-end",  ...
Collection – model IDs and Underscore JSvar stories = new Backbone.Collection([{     id:1,     name:"As a coder I want MVC...
Collection – event bubblingstories.bind("add", function(collection, model) {    console.log("Story #"+model.id+" added to ...
Collection – ordered or unorderedstories.pluck(points); // Returns [21, 33, 5]stories.comparator = function(modelA, modelB...
Collection – fetch from server with AJAXstories.url = "/api/get_stories"; // REST API// Preprocess JSON before creating mo...
View – Render a modelUse of extend to create a new Class.StoryView = Backbone.View.extend({    className: "story",    rend...
View – HTML template (Underscore JS)<script type="text/template" id="story_template">    <label>#<%=id%></label>    <input...
View – User interactioninitialize: function() {    var view = this, model = view.model;    model.bind("change", view.rende...
Client/Server Sync Loop   1.   Push changes to server   2.   Receive acknowledgement   3.   Set attribute change on data m...
Event Payload – options argumentThe options argument can be used as an event payload.Use it not to render unnecessarily.sa...
View – Render a collectionStoriesView = Backbone.View.extend({    tagName: "ul",    className: "stories",    initialize: f...
Other Backbone Artefacts• Sync  – Handles client/server communication  – Uses jQuery (or Zepto) $.ajax  – Follows CRUD (Mo...
Backbone JS    got Planboxed   Martin Drapeau Feb 2012
No CRUD - Rich verbs mapping to API• A story model has:  –   save  www.planbox.com/api/update_story  –   move  www.planb...
Local Persistence in URL  • State of page is saved in URL’s hash tag using    JSON (quotes are stripped)                  ...
Model SelectionNikModel = Backbone.Model.extend({    selected: false,    select: function(options) {        options || (op...
Selection in CollectionNikCollection = Backbone.Collection.extend({    model: NikModel,    select: function(model, options...
Collection Filtering// Argument filters is a hash of attributes and their// allowed values. For example: filters.type = [b...
Sort DOM Against Collection     DOM                                            Collection                                 ...
Cascading Events – Model bound to Collection  • Summary Model listens to changes on Stories Collection  • Keeps sums of po...
Custom Widgets• All widgets are custom    –   Ligthweight and flexible    –   Fast to load    –   No jQuery UI with large ...
Backbone Auto-complete Widgetvar collection = new Backbone.Collection([    {id:"AB", name:"Alberta"},    {id:"BC", name:"B...
Real Time – No page reload• Polling server at 15 second interval  (Tried COMET but was overkill)• Change history Collectio...
DeploymentBuild procedure     – Javascript files are minified and collated     – Version is automatically incremented     ...
The Result   Martin Drapeau Feb 2012
3rd Party Libraries        Application Load Size                                          JS fileGzipped Size (KB) Content...
Benefits of MVC in front-end• Decouple back-end and front-end  – The API becomes the back-end contract (and    performance...
Benefits of MVC in front-end• Release anytime   – Majority of code changes are in UI   – Changes happen faster in front-en...
AcknowledgementsMathieu Dumais-Savard  Sébastien Giroux  Jeremy Ashkenas   js-montreal.org       Martin Drapeau Feb 2012
References & LinksJavascript Frameworks•   Backbone JS http://documentcloud.github.com/backbone/•   JavascriptMVC http://j...
Questions?   Martin Drapeau Feb 2012
Upcoming SlideShare
Loading in...5
×

Planbox Backbone MVC

7,434

Published on

Migrating MVC to the Front-end using Backbone JS.

Planbox is a single-page web application for Agile project management. It was built using the traditional MVC stack with CodeIgniter (PHP) and jQuery (Javascript). AJAX was heavily used to update DOM elements to offer a dynamic user experience. UX logic code quickly became spread across Javascript and PHP. The application code base quickly became unmanageable and scaling functionality became difficult. Things had to change.

A decision was made to change architecture: bring all the UX logic in the front-end, and turn the back-end into an engine in charge of business logic.

This talk is about this experience. How we moved the MVC stack from the back-end to the front-end. How we used Backbone JS as the foundation of our front-end framework and built on top. How the backend became a black-box with a Restful API. What lessons we learned, what benefits we gained, and what reflections we made about the future of MVC in Javascript.

Published in: Technology, Education

Planbox Backbone MVC

  1. 1. Migrating MVC to the Front-end using Backbone JS Martin Drapeau CTO & Co-founder Planbox Feb 2012 Martin Drapeau Feb 2012
  2. 2. AGILE PROJECT MANAGEMENTBased onSCRUM Martin Drapeau Feb 2012
  3. 3. AGILE PROJECT MANAGEMENTCloud SAAStrying to solvea bigproblem... Martin Drapeau Feb 2012
  4. 4. Source: www.solutionsiq.comMartin Drapeau Feb 2012
  5. 5. DEMO TIME! Martin Drapeau Feb 2012
  6. 6. AGILE PROJECT MANAGEMENT Built on LAMP Martin Drapeau Feb 2012
  7. 7. AGILE PROJECT MANAGEMENTWe usepopularframeworks Martin Drapeau Feb 2012
  8. 8. AGILE PROJECT MANAGEMENTSingle PageApplicationVery dynamic Martin Drapeau Feb 2012
  9. 9. AGILE PROJECT MANAGEMENT Client http://www.planbox.com/plan Web Page HTML CSSWas architected 1 Javascripton Back-end Server 4 5 Controller ViewMVC 2 Model 3 Database Engine Martin Drapeau Feb 2012
  10. 10. The Problem Martin Drapeau Feb 2012
  11. 11. MVC in the Back-endHTML • HTML generated in PHP HTMLHTML • AJAX to update page fragments • Javascript is the duck-tape HTML Feature development Bottleneck! Martin Drapeau Feb 2012
  12. 12. MVC in the Back-end ABC Change of A requires update of B and C. Why does the back-end have to do that? Martin Drapeau Feb 2012
  13. 13. MVC in the Back-end does not scale for Dynamic Web AppsWHY?• Where do I write that code? PHP or JS?• Can we push to production? No, back-end has changed!• Developers must be trained on PHP and JS.• Etc... Martin Drapeau Feb 2012
  14. 14. The Solution Martin Drapeau Feb 2012
  15. 15. Move MVC to Front-end Let’s write our code in Javascript! Martin Drapeau Feb 2012
  16. 16. Ground RulesBack-end• Becomes a REST API. Talks JSON.Front-end• Handles UI interactions.• Replicates data models.• Replicates business logic only when necessary.Must be fast and lightweight! Martin Drapeau Feb 2012
  17. 17. Choose a FrameworkRequirements:• Lightweight - fast to load & mobile friendly• Extendible• jQuery Friendly• Open Source• Can be integrated gradually Martin Drapeau Feb 2012
  18. 18. Contenders (back in Nov 2010) • SproutCore A widget framework, too big and constraining! • Javascript MVC Too much stuff in there! Constraining and awkward. • Knockout MVVM?? I don’t want to couple views and models! • Backbone JS Small, has collections, HTML templates, etc. Natural fit.A recent source of comparisons can be found here:http://codebrief.com/2012/01/the-top-10-javascript-mvc-frameworks-reviewed/ Martin Drapeau Feb 2012
  19. 19. Backbone JS Martin Drapeau Feb 2012
  20. 20. Backbone.js gives structure to web applicationsby providing models with key-value binding and custom events, collections with a rich API of enumerable functions, views with declarative event handling, and connects it all to your Jeremy Ashkenas, existing API over a RESTful JSON interface. Author of Backbone JS , Underscore JS, and CoffeeScript •Open Source •5.3kb, Packed and gzipped •87 contributors •6,500 watchers Martin Drapeau Feb 2012
  21. 21. Backbone’s Take on MVC• Data and presentation are decoupled: – “stop tying your data to the DOM”• Models use the Pub/Sub pattern: – Views subscribe to model change events to re-render• Collections are used to group models: – Events bubble up to collections. Subscribe to the add event on the collection to get notified.• Models are synchronized with your back-end: – Via AJAX to your RESTful API. CRUD by default.• Views control user interactions and trigger model updates: – HTML templates, jQuery or Zepto DOM manipulation and AJAX Where is the C in MVC? The Controller is in the View. Martin Drapeau Feb 2012
  22. 22. Model – attributes and eventsvar story = new Backbone.Model({ id:1, name:"As a coder I want MVC in the front-end", points:21});story.bind("change:points", function(model, points) { console.log("Story #"+model.id+" points changed from "+ model.previous("points")+" to "+points);});story.get("points"); // Returns 21story.set({points:99}); Console>>> Story #1 points changed from 21 to 99 Martin Drapeau Feb 2012
  23. 23. Collection – model IDs and Underscore JSvar stories = new Backbone.Collection([{ id:1, name:"As a coder I want MVC in the front-end", points:21}, { id:2, name:"As I PM, I want visibility", points:33}]);var story = stories.get(2); // Return model #2stories.pluck(points); // Returns [21, 33] Martin Drapeau Feb 2012
  24. 24. Collection – event bubblingstories.bind("add", function(collection, model) { console.log("Story #"+model.id+" added to collection");});stories.add({ id:3, name:"As a driver, I want a reliable car" points:5});stories.pluck(points); // Returns [21, 33, 5] Console>>> Story #3 added to collection Martin Drapeau Feb 2012
  25. 25. Collection – ordered or unorderedstories.pluck(points); // Returns [21, 33, 5]stories.comparator = function(modelA, modelB) { var a = modelA.get(points); var b = modelB.get(points); return a < b ? -1 : a > b ? 1 : 0;}stories.sort();stories.pluck(points); // Returns [5, 21, 33] Martin Drapeau Feb 2012
  26. 26. Collection – fetch from server with AJAXstories.url = "/api/get_stories"; // REST API// Preprocess JSON before creating modelsstories.parse = function(object) { if (object && object.content) return object.content;};stories.fetch({ data: {product_id:1}, // jQuery $.ajax arguments dataType: JSONP, success: function(collection) { console.log(Loaded +collection.length+ stories); }, error: function(collection, error) { console.log(error); }});>>> Loaded 87 stories Console Martin Drapeau Feb 2012
  27. 27. View – Render a modelUse of extend to create a new Class.StoryView = Backbone.View.extend({ className: "story", render: function() { var view = this; var model = this.model; $(this.el).text(model.get(name)); return view; // Always return view for chaining }});var view = new StoryView({model: story});view.render();$(view.el).appendTo(body); Martin Drapeau Feb 2012
  28. 28. View – HTML template (Underscore JS)<script type="text/template" id="story_template"> <label>#<%=id%></label> <input type="text" value="<%=name%>" /> <button>Delete</button></script>StoryView = Backbone.View.extend({ className: "story", template: _.template($("#story_template").html()), render: function() { var view = this; var model = this.model; $(this.el).html(view.template(model.toJSON())); return view; }}); Martin Drapeau Feb 2012
  29. 29. View – User interactioninitialize: function() { var view = this, model = view.model; model.bind("change", view.render, view); model.bind("remove", view.remove, view); return view;},events: { // Delegate events "change input": "saveName", "click button": "del"},saveName: function() { var view = this, model = view.model; var new_name = $(view.el).children("input").val(); model.save({name:new_name}); // Call server then model.set return view;},del: function() { var view = this, model = view.model; model.destroy(); // Call server then collection remove return view;} Martin Drapeau Feb 2012
  30. 30. Client/Server Sync Loop 1. Push changes to server 2. Receive acknowledgement 3. Set attribute change on data model 4. View captures change event 5. View renders (maybe) Martin Drapeau Feb 2012
  31. 31. Event Payload – options argumentThe options argument can be used as an event payload.Use it not to render unnecessarily.saveName: function() { var view = this, model = view.model; var new_name = $(view.el).children("input").val(); model.save({name:new_name}, {caller:view}); return view;}render: function(options) { options || (options = {}); if (options.caller == this) return this; var view = this; var model = this.model; $(this.el).html(view.template(model.toJSON())); return view;} Martin Drapeau Feb 2012
  32. 32. View – Render a collectionStoriesView = Backbone.View.extend({ tagName: "ul", className: "stories", initialize: function() { var view = this; var collection = view.collection; collection.bind(add, view.addOne, view); collection.bind(reset, view.addAll, view); }, addOne: function(model) { var view = this; var story_view = new StoryView({model: model}); story_view.render(); $(view.el).append(story_view.el); return view; }, addAll: function() { var view = this, collection = view.collection; collection.each(function(model) { view.addOne(model); }); return view; }}); Martin Drapeau Feb 2012
  33. 33. Other Backbone Artefacts• Sync – Handles client/server communication – Uses jQuery (or Zepto) $.ajax – Follows CRUD (Model.url and Collection.url)• Router & History – Handle URL changes and that Back button – Wraps HTML5 History API – Graceful polling fallback Martin Drapeau Feb 2012
  34. 34. Backbone JS got Planboxed Martin Drapeau Feb 2012
  35. 35. No CRUD - Rich verbs mapping to API• A story model has: – save  www.planbox.com/api/update_story – move  www.planbox.com/api/move_story – duplicate  www.planbox.com/api/duplicate_story – etc...• Did not use Sync – Overwrote methods fetch, save, etc. – Used jQuery $.ajax of course Martin Drapeau Feb 2012
  36. 36. Local Persistence in URL • State of page is saved in URL’s hash tag using JSON (quotes are stripped) Opened tab in Story Selected Story Panehttp://planbox.com/nik#{product_id:1,story_pane_tab:tasks,filter_id:1,story_id:199486} Top level project selected Selected Sprint https://github.com/martindrapeau/JSON-in-URL-Hash Martin Drapeau Feb 2012
  37. 37. Model SelectionNikModel = Backbone.Model.extend({ selected: false, select: function(options) { options || (options = {}); if (this.selected) return this; this.selected = true; if (!options.silent) this.trigger(select, this, options); return this; }, unselect: function(options) { options || (options = {}); if (!this.selected) return this; this.selected = false; if (!options.silent) this.trigger(unselect, this, options); return this; } Martin Drapeau Feb 2012
  38. 38. Selection in CollectionNikCollection = Backbone.Collection.extend({ model: NikModel, select: function(model, options) { options || (options = {}); var collection = this; _.each(collection.selected(), function(model) { model.unselect(options); }); model.select(options); return this; }, selected: function() { return _.select(this.models, function(model) { return model.selected; }); }, Martin Drapeau Feb 2012
  39. 39. Collection Filtering// Argument filters is a hash of attributes and their// allowed values. For example: filters.type = [bug]// Property Model.filtered is a hash of failed conditions.// For example is Model.type = feature, then// Model.filtered = [bug].Model.filter(filters, options)Model.filtered // Array of filtered out attributesCollection.filter() // Runs filtering on the collectionCollection.filtered()Collection.not_filtered() Martin Drapeau Feb 2012
  40. 40. Sort DOM Against Collection DOM Collection #199834 #190293 #201293 #150932 #188456 #178545 #190293• Stories are ordered by priority (same for everyone)• Need to sync order of stories (i.e. after drag and drop)• Helper function to re-order DOM elements minimizing DOM manipulations Martin Drapeau Feb 2012
  41. 41. Cascading Events – Model bound to Collection • Summary Model listens to changes on Stories Collection • Keeps sums of points, resources, estimate-hours, etc... • Views tied to it update in real time! Martin Drapeau Feb 2012
  42. 42. Custom Widgets• All widgets are custom – Ligthweight and flexible – Fast to load – No jQuery UI with large CSS and JS footprint – Use of Backbone Models and Collections• Types of widgets – Dialogs – Drop-downs – Date picker – Auto-complete – Input Martin Drapeau Feb 2012
  43. 43. Backbone Auto-complete Widgetvar collection = new Backbone.Collection([ {id:"AB", name:"Alberta"}, {id:"BC", name:"British Columbia"}, {id:"MB", name:"Manitoba"}, // ... {id:"AP", name:"Armed Forces Pacific"}]);$(#states_provinces).autocomplete({ collection: collection, attr: name, noCase: true, ul_class: autocomplete shadow, ul_css: {z-index:1234}}); http://www.planbox.com/blog/news/updates/jquery-autocomplete-plugin-for-backbone-js.html Martin Drapeau Feb 2012
  44. 44. Real Time – No page reload• Polling server at 15 second interval (Tried COMET but was overkill)• Change history Collection – Last transaction ID is kept in client – Every 15 seconds, ask server for newer updates – Server sends updated Models Martin Drapeau Feb 2012
  45. 45. DeploymentBuild procedure – Javascript files are minified and collated – Version is automatically incremented – ?v=3.1234 is added to all CSS/JS files (prevent caching issues)Planbox - Minifying Javascript Files...Version: 3.0193rd.js 70397 bytes hom.js 9758 bytesjwysiwyg/jquery.wysiwyg.min.js 36278 bytes acc.js 14505 byteszeroclipboard/zeroclipboard.min.js 6747 bytes rep.js 84129 bytesadw.js 32136 bytes table.js 42311 bytescommon.js 195872 bytes mobile_3rd.js 74594 bytesnik.js 230008 bytes mobile_common.js 71371 bytesmag.js 53275 bytes mobile.js 116873 bytes Martin Drapeau Feb 2012
  46. 46. The Result Martin Drapeau Feb 2012
  47. 47. 3rd Party Libraries Application Load Size JS fileGzipped Size (KB) Content (KB) jquery.min.js 29.34 83.26 jquery.wysiwyg.min.js 10.54 35.43 zeroclipboard.min.js 2.56 6.59 3rd.js 17.82 68.75 60.26 194.03 common.js 41.27 191.28Application nik.js 45.58 224.62 86.85 415.9 Total 147.11 609.93 346 Story Models (JSON) 124.67 949.01 KB/Story 0.36 2.74 Martin Drapeau Feb 2012
  48. 48. Benefits of MVC in front-end• Decouple back-end and front-end – The API becomes the back-end contract (and performance too I hope). – Back-end and front-end teams are decoupled• Bandwidth reduction – Data and presentation loaded once – Subsequently, only Model deltas are transferred• Reduce server load – All UX processing is performed client side Martin Drapeau Feb 2012
  49. 49. Benefits of MVC in front-end• Release anytime – Majority of code changes are in UI – Changes happen faster in front-end than back-end – Concurrent versions of the client can exist. Hit F5 to get the latest and greatest. – Back-end doesn’t change – no risk of conflicts in browser• Faster development – Know and master one language – Use the browser as your development and testing bed (yeah Firebug!) – Release often – Don’t accumulate ‘corrected’ defects in development – push them out as they are fixed Martin Drapeau Feb 2012
  50. 50. AcknowledgementsMathieu Dumais-Savard Sébastien Giroux Jeremy Ashkenas js-montreal.org Martin Drapeau Feb 2012
  51. 51. References & LinksJavascript Frameworks• Backbone JS http://documentcloud.github.com/backbone/• JavascriptMVC http://javascriptmvc.com/• SproutCore http://sproutcore.com/• Underscore JS http://documentcloud.github.com/underscore/• jQuery http://jquery.com/Helpers• John Resig’s Javascript Micro-Template http://ejohn.org/blog/javascript-micro-templating/• JSON2 http://www.json.org/js.html• jsmin (PHP minifier) https://github.com/rgrove/jsmin-php/Books• JavasScript Web Applications (Alex MaxCaw) http://shop.oreilly.com/product/0636920018421.do• jQuery Cookbook (Cody Lindley) http://shop.oreilly.com/product/9780596159788.do?sortby=bestSellersPlanbox Open Source Contributions• Backbone Widgets https://github.com/martindrapeau/Backbone-Widgets• JSON in URL Hash https://github.com/martindrapeau/JSON-in-URL-Hash Martin Drapeau Feb 2012
  52. 52. Questions? Martin Drapeau Feb 2012
  1. A particular slide catching your eye?

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

×