var m = new Backbone.Model();
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 () {
}
});
thing.trigger('namespace:eventname', data);
thing.on('namespace:eventname', onNamespaceEventname);
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;
});
Use DBC style assertions to validate dependencies and fail fast. Enforce view and model requirements in their initializers.
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.
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.
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.
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.
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.
A leaf view is a view that does not contain other views. Render with:
this.$el.html([content here]);
A parent view is a view that contains other views. Render with:
this.$el.append(view1.render().el, view2.render().el, ...);
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>
}
A view's side effects (its consequence on the rest of the system) should occur via events. The benefits of this are:
- Views become easy to test
- 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);
}
}
});
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.
Models represent behaviour - not data. The schema of a model's data can change.
Conrad Irwin has a nice description of a sensible architecture.
The key points are:
-
Models don't depend on views, routers or operations.
-
Views raise application events.
-
Application events are handled by operations.
Screen conductors are responsible for controlling the activation and deactivation of screens.