Tuesday, October 11, 2011

Adventures in HTML5: A JavaScript Doomsday Counter

Part of mastering HTML5 is mastering JavaScript, a big focus for me and 120 colleagues this month. The best way to go deep on anything in the software world is to be constantly building stuff with it, so here’s something I developed over the last few days: an animated odometer-style counter control suitable for counting down to a deadline.

DEVELOPING THE CONTROL
I didn’t start out intending to write a counter control at all, I was trying to find one to use in a dashboard. I want to be building some cool HTML5 business dashboards in the months to come, and a counter is one of the many widgets I’ll need. I started out looking for one, and sure enough there are a number of free JavaScript counters with source to be found online. Unfortunately, after some evaluation I found issues with each of the ones I tried and I wasn’t able to control appearance or behavior to my satisfaction. Ultimately, I decided to take a shot at creating my own counter control. I wasn’t sure how far I’d get but if nothing else it would be a good learning experience. Rather than writing a simple counter that shows a single value, I chose to create a days-hours-minutes-seconds counter that would count down to a deadline.

The first step was to obtain graphics for the odometer images, then think through how the digit display should work. If we were merely displaying and updating digit images this would be no big deal, but for this control I wanted the seconds digits to be moving as well as the minutes, hours, and days digits when they get decremented. I created a filmstrip-like image (below) with the digits 0-9 arranged vertically beneath each other. Using this image as the background for each digit, showing any particular value is just a matter of clipping the image and changing its top offset. Moving the image only requires use of a timer to increment or decrement the clip area and top offset until the new digit has rolled into place. As you can imagine there’s quite a bit of logic needed to do this well and bump seconds to minutes to hours to days.

Using top margin and CSS clipping to show and roll digits

To show one digit from the image, I used the CSS clipping feature. I created CSS classes for each digit that set the image’s top margin and clipping area. One complication of CSS clipping is that it requires absolute positioning, something I didn’t want. It turns out you can have it both ways, by using absolute positioning inside a DIV tag container that uses relative positioning.
/* Digit classes */

.d0 { position:absolute; clip:rect(39px 37px 78px 0px); vertical-align: middle; margin-top:-39px; }

.d1 { position:absolute; clip:rect(78px 37px 117px 0px); vertical-align: middle; margin-top:-78px; }

.d2 { position:absolute; clip:rect(117px 37px 156px 0px); vertical-align: middle; margin-top:-117px; }

.d3 { position:absolute; clip:rect(156px 37px 195px 0px); vertical-align: middle; margin-top:-156px; }

.d4 { position:absolute; clip:rect(195px 37px 234px 0px); vertical-align: middle; margin-top:-195px; }

.d5 { position:absolute; clip:rect(234px 37px 273px 0px); vertical-align: middle; margin-top:-234px; }

.d6 { position:absolute; clip:rect(273px 37px 312px 0px); vertical-align: middle; margin-top:-273px; }

.d7 { position:absolute; clip:rect(312px 37px 351px 0px); vertical-align: middle; margin-top:-312px; }

.d8 { position:absolute; clip:rect(351px 37px 390px 0px); vertical-align: middle; margin-top:-351px; }

.d9 { position:absolute; clip:rect(390px 37px 429px 0px); vertical-align: middle; margin-top:-390px; }

Lots of JavaScript code later, I had a decent working prototype control. This afforded me an opportunity to learn more about JavaScript as I tackled events, timers, dynamic DOM alteration, and date calculation. Although I’ve only implemented decrementing deadline counters so far, the code is written in anticipation of other counter types in the future. While my code is in need of completion and refactoring, I’m still proud of it for a first effort. 
CREATING THE DOOMSDAY COUNTDOWN DEMO

Next it was time to incorporate the countdown control into a demo application, and it would need to involve one or more deadlines. With all the end-of the-world date setting going on these days, it seemed a natural to create a tongue-in-cheek “Doomsday Countdown” web site that shows the running countdown to the various end-of-the-world dates. Check it out here. Unfortunately there are some positioning problems in Firefox I have yet to figure out with the days counter, but it renders just as it should on Chrome and IE.


