Yesterday, I got started looking at Underscore.js templating for my Backbone.js calendar application. It was trivial to replace the default ERB style
<%= ... %>
variable interpolation with mustache style {{ ... }}
structures.I also replaced
<% ... %>
code evaluation structures with the un-mustache like {[ ... ]}
. I hope to use that to replace the legacy calendar "view" with which I have been stuck. The appointments, add/update forms, and month navigation controls all come from Backbone code in my app:The calendar itself comes from a pre-Backbone static HTML:
<h1>Funky Calendar<span class="year-and-month"></span></h1> <table id="calendar"> <thead> <tr> <th>S</th> <th>M</th> <th>T</th> <th>W</th> <th>T</th> <th>F</th> <th>S</th> </tr> </thead> <tr id="week0"> <td class="sunday"></td> <td class="monday"></td> <td class="tuesday"></td> <td class="wednesday"></td> <td class="thursday"></td> <td class="friday"></td> <td class="saturday"></td> </tr> <!-- .... --> <tr id="week5"> <td class="sunday"></td> <td class="monday"></td> <td class="tuesday"></td> <td class="wednesday"></td> <td class="thursday"></td> <td class="friday"></td> <td class="saturday"></td> </tr> </table>I probably need to keep the header row containing the days of the week, but the 6 table rows containing the actual calendar days can get DRYed up. Last night, I converted this to an underscore template (making use of the un-mustache
{[ ... ]}
construct):<script type="text/template" id="calendar-template"> <table> <thead> <tr> <th>S</th> <th>M</th> <th>T</th> <th>W</th> <th>T</th> <th>F</th> <th>S</th> </tr> </thead> <tbody> {[ _([0,1,2,3,4,5]).each(function(i) { ]} <tr class="week{{ i }}"> <td class="sunday"></td> <td class="monday"></td> <td class="tuesday"></td> <td class="wednesday"></td> <td class="thursday"></td> <td class="friday"></td> <td class="saturday"></td> </tr> {[ }); ]} </tbody> </table> </script>Tonight I would like to create a Backbone view to make use of this template.
So, I replace all of that static HTML with:
<h1> Funky Calendar <span class="year-and-month"></span> </h1> <div id="calendar"></div>To populate the now empty
<div id="calendar">
tag, I start with the Router which now needs to tell the calendar view to render itself for the correct date: var Routes = Backbone.Router.extend({
routes: {
// ...
"month/:date": "setMonth"
},
// ...
setMonth: function(date) {
console.log("[setMonth] %s", date);
Views.Calendar.setDate(date);
appointments.setDate(date);
}
});
Here I am going to call the Views.Calendar
as a singleton view (one of the recipes from Recipes with Backbone). I define that view simply as: var Calendar = new (Backbone.View.extend({
el: $('#calendar'),
template: _.template($('#calendar-template').html()),
setDate: function(date) {
this.date = date;
this.render();
},
render: function() {
$('span.year-and-month', 'h1').html(this.date);
$(this.el).html(this.template({
date: this.date
}));
return this;
}
}));
The setDate()
method is a simple wrapper to set the date instance variable and to tell the view to render itself. I explicitly tell the view to attached itself to the '#calendar'
element. At some point I should make that dynamic so that it can configured, but that will do for now. With that, I have my empty calendar:To get the dates added to the calendar, as well as to assign the ISO 8601 date as the ID for each of the cells in the calendar, I pull in some of the old app code:
var Calendar = new (Backbone.View.extend({
// ...
render: function() {
// ...
$(this.el).html(this.template({ date: this.date }));
this.addDates();
this.hideEmptyWeeks();
return this;
},
addDates: function() {
var year = parseInt(this.date.split(/\\D/)[0], 10),
month = parseInt(this.date.split(/\\D/)[1], 10) - 1,
firstOfTheMonth = new Date(year, month, 1),
firstSunday = new Date(firstOfTheMonth.getTime() -
firstOfTheMonth.getDay()*24*60*60*1000);
var date = firstSunday;
_([0,1,2,3,4,5]).each(function(week) {
var week_el = $('.week' + week, this.el),
day_elements = week_el.find('td');
_([0,1,2,3,4,5,6]).each(function(day) {
var day_element = day_elements[day],
other_month = (date.getMonth() != month),
html = '<span class="day-of-month">' + date.getDate() + '</span>';
$(day_element).
html(html).
attr('id', Helpers.to_iso8601(date)).
addClass(other_month ? 'other-month' : '');
date = new Date(date.getTime() + 24*60*60*1000);
});
});
},
hideEmptyWeeks: function() {
// remove whole week from next month
var week5_other_month = _($('td', '.week5')).all(function(el) {
return $(el).hasClass('other-month')
});
if (week5_other_month) { $('.week5').hide() }
}
}));
Now, that is some ugly, non Backbone-y code, but it works:I am torn between trying to do all of that inside a template or leave it more or less as is. That seems like it could get ugly real quick in a template. After all, it is already pretty ugly.
I will sleep on that and pick back up here tomorrow.
Day #183
Hi Chris, found this after googling for something similar and wondered if you had any followup on it?
ReplyDeleteI agree that it is some ugly non backboney code and I am also trying to do something similar but in ember. Did this get resolved or shelved?
The next night, I dropped all of this for a series of sub-views (CalendarMonth, CalendarMonthBody, CalendarMonthWeek, and CalendarMonthDay). The post is at: http://japhr.blogspot.com/2011/10/refactoring-large-backbonejs-views-into.html.
DeleteThat ultimately formed what is now my final solution at: https://github.com/eee-c/Funky-Backbone.js-Calendar/tree/master/public/scripts/Calendar (that also relies on requirejs).
Hope that helps!