After refactoring the views in my appointment calendar Backbone.js application, I am ready to move onto to other topics. Except that I forgot a TODO that I left myself in the month View:
var CalendarMonth = Backbone.View.extend({
tagName: 'table',
initialize: function(options) {
this.date = options.date;
},
render: function() {
// TODO:
// $('span.year-and-month', 'h1').html(' (' + this.date + ')');
// ..
}
});Ah. My old template-based view was doing so much that I had not felt bad about adding a side-effect in there as well. This particular side-effect adds the date to the page's <h1> tag.I want a Backbone view that is dedicated to updating the page's title—a title view. If the page title has a
year-and-month span in it:<h1> Funky Calendar <span class="year-and-month"></span> </h1>Then I want my title view to populate said
<span> tag whenever the month changes. The appointment collection only holds appointments from the current month. It also exposes a
getDate() getter for the current month. I should be able to use this to update the title: var TitleView = Backbone.View.extend({
tagName: 'span',
initialize: function(options) {
$('span.year-and-month', 'h1').
replaceWith(this.el);
},
render: function() {
$(this.el).html(' (' + this.collection.getDate() + ') ');
}
});My main Application view is injected with the month appointments collection:appointments = new Collections.Appointments({date: year_and_month}),
application = new Views.Application({
collection: appointments,
el: root_el
});It can then inject the same collection into the title view when it creates it: var Application = Backbone.View.extend({
initialize: function(options) {
// ...
this.initialize_title();
},
// ...
initialize_title: function() {
new TitleView({collection: this.collection});
},
// ...
});But how to get that to change when the user navigates between months? When it was a side-effect of the calendar render(), that was easy. But now?The answer is to use events—this is Backbone after all. In the appointment collection, I trigger a custom event,
calendar:change:date, after successfully fetching a month's appointments: var Appointments = Backbone.Collection.extend({
model: Models.Appointment,
// ...
fetch: function(options) {
options || (options = {});
var data = (options.data || {});
options.data = {date: this.date};
var collection = this;
var success = options.success;
options.success = function (resp, status, xhr) {
collection.trigger('calendar:change:date');
if (success) success(collection, resp);
};
return Backbone.Collection.prototype.fetch.call(this, options);
},
// ...
});By triggering the event in the success callback of my fetch() class, I ensure that the date in the calendar's title will only change if the next month is successfully obtained. The dance of saving existing
success callbacks might be unnecessary future-proofing on my part. I have gotten in the habit of doing that, especially in my models where is makes more sense. I am not currently calling this collection's fetch() with any options, so ensuring that a success callback is retained is definitely overkill. If I ever did want to pass in a success callback, I would likely check to ensure that fetch() was not overridden and, if it was, that it supported the success callback. Still, this feels like a good habit to have.The event itself,
calendar:change:date is chosen to avoid conflicting with "real" events. If the model's date is updated in the application, the model automatically emits a change:date event. To prevent confusion with that "real" event, I opt for a clearer namespaced event. There is no need to update the title of the page if the user updates an appointment! With my collection successfully generating the event, I can now bind to that event in my view:
var TitleView = Backbone.View.extend({
tagName: 'span',
initialize: function(options) {
options.collection.bind('calendar:change:date', this.render, this);
$('span.year-and-month', 'h1').
replaceWith(this.el);
},
render: function() {
$(this.el).html(' (' + this.collection.getDate() + ') ');
}
});With that, I have dates back in the title of funky calendar:For good measure, I also tie my calendar navigation view to listen for this event (so that next and previous links can use the proper months).
I really felt bad about updating the title as a side-effect of rendering the new calendar month. This is a bit more code, but it feels much cleaner—mainly because it only has a single responsibility. In addition to clearer intent (what could be clearer than "TitleView"?), I am secure knowing that, should I need to make this more sophisticated in the future, I know where changes need to go.
Day #189

No comments:
Post a Comment