In my HTML page, the application references JQuery, the counter.js library for the control, and the counter.css CSS for the control.
    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js">script>
     <link rel="stylesheet" href="counter.css" type="text/css"/>
    <script type="text/javascript" src="counter.js">script>

The HTML page also contains some initialization JavaScript that creates an array of counter objects. Each object specifies an Id prefix, a counter type (“Date”), an increment/decrement direction, a target date, and an optional hour of the day adjustment.

    <script type="text/javascript">

      // Create an array of counter objects.

      var counters;

      var counterCamping = {
          Prefix: "Camping",
          CounterType: "Date",
          Direction: -1,
          EndDate: "10/21/2011",
          Hours: 8
      };

      var counterBibleCode = {
          Prefix: "BibleCode",
          CounterType: "Date",
          Direction: -1,
          EndDate: "01/01/2012",
          Hour: -16
      };

      var counterMayan = {
          Prefix: "Mayan",
          CounterType: "Date",
          Direction: -1,
          EndDate: "12/21/2012",
          Hour: 0
      };

      var counterRasha = {
          Prefix: "Rasha",
          CounterType: "Date",
          Direction: -1,
          EndDate: "01/01/2280",
          Hour: -16
      };

      var counterNostradamus = {
          Prefix: "Nostradamus",
          CounterType: "Date",
          Direction: -1,
          EndDate: "01/01/3739",
          Hour: -8
      };

      var counterSun = {
          Prefix: "Sun",
          CounterType: "Date",
          Direction: -1,
          EndDate: "01/01/99999",
          Hour: 42
      };

      counters = [6];

      counters[0] = counterCamping;
      counters[1] = counterBibleCode;
      counters[2] = counterMayan;
      counters[3] = counterRasha;
      counters[4] = counterNostradamus;
      counters[5] = counterSun;

      // Load event handler - add counters to the DOM and start update timer.

      function load() {
          SetCounters(counterCamping);
          SetCounters(counterBibleCode);
          SetCounters(counterMayan);
          SetCounters(counterRasha);
          SetCounters(counterNostradamus);
          SetCounters(counterSun);
          StartCounters();
      }
  <script>

The onload event handler calls the counter.js library to insert counters into the DOM and start a countdown timer running. From there the counters count down all by themselves.

The body of the document is a header, a table that holds the counters and labels, and a footer. Yes, I’m well aware that tables aren’t cool these days—but I had to timebox this experiment and was focused on the JavaScript. As you can see, the Ids used in the table entries match the JavaScript object prefixes seen above. The counter.js library assumes for a counter object with name X that there are elements named XDays, XMinutes, XMinutes, and XSeconds.

<tr>
<th class="Label SideLabel">Mayan Calendar</th>
<td> </td>
<td class="Label DateLabel">2012-DEC-21</td>
<td> </td>
<td><div id="MayanDays" class="BigCounter" /></td>
<td class="sep"> &#8226 </td>
<td><div id="MayanHours" class="Counter" /></td>
<td class="sep"> &#8226 </td>
<td><div id="MayanMinutes" class="Counter" /></td>
<td class="sep"> &#8226 </td>
<td><div id="MayanSeconds" class="Counter" /></td>
</tr>

Was this the valuable learning experience I expected it to be? It was. I wouldn't have gotten very far without the many useful tips and code snippets about JavaScript online. For the application heading I found a nice fire text effect on UI Hacker.  The application also uses a function or two from the JQuery library.

With the understanding this is only prototype code and there is a display issue on Firefox, the Doomsday Countdown application and countdown control may be downloaded from here.

(we're counting on you, but if you don't use it, it's not the end of the world)

2 comments:

prog1981 said...

cool!

-Jessie said...

David,
I have a CSS snippet that fixes the positioning for FireFox somewhere(I can't seem to find it on this computer, still looking though). The bad thing is, it messes stuff up on IE and REALLY jacks it up for chrome. I haven't found a 'universal' fix.