Showing posts with label oop. Show all posts
Showing posts with label oop. Show all posts

Wednesday, January 21, 2009

Object Oriented Javascript Programming

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:
  1. just copy the code to a file or run the code
  2. then instantiate a timer by hooking it to an existing div tag on the page
  3. 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 ==========================

Tuesday, December 23, 2008

Great JavaScript articles, references

great resources:
  1. We know that dom manipulation in js is slower than innerHTML, but when innerHTML isn't fast enough? http://blog.stevenlevithan.com/archives/faster-than-innerhtml
  2. Object oriented programming and SetTimeout (what to do when we require context for a js object when executing global functions like settimeout?) http://www.kevlindev.com/blog/?p=72
  3. Need the reference info for the DOM element, refer to Mozilla's great developer center. Gecko DOM reference. https://developer.mozilla.org/en/Gecko_DOM_Reference https://developer.mozilla.org/en/DOM/element
  4. Object Oriented Programming in JS, an intro: http://nefariousdesigns.co.uk/archive/2006/05/object-oriented-javascript