Creating a Template-able Calendar with jQuery’s jBind Plugin
2009.07.17 Leave a comment
So, when we started talking about our JavaScript coding standards for the project I’m working on, I pushed to “find a template system that didn’t use string concatenation”. One of my pet-peeves about most JavaScript code is the constant use of string concatenation. Having a template that separates your coding from your HTML, and having your HTML “readable” is a major plus, especially if you have designers working on the project with you. So, anyway, since we were using JQuery, one of the other guys found the jBind plugin, which looked exactly like what I was looking for. So, I tried it out for our calendar system, and I have to say that jBind is pretty stinking cool.
jBind can work with objects and arrays to build out the template. So, we needed a calendar object with properties, such as MonthName, and an array of Weeks. Each Week, then, needed to be an object with an array of Days, which are in turn their own object with properties such as “DateNumber”, “DayOfWeek”, and other properties that might be used within the calendar HTML to display or help locate specific days.
I’ve stripped down the below code a little bit, but left in namespacing and the arrays for Days of Week and Months so that you can see more of the functionality working and understand it. The below calendar code is technically not dependent on any JavaScript library, but it is optimal for use with jQuery’s jBind Templating plugin.
var w3 = new Object();
w3.Calendar = new Object();
w3.Calendar.Arrays = new Object();
w3.Calendar.Arrays.Days = [
{Abbr: "Sun", Name: "Sunday"},
{Abbr: "Mon", Name: "Monday"},
{Abbr: "Tue", Name: "Tuesday"},
{Abbr: "Wed", Name: "Wednesday"},
{Abbr: "Thu", Name: "Thursday"},
{Abbr: "Fri", Name: "Friday"},
{Abbr: "Sat", Name: "Saturday"}
];
w3.Calendar.Arrays.Months = [
{Abbr: "Jan", Name:"January"},
{Abbr: "Feb", Name:"February"},
{Abbr: "Mar", Name:"March"},
{Abbr: "Apr", Name:"April"},
{Abbr: "May", Name:"May"},
{Abbr: "Jun", Name:"June"},
{Abbr: "Jul", Name:"July"},
{Abbr: "Aug", Name:"August"},
{Abbr: "Sep", Name:"September"},
{Abbr: "Oct", Name:"October"},
{Abbr: "Nov", Name:"November"},
{Abbr: "Dec", Name:"December"}
];
w3.Calendar.Week = function( weekNumber ) {
this.WeekNumber = weekNumber;
this.Days = new Array();
}
w3.Calendar.Day = function( cssClass, week, date ) {
this.CssClass = cssClass;
this.DateNumber = date == null ? "" : date.getDate();
this.DayOfWeek = date == null ? "" : date.getDay();
this.DayOfWeekName = date == null ? "" : w3.Calendar.Arrays.Days[this.DayOfWeek].Name;
this.DayOfWeekAbbr = date == null ? "" : w3.Calendar.Arrays.Days[this.DayOfWeek].Abbr;
this.WeekNumber = week == null ? "" : week.WeekNumber;
// other properties you might want to stick in template such as IsWeekend, etc.
}
w3.Calendar.Builder = {
getData: function(year, month) {
this.jsMonth = month-1; // JavaScript uses 0 for Jan, etc., so subtract 1
this.Data = new Object();
this.Data.Weeks = new Array();
this.Data.Year = year;
this.Data.MonthNumber = month;
this.Data.MonthAbbr = w3.Calendar.Arrays.Months[this.jsMonth].Abbr;
this.Data.MonthName = w3.Calendar.Arrays.Months[this.jsMonth].Name;
var oCounter = new Date( year, this.jsMonth, 1 );
var iWeekCounter = 1;
var CurrentWeek = new w3.Calendar.Week( iWeekCounter );
// fill up week with empty days until day matches day for 1st of month
for( var i=0; i != oCounter.getDay(); i++ ) {
CurrentWeek.Days.push( new w3.Calendar.Day( "empty", null, null ) );
}
// fill up week with days. Start new week on Sundays
while( oCounter.getMonth() == this.jsMonth ) {
CurrentWeek.Days.push( new w3.Calendar.Day( "date", CurrentWeek, oCounter ) );
oCounter.setDate( oCounter.getDate() + 1 );
if( oCounter.getDay() == 0 ) {
this.Data.Weeks.push( CurrentWeek );
CurrentWeek = new w3.Calendar.Week( ++iWeekCounter );
}
}
// fill up last week with empty days until Saturday is filled
while( oCounter.getDay() != 0 ) {
CurrentWeek.Days.push( new w3.Calendar.Day( "empty", null, null ) );
oCounter.setDate( oCounter.getDate() + 1 );
}
this.Data.Weeks.push( CurrentWeek );
return this.Data;
}
}
These properties can then be used as needed in the template. So, here is the beauty of jBind. We put the following HTML at the bottom of our page inside a hidden div. Notice how simple and clean the HTML is:
<div id='Template' style='display:none;'>
<!--data-->
<div class='header'>{MonthName}, {Year}</div>
<table class='calendar'>
<thead>
<th class='week'> </th><th>S</th><th>M</th><th>T</th><th>W</th><th>Th</th><th>F</th><th>S</th>
</thead>
<tbody>
<!--Weeks-->
<tr><td class='week'>{WeekNumber}</td>
<!--Days-->
<td class='{CssClass}' DayOfWeek='{DayOfWeekAbbr}'>{DateNumber}</td>
<!--Days-->
</tr>
<!--Weeks-->
</tbody>
</table>
<!--data-->
</div>
So, now we have a template and a way to create a calendar object to populate it. All we have to do now is combine the two, which can be done easily. However, now, obviously our code becomes dependent on two JavaScript files, the jQuery core, and the jBind plugin. Once you have those two files linked, you can plugin your calendar code to your template like so:
$(document).ready( function() {
var data = w3.Calendar.Builder.getData( 2009, 5 );
var template = $('#Template').html();
var formatted = $(template).bindTo(data);
$('#WhereToPlaceHTML').html( formatted );
} );
Using this approach, you have an extremely flexible calendar system that can be built with any template. Then, you just run your script to populate/manipulate the calendar as needed thereafter.
Benchmarking?
Now, I haven’t done any benchmarking to see if the above is any slower than traditional string-concatenated templates. To build an entire year of calendars on a single page was taking about 6 seconds in FireFox and 10 seconds in IE, which is a bit slower than I’d like, but I’m not sure JavaScript would be any faster using string concatenation, and with the ease of use, and flexibility provided by the template engine, I consider it well worth it.