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