Tuesday, August 30, 2011

Backbone Removing from the UI Too!

‹prev | My Chain | next›

Last night, I figured out how to get Backbone.js to delete calendar events from my backend CouchDB store. In the end it required very little setup and code, which gives it a good feel—at least to this beginner. One deficiency of my approach, however, was that the calendar events were not immediately removed from the page. Tonight I hope to rectify this.

Even after I told Backbone to the remove the "foo" event from the calendar and verified that it was, in fact, removed from the CouchDB store, it still remained on the web page for Sep 1:
After manually reloading the page, the bogus calendar is no more, but there has to be a better way. Right?

After digging through the documentation a bit, I think that the solution is binding Backbone events. Specifically, binding a calendar event's "destroy" event to the view's remove() sounds about right. This most certainly not the same thing as telling the model about the view and how to destroy it. That would constitute an insane degree of couple between the view and model.

Rather, the model is always emitting events as it changes. Why not teach the view to listen for one such event (destroy, for instance) and to act accordingly. It might seem that the view now has too much knowledge of the model and that coupling is taking place in spite of my desires. But this not the case. Something needs to bind the event emitter to the listener, but neither the listener or emitter care to whom the event is being sent or from where the event came. I could just as easily trigger a manual destroy event and see the same view behavior.

Anyhow, to actually bind the two, this ought to work:
    window.EventView = Backbone.View.extend({
// ...
      initialize: function(options) {
        options.model.bind('destroy', this.remove, this);
      },
// ...
    });
When a view object is created, one of the attributes sent is the model being represented by the view:
view = new EventView({model: calendar_event, el: el});
So the bind() call adds a listener to the model's event emitting function—but only for 'destroy'. Models can be instructed to emit arbitrary event strings, but Backbone includes some built-in events like 'change' and 'destroy'. It is the latter in which I am interested today.

On reception of such an event, my View object should call its own remove() method (built into Backbone.js). Theoretically, this should remove the calendar event from the calendar.

Theory is great, but in practice, this winds up removing the entire Sep 1 day from the calendar:
Yikes!

This occurs because the element to which I tie my views is the calendar day:

          var el = $('#' + event.get("startDate")),
              view = new EventView({model: event, el: el});
In my view class, I then append calendar events to this element via a specialized render() method:
    window.EventView = Backbone.View.extend({
//...
      render: function() {
        $(this.el).append(this.template(this.model.toJSON()));
        return this;
      }
    });
Since I am already off the rails for my render() method, I need to do the same for my remove() method. Fortunately, Backbone does not make it too hard to concoct such a thing:
    window.EventView = Backbone.View.extend({
//...
      remove: function() {
        $(this.el).find('.event').remove();
      }
    });
Now, when I delete the offending calendar event (still in the Javascript console), the event is removed both from the backend and from the calendar UI:
Best of all: no lost day.

I worry that the need for customized render() and remove() methods are Backbone's vinegar, telling me that I should refactor things a tad. I will circle back on that question in a day or two. But up tomorrow, I will try adding a UI element to delete calendar events so that I do not have to drop down to the Javascript console every time I spy an appointment I'd rather not keep.


Day #129

No comments:

Post a Comment