/*
Events.2.js





- 10/20/2006 - Fixed bug in removeEvent, where all handlers were wiped out.  TJB

*/

// ------------------------------------------------------------
// Requirements.
// ------------------------------------------------------------
if (typeof dbg != "function") {
	var dbg = function () {};
};

// ------------------------------------------------------------
// Utilities.
// ------------------------------------------------------------
function hitch (obj, meth) {
	return function () {
		return typeof meth == "function" ? meth.apply(obj, arguments) : obj[meth].apply(obj, arguments);
	};
};


// ------------------------------------------------------------
// Events
// ------------------------------------------------------------

Events_class = function() {
	this.init();
};

Events_class.prototype.init = function() {
	this.dbgColor = "#006666";
	this.debug = true;
	this.triggerElements = new Array();

	// memory cleanup
	this.add({
		element: window,
		eventType: 'unload',
		handler: this.clearTriggers,
		context: this
	});
};

Events_class.prototype.add = function (/* element, eventType, [object || function], [method] */) {


	// Consolidate arguments

	var element;
	var eventType;
	var handler;
	var delay = 0;
	var dataPackage;
	var trace = false;
	var context = false;
	var label = "";

	if (arguments.length == 1) {

		element = arguments[0].element;
		eventType = arguments[0].type;
		handler = arguments[0].handler;
		delay = parseInt(arguments[0].delay) || 0;

		if (typeof element == "undefined" || typeof eventType == "undefined" || typeof handler == "undefined") {
			return false;
		};

		context = typeof arguments[0].context != "undefined" ? arguments[0].context : false;
		dataPackage = typeof arguments[0].data != "undefined" ? arguments[0].data : {};
		trace = (arguments[0].trace == true);
		label = typeof arguments[0].label != "undefined" ? arguments[0].label : "";

		if (context) {
			// will execute "handler" as a method of object instance: "context"
			handler = hitch(context, handler);
		};
	}
	else {

		element = arguments[0];
		eventType = arguments[1];

		// the third argument can be either a function pointer, or an object with a method reference
		if (typeof arguments[2] == "object" && typeof arguments[3] != "undefined") {
			// will execute "handler" as a method of object instance: "context"
			handler = hitch(context, handler);
			dataPackage = typeof arguments[4] == "undefined" ? {} : arguments[4];
		}
		else if (typeof arguments[2] == "function") {
			// executes handler as method of window (free floating function)
			handler = arguments[2];
			dataPackage = typeof arguments[3] == "undefined" ? {} : arguments[3];
		}
		else {
			return false;
		};
	};



	// CREATE AND STORE EVENT HANDLER
	// This puts a function reference on an array associated with the element an eventType. Handlers
	// so stored will be fired by the trigger function in sequence, the handler will always have
	// an event object passed to it because the trigger handles cross-browser event parameter issues

	// closure variables: element, eventType, handler, dataPackage, trace, label
	var evtHandler = function(e) {
		if (trace) {
			dbg("Event Triggered", "", "");
			dbg("Event Info", eventType, "");
			dbg("Object Info", element.tagName + (element.id ? ": " + element.id : ""));
			dbg("Handler Label", label);
		};
		handler(e, element, dataPackage);
	}

	this.storeHandler(element, eventType, evtHandler);



	// CREATE TRIGGER
	// The trigger will fire all handlers associated with a given element/eventType, the purpose of
	// the trigger is to encapsulate cross-browser parameter issues and to consolidate multiple
	// event references per element

	// re-reference "this" for the trigger closure
	var self = this;

	// create a closure to allow us to customize the event handler call
	var trigger = function () {
		// dbg("trigger firing on element: " + element);
		var e = arguments.length ? arguments[0] : window.event;
		self.runHandlers(e, element, eventType);
	};

	// create a delay if requested
	if (delay) {
		trigger = this.createDelayedTrigger(element, eventType, trigger, delay);
	}

	return this.setTrigger(element, eventType, trigger);

};

Events_class.prototype.setTrigger = function(element, eventType, fnTrigger) {

	if (!fnTrigger) {
		dbg("Events.setTrigger: no trigger function provided");
		return false;
	};

	// default the trigger storage spot
	element._triggers = element._triggers || new Object();

	// wipe old trigger and old JS event (if any)
	this.removeTrigger(element, eventType);

	// store the new trigger function
	element._triggers[eventType] = fnTrigger;

	// element needs to be stored for memory cleanup at page unload
	this.registerTriggerElement(element);

	// Add trigger as the actual event that fires
	return this.addJSEvent(element, eventType, fnTrigger);
};

// wipe the old trigger, if one existed
Events_class.prototype.removeTrigger = function(element, eventType) {
	if (element._triggers && element._triggers[eventType]) {
		var fnTrigger = element._triggers[eventType] || null;
		this.removeJSEvent(element, eventType, fnTrigger);
		element._triggers[eventType] = null;
	};
}

// memory cleanup function
Events_class.prototype.clearTriggers = function(els) {
	var rElements = new Array();
	if (els !== undefined) {
		if (els instanceof Array) {
			rElements = els;
		}
		else {
			rElements = new Array(els);
		};
	} else {
		rElements = this.triggerElements || new Array();
	};
	for(var i=0, thisEl, eventType; thisEl=rElements[i]; i++) {
		if (thisEl._triggers !== undefined) {
			for(eventType in thisEl._triggers) {
				this.removeTrigger(thisEl, eventType);
			};
			delete(thisEl._triggers);
		};
	};
};

Events_class.prototype.registerTriggerElement = function(element) {
	var found = false;
	for(var i=0, elCount = this.triggerElements.length, thisEl; thisEl = this.triggerElements[i]; i++) {
		found = found || ( thisEl == element );
		if (found) {
			return;
		};
	};
	if (!found) {
		this.triggerElements.push(element);
	};
};



// low-level event add/remove wrappers

Events_class.prototype.removeJSEvent = function(element, eventType, fnHandler) {
	if (fnHandler !== undefined) {
		if (element.removeEventListener) {
			// firefox
			element.removeEventListener(eventType, fnHandler, false);
			result = true;
		}
		else if (element.detachEvent) {
			// ie
			result = element.detachEvent("on" + eventType, fnHandler);
		}
		return result
	}
	return false;
};

Events_class.prototype.addJSEvent = function(element, eventType, fnHandler) {
	if (element && eventType && fnHandler) {
		if (element.addEventListener) {
			// firefox
			element.addEventListener(eventType, fnHandler, false);
			return true;
		}
		else if (element.attachEvent) {
			// ie
			return element.attachEvent("on" + eventType, fnHandler);
		};
	}
	return false;
};


// a handler is an individual operation assigned to an element/event

Events_class.prototype.storeHandler = function(el, eventType, fnHandler) {
	// dbg("Events: store trigger", eventType, this.dbgColor);
	if (el && eventType && fnHandler) {

		// default storage stuff, the triggers object is an associative array
		// of all the assigned event types which are assigned to this element
		el._handlers = el._handlers || new Object();
		el._handlers[eventType] = el._handlers[eventType] || new Array()
		var rHandlers = el._handlers[eventType];

		// push the function reference onto the handlers array
		rHandlers.push(fnHandler);
	};
};

Events_class.prototype.runHandlers = function(e, element, eventType) {
	var rHandlers = element._handlers[eventType];
	if (rHandlers && rHandlers instanceof Array) {
		for(var i=0, handlerCount = rHandlers.length; i < handlerCount; i++) {
			rHandlers[i](e);
		};
	};
};



// REMOVE wipes all handlers tied to a given event/type

Events_class.prototype.removeEvent = function(element, eventType) {
	this.removeTrigger(element, eventType);
	if (element._handlers && element._handlers[eventType]) {
		element._handlers[eventType] = new Array();
	}
};

Events_class.prototype.remove = Events_class.prototype.removeEvent;




// Delayed event firing, creates a trigger which is actually a timer reset that calls
// the originally defined trigger

Events_class.prototype._delayedTriggers = new Object();

Events_class.prototype.createDelayedTrigger = function(el, eventType, trigger, delay) {

	// create an id that we can refer to with a string because that's the way setTimeout likes it
	var d = new Date();
	var id = "delayed_event_" + this.getUniqueID(el) + "_" + eventType + "_" + d.getTime();

	// store the trigger in a separate timer array
	this._delayedTriggers[id] = trigger;

	// create a timer object and the command it will fire
	var eventTimer = null;
	var cmd = "Events._delayedTriggers['" + id + "']()";

	return function() {
		// closure variables: eventTimer, cmd, delay
		if (eventTimer) {
			clearTimeout(eventTimer);
		}
		eventTimer = setTimeout(cmd, delay);
	}
};


Events_class.prototype.idCounter = 0;

Events_class.prototype.getUniqueID = function(el) {
	if (el.id) return el.id;
	return this.idCounter++;
};






// Events cancelling, halts execution of default event stuff
// using passed event object

Events_class.prototype.cancelEvent = function(event) {
	if (event) {
		if (event.stopPropagation) {
			event.stopPropagation();
		} else {
			event.cancelBubble = true;
		}
		if (event.preventDefault) {
			event.preventDefault();
		} else {
			event.returnValue = false;	
		}
	};
	return false;
};

Events_class.prototype.cancel = Events_class.prototype.cancelEvent;





// Determines object which fired event from event object reference
// The second argument of every handler is now a direct reference to the assigned element, so I
// do not see the value of this function anymore, but it exists here for backwards compatibility

Events_class.prototype.getSrcElement = function(/* native js event object */) {
	var el = arguments[0].srcElement || arguments[0].currentTarget || window;
	try {
		/* Safari triggers events on text nodes */
		if (el.nodeType == 3) {
			el = el.parentNode;
		};
	}
	catch (e) {
		el = window;
	};
	return el;
};



// Not sure these really belong in here, preserved for backwards compatibility

Events_class.prototype.disableTextSelect = function() {
	if (!document.onselectstart) {
		document.onselectstart = function() {
			return false;
		};
	}
};

Events_class.prototype.removeTextSelect = Events_class.prototype.disableTextSelect; // backward compat

Events_class.prototype.enableTextSelect = function() {
	if (document.onselectstart) {
		document.onselectstart = null;
	}
};




var Events = new Events_class();