Revision: 44563
Updated Code
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
Updated Code
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
Updated Code
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
Updated Code
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
Updated Code
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
Updated Code
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
Updated Code
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
Updated Code
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
Updated Code
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
Updated Code
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
Updated Code
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
Initial Code
Initial URL
Initial Description
Initial Title
Initial Tags
Initial Language
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