Pre p.s.: I have made a gist for you with the code that I wrote below:
https://gist.github.com/2863979
I agree with the pub/sub ('Observer pattern') that is suggested by the other answers. I would however also use the power of Require.js along with Backbone.
Addy Osmani has written a few GREAT! resources about Javascript design patterns and about building Backbone applications:
http://addyosmani.com/resources/essentialjsdesignpatterns/book/
http://addyosmani.com/writing-modular-js/
http://addyosmani.github.com/backbone-fundamentals/
The cool thing about using AMD (implemented in Require.js) along with Backbone is that you solve a few problems that you'd normally have.
Normally you will define classes and store these in some sort of namespaced way, e.g.:
MyApp.controllers.Tasks = Backbone.Controller.extend({})
This is fine, as long as you define most things in one file, when you start adding more and more different files to the mix it gets less robust and you have to start paying attention to how you load in different files, controllers\Tasks.js
after models\Task.js
etc. You could of course compile all the files in proper order, etc, but it is far from perfect.
On top of this, the problem with the non AMD way is that you have to nest Views inside of each other more tightly. Lets say:
MyApp.classes.views.TaskList = Backbone.View.extend({
// do stuff
});
MyApp.views.App = Backbone.View.extend({
el: '#app',
initialize: function(){
_.bindAll(this, 'render');
this.task_list = new MyApp.classes.views.TaskList();
},
render: function(){
this.task_list.render();
}
});
window.app = new MyApp.views.App();
All good and well, but this can become a nightmare.
With AMD you can define a module and give it a few dependencies, if you are interested in how this works read the above links, but the above example would look like this:
// file: views/TaskList.js
define([], function(){
var TaskList = Backbone.View.extend({
//do stuff
});
return new TaskList();
});
// file: views/App.js
define(['views/TaskList'], function(TaskListView){
var App = Backbone.View.extend({
el: '#app',
initialize: function(){
_.bindAll(this, 'render');
},
render: function(){
TaskListView.render();
}
});
return new App();
});
// called in index.html
Require(['views/App'], function(AppView){
window.app = AppView;
});
Notice that in the case of views you'd return instances, I do this for collections too, but for models I'd return classes:
// file: models/Task.js
define([], function(){
var Task = Backbone.Model.extend({
//do stuff
});
return Task;
});
This may seem a bit much at first, and people may think 'wow this is overkill'. But the true power becomes clear when you have to use the same objects in many different modules, for example collections:
// file: models/Task.js
define([], function(){
var Task = Backbone.Model.extend({
//do stuff
});
return Task;
});
// file: collections/Tasks.js
define(['models/Task'], function(TaskModel){
var Tasks = Backbone.Collection.extend({
model: TaskModel
});
return new Tasks();
});
// file: views/TaskList.js
define(['collections/Tasks'], function(Tasks){
var TaskList = Backbone.View.extend({
render: function(){
_.each(Tasks.models, function(task, index){
// do something with each task
});
}
});
return new TaskList();
});
// file: views/statistics.js
define(['collections/Tasks'], function(Tasks){
var TaskStats = Backbone.View.extend({
el: document.createElement('div'),
// Note that you'd have this function in your collection normally (demo)
getStats: function(){
totals = {
all: Tasks.models.length
done: _.filter(Tasks, function(task){ return task.get('done'); });
};
return totals;
},
render: function(){
var stats = this.getStats();
// do something in a view with the stats.
}
});
return new TaskStats();
});
Note that the 'Tasks' object is exactly the same in both views, so the same models, state, etc. This is a lot nicer than having to create instances of the Tasks collection at one point and then reference it through the whole application all the time.
At least for me using Require.js with Backbone has taken away a gigantic piece of the puzzling with where to instantiate what. Using modules for this is very very helpful. I hope this is applicable to your question as well.
p.s. please also note that you'd include Backbone, Underscore and jQuery as modules to your app too, although you don't have to, you can just load them in using the normal script tags.