Planbox Backbone MVC

52
Martin Drapeau Feb 2012 Migrating MVC to the Front-end using Backbone JS Martin Drapeau CTO & Co-founder Planbox Feb 2012

description

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.

Transcript of Planbox Backbone MVC

Page 1: Planbox Backbone MVC

Martin Drapeau Feb 2012

Migrating MVC to theFront-end using

Backbone JSMartin Drapeau

CTO & Co-founder PlanboxFeb 2012

Page 2: Planbox Backbone MVC

Martin Drapeau Feb 2012

AGILE PROJECT MANAGEMENT

Based on SCRUM

Page 3: Planbox Backbone MVC

Martin Drapeau Feb 2012

AGILE PROJECT MANAGEMENT

Cloud SAAS trying to solve a big problem...

Page 4: Planbox Backbone MVC

Martin Drapeau Feb 2012

Source: www.solutionsiq.com

Page 5: Planbox Backbone MVC

Martin Drapeau Feb 2012

DEMO TIME!

Page 6: Planbox Backbone MVC

Martin Drapeau Feb 2012

AGILE PROJECT MANAGEMENT

Built on LAMP

Page 7: Planbox Backbone MVC

Martin Drapeau Feb 2012

AGILE PROJECT MANAGEMENT

We usepopular frameworks

Page 8: Planbox Backbone MVC

Martin Drapeau Feb 2012

AGILE PROJECT MANAGEMENT

Single Page Application

Very dynamic

Page 9: Planbox Backbone MVC

Martin Drapeau Feb 2012

AGILE PROJECT MANAGEMENT

Was architected on Back-end MVC

Server

Model

ViewController

Client

Web PageHTML

CSSJavascript

http://www.planbox.com/plan

Database

1

2

3

4

5

Engine

Page 10: Planbox Backbone MVC

Martin Drapeau Feb 2012

The Problem

Page 11: Planbox Backbone MVC

Martin Drapeau Feb 2012

HTML

HTMLHTML

HTMLHTML

HTM

L

MVC in the Back-end

• HTML generated in PHP• AJAX to update page

fragments• Javascript is the duck-tape

Feature development Bottleneck!

Page 12: Planbox Backbone MVC

Martin Drapeau Feb 2012

MVC in the Back-endA

B

C

Change of A requires update of B and C.Why does the back-end have to do that?

Page 13: Planbox Backbone MVC

Martin Drapeau Feb 2012

MVC in the Back-enddoes not scale for

Dynamic Web Apps

WHY?• 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...

Page 14: Planbox Backbone MVC

Martin Drapeau Feb 2012

The Solution

Page 15: Planbox Backbone MVC

Martin Drapeau Feb 2012

Move MVC to Front-end

MVC

Let’s write our code in Javascript!

Page 16: Planbox Backbone MVC

Martin Drapeau Feb 2012

Ground Rules

Back-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!

Page 17: Planbox Backbone MVC

Martin Drapeau Feb 2012

Choose a Framework

Requirements:• Lightweight - fast to load & mobile friendly • Extendible• jQuery Friendly• Open Source• Can be integrated gradually

Page 18: Planbox Backbone MVC

Martin Drapeau Feb 2012

Contenders (back in Nov 2010)

• SproutCoreA widget framework, too big and constraining!

• Javascript MVCToo much stuff in there! Constraining and awkward.

• KnockoutMVVM?? I don’t want to couple views and models!

• Backbone JSSmall, 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/

Page 19: Planbox Backbone MVC

Martin Drapeau Feb 2012

Backbone JS

Page 20: Planbox Backbone MVC

Martin Drapeau Feb 2012

Jeremy Ashkenas,Author of Backbone JS , Underscore JS, and CoffeeScript

Backbone.js gives structure to web applications by 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 existing API over a RESTful JSON interface.

•Open Source•5.3kb, Packed and gzipped•87 contributors•6,500 watchers

Page 21: Planbox Backbone MVC

Martin Drapeau Feb 2012

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

The Controller is in the View.Where is the C in MVC?

Page 22: Planbox Backbone MVC

Martin Drapeau Feb 2012

Model – attributes and events

var 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 21

story.set({points:99});

>>> Story #1 points changed from 21 to 99Console

Page 23: Planbox Backbone MVC

Martin Drapeau Feb 2012

Collection – model IDs and Underscore JS

var 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 #2

stories.pluck('points'); // Returns [21, 33]

Page 24: Planbox Backbone MVC

Martin Drapeau Feb 2012

Collection – event bubbling

stories.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]

>>> Story #3 added to collectionConsole

Page 25: Planbox Backbone MVC

