if (!JSLIB.eventData) { 
	// eventData class. use new to create new copy
	// dE - domEvent
	JSLIB.namespace("JSLIB.eventData");
	JSLIB.eventData = function(registeredTarget, dE) {
	    // browser event object
	    this.domEvent=dE;
	    this.registeredTarget=registeredTarget;
		if (dE) {
		    this.target=(dE.target)?dE.target:dE.srcElement;
		    this.type=dE.type;
	
		    // mouse specific
		    this.clientX=dE.clientX;
		    this.clientY=dE.clientY;
		    this.button=dE.button;
	
		    // keyboard specific
		    
		    // more for the keyCode, charCode madness on KeyPress event
		    // ref: http://unixpapa.com/js/key.html
		    // following logic works only for keypress
			if (dE.which == null) {
				this.ch=String.fromCharCode(dE.keyCode);    // IE
			}
			else if (dE.which != 0 && dE.charCode != 0) {
				this.ch=String.fromCharCode(dE.which);	  // All others
			}
			else { 
				// special key
				this.ch=null;
			}
		    
		    this.keyCode=dE.keyCode;
		    this.altKey=dE.altKey;
		    this.ctrlKey=dE.ctrlKey;
		    this.shiftKey=dE.shiftKey;
		}
		else {
		    this.target=registeredTarget;
		}
	}
}

if (!JSLIB.event) {
	// object used as-is
	JSLIB.namespace("JSLIB.event");
	JSLIB.event = new function () {
	
	    return {
			// used by ondomready handling. may use onload as fall back but this var indicate whether DOMContentLoaded event handled already
			_domReadyDone:false,
			_domReadyTimer:null,
			_domReadyCallback:[],
			_domReadySetup:false,
			
			// event cache to handle memory leak under <= IE6
			_eventCache:[],
	
			// cache events and they will be cleared upon page unload to deal with memory leak
			// two modes of operations:
			// 1. obj.onsomething = func; - JSLIB.event.cacheEvent(obj, "onsomething", null);
			// 2. eventListener; - JSLIB.event.cacheEvent(obj, "something", func);
			cacheEvent:function(elem, eventName, callbackFunc) {
				this._eventCache[this._eventCache.length] = [elem, eventName, callbackFunc];
			},
			
			// call in unload
			flushEvents:function() {
				var i, eC = this._eventCache, obj;
				for (i = eC.length - 1; i >= 0; i --) {
					obj = JS$(eC[i][0]);
					if (!obj) {
						continue;
					}
					if (eC[i][0] == window && eC[i][1] == "unload") {
						eC[i][2]();
					}
					else {
						if (eC[i][2]) {
							this.removeListener(eC[i][0], eC[i][1], eC[i][2]);
						}
						else {
							obj.setAttribute(eC[i][1], null);
						}
					}
				}
			},
	
			_domReadyHandler:function(evtData) {
				if (!JSLIB.event._domReadyDone) {
					JSLIB.event._domReadyDone = true;
					for (var i = 0; i < JSLIB.event._domReadyCallback.length; i ++) {
						JSLIB.event.callHandler(JSLIB.event._domReadyCallback[i], evtData);
					}
				}
			},
			
	        addListener:function(elem, eventName, callbackFunc) {
				var obj = JS$(elem), i, r;
				if (JSLIB.util.isArray(obj)) {
					r = true;
					for (i = 0; i < obj.length; i ++) {
						if (!this.addListener(obj[i], eventName, callbackFunc)) {
							r = false;
						}
					}
					return r;
				}
				else if (JSLIB.util.isNull(elem)) {
					return false;
				}
				else if (JSLIB.util.isObject(eventName)) {
					r = true;
					for (i in eventName) {
						if (!this.addListener(elem, i, eventName[i])) {
							r = false;
						}
					}
					return false;
				}
				else if (eventName.toLowerCase() == "domready") {
					this._domReadyCallback.push(callbackFunc);
				
					if (!this._domReadySetup) {
						this._domReadySetup = true;
						var func = function(evt) {
								JSLIB.event.callHandler(JSLIB.event._domReadyHandler, new JSLIB.eventData(obj, evt));
							};
				
						/*
						// newer webkit has DOMContentLoaded listener. use onload handler to take care of older webkit browsers
						else if (JSLIB.browser.browser == "safari" &&
								JSLIB.browser.version < 525) { // older safari or webkit browser
							setInterval(function() {
									if (/loaded|complete/.test(document.readyState)) {
										window.clearInterval(JSLIB.event._domReadyTimer);
										JSLIB.event.callHandler(JSLIB.event._domReadyHandler, new JSLIB.eventData(obj, null));
									}
								}, 10);					
						}
						*/
						if (obj.addEventListener) { // all other DOM compatible browsers and IE9
							obj.addEventListener("DOMContentLoaded", func, false);
							this.cacheEvent(obj, "DOMContentLoaded", func);
						}
						// idea from http://www.javascriptkit.com/dhtmltutors/domready.shtml
						// also from http://snipplr.com/view/2337/ondomready/
						else if (obj.attachEvent){ // IE
							document.write('<script type="text/javascript" id="JSLIB_contentloadtag" defer="defer" src="//javascript:void(0)"><\/script>');
							var contentloadtag=document.getElementById("JSLIB_contentloadtag");
							contentloadtag.onreadystatechange=function(evt){
								if (this.readyState=="complete"){
									JSLIB.event.callHandler(JSLIB.event._domReadyHandler, new JSLIB.eventData(obj, window.event));
								}
							}
							this.cacheEvent(contentloadtag, "onreadystatechange", null);
						}
		
						// use onload handler in case browser does not support DOMContentLoaded
						this.__addListenerSub(obj, "load", func);
					}
				}
				else {
					return this.__addListenerSub(elem, eventName, callbackFunc, true);
				}
			},
			
	        __addListenerSub:function(elem, eventName, callbackFunc, cacheWindowUnloads) {
	            var obj = JS$(elem);
	            if (!obj) {
	                return false;
	            }
	
	            var wrapfunc = function(evt) {
	                var realEvent = evt || window.event;
	                return JSLIB.event.callHandler(callbackFunc, new JSLIB.eventData(obj, realEvent));
	            };
	
	            if (obj.addEventListener) { // DOM browsers and IE9
					if (obj == window && eventName == "unload") {
						if (cacheWindowUnloads) {
							this.cacheEvent(obj, eventName, wrapfunc);
						}
						else {
							obj.addEventListener(eventName, wrapfunc, false);
						}
					}
					else {
						obj.addEventListener(eventName, wrapfunc, false);
						this.cacheEvent(obj, eventName, wrapfunc);
					}
	                return true;
	            }
	            else if(obj.attachEvent){ // older IE
					if (obj == window && eventName == "unload") {
						if (cacheWindowUnloads) {
							this.cacheEvent(obj, eventName, wrapfunc);
						}
						else {
							obj.attachEvent("on"+eventName, wrapfunc);
						}
					}
					else {
						obj.attachEvent("on"+eventName, wrapfunc);
						this.cacheEvent(obj, eventName, wrapfunc);
					}
	                return true;
	            }
	            return false;
	        },
	        
	        bind:function(elem, eventName, callbackFunc) {
	        	return this.addListener(elem, eventName, callbackFunc);
	        },
	        
	        ready:function(callbackFunc) {
	        	return this.addListener(window, "domready", callbackFunc); 
	        },
	        
	        click:function(elem, callbackFunc) {
	        	return this.addListener(elem, "click", callbackFunc);
	        },
	
			removeListener:function(elem, eventName, callbackFunc) {
	            var o = JS$(elem);
	            if (!o) {
	                return false;
	            }
				
				if(o.removeEventListener){
					o.removeEventListener(eventName, callbackFunc, false);
				}
				else if(o.detachEvent){
					o.detachEvent("on"+eventName, callbackFunc);
				};
				
				// should remove from event cache
			},
			
			relatedTarget:function (ed) {
				if (ed.domEvent) {
					return (ed.domEvent.relatedTarget ? ed.domEvent.relatedTarget : ed.domEvent.toElement);
				}
			},

	        preventDefault:function (ed) {
	            if (JSLIB.browser.browser == "msie") {
	                ed.domEvent.returnValue = false;
	            }
	            else if (ed.domEvent.preventDefault) {
	                ed.domEvent.preventDefault();
	            }
	        },
	
	        stopPropagation:function (ed) {
	            if (JSLIB.browser.browser == "msie") {
	                ed.domEvent.cancelBubble = true;
	            }
	            else if (ed.domEvent.stopPropagation) {
	                ed.domEvent.stopPropagation();
	            }
	        },
	        
			callHandler : function(callback, handlerData) {
				if (JSLIB.util.isFunction(callback)) {
					return callback(handlerData);
				}
				else if (JSLIB.util.isObject(callback)) {
					if (callback.callback) {
						var scope = callback.scope ? callback.scope : (callback.context ? callback.context : (callback.obj ? callback.obj : null));
						if (JSLIB.util.isNull(handlerData)) {
							handlerData = typeof callback.data == "undefined" ? null : callback.data;
						}
						else {
							if (typeof handlerData == "undefined") {
								handlerData = {};
							}
							if (JSLIB.util.isObject(handlerData) && callback.data) {
								handlerData.data = callback.data;
							}
						}
						
						if (scope) {
							return callback.callback.call(scope, handlerData);
						}
						else {
							return callback.callback(handlerData);
						} 			
					}
				}
				return false;
			}	        
	    }
	}
	JSLIB.event.__addListenerSub(window, "unload", function() { JSLIB.event.flushEvents(); }, false);
}

if (!JSLIB.customEvent) {
	JSLIB.namespace("JSLIB.customEvent");
	JSLIB.customEvent = function (p) {
	    JSLIB.callParentConstructor(JSLIB.object, this);
	    this.chained = false;
        this._listeners = [];
		if (p) {
	        if (p.chained) {
	        	this.chained = true;
	        }
	    }
	}
	JSLIB.inherits(JSLIB.object, JSLIB.customEvent);
	
	JSLIB.customEvent.prototype.numberOfSubscribers = function () {
		var count = 0;
		for(var i in this._listeners) {
			count++;
		}
		return count;
	}	
	
	// listener: object has properties 
	//   callback (callback function), 
	//   context/scope/obj (callback will be under this scope)
	//   data (parameter that will be passed to callback)
	JSLIB.customEvent.prototype.subscribe = function (listener) {
	    if (JSLIB.util.isObject(listener)) {
	        var id = this._listeners.length;
	        this._listeners[id] = listener;
	        return id;
	    }
	    else {
	        return -1;
	    }
	}
	
	JSLIB.customEvent.prototype.unsubscribe = function (id) {
		var arr = this._listeners;
	    if (arr[id]) {
	        delete arr[id];
	        arr[id] = null;
	    }
	}
	
	JSLIB.customEvent.prototype.fireEvent = function (handlerData) {
		var arr = this._listeners, i;
		if (this.chained) {
			if (arr.length > 0) {
	        	JSLIB.event.callHandler(
					arr[0],
					{
						event: this, 
						data: (typeof handlerData == "undefined" ? null : handlerData),
						index: 0
					});
			}
		}
		else {
		    for (i = 0; i < arr.length; i ++) {
	        	JSLIB.event.callHandler(arr[i], typeof handlerData == "undefined" ? null : handlerData);
		    }
		}
	}
	
	JSLIB.customEvent.prototype.callNextHandler = function (data) {
		var arr = this._listeners;
		data.index ++;
		if (data.index < arr.length) {
			JSLIB.event.callHandler(arr[data.index], data);
		}
	}
}


