javascript mvc & backbone tips & tricks

88
MVC Frameworks in Javascript Hjörtur Hilmarsson @hjortureh

Upload: hjoertur-hilmarsson

Post on 10-May-2015

5.941 views

Category:

Technology


0 download

TRANSCRIPT

Page 1: Javascript MVC & Backbone Tips & Tricks

MVC Frameworksin Javascript

Hjörtur Hilmarsson@hjortureh

Page 2: Javascript MVC & Backbone Tips & Tricks

Agenda

• Why MVC in Javascript ?

• Backbone & Spine

• Backbone fundamentals

• Backbone Tips & Tricks

Page 3: Javascript MVC & Backbone Tips & Tricks

Why MVC ?

Page 4: Javascript MVC & Backbone Tips & Tricks

“The world web is changed”

Page 5: Javascript MVC & Backbone Tips & Tricks

Evolution of web apps

Page 6: Javascript MVC & Backbone Tips & Tricks

Help!

Page 7: Javascript MVC & Backbone Tips & Tricks

Contact Us

Page 8: Javascript MVC & Backbone Tips & Tricks

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>

Page 9: Javascript MVC & Backbone Tips & Tricks

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

Page 10: Javascript MVC & Backbone Tips & Tricks

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

Page 11: Javascript MVC & Backbone Tips & Tricks

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

};

Page 12: Javascript MVC & Backbone Tips & Tricks

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

});

Page 13: Javascript MVC & Backbone Tips & Tricks

Backbone.js model

var Enquiry = Backbone.Model.extend({});

Page 14: Javascript MVC & Backbone Tips & Tricks
Page 15: Javascript MVC & Backbone Tips & Tricks

MVC Benefits

StructureClasses, 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.

Page 16: Javascript MVC & Backbone Tips & Tricks

Challenges

• Going out of the box

• Nested models

• Complex ajax requests

• Understanding the limitations

• Its still hard

Page 17: Javascript MVC & Backbone Tips & Tricks

Challenges

TodoMVC - http://addyosmani.github.com/todomvc/

Page 18: Javascript MVC & Backbone Tips & Tricks

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

Page 19: Javascript MVC & Backbone Tips & Tricks

Web Apps

Page 20: Javascript MVC & Backbone Tips & Tricks

Backbone & Spine

Page 21: Javascript MVC & Backbone Tips & Tricks

• Created 2010 by Jeremy Ashkenas

• File size 5.4k

• Depends on Underscore.js ( 4k )

• Very popular

Page 23: Javascript MVC & Backbone Tips & Tricks
Page 25: Javascript MVC & Backbone Tips & Tricks

• Inspired by Backbone

• Written in CoffeeScript by Alex McCaw

• File size 7k

• Introduced async UI concept

Spine

Page 27: Javascript MVC & Backbone Tips & Tricks

Fundamentals

Page 28: Javascript MVC & Backbone Tips & Tricks

Modules

• Events

• Models

• Collections

• Views

• Routes

• History

Page 29: Javascript MVC & Backbone Tips & Tricks

Events

Page 30: Javascript MVC & Backbone Tips & Tricks

Events

• Consists of on, off & trigger methods

• All Backbone modules can trigger events

• All Javascript object can be extended with the Backbone events module

Page 31: Javascript MVC & Backbone Tips & Tricks

Event example

user.on("change:name", function( name ) {! alert( "Name changed to " + name );});

Bind to a name change event

Event triggered inside User class when name is changed

this.trigger("change:name", "Mr Hilmarsson");

Page 32: Javascript MVC & Backbone Tips & Tricks

Models

Page 33: Javascript MVC & Backbone Tips & Tricks

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

Page 34: Javascript MVC & Backbone Tips & Tricks

Model

var Todo = Backbone.Model.extend({

defaults: { done: false },

toggle: function() { this.save({done: !this.get("done")}); },

clear: function() { this.destroy(); }

});

Page 35: Javascript MVC & Backbone Tips & Tricks

TodoMVC - example

http://addyosmani.github.com/todomvc/architecture-examples/backbone/index.html

Page 36: Javascript MVC & Backbone Tips & Tricks

Collections

Page 37: Javascript MVC & Backbone Tips & Tricks

Collections

• List of models

• Fires events for collection and the models

• Keeps models sorted

• Includes many utility methods

Page 38: Javascript MVC & Backbone Tips & Tricks

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

});

Page 39: Javascript MVC & Backbone Tips & Tricks

Views

Page 40: Javascript MVC & Backbone Tips & Tricks

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

Page 41: Javascript MVC & Backbone Tips & Tricks

Organizing views

1 : 1View Model

Page 42: Javascript MVC & Backbone Tips & Tricks

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

...}

Page 43: Javascript MVC & Backbone Tips & Tricks

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>

Page 44: Javascript MVC & Backbone Tips & Tricks

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

! ...

}

Page 45: Javascript MVC & Backbone Tips & Tricks

Router & History

Page 46: Javascript MVC & Backbone Tips & Tricks

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 )  

Page 47: Javascript MVC & Backbone Tips & Tricks

Be Careful!

• Its stateful !

• Its not easy

• Don’t set navigate trigger to true

Page 48: Javascript MVC & Backbone Tips & Tricks

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

});

Page 49: Javascript MVC & Backbone Tips & Tricks

History - example

// Start the historyBackbone.history.start();

