PDA

View Full Version : Cross-Browser Event Handling


Dormilich
05-25-2010, 02:05 PM
a cross-browser event handler, that supports event capturing (even for IE). (it might not be the fastest implementation, but as far as I could get it tested, it works)

happy coding and be the age of inline events gone forever
// based upon an addEvent() implementation of Tino Zijdel
// http://www.quirksmode.org/blog/archives/2005/09/addevent_recodi.html
var Events = (function ()
{
/**
* workaround for Array.lastIndexOf()
*
* @param (mixed) val value to search for
* @param (Array) arr array to search in
* @return (int) array index of val or -1
*/
function array_search(val, arr)
{
if (Array.lastIndexOf) {
return arr.lastIndexOf(val);
}
else {
var i = arr.length;
while (i--) {
if (arr[i] && arr[i] === val) {
break;
}
}
return i;
}
}

/**
* build the array of elements to loop through in capturing phase
*
* @param (Node) elem element from which to search up
* @return (Array) list of elements starting with input
* element until top node
*/
function getParents(elem)
{
var p = [elem];
while (elem.parentNode) {
elem = elem.parentNode;
p.push(elem);
}
return p;
}

/**
* workaround for Function.call()
*
* @param (Function) Fn function reference
* @param (Node) obj node on which the handler is to execute
* @param (Event) evt current event object
* @return (mixed) return value of the function Fn
*/
function callHandler(Fn, obj, evt)
{
var retValue = true;

if (Function.call) {
retValue = Fn.call(obj, evt);
}
else {
try {
obj.__fn = Fn;
retValue = obj.__fn(evt);
delete obj.__fn;
}
catch (f) {
obj.__fn = null;
}
}
return retValue;
}

/**
* function that executes events according to DOM
*
* @param (mixed) e Event object, if passed
* @return (bool) combined return value of bubble handlers
*/
function IEHandler(e)
{
// if no Event object is passed (IE)
e = e || window.event;
// add .stopPropagation()
if (!e.stopPropagation) {
e.stopPropagation = function ()
{
this.cancelEvent = true;
this.cancelBubble = true;
}
}
// get Event Target
var base = e.target || e.srcElement,
// get Event Type
evTypeRef = '__' + e.type,
// return values for bubbling handlers
retValue = true,
i, j, l, elem, chain, evPhase;

// execute only once
if (base == this) {
// get elements for capturing
chain = getParents(base);
for (i = chain.length; i--;) {
elem = chain[i];
// if there are functions attached
// to execute in capturing context
if (elem[evTypeRef] && elem[evTypeRef].capture) {
evPhase = elem[evTypeRef].capture;
// execute each function
for (j = 0, l = evPhase.length; j < l; j++) {
if (evPhase[j]) {
// exit on stopPropagation()
if (e.cancelEvent === true) {
return null;
}
// there can be no value returned due to the
// executing element of this loop is the Event
// target and not the element the handler
// executes on.
callHandler(evPhase[j], elem, e);
}
}
}
}
}
// if there are functions attached to execute in bubbling context
if (this[evTypeRef] && this[evTypeRef].bubble) {
evPhase = this[evTypeRef].bubble;
for (j = 0, l = evPhase.length; j < l; j++) {
if (evPhase[j]) {
// this time we're in the correct element context so we can
// safely return something
retValue = callHandler(evPhase[j], this, e) && retValue;
}
}
}
return retValue;
}

/**
* return interface
*
* @public add
* @public remove
*/
return {
add : function (obj, evType, fn, useCapture)
{
if (typeof fn !== "function") {
throw new TypeError("Function expected!");
}
// make useCapture a Boolean
useCapture = !!useCapture;

// W3C
if (obj.addEventListener) {
obj.addEventListener(evType, fn, useCapture);
}
else {
var evTypeRef = '__' + evType,
phase = useCapture ? "capture" : "bubble";

// create ".__event" property
if (!obj[evTypeRef]) {
obj[evTypeRef] = {};
}
// search in ".__event.bubble"/".__event.capture"
// if function is already registered
if (obj[evTypeRef][phase]) {
if (array_search(fn, obj[evTypeRef][phase]) > -1) {
return;
}
}
// create ".__event.bubble"/".__event.capture" property
else {
obj[evTypeRef][phase] = [];
if (!useCapture && obj['on' + evType] && obj['on' + evType] != IEHandler) {
// add any previous function assigned to .onevent
obj[evTypeRef][phase][0] = obj['on' + evType];
}
}
// add function to stack
obj[evTypeRef][phase].push(fn);
// set "global" handler
obj['on' + evType] = IEHandler;
}
},

remove : function (obj, evType, fn, useCapture)
{
useCapture = !!useCapture;

if (obj.removeEventListener) {
obj.removeEventListener(evType, fn, useCapture);
}
else {
var evTypeRef = '__' + evType,
phase = useCapture ? "capture" : "bubble",
evPhase, i;

if (obj[evTypeRef] && obj[evTypeRef][phase]) {
evPhase = obj[evTypeRef][phase];
i = array_search(fn, evPhase);
if (i > -1) {
try {
delete evPhase[i];
}
catch (e) {
evPhase[i] = null;
}
}
}
}
}
}
})();