Martin Drapeau Feb 2012

Collection – ordered or unordered

stories.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]

Page 26: Planbox Backbone MVC

Martin Drapeau Feb 2012

Collection – fetch from server with AJAX

>>> Loaded 87 stories Console

stories.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); }});

Page 27: Planbox Backbone MVC

Martin Drapeau Feb 2012

View – Render a model

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');

Use of extend to create a new Class.

Page 28: Planbox Backbone MVC

Martin Drapeau Feb 2012

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; }});

Page 29: Planbox Backbone MVC

Martin Drapeau Feb 2012

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;}

Page 30: Planbox Backbone MVC

Martin Drapeau Feb 2012

Client/Server Sync Loop

1. Push changes to server2. Receive acknowledgement3. Set attribute change on data model4. View captures change event5. View renders (maybe)

Page 31: Planbox Backbone MVC

Martin Drapeau Feb 2012

Event Payload – options argument

The 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;}

Page 32: Planbox Backbone MVC

Martin Drapeau Feb 2012

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; }});

Page 33: Planbox Backbone MVC

Martin Drapeau Feb 2012

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

Page 34: Planbox Backbone MVC

Martin Drapeau Feb 2012

Backbone JSgot

Planboxed

Page 35: Planbox Backbone MVC

Martin Drapeau Feb 2012

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

Page 36: Planbox Backbone MVC

Martin Drapeau Feb 2012

Local Persistence in URL• State of page is saved in URL’s hash tag using

JSON (quotes are stripped)

http://planbox.com/nik#{product_id:1,story_pane_tab:tasks,filter_id:1,story_id:199486}

Top level project selected

Opened tab in Story Pane

Selected Sprint

Selected Story

https://github.com/martindrapeau/JSON-in-URL-Hash

Page 37: Planbox Backbone MVC

Martin Drapeau Feb 2012

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; }

Page 38: Planbox Backbone MVC

Martin Drapeau Feb 2012

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; }); },

Page 39: Planbox Backbone MVC

Martin Drapeau Feb 2012

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 attributes

Collection.filter() // Runs filtering on the collectionCollection.filtered()Collection.not_filtered()

Page 40: Planbox Backbone MVC

Martin Drapeau Feb 2012

Sort DOM Against Collection

• 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

DOM Collection#199834

#190293

#150932#188456#178545#190293

#201293

Page 41: Planbox Backbone MVC

Martin Drapeau Feb 2012

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!

Page 42: Planbox Backbone MVC

Martin Drapeau Feb 2012

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

Page 43: Planbox Backbone MVC

Martin Drapeau Feb 2012

Backbone Auto-complete Widget

http://www.planbox.com/blog/news/updates/jquery-autocomplete-plugin-for-backbone-js.html

var 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}});

Page 44: Planbox Backbone MVC

Martin Drapeau Feb 2012

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

Page 45: Planbox Backbone MVC

Martin Drapeau Feb 2012

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.019

3rd.js 70397 bytes jwysiwyg/jquery.wysiwyg.min.js 36278 bytes zeroclipboard/zeroclipboard.min.js 6747 bytes adw.js 32136 bytescommon.js 195872 bytesnik.js 230008 bytesmag.js 53275 bytes

hom.js 9758 bytesacc.js 14505 bytesrep.js 84129 bytestable.js 42311 bytesmobile_3rd.js 74594 bytesmobile_common.js 71371 bytesmobile.js 116873 bytes

Page 46: Planbox Backbone MVC

Martin Drapeau Feb 2012

The Result

Page 47: Planbox Backbone MVC

Martin Drapeau Feb 2012

Application Load Size3rd

Par

ty L

ibra

ries

Appl

icati

on

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.28

nik.js 45.58 224.62   86.85 415.9

Total 147.11 609.93

346 Story Models (JSON) 124.67 949.01KB/Story 0.36 2.74

Page 48: Planbox Backbone MVC

Martin Drapeau Feb 2012

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

Page 49: Planbox Backbone MVC

Martin Drapeau Feb 2012

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

Page 50: Planbox Backbone MVC

Martin Drapeau Feb 2012

Acknowledgements

Mathieu Dumais-SavardSébastien GirouxJeremy Ashkenasjs-montreal.org

Page 51: Planbox Backbone MVC

Martin Drapeau Feb 2012

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=bestSellers

Planbox Open Source Contributions• Backbone Widgets https://github.com/martindrapeau/Backbone-Widgets• JSON in URL Hash https://github.com/martindrapeau/JSON-in-URL-Hash

Page 52: Planbox Backbone MVC

Martin Drapeau Feb 2012

Questions?