Example of using closure to access 'this' object in a different context, e.g. in a global context when using setTimeout or setInterval.
//Example of using closures to access 'this' object in a different context,
//e.g. in a global context when using setTimeout()
function Person(name, count) {
this.name = name;
this.count = count;
}
Person.prototype.countDown = function() {
var self = this;
var closureFunc = function() {
console.log(self.name + ": " + self.count);
self.count–;
if (self.count > 0) {
self.countDown();
}
}
setTimeout(closureFunc, 500);
}
The usage is pretty simple:
- just copy the code to a file or run the code
- then instantiate a timer by hooking it to an existing div tag on the page
- then invoke two functions on the timer object: timer.createHTML(); timer.start();
click here to try it.
Example Usage:
Now here's the javascript for the CustomTimer.
/*
Usage:
var myCt = new com.clt.CustomTimer('myCt', 20, 'div1');
myCt.createHTML(); //creates tabular div progress bar
[and/or]
myCt.createHTML2(); //creates continuous div progress bar
myCt.addTickEventHandler(function(txt, timerObj) { alert(txt+'\n'+timerObj.id); });
myCt.addTickEventHandler(function(txt, timerObj) { fireAjaxWebRequest(....); })
//optionally add extra event handlers to the timer div
//$addHandler($get('div1'), 'click', com.clt.createDelegate(myCt, myCt.timerDiv_onclick));
myCt.start();
myCt.pause();
myCt.resume(); or myCt.tickTock();
myCt.reset();
myCt.start();
To reInitialize with a different time and widthFactor:
---------------------------------------------------------------------
myCt.reInitializeTimer(15000, 2);
OR
reInitializeTimer = function(queryIntervalInMS, widthFactor) {
var numTicks = queryIntervalInMS / 1000;
var timerId = _timerObject.id;
_timerObject.dispose(false);
_timerObject = new com.clt.CustomTimer(timerId, numTicks, 'timerDivId');
if (_timerObject) {
_timerObject.widthFactor = widthFactor;
_timerObject.createHTML2();
_timerObject.addTickEventHandler(_timerEventHandler);
_timerObject.start();
}
}
*/
var com;
if (typeof com == 'undefined' || com == null) {
com = {};
}
if (typeof com.clt == 'undefined') {
com.clt = {};
}
//public static variable
com.clt.numCustomTimerObj = 0;
//Utility function
com.clt.createDelegate = function(instance, method){
return function() {
return method.apply(instance, arguments);
}
}
//function in global namespace, usage: var divEl = dget('div1');
function dget(id){
return document.getElementById(id);
}
/*********************************************************************************
Implements:
Observer design pattern: listeners/eventHandlers are registered, events fired and
corresponding listeners/eventHandlers are invoked.
TODO:
use a logger - if IE use logging in a txtbox,
if firefox & firebug use console.log(),
if ie & 'asp.net ajax' use sys.debug.trace()
Dependencies:
- asp.net ajax library:
- $clearHandlers
- $addHandler
- $removeHandler
- homegrown:
- log("message","optional_log_level=debug")
*********************************************************************************/
/*
valid values for CustomTimer.state =
started, paused, stopped, created, initialized, disposed
cycle:
- created (when constructed)
- initialized (when everything is ready, including html)
- started
- paused (optional)
- stopped (when reset)
- disposed
*/
com.clt.CustomTimer = function(id, numTicks, divId, tableId, tableRowId) {
com.clt.numCustomTimerObj++;
this.id = id;
this.currentTick = 0;
this.numTicks = numTicks;
this.divId = divId;
this.divEl = dget(this.divId);
this.divElEventHandlers = new Array();
this.backgroundColor = 'gray';
this.height = '10px';
this.activeColor = 'red';
this.tableCellWidth = '1px';
this.tableId = tableId;
this.tableRowId = tableRowId;
this.tableEl = null;
this.tableRowEl = null;
this.timerProg;
this.updateIntervalInMs = 1000;
this.progDivContainerEl = null;
this.progDivContainerId = null;
this.progressDivId = null;
this.progressDivEl = null;
//multiplier for progress bar when using div instead of table
this.widthFactor = 2;
this.tickEventHandlers = new Array();
this.startTime = null;
this.state = 'created';
this.prevTickEventTime = null;
this.totalTickEvents = 0;
}
com.clt.CustomTimer.prototype.update = function() {
}
com.clt.CustomTimer.prototype.addTickEventHandler = function(handler) {
var index = this.tickEventHandlers.length;
this.tickEventHandlers[index] = handler;
return index;
}
// removes by index or the function eventHandler Object
com.clt.CustomTimer.prototype.removeTickEventHandler = function(indexOrFunction) {
if (typeof indexOrFunction == 'number') {
if (indexOrFunction >= this.tickEventHandlers.length) {
//error
return null;
}
var f = this.tickEventHandlers[indexOrFunction];
this.tickEventHandlers[indexOrFunction] = null;
return f;
} else {
for (var i = 0; i < this.tickEventHandlers.length; i++) {
if (this.tickEventHandlers[i] == indexOrFunction) {
var f = this.tickEventHandlers[i];
this.tickEventHandlers[i] = null;
return f;
}
}
}
return null;
}
/*
@param data:Object = {'startTime':this.startTime, 'prevTickTime':prevTickTime, 'endTime':new Date()}
*/
com.clt.CustomTimer.prototype.raiseTickEvent = function() {
this.totalTickEvents++;
var endTime = new Date();
this.currentTick = 0;
this.resetUI();
this.state = 'started';
//var dataObj = {'startTime':this.startTime.getTime(), 'prevTickTime':prevTickTime.getTime(), 'endTime':endTime.getTime()}
var txt = "start time: " + this.startTime + "\nprev tick time:"+this.prevTickEventTime
+"\nend time:" + endTime + "\nTotalTickEvents: " + this.totalTickEvents;
this.prevTickEventTime = endTime;
for (var i = 0; i < this.tickEventHandlers.length; i++) {
var f = this.tickEventHandlers[i];
if (f) {
if (f != null && typeof f == 'function') {
var self = this;
f(txt, self);
}
}
}
}
com.clt.CustomTimer.prototype.setNumTicks = function(totalTicks) {
this.numTicks = totalTicks;
this.reset();
}
com.clt.CustomTimer.prototype.setUpdateInterval = function(inMilliseconds){
this.updateIntervalInMs = inMilliseconds;
}
//this creates a div progress bar as opposed to a tabular prog bar
com.clt.CustomTimer.prototype.createHTML2 = function() {
if (this.divEl) {
if (this.progDivContainerEl) {
//do nothing
} else {
if (this.progDivContainerId) {
//do nothing, use specified divId
} else {
//define a new ID for this div tag
this.progDivContainerId = this.id + "_Container";
}
this.progDivContainerEl = dget(this.progDivContainerId);
if (this.progDivContainerEl) {
// do nothing, use existing div
} else {
// create a new div, append to parent
this.progDivContainerEl = document.createElement("div");
this.progDivContainerEl.id = this.progDivContainerId;
this.divEl.appendChild(this.progDivContainerEl);
}
this.progDivContainerEl.style.backgroundColor = this.backgroundColor;
this.progDivContainerEl.style.width = "" + (this.numTicks * this.widthFactor) + "px";
this.progDivContainerEl.style.height = this.height;
this.progDivContainerEl.style.border = "1px solid black";
}
//.....
if (this.progressDivEl) {
//do nothing
} else {
if (this.progressDivId) {
//do nothing
} else {
this.progressDivId = this.id + "_ProgressBar";
}
this.progressDivEl = dget(this.progressDivId);
if (this.progressDivEl) {
//do nothing, use existing div
} else {
//create a new div, append to parent
this.progressDivEl = document.createElement("div");
this.progressDivEl.id = this.progressDivId;
this.progDivContainerEl.appendChild(this.progressDivEl);
}
this.progressDivEl.style.backgroundColor = this.activeColor;
this.progressDivEl.style.width = "0px";
this.progressDivEl.style.height = "10px";
}
this.reset();
this.state = 'initialized';
} else {
//log error
if(typeof log != 'undefined') {
log('Unable to createHTML2(), DIV element is null','warn');
}
}
}
//creates a tabular progres bar with discrete visible timer steps/ticks
com.clt.CustomTimer.prototype.createHTML = function() {
if (this.divEl) {
this.tableEl = dget(this.tableId);
if (this.tableEl) {
//do noting
} else {
if (this.tableId) {
//do nothing
} else {
this.tableId = this.id + "_Table";
}
this.tableEl = dget(this.tableId);
if (this.tableEl) {
//do nothing
} else {
this.tableEl = document.createElement("table");
this.tableId = this.id + "_Table";
this.divEl.appendChild(this.tableEl);
}
this.tableEl.id = this.tableId;
this.tableEl.style.backgroundColor = this.backgroundColor;
this.tableEl.style.height = this.height;
}
//......
this.tableRowEl = dget(this.tableRowId);
if (this.tableRowEl) {
//do nothing
} else {
if (this.tableRowId) {
//do nothing
} else {
this.tableRowId = this.id + "_TableRow";
}
this.tableRowEl = dget(this.tableRowId);
if (this.tableRowEl) {
//do nothing
} else {
this.tableRowEl = document.createElement("tr");
this.tableRowEl.id = this.tableRowId;
this.tableEl.appendChild(this.tableRowEl);
}
this.tableRowEl.style.backgroundColor = this.backgroundColor;
}
if (this.tableEl && this.tableRowEl) {
this.reset();
this.state = 'initialized';
//this.createRows();
} else {
//serious error - log message
}
} else {
//error
if(typeof log != 'undefined') {
log('Unable to createHTML2(), DIV element is null','warn');
}
}
}
//this updates the progress bar after a 1 sec, i.e. if numTicks=10, then timer will stop at exactly the 10th second mark
com.clt.CustomTimer.prototype.start = function() {
if (this.state == 'paused') {
this.resume();
return;
}
this.startTime = new Date();
var f = com.clt.createDelegate(this, this.tickTock);
//this.timerProg = setTimeout(f, this.updateIntervalInMs);
f(); //execute a tick immediately
this.timerProg = setInterval(f, this.updateIntervalInMs);
this.state = 'started';
}
//this updates the progress bar immediately, i.e. if numTicks=10, then timer will stop at 11th second mark
com.clt.CustomTimer.prototype.tickTock = function() {
if (this.numTicks) {
if (this.currentTick <= this.numTicks) {
if (this.startTime == null) {
this.startTime = new Date();
}
if (this.tableRowEl) {
var el1 = this.tableRowEl.getElementsByTagName("td")[this.currentTick];
if (el1)
el1.style.backgroundColor = this.activeColor;
}
if (this.progressDivEl) {
this.progressDivEl.style.width = "" + (this.widthFactor * (this.currentTick)) + "px";
}
if (this.currentTick == this.numTicks) {
this.raiseTickEvent();
return;
//clearInterval(this.timerProg);
}
this.currentTick++;
//var f = com.clt.createDelegate(this, this.tickTock);
//this.timerProg = setTimeout(f, this.updateIntervalInMs);
} else {
//clearInterval(this.timerProg);
}
}
}
com.clt.CustomTimer.prototype.resume = function() {
var f = com.clt.createDelegate(this, this.tickTock);
this.tickTock();
this.timerProg = setInterval(f, this.updateIntervalInMs);
this.state = 'started';
}
com.clt.CustomTimer.prototype.pause = function() {
if (this.numTicks) {
//clearTimeout(this.timerProg);
clearInterval(this.timerProg);
this.state = 'paused';
} else {
}
}
com.clt.CustomTimer.prototype.reInitializeTimer = function(tickIntervalInMS, widthFactor) {
var numTicks = tickIntervalInMS / 1000;
this.reset();
this.numTicks = numTicks;
this.widthFactor = widthFactor;
this.createHTML2();
this.start();
}
com.clt.CustomTimer.prototype.resetUI = function() {
if (this.tableRowEl) {
this.tableRowEl.innerHTML = "";
this.createRows();
}
if (this.progressDivEl) {
this.progressDivEl.style.width = "0px";
}
}
com.clt.CustomTimer.prototype.reset = function() {
if (this.numTicks) {
this.currentTick = 0;
//this.startTime = null;
clearTimeout(this.timerProg);
this.resetUI();
this.state = 'stopped';
} else {
//log error msg
}
}
com.clt.CustomTimer.prototype.createRows = function() {
if (this.tableRowEl && this.numTicks) {
for (var i = 0; i < this.numTicks; i++) {
var tdElem = document.createElement("td");
tdElem.style.backgroundColor = this.backgroundColor;
tdElem.style.width = this.tableCellWidth;
tdElem.innerHTML = ''; //' '
this.tableRowEl.appendChild(tdElem);
}
} else {
//log error msg
}
}
com.clt.CustomTimer.prototype.dispose = function(removeParentDiv) {
//remove any eventHandlers for DOM nodes
//set reference to event handlers to null
if (this.tickEventHandlers) {
this.tickEventHandlers.splice(0, this.tickEventHandlers.length);
this.tickEventHandlers = null;
}
//remove html DOM elements & set reference to DOM nodes to null
if (this.progDivContainerId) {
var el = dget(this.progDivContainerId);
if (el) el.parentNode.removeChild(el);
el = null;
}
if (this.tableId) {
var el = dget(this.tableId);
if (el) el.parentNode.removeChild(el);
el = null;
}
this.tableEl = null;
this.progDivContainerEl = null;
this.progressDivEl = null;
this.tableEl = null;
this.tableRowEl = null;
if (removeParentDiv) {
if (this.divEl)
this.divEl.parentNode.removeChild(this.divEl);
var domEl1 = dget(this.divId);
if (domEl1) {
domEl1.parentNode.removeChild(domEl1);
domEl1 = null;
}
}
this.divEl = null;
///
if (this.timerProg) {
clearInterval(this.timerProg);
this.timerProg = null;
}
//set everything else to null, not necessary
this.id = null;
this.currentTick = null;
this.numTicks = null;
this.updateIntervalInMs = null;
this.widthFactor = null;
this.startTime = null;
this.divId = null;
this.tableId = null;
this.tableRowId = null;
this.progDivContainerId = null;
this.progressDivId = null;
this.state = 'disposed';
return true;
}
com.clt.CustomTimer.prototype.timerDiv_onclick = function(event) {
if (!this) {
alert('Timer is not available');
return;
}
var timerState = this.state;
if (timerState == null) {
alert('Timer state is NULL');
return;
}
var userConfirm = false;
if (timerState == 'stopped') {
userConfirm = confirm('Are you sure you want to START the timer?');
if (userConfirm) {
this.reset();
this.start();
}
} else if (timerState == 'started') {
userConfirm = confirm('Are you sure you want to PAUSE the timer?');
if (userConfirm) {
this.pause();
}
} else if (timerState == 'paused') {
userConfirm = confirm('Are you sure you want the timer to CONTINUE?');
if (userConfirm) {
this.start();
}
} else if (timerState == 'disposed') {
alert('Timer is not available');
} else if (timerState == 'created') {
this.createHTML2();
//add event handlers
this.start();
} else if (timerState == 'initialized') {
this.start();
} else {
alert('Invalid timer state[' + timerState + '], doing nothing.');
}
}
//============================== END CLASS ==========================