Cross-browser event registration


/ Published in: JavaScript
Save to your folder(s)

Enhanced cross-browser event handling


Copy this code and paste it in your HTML
  1. /*****************************************
  2.  * Events Max v10.0
  3.  * Enhanced cross-browser event handling
  4.  *
  5.  * This work is licensed under a Creative Commons Attribution 3.0 Unported License
  6.  * http://creativecommons.org/licenses/by/3.0/
  7.  *
  8.  * Author: Andy Harrison, http://dragonzreef.com/
  9.  * Date: 28 June 2012
  10.  *****************************************/
  11.  
  12. //Cross-browser event registration functions
  13. //Supports both capture and bubble phases
  14. //Handlers execute in FIFO order
  15. //Supports mouseenter and mouseleave events
  16. //Custom event attributes:
  17. // event.mouse.button: the mouse button value (1, 2, or 4) for mousedown, mouseup, click, and dblclick events
  18. // event.mouse.position: object containing the mouse positions within the screen, window, document, and layer (e.g., evt.mouse.position.document.x)
  19. // event.mouse.wheelDelta: distance ("clicks") the mouse wheel rolled; negative means it rolled up
  20. // event.keyboard.charCode: Unicode character code that was generated on a keypress event
  21. // event.keyboard.char: Unicode character that was generated on a keypress event
  22. //
  23. //addEventHandler(obj, type, handler, useCapture)
  24. //removeEventHandler(obj, type, handler_or_guid, useCapture)
  25. //
  26. //Event types are case-sensitive and should not include "on" (e.g., use "click", not "onclick")
  27. //
  28. //Usage examples:
  29. // addEventHandler(element, "click", handlerFunction, true);
  30. // removeEventHandler(element, "click", handlerFunction, true);
  31. //or:
  32. // var guid = addEventHandler(element, "click", function(evt){doSomething()});
  33. // removeEventHandler(element, "click", guid);
  34. //
  35. //This script uses a custom event attribute for the mouse button value: event.mouse.button
  36. //This goes by the Microsoft model, where left==1, right==2, and middle==4
  37. //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.
  38. // (e.g., if the mouse leaves the window, buttons are changed by the user without the browser's detection, and the mouse comes back)
  39. //
  40. //There are a few custom attributes used by this script that must not be manipulated:
  41. // ._handlerGUID on functions passed as handlers
  42. // ._eventHandlers on DOM objects that have handlers assigned to them
  43. //
  44. //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
  45. // 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
  46. // events when an element is added/removed/positioned under the mouse, some stop bubbling an event if the currentTarget has been removed
  47. // from the DOM, etc.
  48. //
  49. //Techniques and inspiration largely from:
  50. // http://dean.edwards.name/weblog/2005/10/add-event2/
  51. // http://outofhanwell.wordpress.com/2006/07/03/cross-window-events/
  52. // http://blog.metawrap.com/2005/10/24/ie-closures-leaks/
  53. // http://www.quirksmode.org/js/events_properties.html
  54.  
  55.  
  56.  
  57. //addEventHandler(obj, type, handler, useCapture)
  58. //returns the GUID assigned to the handler
  59. var addEventHandler;
  60.  
  61. //removeEventHandler(obj, type, handler_or_guid, useCapture)
  62. var removeEventHandler;
  63.  
  64. (function (){
  65.  
  66. "use strict";
  67.  
  68.  
  69. /*** event handler registration ***/
  70.  
  71. var newGUID = 1; //GUID to assign to the next event handler function without one
  72.  
  73. addEventHandler = function (obj, type, handler, useCapture){
  74.  
  75. type = ""+type;
  76.  
  77. if(!(obj instanceof Object || (!document.addEventListener && typeof(obj) === "object"))){
  78. throw new TypeError("Invalid argument: obj");
  79. }
  80. if(!(/^[0-9a-z]+$/i).test(type)){
  81. throw new TypeError("Invalid argument: type");
  82. }
  83. if(typeof(handler) !== "function"){
  84. throw new TypeError("Invalid argument: handler");
  85. }
  86.  
  87. var ownerWindow = getOwnerWindow(obj);
  88.  
  89. //make sure the object's window flushes handlers when the page unloads to avoid memory leaks
  90. if(!flushAllHandlers.adding && !handlerIsAssigned(ownerWindow, "unload", "bubble", flushAllHandlers._handlerGUID)){
  91. flushAllHandlers.adding = true;
  92. addEventHandler(ownerWindow, "unload", flushAllHandlers);
  93. flushAllHandlers.adding = false;
  94. }
  95.  
  96. if(type === "mouseenter"){
  97. //make sure the object's window handles the mouseover type so that custom events can be triggered after the bubble phase of mouseenter
  98. addTypeHandler(ownerWindow, "mouseover");
  99.  
  100. //add global handlers for the mouseover event type for the object (if they don't already exist)
  101. addTypeHandler(obj, "mouseover");
  102. if(!obj._eventHandlers) obj._eventHandlers = {};
  103. if(!obj._eventHandlers["mouseenter"]) obj._eventHandlers["mouseenter"] = { capture: [], bubble: [] };
  104. }
  105. else if(type === "mouseleave"){
  106. //make sure the object's window handles the mouseout type so that custom events can be triggered after the bubble phase of mouseleave
  107. addTypeHandler(ownerWindow, "mouseout");
  108.  
  109. //add global handlers for the mouseout event type for the object (if they don't already exist)
  110. addTypeHandler(obj, "mouseout");
  111. if(!obj._eventHandlers) obj._eventHandlers = {};
  112. if(!obj._eventHandlers["mouseleave"]) obj._eventHandlers["mouseleave"] = { capture: [], bubble: [] };
  113. }
  114. else{
  115. //add global handlers for the event type for the object (if they don't already exist)
  116. addTypeHandler(obj, type);
  117. }
  118.  
  119. if(isNaN(handler._handlerGUID) || handler._handlerGUID < 1 || handler._handlerGUID === Infinity){
  120. handler._handlerGUID = newGUID++; //assign a GUID to the handler if it doesn't have one (or if the user messed with it)
  121. }
  122.  
  123. var phase = useCapture ? "capture" : "bubble";
  124. if(!handlerIsAssigned(obj, type, phase, handler._handlerGUID)){ //if this handler isn't already assigned to this object, event type, and phase
  125. obj._eventHandlers[type][phase].push({ guid: handler._handlerGUID, handler: handler }); //add the handler to the list
  126. }
  127.  
  128. return handler._handlerGUID;
  129.  
  130. };
  131.  
  132. //get the window in which the object resides; this is not necessarily the same window where a function is defined
  133. function getOwnerWindow(obj){
  134. return (obj.ownerDocument || obj.document || obj).parentWindow || window;
  135. /* obj==element obj==window obj==document */
  136. }
  137.  
  138. //add global handlers for an event type for an object (if they don't already exist)
  139. function addTypeHandler(obj, type){
  140. if(!obj._eventHandlers) obj._eventHandlers = {};
  141. if(!obj._eventHandlers[type]){
  142. obj._eventHandlers[type] = { capture: [], bubble: [] };
  143.  
  144. if(document.addEventListener){ //not IE lte 8
  145. obj.addEventListener(type, patchHandler(obj, handleEvent, true), true);
  146. obj.addEventListener(type, patchHandler(obj, handleEvent), false);
  147. }
  148. else if(obj.attachEvent){ //IE lte 8
  149. obj.attachEvent("on"+type, patchHandler(obj, handleEvent));
  150. }
  151. else{ //just in case
  152. if(obj["on"+type]){ //if there is already a handler assigned
  153. obj["on"+type]._handlerGUID = newGUID;
  154. obj._eventHandlers[type]["bubble"][0] = { guid: newGUID++, handler: obj["on"+type] };
  155. }
  156. obj["on"+type] = patchHandler(obj, handleEvent);
  157. }
  158. }
  159. }
  160.  
  161. function patchHandler(obj, handler, capturing){
  162.  
  163. return function (evt){
  164. //In IE lte 8, if this patched handler is assigned to an event attribute on a DOM node instead of using attachEvent(),
  165. // we need to get the event object from the window the node resides in (which is not necessarily where the handler was defined).
  166. var evtWindow = getOwnerWindow(obj);
  167. evt = evt || evtWindow.event;
  168. if(!evt.view) evt.view = evtWindow;
  169.  
  170. patchEvent.call(obj, evt, capturing);
  171.  
  172. handler.call(obj, evt); //applies the correct value for the `this` keyword and passes the patched event object
  173. };
  174.  
  175. }
  176.  
  177. //is the handler for this object, event type, and phase already in the list?
  178. function handlerIsAssigned(obj, type, phase, guid){
  179. if(!obj._eventHandlers || !obj._eventHandlers[type] || !guid) return false;
  180. var handlerList = obj._eventHandlers[type][phase];
  181. for(var i=0; i<handlerList.length; i++){
  182. if(handlerList[i].guid === guid)
  183. return true; //handler is already in the list
  184. }
  185. return false;
  186. }
  187.  
  188.  
  189.  
  190. /*** event handling ***/
  191.  
  192. var _evtStatus = {}; //.capturing, .propagationStopped, .propagationStoppedAtTarget, .propagationStoppedImmediately
  193. var mouseELQueue = []; //propagation path for mouseenter and mouseleave events
  194.  
  195. function handleEvent(evt){
  196.  
  197. var returnValue, evtClone, path, handlers, i, j;
  198.  
  199. returnValue = evt.type==="mouseover" ? false : evt.type!=="beforeunload" ? true : returnValue;
  200.  
  201. //test whether an object is still in the DOM or if it has been removed by a previous handler
  202. function inDOM(obj){
  203. if(!obj) return false;
  204. if(obj === evt.view || obj === evt.view.document) return true;
  205. var tmp = obj;
  206. do{
  207. if(!tmp.parentNode) return false; //object is not in the DOM
  208. tmp = tmp.parentNode;
  209. }while(tmp !== evt.view.document)
  210. return true;
  211. }
  212.  
  213. function updateReturnValue(evt, newValue){
  214. if(evt.type === "mouseover"){
  215. returnValue = returnValue === true || newValue === true || evt.defaultPrevented;
  216. }
  217. else if(evt.type === "beforeunload"){
  218. //in this implementation, only the first defined return value will be used; return values from subsequent handlers will be ignored
  219. returnValue = typeof(returnValue) !== "undefined" ? returnValue : newValue;
  220. }
  221. else{
  222. returnValue = !evt.cancelable || (returnValue && newValue !== false && !evt.defaultPrevented);
  223. }
  224. }
  225.  
  226.  
  227. /*** mouseenter & mouseleave preparations ***/
  228.  
  229. function fillMouseELQueue(evt){
  230.  
  231. //note: this can get screwed up if elements are moved/removed from the DOM
  232.  
  233. var obj, obj2;
  234.  
  235. mouseELQueue = [];
  236.  
  237. if(evt.target === evt.relatedTarget){
  238. //do nothing
  239. }
  240. else if(evt.target === evt.view){
  241. //related is a child of window; did not enter or leave window; do nothing
  242. }
  243. else if(evt.relatedTarget === null){
  244. //entered/left window; events will be fired
  245. obj = evt.target;
  246. while(obj){
  247. mouseELQueue.push(obj);
  248. obj = obj.parentNode;
  249. }
  250. mouseELQueue.push(evt.view);
  251. }
  252. else{
  253. obj = evt.relatedTarget;
  254. while(obj && obj !== evt.target){
  255. obj = obj.parentNode
  256. }
  257. if(obj === evt.target){
  258. //related is a child of target; did not enter or leave target; do nothing
  259. }
  260. else{
  261. //related is not a child of target (but target is not necessarily a child of related);
  262. // entered/left target; possibly left/entered related; events will be fired
  263. obj = evt.target;
  264. while(obj && obj !== evt.relatedTarget){
  265. obj2 = evt.relatedTarget;
  266. while(obj2 && obj2 !== obj){
  267. obj2 = obj2.parentNode;
  268. }
  269. if(obj === obj2){ //common ancestor of target & related (mouse left/entered related)
  270. break;
  271. }
  272. mouseELQueue.push(obj); //obj is a child of related
  273. obj = obj.parentNode;
  274. }
  275. }
  276. }
  277.  
  278. }
  279.  
  280. //at beginning of capture phase, fill mouseELQueue (if applicable)
  281. if((evt.type === "mouseover" || evt.type === "mouseout") && _evtStatus.capturing && this === evt.view){
  282. fillMouseELQueue(evt);
  283. }
  284.  
  285.  
  286. /*** manually run capture phase, if required ***/
  287.  
  288. //for IE lte 8, run capture phase manually
  289. if(!document.addEventListener && evt.eventPhase === 2){
  290.  
  291. //create copy of event object (so we can modify read-only properties)
  292. evtClone = createEventClone(evt);
  293. evtClone.eventPhase = 1;
  294.  
  295. //at beginning of capture phase, fill mouseELQueue (if applicable)
  296. if(evt.type === "mouseover" || evt.type === "mouseout"){
  297. fillMouseELQueue(evtClone);
  298. }
  299.  
  300. path = getPropagationPath(evt); //array of objects with related event handler (not including the target)
  301. for(i=path.length-1; i>=0; i--){ //for each element in the propagation path array
  302. if(_evtStatus.propagationStopped) break;
  303.  
  304. //update event object
  305. evtClone.currentTarget = path[i];
  306.  
  307. //execute the capture handlers (in FIFO order)
  308. handlers = path[i]._eventHandlers[evtClone.type]["capture"];
  309. for(j=0; j<handlers.length; j++){
  310. //execute the handler and update the return value
  311. updateReturnValue(evtClone, handlers[j].handler.call(path[j], evtClone));
  312. if(_evtStatus.propagationStoppedImmediately) break;
  313. }
  314. }
  315.  
  316. //execute the capture handlers on the target (in FIFO order)
  317. if(!_evtStatus.propagationStopped){
  318. evtClone.eventPhase = 2;
  319. evtClone.currentTarget = this;
  320. handlers = this._eventHandlers[evtClone.type]["capture"];
  321. for(i=0; i<handlers.length; i++){
  322. //execute the handler and update the return value
  323. updateReturnValue(evtClone, handlers[i].handler.call(this, evtClone));
  324. if(_evtStatus.propagationStoppedImmediately) break;
  325. }
  326. }
  327.  
  328. evtClone = null;
  329.  
  330. }
  331.  
  332.  
  333. /*** process handlers for currentTarget ***/
  334.  
  335. //execute the handlers for this phase (in FIFO order)
  336. if(!_evtStatus.propagationStopped || (evt.eventPhase===2 && _evtStatus.propagationStoppedAtTarget)){
  337.  
  338. handlers = this._eventHandlers[evt.type][_evtStatus.capturing ? "capture" : "bubble"];
  339. for(i=0; i<handlers.length; i++){
  340. if(_evtStatus.propagationStoppedImmediately) break;
  341. //execute the handler and update the return value
  342. updateReturnValue(evt, handlers[i].handler.call(this, evt));
  343. }
  344.  
  345. }
  346.  
  347. if((evt.type === "mouseover" && returnValue === true) || (evt.type !== "mouseover" && returnValue === false)){
  348. evt.preventDefault();
  349. }
  350.  
  351.  
  352. /*** finalize event ***/
  353.  
  354. //if done handling this event
  355. if(!_evtStatus.capturing && (this === evt.view || !evt.bubbles)){
  356.  
  357. //trigger mouseenter events, if applicable
  358. if(evt.type === "mouseover" && mouseELQueue.length > 0){
  359. evtClone = createEventClone(evt);
  360. while(mouseELQueue.length > 0){
  361. evtClone.target = mouseELQueue.pop();
  362. triggerCustomEvent(evtClone, "mouseenter", false);
  363. }
  364. }
  365. //trigger mouseleave events, if applicable
  366. else if(evt.type === "mouseout" && mouseELQueue.length > 0){
  367. evtClone = createEventClone(evt);
  368. while(mouseELQueue.length > 0){
  369. evtClone.target = mouseELQueue.shift();
  370. triggerCustomEvent(evtClone, "mouseleave", false);
  371. }
  372. }
  373.  
  374. //reset event status
  375. _evtStatus = {};
  376.  
  377. }
  378.  
  379.  
  380. evt.returnValue = returnValue;
  381. return returnValue;
  382.  
  383. }
  384.  
  385. //get hierarchical array of objects with handlers for a specific event; first item is object closest to target (target is not included)
  386. function getPropagationPath(evt){
  387.  
  388. var path = [];
  389. var obj = evt.target;
  390. var handlers;
  391.  
  392. while(obj.parentNode){
  393. obj = obj.parentNode;
  394. if(!obj._eventHandlers || !obj._eventHandlers[evt.type]) continue;
  395. handlers = obj._eventHandlers[evt.type];
  396. if(handlers["capture"].length > 0 || handlers["bubble"].length > 0){
  397. path.push(obj);
  398. }
  399. }
  400. if(evt.target !== evt.view && evt.view._eventHandlers && evt.view._eventHandlers[evt.type]){
  401. handlers = evt.view._eventHandlers[evt.type];
  402. if(handlers["capture"].length > 0 || handlers["bubble"].length > 0){
  403. path.push(evt.view);
  404. }
  405. }
  406.  
  407. return path;
  408.  
  409. }
  410.  
  411. function patchEvent(evt, capturing){
  412.  
  413. if(!evt.target) evt.target = evt.srcElement;
  414. if(!evt.srcElement) evt.srcElement = evt.target;
  415. if(!evt.relatedTarget) try{ evt.relatedTarget = evt.target===evt.toElement ? evt.fromElement : evt.toElement; }catch(e){}
  416. if(!evt.currentTarget) evt.currentTarget = this;
  417. if(!evt.eventPhase) evt.eventPhase = evt.target===this ? 2 : 3; //capturing==1 (not supported), at_target==2, bubbling==3
  418. _evtStatus.capturing = capturing; //to determine, when evt.eventPhase===2, whether we need to execute capture or bubble handlers on the target
  419.  
  420. var originalPreventDefault = evt.preventDefault;
  421. evt.preventDefault = function (){
  422. if(this.cancelable){
  423. if(originalPreventDefault) originalPreventDefault();
  424. this.returnValue = false;
  425. if(!this.defaultPrevented) this.defaultPrevented = true;
  426. }
  427. };
  428. evt.stopPropagation = function (){
  429. if(this.eventPhase === 2 && !_evtStatus.propagationStopped) _evtStatus.propagationStoppedAtTarget = true;
  430. _evtStatus.propagationStopped = true;
  431. };
  432. evt.stopImmediatePropagation = function (){
  433. _evtStatus.propagationStopped = true;
  434. _evtStatus.propagationStoppedImmediately = true;
  435. };
  436.  
  437.  
  438. /*** mouse event attributes ***/
  439.  
  440. if(!evt.fromElement && !evt.toElement){
  441. try{
  442. if(evt.type === "mouseover" || evt.type === "mouseenter"){
  443. evt.fromElement = evt.relatedTarget;
  444. evt.toElement = evt.target;
  445. }
  446. else if(evt.type === "mouseout" || evt.type === "mouseleave"){
  447. evt.fromElement = evt.target;
  448. evt.toElement = evt.relatedTarget;
  449. }
  450. }catch(e){}
  451. }
  452.  
  453. //add custom mouse attributes
  454. evt.mouse = {};
  455.  
  456. //mouse button
  457. //this is the button that was pressed to trigger this event: 1==left, 2==right, 4==middle
  458. if(evt.type === "mousedown" || evt.type === "mouseup" || evt.type === "click" || evt.type === "dblclick"){
  459. if(evt.which){
  460. evt.mouse.button = evt.which===1 ? 1 : evt.which===2 ? 4 : 2;
  461. }
  462. else{ //IE lte 8
  463. var mb = patchEvent.mouseButtons;
  464. if(evt.target === this){ //update mb.button
  465. if(evt.type === "mousedown"){
  466. mb.button = (evt.button ^ mb.pressed) & evt.button;
  467. if((mb.button & evt.button) === 0) mb.button = evt.button;
  468. mb.pressed = evt.button;
  469.  
  470. //note: mb.button may be incorrect on mousedown since we can't reliably keep track of IE's event.button
  471. // value (i.e., which buttons are pressed when the event is fired) between events (e.g., if the mouse
  472. // leaves the window, buttons are changed by the user without the browser's detection, and the mouse comes back)
  473. }
  474. else if(evt.type === "mouseup"){
  475. mb.button = evt.button;
  476. mb.pressed = ~evt.button & mb.pressed;
  477. }
  478. }
  479. evt.mouse.button = mb.button;
  480. }
  481. }
  482. else{
  483. evt.mouse.button = patchEvent.mouseButtons.button = 0;
  484. }
  485.  
  486. //mouse wheel distance
  487. //this is the distance ("clicks") the mouse wheel rolled; negative means it rolled up
  488. if((evt.type==="mousewheel" || evt.type==="wheel" || evt.type==="DOMMouseScroll") && (evt.wheelDelta || evt.detail)){
  489. evt.mouse.wheelDelta = evt.wheelDelta ? -evt.wheelDelta/120 : evt.detail ? evt.detail/3 : 0;
  490. }
  491.  
  492. //mouse position
  493. if(evt.type.slice(0,5) === "mouse" || evt.type==="wheel" || evt.type==="DOMMouseScroll" || evt.type.slice(0,4)==="drag" || evt.type==="drop"){
  494. evt.mouse.position = {};
  495. evt.mouse.position.screen = { x:evt.screenX, y:evt.screenY, left:evt.screenX, top:evt.screenY };
  496. evt.mouse.position.window = evt.mouse.position.frame = { x:evt.clientX, y:evt.clientY, left:evt.clientX, top:evt.clientY };
  497. evt.mouse.position.document = (function(){
  498. if(isNaN(evt.pageX) || isNaN(evt.pageY)){
  499. var left, top; //scroll position of document
  500. if(window.pageYOffset) //all except IE
  501. { left = window.pageXOffset; top = window.pageYOffset; }
  502. else if(document.documentElement && !isNaN(document.documentElement.scrollTop)) //IE standards compliance mode
  503. { left = document.documentElement.scrollLeft; top = document.documentElement.scrollTop; }
  504. else //IE quirks mode
  505. { left = document.body.scrollLeft; top = document.body.scrollTop; }
  506. return { x:left+evt.clientX, y:top+evt.clientY, left:left+evt.clientX, top:top+evt.clientY };
  507. }
  508. else return { x:evt.pageX, y:evt.pageY, left:evt.pageX, top:evt.pageY };
  509. })();
  510. 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 };
  511.  
  512. try{
  513. evt.pageX = evt.mouse.position.document.x;
  514. evt.pageY = evt.mouse.position.document.y;
  515. }catch(e){}
  516. try{
  517. evt.layerX = evt.mouse.position.layer.x;
  518. evt.layerY = evt.mouse.position.layer.y;
  519. }catch(e){}
  520. }
  521.  
  522.  
  523. /*** keyboard event attributes ***/
  524.  
  525. //add custom key attributes
  526. evt.keyboard = {};
  527.  
  528. //see http://unixpapa.com/js/key.html
  529. // http://www.quirksmode.org/js/keys.html
  530. if(evt.type === "keypress"){
  531. if(isNaN(evt.which)){ //IE lte 8
  532. evt.keyboard.charCode = evt.keyCode;
  533. evt.keyboard.char = String.fromCharCode(evt.keyCode);
  534. }
  535. else if(evt.which !== 0 && evt.charCode !== 0){ //other browsers (note: sometimes special keys still give a non-zero value)
  536. evt.keyboard.charCode = evt.which;
  537. evt.keyboard.char = String.fromCharCode(evt.which);
  538. }
  539. else{ //special key
  540. evt.keyboard.charCode = 0;
  541. evt.keyboard.char = evt.key; //evt.key only works in IE gte 9; it gives "Control", "Shift", "Down", etc. for special keys
  542. }
  543.  
  544. try{ evt.which = evt.keyboard.charCode; }catch(e){}
  545. try{ evt.keyCode = evt.keyboard.charCode; }catch(e){}
  546. try{ evt.charCode = evt.keyboard.charCode; }catch(e){}
  547. try{ evt.key = evt.keyboard.char; }catch(e){}
  548. }
  549. else if(evt.type === "keydown" || evt.type === "keyup"){
  550. evt.keyboard.char = evt.key; //evt.key only works in IE gte 9
  551. }
  552.  
  553. }
  554. patchEvent.mouseButtons = { button:0, pressed:0 }; //for IE lte 8; keeps track of which mouse buttons are pressed (not always accurate)
  555.  
  556. function triggerCustomEvent(evt, type, bubbles){
  557.  
  558. var path, handlers, i, j;
  559.  
  560. //reset event status
  561. _evtStatus = {};
  562.  
  563. //create custom event object
  564. evt = createCustomEventClone(evt);
  565. evt.type = type;
  566. evt.bubbles = !!bubbles;
  567.  
  568. path = getPropagationPath(evt); //array of objects with related event handler (not including the target)
  569.  
  570. //run capture phase
  571. for(i=path.length-1; i>=0; i--){ //for each element in the propagation path array
  572. if(_evtStatus.propagationStopped) break;
  573.  
  574. //update event object
  575. evt.eventPhase = 1;
  576. evt.currentTarget = path[i];
  577.  
  578. //execute the capture handlers (in FIFO order)
  579. handlers = path[i]._eventHandlers[evt.type]["capture"];
  580. for(j=0; j<handlers.length; j++){
  581. handlers[j].handler.call(path[j], evt); //execute the handler
  582. if(_evtStatus.propagationStoppedImmediately) break;
  583. }
  584. }
  585.  
  586. //run target phase
  587. if(!_evtStatus.propagationStopped && evt.target._eventHandlers && evt.target._eventHandlers[evt.type]){
  588. //update event object
  589. evt.eventPhase = 2;
  590. evt.currentTarget = evt.target;
  591.  
  592. //execute capture & bubble handlers (in FIFO order)
  593. handlers = evt.currentTarget._eventHandlers[evt.type]["capture"].concat(evt.currentTarget._eventHandlers[evt.type]["bubble"]);
  594. for(i=0; i<handlers.length; i++){
  595. handlers[i].handler.call(evt.target, evt); //execute the handler
  596. if(_evtStatus.propagationStoppedImmediately) break;
  597. }
  598. }
  599.  
  600. //if this event can bubble
  601. if(evt.bubbles){
  602. //run bubble phase
  603. for(i=0; i<path.length; i++){ //for each element in the propagation path array
  604. if(_evtStatus.propagationStopped) break;
  605.  
  606. //update event object
  607. evt.eventPhase = 3;
  608. evt.currentTarget = path[i];
  609.  
  610. //execute the bubble handlers (in FIFO order)
  611. handlers = path[i]._eventHandlers[evt.type]["bubble"];
  612. for(j=0; j<handlers.length; j++){
  613. handlers[j].handler.call(path[j], evt); //execute the handler
  614. if(_evtStatus.propagationStoppedImmediately) break;
  615. }
  616. }
  617. }
  618.  
  619. //reset event status
  620. _evtStatus = {};
  621.  
  622. }
  623.  
  624. //creates a custom object to use as an event object (e.g., based on a mouseover event to use for a mouseenter event)
  625. function createEventClone(eventToClone){
  626. var evt = {};
  627. for(var i in eventToClone) evt[i] = eventToClone[i];
  628.  
  629. return evt;
  630. }
  631.  
  632. //creates a custom object to use as an event object (e.g., based on a mouseover event to use for a mouseenter event)
  633. function createCustomEventClone(eventToClone){
  634. var evt = createEventClone(eventToClone);
  635.  
  636. evt.cancelable = false;
  637. evt.returnValue = true;
  638. evt.defaultPrevented = false;
  639.  
  640. _evtStatus.propagationStopped = false;
  641. _evtStatus.propagationStoppedImmediately = false;
  642.  
  643. return evt;
  644. }
  645.  
  646.  
  647.  
  648. /*** avoid memory leaks ***/
  649.  
  650. //remove circular references and avoid memory leaks when the window unloads (especially for IE)
  651.  
  652. //nulls event attributes and handler collection
  653. function flushEventHandlers(obj){
  654. obj._eventHandlers = null;
  655. for(var prop in obj){
  656. if(prop.slice(0, 2) === "on") obj[prop] = null;
  657. }
  658. }
  659.  
  660. function flushAllHandlers(evt){
  661. var elems = evt.view.document.getElementsByTagName("*");
  662. for(var i=0; i<elems.length; i++){
  663. flushEventHandlers(elems[i]);
  664. }
  665. flushEventHandlers(evt.view.document);
  666. flushEventHandlers(evt.view);
  667. }
  668. flushAllHandlers.adding = false;
  669.  
  670.  
  671.  
  672. /*** event handler removal ***/
  673.  
  674. removeEventHandler = function (obj, type, handler_or_guid, useCapture){
  675.  
  676. if(!(obj instanceof Object || (!document.addEventListener && typeof(obj) === "object"))){
  677. throw new TypeError("Invalid argument: obj");
  678. }
  679. if(!(/^[0-9a-z]+$/i).test(type)){
  680. throw new TypeError("Invalid argument: type");
  681. }
  682. if(( isNaN(handler_or_guid) && typeof(handler_or_guid) !== "function") || handler_or_guid < 1 || handler_or_guid === Infinity){
  683. throw new TypeError("Invalid argument: handler_or_guid");
  684. }
  685.  
  686. var guid = typeof(handler_or_guid)==="function" ? handler_or_guid._handlerGUID : handler_or_guid;
  687. if(isNaN(guid) || guid < 1 || guid === Infinity){ //in case the user messed with ._handlerGUID
  688. throw new TypeError("Handler GUID is invalid");
  689. }
  690.  
  691. if(obj._eventHandlers && obj._eventHandlers[type]){
  692. var handlers = obj._eventHandlers[type][useCapture ? "capture" : "bubble"];
  693. for(var i=0; i<handlers.length; i++){
  694. if(handlers[i].guid === guid){ //handler is in the list
  695. handlers.splice(i, 1); //remove the handler from the list
  696. break;
  697. }
  698. }
  699. }
  700.  
  701. };
  702.  
  703. })();

Report this snippet


Comments

RSS Icon Subscribe to comments

You need to login to post a comment.