Skip to content
Ludvig edited this page Mar 25, 2014 · 5 revisions

General guidelines

We can come a long way by just following these basic rules:

  • Only use global jQuery selectors in the router!
  • Try to always use the default backbone variable names (@model, @collection). The @model in Views.ProgramShow is obviously a program model, and @collection a list of program models.
  • Always try to fetch from the router. This will clean up the views alot and make them alot easier to spec.
  • The data should only exist in one place (the models in the collections).
  • Never save any state in the DOM (save it in the models).
  • We should never have to render any hidden model attributes to the DOM (Ex: data-program-id="1")
  • Try to never update anything in the DOM with jQuery. Only render other views .el with .html(). (One exception to this is when we need to update an attribute to the current views $el in the render function)

Router

  • Handles fetching.
initialize: (@options) ->
  @collection = new Collections.Programs()
  @collection.fetch()
  • Handles routing (fragment paths) with fallback to default action. Example: http://www.url.com/#programs
routes:
  "program/:id" : "renderProgram"
  "programs"    : "renderPrograms"
  ".*"          : "renderPrograms"
  • Renders our views.
  • Handles scrolling to a selected part of the page.
renderPrograms: =>
  view = new Views.ProgramsIndex(collection: @collection)  
  if $('#program-index').length == 0
    $('#content').html(view.render().el)
    $('#content').scrollTop(0)
  • Nested routes can be handled using Backbone.SubRoute. Example: http://www.url.com/#program/1/announcements
class Routers.ProgramAnnouncements extends Backbone.SubRoute

routes:
  "announcements.*": "index"

Collection:

  • Handle the fetching of multiple models from the server. Setup url or urlRoot then fetch.
class Collections.Programs extends Collections.Base
  model: Models.Program
  urlRoot: '/api/programs'
  • This is an example how to fetch a collection with a custom url:
class Collections.Announcements extends Collections.Base
  
  url: =>
    "/api/programs/#{programId}/announcements"
@collection = new Collections.Announcements()
@collection.programId = 1
@collection.fetch()
  • To have a selected model/multiple models in a collection, use the method setSelected(model) function. The best place to do this is inside the render function in the view we are rendering. Here in the show view (one program selected):
render: ->
  @collection.setSelected(@model)

And the index view (no program selected):

render: ->
  @collection.setSelected(null)

Model:

  • Handle the fetching of a single model from the server. The same way the collection does this. The difference is that we shouldn't have to use variables on the model when we have a custom url, the model should always have the needed attribute. Ex:
class Models.Announcement extends Models.Base
  
  url: =>
    "/api/programs/#{@get('program_id')}/announcements"
  • Whitelist all attributes we want to be able to save to the server.
whitelist: ["name", "internal_name", "site_id", "locale", "target_description", "introduction", "display_mode_class", "timezone"]
  • Try to avoid defaults: values when we always need fetch them from the server anyways. This will prevent strange errors in both production code and spec code.

Views:

  • The view should always use this context for jQuery selectors.
@$('.placeholder')
  • And @$el for the current element.
@$el.html('')
  • Always return this explicity from the render function, for consistency.
render: ->
  return this
  • In the beginning we always serialized our models to plain javascript objects before passing them to our templates. (Using .attributes for models and .toAttributes() on collections). This is not necessary. Using the backbone model in the template is completely fine.
render: ->
  @$el.html(@template(model: @model))
  return this

Specs

  • Remember to always check the length of a jQuery selector. $('selector-that-doesnt-exist').to.equal.true will always equal true! (Not what we want)
expect($('selector-that-doesnt-exist').length).to.equal.true
  • View specs should only spec that specific view.
it "displays a name", ->
  @model = new Models.Program(name: "name")
  @view = new Views.ProgramShow(model: @model) 
  expect(@view.find("name-selector").text()).to.equal("name")
  • Integration specs should go through the router

Templates:

  • Currently we are using the javascript template language, EJS in admin, and we use the coffeescript template language ECO in site. We should probably switch to use ECO admin too.

  • Never use data-attributes with the model data in the DOM.

Application structure

  • We have a similar folder setup in both our projects. In admin_core we have one big backbone application. With the folder structure:
.
├── helpers
├── lib
├── mixins
├── models
├── views
├── routers
├── templates
└── promote_admin.js.coffee **<- requiring all folders**
  • In site we have multiple smaller backbone applications, each with their own application structure:
.
├── coach
│   ├── models
│   ├── routers
│   ├── views
│   ├── templates
│   └── setup.js.coffee **<- requiring all folders inside coach**
├── instructor
│   └── Same folder structure ....