The short version : I have a Backbone model which has as one of its attributes, a second Backbone model. A function in the first model changes the state of the second model, but my view, which is listening for changes to the first model, does not seem to be picking up the state of the second model, despite any amount of logging that suggests otherwise (I’ve been logging this at various points to confirm scope, etc.). How can I fix this?
The long version : I have a Backbone model Course, which represents an academic course, and a model NameList, which represents the list of students enrolled in a Course. A Course “has-a” NameList. A NameList is backed by a single text file on the server.
I want to have a function in the Course model called importNameList which creates a new NameList model and causes that NameList model to go and fetch its data from the backend. Since my view, CourseView is listening for changes to the Course model, and a Course model has-a NameList, it seems like this should update the view accordingly. My problem is that it doesn’t.
What I want to do
var course = new Course();
var courseView = new CourseView({model : course});
courseView.model.importNameList('students.txt'); // Will cause view to re-render
What I have to do
var course = new Course(); // same
var courseView = new CourseView({model : course}); // same
courseView.model.importNameList('students.txt'); // same
courseView.render(); // Argh, manually calling this is ugly.
Here is my code with logging statements. The models extend Backbone.DeepModel only because I thought it might solve my problem, but it didn’t.
Console output
[console] var course = new Course();
[console] var courseView = new CourseView({model : course});
[console] course.importNameList('students.txt');
Course >>> importNameList
NameList >>> initialize()
NameList >>> fetch()
GET http://localhost/files/students.txt 200 OK 16ms
CourseView >>> render() // Render starts before fetch completes
CourseView <<< render() // Render finishes before fetch completes
Course <<< importNameList
NameList <<< fetch()
[console] courseView.render();
CourseView >>> render()
alice
bob
charlie
dan
erica
fred
george
CourseView <<< render()
Course.js
var Course = Backbone.DeepModel.extend({
defaults : {
nameList : new NameList()
},
initialize: function(options) {
if (options && options.nameList) {
this.set({nameList : options.nameList});
}
},
importNameList : function(fileName) {
console.log("Course >>> importNameList");
var nameList = new NameList({fileName : fileName});
this.set({nameList : nameList});
console.log("Course <<< importNameList");
}
});
NameList.js
var NameList = Backbone.DeepModel.extend({
defaults : {
fileName : 'new.txt',
names : []
},
initialize: function(options) {
console.log("NameList >>> initialize()");
var model = this;
if (options && options.fileName) {
model.set({fileName : options.fileName});
}
model.fetch();
},
fetch : function() {
console.log("NameList >>> fetch()");
var model = this;
$.ajax({
url : '/files/' + model.get('fileName'),
success : function(response) {
model.set({names : response.split('\n')});
console.log("NameList <<< fetch()");
}
});
}
});
CourseView.js
var CourseView = Backbone.View.extend({
initialize : function(options) {
var view = this;
if (options && options.model) {
view.model = options.model;
} else {
view.model = new Course();
}
view.model.on('change', function() {
view.render();
});
},
render : function() {
console.log("CourseView >>> render()");
var names = this.model.get('nameList').get('names');
for (var i = 0; i < names.length; i++) {
console.log(names[i]);
}
console.log("CourseView <<< render()");
return this;
}
});
I ended up doing the following.
CourseView.js:
Within Course.js:
Within NameList.js: