Yesterday, I was able to get an add-appointment view into my Backbone.js calendar application. The add View is a thin wrapper around a jQuery UI dialog. It seems to work well (I did the same thing with my edit View).
The problem is that, after I create my View, I have to manually add a new appointment View:
window.AppointmentAddView = Backbone.View.extend({
create: function() {
var appointment = Appointments.create({ /* options here */ });
// TODO: convert to event listener....
var view = new AppointmentView({model: appointment});
view.render();
}
});What I would like to have occur is that the collection notices a new appointment and is responsible for creating a new View object. The collection is already responsible for doing this when existing appointments are fetched after page load: window.Appointments = new AppointmentList;
Appointments.bind('reset', function(appointments) {
appointments.each(function(appointment) {
var view = new AppointmentView({model: appointment});
view.render();
});
});So, when my appointment-add View calls Appointments.create(), this ought to trigger an "add" event on the collection. So, can I extract the appointment View creation call out of the appointment-add View's create() method and dump it into an event handler: Appointments.bind('add', function(appointment) {
console.log('add!!!!');
var view = new AppointmentView({model: appointment});
view.render();
});With that, I see some definite similarities between the Collection reset and add event handlers. I will worry about DRYing things up once I have it working. And I don't... my jasmine spec covering add-appointments is now failing:It takes me quite a while to track this down. The Firebug debugger does not work for me right now and the specs do not work in Chrome, so I reduced to
console.log() debugging. Eventually, I realize that my model is not passing success events back to the Collection. It sounds easy enough to find in retrospect, but I had 30+ console.log() statements strewn about my code and the Backbone.js library. Ultimately, I trace the issue down to my Model class. Since I am using a CouchDB data store, I need to pass document revisions along with the document. I accomplish this via an
If-Match header. It's all very slick: window.Appointment = Backbone.Model.extend({
urlRoot : '/appointments',
save: function() {
Backbone.Model.prototype.save.call(this, {
headers: {'If-Match': this.get("rev")}
});
},
// ...
});Except, I am missing something crucial to getting this to work. My save() method takes no arguments whereas the Backbone method that I am overriding takes two arguments. The second argument contains options, which include on-success and on-error handlers. Since I completely ignore them here, my Model's save() method has no way of calling them. Duh.The fix is easy enough:
save: function(attributes, options) {
attributes || (attributes = {});
attributes['headers'] = {'If-Match': this.get("rev")};
Backbone.Model.prototype.save.call(this, attributes, options});
},Instead of ignoring the options being passed in, I am now sure to pass them along to the save() call on the prototype's save() method. For good measure, I do the same for attributes while I am at it.With that, I have my tests passing again:
I am happy to stop here for the day. I consider myself lucky to have escaped that mess unscathed. The lesson learned from today: if you're going to override a method, be sure to accept the same arguments (and pass them along the original). Sadly, I should have already known that particular lesson. The other lesson: be grateful for solid tests. I shudder to think how long that would have taken me to track down without my Jasmine suite.
Day #136



0 comments:
Post a Comment