• Save
Backbone.js: Run your Application Inside The Browser
Upcoming SlideShare
Loading in...5

Backbone.js: Run your Application Inside The Browser



As presented at No Fluff Just Stuff San Antonio, April 14th 2012.

As presented at No Fluff Just Stuff San Antonio, April 14th 2012.



Total Views
Views on SlideShare
Embed Views



2 Embeds 2

http://nodeslide.herokuapp.com 1
http://localhost 1



Upload Details

Uploaded via as Adobe PDF

Usage Rights

CC Attribution-NoDerivs LicenseCC Attribution-NoDerivs License

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
Post Comment
Edit your comment

Backbone.js: Run your Application Inside The Browser Backbone.js: Run your Application Inside The Browser Presentation Transcript

  • Backbone.js: Run yourApplication Inside TheBrowserHoward M. Lewis ShipTWD Consultinghlship@gmail.com@hlship © 2012 Howard M. Lewis Ship
  • Are We Doing it Wrong? GET / Server <html><head>… POST /addCustomer Server <html><head>…
  • Page Refresh vs. User Interaction
  • Challenges Statefulness Browser Vs. Server HTML POST key values vs. heirarchical data Avoid full page updates ❝Can it be as easy to use as an iPad app?❞
  • Servers Role Load the application: Static HTML, images, stylesheets JavaScript Source/Sink of data JSON is the easy way! Ties in with NoSQL In-place page updates
  • Whats new here? Isnt this just Ajax? Core structure vs. optional enhancements Isnt this just jQuery? jQuery is great w/ elements, events, effects More structure is needed
  • http://documentcloud.github.com/backbone/
  • Dependencies Ba ck 0.9 bon .2 e Backbone jQuery (or Zepto) Underscore JSON2 Not needed in modern browsers
  • Structure XHRServer Sync Collection 0..n Model Events DOM Events Model View Property Updates <div> <input ... DOM Updates
  • Underscore.js
  • Caution: CoffeeScript
  • Caution: CoffeeScript CoffeeScript ➠ ❝… a little language that compiles into JavaScript❞ Concise and readable Optional parenthesis Implicit returns Concise function definitions Fits nicely on slides!
  • CoffeeScript: Invoking Functions$(".x-cancel").tooltip "hide" $(".x-cancel").tooltip("hide")collection.add new Quiz(originalModel), at: 0 collection.add(new Quiz(originalModel), { at: 0 });
  • CoffeeScript: Defining Functions(x,y) -> x * y function (x, y) { return x * y; }isBlank = (str) -> function isBlank(str) { _.isNull(str) or return _.isNull(str) || _.isUndefined(str) or _.isUndefined(str) || str.trim() is "" str.trim() === ""; }@collection.each (round) -> this.collection.each(function(round) { round.set "index", index++ return round.set("index", index++); });
  • http://jsconsole.com
  • http://jsconsole.com:load underscore:load coffeescript:load https://raw.github.com/⏎documentcloud/backbone/0.9.2/backbone.js
  • Backbone Events
  • Backbone.EventsMixin to be added to any objectAdd/remove listenersTrigger eventsAlternative to DOM events
  • on(eventName, listener, [context])dispatcher = _.extend {}, Backbone.Events➠ …dispatcher.on "gnip", (id) -> console.log "gnip: #{id}"➠ …dispatcher.trigger "gnip", 1234"gnip: 1234"➠ …dispatcher.on "all", (eventName, args...) -> console.log "Received #{eventName}"➠ …dispatcher.trigger "fnord""Received fnord"➠ …dispatcher.trigger "gnip", 54321"gnip: 54321""Received gnip"➠ …
  • context and this this is a side-effect of method invocation: anObject.aMethod() ➠ var fn = anObject["aMethod"]; fn.call(anObject) Sets this for new stack frame DOM and JQuery manipulate this ➠ Usually the DOM element that triggered event this not relevant to HOFs Functions, parameters, local scope
  • Backbone.Eventson(), off(), trigger() return thistrigger()➠ arguments passed to listenersEvent name "all"➠ Listener sees all events, first parameter is event name
  • off([event], [callback], [context]) Removes matching listeners event ➠ event to remove, or null for all callback ➠ callback function to match context ➠ context to match off() ➠ remove all listeners off("gnip") ➠ remove all gnip listeners
  • Backbone Models
  • Backbone.ModelBase class via extend()Triggers events on property changesValidates changes for consistencyCRUD via Backbone.Sync
  • Quizzical Empire
  • Paperwork
  • HTTP / XHRBackbone ExpressUnderscore Jade jQuery Mongoose Mustache connect-assets Bootstrap Underscore
  • Backbone.Model id idAttribute cid extend() get() set() escape() fetch() save() destroy() defaults() Quiz Round QuestionenableSave() : boolean enableSave() : boolean enableSave() : boolean _id title kind text location title answer created questions value rounds
  • Models and Attributes Models contain attributes … data from server Nothing is pre-defined One attribute uniquely identifies the Model idAttribute property defines this idAttribute is "_id" for MongoDB id property shadows the id attribute cid property ➠ temporary unique id before saved to server
  • extend(properties, [class props]) Creates a new type of model Properties used to define / override new methods
  • extend(properties, [class props]) Model = Backbone.Model Quiz = Model.extend idAttribute: "_id" urlRoot: "/api/quiz" parse: (response) -> response.rounds = _(response.rounds).map (raw) -> new Round raw, { parse: true } return response default: -> rounds: [] enableSave: -> (not isBlank @get "title") and _(@get "rounds").all (round) -> round.enableSave()
  • Creating an InstanceQuiz = Backbone.Model.extend initialize: -> console.log "Quiz initialized with", @attributes➠ …new Quiz title: "New Quiz" location: "Portland, OR""Quiz initialized with"{"location": "Portland, OR", "title": "New Quiz"}➠ …
  • Simple Defaults Quiz = Backbone.Model.extend initialize: -> console.log "Quiz initialized with", @attributes defaults: location: "Default Location" ➠ … new Quiz title: "Simple Defaults Quiz" "Quiz initialized with" {"location": "Default Location", "title": "Simple Defaults Quiz"} ➠ …
  • Computed Defaults Quiz = Backbone.Model.extend initialize: -> console.log "Quiz initialized with", @attributes defaults: -> title: "New Quiz at #{new Date().toDateString()}" ➠ … new Quiz "Quiz initialized with" {"title": "New Quiz at Mon Apr 09 2012"} ➠ …
  • Reading Attributesquiz = new Quiz title: "<Script>ing Attack""Quiz initialized with" jsconsole bug!{"title": "➠ …quiz.get "title"➠ "<Script>ing Attack"quiz.escape "title"➠ "&lt;Script&gt;ing Attack"
  • Changing Attributesquiz.set "title", "New Title" attribute key and value➠ …quiz.attributes➠ {"title": "New Title"}quiz.set location: "New Location" keys and values➠ …quiz.attributes➠ {"location": "New Location", "title":"New Title"}
  • Attribute Change Notification quiz.on "all", (name) -> console.log "Event: #{name}" ➠ … quiz.set title: "Updated Title" "Event: change:title" "Event: change" quiz.set { location: "Updated Location"}, silent:true ➠ … quiz.change() ➠ … "Event: change:location" "Event: change" ➠ …
  • ValidationQuiz = Backbone.Model.extend validate: (attributes) -> return "Title may not be blank" if isBlank attributes.title➠ …quiz = new Quiz➠ …quiz.isValid()➠ falsequiz.set title:null➠ falsequiz.set title:"Valid Title"➠ …quiz.isValid()➠ true NOT recommended for user input validation
  • Backbone Views
  • Backbone ViewsAre Controllers not ViewsCreate the UI for the element Handle model events, update DOM Handle DOM events, update model Trigger own events
  • View.extend(properties, [class properties]) Properties are added to Standard properties: the View model Defaults: collection tagName ➠ "div" el id attributes className tagName
  • View and Element @el is CoffeeScript for this.el @el ➠ the element for this View passed as an option to the constructor or created from tagName, className, id, and attributes options Initially detached @render or @initialize inserts it into DOM
  • Constructor new MyView([options]) Merges options into @options Creates @el if not passed as an option Passes all constructor arguments to @initialize Delegates events to @el
  • Event Delegation Automatically dispatch events from inside @el QuizTableRowView = View.extend tagName: "tr" initialize: -> … event name and events: "click .x-delete": "deleteDialog" selector "click .x-edit": "editQuiz" render: -> … method name deleteDialog: -> … editQuiz: -> … .x-verb: Personal style
  • @render vs. @initialize() Default @render implementation is: return this Goal: minimize DOM reflow/recalc Sub-views attach their @el inside containers detached @el Top-most @el attached once Also OK to attach @el inside @initialize
  • Uh … attach? QuizEditorView = FormView.extend className: "tab-pane" initialize: -> … Text @$el.attr("id", tabId) .html(readTemplate "QuizEditorView") .appendTo("#top-level-tabs > .tab-content") … Detached @el attached to DOM
  • Elements and jQuery @$el ➠ jQuery reference to the element ➠ @$el.html "<div>This Views Content</div>" $ ➠ Equivalent to @$el.find setElement(element, delegate) Sets views @el Undelegates events from old @el Delegates events to new @el
  • Client-Side Templating id aligns with Viewlayout.jadescript#ConfirmDialog(type="text/template") .modal-header a.close(data-dismiss="modal") &times; h3 {{title}} .modal-body {{{body}}} .modal-footer button.btn.btn-danger.x-confirm(data-dismiss="modal") {{label}} button.btn(data-dismiss="modal") CancelRendered HTML<script id="ConfirmDialog" type="text/template"><div class="modal-header"><a class="close" … </script>
  • Mustache readTemplate = (scriptId) -> $("##{scriptId}").html() fromMustacheTemplate = (scriptId, attributes) -> Mustache.render readTemplate(scriptId), attributesMustache.render(string, attributes) Parses the string {{name}} inserts escaped attribute {{{name}}} inserts un-escaped attribute Result ready to insert into DOM
  • Putting It All Together – Reusable Confirmation Dialog
  • ConfirmDialog = View.extend className: "modal fade in" initialize: -> @$el.html fromMustacheTemplate "ConfirmDialog", title: @options.title body: @options.body label: @options.label or "Confirm" @$(".x-confirm").addClass @options.buttonClass or "btn-primary" $("body").append(@$el) @$el.modal().on "hidden", => @remove() doConfirm: -> @trigger "confirm" events: "click .x-confirm": "doConfirm"
  • Using ConfirmDialog deleteDialog: -> title = @model.escape "title" dialog = new ConfirmDialog title: "Really delete Quiz?" body: "<p>Deletion of quiz <strong>#{title}</strong> is immediate and can not be undone.</p>" label: "Delete Quiz" buttonClass: "btn-danger" dialog.on "confirm", => @model.destroy() If the user clicks Delete Quiz, then destroy model
  • ConfirmDialog = View.extend className: "modal fade in" initialize: -> @$el.html fromMustacheTemplate "ConfirmDialog", title: @options.title body: @options.body label: @options.label or "Confirm" @$(".x-confirm").addClass @options.buttonClass or "btn-primary" $("body").append(@$el) Update body of @el from template @$el.modal().on "hidden", => Modify the class of the button @remove() doConfirm: -> @trigger "confirm" events: "click .x-confirm": "doConfirm"
  • ConfirmDialog = View.extend className: "modal fade in" initialize: -> @$el.html fromMustacheTemplate "ConfirmDialog", title: @options.title body: @options.body label: @options.label or "Confirm" @$(".x-confirm").addClass @options.buttonClass or "btn-primary" $("body").append(@$el) Insert elements into the DOM @$el.modal().on "hidden", => @remove() doConfirm: -> @trigger "confirm" events: "click .x-confirm": "doConfirm"
  • ConfirmDialog = View.extend className: "modal fade in" initialize: -> @$el.html fromMustacheTemplate "ConfirmDialog", title: @options.title body: @options.body label: @options.label or "Confirm" @$(".x-confirm").addClass @options.buttonClass or "btn-primary" $("body").append(@$el) @$el.modal().on "hidden", => @remove() Remove the element from the DOM doConfirm: -> after hide animation completes @trigger "confirm" events: "click .x-confirm": "doConfirm"
  • ConfirmDialog = View.extend className: "modal fade in" initialize: -> @$el.html fromMustacheTemplate "ConfirmDialog", title: @options.title body: @options.body label: @options.label or "Confirm" @$(".x-confirm").addClass @options.buttonClass or "btn-primary" $("body").append(@$el) @$el.modal().on "hidden", => @remove() doConfirm: -> Delegate a listener to the main button @trigger "confirm" events: "click .x-confirm": "doConfirm"
  • Form Views
  • QuizFieldsEditorView QuizFieldsEditorView = FormView.extend initialize: -> @$el.html readTemplate "QuizFieldsEditorView" @linkField name for name in ["title", "location"] script#QuizFieldsEditorView(type="text/template") .control-group.x-title label Quiz Title .controls input.span4(type="text", required) span.help-inline Title or theme for the Quiz .control-group.x-location label Location input.span4(type="text") span.help-inline Location where Quiz will take place
  • FormView FormView = View.extend linkField: (name, selector = ".x-#{name}") -> $field = @$("#{selector} input") $field.val @model.get(name) $field.on "change", (event) => newValue = event.target.value @model.set name, newValue linkElement: (name, selector = ".x-#{name}", defaultText) -> element = @$(selector) update = => element.html (@model.escape name) or defaultText update() @model.on "change:#{name}", update
  • Backbone Collections
  • Collection Contains Models Has its own URL for queries & updates May represent a subset of the data Fires own events Forwards events from Models Can create new Model instances
  • Collection Access Implements Underscore collection methods (pluck, map, etc.) get(id) ➠ Get by unique id getByCid(cid) ➠ Get by temporary client id at(index) ➠ Get by index in collection push(model) ➠ Add to end of collection unshift(model) ➠ Add to start of collection pop() ➠ Remove and return last model shift() ➠ Remove and return first model length ➠ Count of models in collection
  • Collection Events add ➠ Model added to Collection remove ➠ Model removed from Collection reset ➠ Collections entire contents updated change ➠ Models attributes have changed destroy ➠ Model is destroyed (deleted)
  • QuizTableRowView QuizTableView
  • QuizListQuizList = Collection.extend model: Quiz Server returns just _id, title, location, url: "/api/quizzes" created in descending order
  • QuizTableView Alert visible when the QuizList is empty.alert strong. No Quizzes have been created yet ... feel free to start creating some!.limit-height table.table.table-bordered.table-striped.table-condensed thead tr th Title th.span3 Location th.span3 Created th.span3 tbody QuizTableRowView added as children.well button.btn.btn-primary.x-create-new Create New Quiz &nbsp; button.btn.x-create-test-data(data-loading-text="Loading ..."). Create Test Quizzes
  • QuizTableViewQuizTableView = View.extend initialize: -> @quizzes = new QuizList @$el.html readTemplate "QuizTableView" @$(".alert, table").hide() @quizzes.on "reset", @addAll, this @quizzes.on "add", @addOne, this @quizzes.on "destroy", @render, this @quizzes.fetch() @$(".x-create-test-data").popover title: "For Testing" content: "Creates many Quizzes with random text, for testing purposes. This will be removed in the final application." …
  • QuizTableView.render() render: -> if @quizzes.length is 0 @$(".alert").show() @$("table").hide() else @$(".alert").hide() @$("table").show() this
  • QuizTableView – Adding Models @quizzes.on "reset", @addAll, this @quizzes.on "add", @addOne, thisaddOne: (quiz, collection, options) -> view = new QuizTableRowView model: quiz collection: @quizzes # Special case for inserting at index 0, which happens when a new quiz # is added in the UI. fname = if options? and options.index is 0 then "prepend" else "append" @$("tbody")[fname] view.render().eladdAll: (quizzes) -> @$("tbody").empty() quizzes.each (quiz) => @addOne quiz @render()
  • QuizTableRowView view = new QuizTableRowView model: quiz collection: @quizzes QuizTableRowView = View.extend tagName: "tr" initialize: -> @model.on "change", @render, this @model.on "destroy", @remove, this @template = readTemplate "QuizTableRowView" render: -> @$el.html Mustache.render @template, title: @model.escape "title" or "<em>No Title</em>" location: @model.escape "location" created: @model.get "created" …
  • Dealing With Relationships
  • QuizenableSave() : boolean _id title location created 0..n rounds Round enableSave() : boolean kind title 0..n questions Question enableSave() : boolean text answer value
  • ❝Backbone doesnt include direct support for nested models and collections or "has many" associations because there are a numberof good patterns for modeling structured data on the client side, and Backbone should provide the foundation for implementing any of them.❞
  • Quiz inside MongoDB { "title" : "NFJS", "location" : "San Antonio", "_id" : ObjectId("4f835669c34c2a9c6f000003"), "rounds" : [ # { "kind" : "normal", # "title" : "Server-Side Java", # "_id" : ObjectId("4f835669c34c2a9c6f000004"), # "questions" : [ ] } ], "created" : ISODate("2012-04-09T21:36:41.726Z") }
  • Quiz.parse() Quiz = Model.extend idAttribute: "_id" urlRoot: "/api/quiz" parse: (response) -> response.rounds = _(response.rounds).map (raw) -> new Round raw, { parse: true } return response default: -> rounds: [] # of Round enableSave: -> (not isBlank @get "title") and _(@get "rounds").all (round) -> round.enableSave()
  • Round.parse() Round = Model.extend default: -> questions: [] # of Question parse: (response) -> response.questions = _(response.questions).map (raw) -> new Question raw, { parse: true } return response enableSave: -> return false if isBlank @get "title" questions = @get "questions" _.all questions, (q) -> q.enableSave()
  • Managing RelationshipsQuizRoundsEditorView = View.extend initialize: -> Build collection from simple array … @collection = new RoundCollection @model.get "rounds" @collection.on "all", => @model.set "rounds", @collection.toArray() @model.trigger "childChange" Update array from collection on any change RoundCollection = Collection.extend model: Round
  • Wrap Up
  • BackboneClient-centered design
  • Not Covered Lots! Details of Sync Router ➠ encode state into URL Server-side details Lots of libraries on top of Backbone
  • http://knockoutjs.com/
  • http://spinejs.com/
  • http://howardlewisship.com
  • Q&A