addEvent() and removeEvent() provide one unified, cross-browser safe way to handle event listeners.
Unlike many other implementations, this addEvent not only adds event listeners. Usually, working with events is pain because of a number of browser inconsistencies (read: Internet Explorer incapabilities). Hence addEvent does:
event.stopPropagation(), event.preventDefault()
event.currentTarget, event.target, event.relatedTarget (MouseEvent), event.eventPhase
Event.AT_TARGET, Event.BUBBLING_PHASE
handleEvent)
this with the target which the listener was registered on (instead of window)
addEventListener, addEvent has no useCapture argument. For what it’s worth, it’s impossible to implement capture for IE.
unload handlers are said to break bfcache, I’ve implemented the cleanup for IE only.
/**
* addEvent & removeEvent -- cross-browser event handling
* Copyright (C) 2006-2007 Dao Gottwald
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Contact information:
* Dao Gottwald <dao at design-noir.de>
*
* @version 1.2.1
*/
function addEvent(o, type, fn) {
o.addEventListener(type, fn, false);
}
function removeEvent(o, type, fn) {
o.removeEventListener(type, fn, false);
}
/*@cc_on if (!window.addEventListener) {
var addEvent = function (o, type, fn) {
if (!o._events) o._events = {};
var queue = o._events[type];
if (!queue) {
o._events[type] = [fn];
if (!o._events._callback)
o._events._callback = function (e) { Event._callListeners(e, o) };
o.attachEvent("on" + type, o._events._callback);
} else if (Event._fnIndex(o, type, fn) == -1)
queue.push(fn);
else return;
Event._mem.push([o, type, fn]);
};
var removeEvent = function (o, type, fn) {
var i = Event._fnIndex(o, type, fn);
if (i < 0) return;
var queue = o._events[type];
if (queue.calling) {
delete queue[i];
if (queue.removeListeners)
queue.removeListeners.push(i);
else
queue.removeListeners = [i];
} else
if (queue.length == 1)
Event._detach(o, type);
else
queue.splice(i, 1);
};
var Event = {
AT_TARGET: 2,
BUBBLING_PHASE: 3,
stopPropagation: function () { this.cancelBubble = true },
preventDefault: function () { this.returnValue = false },
_mem: [],
_callListeners: function (e, o) {
e.stopPropagation = this.stopPropagation;
e.preventDefault = this.preventDefault;
e.currentTarget = o;
e.target = e.srcElement;
e.eventPhase = e.currentTarget == e.target ? this.AT_TARGET : this.BUBBLING_PHASE;
switch (e.type) {
case "mouseover":
e.relatedTarget = e.fromElement;
break;
case "mouseout":
e.relatedTarget = e.toElement;
}
var queue = o._events[e.type];
queue.calling = true;
for (var i = 0, l = queue.length; i < l; i++)
if (queue[i])
if ("handleEvent" in queue[i])
queue[i].handleEvent(e);
else
queue[i].call(o,e);
queue.calling = null;
if (!queue.removeListeners)
return;
if (queue.length == queue.removeListeners.length) {
this._detach(o, e.type);
return;
}
queue.removeListeners = queue.removeListeners.sort(function(a,b){return a-b});
var i = queue.removeListeners.length;
while (i--)
queue.splice(queue.removeListeners[i], 1);
if (queue.length == 0)
this._detach(o, e.type);
else
queue.removeListeners = null;
},
_detach: function (o, type) {
o.detachEvent("on" + type, o._events._callback);
delete o._events[type];
},
_fnIndex: function (o, type, fn) {
var queue = o._events[type];
if (queue)
for (var i = 0, l = queue.length; i < l; i++)
if (queue[i] == fn)
return i;
return -1;
},
_cleanup: function () {
for (var m, i = 0; m = Event._mem[i]; i++)
if (m[1] != "unload" || m[2] == Event._cleanup)
removeEvent(m[0], m[1], m[2]);
}
};
addEvent(window, "unload", Event._cleanup);
} @*/
This example is the same site, that had to be used at the addEvent() recoding contest. I’ve modified it slightly, because the original noBubble function isn’t necessary anymore thanks to stopPropagation().
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>addEvent() recoding contest entry</title>
<script type="text/javascript" src="addEvent.js"></script>
<script type="text/javascript">//<!--
/*
Original idea by John Resig
Tweaked by Scott Andrew LePera, Dean Edwards and Peter-Paul Koch
*/
var menu = {
init: function () {
var menu = document.getElementById("navigation");
addEvent(menu, "mouseover", this);
addEvent(menu, "mouseover", showBorder);
var items = menu.getElementsByTagName("li");
for (var i = 0; i < items.length; i++) {
addEvent(items[i], "mouseout", this);
addEvent(items[i], "mouseout", hideBorder);
}
},
handleEvent: function (event) {
switch (event.type) {
case "load":
this.init();
break;
case "mouseover":
if (event.target.nodeName.toLowerCase() == "li")
event.target.className += " over";
break;
case "mouseout":
if (event.relatedTarget.parentNode != event.target &&
event.relatedTarget.parentNode.parentNode != event.target &&
event.relatedTarget.parentNode.parentNode.parentNode != event.target)
event.target.className = event.target.className.replace(/over/g, "");
event.stopPropagation();
break;
}
}
};
addEvent(window, "load", menu);
function showBorder(event) {
if (event.target.nodeName.toLowerCase() == "li")
event.target.className += " current";
}
function hideBorder(event) {
if (event.relatedTarget.parentNode != event.target &&
event.relatedTarget.parentNode.parentNode != event.target &&
event.relatedTarget.parentNode.parentNode.parentNode != event.target)
this.className = this.className.replace(/current/g,'');
event.stopPropagation();
}
function removeBorders() {
var menu = document.getElementById("navigation");
removeEvent(menu, "mouseover", showBorder);
removeEvent(menu, "mouseout", hideBorder);
}
//--></script>
<style type="text/css">
<!--
ul#navigation {
width: 150px;
}
li {
border: 1px solid #ffffff;
}
li ul {
display: none;
}
.over ul {
display: block;
}
.current {
border-color: #cc0000;
}
-->
</style>
</head>
<body>
<h1><code>addEvent()</code> recoding contest entry</h1>
<ul id="navigation">
<li><a href="#">Item 1</a>
<ul>
<li><a href="#">Item 1.1</a></li>
<li><a href="#">Item 1.2</a></li>
<li><a href="#">Item 1.3</a></li>
</ul>
</li>
<li><a href="#">Item 2</a>
<ul>
<li><a href="#">Item 2.1</a></li>
<li><a href="#">Item 2.2</a></li>
<li><a href="#">Item 2.3</a></li>
</ul>
</li>
<li><a href="#">Item 3</a>
<ul>
<li><a href="#">Item 3.1</a></li>
<li><a href="#">Item 3.2</a></li>
<li><a href="#">Item 3.3</a></li>
</ul>
</li>
</ul>
<p><a href="#" onclick="removeBorders()">Remove border effect</a>.</p>
</body>
</html>
In order to debug how would you recommend intercepting all events.I’m not sure what you want to do, but I guess _callListeners would be the point to intercept events.
Would we add a generic pre event before the other events? or is there a better way?
is there a way to be notified of new versionsI’ll see if I can add a microsummary
and to see a version log?Currently not, but maybe I’ll introduce one later on.
e|, the event object, and |o|, the object the listeners were registered on. That’s all you need, right? You can just do something like |log += 'fired '+e.type+' for #'+o.id+'\n';| (presuming that all the objects have an id). |log| could be |document.getElementById('debug-output').firstChild.nodeValue| or so.
Are you handling the IE returnValue as returned by each listeners ?I’m using returnValue internally for emulating event.preventDefault(). returnValue isn’t part of the DOM spec and won’t work across browsers when set inside of a listener.
The first listener returning „false“ should also break the chain execution.Should it? I don’t think the DOM spec says so.
This is needed where „Event Managers“ build chains of events to maintain cross-browser compatibility and to be able to implement event ordering (missing in IE).Not sure what you mean. addEvent takes care of the order of events. Therefore, multiple event listeners attached to the same target can just share custom code if they want to communicate with each other.
The standard „addEventListener“ DOM method in the remaining browsers already handle these requirements correctly.It’s a bit odd to say “correctly” if it’s not spec’ed anywhere. If this remains the case, I tend to not implement it.
Comments are closed.