// Start the historyBackbone.history.start({pushState: true});

Use html5 history API

Start listening for hash-change events

Page 50: Javascript MVC & Backbone Tips & Tricks

Demo

Page 51: Javascript MVC & Backbone Tips & Tricks

Backbone tips & tricks

Page 52: Javascript MVC & 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

Page 53: Javascript MVC & Backbone Tips & Tricks

Tip #1Bootstrapping data

Page 54: Javascript MVC & Backbone Tips & Tricks

Bootstrapping data

• Using fetch extends waiting time

• Possible to bootstrap the most important data when the page is rendered

• No loading spinners !

Page 55: Javascript MVC & Backbone Tips & Tricks

Bootstrapping Data

// Current userAPP.currentUser = new APP.Models.User(<%= @current_user.to_json.html_safe %>);

// NotesAPP.notes.reset(<%= @notes.to_json.html_safe %>);

The code

After render

// Current userAPP.currentUser = new APP.Models.User({ id: 1, username: "hjortureh", name: "Hjortur Hilmarsson", avatar: "avatar.gif" });

// NotesAPP.notes.reset([ { id: 1, text: "Note 1" }, { id: 1, text: "Note 2" }, { id: 1, text: "Note 3" }]);

Page 56: Javascript MVC & Backbone Tips & Tricks

Demo

Page 57: Javascript MVC & Backbone Tips & Tricks

Twitter demo

Page 58: Javascript MVC & Backbone Tips & Tricks

Tip #2Async User Interfaces

Page 59: Javascript MVC & Backbone Tips & Tricks

Importance of speedAmazon 100 ms of extra load time caused a 1% drop in sales (source: Greg Linden, Amazon).

Google500 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 - Basecamp500 ms increase in speed on basecamp.com resulted in 5% improvement in conversion rate.

Page 60: Javascript MVC & Backbone Tips & Tricks

Importance of speed

Page 61: Javascript MVC & Backbone Tips & Tricks

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 !

Page 62: Javascript MVC & Backbone Tips & Tricks

Demo

Page 63: Javascript MVC & Backbone Tips & Tricks

Tip #3Nested Models

Page 64: Javascript MVC & Backbone Tips & Tricks

Question

Has many

Answers

Page 65: Javascript MVC & Backbone Tips & Tricks

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

Page 66: Javascript MVC & Backbone Tips & Tricks

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

});

Page 67: Javascript MVC & Backbone Tips & Tricks

Tip #4Custom ajax requests

Page 68: Javascript MVC & Backbone Tips & Tricks

Custom ajax request

• Sometimes RESTful methods are not enough

• Example: Sorting tasks in to-do list

Page 69: Javascript MVC & Backbone Tips & Tricks
Page 70: 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! ! }! });!}

Page 71: Javascript MVC & Backbone Tips & Tricks

Tip #5Send zombies to heaven

Page 72: Javascript MVC & Backbone Tips & Tricks

Zombies to heaven

• Its not enough to remove views from the DOM

• Events must be released so you don’t have zombies walking around

Page 73: Javascript MVC & Backbone Tips & Tricks

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

Page 74: Javascript MVC & Backbone Tips & Tricks

Tip #6Use the toolbox

Page 75: Javascript MVC & Backbone Tips & Tricks

Use the toolbox

• Underscore has some wonderful methods

• isFunction, isObject, isString, isNumber, isDate & more.

• Underscore: http://documentcloud.github.com/underscore

Page 76: Javascript MVC & Backbone Tips & Tricks

Underscore

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

Line 865 from the Backbone.js code.

Page 77: Javascript MVC & Backbone Tips & Tricks

Tip #7Test, test, test

Page 78: Javascript MVC & Backbone Tips & Tricks

Testing

• Recommend Jasmine for testing

• Recommend Sinon to fake the server

• jQuery-jasmine to test views

• Use setDomLibrary method to fake jQuery

Page 79: Javascript MVC & Backbone Tips & Tricks

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

});

Page 80: Javascript MVC & Backbone Tips & Tricks

Demo

Page 81: Javascript MVC & Backbone Tips & Tricks

Tip #8CoffeeScript

Page 82: Javascript MVC & Backbone Tips & Tricks

CoffeeScript

• Advanced programing language

• Compiles to javascript

• Same creator of Backbone and CoffeeScript

• Integrates well with Backbone

Page 83: Javascript MVC & Backbone Tips & Tricks

Coffee Script example

class TodoList extends Backbone.View

_.bindAll( this, 'render' )

render: => @$el.html( @template( @.model.toJSON() )) @

initialize: ->! super

Extending Backbone module

Double arrow to bind to the contextUse @ instead of thisLast line is the return value, returns this

Need to call super on parent constructors

Page 84: Javascript MVC & Backbone Tips & Tricks

Tip #9The Basics

Page 85: Javascript MVC & Backbone Tips & Tricks

The basics

• The basics still apply with MVC in place

• Minimize ajax requests

• Keep your views thin & models fat

• Understanding Javascript is the key

Page 86: Javascript MVC & Backbone Tips & Tricks

Tip #10Bonus

Page 87: Javascript MVC & Backbone Tips & Tricks

Bonus points

• Read the documentation

• Read the source code

• Just do it !

Page 88: Javascript MVC & Backbone Tips & Tricks

Tack så mycketHjörtur Elvar Hilmarsson

@hjortureh