Mit addEvent() und removeEvent() lassen sich Event-Listener browserübergreifend und einheitlich verwalten.
Im Unterschied zu vielen andern Implementierungen, macht dieses addEvent einiges mehr als nur Event-Listener zu registrieren. Normalerweise ist der Umgang mit Events wegen vieler Browser-Inkonsistenzen grausam, oder genauer gesagt: wegen der Unfähigkeit des Internet Explorer. Diese Punkte werden von addEvent für den IE umgesetzt:
event.stopPropagation(), event.preventDefault()
event.currentTarget, event.target, event.relatedTarget (MouseEvent), event.eventPhase
Event.AT_TARGET, Event.BUBBLING_PHASE
handleEvent)
this wird mit dem Zielobjekt referenzieren, auf das der Listener registriert wurde (statt mit window)
addEventListener hat addEvent keinen useCapture-Parameter. Meiner Meinung nach lässt sich capture im Internet Explorer nicht implementieren.
unload-Event angeblich den bfcache deaktiviert, habe ich die Säuberung nur für den IE implementiert.
/**
* 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);
} @*/
Bei dem Beispiel handelt es sich um die Seite, die beim addEvent() recoding contest zu verwenden war. Ich habe sie leicht modifiziert, denn die ursprüngliche noBubble-Funktion ist dank stopPropagation() nicht mehr nötig.
<!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.