SiteExperts.com Logo Home | Community | Developer's Paradise | Jobs
User Groups | Site Tools | Site Information | Search

Inside Technique : Extending JavaScript with Function Pointers : Creating a custom setInterval Method

The built-in properties and methods of the browser are exposed in the same manner as user-defined variables. This allows you to override and reassign any of the built-in functions. We are going to take advantage of this to rewrite the setInterval method in Internet Explorer to be fully compatible with its Netscape counterpart.

All functions in JavaScript are methods of a specific object. The setInterval and clearInterval functions are methods of the window object. Since the window object is the default method of the browser it is usually not explicitly specified. When we override built-in methods, we explicitly specify the object and the method we want to override. In this scenario we want to override the setInterval method. For example, to override the setInterval method to display an alert, we can do the following:

	window.setInterval = new Function("alert()")

This example is not very interesting but demonstrates how we are going to make the Internet Explorer setInterval method compatible with Netscapes. We are going to show you how to manually implement the setInterval method when the browser is not Netscape Navigator 4.0. A positive side-effect is that this also adds a complete setInterval method to Netscape Navigator 3.0.

Remember, the setInterval without the extra function pointer and argument support is essentially a special case of the setTimeout method. We use this concept and the setTimeout method to rewrite the enhanced version of the setInterval method.

Our first step is to determine the functionality required by our new setInterval method. Our new method requires the following support:

  • Detect whether the first argument is a function pointer or a line of script.
  • Determine whether there are any optional arguments specified
  • Return a reference to the timer that can be used by the clearInterval method.

The first requirement is actually fairly simple. JavaScript exposes a typeof statement that returns the data type of any variable. Functions return "function" and strings return "string". We will assume that any datatype that is not a function is a line of script. To turn a line of script into a function, we use the function constructor and pass in the line of script.

The second requirement is processing any additional arguments. When a javascript function is called an arguments array is created representing all the passed in arguments. By checking the length of this array, we can easily determine if any optional arguments were specified and then process them accordingly.

The third requirement is actually a little tricky. Whenever this function is called we need to return a unique reference that can be used to later clear the timer using the clearInterval method. The actual value of this variable is opaque to the user meaning the user should never look at or evaluate the actual value. Instead, it is merely an identifier that is used by the clearInterval method. Therefore, since we use an array to track all running timers we return the index into the array as our unique identifier.

Below is our newSetInterval function:

// Internal array used to track all running intervals
var aTracking = new Array()

// Determine if Netscape 4 is running
var version = parseInt(navigator.appVersion)
var appName = navigator.appName
var ns4 = version>=4 && appName=="Netscape"

function newSetInterval(func,interval) {
 var fCall = func
 // If necessary, turn first argument into a function
 if (typeof func!="function") 
  var fCall= new Function(func)

 // Find next available identifier
 var nextIdx = aTracking.length

 // Store details of interval in the array
 aTracking[nextIdx] = new Object

 // How often do we execute
 aTracking[nextIdx].interval = interval

 // Function to call
 aTracking[nextIdx].code = fCall

 // Store any optional arguments
 aTracking[nextIdx].arguments = new Array()
 for (var i=2;i < arguments.length;i++) 
  aTracking[nextIdx].arguments[aTracking[nextIdx].arguments.length] = arguments[i]
 
 // Start up timer. RunInterval explained next.
 // Store "real" identifier to the timer.
 aTracking[nextIdx].timerID = setTimeout("runInterval("+nextIdx+")",interval)

 // Return an identifier for the timer
 return nextIdx
}

function newClearInterval(idx) {
 // If interval exists, clear it
 if (aTracking[idx]) {
  // Stop timer using the "real" identifier
  clearTimeout(aTracking[idx].timerID)
  aTracking[idx] = null
 }
}

// Setup methods
if (!ns4) {
 window.setInterval = newSetInterval
 window.clearInterval = newClearInterval
}

The next step is to create the runInterval method that actually processes each interval and calls the appropriate code. We started this exercise because we wanted a cross-browser setInterval method that supported Netscape's enhanced setInterval features for function pointers and arguments. To do this we need to emulate Netscape's implementation. Our solution is fairly complex but enables us to provide complete compatibility.

We use the setTimeout method as it has been traditionally used by passing in an index into each loop. This index tells us where to look into a global array for accessing timer's real information. On each execution we look into the array and retrieve the function to execute plus any necessary arguments. We then call this next function passing in the arguments. These arguments work the same as in Netscape and can be of any data type.

Below is the implementation for the runInterval method:

function runInterval(sIndex) {
  if (aTracking[sIndex]) {
    var args = aTracking[sIndex].arguments
    // Call function and pass in any extra argument
    var callargs="aTracking[sIndex].code(";
    for(i=0;i < args.length;++i) {
      callargs=callargs+"args["+i+"]";
      if(i < args.length) callargs+=",";
    }
    callargs=callargs+")";
    eval(callargs);
    // Start up timer for next iteration
    aTracking[sIndex].timerID = setTimeout("runInterval(" + sIndex + ")",aTracking[sIndex].interval)
  }
}

Next we provide the complete script with our original clock demonstration.