Skip to content

Instantly share code, notes, and snippets.

@liammclennan
Created June 7, 2012 06:33
Show Gist options
  • Star 13 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save liammclennan/2886952 to your computer and use it in GitHub Desktop.
Save liammclennan/2886952 to your computer and use it in GitHub Desktop.
Backbone.js Style / Patterns

Naming Rules

Use PascalCase for constructors, namespaces and modules:

var m = new Backbone.Model(); 

Prefix private properties with _

This is a convention to compensate for JavaScript's lack of private properties on objects. Being able to identify private methods is important because it tells us that we don't need to test those methods and that they will not be coupled to anything outside of the object.

var V = Backbone.View.extend({
  _toViewModel: function () {
  }
});

Event names are lower case and colon namespaced

thing.trigger('namespace:eventname', data);

Use a convention for event handler names

thing.on('namespace:eventname', onNamespaceEventname);

Group code into modules

Group related code into named modules, and clearly specify the dependencies between modules. Use a specification like CommonJS or AMD, and use a tool like stitch, require.js or something custom to manage them.

define('Views.Customise', ['Dep1' , 'Dep2'], function (dep1, dep2) {
  // module code here
  return someObjectContainingTheModulesPublicInterface;
});

Backbone Specific

Make implicit dependencies explicit

Use DBC style assertions to validate dependencies and fail fast. Enforce view and model requirements in their initializers.

Organise modules first by type, then by function

models/
  shopping.js
  admin.js
views/
  shopping.js
  admin.js

This minimizes coupling and maximizes cohesion because dependencies tend to go the same way (ie routers depend on models and views, views depend on models) and cohesion tends to be high inside of a module containing models for a certain feature, or views for a certain feature.

Backbone Views

All of a views data should be supplied to the constructor

This makes it easier to determine a views dependencies by inspection, and helps to make views testable. Enforce data requirements in the view's constructor to fail fast and expose bugs quickly and accurately.

Render to a constructor-supplied element or to a newly created element

A view should never insert itself into the dom. Most commonly a view should render to a new dom element that is not inserted into the document. If a view needs to modify the document then it should render to an element that is supplied to the constructor. This keeps views isolated and makes it possible to test them without modifying a document.

Render to a constructor-supplied element when the view needs to refresh in response to model changes

If a view refreshes when model changes are detected then it needs to be rendering to an element that is in the document. It should also replace the entire elements contents so that it can be run repeatedly.

Validate data just-in-time

If a view cannot be valid without certain data validate that data in the constructor. If a view cannot render without certain data, validate that data in the render method. etc.

Validate model content not type

There is little value in asserting that a view's model is of a certain type, because the model's type does not define its data. If a view depends on specific model data then it should validate that data.

Rendering 'leaf' views

A leaf view is a view that does not contain other views. Render with:

this.$el.html([content here]);

Rendering 'parent' views

A parent view is a view that contains other views. Render with:

this.$el.append(view1.render().el, view2.render().el, ...);

view.model and view models

A view's model property represents the data required by that view to do it's job. The model supplied to a template library for rendering represents that data required by the view. They are not the same. Use a function on the view to translate from the view's model to the viewmodel.

A view's model property does not have to be a Backbone model. It can contain all of the views dependencies. ie.

this.model =  { 
  userService: {},
  user: <Backbone.Model>,
  company: <Backbone.Model>
}

View side effects

A view's side effects (its consequence on the rest of the system) should occur via events. The benefits of this are:

  1. Views become easy to test
  2. The application becomes easy to reason about

A view with sneaky, hidden side effects

Backbone.View.extend({
  events: {
    'click button': function () {
      $.post('/documents', this.model.toJSON());  
    }
  }
});

The same view without sneaky, hidden side effects

Backbone.View.extend({
  events: {
    'click button': function () {
      eventAggregator.trigger('documents:save', this.model);
    }
  }
});

favour course-grained views

Fine-grained views are confusing. Start with course-grained views (a single view for the page) and only add sub-views when there is a clear and compelling benefit.

Backbone Models

Models represent behaviour - not data. The schema of a model's data can change.

High-level Architectural Patterns

Conrad Irwin has a nice description of a sensible architecture.

The key points are:

  1. Models don't depend on views, routers or operations.

  2. Views raise application events.

  3. Application events are handled by operations.

Screen Conductor

Screen conductors are responsible for controlling the activation and deactivation of screens.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment