Countdown Timer Object

Published by Marco on in Web: HTML/CSS/JS

This tutorial expands on the timer built in Countdown Timer Basics. In that example, we ended up with the following code for displaying a countdown timer in an HTML page.

var StartValue = 10;

setTimeout ("HandleTimeout()", 1000);

function HandleTimeout ()
{
  var CountDownElement = document.getElementById ("countdown");
  StartValue–;
  CountDownElement.innerHTML = StartValue;
  setTimeout ("HandleTimeout()", 1000);
}

We will take this example and convert it to an object, so that a page can support multiple timers. Specifically, we will:

  • Show how to use the setTimeout function with a method
  • Use inheritance to generalize timer handling

The following examples make use of the prototype syntax used to define objects in JavaScript. See Objects In JavaScript for more information.

Methods as Callbacks

The first parameter to the setTimeout function can be a string or a function pointer. If it is a string, it is evaluated with eval(), or, more specifically, window.eval(). The distinction is important because it determines the execution context. That means that code passed to setTimeout is evaluated in the scope of the window—references to this will refer to the window, not your timer object.

How do we get around this limitation? Again, a quick search on the Internet will tell you that you have to pass a function pointer—and that you can’t use the this reference to call it. Most suggestions run along the lines of the following example:

MyClass.prototype.ScheduleTimer = function ()
{
  var me = this;
  setTimeout( function() { me.update (); }, 1000);
}

Let’s take a look at what’s going on here:

  • Instead of passing a pointer to an exisiting function, we pass an anonymous function. This is simply a way of defining code to execute without cluttering the global namespace with callback handlers.
  • The reference to this is assigned to the variable me, which is available at the global scope.[1] When setTimeout() executes the anonymous function, me refers to the correct object and will execute update() accordingly.

Multiple Timers?

The example as written above has a problem: it doesn’t work when there is more than one MyClass defined on the page. If two timers are created and scheduled, the variable me will hold a reference to the second timer. When the two timers fire, they will both call update() on the second object.
The solution isn’t too difficult, but is also not so elegant. Using a global cannot be avoided; it’s the only common scope for the eval() context and MyClass. However, we can use the JavaScript Array object to keep track of multiple references and retrieve the correct one when the timer is fired.[2] This technique requires that the object is uniquely identifiable somehow (we use a Name property) and is shown below:

var Timers = new Array ();

MyClass.prototype.ScheduleTimer = function ()
{
  timerName = this.Name
  Timers[ timerName ] = this;
  setTimeout( function() 
              { 
                Timers[ timerName ].update (); 
              }
            , 1000);
}

Final Version of the Timer

The code below makes use of techniques illustrated in the following articles. Some error-handling code has been left away for clarity’s sake.[3]

var Timers = new Array ();

function GenericTimer ()
{
  this.Name = "";
  this.Interval = 1000;
}
   
GenericTimer.prototype.ScheduleTimer = function ()
{
  timerName = this.Name
  Timers[ timerName ] = this;
  setTimeout( function() 
              { 
                Timers[ timerName ].update (); 
              }
            , 1000);
}
   
GenericTimer.prototype.Update = function ()
{
  this.ScheduleTimer ();
}

CountdownTimer.prototype = new GenericTimer;

function CountdownTimer (numSeconds, elementName)
{
  this.Name = elementName;
  this.Seconds = numSeconds;
  this.Element = document.getElementById (this.Name);
  this.ScheduleTimer ();
}

CountdownTimer.prototype.update = function ()
{
  this.Seconds–;
  this.Element.innerHTML = this.Seconds;
  this.ScheduleTimer ();
}

Note that, as described in the final section of Inheritance in JavaScript, we cannot call the inherited update() method through a parent because the ScheduleTimer() method makes use of the instance variables, Name and Interval.

[1] Variables declared with var are global. At the global scope, var is required; within a function, variables declared without var are available only within the function.
[2] This pattern would be much more difficult to implement without anonymous functions.
[3] elementName should be assigned, numSeconds greater than 0, etc.