Return to Snippet

Revision: 44563
at June 29, 2012 06:11 by wizard04


Updated Code
/*****************************************
 * Events Max v10.0
 * Enhanced cross-browser event handling
 * 
 * This work is licensed under a Creative Commons Attribution 3.0 Unported License
 * http://creativecommons.org/licenses/by/3.0/
 *
 * Author: Andy Harrison, http://dragonzreef.com/
 * Date: 28 June 2012
 *****************************************/

//Cross-browser event registration functions
//Supports both capture and bubble phases
//Handlers execute in FIFO order
//Supports mouseenter and mouseleave events
//Custom event attributes:
//  event.mouse.button: the mouse button value (1, 2, or 4) for mousedown, mouseup, click, and dblclick events
//  event.mouse.position: object containing the mouse positions within the screen, window, document, and layer (e.g., evt.mouse.position.document.x)
//  event.mouse.wheelDelta: distance ("clicks") the mouse wheel rolled; negative means it rolled up
//  event.keyboard.charCode: Unicode character code that was generated on a keypress event
//  event.keyboard.char: Unicode character that was generated on a keypress event
//
//addEventHandler(obj, type, handler, useCapture)
//removeEventHandler(obj, type, handler_or_guid, useCapture)
//
//Event types are case-sensitive and should not include "on" (e.g., use "click", not "onclick")
//
//Usage examples:
//  addEventHandler(element, "click", handlerFunction, true);
//  removeEventHandler(element, "click", handlerFunction, true);
//or:
//  var guid = addEventHandler(element, "click", function(evt){doSomething()});
//  removeEventHandler(element, "click", guid);
//
//This script uses a custom event attribute for the mouse button value: event.mouse.button
//This goes by the Microsoft model, where left==1, right==2, and middle==4
//In IE lte 8, the value may be incorrect on mousedown since we can't reliably keep track of the event.button value between events.
// (e.g., if the mouse leaves the window, buttons are changed by the user without the browser's detection, and the mouse comes back)
//
//There are a few custom attributes used by this script that must not be manipulated:
// ._handlerGUID on functions passed as handlers
// ._eventHandlers on DOM objects that have handlers assigned to them
//
//Be aware that browsers act differently when the DOM is manipulated. Much of it seems to have to do with when the propagation path is
// determined for events (e.g., as soon as they're added to the event loop vs. just before they're dispatched).  Some fire mouseover/out
// events when an element is added/removed/positioned under the mouse, some stop bubbling an event if the currentTarget has been removed
// from the DOM, etc.
//
//Techniques and inspiration largely from:
// http://dean.edwards.name/weblog/2005/10/add-event2/
// http://outofhanwell.wordpress.com/2006/07/03/cross-window-events/
// http://blog.metawrap.com/2005/10/24/ie-closures-leaks/
// http://www.quirksmode.org/js/events_properties.html



//addEventHandler(obj, type, handler, useCapture)
//returns the GUID assigned to the handler
var addEventHandler;

//removeEventHandler(obj, type, handler_or_guid, useCapture)
var removeEventHandler;

(function (){
	
	"use strict";
	
	
	/*** event handler registration ***/
	
	var newGUID = 1;	//GUID to assign to the next event handler function without one
	
	addEventHandler = function (obj, type, handler, useCapture){
		
		type = ""+type;
		
		if(!(obj instanceof Object || (!document.addEventListener && typeof(obj) === "object"))){
			throw new TypeError("Invalid argument: obj");
		}
		if(!(/^[0-9a-z]+$/i).test(type)){
			throw new TypeError("Invalid argument: type");
		}
		if(typeof(handler) !== "function"){
			throw new TypeError("Invalid argument: handler");
		}
		
		var ownerWindow = getOwnerWindow(obj);
		
		//make sure the object's window flushes handlers when the page unloads to avoid memory leaks
		if(!flushAllHandlers.adding && !handlerIsAssigned(ownerWindow, "unload", "bubble", flushAllHandlers._handlerGUID)){
			flushAllHandlers.adding = true;
			addEventHandler(ownerWindow, "unload", flushAllHandlers);
			flushAllHandlers.adding = false;
		}
		
		if(type === "mouseenter"){
			//make sure the object's window handles the mouseover type so that custom events can be triggered after the bubble phase of mouseenter
			addTypeHandler(ownerWindow, "mouseover");
			
			//add global handlers for the mouseover event type for the object (if they don't already exist)
			addTypeHandler(obj, "mouseover");
			if(!obj._eventHandlers) obj._eventHandlers = {};
			if(!obj._eventHandlers["mouseenter"]) obj._eventHandlers["mouseenter"] = { capture: [], bubble: [] };
		}
		else if(type === "mouseleave"){
			//make sure the object's window handles the mouseout type so that custom events can be triggered after the bubble phase of mouseleave
			addTypeHandler(ownerWindow, "mouseout");
			
			//add global handlers for the mouseout event type for the object (if they don't already exist)
			addTypeHandler(obj, "mouseout");
			if(!obj._eventHandlers) obj._eventHandlers = {};
			if(!obj._eventHandlers["mouseleave"]) obj._eventHandlers["mouseleave"] = { capture: [], bubble: [] };
		}
		else{
			//add global handlers for the event type for the object (if they don't already exist)
			addTypeHandler(obj, type);
		}
		
		if(isNaN(handler._handlerGUID) || handler._handlerGUID < 1 || handler._handlerGUID === Infinity){
			handler._handlerGUID = newGUID++;	//assign a GUID to the handler if it doesn't have one (or if the user messed with it)
		}
		
		var phase = useCapture ? "capture" : "bubble";
		if(!handlerIsAssigned(obj, type, phase, handler._handlerGUID)){	//if this handler isn't already assigned to this object, event type, and phase
			obj._eventHandlers[type][phase].push({ guid: handler._handlerGUID, handler: handler });	//add the handler to the list
		}
		
		return handler._handlerGUID;
		
	};
	
	//get the window in which the object resides; this is not necessarily the same window where a function is defined
	function getOwnerWindow(obj){
		return (obj.ownerDocument || obj.document || obj).parentWindow || window;
		/*      obj==element         obj==window     obj==document */
	}
	
	//add global handlers for an event type for an object (if they don't already exist)
	function addTypeHandler(obj, type){
		if(!obj._eventHandlers) obj._eventHandlers = {};
		if(!obj._eventHandlers[type]){
			obj._eventHandlers[type] = { capture: [], bubble: [] };
			
			if(document.addEventListener){	//not IE lte 8
				obj.addEventListener(type, patchHandler(obj, handleEvent, true), true);
				obj.addEventListener(type, patchHandler(obj, handleEvent), false);
			}
			else if(obj.attachEvent){	//IE lte 8
				obj.attachEvent("on"+type, patchHandler(obj, handleEvent));
			}
			else{	//just in case
				if(obj["on"+type]){	//if there is already a handler assigned
					obj["on"+type]._handlerGUID = newGUID;
					obj._eventHandlers[type]["bubble"][0] = { guid: newGUID++, handler: obj["on"+type] };
				}
				obj["on"+type] = patchHandler(obj, handleEvent);
			}
		}
	}
	
	function patchHandler(obj, handler, capturing){
		
		return function (evt){
			//In IE lte 8, if this patched handler is assigned to an event attribute on a DOM node instead of using attachEvent(), 
			// we need to get the event object from the window the node resides in (which is not necessarily where the handler was defined).
			var evtWindow = getOwnerWindow(obj);
			evt = evt || evtWindow.event;
			if(!evt.view) evt.view = evtWindow;
			
			patchEvent.call(obj, evt, capturing);
			
			handler.call(obj, evt);	//applies the correct value for the `this` keyword and passes the patched event object
		};
		
	}
	
	//is the handler for this object, event type, and phase already in the list?
	function handlerIsAssigned(obj, type, phase, guid){
		if(!obj._eventHandlers || !obj._eventHandlers[type] || !guid) return false;
		var handlerList = obj._eventHandlers[type][phase];
		for(var i=0; i<handlerList.length; i++){
			if(handlerList[i].guid === guid)
				return true;	//handler is already in the list
		}
		return false;
	}
	
	
	
	/*** event handling ***/
	
	var _evtStatus = {};	//.capturing, .propagationStopped, .propagationStoppedAtTarget, .propagationStoppedImmediately
	var mouseELQueue = [];	//propagation path for mouseenter and mouseleave events
	
	function handleEvent(evt){
		
		var returnValue, evtClone, path, handlers, i, j;
		
		returnValue = evt.type==="mouseover" ? false : evt.type!=="beforeunload" ? true : returnValue;
		
		//test whether an object is still in the DOM or if it has been removed by a previous handler
		function inDOM(obj){
			if(!obj) return false;
			if(obj === evt.view || obj === evt.view.document) return true;
			var tmp = obj;
			do{
				if(!tmp.parentNode) return false;	//object is not in the DOM
				tmp = tmp.parentNode;
			}while(tmp !== evt.view.document)
			return true;
		}
		
		function updateReturnValue(evt, newValue){
			if(evt.type === "mouseover"){
				returnValue = returnValue === true || newValue === true || evt.defaultPrevented;
			}
			else if(evt.type === "beforeunload"){
				//in this implementation, only the first defined return value will be used; return values from subsequent handlers will be ignored
				returnValue = typeof(returnValue) !== "undefined" ? returnValue : newValue;
			}
			else{
				returnValue = !evt.cancelable || (returnValue && newValue !== false && !evt.defaultPrevented);
			}
		}
		
		
		/*** mouseenter & mouseleave preparations ***/
		
		function fillMouseELQueue(evt){
			
			//note: this can get screwed up if elements are moved/removed from the DOM
			
			var obj, obj2;
			
			mouseELQueue = [];
			
			if(evt.target === evt.relatedTarget){
				//do nothing
			}
			else if(evt.target === evt.view){
				//related is a child of window; did not enter or leave window; do nothing
			}
			else if(evt.relatedTarget === null){
				//entered/left window; events will be fired
				obj = evt.target;
				while(obj){
					mouseELQueue.push(obj);
					obj = obj.parentNode;
				}
				mouseELQueue.push(evt.view);
			}
			else{
				obj = evt.relatedTarget;
				while(obj && obj !== evt.target){
					obj = obj.parentNode
				}
				if(obj === evt.target){
					//related is a child of target; did not enter or leave target; do nothing
				}
				else{
					//related is not a child of target (but target is not necessarily a child of related);
					// entered/left target; possibly left/entered related; events will be fired
					obj = evt.target;
					while(obj && obj !== evt.relatedTarget){
						obj2 = evt.relatedTarget;
						while(obj2 && obj2 !== obj){
							obj2 = obj2.parentNode;
						}
						if(obj === obj2){	//common ancestor of target & related (mouse left/entered related)
							break;
						}
						mouseELQueue.push(obj);	//obj is a child of related
						obj = obj.parentNode;
					}
				}
			}
			
		}
		
		//at beginning of capture phase, fill mouseELQueue (if applicable)
		if((evt.type === "mouseover" || evt.type === "mouseout") && _evtStatus.capturing && this === evt.view){
			fillMouseELQueue(evt);
		}
		
		
		/*** manually run capture phase, if required ***/
		
		//for IE lte 8, run capture phase manually
		if(!document.addEventListener && evt.eventPhase === 2){
			
			//create copy of event object (so we can modify read-only properties)
			evtClone = createEventClone(evt);
			evtClone.eventPhase = 1;
			
			//at beginning of capture phase, fill mouseELQueue (if applicable)
			if(evt.type === "mouseover" || evt.type === "mouseout"){
				fillMouseELQueue(evtClone);
			}
			
			path = getPropagationPath(evt);	//array of objects with related event handler (not including the target)
			for(i=path.length-1; i>=0; i--){	//for each element in the propagation path array
				if(_evtStatus.propagationStopped) break;
				
				//update event object
				evtClone.currentTarget = path[i];
				
				//execute the capture handlers (in FIFO order)
				handlers = path[i]._eventHandlers[evtClone.type]["capture"];
				for(j=0; j<handlers.length; j++){
					//execute the handler and update the return value
					updateReturnValue(evtClone, handlers[j].handler.call(path[j], evtClone));
					if(_evtStatus.propagationStoppedImmediately) break;
				}
			}
			
			//execute the capture handlers on the target (in FIFO order)
			if(!_evtStatus.propagationStopped){
				evtClone.eventPhase = 2;
				evtClone.currentTarget = this;
				handlers = this._eventHandlers[evtClone.type]["capture"];
				for(i=0; i<handlers.length; i++){
					//execute the handler and update the return value
					updateReturnValue(evtClone, handlers[i].handler.call(this, evtClone));
					if(_evtStatus.propagationStoppedImmediately) break;
				}
			}
			
			evtClone = null;
			
		}
		
		
		/*** process handlers for currentTarget ***/
		
		//execute the handlers for this phase (in FIFO order)
		if(!_evtStatus.propagationStopped || (evt.eventPhase===2 && _evtStatus.propagationStoppedAtTarget)){
			
			handlers = this._eventHandlers[evt.type][_evtStatus.capturing ? "capture" : "bubble"];
			for(i=0; i<handlers.length; i++){
				if(_evtStatus.propagationStoppedImmediately) break;
				//execute the handler and update the return value
				updateReturnValue(evt, handlers[i].handler.call(this, evt));
			}
			
		}
		
		if((evt.type === "mouseover" && returnValue === true) || (evt.type !== "mouseover" && returnValue === false)){
			evt.preventDefault();
		}
		
		
		/*** finalize event ***/
		
		//if done handling this event
		if(!_evtStatus.capturing && (this === evt.view || !evt.bubbles)){
			
			//trigger mouseenter events, if applicable
			if(evt.type === "mouseover" && mouseELQueue.length > 0){
				evtClone = createEventClone(evt);
				while(mouseELQueue.length > 0){
					evtClone.target = mouseELQueue.pop();
					triggerCustomEvent(evtClone, "mouseenter", false);
				}
			}
			//trigger mouseleave events, if applicable
			else if(evt.type === "mouseout" && mouseELQueue.length > 0){
				evtClone = createEventClone(evt);
				while(mouseELQueue.length > 0){
					evtClone.target = mouseELQueue.shift();
					triggerCustomEvent(evtClone, "mouseleave", false);
				}
			}
			
			//reset event status
			_evtStatus = {};
			
		}
		
		
		evt.returnValue = returnValue;
		return returnValue;
		
	}
	
	//get hierarchical array of objects with handlers for a specific event; first item is object closest to target (target is not included)
	function getPropagationPath(evt){
		
		var path = [];
		var obj = evt.target;
		var handlers;
		
		while(obj.parentNode){
			obj = obj.parentNode;
			if(!obj._eventHandlers || !obj._eventHandlers[evt.type]) continue;
			handlers = obj._eventHandlers[evt.type];
			if(handlers["capture"].length > 0 || handlers["bubble"].length > 0){
				path.push(obj);
			}
		}
		if(evt.target !== evt.view && evt.view._eventHandlers && evt.view._eventHandlers[evt.type]){
			handlers = evt.view._eventHandlers[evt.type];
			if(handlers["capture"].length > 0 || handlers["bubble"].length > 0){
				path.push(evt.view);
			}
		}
		
		return path;
		
	}
	
	function patchEvent(evt, capturing){
		
		if(!evt.target) evt.target = evt.srcElement;
		if(!evt.srcElement) evt.srcElement = evt.target;
		if(!evt.relatedTarget) try{ evt.relatedTarget = evt.target===evt.toElement ? evt.fromElement : evt.toElement; }catch(e){}
		if(!evt.currentTarget) evt.currentTarget = this;
		if(!evt.eventPhase) evt.eventPhase = evt.target===this ? 2 : 3;	//capturing==1 (not supported), at_target==2, bubbling==3
		_evtStatus.capturing = capturing;	//to determine, when evt.eventPhase===2, whether we need to execute capture or bubble handlers on the target
		
		var originalPreventDefault = evt.preventDefault;
		evt.preventDefault = function (){
			if(this.cancelable){
				if(originalPreventDefault) originalPreventDefault();
				this.returnValue = false;
				if(!this.defaultPrevented) this.defaultPrevented = true;
			}
		};
		evt.stopPropagation = function (){
			if(this.eventPhase === 2 && !_evtStatus.propagationStopped) _evtStatus.propagationStoppedAtTarget = true;
			_evtStatus.propagationStopped = true;
		};
		evt.stopImmediatePropagation = function (){
			_evtStatus.propagationStopped = true;
			_evtStatus.propagationStoppedImmediately = true;
		};
		
		
		/*** mouse event attributes ***/
		
		if(!evt.fromElement && !evt.toElement){
			try{
				if(evt.type === "mouseover" || evt.type === "mouseenter"){
					evt.fromElement = evt.relatedTarget;
					evt.toElement = evt.target;
				}
				else if(evt.type === "mouseout" || evt.type === "mouseleave"){
					evt.fromElement = evt.target;
					evt.toElement = evt.relatedTarget;
				}
			}catch(e){}
		}
		
		//add custom mouse attributes
		evt.mouse = {};
		
		//mouse button
		//this is the button that was pressed to trigger this event: 1==left, 2==right, 4==middle
		if(evt.type === "mousedown" || evt.type === "mouseup" || evt.type === "click" || evt.type === "dblclick"){
			if(evt.which){
				evt.mouse.button = evt.which===1 ? 1 : evt.which===2 ? 4 : 2;
			}
			else{	//IE lte 8
				var mb = patchEvent.mouseButtons;
				if(evt.target === this){	//update mb.button
					if(evt.type === "mousedown"){
						mb.button = (evt.button ^ mb.pressed) & evt.button;
						if((mb.button & evt.button) === 0) mb.button = evt.button;
						mb.pressed = evt.button;
						
						//note: mb.button may be incorrect on mousedown since we can't reliably keep track of IE's event.button
						// value (i.e., which buttons are pressed when the event is fired) between events (e.g., if the mouse
						// leaves the window, buttons are changed by the user without the browser's detection, and the mouse comes back)
					}
					else if(evt.type === "mouseup"){
						mb.button = evt.button;
						mb.pressed = ~evt.button & mb.pressed;
					}
				}
				evt.mouse.button = mb.button;
			}
		}
		else{
			evt.mouse.button = patchEvent.mouseButtons.button = 0;
		}
		
		//mouse wheel distance
		//this is the distance ("clicks") the mouse wheel rolled; negative means it rolled up
		if((evt.type==="mousewheel" || evt.type==="wheel" || evt.type==="DOMMouseScroll") && (evt.wheelDelta || evt.detail)){
			evt.mouse.wheelDelta = evt.wheelDelta ? -evt.wheelDelta/120 : evt.detail ? evt.detail/3 : 0;
		}
		
		//mouse position
		if(evt.type.slice(0,5) === "mouse" || evt.type==="wheel" || evt.type==="DOMMouseScroll" || evt.type.slice(0,4)==="drag" || evt.type==="drop"){
			evt.mouse.position = {};
			evt.mouse.position.screen = { x:evt.screenX, y:evt.screenY, left:evt.screenX, top:evt.screenY };
			evt.mouse.position.window = evt.mouse.position.frame = { x:evt.clientX, y:evt.clientY, left:evt.clientX, top:evt.clientY };
			evt.mouse.position.document = (function(){
				if(isNaN(evt.pageX) || isNaN(evt.pageY)){
					var left, top;	//scroll position of document
					if(window.pageYOffset)	//all except IE
					{ left = window.pageXOffset; top = window.pageYOffset; }
					else if(document.documentElement && !isNaN(document.documentElement.scrollTop))	//IE standards compliance mode
					{ left = document.documentElement.scrollLeft; top = document.documentElement.scrollTop; }
					else	//IE quirks mode
					{ left = document.body.scrollLeft; top = document.body.scrollTop; }
					return { x:left+evt.clientX, y:top+evt.clientY, left:left+evt.clientX, top:top+evt.clientY };
				}
				else return { x:evt.pageX, y:evt.pageY, left:evt.pageX, top:evt.pageY };
			})();
			evt.mouse.position.layer = { x:evt.layerX||evt.x||0, y:evt.layerY||evt.y||0, left:evt.layerX||evt.x||0, top:evt.layerY||evt.y||0 };
			
			try{
				evt.pageX = evt.mouse.position.document.x;
				evt.pageY = evt.mouse.position.document.y;
			}catch(e){}
			try{
				evt.layerX = evt.mouse.position.layer.x;
				evt.layerY = evt.mouse.position.layer.y;
			}catch(e){}
		}
		
		
		/*** keyboard event attributes ***/
		
		//add custom key attributes
		evt.keyboard = {};
		
		//see http://unixpapa.com/js/key.html
		//    http://www.quirksmode.org/js/keys.html
		if(evt.type === "keypress"){
			if(isNaN(evt.which)){	//IE lte 8
				evt.keyboard.charCode = evt.keyCode;
				evt.keyboard.char = String.fromCharCode(evt.keyCode);
			}
			else if(evt.which !== 0 && evt.charCode !== 0){	//other browsers (note: sometimes special keys still give a non-zero value)
				evt.keyboard.charCode = evt.which;
				evt.keyboard.char = String.fromCharCode(evt.which);
			}
			else{	//special key
				evt.keyboard.charCode = 0;
				evt.keyboard.char = evt.key;	//evt.key only works in IE gte 9; it gives "Control", "Shift", "Down", etc. for special keys
			}
			
			try{ evt.which = evt.keyboard.charCode; }catch(e){}
			try{ evt.keyCode = evt.keyboard.charCode; }catch(e){}
			try{ evt.charCode = evt.keyboard.charCode; }catch(e){}
			try{ evt.key = evt.keyboard.char; }catch(e){}
		}
		else if(evt.type === "keydown" || evt.type === "keyup"){
			evt.keyboard.char = evt.key;	//evt.key only works in IE gte 9
		}
		
	}
	patchEvent.mouseButtons = { button:0, pressed:0 };	//for IE lte 8; keeps track of which mouse buttons are pressed (not always accurate)
	
	function triggerCustomEvent(evt, type, bubbles){
		
		var path, handlers, i, j;
		
		//reset event status
		_evtStatus = {};
		
		//create custom event object
		evt = createCustomEventClone(evt);
		evt.type = type;
		evt.bubbles = !!bubbles;
		
		path = getPropagationPath(evt);	//array of objects with related event handler (not including the target)
		
		//run capture phase
		for(i=path.length-1; i>=0; i--){	//for each element in the propagation path array
			if(_evtStatus.propagationStopped) break;
			
			//update event object
			evt.eventPhase = 1;
			evt.currentTarget = path[i];
			
			//execute the capture handlers (in FIFO order)
			handlers = path[i]._eventHandlers[evt.type]["capture"];
			for(j=0; j<handlers.length; j++){
				handlers[j].handler.call(path[j], evt);	//execute the handler
				if(_evtStatus.propagationStoppedImmediately) break;
			}
		}
		
		//run target phase
		if(!_evtStatus.propagationStopped && evt.target._eventHandlers && evt.target._eventHandlers[evt.type]){
			//update event object
			evt.eventPhase = 2;
			evt.currentTarget = evt.target;
			
			//execute capture & bubble handlers (in FIFO order)
			handlers = evt.currentTarget._eventHandlers[evt.type]["capture"].concat(evt.currentTarget._eventHandlers[evt.type]["bubble"]);
			for(i=0; i<handlers.length; i++){
				handlers[i].handler.call(evt.target, evt);	//execute the handler
				if(_evtStatus.propagationStoppedImmediately) break;
			}
		}
		
		//if this event can bubble
		if(evt.bubbles){
			//run bubble phase
			for(i=0; i<path.length; i++){	//for each element in the propagation path array
				if(_evtStatus.propagationStopped) break;
				
				//update event object
				evt.eventPhase = 3;
				evt.currentTarget = path[i];
				
				//execute the bubble handlers (in FIFO order)
				handlers = path[i]._eventHandlers[evt.type]["bubble"];
				for(j=0; j<handlers.length; j++){
					handlers[j].handler.call(path[j], evt);	//execute the handler
					if(_evtStatus.propagationStoppedImmediately) break;
				}
			}
		}
		
		//reset event status
		_evtStatus = {};
		
	}
	
	//creates a custom object to use as an event object (e.g., based on a mouseover event to use for a mouseenter event)
	function createEventClone(eventToClone){
		var evt = {};
		for(var i in eventToClone) evt[i] = eventToClone[i];
		
		return evt;
	}
	
	//creates a custom object to use as an event object (e.g., based on a mouseover event to use for a mouseenter event)
	function createCustomEventClone(eventToClone){
		var evt = createEventClone(eventToClone);
		
		evt.cancelable = false;
		evt.returnValue = true;
		evt.defaultPrevented = false;
		
		_evtStatus.propagationStopped = false;
		_evtStatus.propagationStoppedImmediately = false;
		
		return evt;
	}
	
	
	
	/*** avoid memory leaks ***/
	
	//remove circular references and avoid memory leaks when the window unloads (especially for IE)
	
	//nulls event attributes and handler collection
	function flushEventHandlers(obj){
		obj._eventHandlers = null;
		for(var prop in obj){
			if(prop.slice(0, 2) === "on") obj[prop] = null;
		}
	}
	
	function flushAllHandlers(evt){
		var elems = evt.view.document.getElementsByTagName("*");
		for(var i=0; i<elems.length; i++){
			flushEventHandlers(elems[i]);
		}
		flushEventHandlers(evt.view.document);
		flushEventHandlers(evt.view);
	}
	flushAllHandlers.adding = false;
	
	
	
	/*** event handler removal ***/
	
	removeEventHandler = function (obj, type, handler_or_guid, useCapture){
		
		if(!(obj instanceof Object || (!document.addEventListener && typeof(obj) === "object"))){
			throw new TypeError("Invalid argument: obj");
		}
		if(!(/^[0-9a-z]+$/i).test(type)){
			throw new TypeError("Invalid argument: type");
		}
		if(( isNaN(handler_or_guid) && typeof(handler_or_guid) !== "function") || handler_or_guid < 1 || handler_or_guid === Infinity){
			throw new TypeError("Invalid argument: handler_or_guid");
		}
		
		var guid = typeof(handler_or_guid)==="function" ? handler_or_guid._handlerGUID : handler_or_guid;
		if(isNaN(guid) || guid < 1 || guid === Infinity){	//in case the user messed with ._handlerGUID
			throw new TypeError("Handler GUID is invalid");
		}
		
		if(obj._eventHandlers && obj._eventHandlers[type]){
			var handlers = obj._eventHandlers[type][useCapture ? "capture" : "bubble"];
			for(var i=0; i<handlers.length; i++){
				if(handlers[i].guid === guid){	//handler is in the list
					handlers.splice(i, 1);	//remove the handler from the list
					break;
				}
			}
		}
		
	};
	
})();

