Creating a Template-able Calendar with jQuery’s jBind Plugin

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'>&nbsp;</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.

Which JavaScript Framework to Use? (Dojo, JQuery, Prototype)

Okay, so I’ve had two projects in the last year, and in both cases, I’ve been largely responsible for the UI JavaScript development. In the first project, prototype was chosen before I joined the project, and in the second project, which is ongoing, it was opted to use the JQuery framework (even though I wanted Dojo :( ), because the designers liked JQuery and didn’t like that dojo added attributes into tags that don’t actually exist and thus throw warnings when running the code through an XHTML validator (turns out, we ended up adding attributes to tags anyway to handle a JQuery tree control…but oh well). Finally, on my own personal projects, I’ve been working to use Dojo.

In any case, having gotten a modicum of experience in these 3 frameworks, I figured I’d throw out my two cents for anyone looking into JavaScript frameworks and trying to decide what to use.

Prototype

Prototype is a great object-oriented base-code, with features that are very useful for building your own code.  It has some really handy methods, at least one of which I can’t live without: Function.prototype.bind (look it up).  However, overall, even with script.aculo.us, it just seemed like we were always re-inventing the wheel.  That’s great, as I love the experience of creating all my own tools, but with deadlines, etc., you end up with a wooden wheel when you really want a high performance radial tire.  So, my opinion is that if you’re going to use prototype, use it in conjunction with another library that has more pre-built standard features. Or just take the useful features of prototype and add just those parts to the other library.

JQuery

JQuery is a step up from prototype in terms of it’s pre-built features.  With the JQuery UI, you have access to a lot of common functionality features: Accordian, Dialog, ProgressBar, DatePicker.  You also have ways to easily implement things like drag/drop and making a list sortable.  There are also a lot of really nice plugins that you can use in conjunction with JQuery.  So, JQuery is a great library to work with. If you want to use JQuery and like how they did things, then I don’t think you can really go wrong with it.

That said, I did end up adding an additional method to handle class inheritance, and I borrowed the Function.prototype.bind that Prototype uses and is extremely handy if you ever pass functions that are within a class to an event handler.  However, these things were easy to add and I don’t really consider them as a detraction from JQuery.  Really, about the only problem I have with JQuery is that I have a $(document).ready() with about 50 calls in it to get it to make conversions on the HTML, whereas, I would really like it if JQuery had a parser that would automatically convert HTML on the page based on tag attributes, which leads me to Dojo.

Dojo

I have to say that I really love the dojo framework.  It is the behemoth of JavaScript frameworks, with more extensions that come with it by default then you’ll probably ever need.  Additionally, since it only loads up the extensions that you need (on-demand) you have access to EVERYTHING without weighing down your users with the entire framework of code.  However, I love dojo for a completely different reason, and thats for its parseOnLoad ability.  You simply specify an attribute in a tag like so: <tag dojoType=”namespace.to.type”>, and add any other attributes applicable to that type, and dojo will automatically parse the page, load the needed JavaScript files for that type (again, on-demand), and convert the tag to that type using the attributes that you specified.  This allows your HTML and CSS to define the attributes of what is displayed instead of having to assign those values via JavaScript in an onload handler like you would with JQuery.  I consider this to be a much more natural way of defining things in a web page.

To try to explain this, though, an example is much better.  So, let’s say we want a tooltip that can display HTML formatted text rather than just the text tooltip we get with the title attribute.  Well, we can do this quite easily, and the greater part is that we can do it with ZERO Javascript coding, like so:

<a id="phrase">Hello World</a>
<div dojoType="dijit.Tooltip" connectId="phrase">
<b>a phrase that is apparently impossible to program without</b>
</div>

If Dojo is set to parse automatically, it will parse the above, find the dojoType=’dijit.Tooltip’ tag, convert it to a tooltip popup and hide it from the browser display. Then, the connectId=”phrase” tells it to assign onmouseover and onmouseout event handlers to the tag with the id “phrase”…presto, no JavaScript in your HTML at all, no need to call anything onload…it’s non-programmer friendly because it’s just HTML, and it’s programmer friendly because there is a nice separation between the code and HTML.

Now, I was originally going to finish this block by talking about how big of a file size dojo has, and how it was the heaviest framework of the bunch to somewhat show it’s negatives. However, then I took a closer look at JQuery. Once you add JQuery-UI, which for my own use is a must…JQuery doesn’t really provide much (if any) bandwidth savings. Combine that with Dojo’s ability for on-demand script, so that you’re only loading what you need, when you need it…and it just comes across as dojo being the all-around winner, at least in my book. Additionally, if you do use particular extensions on a regular basis, you can compile these into a single file and gzip compress it for additional savings.

Note on GZip Compression

In the case of either JQuery or Dojo, make sure that you do take advantage of gzip compression. If you use something like the Google CDN, then google takes care of this for you. However, if you’re using a local copy, make sure to take a look into getting compressed files, or how to compress them using server-side script for custom solutions.

Follow

Get every new post delivered to your Inbox.