Revision: 44562
at May 25, 2012 02:43 by wizard04


Updated Code
/*****************************************
 * Events Max v6.2.2
 * Enhanced cross-browser event handling
 * 
 * This work is licensed under a Creative Commons Attribution 3.0 Unported License
 * http://creativecommons.org/licenses/by/3.0/
 *
 * Author: Andy Harrison, http://dragonzreef.com/
 * Date: 2 March 2012
 *****************************************/

//Cross-browser event registration functions
//Handlers execute in FIFO order
//Supports mouseenter and mouseleave events
//Custom event attributes:
//  event.mouse.button: the mouse button value (1, 2, or 4) for mousedown, mouseup, click, and dblclick events
//  event.mouse.position: object containing the mouse positions within the screen, window, document, and layer (e.g., evt.mouse.position.document.x)
//  event.mouse.wheelDelta: distance ("clicks") the mouse wheel rolled; negative means it rolled up
//  event.keyboard.charCode: Unicode character code that was generated on a keypress event
//  event.keyboard.char: Unicode character that was generated on a keypress event
//
//addEventHandler(obj, type, handler)
//removeEventHandler(obj, type, handler_or_guid)
//
//Usage:
//  addEventHandler(element, "click", handlerFunction);
//  removeEventHandler(element, "click", handlerFunction);
//or:
//  var guid = addEventHandler(element, "click", function(evt){doSomething()});
//  removeEventHandler(element, "click", guid);

//Techniques and inspiration largely from:
// http://www.quirksmode.org/blog/archives/2005/10/_and_the_winner_1.html
// http://dean.edwards.name/weblog/2005/10/add-event/
// http://dean.edwards.name/weblog/2005/10/add-event2/
// http://dean.edwards.name/weblog/2005/10/add-event2/#comment6264
// http://outofhanwell.wordpress.com/2006/07/03/cross-window-events/
// http://blog.metawrap.com/2005/10/24/ie-closures-leaks/
// http://www.quirksmode.org/js/events_properties.html
// http://help.dottoro.com/ljogqtqm.php

//This script uses a custom event attribute for the mouse button value: event.mouse.button
//This goes by the Microsoft model, where left==1, right==2, and middle==4
//In IE lte 8, the value may be incorrect on mousedown since we can't reliably keep track of the event.button value between events.
// (e.g., if the mouse leaves the window, buttons are changed by the user without the browser's detection, and the mouse comes back)
//
//Unlike in jQuery, mouseenter/leave events match IE in the order the events are fired. For example, instead of mouseenter being fired
// immediately after mouseover, this implementation waits for the mouseover event to bubble to the top before firing the mouseenter event.
// Also, mouseenter handlers are executed top-down (as if it were a capture phase).
//For IE 8 and older, I used the mouseenter/leave functionality that's built in since there were problems with stopPropagation()
// (specifically, evt._propagationStopped won't hold its value for some reason)
//
//Be aware that browsers act differently when an affected element is removed/added from the DOM or repositioned on the page. Some fire
// mouseover/out events when an element is added/removed/positioned under the mouse, some execute handlers for a bubbled event even when
// the element has already been removed from the DOM in a previous handler, etc.
//
//Event types are case-sensitive and should not include "on" (e.g., use "click", not "onclick")



//addEventHandler(obj, type, handler)
//returns the GUID of the handler
var addEventHandler = (function(){
	
	"use strict";
	
	var newGUID = 1;	//GUID to assign to the next event handler function without one
	
	function addEvent(obj, type, handler)
	{
		if(!obj || typeof(type) != "string" || typeof(handler) != "function")
			throw new TypeError("Invalid argument");
		
		//make sure the object's window has the required event handlers
		var ownerWindow = getOwnerWindow(obj);
		if(!ownerWindow._eventHandlers) addWindowHandlers(ownerWindow);
		
		if(!obj._eventHandlers) obj._eventHandlers = [];
		
		//if not IE lte 8 and event type is mouseenter/leave, add global handler for the triggering event type instead of the mouseenter/leave event type
		if(document.addEventListener && (type == "mouseenter" || type == "mouseleave"))
		{
			if(!obj._eventHandlers[type]) obj._eventHandlers[type] = [];	//do not add global event handler for the mouseenter/leave event type
			
			addTypeHandler(obj, type=="mouseenter" ? "mouseover" : "mouseout");	//add global handler for the trigger event type instead
		}
		else
			addTypeHandler(obj, type);
		
		if(!handler._handlerGUID) handler._handlerGUID = newGUID++;	//assign a GUID to the handler if it doesn't have one
		var guid = handler._handlerGUID;
		
		if(!assigned(obj, type, guid))	//if this handler isn't already assigned to this event type and object
			obj._eventHandlers[type].push({ guid: guid, handler: handler });	//add the handler to the list
		
		return guid;
	}
	
	
	
	//get the window in which the DOM node resides; this is not necessarily the same window where a function is defined
	function getOwnerWindow(obj)
	{
		return (obj.ownerDocument || obj.document || obj).parentWindow || window;
		/*      obj==element         obj==window     obj==document */
	}
	
	
	
	function addWindowHandlers(ownerWindow)
	{
		if(!ownerWindow._eventHandlers) ownerWindow._eventHandlers = [];
		
		function emptyFunction(){}
		if(document.addEventListener)	//not IE lte 8
		{
			//add empty handlers to window so that mouseenter/leave handlers will be executed when the mouseover/out event stops bubbling
			addEvent(ownerWindow, "mouseover", emptyFunction);
			addEvent(ownerWindow, "mouseout", emptyFunction);
		}
		else	//IE lte 8
		{
			//add empty handlers to document to be sure mouse button variables are updated (these events don't bubble to window in IE lte 8)
			addEvent(ownerWindow.document, "mousedown", emptyFunction);
			addEvent(ownerWindow.document, "mouseup", emptyFunction);
		}
		
		
		//remove circular references and avoid memory leaks when the window unloads (especially for IE)
		function flushEventHandlers(obj)	//nulls event attributes and handler collection
		{
			if(obj._eventHandlers) obj._eventHandlers = null;
			for(var prop in obj)
			{
				if(prop.indexOf("on") == 0) obj[prop] = null;
			}
		}
		function flushAllHandlers()
		{
			var elems = ownerWindow.document.getElementsByTagName("*");
			for(var i=0; i<elems.length; i++){ flushEventHandlers(elems[i]); }
			flushEventHandlers(ownerWindow.document);
			flushEventHandlers(ownerWindow);
		}
		addEvent(ownerWindow, "unload", flushAllHandlers);
	}
	
	
	
	//add global handler for an event type for an object (if it doesn't already exist)
	function addTypeHandler(obj, type)
	{
		if(!obj._eventHandlers[type])
		{
			obj._eventHandlers[type] = [];
			
			if(obj.addEventListener) obj.addEventListener(type, patchHandler(obj, handleEvent), false);
			else if(obj.attachEvent) obj.attachEvent("on"+type, patchHandler(obj, handleEvent));
			else
			{
				if(obj["on"+type]) obj._eventHandlers[type][0] = { handler: obj["on"+type] };
				obj["on"+type] = patchHandler(obj, handleEvent);
			}
		}
	}
	
	//is the handler for this event type for this object already in the list?
	function assigned(obj, type, guid)
	{
		for(var i in obj._eventHandlers[type])
		{
			if(obj._eventHandlers[type].hasOwnProperty(i) && obj._eventHandlers[type][i].guid === guid)
				return true;	//handler is already in the list
		}
		return false;
	}
	
	//"patches" a handler function so that:
	//- the `this` keyword within the handler refers to the correct object
	//- the event object has oft-used W3C-standard properties/methods
	//- the event object is always passed as an argument to the handler
	//it also:
	//- adds custom attributes to the event object
	//- modifies evt.stopPropagation() for use with mouseenter/leave events
	function patchHandler(obj, handler)
	{
		return function(evt){
				//In IE lte 8, if this patched handler is assigned to an event attribute on a DOM node instead of using attachEvent(), 
				// we need to get the event object from the window the node resides in (which is not necessarily where the handler was defined).
				var evtWindow = getOwnerWindow(obj);
				evt = evt || evtWindow.event;
				if(!evt.view) evt.view = evtWindow
				
				
				if(typeof(evt.target) == "undefined") evt.target = evt.srcElement;
				if(typeof(evt.relatedTarget) == "undefined") evt.relatedTarget = evt.target===evt.toElement ? evt.fromElement : evt.toElement;
				if(typeof(evt.fromElement) == "undefined" && typeof(evt.toElement) == "undefined")
				{
					if(evt.type=="mouseover" || evt.type=="mouseenter")
					{
						evt.fromElement = evt.relatedTarget;
						evt.toElement = evt.target;
					}
					else if(evt.type==="mouseout" || evt.type==="mouseleave")
					{
						evt.fromElement = evt.target;
						evt.toElement = evt.relatedTarget;
					}
				}
				if(typeof(evt.currentTarget) == "undefined") evt.currentTarget = obj;	//usually the same as the `this` keyword inside the handler
				if(typeof(evt.eventPhase) == "undefined") evt.eventPhase = 3 - 1*(evt.target === obj);	//capturing==1 (n/a), at_target==2, bubbling==3
				
				evt.preventDefault = evt.preventDefault || patchHandler.preventDefault;
				evt.stopPropagation = patchHandler.stopPropagation;
				
				
				//add custom mouse attributes
				evt.mouse = {};
				
				//mouse button
				//this is the button that was pressed to trigger this event: 1==left, 2==right, 4==middle
				if(evt.type === "mousedown" || evt.type === "mouseup" || evt.type === "click" || evt.type === "dblclick")
				{
					if(evt.which)	//not IE lte 8
						evt.mouse.button = evt.which === 1 ? 1 : evt.which === 2 ? 4 : 2;
					else	//IE lte 8
					{
						var mb = patchHandler.mouseButtons;
						if(obj === evt.target)	//update mb.button
						{
							if(evt.type === "mousedown")
							{
								mb.button = (evt.button ^ mb.pressed) & evt.button;
								if((mb.button & evt.button) === 0) mb.button = evt.button;
								mb.pressed = evt.button;
								
								//note: mb.button may be incorrect on mousedown since we can't reliably keep track of IE's event.button
								// value (i.e., which buttons are pressed when the event is fired) between events (e.g., if the mouse
								// leaves the window, buttons are changed by the user without the browser's detection, and the mouse comes back)
							}
							else if(evt.type === "mouseup")
							{
								mb.button = evt.button;
								mb.pressed = ~evt.button & mb.pressed;
							}
						}
						evt.mouse.button = mb.button;
					}
				}
				else evt.mouse.button = patchHandler.mouseButtons.button = 0;
				
				//mouse wheel distance
				//this is the distance ("clicks") the mouse wheel rolled; negative means it rolled up
				if((evt.type==="mousewheel" || evt.type==="wheel" || evt.type==="DOMMouseScroll") && (evt.wheelDelta || evt.detail))
					evt.mouse.wheelDelta = evt.wheelDelta ? -evt.wheelDelta/120 : evt.detail ? evt.detail/3 : 0;
				
				//mouse position
				if(evt.type.slice(0,5) === "mouse" || evt.type==="wheel" || evt.type==="DOMMouseScroll" || evt.type.slice(0,4)==="drag" || evt.type==="drop")
				{
					evt.mouse.position = {};
					evt.mouse.position.screen = {x:evt.screenX, y:evt.screenY, left:evt.screenX, top:evt.screenY};
					evt.mouse.position.window = evt.mouse.position.frame = {x:evt.clientX, y:evt.clientY, left:evt.clientX, top:evt.clientY};
					evt.mouse.position.document = (function(){
							if(isNaN(evt.pageX) || isNaN(evt.pageY))
							{
								var left, top;	//scroll position of document
								if(window.pageYOffset)	//all except IE
								{ left = window.pageXOffset; top = window.pageYOffset; }
								else if(document.documentElement && !isNaN(document.documentElement.scrollTop))	//IE standards compliance mode
								{ left = document.documentElement.scrollLeft; top = document.documentElement.scrollTop; }
								else	//IE quirks mode
								{ left = document.body.scrollLeft; top = document.body.scrollTop; }
								return {x:left+evt.clientX, y:top+evt.clientY, left:left+evt.clientX, top:top+evt.clientY};
							}
							else return {x:evt.pageX, y:evt.pageY, left:evt.pageX, top:evt.pageY};
						})();
					evt.mouse.position.layer = {x:evt.layerX||evt.x, y:evt.layerY||evt.y, left:evt.layerX||evt.x, top:evt.layerY||evt.y};
				}
				
				
				//add custom key attributes
				evt.keyboard = {};
				
				//see http://unixpapa.com/js/key.html
				//    http://www.quirksmode.org/js/keys.html
				if(evt.type==="keypress")
				{
					if(typeof(evt.which) == "undefined")	//IE lte 8
					{
						evt.keyboard.charCode = evt.keyCode;
						evt.keyboard.char = String.fromCharCode(evt.keyCode);
					}
					else if(evt.which !== 0 && evt.charCode !== 0)	//other browsers (note: sometimes special keys still give a non-zero value)
					{
						evt.keyboard.charCode = evt.which;
						evt.keyboard.char = String.fromCharCode(evt.which);
					}
					else	//special key
					{
						evt.keyboard.charCode = 0;
						evt.keyboard.char = evt.key;	//evt.key only works in IE gte 9; it gives "Control", "Shift", "Down", etc. for special keys
					}
				}
				else if(evt.type==="keydown" || evt.type==="keyup")
				{
					evt.keyboard.char = evt.key;	//evt.key only works in IE gte 9
				}
				
				
				//execute the handler
				handler.call(obj, evt);	//corrects the `this` keyword and passes the patched event object
			};
	}
	patchHandler.preventDefault = function(){ this.returnValue = false; };
	patchHandler.stopPropagation = function(){
			if(!document.addEventListener)	//IE lte 8
				this.cancelBubble = true;
			else
			{
				this._propagationStopped = true;
				if(this.type !== "mouseover" && this.type !== "mouseout") this.stopPropagation();
			}
		};
	patchHandler.mouseButtons = { button: 0, pressed: 0 };	//for IE lte 8, keeps track of which mouse buttons are pressed
	
	
	
	//for browsers other than IE lte 8
	//stores the elements with mouseenter/leave handlers that need to be executed once the mouseover/out event finishes bubbling
	var mouseELQueue = [];
	var stopQueuingEL;	//flag to stop queuing mouseenter/leave handlers in handleEvent()
	
	//global handler that executes all handlers assigned to an object for the passed event
	function handleEvent(evt)
	{
		var returnValue = true;
		var handlers = this._eventHandlers[evt.type];
		
		//if propagation hasn't been stopped, execute the handlers
		if(!evt._propagationStopped)
		{
			//execute each event handler (in FIFO order)
			for(var i=0; i<handlers.length; i++)
			{
				//execute the handler and update the return value
				returnValue = returnValue && handlers[i].handler.call(this, evt) !== false;
			}
		}
		if(!returnValue) evt.preventDefault();
		
		
		
		//if not IE lte 8, and mouseenter/leave handlers may need to be executed
		if(document.addEventListener && (evt.type == "mouseover" || evt.type == "mouseout"))
		{
			if(this == evt.target) stopQueuingEL = false;
			
			//if this element has mouseenter/leave handlers, queue it to have the handlers executed after the mouseover/out event has completed
			
			//test whether the element is still in the DOM or if it has been removed by a previous handler
			function inDOM(elem)
			{
				if(!elem) return false;
				if(elem === evt.view.document.documentElement) return true;
				var tmp = elem;
				do
				{
					if(!tmp.parentNode) return false;	//element is not in the DOM
					tmp = tmp.parentNode;
				}while(tmp !== evt.view.document.documentElement);
				return true;
			}
			
			function queueEL(type, elem, related)
			{
				if(stopQueuingEL) return;
				
				if((type === "mouseenter" && !inDOM(elem)) || (type === "mouseleave" && !inDOM(related)))
				{
					stopQueuingEL = true;
					return;
				}
				
				do
				{
					if(related === elem)	//didn't enter/leave the element, only a child element
					{
						stopQueuingEL = true;
						return;
					}
					related = related.parentNode;
				}while(related);
				
				var evtCopy = {};
				for(i in evt) evtCopy[i] = evt[i];
				evtCopy.type = type;
				//save pointer to the element and a copy of the event object to do the mouseenter/leave handlers later
				mouseELQueue.push([elem, evtCopy]);
			}
			if(evt.type == "mouseover" && this._eventHandlers["mouseenter"])
				queueEL("mouseenter", this, (evt.relatedTarget || this));
				//when the page loads, if the mouse is already inside the element and is then moved, the mouseover event fires. There is no source
				// element in that case so the related target is itself
			else if(evt.type == "mouseout" && this._eventHandlers["mouseleave"])
				queueEL("mouseleave", this, evt.relatedTarget);
			
			//if mouseover/out event has stopped bubbling, "fire" mouseenter/leave event
			if(this === evt.view && mouseELQueue.length > 0)
			{
				var q, elem, evtCopy;
				
				//handle queued events
				while(mouseELQueue.length > 0)	//for each queued element
				{
					q = evt.type==="mouseover" ? mouseELQueue.pop() : mouseELQueue.shift();	//mouseenter events are handled in reverse order
					elem = q[0];
					evtCopy = q[1];
					
					if(inDOM(elem) || elem === evt.view.document || elem === evt.view)
					{
						handlers = elem._eventHandlers[evt.type];
						for(var i=0; i<handlers.length; i++)	//execute the element's mouseenter/leave handlers
						{
							//execute the handler and update the return value
							returnValue = returnValue && handlers[i].handler.call(elem, evtCopy) !== false;
						}
					}
				}
			}
			
		}
		
		return returnValue;
	}
	
	return addEvent;
})();

function removeEventHandler(obj, type, handler_or_guid)
{
	"use strict";
	
	if(!obj || typeof(type) != "string") throw new TypeError("Invalid argument");
	
	var guid = handler_or_guid;
	if(isNaN(guid) && typeof(guid) != "function")	//invalid handler_or_guid
		throw new TypeError("Invalid argument");
	if(typeof(guid) == "function")
		guid = guid._handlerGUID;	//handler function was passed
	
	if(guid && obj._eventHandlers && obj._eventHandlers[type])
	{
		var handlers = obj._eventHandlers[type];
		for(var i in handlers)
		{
			if(handlers.hasOwnProperty(i) && handlers[i].guid === guid)	//handler is in the list
			{
				handlers.splice(i, 1);	//remove the handler from the list
				break;
			}
		}
	}
}

Revision: 44561
at September 27, 2011 04:47 by wizard04


Updated Code
/*****************************************
 * Events Max v6.1.1
 * Enhanced cross-browser event handling
 * 
 * This work is licensed under a Creative Commons Attribution 3.0 Unported License
 * http://creativecommons.org/licenses/by/3.0/
 *
 * Author: Andy Harrison, http://dragonzreef.com/
 * Date: 26 September 2011
 *****************************************/

//Cross-browser event registration functions
//Handlers execute in FIFO order
//Supports mouseenter and mouseleave events
//Custom event attributes:
//  event.mouse.button: the mouse button value (1, 2, or 4) for mousedown, mouseup, click, and dblclick events
//  event.mouse.position: object containing the mouse positions within the screen, window, document, and layer (e.g., evt.mouse.position.document.x)
//  event.mouse.wheelDelta: distance ("clicks") the mouse wheel rolled; negative means it rolled up
//  event.keyboard.charCode: Unicode character code that was generated on a keypress event
//  event.keyboard.char: Unicode character that was generated on a keypress event
//
//addEventHandler(obj, type, handler)
//removeEventHandler(obj, type, handler_or_guid)
//
//Usage:
//  addEventHandler(element, "click", handlerFunction);
//  removeEventHandler(element, "click", handlerFunction);
//or:
//  var guid = addEventHandler(element, "click", function(evt){doSomething()});
//  removeEventHandler(element, "click", guid);

//Techniques and inspiration largely from:
// http://www.quirksmode.org/blog/archives/2005/10/_and_the_winner_1.html
// http://dean.edwards.name/weblog/2005/10/add-event/
// http://dean.edwards.name/weblog/2005/10/add-event2/
// http://dean.edwards.name/weblog/2005/10/add-event2/#comment6264
// http://outofhanwell.wordpress.com/2006/07/03/cross-window-events/
// http://blog.metawrap.com/2005/10/24/ie-closures-leaks/
// http://www.quirksmode.org/js/events_properties.html
// http://help.dottoro.com/ljogqtqm.php

//This script uses a custom event attribute for the mouse button value: event.mouse.button
//This goes by the Microsoft model, where left==1, right==2, and middle==4
//In IE lte 8, the value may be incorrect on mousedown since we can't reliably keep track of the event.button value between events.
// (e.g., if the mouse leaves the window, buttons are changed by the user without the browser's detection, and the mouse comes back)
//
//Unlike in jQuery, mouseenter/leave events match IE in the order the events are fired. For example, instead of mouseenter being fired
// immediately after mouseover, this implementation waits for the mouseover event to bubble to the top before firing the mouseenter event.
// Also, mouseenter handlers are executed top-down (as if it were a capture phase).
//For IE 8 and older, I used the mouseenter/leave functionality that's built in since there were problems with stopPropagation()
// (specifically, evt._propagationStopped won't hold its value for some reason)
//
//Be aware that browsers act differently when an affected element is removed/added from the DOM or repositioned on the page. Some fire
// mouseover/out events when an element is added/removed/positioned under the mouse, some execute handlers for a bubbled event even when
// the element has already been removed from the DOM in a previous handler, etc.
//
//Event types are case-sensitive and should not include "on" (e.g., use "click", not "onclick")



//addEventHandler(obj, type, handler)
//returns the GUID of the handler
var addEventHandler = (function(){
	
	var newGUID = 1;	//GUID to assign to the next event handler function without one
	
	function addEvent(obj, type, handler)
	{
		//make sure the object's window has the required event handlers
		var ownerWindow = getOwnerWindow(obj);
		if(!ownerWindow._eventHandlers) addWindowHandlers(ownerWindow);
		
		if(!obj._eventHandlers) obj._eventHandlers = [];
		
		//if not IE lte 8 and event type is mouseenter/leave, add global handler for the triggering event type instead of the mouseenter/leave event type
		if(document.addEventListener && (type == "mouseenter" || type == "mouseleave"))
		{
			if(!obj._eventHandlers[type]) obj._eventHandlers[type] = [];	//do not add global event handler for the mouseenter/leave event type
			
			addTypeHandler(obj, type=="mouseenter" ? "mouseover" : "mouseout");	//add global handler for the trigger event type instead
		}
		else
			addTypeHandler(obj, type);
		
		if(!handler._handlerGUID) handler._handlerGUID = newGUID++;	//assign a GUID to the handler if it doesn't have one
		var guid = handler._handlerGUID;
		
		if(!assigned(obj, type, guid))	//if this handler isn't already assigned to this event type and object
			obj._eventHandlers[type].push({ guid: guid, handler: handler });	//add the handler to the list
		
		return guid;
	}
	
	
	
	//get the window in which the DOM node resides; this is not necessarily the same window where a function is defined
	function getOwnerWindow(obj)
	{
		return (obj.ownerDocument || obj.document || obj).parentWindow || window;
		/*      obj==element         obj==window     obj==document */
	}
	
	
	
	function addWindowHandlers(ownerWindow)
	{
		if(!ownerWindow._eventHandlers) ownerWindow._eventHandlers = [];
		
		function emptyFunction(){}
		if(document.addEventListener)	//not IE lte 8
		{
			//add empty handlers to window so that mouseenter/leave handlers will be executed when the mouseover/out event stops bubbling
			addEvent(ownerWindow, "mouseover", emptyFunction);
			addEvent(ownerWindow, "mouseout", emptyFunction);
		}
		else	//IE lte 8
		{
			//add empty handlers to document to be sure mouse button variables are updated (these events don't bubble to window in IE lte 8)
			addEvent(ownerWindow.document, "mousedown", emptyFunction);
			addEvent(ownerWindow.document, "mouseup", emptyFunction);
		}
		
		
		//remove circular references and avoid memory leaks when the window unloads (especially for IE)
		function flushEventHandlers(obj)	//nulls event attributes and handler collection
		{
			if(obj._eventHandlers) obj._eventHandlers = null;
			for(var prop in obj)
			{
				if(prop.indexOf("on") == 0) obj[prop] = null;
			}
		}
		function flushAllHandlers()
		{
			var elems = ownerWindow.document.getElementsByTagName("*");
			for(var i=0; i<elems.length; i++){ flushEventHandlers(elems[i]); }
			flushEventHandlers(ownerWindow.document);
			flushEventHandlers(ownerWindow);
		}
		addEvent(ownerWindow, "unload", flushAllHandlers);
	}
	
	
	
	//add global handler for an event type for an object (if it doesn't already exist)
	function addTypeHandler(obj, type)
	{
		if(!obj._eventHandlers[type])
		{
			obj._eventHandlers[type] = [];
			
			if(obj.addEventListener) obj.addEventListener(type, patchHandler(obj, handleEvent), false);
			else if(obj.attachEvent) obj.attachEvent("on"+type, patchHandler(obj, handleEvent));
			else
			{
				if(obj["on"+type]) obj._eventHandlers[type][0] = { handler: obj["on"+type] };
				obj["on"+type] = patchHandler(obj, handleEvent);
			}
		}
	}
	
	//is the handler for this event type for this object already in the list?
	function assigned(obj, type, guid)
	{
		for(var i in obj._eventHandlers[type])
		{
			if(obj._eventHandlers[type][i].guid == guid) return true;	//handler is already in the list
		}
		return false;
	}
	
	//"patches" a handler function so that:
	//- the `this` keyword within the handler refers to the correct object
	//- the event object has oft-used W3C-standard properties/methods
	//- the event object is always passed as an argument to the handler
	//it also:
	//- adds custom attributes to the event object
	//- modifies evt.stopPropagation() for use with mouseenter/leave events
	function patchHandler(obj, handler)
	{
		var undefined;
		
		return function(evt){
				//In IE lte 8, if this patched handler is assigned to an event attribute on a DOM node instead of using attachEvent(), 
				// we need to get the event object from the window the node resides in (which is not necessarily where the handler was defined).
				var evtWindow = getOwnerWindow(obj);
				evt = evt || evtWindow.event;
				if(!evt.view) evt.view = evtWindow
				
				
				if(evt.target === undefined) evt.target = evt.srcElement;
				if(evt.relatedTarget === undefined) evt.relatedTarget = evt.target==evt.toElement ? evt.fromElement : evt.toElement;
				if(evt.fromElement === undefined && evt.toElement === undefined)
				{
					if(evt.type=="mouseover" || evt.type=="mouseenter")
					{
						evt.fromElement = evt.relatedTarget;
						evt.toElement = evt.target;
					}
					else if(evt.type=="mouseout" || evt.type=="mouseleave")
					{
						evt.fromElement = evt.target;
						evt.toElement = evt.relatedTarget;
					}
				}
				if(evt.currentTarget === undefined) evt.currentTarget = obj;	//usually the same as the `this` keyword inside the handler
				if(evt.eventPhase === undefined) evt.eventPhase = 3 - 1*(evt.target === obj);	//capturing==1 (n/a), at_target==2, bubbling==3
				
				evt.preventDefault = evt.preventDefault || patchHandler.preventDefault;
				evt.stopPropagation = patchHandler.stopPropagation;
				
				
				//add custom mouse attributes
				evt.mouse = {};
				
				//mouse button
				//this is the button that was pressed to trigger this event: 1==left, 2==right, 4==middle
				if(evt.type == "mousedown" || evt.type == "mouseup" || evt.type == "click" || evt.type == "dblclick")
				{
					if(evt.which)	//not IE lte 8
						evt.mouse.button = evt.which == 1 ? 1 : evt.which == 2 ? 4 : 2;
					else	//IE lte 8
					{
						var mb = patchHandler.mouseButtons;
						if(obj == evt.target)	//update mb.button
						{
							if(evt.type == "mousedown")
							{
								mb.button = (evt.button ^ mb.pressed) & evt.button;
								if((mb.button & evt.button) == 0) mb.button = evt.button;
								mb.pressed = evt.button;
								
								//note: mb.button may be incorrect on mousedown since we can't reliably keep track of IE's event.button
								// value (i.e., which buttons are pressed when the event is fired) between events (e.g., if the mouse
								// leaves the window, buttons are changed by the user without the browser's detection, and the mouse comes back)
							}
							else if(evt.type == "mouseup")
							{
								mb.button = evt.button;
								mb.pressed = ~evt.button & mb.pressed;
							}
						}
						evt.mouse.button = mb.button;
					}
				}
				else evt.mouse.button = patchHandler.mouseButtons.button = 0;
				
				//mouse wheel distance
				//this is the distance ("clicks") the mouse wheel rolled; negative means it rolled up
				if((evt.type=="mousewheel" || evt.type=="wheel" || evt.type=="DOMMouseScroll") && (evt.wheelDelta || evt.detail))
					evt.mouse.wheelDelta = evt.wheelDelta ? -evt.wheelDelta/120 : evt.detail ? evt.detail/3 : 0;
				
				//mouse position
				if(evt.type.slice(0,5) == "mouse" || evt.type=="wheel" || evt.type=="DOMMouseScroll" || evt.type.slice(0,4)=="drag" || evt.type=="drop")
				{
					evt.mouse.position = {};
					evt.mouse.position.screen = {x:evt.screenX, y:evt.screenY, left:evt.screenX, top:evt.screenY};
					evt.mouse.position.window = evt.mouse.position.frame = {x:evt.clientX, y:evt.clientY, left:evt.clientX, top:evt.clientY};
					evt.mouse.position.document = (function(){
							if(isNaN(evt.pageX) || isNaN(evt.pageY))
							{
								var left, top;	//scroll position of document
								if(window.pageYOffset)	//all except IE
								{ left = window.pageXOffset; top = window.pageYOffset; }
								else if(document.documentElement && !isNaN(document.documentElement.scrollTop))	//IE standards compliance mode
								{ left = document.documentElement.scrollLeft; top = document.documentElement.scrollTop; }
								else	//IE quirks mode
								{ left = document.body.scrollLeft; top = document.body.scrollTop; }
								return {x:left+evt.clientX, y:top+evt.clientY, left:left+evt.clientX, top:top+evt.clientY};
							}
							else return {x:evt.pageX, y:evt.pageY, left:evt.pageX, top:evt.pageY};
						})();
					evt.mouse.position.layer = {x:evt.layerX||evt.x, y:evt.layerY||evt.y, left:evt.layerX||evt.x, top:evt.layerY||evt.y};
				}
				
				
				//add custom key attributes
				evt.keyboard = {};
				
				//see http://unixpapa.com/js/key.html
				//    http://www.quirksmode.org/js/keys.html
				if(evt.type=="keypress")
				{
					if(evt.which === undefined)	//IE lte 8
					{
						evt.keyboard.charCode = evt.keyCode;
						evt.keyboard.char = String.fromCharCode(evt.keyCode);
					}
					else if(evt.which != 0 && evt.charCode != 0)	//other browsers (note: sometimes special keys still give a non-zero value)
					{
						evt.keyboard.charCode = evt.which;
						evt.keyboard.char = String.fromCharCode(evt.which);
					}
					else	//special key
					{
						evt.keyboard.charCode = 0;
						evt.keyboard.char = evt.key;	//evt.key only works in IE gte 9; it gives "Control", "Shift", "Down", etc. for special keys
					}
				}
				else if(evt.type=="keydown" || evt.type=="keyup")
				{
					evt.keyboard.char = evt.key;	//evt.key only works in IE gte 9
				}
				
				
				//execute the handler
				handler.call(obj, evt);	//corrects the `this` keyword and passes the patched event object
			};
	}
	patchHandler.preventDefault = function(){ this.returnValue = false; };
	patchHandler.stopPropagation = function(){
			if(!document.addEventListener)	//IE lte 8
				this.cancelBubble = true;
			else
			{
				this._propagationStopped = true;
				if(this.type != "mouseover" && this.type != "mouseout") this.stopPropagation();
			}
		};
	patchHandler.mouseButtons = { button: 0, pressed: 0 };	//for IE lte 8, keeps track of which mouse buttons are pressed
	
	
	
	//for browsers other than IE lte 8
	//stores the elements with mouseenter/leave handlers that need to be executed once the mouseover/out event finishes bubbling
	var mouseELQueue = [];
	var stopQueuingEL;	//flag to stop queuing mouseenter/leave handlers in handleEvent()
	
	//global handler that executes all handlers assigned to an object for the passed event
	function handleEvent(evt)
	{
		var returnValue = true;
		var handlers = this._eventHandlers[evt.type];
		
		//if propagation hasn't been stopped, execute the handlers
		if(!evt._propagationStopped)
		{
			//execute each event handler (in FIFO order)
			for(var i=0; i<handlers.length; i++)
			{
				//execute the handler and update the return value
				returnValue = returnValue && handlers[i].handler.call(this, evt) !== false;
			}
		}
		if(!returnValue) evt.preventDefault();
		
		
		
		//if not IE lte 8, and mouseenter/leave handlers may need to be executed
		if(document.addEventListener && (evt.type == "mouseover" || evt.type == "mouseout"))
		{
			if(this == evt.target) stopQueuingEL = false;
			
			//if this element has mouseenter/leave handlers, queue it to have the handlers executed after the mouseover/out event has completed
			
			//test whether the element is still in the DOM or if it has been removed by a previous handler
			function inDOM(elem)
			{
				if(!elem) return false;
				if(elem == evt.view.document.documentElement) return true;
				var tmp = elem;
				do
				{
					if(!tmp.parentNode) return false;	//element is not in the DOM
					tmp = tmp.parentNode;
				}while(tmp != evt.view.document.documentElement)
				return true;
			}
			
			function queueEL(type, elem, related)
			{
				if(stopQueuingEL) return;
				
				if((type == "mouseenter" && !inDOM(elem)) || (type == "mouseleave" && !inDOM(related)))
				{
					stopQueuingEL = true;
					return;
				}
				
				do
				{
					if(related == elem)	//didn't enter/leave the element, only a child element
					{
						stopQueuingEL = true;
						return;
					}
					related = related.parentNode;
				}while(related)
				
				var evtCopy = {};
				for(i in evt) evtCopy[i] = evt[i];
				evtCopy.type = type;
				//save pointer to the element and a copy of the event object to do the mouseenter/leave handlers later
				mouseELQueue.push([elem, evtCopy]);
			}
			if(evt.type == "mouseover" && this._eventHandlers["mouseenter"])
				queueEL("mouseenter", this, (evt.relatedTarget || this));
				//when the page loads, if the mouse is already inside the element and is then moved, the mouseover event fires. There is no source
				// element in that case so the related target is itself
			else if(evt.type == "mouseout" && this._eventHandlers["mouseleave"])
				queueEL("mouseleave", this, evt.relatedTarget);
			
			//if mouseover/out event has stopped bubbling, "fire" mouseenter/leave event
			if(this == evt.view && mouseELQueue.length > 0)
			{
				var q, elem, evtCopy;
				
				//handle queued events
				while(mouseELQueue.length > 0)	//for each queued element
				{
					q = evt.type=="mouseover" ? mouseELQueue.pop() : mouseELQueue.shift();	//mouseenter events are handled in reverse order
					elem = q[0];
					evtCopy = q[1];
					
					if(inDOM(elem) || elem == evt.view.document || elem == evt.view)
					{
						handlers = elem._eventHandlers[evt.type];
						for(var i=0; i<handlers.length; i++)	//execute the element's mouseenter/leave handlers
						{
							//execute the handler and update the return value
							returnValue = returnValue && handlers[i].handler.call(elem, evtCopy) !== false;
						}
					}
				}
			}
			
		}
		
		return returnValue;
	}
	
	return addEvent;
})();

function removeEventHandler(obj, type, handler_or_guid)
{
	type = type.toLowerCase();
	if(type.slice(0,2) == "on") type = type.slice(2);
	
	var guid;
	if(!isNaN(1*handler_or_guid)) guid = handler_or_guid;	//GUID was passed
	else guid = handler_or_guid._handlerGUID;	//handler function was passed
	
	if(guid && obj._eventHandlers && obj._eventHandlers[type])
	{
		var handlers = obj._eventHandlers[type];
		for(var i in handlers)
		{
			if(handlers[i].guid == guid)	//handler is in the list
			{
				handlers.splice(i, 1);	//remove the handler from the list
				break;
			}
		}
	}
}

Revision: 44560
at September 27, 2011 04:46 by wizard04


Updated Code
/*****************************************
 * Events Max v6.1.1
 * Enhanced cross-browser event handling
 * 
 * This work is licensed under a Creative Commons Attribution 3.0 Unported License
 * http://creativecommons.org/licenses/by/3.0/
 *
 * Author: Andy Harrison, http://dragonzreef.com/
 * Date: 26 September 2011
 *****************************************/

//Cross-browser event registration functions
//Handlers execute in FIFO order
//Supports mouseenter and mouseleave events
//Custom event attributes:
//	event.mouse.button: the mouse button value (1, 2, or 4) for mousedown, mouseup, click, and dblclick events
//	event.mouse.position: object containing the mouse positions within the screen, window, document, and layer (e.g., evt.mouse.position.document.x)
//	event.mouse.wheelDelta: distance ("clicks") the mouse wheel rolled; negative means it rolled up
//  event.keyboard.charCode: Unicode character code that was generated on a keypress event
//  event.keyboard.char: Unicode character that was generated on a keypress event
//
//addEventHandler(obj, type, handler)
//removeEventHandler(obj, type, handler_or_guid)
//
//Usage:
// addEventHandler(element, "click", handlerFunction);
// removeEventHandler(element, "click", handlerFunction);
//or:
// var guid = addEventHandler(element, "click", function(evt){doSomething()});
// removeEventHandler(element, "click", guid);

//Techniques and inspiration largely from:
// http://www.quirksmode.org/blog/archives/2005/10/_and_the_winner_1.html
// http://dean.edwards.name/weblog/2005/10/add-event/
// http://dean.edwards.name/weblog/2005/10/add-event2/
// http://dean.edwards.name/weblog/2005/10/add-event2/#comment6264
// http://outofhanwell.wordpress.com/2006/07/03/cross-window-events/
// http://blog.metawrap.com/2005/10/24/ie-closures-leaks/
// http://www.quirksmode.org/js/events_properties.html
// http://help.dottoro.com/ljogqtqm.php

//This script uses a custom event attribute for the mouse button value: event.mouse.button
//This goes by the Microsoft model, where left==1, right==2, and middle==4
//In IE lte 8, the value may be incorrect on mousedown since we can't reliably keep track of the event.button value between events.
// (e.g., if the mouse leaves the window, buttons are changed by the user without the browser's detection, and the mouse comes back)
//
//Unlike in jQuery, mouseenter/leave events match IE in the order the events are fired. For example, instead of mouseenter being fired
// immediately after mouseover, this implementation waits for the mouseover event to bubble to the top before firing the mouseenter event.
// Also, mouseenter handlers are executed top-down (as if it were a capture phase).
//For IE 8 and older, I used the mouseenter/leave functionality that's built in since there were problems with stopPropagation()
// (specifically, evt._propagationStopped won't hold its value for some reason)
//
//Be aware that browsers act differently when an affected element is removed/added from the DOM or repositioned on the page. Some fire
// mouseover/out events when an element is added/removed/positioned under the mouse, some execute handlers for a bubbled event even when
// the element has already been removed from the DOM in a previous handler, etc.
//
//Event types are case-sensitive and should not include "on" (e.g., use "click", not "onclick")



//addEventHandler(obj, type, handler)
//returns the GUID of the handler
var addEventHandler = (function(){
	
	var newGUID = 1;	//GUID to assign to the next event handler function without one
	
	function addEvent(obj, type, handler)
	{
		//make sure the object's window has the required event handlers
		var ownerWindow = getOwnerWindow(obj);
		if(!ownerWindow._eventHandlers) addWindowHandlers(ownerWindow);
		
		if(!obj._eventHandlers) obj._eventHandlers = [];
		
		//if not IE lte 8 and event type is mouseenter/leave, add global handler for the triggering event type instead of the mouseenter/leave event type
		if(document.addEventListener && (type == "mouseenter" || type == "mouseleave"))
		{
			if(!obj._eventHandlers[type]) obj._eventHandlers[type] = [];	//do not add global event handler for the mouseenter/leave event type
			
			addTypeHandler(obj, type=="mouseenter" ? "mouseover" : "mouseout");	//add global handler for the trigger event type instead
		}
		else
			addTypeHandler(obj, type);
		
		if(!handler._handlerGUID) handler._handlerGUID = newGUID++;	//assign a GUID to the handler if it doesn't have one
		var guid = handler._handlerGUID;
		
		if(!assigned(obj, type, guid))	//if this handler isn't already assigned to this event type and object
			obj._eventHandlers[type].push({ guid: guid, handler: handler });	//add the handler to the list
		
		return guid;
	}
	
	
	
	//get the window in which the DOM node resides; this is not necessarily the same window where a function is defined
	function getOwnerWindow(obj)
	{
		return (obj.ownerDocument || obj.document || obj).parentWindow || window;
		/*      obj==element         obj==window     obj==document */
	}
	
	
	
	function addWindowHandlers(ownerWindow)
	{
		if(!ownerWindow._eventHandlers) ownerWindow._eventHandlers = [];
		
		function emptyFunction(){}
		if(document.addEventListener)	//not IE lte 8
		{
			//add empty handlers to window so that mouseenter/leave handlers will be executed when the mouseover/out event stops bubbling
			addEvent(ownerWindow, "mouseover", emptyFunction);
			addEvent(ownerWindow, "mouseout", emptyFunction);
		}
		else	//IE lte 8
		{
			//add empty handlers to document to be sure mouse button variables are updated (these events don't bubble to window in IE lte 8)
			addEvent(ownerWindow.document, "mousedown", emptyFunction);
			addEvent(ownerWindow.document, "mouseup", emptyFunction);
		}
		
		
		//remove circular references and avoid memory leaks when the window unloads (especially for IE)
		function flushEventHandlers(obj)	//nulls event attributes and handler collection
		{
			if(obj._eventHandlers) obj._eventHandlers = null;
			for(var prop in obj)
			{
				if(prop.indexOf("on") == 0) obj[prop] = null;
			}
		}
		function flushAllHandlers()
		{
			var elems = ownerWindow.document.getElementsByTagName("*");
			for(var i=0; i<elems.length; i++){ flushEventHandlers(elems[i]); }
			flushEventHandlers(ownerWindow.document);
			flushEventHandlers(ownerWindow);
		}
		addEvent(ownerWindow, "unload", flushAllHandlers);
	}
	
	
	
	//add global handler for an event type for an object (if it doesn't already exist)
	function addTypeHandler(obj, type)
	{
		if(!obj._eventHandlers[type])
		{
			obj._eventHandlers[type] = [];
			
			if(obj.addEventListener) obj.addEventListener(type, patchHandler(obj, handleEvent), false);
			else if(obj.attachEvent) obj.attachEvent("on"+type, patchHandler(obj, handleEvent));
			else
			{
				if(obj["on"+type]) obj._eventHandlers[type][0] = { handler: obj["on"+type] };
				obj["on"+type] = patchHandler(obj, handleEvent);
			}
		}
	}
	
	//is the handler for this event type for this object already in the list?
	function assigned(obj, type, guid)
	{
		for(var i in obj._eventHandlers[type])
		{
			if(obj._eventHandlers[type][i].guid == guid) return true;	//handler is already in the list
		}
		return false;
	}
	
	//"patches" a handler function so that:
	//- the `this` keyword within the handler refers to the correct object
	//- the event object has oft-used W3C-standard properties/methods
	//- the event object is always passed as an argument to the handler
	//it also:
	//- adds custom attributes to the event object
	//- modifies evt.stopPropagation() for use with mouseenter/leave events
	function patchHandler(obj, handler)
	{
		var undefined;
		
		return function(evt){
				//In IE lte 8, if this patched handler is assigned to an event attribute on a DOM node instead of using attachEvent(), 
				// we need to get the event object from the window the node resides in (which is not necessarily where the handler was defined).
				var evtWindow = getOwnerWindow(obj);
				evt = evt || evtWindow.event;
				if(!evt.view) evt.view = evtWindow
				
				
				if(evt.target === undefined) evt.target = evt.srcElement;
				if(evt.relatedTarget === undefined) evt.relatedTarget = evt.target==evt.toElement ? evt.fromElement : evt.toElement;
				if(evt.fromElement === undefined && evt.toElement === undefined)
				{
					if(evt.type=="mouseover" || evt.type=="mouseenter")
					{
						evt.fromElement = evt.relatedTarget;
						evt.toElement = evt.target;
					}
					else if(evt.type=="mouseout" || evt.type=="mouseleave")
					{
						evt.fromElement = evt.target;
						evt.toElement = evt.relatedTarget;
					}
				}
				if(evt.currentTarget === undefined) evt.currentTarget = obj;	//usually the same as the `this` keyword inside the handler
				if(evt.eventPhase === undefined) evt.eventPhase = 3 - 1*(evt.target === obj);	//capturing==1 (n/a), at_target==2, bubbling==3
				
				evt.preventDefault = evt.preventDefault || patchHandler.preventDefault;
				evt.stopPropagation = patchHandler.stopPropagation;
				
				
				//add custom mouse attributes
				evt.mouse = {};
				
				//mouse button
				//this is the button that was pressed to trigger this event: 1==left, 2==right, 4==middle
				if(evt.type == "mousedown" || evt.type == "mouseup" || evt.type == "click" || evt.type == "dblclick")
				{
					if(evt.which)	//not IE lte 8
						evt.mouse.button = evt.which == 1 ? 1 : evt.which == 2 ? 4 : 2;
					else	//IE lte 8
					{
						var mb = patchHandler.mouseButtons;
						if(obj == evt.target)	//update mb.button
						{
							if(evt.type == "mousedown")
							{
								mb.button = (evt.button ^ mb.pressed) & evt.button;
								if((mb.button & evt.button) == 0) mb.button = evt.button;
								mb.pressed = evt.button;
								
								//note: mb.button may be incorrect on mousedown since we can't reliably keep track of IE's event.button
								// value (i.e., which buttons are pressed when the event is fired) between events (e.g., if the mouse
								// leaves the window, buttons are changed by the user without the browser's detection, and the mouse comes back)
							}
							else if(evt.type == "mouseup")
							{
								mb.button = evt.button;
								mb.pressed = ~evt.button & mb.pressed;
							}
						}
						evt.mouse.button = mb.button;
					}
				}
				else evt.mouse.button = patchHandler.mouseButtons.button = 0;
				
				//mouse wheel distance
				//this is the distance ("clicks") the mouse wheel rolled; negative means it rolled up
				if((evt.type=="mousewheel" || evt.type=="wheel" || evt.type=="DOMMouseScroll") && (evt.wheelDelta || evt.detail))
					evt.mouse.wheelDelta = evt.wheelDelta ? -evt.wheelDelta/120 : evt.detail ? evt.detail/3 : 0;
				
				//mouse position
				if(evt.type.slice(0,5) == "mouse" || evt.type=="wheel" || evt.type=="DOMMouseScroll" || evt.type.slice(0,4)=="drag" || evt.type=="drop")
				{
					evt.mouse.position = {};
					evt.mouse.position.screen = {x:evt.screenX, y:evt.screenY, left:evt.screenX, top:evt.screenY};
					evt.mouse.position.window = evt.mouse.position.frame = {x:evt.clientX, y:evt.clientY, left:evt.clientX, top:evt.clientY};
					evt.mouse.position.document = (function(){
							if(isNaN(evt.pageX) || isNaN(evt.pageY))
							{
								var left, top;	//scroll position of document
								if(window.pageYOffset)	//all except IE
								{ left = window.pageXOffset; top = window.pageYOffset; }
								else if(document.documentElement && !isNaN(document.documentElement.scrollTop))	//IE standards compliance mode
								{ left = document.documentElement.scrollLeft; top = document.documentElement.scrollTop; }
								else	//IE quirks mode
								{ left = document.body.scrollLeft; top = document.body.scrollTop; }
								return {x:left+evt.clientX, y:top+evt.clientY, left:left+evt.clientX, top:top+evt.clientY};
							}
							else return {x:evt.pageX, y:evt.pageY, left:evt.pageX, top:evt.pageY};
						})();
					evt.mouse.position.layer = {x:evt.layerX||evt.x, y:evt.layerY||evt.y, left:evt.layerX||evt.x, top:evt.layerY||evt.y};
				}
				
				
				//add custom key attributes
				evt.keyboard = {};
				
				//see http://unixpapa.com/js/key.html
				//    http://www.quirksmode.org/js/keys.html
				if(evt.type=="keypress")
				{
					if(evt.which === undefined)	//IE lte 8
					{
						evt.keyboard.charCode = evt.keyCode;
						evt.keyboard.char = String.fromCharCode(evt.keyCode);
					}
					else if(evt.which != 0 && evt.charCode != 0)	//other browsers (note: sometimes special keys still give a non-zero value)
					{
						evt.keyboard.charCode = evt.which;
						evt.keyboard.char = String.fromCharCode(evt.which);
					}
					else	//special key
					{
						evt.keyboard.charCode = 0;
						evt.keyboard.char = evt.key;	//evt.key only works in IE gte 9; it gives "Control", "Shift", "Down", etc. for special keys
					}
				}
				else if(evt.type=="keydown" || evt.type=="keyup")
				{
					evt.keyboard.char = evt.key;	//evt.key only works in IE gte 9
				}
				
				
				//execute the handler
				handler.call(obj, evt);	//corrects the `this` keyword and passes the patched event object
			};
	}
	patchHandler.preventDefault = function(){ this.returnValue = false; };
	patchHandler.stopPropagation = function(){
			if(!document.addEventListener)	//IE lte 8
				this.cancelBubble = true;
			else
			{
				this._propagationStopped = true;
				if(this.type != "mouseover" && this.type != "mouseout") this.stopPropagation();
			}
		};
	patchHandler.mouseButtons = { button: 0, pressed: 0 };	//for IE lte 8, keeps track of which mouse buttons are pressed
	
	
	
	//for browsers other than IE lte 8
	//stores the elements with mouseenter/leave handlers that need to be executed once the mouseover/out event finishes bubbling
	var mouseELQueue = [];
	var stopQueuingEL;	//flag to stop queuing mouseenter/leave handlers in handleEvent()
	
	//global handler that executes all handlers assigned to an object for the passed event
	function handleEvent(evt)
	{
		var returnValue = true;
		var handlers = this._eventHandlers[evt.type];
		
		//if propagation hasn't been stopped, execute the handlers
		if(!evt._propagationStopped)
		{
			//execute each event handler (in FIFO order)
			for(var i=0; i<handlers.length; i++)
			{
				//execute the handler and update the return value
				returnValue = returnValue && handlers[i].handler.call(this, evt) !== false;
			}
		}
		if(!returnValue) evt.preventDefault();
		
		
		
		//if not IE lte 8, and mouseenter/leave handlers may need to be executed
		if(document.addEventListener && (evt.type == "mouseover" || evt.type == "mouseout"))
		{
			if(this == evt.target) stopQueuingEL = false;
			
			//if this element has mouseenter/leave handlers, queue it to have the handlers executed after the mouseover/out event has completed
			
			//test whether the element is still in the DOM or if it has been removed by a previous handler
			function inDOM(elem)
			{
				if(!elem) return false;
				if(elem == evt.view.document.documentElement) return true;
				var tmp = elem;
				do
				{
					if(!tmp.parentNode) return false;	//element is not in the DOM
					tmp = tmp.parentNode;
				}while(tmp != evt.view.document.documentElement)
				return true;
			}
			
			function queueEL(type, elem, related)
			{
				if(stopQueuingEL) return;
				
				if((type == "mouseenter" && !inDOM(elem)) || (type == "mouseleave" && !inDOM(related)))
				{
					stopQueuingEL = true;
					return;
				}
				
				do
				{
					if(related == elem)	//didn't enter/leave the element, only a child element
					{
						stopQueuingEL = true;
						return;
					}
					related = related.parentNode;
				}while(related)
				
				var evtCopy = {};
				for(i in evt) evtCopy[i] = evt[i];
				evtCopy.type = type;
				//save pointer to the element and a copy of the event object to do the mouseenter/leave handlers later
				mouseELQueue.push([elem, evtCopy]);
			}
			if(evt.type == "mouseover" && this._eventHandlers["mouseenter"])
				queueEL("mouseenter", this, (evt.relatedTarget || this));
				//when the page loads, if the mouse is already inside the element and is then moved, the mouseover event fires. There is no source
				// element in that case so the related target is itself
			else if(evt.type == "mouseout" && this._eventHandlers["mouseleave"])
				queueEL("mouseleave", this, evt.relatedTarget);
			
			//if mouseover/out event has stopped bubbling, "fire" mouseenter/leave event
			if(this == evt.view && mouseELQueue.length > 0)
			{
				var q, elem, evtCopy;
				
				//handle queued events
				while(mouseELQueue.length > 0)	//for each queued element
				{
					q = evt.type=="mouseover" ? mouseELQueue.pop() : mouseELQueue.shift();	//mouseenter events are handled in reverse order
					elem = q[0];
					evtCopy = q[1];
					
					if(inDOM(elem) || elem == evt.view.document || elem == evt.view)
					{
						handlers = elem._eventHandlers[evt.type];
						for(var i=0; i<handlers.length; i++)	//execute the element's mouseenter/leave handlers
						{
							//execute the handler and update the return value
							returnValue = returnValue && handlers[i].handler.call(elem, evtCopy) !== false;
						}
					}
				}
			}
			
		}
		
		return returnValue;
	}
	
	return addEvent;
})();

function removeEventHandler(obj, type, handler_or_guid)
{
	type = type.toLowerCase();
	if(type.slice(0,2) == "on") type = type.slice(2);
	
	var guid;
	if(!isNaN(1*handler_or_guid)) guid = handler_or_guid;	//GUID was passed
	else guid = handler_or_guid._handlerGUID;	//handler function was passed
	
	if(guid && obj._eventHandlers && obj._eventHandlers[type])
	{
		var handlers = obj._eventHandlers[type];
		for(var i in handlers)
		{
			if(handlers[i].guid == guid)	//handler is in the list
			{
				handlers.splice(i, 1);	//remove the handler from the list
				break;
			}
		}
	}
}

Revision: 44559
at September 24, 2011 05:54 by wizard04


Updated Code
/*****************************************
 * Enhanced cross-browser event handling v6.1
 * 
 * This work is licensed under a Creative Commons Attribution 3.0 Unported License
 * http://creativecommons.org/licenses/by/3.0/
 *
 * Author: Andy Harrison, http://dragonzreef.com/
 * Date: 23 September 2011
 *****************************************/

//Cross-browser event registration functions: addEventHandler() and removeEventHandler()
//Handlers execute in FIFO order
//Supports mouseenter and mouseleave events
//Custom event attributes:
//	event.mouse.button: the mouse button value (1, 2, or 4) for mousedown, mouseup, click, and dblclick events
//	event.mouse.position: object containing the mouse positions within the screen, window, document, and layer (e.g., evt.mouse.position.document.x)
//	event.mouse.wheelDelta: distance ("clicks") the mouse wheel rolled; negative means it rolled up
//  event.keyboard.charCode: Unicode character code that was generated on a keypress event
//  event.keyboard.char: Unicode character that was generated on a keypress event

//Techniques and inspiration largely from:
// http://www.quirksmode.org/blog/archives/2005/10/_and_the_winner_1.html
// http://dean.edwards.name/weblog/2005/10/add-event/
// http://dean.edwards.name/weblog/2005/10/add-event2/
// http://dean.edwards.name/weblog/2005/10/add-event2/#comment6264
// http://outofhanwell.wordpress.com/2006/07/03/cross-window-events/
// http://blog.metawrap.com/2005/10/24/ie-closures-leaks/
// http://www.quirksmode.org/js/events_properties.html

//This script uses a custom event attribute for the mouse button value: event.mouse.button
//This goes by the Microsoft model, where left==1, right==2, and middle==4
//In IE lte 8, the value may be incorrect on mousedown since we can't reliably keep track of IE's event.button value between events.
// (e.g., if the mouse leaves the window, buttons are changed by the user without the browser's detection, and the mouse comes back)
//
//Unlike in jQuery, mouseenter/leave events match IE in the order the events are fired. For example, instead of mouseenter being fired
// immediately after mouseover, this implementation waits for the mouseover event to bubble to the top before firing the mouseenter event.
// Also, mouseenter handlers are executed top-down (as if it were a capture phase).
//For IE 8 and older, I used the mouseenter/leave functionality that's built in since there were problems with stopPropagation()
// (specifically, evt._propagationStopped won't hold its value)
//
//Be aware that browsers act differently when an affected element is removed/added from the DOM or repositioned on the page. Some fire
// mouseover/out events when an element is added/removed/positioned under the mouse, some execute handlers for a bubbled event even when
// the element has already been removed from the DOM in a previous handler, etc.
//
//Note: event types are case sensitive and should not include "on" (e.g., use "click", not "onclick")



//addEventHandler(obj, type, handler)
//returns the GUID of the handler
var addEventHandler = (function(){
	
	var newGUID = 1;	//GUID to assign to the next event handler function without one
	
	function addEvent(obj, type, handler)
	{
		//make sure the object's window has the required event handlers
		var ownerWindow = getOwnerWindow(obj);
		if(!ownerWindow._eventHandlers) addWindowHandlers(ownerWindow);
		
		if(!obj._eventHandlers) obj._eventHandlers = [];
		
		//if not IE lte 8 and event type is mouseenter/leave, add global handler for the triggering event type instead of the mouseenter/leave event type
		if(document.addEventListener && (type == "mouseenter" || type == "mouseleave"))
		{
			if(!obj._eventHandlers[type]) obj._eventHandlers[type] = [];	//do not add global event handler for the mouseenter/leave event type
			
			addTypeHandler(obj, type=="mouseenter" ? "mouseover" : "mouseout");	//add global handler for the trigger event type instead
		}
		else
			addTypeHandler(obj, type);
		
		if(!handler._handlerGUID) handler._handlerGUID = newGUID++;	//assign a GUID to the handler if it doesn't have one
		var guid = handler._handlerGUID;
		
		if(!assigned(obj, type, guid))	//if this handler isn't already assigned to this event type and object
			obj._eventHandlers[type].push({ guid: guid, handler: handler });	//add the handler to the list
		
		return guid;
	}
	
	
	
	//get the window in which the DOM node resides; this is not necessarily the same window where a function is defined
	function getOwnerWindow(obj)
	{
		return (obj.ownerDocument || obj.document || obj).parentWindow || window;
		/*      obj==element         obj==window     obj==document */
	}
	
	
	
	function addWindowHandlers(ownerWindow)
	{
		if(!ownerWindow._eventHandlers) ownerWindow._eventHandlers = [];
		
		function emptyFunction(){}
		if(document.addEventListener)	//not IE lte 8
		{
			//add empty handlers to window so that mouseenter/leave handlers will be executed when the mouseover/out event stops bubbling
			addEvent(ownerWindow, "mouseover", emptyFunction);
			addEvent(ownerWindow, "mouseout", emptyFunction);
		}
		else	//IE lte 8
		{
			//add empty handlers to document to be sure mouse button variables are updated (these events don't bubble to window in IE lte 8)
			addEvent(ownerWindow.document, "mousedown", emptyFunction);
			addEvent(ownerWindow.document, "mouseup", emptyFunction);
		}
		
		
		//remove circular references and avoid memory leaks when the window unloads (especially for IE)
		function flushEventHandlers(obj)	//nulls event attributes and handler collection
		{
			if(obj._eventHandlers) obj._eventHandlers = null;
			for(var prop in obj)
			{
				if(prop.indexOf("on") == 0) obj[prop] = null;
			}
		}
		function flushAllHandlers()
		{
			var elems = ownerWindow.document.getElementsByTagName("*");
			for(var i=0; i<elems.length; i++){ flushEventHandlers(elems[i]); }
			flushEventHandlers(ownerWindow.document);
			flushEventHandlers(ownerWindow);
		}
		addEvent(ownerWindow, "unload", flushAllHandlers);
	}
	
	
	
	//add global handler for an event type for an object (if it doesn't already exist)
	function addTypeHandler(obj, type)
	{
		if(!obj._eventHandlers[type])
		{
			obj._eventHandlers[type] = [];
			
			if(obj.addEventListener) obj.addEventListener(type, patchHandler(obj, handleEvent), false);
			else if(obj.attachEvent) obj.attachEvent("on"+type, patchHandler(obj, handleEvent));
			else
			{
				if(obj["on"+type]) obj._eventHandlers[type][0] = { handler: obj["on"+type] };
				obj["on"+type] = patchHandler(obj, handleEvent);
			}
		}
	}
	
	//is the handler for this event type for this object already in the list?
	function assigned(obj, type, guid)
	{
		for(var i in obj._eventHandlers[type])
		{
			if(obj._eventHandlers[type][i].guid == guid) return true;	//handler is already in the list
		}
		return false;
	}
	
	//"patches" a handler function so that:
	//- the `this` keyword within the handler refers to the correct object
	//- the event object has oft-used W3C-standard properties/methods
	//- the event object is always passed as an argument to the handler
	//it also:
	//- adds custom attributes to the event object
	//- modifies evt.stopPropagation() for use with mouseenter/leave events
	function patchHandler(obj, handler)
	{
		var undefined;
		
		return function(evt){
				//In IE lte 8, if this patched handler is assigned to an event attribute on a DOM node instead of using attachEvent(), 
				// we need to get the event object from the window the node resides in (which is not necessarily where the handler was defined).
				var evtWindow = getOwnerWindow(obj);
				evt = evt || evtWindow.event;
				if(!evt.view) evt.view = evtWindow
				
				
				if(evt.target === undefined) evt.target = evt.srcElement;
				if(evt.relatedTarget === undefined) evt.relatedTarget = evt.target==evt.toElement ? evt.fromElement : evt.toElement;
				if(evt.fromElement === undefined && evt.toElement === undefined)
				{
					if(evt.type=="mouseover" || evt.type=="mouseenter")
					{
						evt.fromElement = evt.relatedTarget;
						evt.toElement = evt.target;
					}
					else if(evt.type=="mouseout" || evt.type=="mouseleave")
					{
						evt.fromElement = evt.target;
						evt.toElement = evt.relatedTarget;
					}
				}
				if(evt.currentTarget === undefined) evt.currentTarget = obj;	//usually the same as the `this` keyword inside the handler
				if(evt.eventPhase === undefined) evt.eventPhase = 3 - 1*(evt.target === obj);	//capturing==1 (n/a), at_target==2, bubbling==3
				
				evt.preventDefault = evt.preventDefault || patchHandler.preventDefault;
				evt.stopPropagation = patchHandler.stopPropagation;
				
				
				//add custom mouse attributes
				evt.mouse = {};
				
				//mouse button
				//this is the button that was pressed to trigger this event: 1==left, 2==right, 4==middle
				if(evt.type == "mousedown" || evt.type == "mouseup" || evt.type == "click" || evt.type == "dblclick")
				{
					if(evt.which)	//not IE lte 8
						evt.mouse.button = evt.which == 1 ? 1 : evt.which == 2 ? 4 : 2;
					else	//IE lte 8
					{
						var mb = patchHandler.mouseButtons;
						if(obj == evt.target)	//update mb.button
						{
							if(evt.type == "mousedown")
							{
								mb.button = (evt.button ^ mb.pressed) & evt.button;
								if((mb.button & evt.button) == 0) mb.button = evt.button;
								mb.pressed = evt.button;
								
								//note: mb.button may be incorrect on mousedown since we can't reliably keep track of IE's event.button
								// value (i.e., which buttons are pressed when the event is fired) between events (e.g., if the mouse
								// leaves the window, buttons are changed by the user without the browser's detection, and the mouse comes back)
							}
							else if(evt.type == "mouseup")
							{
								mb.button = evt.button;
								mb.pressed = ~evt.button & mb.pressed;
							}
						}
						evt.mouse.button = mb.button;
					}
				}
				else evt.mouse.button = patchHandler.mouseButtons.button = 0;
				
				//mouse wheel distance
				//this is the distance ("clicks") the mouse wheel rolled; negative means it rolled up
				if((evt.type=="mousewheel" || evt.type=="wheel" || evt.type=="DOMMouseScroll") && (evt.wheelDelta || evt.detail))
					evt.mouse.wheelDelta = evt.wheelDelta ? -evt.wheelDelta/120 : evt.detail ? evt.detail/3 : 0;
				
				//mouse position
				if(evt.type.slice(0,5) == "mouse" || evt.type=="wheel" || evt.type=="DOMMouseScroll" || evt.type.slice(0,4)=="drag" || evt.type=="drop")
				{
					evt.mouse.position = {};
					evt.mouse.position.screen = {x:evt.screenX, y:evt.screenY, left:evt.screenX, top:evt.screenY};
					evt.mouse.position.window = evt.mouse.position.frame = {x:evt.clientX, y:evt.clientY, left:evt.clientX, top:evt.clientY};
					evt.mouse.position.document = (function(){
							if(isNaN(evt.pageX) || isNaN(evt.pageY))
							{
								var left, top;	//scroll position of document
								if(window.pageYOffset)	//all except IE
								{ left = window.pageXOffset; top = window.pageYOffset; }
								else if(document.documentElement && !isNaN(document.documentElement.scrollTop))	//IE standards compliance mode
								{ left = document.documentElement.scrollLeft; top = document.documentElement.scrollTop; }
								else	//IE quirks mode
								{ left = document.body.scrollLeft; top = document.body.scrollTop; }
								return {x:left+evt.clientX, y:top+evt.clientY, left:left+evt.clientX, top:top+evt.clientY};
							}
							else return {x:evt.pageX, y:evt.pageY, left:evt.pageX, top:evt.pageY};
						})();
					evt.mouse.position.layer = {x:evt.layerX||evt.x, y:evt.layerY||evt.y, left:evt.layerX||evt.x, top:evt.layerY||evt.y};
				}
				
				
				//add custom key attributes
				evt.keyboard = {};
				
				//see http://unixpapa.com/js/key.html
				//    http://www.quirksmode.org/js/keys.html
				if(evt.type=="keypress")
				{
					if(evt.which === undefined)	//IE lte 8
					{
						evt.keyboard.charCode = evt.keyCode;
						evt.keyboard.char = String.fromCharCode(evt.keyCode);
					}
					else if(evt.which != 0 && evt.charCode != 0)	//other browsers (note: sometimes special keys still give a non-zero value)
					{
						evt.keyboard.charCode = evt.which;
						evt.keyboard.char = String.fromCharCode(evt.which);
					}
					else	//special key
					{
						evt.keyboard.charCode = 0;
						evt.keyboard.char = evt.key;	//evt.key only works in IE gte 9; it gives "Control", "Shift", "Down", etc. for special keys
					}
				}
				else if(evt.type=="keydown" || evt.type=="keyup")
				{
					evt.keyboard.char = evt.key;	//evt.key only works in IE gte 9
				}
				
				
				//execute the handler
				handler.call(obj, evt);	//corrects the `this` keyword and passes the patched event object
			};
	}
	patchHandler.preventDefault = function(){ this.returnValue = false; };
	patchHandler.stopPropagation = function(){
			if(!document.addEventListener)	//IE lte 8
				this.cancelBubble = true;
			else
			{
				this._propagationStopped = true;
				if(this.type != "mouseover" && this.type != "mouseout") this.stopPropagation();
			}
		};
	patchHandler.mouseButtons = { button: 0, pressed: 0 };	//for IE lte 8, keeps track of which mouse buttons are pressed
	
	
	
	//for browsers other than IE lte 8
	//stores the elements with mouseenter/leave handlers that need to be executed once the mouseover/out event finishes bubbling
	var mouseELQueue = [];
	var stopQueuingEL;	//flag to stop queuing mouseenter/leave handlers in handleEvent()
	
	//global handler that executes all handlers assigned to an object for the passed event
	function handleEvent(evt)
	{
		var returnValue = true;
		var handlers = this._eventHandlers[evt.type];
		
		//if propagation hasn't been stopped, execute the handlers
		if(!evt._propagationStopped)
		{
			//execute each event handler (in FIFO order)
			for(var i=0; i<handlers.length; i++)
			{
				//execute the handler and update the return value
				returnValue = returnValue && handlers[i].handler.call(this, evt) !== false;
			}
		}
		if(!returnValue) evt.preventDefault();
		
		
		
		//if not IE lte 8, and mouseenter/leave handlers may need to be executed
		if(document.addEventListener && (evt.type == "mouseover" || evt.type == "mouseout"))
		{
			if(this == evt.target) stopQueuingEL = false;
			
			//if this element has mouseenter/leave handlers, queue it to have the handlers executed after the mouseover/out event has completed
			
			//test whether the element is still in the DOM or if it has been removed by a previous handler
			function inDOM(elem)
			{
				if(!elem) return false;
				if(elem == evt.view.document.documentElement) return true;
				var tmp = elem;
				do
				{
					if(!tmp.parentNode) return false;	//element is not in the DOM
					tmp = tmp.parentNode;
				}while(tmp != evt.view.document.documentElement)
				return true;
			}
			
			function queueEL(type, elem, related)
			{
				if(stopQueuingEL) return;
				
				if((type == "mouseenter" && !inDOM(elem)) || (type == "mouseleave" && !inDOM(related)))
				{
					stopQueuingEL = true;
					return;
				}
				
				do
				{
					if(related == elem)	//didn't enter/leave the element, only a child element
					{
						stopQueuingEL = true;
						return;
					}
					related = related.parentNode;
				}while(related)
				
				var evtCopy = {};
				for(i in evt) evtCopy[i] = evt[i];
				evtCopy.type = type;
				//save pointer to the element and a copy of the event object to do the mouseenter/leave handlers later
				mouseELQueue.push([elem, evtCopy]);
			}
			if(evt.type == "mouseover" && this._eventHandlers["mouseenter"])
				queueEL("mouseenter", this, (evt.relatedTarget || this));
				//when the page loads, if the mouse is already inside the element and is then moved, the mouseover event fires. There is no source
				// element in that case so the related target is itself
			else if(evt.type == "mouseout" && this._eventHandlers["mouseleave"])
				queueEL("mouseleave", this, evt.relatedTarget);
			
			//if mouseover/out event has stopped bubbling, "fire" mouseenter/leave event
			if(this == evt.view && mouseELQueue.length > 0)
			{
				var q, elem, evtCopy;
				
				//handle queued events
				while(mouseELQueue.length > 0)	//for each queued element
				{
					q = evt.type=="mouseover" ? mouseELQueue.pop() : mouseELQueue.shift();	//mouseenter events are handled in reverse order
					elem = q[0];
					evtCopy = q[1];
					
					if(inDOM(elem) || elem == evt.view.document || elem == evt.view)
					{
						handlers = elem._eventHandlers[evt.type];
						for(var i=0; i<handlers.length; i++)	//execute the element's mouseenter/leave handlers
						{
							//execute the handler and update the return value
							returnValue = returnValue && handlers[i].handler.call(elem, evtCopy) !== false;
						}
					}
				}
			}
			
		}
		
		return returnValue;
	}
	
	return addEvent;
})();

function removeEventHandler(obj, type, handler_or_guid)
{
	type = type.toLowerCase();
	if(type.slice(0,2) == "on") type = type.slice(2);
	
	var guid;
	if(!isNaN(1*handler_or_guid)) guid = handler_or_guid;	//GUID was passed
	else guid = handler_or_guid._handlerGUID;	//handler function was passed
	
	if(guid && obj._eventHandlers && obj._eventHandlers[type])
	{
		var handlers = obj._eventHandlers[type];
		for(var i in handlers)
		{
			if(handlers[i].guid == guid)	//handler is in the list
			{
				handlers.splice(i, 1);	//remove the handler from the list
				break;
			}
		}
	}
}

Revision: 44558
at September 21, 2011 04:08 by wizard04


Updated Code
/*****************************************
 * Enhanced cross-browser event handling v5.10
 * 
 * This work is licensed under a Creative Commons Attribution 3.0 Unported License
 * http://creativecommons.org/licenses/by/3.0/
 *
 * Author: Andy Harrison, http://dragonzreef.com/
 * Date: 20 September 2011
 *****************************************/

//Cross-browser event registration functions: addEventHandler() and removeEventHandler()
//Handlers execute in FIFO order
//Supports mouseenter and mouseleave events
//Custom event attributes:
//	event.window: the window that the target resides in
//	event.mouseButton: the mouse button value (1, 2, or 4) for mousedown, mouseup, click, and dblclick events
//	event.mousePosition: object containing the mouse positions within the screen, window, document, and layer (e.g., evt.mousePosition.document.x)

//Techniques and inspiration largely from:
// http://www.quirksmode.org/blog/archives/2005/10/_and_the_winner_1.html
// http://dean.edwards.name/weblog/2005/10/add-event/
// http://dean.edwards.name/weblog/2005/10/add-event2/
// http://dean.edwards.name/weblog/2005/10/add-event2/#comment6264
// http://outofhanwell.wordpress.com/2006/07/03/cross-window-events/
// http://blog.metawrap.com/2005/10/24/ie-closures-leaks/
// http://www.quirksmode.org/js/events_properties.html

//This script uses a custom event attribute for the mouse button value: event.mouseButton
//This goes by the Microsoft model, where left==1, right==2, and middle==4
//In IE lte 8, the value may be incorrect on mousedown since we can't reliably keep track of IE's event.button value between events.
// (e.g., if the mouse leaves the window, buttons are changed by the user without the browser's detection, and the mouse comes back)
//
//Unlike in jQuery, mouseenter/leave events match IE in the order the events are fired. For example, instead of mouseenter being fired
// immediately after mouseover, this implementation waits for the mouseover event to bubble to the top before firing the mouseenter event.
// Also, mouseenter handlers are executed top-down (as if it were a capture phase).
//For IE 8 and older, I used the mouseenter/leave functionality that's built in since there were problems with stopPropagation()
// (specifically, evt._propagationStopped won't hold its value)
//
//Be aware that browsers act differently when an affected element is removed/added from the DOM or repositioned on the page. Some fire
// mouseover/out events when an element is added/removed/positioned under the mouse, some execute handlers for a bubbled event even when
// the element has already been removed from the DOM in a previous handler, etc.



//addEventHandler(obj, type, handler)
//returns the GUID of the handler
var addEventHandler = (function(){
	
	var newGUID = 1;	//GUID to assign to the next event handler function without one
	
	function addEvent(obj, type, handler)
	{
		type = type.toLowerCase();
		if(type.slice(0,2) == "on") type = type.slice(2);
		
		//make sure the object's window has the required event handlers
		var ownerWindow = getOwnerWindow(obj);
		if(!ownerWindow._eventHandlers) addWindowHandlers(ownerWindow);
		
		if(!obj._eventHandlers) obj._eventHandlers = [];
		
		//if not IE lte 8 and event type is mouseenter/leave, add global handler for the triggering event type instead of the mouseenter/leave event type
		if(document.addEventListener && (type == "mouseenter" || type == "mouseleave"))
		{
			if(!obj._eventHandlers[type]) obj._eventHandlers[type] = [];	//do not add global event handler for the mouseenter/leave event type
			
			addTypeHandler(obj, type=="mouseenter" ? "mouseover" : "mouseout");	//add global handler for the trigger event type instead
		}
		else
			addTypeHandler(obj, type);
		
		if(!handler._handlerGUID) handler._handlerGUID = newGUID++;	//assign a GUID to the handler if it doesn't have one
		var guid = handler._handlerGUID;
		
		if(!assigned(obj, type, guid))	//if this handler isn't already assigned to this event type and object
			obj._eventHandlers[type].push({ guid: guid, handler: handler });	//add the handler to the list
		
		return guid;
	}
	
	
	
	//get the window in which the DOM node resides; this is not necessarily the same window where a function is defined
	function getOwnerWindow(obj)
	{
		return (obj.ownerDocument || obj.document || obj).parentWindow || window;
		/*      obj==element         obj==window     obj==document */
	}
	
	
	
	function addWindowHandlers(ownerWindow)
	{
		if(!ownerWindow._eventHandlers) ownerWindow._eventHandlers = [];
		
		function emptyFunction(){}
		if(document.addEventListener)	//not IE lte 8
		{
			//add empty handlers to window so that mouseenter/leave handlers will be executed when the mouseover/out event stops bubbling
			addEvent(ownerWindow, "mouseover", emptyFunction);
			addEvent(ownerWindow, "mouseout", emptyFunction);
		}
		else	//IE lte 8
		{
			//add empty handlers to document to be sure mouse button variables are updated (these events don't bubble to window in IE lte 8)
			addEvent(ownerWindow.document, "mousedown", emptyFunction);
			addEvent(ownerWindow.document, "mouseup", emptyFunction);
		}
		
		
		//remove circular references and avoid memory leaks when the window unloads (especially for IE)
		function flushEventHandlers(obj)	//nulls event attributes and handler collection
		{
			if(obj._eventHandlers) obj._eventHandlers = null;
			for(var prop in obj)
			{
				if(prop.indexOf("on") == 0) obj[prop] = null;
			}
		}
		function flushAllHandlers()
		{
			var elems = ownerWindow.document.getElementsByTagName("*");
			for(var i=0; i<elems.length; i++){ flushEventHandlers(elems[i]); }
			flushEventHandlers(ownerWindow.document);
			flushEventHandlers(ownerWindow);
		}
		addEvent(ownerWindow, "unload", flushAllHandlers);
	}
	
	
	
	//add global handler for an event type for an object (if it doesn't already exist)
	function addTypeHandler(obj, type)
	{
		if(!obj._eventHandlers[type])
		{
			obj._eventHandlers[type] = [];
			
			if(obj.addEventListener) obj.addEventListener(type, patchHandler(obj, handleEvent), false);
			else if(obj.attachEvent) obj.attachEvent("on"+type, patchHandler(obj, handleEvent));
			else
			{
				if(obj["on"+type]) obj._eventHandlers[type][0] = { handler: obj["on"+type] };
				obj["on"+type] = patchHandler(obj, handleEvent);
			}
		}
	}
	
	//is the handler for this event type for this object already in the list?
	function assigned(obj, type, guid)
	{
		for(var i in obj._eventHandlers[type])
		{
			if(obj._eventHandlers[type][i].guid == guid) return true;	//handler is already in the list
		}
		return false;
	}
	
	//"patches" a handler function so that:
	//- the `this` keyword within the handler refers to the correct object
	//- the event object has oft-used W3C-standard properties/methods
	//- the event object is always passed as an argument to the handler
	//it also:
	//- adds custom attributes .window, .mousePosition, and .mouseButton to the event object
	//- modifies evt.stopPropagation() for use with mouseenter/leave events
	function patchHandler(obj, handler)
	{
		var undefined;
		
		return function(evt){
				//In IE lte 8, if this patched handler is assigned to an event attribute on a DOM node instead of using attachEvent(), 
				// we need to get the event object from the window the node resides in (which is not necessarily where the handler was defined).
				var evtWindow = getOwnerWindow(obj);
				evt = evt || evtWindow.event;
				
				//custom attribute for easy access to the window in which the event was fired
				evt.window = evtWindow;
				
				
				if(evt.target === undefined) evt.target = evt.srcElement;
				if(evt.relatedTarget === undefined) evt.relatedTarget = evt.target==evt.toElement ? evt.fromElement : evt.toElement;
				if(evt.currentTarget === undefined) evt.currentTarget = obj;	//usually the same as the `this` keyword inside the handler
				if(evt.eventPhase === undefined) evt.eventPhase = 3 - 1*(evt.target === obj);	//capturing==1 (n/a), at_target==2, bubbling==3
				
				evt.preventDefault = evt.preventDefault || patchHandler.preventDefault;
				evt.stopPropagation = patchHandler.stopPropagation;
				
				
				//add custom mouse position attributes
				if(evt.type.slice(0,5) == "mouse" || evt.type=="wheel" || evt.type.slice(0,4)=="drag" || evt.type=="drop")
				{
					evt.mousePosition = {};
					evt.mousePosition.screen = {x:evt.screenX, y:evt.screenY, left:evt.screenX, top:evt.screenY};
					evt.mousePosition.window = evt.mousePosition.frame = {x:evt.clientX, y:evt.clientY, left:evt.clientX, top:evt.clientY};
					evt.mousePosition.document = (function(){
							if(isNaN(evt.pageX) || isNaN(evt.pageY))
							{
								var left, top;	//scroll position of document
								if(window.pageYOffset)	//all except IE
								{ left = window.pageXOffset; top = window.pageYOffset; }
								else if(document.documentElement && !isNaN(document.documentElement.scrollTop))	//IE standards compliance mode
								{ left = document.documentElement.scrollLeft; top = document.documentElement.scrollTop; }
								else	//IE quirks mode
								{ left = document.body.scrollLeft; top = document.body.scrollTop; }
								return {x:left+evt.clientX, y:top+evt.clientY, left:left+evt.clientX, top:top+evt.clientY};
							}
							else return {x:evt.pageX, y:evt.pageY, left:evt.pageX, top:evt.pageY};
						})();
					evt.mousePosition.layer = {x:evt.layerX||evt.x, y:evt.layerY||evt.y, left:evt.layerX||evt.x, top:evt.layerY||evt.y};
				}
				
				
				//add custom mouse button attribute
				//this is the button that was pressed to trigger this event: 1==left, 2==right, 4==middle
				if(evt.type == "mousedown" || evt.type == "mouseup" || evt.type == "click" || evt.type == "dblclick")
				{
					if(evt.which)	//not IE lte 8
						evt.mouseButton = evt.which == 1 ? 1 : evt.which == 2 ? 4 : 2;
					else	//IE lte 8
					{
						var mb = patchHandler.mouseButtons;
						if(obj == evt.target)	//update mb.button
						{
							if(evt.type == "mousedown")
							{
								mb.button = (evt.button ^ mb.pressed) & evt.button;
								if((mb.button & evt.button) == 0) mb.button = evt.button;
								mb.pressed = evt.button;
								
								//note: mb.button may be incorrect on mousedown since we can't reliably keep track of IE's event.button
								// value (i.e., which buttons are pressed when the event is fired) between events (e.g., if the mouse
								// leaves the window, buttons are changed by the user without the browser's detection, and the mouse comes back)
							}
							else if(evt.type == "mouseup")
							{
								mb.button = evt.button;
								mb.pressed = ~evt.button & mb.pressed;
							}
						}
						evt.mouseButton = mb.button;
					}
				}
				else evt.mouseButton = patchHandler.mouseButtons.button = 0;
				
				
				handler.call(obj, evt);	//corrects the `this` keyword and passes the patched event object
			};
	}
	patchHandler.preventDefault = function(){ this.returnValue = false; };
	patchHandler.stopPropagation = function(){
			if(!document.addEventListener)	//IE lte 8
				this.cancelBubble = true;
			else
			{
				this._propagationStopped = true;
				if(this.type != "mouseover" && this.type != "mouseout") this.stopPropagation();
			}
		};
	patchHandler.mouseButtons = { button: 0, pressed: 0 };	//for IE lte 8, keeps track of which mouse buttons are pressed
	
	
	
	//for browsers other than IE lte 8
	//stores the elements with mouseenter/leave handlers that need to be executed once the mouseover/out event finishes bubbling
	var mouseELQueue = [];
	var stopQueuingEL;	//flag to stop queuing mouseenter/leave handlers in handleEvent()
	
	//global handler that executes all handlers assigned to an object for the passed event
	function handleEvent(evt)
	{
		var returnValue = true;
		var handlers = this._eventHandlers[evt.type];
		
		//if propagation hasn't been stopped, execute the handlers
		if(!evt._propagationStopped)
		{
			//execute each event handler (in FIFO order)
			for(var i=0; i<handlers.length; i++)
			{
				//execute the handler and update the return value
				returnValue = returnValue && handlers[i].handler.call(this, evt) !== false;
			}
		}
		if(!returnValue) evt.preventDefault();
		
		
		
		//if not IE lte 8, and mouseenter/leave handlers may need to be executed
		if(document.addEventListener && (evt.type == "mouseover" || evt.type == "mouseout"))
		{
			if(this == evt.target) stopQueuingEL = false;
			
			//if this element has mouseenter/leave handlers, queue it to have the handlers executed after the mouseover/out event has completed
			
			//test whether the element is still in the DOM or if it has been removed by a previous handler
			function inDOM(elem)
			{
				if(!elem) return false;
				if(elem == evt.window.document.documentElement) return true;
				var tmp = elem;
				do
				{
					if(!tmp.parentNode) return false;	//element is not in the DOM
					tmp = tmp.parentNode;
				}while(tmp != evt.window.document.documentElement)
				return true;
			}
			
			function queueEL(type, elem, related)
			{
				if(stopQueuingEL) return;
				
				if((type == "mouseenter" && !inDOM(elem)) || (type == "mouseleave" && !inDOM(related)))
				{
					stopQueuingEL = true;
					return;
				}
				
				do
				{
					if(related == elem)	//didn't enter/leave the element, only a child element
					{
						stopQueuingEL = true;
						return;
					}
					related = related.parentNode;
				}while(related)
				
				var evtCopy = {};
				for(i in evt) evtCopy[i] = evt[i];
				evtCopy.type = type;
				//save pointer to the element and a copy of the event object to do the mouseenter/leave handlers later
				mouseELQueue.push([elem, evtCopy]);
			}
			if(evt.type == "mouseover" && this._eventHandlers["mouseenter"])
				queueEL("mouseenter", this, (evt.relatedTarget || this));
				//when the page loads, if the mouse is already inside the element and is then moved, the mouseover event fires. There is no source
				// element in that case so the related target is itself
			else if(evt.type == "mouseout" && this._eventHandlers["mouseleave"])
				queueEL("mouseleave", this, evt.relatedTarget);
			
			//if mouseover/out event has stopped bubbling, "fire" mouseenter/leave event
			if(this == evt.window && mouseELQueue.length > 0)
			{
				var q, elem, evtCopy;
				
				//handle queued events
				while(mouseELQueue.length > 0)	//for each queued element
				{
					q = evt.type=="mouseover" ? mouseELQueue.pop() : mouseELQueue.shift();	//mouseenter events are handled in reverse order
					elem = q[0];
					evtCopy = q[1];
					
					if(inDOM(elem) || elem == evt.window.document || elem == evt.window)
					{
						handlers = elem._eventHandlers[evt.type];
						for(var i=0; i<handlers.length; i++)	//execute the element's mouseenter/leave handlers
						{
							//execute the handler and update the return value
							returnValue = returnValue && handlers[i].handler.call(elem, evtCopy) !== false;
						}
					}
				}
			}
			
		}
		
		return returnValue;
	}
	
	return addEvent;
})();

function removeEventHandler(obj, type, handler_or_guid)
{
	type = type.toLowerCase();
	if(type.slice(0,2) == "on") type = type.slice(2);
	
	var guid;
	if(!isNaN(1*handler_or_guid)) guid = handler_or_guid;	//GUID was passed
	else guid = handler_or_guid._handlerGUID;	//handler function was passed
	
	if(guid && obj._eventHandlers && obj._eventHandlers[type])
	{
		var handlers = obj._eventHandlers[type];
		for(var i in handlers)
		{
			if(handlers[i].guid == guid)	//handler is in the list
			{
				handlers.splice(i, 1);	//remove the handler from the list
				break;
			}
		}
	}
}

Revision: 44557
at September 20, 2011 02:17 by wizard04


Updated Code
/*****************************************
 * Enhanced cross-browser event handling v5.9
 * 
 * This work is licensed under a Creative Commons Attribution 3.0 Unported License
 * http://creativecommons.org/licenses/by/3.0/
 *
 * Author: Andy Harrison, http://dragonzreef.com/
 * Date: 19 September 2011
 *****************************************/

//Cross-browser event registration functions: addEventHandler() and removeEventHandler()
//Handlers execute in FIFO order
//Supports mouseenter and mouseleave events
//Custom event attributes:
//	event.window: the window that the target resides in
//	event.mouseButton: the mouse button value (1, 2, or 4) for mousedown, mouseup, click, and dblclick events
//	event.mousePosition: object containing the mouse positions within the screen, window, document, and layer (e.g., evt.mousePosition.document.x)

//Techniques and inspiration largely from:
// http://www.quirksmode.org/blog/archives/2005/10/_and_the_winner_1.html
// http://dean.edwards.name/weblog/2005/10/add-event/
// http://dean.edwards.name/weblog/2005/10/add-event2/
// http://dean.edwards.name/weblog/2005/10/add-event2/#comment6264
// http://outofhanwell.wordpress.com/2006/07/03/cross-window-events/
// http://blog.metawrap.com/2005/10/24/ie-closures-leaks/
// http://www.quirksmode.org/js/events_properties.html

//This script uses a custom event attribute for the mouse button value: event.mouseButton
//This goes by the Microsoft model, where left==1, right==2, and middle==4
//In IE lte 8, the value may be incorrect on mousedown since we can't reliably keep track of IE's event.button value between events.
// (e.g., if the mouse leaves the window, buttons are changed by the user without the browser's detection, and the mouse comes back)
//
//Unlike in jQuery, mouseenter/leave events match IE in the order the events are fired. For example, instead of mouseenter being fired
// immediately after mouseover, this implementation waits for the mouseover event to bubble to the top before firing the mouseenter event.
// Also, mouseenter handlers are executed top-down (as if it were a capture phase).
//For IE 8 and older, I used the mouseenter/leave functionality that's built in since there were problems with stopPropagation()
// (specifically, evt._propagationStopped won't hold its value)
//
//Be aware that browsers act differently when an affected element is removed/added from the DOM or repositioned on the page. Some fire
// mouseover/out events when an element is added/removed/positioned under the mouse, some execute handlers for a bubbled event even when
// the element has already been removed from the DOM in a previous handler, etc.



//addEventHandler(obj, type, handler)
//returns the GUID of the handler
var addEventHandler = (function(){
	
	var newGUID = 1;	//GUID to assign to the next event handler function without one
	
	function addEvent(obj, type, handler)
	{
		type = type.toLowerCase();
		if(type.slice(0,2) == "on") type = type.slice(2);
		
		//make sure the object's window has the required event handlers
		var ownerWindow = getOwnerWindow(obj);
		if(!ownerWindow._eventHandlers) addWindowHandlers(ownerWindow);
		
		if(!obj._eventHandlers) obj._eventHandlers = [];
		
		//if not IE lte 8 and event type is mouseenter/leave, add global handler for the triggering event type instead of the mouseenter/leave event type
		if(document.addEventListener && (type == "mouseenter" || type == "mouseleave"))
		{
			if(!obj._eventHandlers[type]) obj._eventHandlers[type] = [];	//do not add global event handler for the mouseenter/leave event type
			
			addTypeHandler(obj, type=="mouseenter" ? "mouseover" : "mouseout");	//add global handler for the trigger event type instead
		}
		else
			addTypeHandler(obj, type);
		
		if(!handler._handlerGUID) handler._handlerGUID = newGUID++;	//assign a GUID to the handler if it doesn't have one
		var guid = handler._handlerGUID;
		
		if(!assigned(obj, type, guid))	//if this handler isn't already assigned to this event type and object
			obj._eventHandlers[type].push({ guid: guid, handler: handler });	//add the handler to the list
		
		return guid;
	}
	
	
	
	//get the window in which the DOM node resides; this is not necessarily the same window where a function is defined
	function getOwnerWindow(obj)
	{
		return (obj.ownerDocument || obj.document || obj).parentWindow || window;
		/*      obj==element         obj==window     obj==document */
	}
	
	
	
	function addWindowHandlers(ownerWindow)
	{
		if(!ownerWindow._eventHandlers) ownerWindow._eventHandlers = [];
		
		function emptyFunction(){}
		if(document.addEventListener)	//not IE lte 8
		{
			//add empty handlers to window so that mouseenter/leave handlers will be executed when the mouseover/out event stops bubbling
			addEvent(ownerWindow, "mouseover", emptyFunction);
			addEvent(ownerWindow, "mouseout", emptyFunction);
		}
		else	//IE lte 8
		{
			//add empty handlers to document to be sure mouse button variables are updated (these events don't bubble to window in IE lte 8)
			addEvent(ownerWindow.document, "mousedown", emptyFunction);
			addEvent(ownerWindow.document, "mouseup", emptyFunction);
		}
		
		
		//remove circular references and avoid memory leaks when the window unloads (especially for IE)
		function flushEventHandlers(obj)	//nulls event attributes and handler collection
		{
			if(obj._eventHandlers) obj._eventHandlers = null;
			for(var prop in obj)
			{
				if(prop.indexOf("on") == 0) obj[prop] = null;
			}
		}
		function flushAllHandlers()
		{
			var elems = ownerWindow.document.getElementsByTagName("*");
			for(var i=0; i<elems.length; i++){ flushEventHandlers(elems[i]); }
			flushEventHandlers(ownerWindow.document);
			flushEventHandlers(ownerWindow);
		}
		addEvent(ownerWindow, "unload", flushAllHandlers);
	}
	
	
	
	//add global handler for an event type for an object (if it doesn't already exist)
	function addTypeHandler(obj, type)
	{
		if(!obj._eventHandlers[type])
		{
			obj._eventHandlers[type] = [];
			
			if(obj.addEventListener) obj.addEventListener(type, patchHandler(obj, handleEvent), false);
			else if(obj.attachEvent) obj.attachEvent("on"+type, patchHandler(obj, handleEvent));
			else
			{
				if(obj["on"+type]) obj._eventHandlers[type][0] = { handler: obj["on"+type] };
				obj["on"+type] = patchHandler(obj, handleEvent);
			}
		}
	}
	
	//is the handler for this event type for this object already in the list?
	function assigned(obj, type, guid)
	{
		for(var i in obj._eventHandlers[type])
		{
			if(obj._eventHandlers[type][i].guid == guid) return true;	//handler is already in the list
		}
		return false;
	}
	
	//"patches" a handler function so that:
	//- the `this` keyword within the handler refers to the correct object
	//- the event object has oft-used W3C-standard properties/methods
	//- the event object is always passed as an argument to the handler
	//it also:
	//- adds custom attributes .window, .mousePosition, and .mouseButton to the event object
	//- modifies evt.stopPropagation() for use with mouseenter/leave events
	function patchHandler(obj, handler)
	{
		var undefined;
		
		return function(evt){
				//In IE lte 8, if this patched handler is assigned to an event attribute on a DOM node instead of using attachEvent(), 
				// we need to get the event object from the window the node resides in (which is not necessarily where the handler was defined).
				var evtWindow = getOwnerWindow(obj);
				evt = evt || evtWindow.event;
				
				//custom attribute for easy access to the window in which the event was fired
				evt.window = evtWindow;
				
				
				if(evt.target === undefined) evt.target = evt.srcElement;
				if(evt.relatedTarget === undefined) evt.relatedTarget = evt.target==evt.toElement ? evt.fromElement : evt.toElement;
				if(evt.currentTarget === undefined) evt.currentTarget = obj;	//usually the same as the `this` keyword inside the handler
				if(evt.eventPhase === undefined) evt.eventPhase = 3 - 1*(evt.target === obj);	//capturing==1 (n/a), at_target==2, bubbling==3
				
				evt.preventDefault = evt.preventDefault || patchHandler.preventDefault;
				evt.stopPropagation = patchHandler.stopPropagation;
				
				
				//add custom mouse position attributes
				evt.mousePosition = {};
				evt.mousePosition.screen = {x:evt.screenX, y:evt.screenY, left:evt.screenX, top:evt.screenY};
				evt.mousePosition.window = evt.mousePosition.frame = {x:evt.clientX, y:evt.clientY, left:evt.clientX, top:evt.clientY};
				evt.mousePosition.document = (function(){
						if(isNaN(evt.pageX) || isNaN(evt.pageY))
						{
							var left, top;	//scroll position of document
							if(window.pageYOffset)	//all except IE
							{ left = window.pageXOffset; top = window.pageYOffset; }
							else if(document.documentElement && !isNaN(document.documentElement.scrollTop))	//IE standards compliance mode
							{ left = document.documentElement.scrollLeft; top = document.documentElement.scrollTop; }
							else	//IE quirks mode
							{ left = document.body.scrollLeft; top = document.body.scrollTop; }
							return {x:left+evt.clientX, y:top+evt.clientY, left:left+evt.clientX, top:top+evt.clientY};
						}
						else return {x:evt.pageX, y:evt.pageY, left:evt.pageX, top:evt.pageY};
					})();
				evt.mousePosition.layer = {x:evt.layerX||evt.x, y:evt.layerY||evt.y, left:evt.layerX||evt.x, top:evt.layerY||evt.y};
				
				
				//add custom mouse button attribute
				//this is the button that was pressed to trigger this event: 1==left, 2==right, 4==middle
				if(evt.type == "mousedown" || evt.type == "mouseup" || evt.type == "click" || evt.type == "dblclick")
				{
					if(evt.which)	//not IE lte 8
						evt.mouseButton = evt.which == 1 ? 1 : evt.which == 2 ? 4 : 2;
					else	//IE lte 8
					{
						var mb = patchHandler.mouseButtons;
						if(obj == evt.target)	//update mb.button
						{
							if(evt.type == "mousedown")
							{
								mb.button = (evt.button ^ mb.pressed) & evt.button;
								if((mb.button & evt.button) == 0) mb.button = evt.button;
								mb.pressed = evt.button;
								
								//note: mb.button may be incorrect on mousedown since we can't reliably keep track of IE's event.button
								// value (i.e., which buttons are pressed when the event is fired) between events (e.g., if the mouse
								// leaves the window, buttons are changed by the user without the browser's detection, and the mouse comes back)
							}
							else if(evt.type == "mouseup")
							{
								mb.button = evt.button;
								mb.pressed = ~evt.button & mb.pressed;
							}
						}
						evt.mouseButton = mb.button;
					}
				}
				else evt.mouseButton = patchHandler.mouseButtons.button = 0;
				
				
				handler.call(obj, evt);	//corrects the `this` keyword and passes the patched event object
			};
	}
	patchHandler.preventDefault = function(){ this.returnValue = false; };
	patchHandler.stopPropagation = function(){
			if(!document.addEventListener)	//IE lte 8
				this.cancelBubble = true;
			else
			{
				this._propagationStopped = true;
				if(this.type != "mouseover" && this.type != "mouseout") this.stopPropagation();
			}
		};
	patchHandler.mouseButtons = { button: 0, pressed: 0 };	//for IE lte 8, keeps track of which mouse buttons are pressed
	
	
	
	//for browsers other than IE lte 8
	//stores the elements with mouseenter/leave handlers that need to be executed once the mouseover/out event finishes bubbling
	var mouseELQueue = [];
	var stopQueuingEL;	//flag to stop queuing mouseenter/leave handlers in handleEvent()
	
	//global handler that executes all handlers assigned to an object for the passed event
	function handleEvent(evt)
	{
		var returnValue = true;
		var handlers = this._eventHandlers[evt.type];
		
		//if propagation hasn't been stopped, execute the handlers
		if(!evt._propagationStopped)
		{
			//execute each event handler (in FIFO order)
			for(var i=0; i<handlers.length; i++)
			{
				//execute the handler and update the return value
				returnValue = returnValue && handlers[i].handler.call(this, evt) !== false;
			}
		}
		if(!returnValue) evt.preventDefault();
		
		
		
		//if not IE lte 8, and mouseenter/leave handlers may need to be executed
		if(document.addEventListener && (evt.type == "mouseover" || evt.type == "mouseout"))
		{
			if(this == evt.target) stopQueuingEL = false;
			
			//if this element has mouseenter/leave handlers, queue it to have the handlers executed after the mouseover/out event has completed
			
			//test whether the element is still in the DOM or if it has been removed by a previous handler
			function inDOM(elem)
			{
				if(!elem) return false;
				if(elem == evt.window.document.documentElement) return true;
				var tmp = elem;
				do
				{
					if(!tmp.parentNode) return false;	//element is not in the DOM
					tmp = tmp.parentNode;
				}while(tmp != evt.window.document.documentElement)
				return true;
			}
			
			function queueEL(type, elem, related)
			{
				if(stopQueuingEL) return;
				
				if((type == "mouseenter" && !inDOM(elem)) || (type == "mouseleave" && !inDOM(related)))
				{
					stopQueuingEL = true;
					return;
				}
				
				do
				{
					if(related == elem)	//didn't enter/leave the element, only a child element
					{
						stopQueuingEL = true;
						return;
					}
					related = related.parentNode;
				}while(related)
				
				var evtCopy = {};
				for(i in evt) evtCopy[i] = evt[i];
				evtCopy.type = type;
				//save pointer to the element and a copy of the event object to do the mouseenter/leave handlers later
				mouseELQueue.push([elem, evtCopy]);
			}
			if(evt.type == "mouseover" && this._eventHandlers["mouseenter"])
				queueEL("mouseenter", this, (evt.relatedTarget || this));
				//when the page loads, if the mouse is already inside the element and is then moved, the mouseover event fires. There is no source
				// element in that case so the related target is itself
			else if(evt.type == "mouseout" && this._eventHandlers["mouseleave"])
				queueEL("mouseleave", this, evt.relatedTarget);
			
			//if mouseover/out event has stopped bubbling, "fire" mouseenter/leave event
			if(this == evt.window && mouseELQueue.length > 0)
			{
				var q, elem, evtCopy;
				
				//handle queued events
				while(mouseELQueue.length > 0)	//for each queued element
				{
					q = evt.type=="mouseover" ? mouseELQueue.pop() : mouseELQueue.shift();	//mouseenter events are handled in reverse order
					elem = q[0];
					evtCopy = q[1];
					
					if(inDOM(elem) || elem == evt.window.document || elem == evt.window)
					{
						handlers = elem._eventHandlers[evt.type];
						for(var i=0; i<handlers.length; i++)	//execute the element's mouseenter/leave handlers
						{
							//execute the handler and update the return value
							returnValue = returnValue && handlers[i].handler.call(elem, evtCopy) !== false;
						}
					}
				}
			}
			
		}
		
		return returnValue;
	}
	
	return addEvent;
})();

function removeEventHandler(obj, type, handler_or_guid)
{
	type = type.toLowerCase();
	if(type.slice(0,2) == "on") type = type.slice(2);
	
	var guid;
	if(!isNaN(1*handler_or_guid)) guid = handler_or_guid;	//GUID was passed
	else guid = handler_or_guid._handlerGUID;	//handler function was passed
	
	if(guid && obj._eventHandlers && obj._eventHandlers[type])
	{
		var handlers = obj._eventHandlers[type];
		for(var i in handlers)
		{
			if(handlers[i].guid == guid)	//handler is in the list
			{
				handlers.splice(i, 1);	//remove the handler from the list
				break;
			}
		}
	}
}

Revision: 44556
at September 20, 2011 01:48 by wizard04


Updated Code
/*****************************************
 * Enhanced cross-browser event handling v5.9
 * 
 * This work is licensed under a Creative Commons Attribution 3.0 Unported License
 * http://creativecommons.org/licenses/by/3.0/
 *
 * Author: Andy Harrison, http://dragonzreef.com/
 * Date: 19 September 2011
 *****************************************/

//Cross-browser event registration functions: addEventHandler() and removeEventHandler()
//Handlers execute in FIFO order
//Supports mouseenter and mouseleave events
//Custom event attributes:
//	event.window: the window that the target resides in
//	event.mouseButton: the mouse button value (1, 2, or 4) for mousedown, mouseup, click, and dblclick events
//	event.mousePosition: object containing the mouse positions within the screen, window, document, and layer (e.g., evt.mousePosition.document.x)

//Techniques and inspiration largely from:
// http://www.quirksmode.org/blog/archives/2005/10/_and_the_winner_1.html
// http://dean.edwards.name/weblog/2005/10/add-event/
// http://dean.edwards.name/weblog/2005/10/add-event2/
// http://dean.edwards.name/weblog/2005/10/add-event2/#comment6264
// http://outofhanwell.wordpress.com/2006/07/03/cross-window-events/
// http://blog.metawrap.com/2005/10/24/ie-closures-leaks/
// http://www.quirksmode.org/js/events_properties.html

//This script uses a custom event attribute for the mouse button value: event.mouseButton
//This goes by the Microsoft model, where left==1, right==2, and middle==4
//In IE lte 8, the value may be incorrect on mousedown since we can't reliably keep track of IE's event.button value between events.
// (e.g., if the mouse leaves the window, buttons are changed by the user without the browser's detection, and the mouse comes back)
//
//Unlike in jQuery, mouseenter/leave events match IE in the order the events are fired. For example, instead of mouseenter being fired
// immediately after mouseover, this implementation waits for the mouseover event to bubble to the top before firing the mouseenter event.
// Also, mouseenter handlers are executed top-down (as if it were a capture phase).
//For IE 8 and older, I used the mouseenter/leave functionality that's built in since there were problems with stopPropagation()
// (specifically, evt._propagationStopped won't hold its value)
//
//Be aware that browsers act differently when an affected element is removed/added from the DOM or repositioned on the page. Some fire
// mouseover/out events when an element is added/removed/positioned under the mouse, some execute handlers for a bubbled event even when
// the element has already been removed from the DOM in a previous handler, etc.



//addEventHandler(obj, type, handler)
//returns the GUID of the handler
var addEventHandler = (function(){
	
	var newGUID = 1;	//GUID to assign to the next event handler function without one
	
	function addEvent(obj, type, handler)
	{
		type = type.toLowerCase();
		if(type.slice(0,2) == "on") type = type.slice(2);
		
		//make sure the object's window has the required event handlers
		var ownerWindow = getOwnerWindow(obj);
		if(!ownerWindow._eventHandlers) addWindowHandlers(ownerWindow);
		
		if(!obj._eventHandlers) obj._eventHandlers = [];
		
		//if not IE lte 8 and event type is mouseenter/leave, add global handler for the triggering event type instead of the mouseenter/leave event type
		if(document.addEventListener && (type == "mouseenter" || type == "mouseleave"))
		{
			if(!obj._eventHandlers[type]) obj._eventHandlers[type] = [];	//do not add global event handler for the mouseenter/leave event type
			
			addTypeHandler(obj, type=="mouseenter" ? "mouseover" : "mouseout");	//add global handler for the trigger event type instead
		}
		else
			addTypeHandler(obj, type);
		
		if(!handler._handlerGUID) handler._handlerGUID = newGUID++;	//assign a GUID to the handler if it doesn't have one
		var guid = handler._handlerGUID;
		
		if(!assigned(obj, type, guid))	//if this handler isn't already assigned to this event type and object
			obj._eventHandlers[type].push({ guid: guid, handler: handler });	//add the handler to the list
		
		return guid;
	}
	
	
	
	//get the window in which the DOM node resides; this is not necessarily the same window where a function is defined
	function getOwnerWindow(obj)
	{
		return (obj.ownerDocument || obj.document || obj).parentWindow || window;
		/*      obj==element         obj==window     obj==document */
	}
	
	
	
	function addWindowHandlers(ownerWindow)
	{
		if(!ownerWindow._eventHandlers) ownerWindow._eventHandlers = [];
		
		function emptyFunction(){}
		if(document.addEventListener)	//not IE lte 8
		{
			//add empty handlers to window so that mouseenter/leave handlers will be executed when the mouseover/out event stops bubbling
			addEvent(ownerWindow, "mouseover", emptyFunction);
			addEvent(ownerWindow, "mouseout", emptyFunction);
		}
		else	//IE lte 8
		{
			//add empty handlers to document to be sure mouse button variables are updated (these events don't bubble to window in IE lte 8)
			addEvent(ownerWindow.document, "mousedown", emptyFunction);
			addEvent(ownerWindow.document, "mouseup", emptyFunction);
		}
		
		
		//remove circular references and avoid memory leaks when the window unloads (especially for IE)
		function flushEventHandlers(obj)	//nulls event attributes and handler collection
		{
			if(obj._eventHandlers) obj._eventHandlers = null;
			for(var prop in obj)
			{
				if(prop.indexOf("on") == 0) obj[prop] = null;
			}
		}
		function flushAllHandlers()
		{
			var elems = ownerWindow.document.getElementsByTagName("*");
			for(var i=0; i<elems.length; i++){ flushEventHandlers(elems[i]); }
			flushEventHandlers(ownerWindow.document);
			flushEventHandlers(ownerWindow);
		}
		addEvent(ownerWindow, "unload", flushAllHandlers);
	}
	
	
	
	//add global handler for an event type for an object (if it doesn't already exist)
	function addTypeHandler(obj, type)
	{
		if(!obj._eventHandlers[type])
		{
			obj._eventHandlers[type] = [];
			
			if(obj.addEventListener) obj.addEventListener(type, patchHandler(obj, handleEvent), false);
			else if(obj.attachEvent) obj.attachEvent("on"+type, patchHandler(obj, handleEvent));
			else
			{
				if(obj["on"+type]) obj._eventHandlers[type][0] = { handler: obj["on"+type] };
				obj["on"+type] = patchHandler(obj, handleEvent);
			}
		}
	}
	
	//is the handler for this event type for this object already in the list?
	function assigned(obj, type, guid)
	{
		for(var i in obj._eventHandlers[type])
		{
			if(obj._eventHandlers[type][i].guid == guid) return true;	//handler is already in the list
		}
		return false;
	}
	
	//"patches" a handler function so that:
	//- the `this` keyword within the handler refers to the correct object
	//- the event object has oft-used W3C-standard properties/methods
	//- the event object is always passed as an argument to the handler
	//it also:
	//- adds custom attributes .window, .mousePosition, and .mouseButton to the event object
	//- modifies evt.stopPropagation() for use with mouseenter/leave events
	function patchHandler(obj, handler)
	{
		var undefined;
		
		return function(evt){
				//In IE lte 8, if this patched handler is assigned to an event attribute on a DOM node instead of using attachEvent(), 
				// we need to get the event object from the window the node resides in (which is not necessarily where the handler was defined).
				var evtWindow = getOwnerWindow(obj);
				evt = evt || evtWindow.event;
				
				//custom attribute for easy access to the window in which the event was fired
				evt.window = evtWindow;
				
				
				if(evt.target === undefined) evt.target = evt.srcElement;
				if(evt.relatedTarget === undefined) evt.relatedTarget = evt.target==evt.toElement ? evt.fromElement : evt.toElement;
				if(evt.currentTarget === undefined) evt.currentTarget = obj;	//usually the same as the `this` keyword inside the handler
				if(evt.eventPhase === undefined) evt.eventPhase = 3 - 1*(evt.target === obj);	//capturing==1 (n/a), at_target==2, bubbling==3
				
				evt.preventDefault = evt.preventDefault || patchHandler.preventDefault;
				evt.stopPropagation = patchHandler.stopPropagation;
				
				
				//add custom mouse position attributes
				evt.mousePosition = {};
				evt.mousePosition.screen = {x:evt.screenX, y:evt.screenY, left:evt.screenX, top:evt.screenY};
				evt.mousePosition.window = evt.mousePosition.frame = {x:evt.clientX, y:evt.clientY, left:evt.clientX, top:evt.clientY};
				evt.mousePosition.document = (function(){
						if(isNaN(evt.pageX) || isNaN(evt.pageY))
						{
							var left, top;	//scroll position of document
							if(window.pageYOffset)	//all except IE
							{ left = window.pageXOffset; top = window.pageYOffset; }
							else if(document.documentElement && !isNaN(document.documentElement.scrollTop))	//IE standards compliance mode
							{ left = document.documentElement.scrollLeft; top = document.documentElement.scrollTop; }
							else	//IE quirks mode
							{ left = document.body.scrollLeft; top = document.body.scrollTop; }
							return {x:left+evt.clientX, y:top+evt.clientY, left:left+evt.clientX, top:top+evt.clientY};
						}
						else return {x:evt.pageX, y:evt.pageY, left:evt.pageX, top:evt.pageY};
					})();
				evt.mousePosition.layer = {x:evt.layerX||evt.x, y:evt.layerY||evt.y, left:evt.layerX||evt.x, top:evt.layerY||evt.y};
				
				
				//add custom mouse button attribute
				//this is the button that was pressed to trigger this event: 1==left, 2==right, 4==middle
				if(evt.type == "mousedown" || evt.type == "mouseup" || evt.type == "click" || evt.type == "dblclick")
				{
					if(evt.which)	//not IE lte 8
						evt.mouseButton = evt.which == 1 ? 1 : evt.which == 2 ? 4 : 2;
					else	//IE lte 8
					{
						var mb = patchHandler.mouseButtons;
						if(obj == evt.target)	//update mb.button
						{
							if(evt.type == "mousedown")
							{
								mb.button = (evt.button ^ mb.pressed) & evt.button;
								if((mb.button & evt.button) == 0) mb.button = evt.button;
								mb.pressed = evt.button;
								
								//note: mb.button may be incorrect on mousedown since we can't reliably keep track of IE's event.button
								// value (i.e., which buttons are pressed when the event is fired) between events (e.g., if the mouse
								// leaves the window, buttons are changed by the user without the browser's detection, and the mouse comes back)
							}
							else if(evt.type == "mouseup")
							{
								mb.button = evt.button;
								mb.pressed = ~evt.button & mb.pressed;
							}
						}
						evt.mouseButton = mb.button;
					}
				}
				else evt.mouseButton = mb.button = 0;
				
				
				handler.call(obj, evt);	//corrects the `this` keyword and passes the patched event object
			};
	}
	patchHandler.preventDefault = function(){ this.returnValue = false; };
	patchHandler.stopPropagation = function(){
			if(!document.addEventListener)	//IE lte 8
				this.cancelBubble = true;
			else
			{
				this._propagationStopped = true;
				if(this.type != "mouseover" && this.type != "mouseout") this.stopPropagation();
			}
		};
	patchHandler.mouseButtons = { button: 0, pressed: 0 };	//for IE lte 8, keeps track of which mouse buttons are pressed
	
	
	
	//For browsers other than IE lte 8
	//stores the elements with mouseenter/leave handlers that need to be executed once the mouseover/out event finishes bubbling
	var mouseELQueue = [];
	var stopQueuingEL;	//flag to stop queuing mouseenter/leave handlers in handleEvent()
	
	//global handler that executes all handlers assigned to an object for the passed event
	function handleEvent(evt)
	{
		var returnValue = true;
		var handlers = this._eventHandlers[evt.type];
		
		//if propagation hasn't been stopped, execute the handlers
		if(!evt._propagationStopped)
		{
			//execute each event handler (in FIFO order)
			for(var i=0; i<handlers.length; i++)
			{
				//execute the handler and update the return value
				returnValue = returnValue && handlers[i].handler.call(this, evt) !== false;
			}
		}
		if(!returnValue) evt.preventDefault();
		
		
		
		//if not IE lte 8, and mouseenter/leave handlers may need to be executed
		if(document.addEventListener && (evt.type == "mouseover" || evt.type == "mouseout"))
		{
			if(this == evt.target) stopQueuingEL = false;
			
			//if this element has mouseenter/leave handlers, queue it to have the handlers executed after the mouseover/out event has completed
			
			//test whether the element is still in the DOM or if it has been removed by a previous handler
			function inDOM(elem)
			{
				if(!elem) return false;
				if(elem == evt.window.document.documentElement) return true;
				var tmp = elem;
				do
				{
					if(!tmp.parentNode) return false;	//element is not in the DOM
					tmp = tmp.parentNode;
				}while(tmp != evt.window.document.documentElement)
				return true;
			}
			
			function queueEL(type, elem, related)
			{
				if(stopQueuingEL) return;
				
				if((type == "mouseenter" && !inDOM(elem)) || (type == "mouseleave" && !inDOM(related)))
				{
					stopQueuingEL = true;
					return;
				}
				
				do
				{
					if(related == elem)	//didn't enter/leave the element, only a child element
					{
						stopQueuingEL = true;
						return;
					}
					related = related.parentNode;
				}while(related)
				
				var evtCopy = {};
				for(i in evt) evtCopy[i] = evt[i];
				evtCopy.type = type;
				//save pointer to the element and a copy of the event object to do the mouseenter/leave handlers later
				mouseELQueue.push([elem, evtCopy]);
			}
			if(evt.type == "mouseover" && this._eventHandlers["mouseenter"])
				queueEL("mouseenter", this, (evt.relatedTarget || this));
				//when the page loads, if the mouse is already inside the element and is then moved, the mouseover event fires. There is no source
				// element in that case so the related target is itself
			else if(evt.type == "mouseout" && this._eventHandlers["mouseleave"])
				queueEL("mouseleave", this, evt.relatedTarget);
			
			//if mouseover/out event has stopped bubbling, "fire" mouseenter/leave event
			if(this == evt.window && mouseELQueue.length > 0)
			{
				var q, elem, evtCopy;
				
				//handle queued events
				while(mouseELQueue.length > 0)	//for each queued element
				{
					q = evt.type=="mouseover" ? mouseELQueue.pop() : mouseELQueue.shift();	//mouseenter events are handled in reverse order
					elem = q[0];
					evtCopy = q[1];
					
					if(inDOM(elem) || elem == evt.window.document || elem == evt.window)
					{
						handlers = elem._eventHandlers[evt.type];
						for(var i=0; i<handlers.length; i++)	//execute the element's mouseenter/leave handlers
						{
							//execute the handler and update the return value
							returnValue = returnValue && handlers[i].handler.call(elem, evtCopy) !== false;
						}
					}
				}
			}
			
		}
		
		return returnValue;
	}
	
	return addEvent;
})();

function removeEventHandler(obj, type, handler_or_guid)
{
	type = type.toLowerCase();
	if(type.slice(0,2) == "on") type = type.slice(2);
	
	var guid;
	if(!isNaN(1*handler_or_guid)) guid = handler_or_guid;	//GUID was passed
	else guid = handler_or_guid._handlerGUID;	//handler function was passed
	
	if(guid && obj._eventHandlers && obj._eventHandlers[type])
	{
		var handlers = obj._eventHandlers[type];
		for(var i in handlers)
		{
			if(handlers[i].guid == guid)	//handler is in the list
			{
				handlers.splice(i, 1);	//remove the handler from the list
				break;
			}
		}
	}
}

Revision: 44555
at September 17, 2011 06:17 by wizard04


Updated Code
/*****************************************
 * Enhanced cross-browser event handling v5.7
 * 
 * This work is licensed under a Creative Commons Attribution 3.0 Unported License
 * http://creativecommons.org/licenses/by/3.0/
 *
 * Author: Andy Harrison, http://dragonzreef.com/
 * Date: 16 September 2011
 *****************************************/

//Cross-browser event registration functions
//Handlers execute in FIFO order
//Supports mouseenter and mouseleave events
//Custom event attributes:
//	event.window: the window that the target resides in
//	event.mouseButton: the mouse button value (1, 2, or 4) for mousedown, mouseup, click, and dblclick events

//Techniques and inspiration mainly from:
// http://www.quirksmode.org/blog/archives/2005/10/_and_the_winner_1.html
// http://dean.edwards.name/weblog/2005/10/add-event/
// http://dean.edwards.name/weblog/2005/10/add-event2/
// http://dean.edwards.name/weblog/2005/10/add-event2/#comment6264
// http://outofhanwell.wordpress.com/2006/07/03/cross-window-events/
// http://blog.metawrap.com/2005/10/24/ie-closures-leaks/
// http://www.quirksmode.org/js/events_properties.html

//This script uses a custom event attribute for the mouse button value: event.mouseButton
//This goes by the Microsoft model, where left==1, right==2, and middle==4
//In IE lte 8, the value may be incorrect on mousedown since we can't reliably keep track of the event.button value between events.
// (e.g., if the mouse leaves the window, buttons are changed by the user without the browser's detection, and the mouse comes back)
//
//Unlike in jQuery, mouseenter/leave events match IE in the order the events are fired. For example, instead of mouseenter being fired
// immediately after mouseover, this implementation waits for the mouseover event to bubble to the top before firing the mouseenter event.
// Also, mouseenter handlers are executed top-down (as if it were a capture phase).
//For IE 8 and older, I used the mouseenter/leave functionality that's built in since there were problems with stopPropagation()
// (specifically, evt._propagationStopped won't hold its value)
//
//Be aware that browsers act differently when an affected element is removed/added from the DOM or repositioned on the page. Some fire
// mouseover/out events when an element is added/removed/positioned under the mouse, some execute handlers for a bubbled event even when
// the element has already been removed from the DOM in a previous handler, etc.

//addEventHandler(obj, type, handler)
//`type` argument should not include "on"
//returns the GUID of the handler
var addEventHandler = (function(){
	
	var newGUID = 1;	//GUID to assign to the next event handler function without one
	
	//For browsers other than IE lte 8
	//stores the elements with mouseenter/leave handlers that need to be executed once the mouseover/out event finishes bubbling
	var mouseELQueue = [];
	var stopQueuingEL;	//flag to stop queuing mouseenter/leave handlers in handleEvent()
	
	//For IE lte 8
	var mouseButton = 0, pressedMouseButtons = 0, mouseButtonsUpdated;	//keeps track of which mouse buttons are pressed
	
	function emptyFunction(){}
	function addWindowHandlers(ownerWindow)
	{
		if(!ownerWindow._eventHandlers) ownerWindow._eventHandlers = [];
		
		if(document.addEventListener)	//not IE lte 8
		{
			//add empty handlers to window so that mouseenter/leave handlers will be executed when the mouseover/out event stops bubbling
			addEvent(ownerWindow, "mouseover", emptyFunction);
			addEvent(ownerWindow, "mouseout", emptyFunction);
		}
		else	//IE lte 8
		{
			//add empty handlers to document to be sure mouse button variables are updated (these events don't bubble to window in IE lte 8)
			addEvent(ownerWindow.document, "mousedown", emptyFunction);
			addEvent(ownerWindow.document, "mouseup", emptyFunction);
		}
		
		//remove circular references and avoid memory leaks when the window unloads (especially for IE)
		function flushElementHandlers(obj)	//nulls event attributes and ._eventHandlers
		{
			for(var prop in obj)
			{
				if(prop.indexOf("on") == 0 && obj[prop]) obj[prop] = null;
			}
			if(obj._eventHandlers) obj._eventHandlers = null;
		}
		function flushAllHandlers()
		{
			var elems = ownerWindow.document.getElementsByTagName("*");
			for(var i=0; i<elems.length; i++){ flushElementHandlers(elems[i]); }
			flushElementHandlers(ownerWindow.document);
			flushElementHandlers(ownerWindow);
		}
		addEvent(ownerWindow, "unload", flushAllHandlers);
	}
	
	function addEvent(obj, type, handler)
	{
		//make sure the object's window has the required event handlers
		var ownerWindow = (obj.ownerDocument || obj.document || obj).parentWindow || window;
		/*                 obj==element         obj==window     obj==document */
		if(!ownerWindow._eventHandlers) addWindowHandlers(ownerWindow);
		
		if(!obj._eventHandlers) obj._eventHandlers = [];
		
		//if not IE lte 8 and event type is mouseenter/leave, add global handler for the triggering event type instead of the mouseenter/leave event type
		if(document.addEventListener && (type == "mouseenter" || type == "mouseleave"))
		{
			if(!obj._eventHandlers[type]) obj._eventHandlers[type] = [];	//do not add global event handler for the mouseenter/leave event type
			
			addTypeHandler(obj, type=="mouseenter" ? "mouseover" : "mouseout");	//add global handler for the trigger event type instead
		}
		else
			addTypeHandler(obj, type);
		
		if(!handler._handlerGUID) handler._handlerGUID = newGUID++;	//assign a GUID to the handler if it doesn't have one
		var guid = handler._handlerGUID;
		
		if(!assigned(obj, type, guid))	//if this handler isn't already assigned to this event type and object
			obj._eventHandlers[type].push({ guid: guid, handler: handler });	//add the handler to the list
		
		return guid;
	}
	
	//add global handler for an event type for an object (if it doesn't already exist)
	function addTypeHandler(obj, type)
	{
		if(!obj._eventHandlers[type])
		{
			obj._eventHandlers[type] = [];
			
			if(obj.addEventListener) obj.addEventListener(type, patchHandler(obj, handleEvent), false);
			else if(obj.attachEvent) obj.attachEvent("on"+type, patchHandler(obj, handleEvent));
			else
			{
				if(obj["on"+type]) obj._eventHandlers[type][0] = { handler: obj["on"+type] };
				obj["on"+type] = patchHandler(obj, handleEvent);
			}
		}
	}
	
	//is the handler for this event type for this object already in the list?
	function assigned(obj, type, guid)
	{
		for(var i in obj._eventHandlers[type])
		{
			if(obj._eventHandlers[type][i].guid == guid) return true;	//handler is already in the list
		}
		return false;
	}
	
	//"patches" a handler function so that:
	//- the `this` keyword within the handler refers to the correct object
	//- the event object has oft-used W3C-standard properties/methods
	//- the event object is always passed as an argument to the handler
	//also modifies evt.stopPropagation() for use with mouseenter/leave events
	function patchHandler(obj, handler)
	{
		var undefined;
		
		return function(evt){
				/*In IE lte 8, if this patched handler is assigned to an event attribute on a DOM node instead of using attachEvent(), 
				  we need to get the event object from window.event, which is in the window the node resides in. 
				  This looks a bit complicated because `window` inside the handler will refer to the 
				  window that the handler was defined in, not where the DOM node resides.*/
				evt.window = (obj.ownerDocument || obj.document || obj).parentWindow || window;
				/*            obj==element         obj==window     obj==document */
				evt = evt || evt.window.event;
				
				if(evt.target === undefined) evt.target = evt.srcElement;
				if(evt.relatedTarget === undefined) evt.relatedTarget = evt.target==evt.toElement ? evt.fromElement : evt.toElement;
				if(evt.currentTarget === undefined) evt.currentTarget = obj;	//usually the same as the `this` keyword inside the handler
				if(evt.eventPhase === undefined) evt.eventPhase = 3 - 1*(evt.target === obj);	//capturing==1 (n/a), at_target==2, bubbling==3
				
				evt.preventDefault = evt.preventDefault || patchHandler.preventDefault;
				evt.stopPropagation = patchHandler.stopPropagation;
				
				handler.apply(obj, arguments);	//corrects the `this` keyword and passes the patched event object
			};
	}
	patchHandler.preventDefault = function(){ this.returnValue = false; };
	patchHandler.stopPropagation = function(){
			if(!document.addEventListener)	//IE lte 8
				this.cancelBubble = true;
			else
			{
				this._propagationStopped = true;
				if(this.type != "mouseover" && this.type != "mouseout") this.stopPropagation();
			}
		};
	
	function handleEvent(evt)
	{
		if(!this._eventHandlers) return;
		
		
		
		//set evt.mouseButton
		if(evt.type == "mousedown" || evt.type == "mouseup" || evt.type == "click" || evt.type == "dblclick")
		{
			//set custom evt.mouseButton attribute: 1==left, 2==right, 4==middle
			//this is the button that was pressed to trigger this event
			
			if(evt.which)
				evt.mouseButton = evt.which == 1 ? 1 /*left*/ : evt.which == 2 ? 4 /*middle*/ : 2 /*right*/;
			else	//IE lte 8
			{
				if(!mouseButtonsUpdated)
				{
					if(evt.type == "mousedown")
					{
						mouseButton = (evt.button ^ pressedMouseButtons) & evt.button;
						if(mouseButton & evt.button == 0) mouseButton = evt.button;
						pressedMouseButtons = evt.button;
					}
					else if(evt.type == "mouseup")
					{
						mouseButton = evt.button;
						pressedMouseButtons = ~evt.button & pressedMouseButtons;
					}
					mouseButtonsUpdated = true;
				}
				evt.mouseButton = mouseButton;
				if(this == evt.window.document) mouseButtonsUpdated = false;	//note: mouse button events do not bubble to window in IE lte 8
			}
		}
		else evt.mouseButton = mouseButton = 0;
		
		
		
		var returnValue = true;
		var handlers = this._eventHandlers[evt.type];
		
		//if propagation hasn't been stopped, execute the handlers
		if(!evt._propagationStopped)
		{
			//execute each event handler (in FIFO order)
			for(var i=0; i<handlers.length; i++)
			{
				//execute the handler and update the return value
				returnValue = returnValue && handlers[i].handler.apply(this, arguments) !== false;
			}
		}
		if(!returnValue) evt.preventDefault();
		
		
		
		//if not IE lte 8, and mouseenter/leave handlers may need to be executed
		if(document.addEventListener && (evt.type == "mouseover" || evt.type == "mouseout"))
		{
			if(this == evt.target) stopQueuingEL = false;
			
			//if this element has mouseenter/leave handlers, queue it to have the handlers executed after the mouseover/out event has completed
			
			//test whether the element is still in the DOM or if it has been removed by a previous handler
			function inDOM(elem)
			{
				if(!elem) return false;
				if(elem == evt.window.document.documentElement) return true;
				var tmp = elem;
				do
				{
					if(!tmp.parentNode) return false;	//element is not in the DOM
					tmp = tmp.parentNode;
				}while(tmp != evt.window.document.documentElement)
				return true;
			}
			
			function queueEL(type, elem, related)
			{
				if(stopQueuingEL) return;
				
				if((type == "mouseenter" && !inDOM(elem)) || (type == "mouseleave" && !inDOM(related)))
				{
					stopQueuingEL = true;
					return;
				}
				
				do
				{
					if(related == elem)	//didn't enter/leave the element, only a child element
					{
						stopQueuingEL = true;
						return;
					}
					related = related.parentNode;
				}while(related)
				
				var evtCopy = {};
				for(i in evt) evtCopy[i] = evt[i];
				evtCopy.type = type;
				//save pointer to the element and a copy of the event object to do the mouseenter/leave handlers later
				mouseELQueue.push([elem, evtCopy]);
			}
			if(evt.type == "mouseover" && this._eventHandlers["mouseenter"])
				queueEL("mouseenter", this, (evt.relatedTarget || this));
				//when the page loads, if the mouse is already inside the element and is then moved, the mouseover event fires. There is no source
				// element in that case so the related target is itself
			else if(evt.type == "mouseout" && this._eventHandlers["mouseleave"])
				queueEL("mouseleave", this, evt.relatedTarget);
			
			//if mouseover/out event has stopped bubbling, "fire" mouseenter/leave event
			if(this == evt.window && mouseELQueue.length > 0)
			{
				var q, elem, evtCopy;
				
				//handle queued events
				while(mouseELQueue.length > 0)
				{
					q = evt.type=="mouseover" ? mouseELQueue.pop() : mouseELQueue.shift();	//mouseenter events are handled in reverse order
					elem = q[0];
					evtCopy = q[1];
					
					if(inDOM(elem) || elem == evt.window.document || elem == evt.window)
					{
						handlers = elem._eventHandlers[evt.type];
						for(var i=0; i<handlers.length; i++)
						{
							//execute the handler and update the return value
							returnValue = returnValue && handlers[i].handler.call(elem, evtCopy) !== false;
						}
					}
				}
			}
			
		}
		
		return returnValue;
	}
	
	return addEvent;
})();

//`type` argument should not include "on"
function removeEventHandler(obj, type, handler_or_guid)
{
	var guid;
	if(!isNaN(1*handler_or_guid)) guid = handler_or_guid;	//GUID was passed
	else guid = handler_or_guid._handlerGUID;	//handler function was passed
	
	if(guid && obj._eventHandlers && obj._eventHandlers[type])
	{
		var handlers = obj._eventHandlers[type];
		for(var i in handlers)
		{
			if(handlers[i].guid == guid)	//handler is in the list
			{
				handlers.splice(i, 1);	//remove the handler from the list
				break;
			}
		}
	}
}

Revision: 44554
at September 17, 2011 04:27 by wizard04


Updated Code
/* Enhanced cross-browser event handling v5.7
 * 
 * This work is licensed under a Creative Commons Attribution 3.0 Unported License
 * http://creativecommons.org/licenses/by/3.0/
 *
 * Author: Andy Harrison, http://dragonzreef.com/
 * Date: 16 September 2011
 */

//Cross-browser event registration functions
//Handlers execute in FIFO order
//Supports mouseenter and mouseleave events
//Custom event attributes:
//	event.window: the window that the target resides in
//	event.mouseButton: the mouse button value (1, 2, or 4) for mousedown, mouseup, click, and dblclick events

//Techniques and inspiration mainly from:
// http://www.quirksmode.org/blog/archives/2005/10/_and_the_winner_1.html
// http://dean.edwards.name/weblog/2005/10/add-event/
// http://dean.edwards.name/weblog/2005/10/add-event2/
// http://dean.edwards.name/weblog/2005/10/add-event2/#comment6264
// http://outofhanwell.wordpress.com/2006/07/03/cross-window-events/
// http://blog.metawrap.com/2005/10/24/ie-closures-leaks/
// http://www.quirksmode.org/js/events_properties.html

//This script uses a custom event attribute for the mouse button value: event.mouseButton
//This goes by the Microsoft model, where left==1, right==2, and middle==4
//In IE lte 8, the value may be incorrect on mousedown since we can't reliably keep track of the event.button value between events.
// (e.g., if the mouse leaves the window, buttons are changed by the user without the browser's detection, and the mouse comes back)
//
//Unlike in jQuery, mouseenter/leave events match IE in the order the events are fired. For example, instead of mouseenter being fired
// immediately after mouseover, this implementation waits for the mouseover event to bubble to the top before firing the mouseenter event.
// Also, mouseenter handlers are executed top-down (as if it were a capture phase).
//For IE 8 and older, I used the mouseenter/leave functionality that's built in since there were problems with stopPropagation()
// (specifically, evt._propagationStopped won't hold its value)
//
//Be aware that browsers act differently when an affected element is removed/added from the DOM or repositioned on the page. Some fire
// mouseover/out events when an element is added/removed/positioned under the mouse, some execute handlers for a bubbled event even when
// the element has already been removed from the DOM in a previous handler, etc.

//addEventHandler(obj, type, handler)
//`type` argument should not include "on"
//returns the GUID of the handler
var addEventHandler = (function(){
	
	var newGUID = 1;	//GUID to assign to the next event handler function without one
	
	//For browsers other than IE lte 8
	//stores the elements with mouseenter/leave handlers that need to be executed once the mouseover/out event finishes bubbling
	var mouseELQueue = [];
	var stopQueuingEL;	//flag to stop queuing mouseenter/leave handlers in handleEvent()
	
	//For IE lte 8
	var mouseButton = 0, pressedMouseButtons = 0, mouseButtonsUpdated;	//keeps track of which mouse buttons are pressed
	
	function emptyFunction(){}
	function addWindowHandlers(ownerWindow)
	{
		if(!ownerWindow._eventHandlers) ownerWindow._eventHandlers = [];
		
		if(document.addEventListener)	//not IE lte 8
		{
			//add empty handlers to window so that mouseenter/leave handlers will be executed when the mouseover/out event stops bubbling
			addEvent(ownerWindow, "mouseover", emptyFunction);
			addEvent(ownerWindow, "mouseout", emptyFunction);
		}
		else	//IE lte 8
		{
			//add empty handlers to document to be sure mouse button variables are updated (these events don't bubble to window in IE lte 8)
			addEvent(ownerWindow.document, "mousedown", emptyFunction);
			addEvent(ownerWindow.document, "mouseup", emptyFunction);
		}
		
		//remove circular references and avoid memory leaks when the window unloads (especially for IE)
		function flushElementHandlers(obj)	//nulls event attributes and ._eventHandlers
		{
			for(var prop in obj)
			{
				if(prop.indexOf("on") == 0 && obj[prop]) obj[prop] = null;
			}
			if(obj._eventHandlers) obj._eventHandlers = null;
		}
		function flushAllHandlers()
		{
			var elems = ownerWindow.document.getElementsByTagName("*");
			for(var i=0; i<elems.length; i++){ flushElementHandlers(elems[i]); }
			flushElementHandlers(ownerWindow.document);
			flushElementHandlers(ownerWindow);
		}
		addEvent(ownerWindow, "unload", flushAllHandlers);
	}
	
	function addEvent(obj, type, handler)
	{
		//make sure the object's window has the required event handlers
		var ownerWindow = (obj.ownerDocument || obj.document || obj).parentWindow || window;
		/*                 obj==element         obj==window     obj==document */
		if(!ownerWindow._eventHandlers) addWindowHandlers(ownerWindow);
		
		if(!obj._eventHandlers) obj._eventHandlers = [];
		
		//if not IE lte 8 and event type is mouseenter/leave, add global handler for the triggering event type instead of the mouseenter/leave event type
		if(document.addEventListener && (type == "mouseenter" || type == "mouseleave"))
		{
			if(!obj._eventHandlers[type]) obj._eventHandlers[type] = [];	//do not add global event handler for the mouseenter/leave event type
			
			addTypeHandler(obj, type=="mouseenter" ? "mouseover" : "mouseout");	//add global handler for the trigger event type instead
		}
		else
			addTypeHandler(obj, type);
		
		if(!handler._handlerGUID) handler._handlerGUID = newGUID++;	//assign a GUID to the handler if it doesn't have one
		var guid = handler._handlerGUID;
		
		if(!assigned(obj, type, guid))	//if this handler isn't already assigned to this event type and object
			obj._eventHandlers[type].push({ guid: guid, handler: handler });	//add the handler to the list
		
		return guid;
	}
	
	//add global handler for an event type for an object (if it doesn't already exist)
	function addTypeHandler(obj, type)
	{
		if(!obj._eventHandlers[type])
		{
			obj._eventHandlers[type] = [];
			
			if(obj.addEventListener) obj.addEventListener(type, patchHandler(obj, handleEvent), false);
			else if(obj.attachEvent) obj.attachEvent("on"+type, patchHandler(obj, handleEvent));
			else
			{
				if(obj["on"+type]) obj._eventHandlers[type][0] = { handler: obj["on"+type] };
				obj["on"+type] = patchHandler(obj, handleEvent);
			}
		}
	}
	
	//is the handler for this event type for this object already in the list?
	function assigned(obj, type, guid)
	{
		for(var i in obj._eventHandlers[type])
		{
			if(obj._eventHandlers[type][i].guid == guid) return true;	//handler is already in the list
		}
		return false;
	}
	
	//"patches" a handler function so that:
	//- the `this` keyword within the handler refers to the correct object
	//- the event object has oft-used W3C-standard properties/methods
	//- the event object is always passed as an argument to the handler
	//also modifies evt.stopPropagation() for use with mouseenter/leave events
	function patchHandler(obj, handler)
	{
		var undefined;
		
		return function(evt){
				/*In IE lte 8, if this patched handler is assigned to an event attribute on a DOM node instead of using attachEvent(), 
				  we need to get the event object from window.event, which is in the window the node resides in. 
				  This looks a bit complicated because `window` inside the handler will refer to the 
				  window that the handler was defined in, not where the DOM node resides.*/
				evt.window = (obj.ownerDocument || obj.document || obj).parentWindow || window;
				/*            obj==element         obj==window     obj==document */
				evt = evt || evt.window.event;
				
				if(evt.target === undefined) evt.target = evt.srcElement;
				if(evt.relatedTarget === undefined) evt.relatedTarget = evt.target==evt.toElement ? evt.fromElement : evt.toElement;
				if(evt.currentTarget === undefined) evt.currentTarget = obj;	//usually the same as the `this` keyword inside the handler
				if(evt.eventPhase === undefined) evt.eventPhase = 3 - 1*(evt.target === obj);	//capturing==1 (n/a), at_target==2, bubbling==3
				
				evt.preventDefault = evt.preventDefault || patchHandler.preventDefault;
				evt.stopPropagation = patchHandler.stopPropagation;
				
				handler.apply(obj, arguments);	//corrects the `this` keyword and passes the patched event object
			};
	}
	patchHandler.preventDefault = function(){ this.returnValue = false; };
	patchHandler.stopPropagation = function(){
			if(!document.addEventListener)	//IE lte 8
				this.cancelBubble = true;
			else
			{
				this._propagationStopped = true;
				if(this.type != "mouseover" && this.type != "mouseout") this.stopPropagation();
			}
		};
	
	function handleEvent(evt)
	{
		if(!this._eventHandlers) return;
		
		
		
		//set evt.mouseButton
		if(evt.type == "mousedown" || evt.type == "mouseup" || evt.type == "click" || evt.type == "dblclick")
		{
			//set custom evt.mouseButton attribute: 1==left, 2==right, 4==middle
			//this is the button that was pressed to trigger this event
			
			if(evt.which)
				evt.mouseButton = evt.which == 1 ? 1 /*left*/ : evt.which == 2 ? 4 /*middle*/ : 2 /*right*/;
			else	//IE lte 8
			{
				if(!mouseButtonsUpdated)
				{
					if(evt.type == "mousedown")
					{
						mouseButton = (evt.button ^ pressedMouseButtons) & evt.button;
						if(mouseButton & evt.button == 0) mouseButton = mouseButton | evt.button;
						pressedMouseButtons = evt.button;
					}
					else if(evt.type == "mouseup")
					{
						mouseButton = evt.button;
						pressedMouseButtons = ~evt.button & pressedMouseButtons;
					}
					mouseButtonsUpdated = true;
				}
				evt.mouseButton = mouseButton;
				if(this == evt.window.document) mouseButtonsUpdated = false;	//note: mouse button events do not bubble to window in IE lte 8
			}
		}
		else evt.mouseButton = mouseButton = 0;
		
		
		
		var returnValue = true;
		var handlers = this._eventHandlers[evt.type];
		
		//if propagation hasn't been stopped, execute the handlers
		if(!evt._propagationStopped)
		{
			//execute each event handler (in FIFO order)
			for(var i=0; i<handlers.length; i++)
			{
				//execute the handler and update the return value
				returnValue = returnValue && handlers[i].handler.apply(this, arguments) !== false;
			}
		}
		if(!returnValue) evt.preventDefault();
		
		
		
		//if not IE lte 8, and mouseenter/leave handlers may need to be executed
		if(document.addEventListener && (evt.type == "mouseover" || evt.type == "mouseout"))
		{
			if(this == evt.target) stopQueuingEL = false;
			
			//if this element has mouseenter/leave handlers, queue it to have the handlers executed after the mouseover/out event has completed
			
			//test whether the element is still in the DOM or if it has been removed by a previous handler
			function inDOM(elem)
			{
				if(!elem) return false;
				if(elem == evt.window.document.documentElement) return true;
				var tmp = elem;
				do
				{
					if(!tmp.parentNode) return false;	//element is not in the DOM
					tmp = tmp.parentNode;
				}while(tmp != evt.window.document.documentElement)
				return true;
			}
			
			function queueEL(type, elem, related)
			{
				if(stopQueuingEL) return;
				
				if((type == "mouseenter" && !inDOM(elem)) || (type == "mouseleave" && !inDOM(related)))
				{
					stopQueuingEL = true;
					return;
				}
				
				do
				{
					if(related == elem)	//didn't enter/leave the element, only a child element
					{
						stopQueuingEL = true;
						return;
					}
					related = related.parentNode;
				}while(related)
				
				var evtCopy = {};
				for(i in evt) evtCopy[i] = evt[i];
				evtCopy.type = type;
				//save pointer to the element and a copy of the event object to do the mouseenter/leave handlers later
				mouseELQueue.push([elem, evtCopy]);
			}
			if(evt.type == "mouseover" && this._eventHandlers["mouseenter"])
				queueEL("mouseenter", this, (evt.relatedTarget || this));
				//when the page loads, if the mouse is already inside the element and is then moved, the mouseover event fires. There is no source
				// element in that case so the related target is itself
			else if(evt.type == "mouseout" && this._eventHandlers["mouseleave"])
				queueEL("mouseleave", this, evt.relatedTarget);
			
			//if mouseover/out event has stopped bubbling, "fire" mouseenter/leave event
			if(this == evt.window && mouseELQueue.length > 0)
			{
				var q, elem, evtCopy;
				
				//handle queued events
				while(mouseELQueue.length > 0)
				{
					q = evt.type=="mouseover" ? mouseELQueue.pop() : mouseELQueue.shift();	//mouseenter events are handled in reverse order
					elem = q[0];
					evtCopy = q[1];
					
					if(inDOM(elem) || elem == evt.window.document || elem == evt.window)
					{
						handlers = elem._eventHandlers[evt.type];
						for(var i=0; i<handlers.length; i++)
						{
							//execute the handler and update the return value
							returnValue = returnValue && handlers[i].handler.call(elem, evtCopy) !== false;
						}
					}
				}
			}
			
		}
		
		return returnValue;
	}
	
	return addEvent;
})();

//`type` argument should not include "on"
function removeEventHandler(obj, type, handler_or_guid)
{
	var guid;
	if(!isNaN(1*handler_or_guid)) guid = handler_or_guid;	//GUID was passed
	else guid = handler_or_guid._handlerGUID;	//handler function was passed
	
	if(guid && obj._eventHandlers && obj._eventHandlers[type])
	{
		var handlers = obj._eventHandlers[type];
		for(var i in handlers)
		{
			if(handlers[i].guid == guid)	//handler is in the list
			{
				handlers.splice(i, 1);	//remove the handler from the list
				break;
			}
		}
	}
}

Revision: 44553
at September 17, 2011 04:20 by wizard04


Updated Code
/* Enhanced cross-browser event handling v5.7
 * 
 * This work is licensed under a Creative Commons Attribution 3.0 Unported License
 * http://creativecommons.org/licenses/by/3.0/
 *
 * Author: Andy Harrison, http://dragonzreef.com/
 * Date: 16 September 2011
 */

//Cross-browser event registration functions
//Handlers execute in FIFO order
//Supports mouseenter and mouseleave events
//Custom event attributes:
//	event.window: the window that the target resides in
//	event.mouseButton: the mouse button value (1, 2, or 4) for mousedown, mouseup, click, and dblclick events

//Techniques and inspiration mainly from:
// http://www.quirksmode.org/blog/archives/2005/10/_and_the_winner_1.html
// http://dean.edwards.name/weblog/2005/10/add-event/
// http://dean.edwards.name/weblog/2005/10/add-event2/
// http://dean.edwards.name/weblog/2005/10/add-event2/#comment6264
// http://outofhanwell.wordpress.com/2006/07/03/cross-window-events/
// http://blog.metawrap.com/2005/10/24/ie-closures-leaks/
// http://www.quirksmode.org/js/events_properties.html

//This script uses a custom event attribute for the mouse button value: event.mouseButton
//This goes by the Microsoft model, where left==1, right==2, and middle==4
//In IE lte 8, the value may be incorrect on mousedown since we can't reliably keep track of the event.button value between events.
// (e.g., if the mouse leaves the window, buttons are changed by the user without the browser's detection, and the mouse comes back)
//
//Unlike in jQuery, mouseenter/leave events match IE in the order the events are fired. For example, instead of mouseenter being fired
// immediately after mouseover, this implementation waits for the mouseover event to bubble to the top before firing the mouseenter event.
// Also, mouseenter handlers are executed top-down (as if it were a capture phase).
//For IE 8 and older, I used the mouseenter/leave functionality that's built in since there were problems with stopPropagation()
// (specifically, evt._propagationStopped won't hold its value)
//
//Be aware that browsers act differently when an affected element is removed/added from the DOM or repositioned on the page. Some fire
// mouseover/out events when an element is added/removed/positioned under the mouse, some execute handlers for a bubbled event even when
// the element has already been removed from the DOM in a previous handler, etc.

var addEventHandler = (function(){
	
	var newGUID = 1;	//GUID to assign to the next event handler function without one
	
	//For browsers other than IE lte 8
	//stores the elements with mouseenter/leave handlers that need to be executed once the mouseover/out event finishes bubbling
	var mouseELQueue = [];
	var stopQueuingEL;	//flag to stop queuing mouseenter/leave handlers in handleEvent()
	
	//For IE lte 8
	var mouseButton = 0, pressedMouseButtons = 0, mouseButtonsUpdated;	//keeps track of which mouse buttons are pressed
	
	function emptyFunction(){}
	function addWindowHandlers(ownerWindow)
	{
		if(!ownerWindow._eventHandlers) ownerWindow._eventHandlers = [];
		
		if(document.addEventListener)	//not IE lte 8
		{
			//add empty handlers to window so that mouseenter/leave handlers will be executed when the mouseover/out event stops bubbling
			addEvent(ownerWindow, "mouseover", emptyFunction);
			addEvent(ownerWindow, "mouseout", emptyFunction);
		}
		else	//IE lte 8
		{
			//add empty handlers to document to be sure mouse button variables are updated (these events don't bubble to window in IE lte 8)
			addEvent(ownerWindow.document, "mousedown", emptyFunction);
			addEvent(ownerWindow.document, "mouseup", emptyFunction);
		}
		
		//remove circular references and avoid memory leaks when the window unloads (especially for IE)
		function flushElementHandlers(obj)	//nulls event attributes and ._eventHandlers
		{
			for(var prop in obj)
			{
				if(prop.indexOf("on") == 0 && obj[prop]) obj[prop] = null;
			}
			if(obj._eventHandlers) obj._eventHandlers = null;
		}
		function flushAllHandlers()
		{
			var elems = ownerWindow.document.getElementsByTagName("*");
			for(var i=0; i<elems.length; i++){ flushElementHandlers(elems[i]); }
			flushElementHandlers(ownerWindow.document);
			flushElementHandlers(ownerWindow);
		}
		addEvent(ownerWindow, "unload", flushAllHandlers);
	}
	
	function addEvent(obj, type, handler)
	{
		//make sure the object's window has the required event handlers
		var ownerWindow = (obj.ownerDocument || obj.document || obj).parentWindow || window;
		/*                 obj==element         obj==window     obj==document */
		if(!ownerWindow._eventHandlers) addWindowHandlers(ownerWindow);
		
		if(!obj._eventHandlers) obj._eventHandlers = [];
		
		//if not IE lte 8 and event type is mouseenter/leave, add global handler for the triggering event type instead of the mouseenter/leave event type
		if(document.addEventListener && (type == "mouseenter" || type == "mouseleave"))
		{
			if(!obj._eventHandlers[type]) obj._eventHandlers[type] = [];	//do not add global event handler for the mouseenter/leave event type
			
			addTypeHandler(obj, type=="mouseenter" ? "mouseover" : "mouseout");	//add global handler for the trigger event type instead
		}
		else
			addTypeHandler(obj, type);
		
		if(!handler._handlerGUID) handler._handlerGUID = newGUID++;	//assign a GUID to the handler if it doesn't have one
		var guid = handler._handlerGUID;
		
		if(!assigned(obj, type, guid))	//if this handler isn't already assigned to this event type and object
			obj._eventHandlers[type].push({ guid: guid, handler: handler });	//add the handler to the list
		
		return guid;
	}
	
	//add global handler for an event type for an object (if it doesn't already exist)
	function addTypeHandler(obj, type)
	{
		if(!obj._eventHandlers[type])
		{
			obj._eventHandlers[type] = [];
			
			if(obj.addEventListener) obj.addEventListener(type, patchHandler(obj, handleEvent), false);
			else if(obj.attachEvent) obj.attachEvent("on"+type, patchHandler(obj, handleEvent));
			else
			{
				if(obj["on"+type]) obj._eventHandlers[type][0] = { handler: obj["on"+type] };
				obj["on"+type] = patchHandler(obj, handleEvent);
			}
		}
	}
	
	//is the handler for this event type for this object already in the list?
	function assigned(obj, type, guid)
	{
		for(var i in obj._eventHandlers[type])
		{
			if(obj._eventHandlers[type][i].guid == guid) return true;	//handler is already in the list
		}
		return false;
	}
	
	//"patches" a handler function so that:
	//- the `this` keyword within the handler refers to the correct object
	//- the event object has oft-used W3C-standard properties/methods
	//- the event object is always passed as an argument to the handler
	//also modifies evt.stopPropagation() for use with mouseenter/leave events
	function patchHandler(obj, handler)
	{
		var undefined;
		
		return function(evt){
				/*In IE lte 8, if this patched handler is assigned to an event attribute on a DOM node instead of using attachEvent(), 
				  we need to get the event object from window.event, which is in the window the node resides in. 
				  This looks a bit complicated because `window` inside the handler will refer to the 
				  window that the handler was defined in, not where the DOM node resides.*/
				evt.window = (obj.ownerDocument || obj.document || obj).parentWindow || window;
				/*            obj==element         obj==window     obj==document */
				evt = evt || evt.window.event;
				
				if(evt.target === undefined) evt.target = evt.srcElement;
				if(evt.relatedTarget === undefined) evt.relatedTarget = evt.target==evt.toElement ? evt.fromElement : evt.toElement;
				if(evt.currentTarget === undefined) evt.currentTarget = obj;	//usually the same as the `this` keyword inside the handler
				if(evt.eventPhase === undefined) evt.eventPhase = 3 - 1*(evt.target === obj);	//capturing==1 (n/a), at_target==2, bubbling==3
				
				evt.preventDefault = evt.preventDefault || patchHandler.preventDefault;
				evt.stopPropagation = patchHandler.stopPropagation;
				
				handler.apply(obj, arguments);	//corrects the `this` keyword and passes the patched event object
			};
	}
	patchHandler.preventDefault = function(){ this.returnValue = false; };
	patchHandler.stopPropagation = function(){
			if(!document.addEventListener)	//IE lte 8
				this.cancelBubble = true;
			else
			{
				this._propagationStopped = true;
				if(this.type != "mouseover" && this.type != "mouseout") this.stopPropagation();
			}
		};
	
	function handleEvent(evt)
	{
		if(!this._eventHandlers) return;
		
		
		
		//set evt.mouseButton
		if(evt.type == "mousedown" || evt.type == "mouseup" || evt.type == "click" || evt.type == "dblclick")
		{
			//set custom evt.mouseButton attribute: 1==left, 2==right, 4==middle
			//this is the button that was pressed to trigger this event
			
			if(evt.which)
				evt.mouseButton = evt.which == 1 ? 1 /*left*/ : evt.which == 2 ? 4 /*middle*/ : 2 /*right*/;
			else	//IE lte 8
			{
				if(!mouseButtonsUpdated)
				{
					if(evt.type == "mousedown")
					{
						mouseButton = (evt.button ^ pressedMouseButtons) & evt.button;
						if(mouseButton & evt.button == 0) mouseButton = mouseButton | evt.button;
						pressedMouseButtons = evt.button;
					}
					else if(evt.type == "mouseup")
					{
						mouseButton = evt.button;
						pressedMouseButtons = ~evt.button & pressedMouseButtons;
					}
					mouseButtonsUpdated = true;
				}
				evt.mouseButton = mouseButton;
				if(this == evt.window.document) mouseButtonsUpdated = false;	//note: mouse button events do not bubble to window in IE lte 8
			}
		}
		else evt.mouseButton = mouseButton = 0;
		
		
		
		var returnValue = true;
		var handlers = this._eventHandlers[evt.type];
		
		//if propagation hasn't been stopped, execute the handlers
		if(!evt._propagationStopped)
		{
			//execute each event handler (in FIFO order)
			for(var i=0; i<handlers.length; i++)
			{
				//execute the handler and update the return value
				returnValue = returnValue && handlers[i].handler.apply(this, arguments) !== false;
			}
		}
		if(!returnValue) evt.preventDefault();
		
		
		
		//if not IE lte 8, and mouseenter/leave handlers may need to be executed
		if(document.addEventListener && (evt.type == "mouseover" || evt.type == "mouseout"))
		{
			if(this == evt.target) stopQueuingEL = false;
			
			//if this element has mouseenter/leave handlers, queue it to have the handlers executed after the mouseover/out event has completed
			
			//test whether the element is still in the DOM or if it has been removed by a previous handler
			function inDOM(elem)
			{
				if(!elem) return false;
				if(elem == evt.window.document.documentElement) return true;
				var tmp = elem;
				do
				{
					if(!tmp.parentNode) return false;	//element is not in the DOM
					tmp = tmp.parentNode;
				}while(tmp != evt.window.document.documentElement)
				return true;
			}
			
			function queueEL(type, elem, related)
			{
				if(stopQueuingEL) return;
				
				if((type == "mouseenter" && !inDOM(elem)) || (type == "mouseleave" && !inDOM(related)))
				{
					stopQueuingEL = true;
					return;
				}
				
				do
				{
					if(related == elem)	//didn't enter/leave the element, only a child element
					{
						stopQueuingEL = true;
						return;
					}
					related = related.parentNode;
				}while(related)
				
				var evtCopy = {};
				for(i in evt) evtCopy[i] = evt[i];
				evtCopy.type = type;
				//save pointer to the element and a copy of the event object to do the mouseenter/leave handlers later
				mouseELQueue.push([elem, evtCopy]);
			}
			if(evt.type == "mouseover" && this._eventHandlers["mouseenter"])
				queueEL("mouseenter", this, (evt.relatedTarget || this));
				//when the page loads, if the mouse is already inside the element and is then moved, the mouseover event fires. There is no source
				// element in that case so the related target is itself
			else if(evt.type == "mouseout" && this._eventHandlers["mouseleave"])
				queueEL("mouseleave", this, evt.relatedTarget);
			
			//if mouseover/out event has stopped bubbling, "fire" mouseenter/leave event
			if(this == evt.window && mouseELQueue.length > 0)
			{
				var q, elem, evtCopy;
				
				//handle queued events
				while(mouseELQueue.length > 0)
				{
					q = evt.type=="mouseover" ? mouseELQueue.pop() : mouseELQueue.shift();	//mouseenter events are handled in reverse order
					elem = q[0];
					evtCopy = q[1];
					
					if(inDOM(elem) || elem == evt.window.document || elem == evt.window)
					{
						handlers = elem._eventHandlers[evt.type];
						for(var i=0; i<handlers.length; i++)
						{
							//execute the handler and update the return value
							returnValue = returnValue && handlers[i].handler.call(elem, evtCopy) !== false;
						}
					}
				}
			}
			
		}
		
		return returnValue;
	}
	
	return addEvent;
})();

function removeEventHandler(obj, type, handler_or_guid)
{
	var guid;
	if(!isNaN(1*handler_or_guid)) guid = handler_or_guid;	//GUID was passed
	else guid = handler_or_guid._handlerGUID;	//handler function was passed
	
	if(guid && obj._eventHandlers && obj._eventHandlers[type])
	{
		var handlers = obj._eventHandlers[type];
		for(var i in handlers)
		{
			if(handlers[i].guid == guid)	//handler is in the list
			{
				handlers.splice(i, 1);	//remove the handler from the list
				break;
			}
		}
	}
}

Revision: 44552
at April 14, 2011 02:16 by wizard04


Initial Code
//Cross-browser event registration functions
//Handlers execute in FIFO order
//Supports mouseleave and mouseenter events
//
//Inspiration mainly from:
//http://www.quirksmode.org/blog/archives/2005/10/_and_the_winner_1.html
//http://dean.edwards.name/weblog/2005/10/add-event/
//http://dean.edwards.name/weblog/2005/10/add-event2/
//http://dean.edwards.name/weblog/2005/10/add-event2/#comment6264
//http://blog.outofhanwell.com/2006/07/03/cross-window-events/
//http://blog.metawrap.com/blog/IEClosuresLeaks.aspx
//http://api.jquery.com/category/events/

//Unlike in jQuery, MouseLeave/Enter events match IE in the order the events are fired. For example, instead of mouseenter being fired
// immediately after mouseover, this implementation waits for the mouseover event to bubble to the top before firing the mouseenter event.
// Also, mouseenter handlers are executed top-down (as if it were a capture phase).
//For IE 8 and older, I used the mouseleave/enter functionality that's built in since there were problems with stopPropagation()
// (specifically, evt._propagationStopped won't hold its value)
//Browsers act differently when an element is removed from or relocated within the DOM if the mouse is over that element. Some fire
// mouseout/over events, some don't, etc.

var addEventHandler = (function(){
	
	var newGUID = 1;	//GUID to assign to the next event handler function without one
	
	if(document.addEventListener)	//not IE lte 8
	{
		//for mouseleave and mouseenter events:
		
		//stores the elements with mouseleave/enter handlers that need to be executed once the mouseout/over event finishes bubbling
		var mouseLEQueue = [];
		//add empty handlers so that handleEvent() is sure to be executed on mouseout/over events
		//this makes sure that mouseleave/enter handlers for each element in mouseLEQueue are executed once the mouseout/over events
		// finish bubbling
		addEvent(document.documentElement, "mouseout", function(){});
		addEvent(document.documentElement, "mouseover", function(){});
	}
	
	function addEvent(obj, type, handler)
	{
		if(!obj._eventHandlers) obj._eventHandlers = [];
		
		//if not IE lte 8, add global handler for the event type (and trigger type) for the object -- if it doesn't already exist
		if(document.addEventListener && (type == "mouseleave" || type == "mouseenter"))
		{
			if(!obj._eventHandlers[type]) obj._eventHandlers[type] = [];
			
			var triggerType = type=="mouseleave" ? "mouseout" : "mouseover";
			addTypeHandler(obj, triggerType);
		}
		else
			addTypeHandler(obj, type);
		
		if(!handler._handlerGUID) handler._handlerGUID = newGUID++;	//assign a GUID to the handler if it doesn't have one
		var guid = handler._handlerGUID;
		
		if(!assigned(obj, type, guid))	//if this handler isn't already assigned to this event type and object
			obj._eventHandlers[type].push({ guid: guid, handler: handler });	//add the handler to the list
		
		return guid;
	}
	
	//add global handler for an event type for an object (if it doesn't already exist)
	function addTypeHandler(obj, type)
	{
		if(!obj._eventHandlers[type])
		{
			obj._eventHandlers[type] = [];
			
			if(obj.addEventListener) obj.addEventListener(type, patchHandler(obj, handleEvent), false);
			else if(obj.attachEvent) obj.attachEvent("on"+type, patchHandler(obj, handleEvent));
			else
			{
				if(obj["on"+type]) obj._eventHandlers[type][0] = { handler: obj["on"+type] };
				obj["on"+type] = patchHandler(obj, handleEvent);
			}
		}
	}
	
	//is the handler for this event type for this object already in the list?
	function assigned(obj, type, guid)
	{
		for(var i in obj._eventHandlers[type])
		{
			if(obj._eventHandlers[type][i].guid == guid) return true;	//handler is already in the list
		}
		return false;
	}
	
	//"patches" a handler function so that:
	//- the `this` keyword within the handler refers to the correct object
	//- the event object has oft-used W3C-standard properties/methods
	//- the event object is always passed as an argument to the handler
	//also modifies evt.stopPropagation() for use with mouseleave/enter events
	function patchHandler(obj, handler)
	{
		var undefined;
		
		return function(evt){
				/*If this patched handler is assigned to an event attribute on a DOM node instead of using attachEvent(), 
				  we need to get the event object from window.event, which is in the window the node resides in. 
				  This looks a bit complicated because `window` inside the handler will refer to the 
				  window that the handler was defined in, not where the DOM node resides.*/
				evt = evt || ((obj.ownerDocument || obj.document || obj).parentWindow || window).event;
				/*             obj==element         obj==window     obj==document */
				
				if(evt.target === undefined) evt.target = evt.srcElement;
				if(evt.relatedTarget === undefined) evt.relatedTarget = evt.target==evt.toElement ? evt.fromElement : evt.toElement;
				if(evt.currentTarget === undefined) evt.currentTarget = obj;	//usually the same as the `this` keyword inside the handler
				if(evt.eventPhase === undefined) evt.eventPhase = 3 - 1*(evt.target === obj);	//capturing==1 (n/a), at_target==2, bubbling==3
				
				evt.preventDefault = evt.preventDefault || patchHandler.preventDefault;
				
				if(document.attachEvent)	//IE
					evt.stopPropagation = evt.stopPropagation || patchHandler.stopPropagation;
				else	//not IE
					evt.stopPropagation = function(){
						this._propagationStopped = true;
						if(this.type != "mouseout" && this.type != "mouseover") this.stopPropagation();
					};
				
				handler.apply(obj, arguments);	//corrects the `this` keyword and passes the patched event object
			};
	}
	patchHandler.preventDefault = function(){ this.returnValue = false; };
	patchHandler.stopPropagation = function(){ this.cancelBubble = true; };
	
	function handleEvent(evt)
	{
		if(!this._eventHandlers) return;
		
		var returnValue = true;
		var handlers = this._eventHandlers[evt.type];
		
		if(!evt._propagationStopped)
		{
			//execute each event handler (in FIFO order)
			for(var i=0; i<handlers.length; i++)
			{
				//execute the handler and update the return value
				returnValue = returnValue && handlers[i].handler.apply(this, arguments) !== false;
			}
		}
		if(!returnValue) evt.preventDefault();
		
		//if not IE lte 8, and mouseleave/enter handlers may need to be executed
		if(document.addEventListener && (evt.type == "mouseout" || evt.type == "mouseover"))
		{
			
			if(evt.type == "mouseout" && this._eventHandlers["mouseleave"])
			{
				var from = this;
				var to = evt.relatedTarget;
				
				do
				{
					if(to == from) break;	//didn't leave this element, only a child element
					to = to.parentNode;
				}while(to)
				if(to != from)	//mouse left the element
				{
					var evtCopy = {};
					for(i in evt) evtCopy[i] = evt[i];
					evtCopy.type = "mouseleave";
					//save pointer to this element and a copy of the event object to do the mouseleave handlers later
					mouseLEQueue.push([this, evtCopy]);
				}
			}
			else if(evt.type == "mouseover" && this._eventHandlers["mouseenter"])
			{
				var to = this;
				var from = evt.relatedTarget || this;
				//when the page loads, if the mouse is already inside the element and is then moved, the mouseover event fires. There is no source
				// element in that case so from == this
				
				do
				{
					if(from == to) break;	//didn't enter this element, only a child element
					from = from.parentNode;
				}while(from)
				if(from != to)	//mouse entered the element
				{
					var evtCopy = {};
					for(i in evt) evtCopy[i] = evt[i];
					evtCopy.type = "mouseenter";
					//save pointer to this element and a copy of the event object to do the mouseenter handlers later
					mouseLEQueue.push([this, evtCopy]);
				}
			}
			
			function inDOM(elem)
			{
				var tmp = elem;
				do
				{
					if(!tmp.parentNode) return false;	//element is not in the DOM
					tmp = tmp.parentNode;
				}while(tmp.nodeName != "HTML")
				return true;
			}
			
			if(this.nodeName != "HTML" && !inDOM(evt.target))
			{
				//target is not in the DOM; don't execute any more mouseleave/enter handlers
				mouseLEQueue = [];
			}
			else if(this.nodeName == "HTML" && mouseLEQueue.length > 0)
			{
				var q, elem, evtCopy;
				if(evt.type == "mouseout")
				{
					if(!inDOM(evt.target))	//target element was removed from the DOM in a previous handler
					{
						mouseLEQueue = [];	//don't execute any more mouseleave handlers
						return;
					}
					
					//execute queued mouseleave events
					while(mouseLEQueue.length > 0)
					{
						q = mouseLEQueue.shift();
						tmp = elem = q[0];
						evtCopy = q[1];
						
						if(!inDOM(elem))	//element was removed from the DOM in a previous handler
						{
							elem._eventHandlers = null;
							return;
						}
						
						handlers = elem._eventHandlers["mouseleave"];
						for(var i=0; i<handlers.length; i++)
						{
							//execute the handler and update the return value
							returnValue = returnValue && handlers[i].handler.call(elem, evtCopy) !== false;
						}
					}
				}
				else if(evt.type == "mouseover")
				{
					if(!inDOM(evt.target))	//target element was removed from the DOM in a previous handler
					{
						mouseLEQueue = [];	//don't execute any more mouseenter handlers
						return;
					}
					
					//execute queued mouseenter events in reverse order
					while(mouseLEQueue.length > 0)
					{
						q = mouseLEQueue.pop();
						elem = q[0];
						evtCopy = q[1];
						
						if(!inDOM(elem))	//element was removed from the DOM in a previous handler
						{
							elem._eventHandlers = null;
							return;
						}
						
						handlers = elem._eventHandlers["mouseenter"];
						for(var i=0; i<handlers.length; i++)
						{
							//execute the handler and update the return value
							returnValue = returnValue && handlers[i].handler.call(elem, evtCopy) !== false;
						}
					}
				}
			}
			
		}
		
		return returnValue;
	}
	
	return addEvent;
})();

function removeEventHandler(obj, type, handler_or_guid)
{
	var guid;
	if(!isNaN(1*handler_or_guid)) guid = handler_or_guid;	//GUID was passed
	else guid = handler_or_guid._handlerGUID;	//handler function was passed
	
	if(guid && obj._eventHandlers && obj._eventHandlers[type])
	{
		var handlers = obj._eventHandlers[type];
		for(var i in handlers)
		{
			if(handlers[i].guid == guid)	//handler is in the list
			{
				handlers.splice(i, 1);	//remove the handler from the list
				break;
			}
		}
	}
}

//remove circular references and avoid memory leaks when the window unloads (especially for IE)
(function(){
	
	//nulls event attributes and ._eventHandlers
	function flushElementHandlers(obj)
	{
		for(var prop in obj)
		{
			if(prop.indexOf("on") == 0 && obj[prop]) obj[prop] = null;
		}
		if(obj._eventHandlers) obj._eventHandlers = null;
	}
	function flushAllHandlers()
	{
		var elems = document.getElementsByTagName("*");
		for(var i=0; i<elems.length; i++){ flushElementHandlers(elems[i]); }
		flushElementHandlers(document);
		flushElementHandlers(window);
	}
	
	addEventHandler(window, "unload", flushAllHandlers);
	
})();

Initial URL

                                

Initial Description
Enhanced cross-browser event handling

Initial Title
Cross-browser event registration

Initial Tags
javascript, event, button

Initial Language
JavaScript