View Full Version : DOM Event Scripting, (undocumented) Tip #1: DOM2 Events and Internet Explorer
Forgive my conciseness, but in Win/IE, if you attach a function to an event via attachEvent(), window.event is passed in as an argument to it. MSDN does not document this behavior, but it is incredibly useful:
function myListener(evt) {
window.status = [evt.clientX, evt.clientY];
}
if ("addEventListener" in document) {
document.addEventListener("mousemove", myListener, false);
}
else if ("attachEvent" in document) {
document.attachEvent("onmousemove", myListener);
}
And that will work crossbrowser. Notice no checking for the event argument and reassigning the window.event object to the identifier.
Further more, if you're only attaching events on a particular node, then you can extend it like so:
if (!("addEventListener" in document) && "attachEvent" in document) {
document.addEventListener = function(evt, func, capture) {
document.attachEvent("on" + evt, func);
}
}
// likewise for removeEventListener, if necessary
And you simply use document.addEventListener from now on, instead of repetitive code-branching:
document.addEventListener("mousemove", myListener, false);
Enjoy.
Ok, no one seems to be interested. Well, I'll one up the previous post.
listener.htc:
<!-- Licensed under Creative Commons Attribution 2.0
Original code by Jason Davis, www.jasonkarldavis.com -->
<PUBLIC:COMPONENT lightWeight="true">
<PUBLIC:METHOD NAME="addEventListener" />
<PUBLIC:METHOD NAME="removeEventListener" />
<SCRIPT LANGUAGE="JScript">
function addEventListener(type, listener, useCapture) {
attachEvent("on" + type, listener);
}
function removeEventListener(type, listener, useCapture) {
detachEvent("on" + type, listener);
}
</SCRIPT>
</PUBLIC:COMPONENT>
listener.js
/* Licensed under Creative Commons Attribution 2.0
Original code by Jason Davis, www.jasonkarldavis.com */
window.addEventListener = document.addEventListener =
function(type, listener, useCapture) {
this.attachEvent("on" + type, listener);
}
window.removeEventListener = document.removeEventListener =
function(type, listener, useCapture) {
this.detachEvent("on" + type, listener);
}
listener.css
/* Licensed under Creative Commons Attribution 2.0
Original code by Jason Davis, www.jasonkarldavis.com */
* { behavior: url(listener.htc) }
And finally, include this in the <head> of your pages:
<!--[if gte IE 5]>
<link rel="StyleSheet" type="text/css" href="listener.css" />
<script type="text/javascript" src="listener.js"></script>
<![endif]-->
Congratulations, you just made Internet Explorer support two key methods of DOM2 events on every element, document, and window. Code-forking eliminated.
enumerator
05-23-2005, 10:54 AM
I think that's cool and stuff... even licensed, wow! :D
MikeFoster
05-23-2005, 04:09 PM
Ok, no one seems to be interested.
Oh, that's not true at all! ;) Great stuff!
I'll make an observation...
attachEvent is similar to addEventListener in that it allows you to add more than one listener for the same event to the same element object. However, attachEvent has a major drawback - in your listener 'this' will not reference the correct instance, in fact it will point to window or document.
So my advice is...
Always use the DOM0 Events interface unless you specifically need to add more than one listener for the same event to the same element.
brothercake
05-23-2005, 05:04 PM
in your listener 'this' will not reference the correct instance, in fact it will point to window or document.
So my advice is...
Always use the DOM0 Events interface unless you specifically need to add more than one listener for the same event to the same element.
Aha well .. your observation is quite correct, but the wider reasoning is more complex than it appears, as I recently discovered myself: http://www.codingforums.com/showthread.php?t=53789
Essentially, you shouldn't rely on "this" as a self-reference in addEventListener either, so for all encapsulated event handling, references should be obtained by upwards iteration from target|srcElement.
btw jkd - why do you pass the useCapture argument through, when attachEvent can't use it? Wouldn't it be marginally more efficient to lose that argument, or did you foresee a future extension for it?
MikeFoster
05-23-2005, 05:55 PM
Ah, thanks for pointing that out. These issues are why I advise to only use the DOM0 event interface - except in a few cases.
brothercake, in the thread you linked to I notice liorean said this:
For W3C DOM events, the Event.prototype.target property should contain a reference to the element which originally dispatched the event, which means that it should be the same as this for a DOM0 event.
Perhaps I'm misunderstanding something by his reference to Event.prototype. I thought event.currentTarget was equivalent to this. There are situations where event.target is not equivalent to this for example during the BUBBLING_PHASE. My understanding was that event.target is only equivalent to this during the AT_TARGET phase. Perhaps I am misunderstanding liorean's statement?
brothercake
05-23-2005, 06:10 PM
No you're right, and that just makes it twice as complicated :eek:
As you know, writing libraries is a whole different discipline from writing scripts for yourself, and for such scripting I generally go by the opposite principle - never use the DOM 0 event interface for event handling on pre-existing HTML elements - only ever use it on elements you created yourself in the script - or you risk conflict with other scripting.
But this is a real problem - target may not be the target you want, depending on the event phase. Now I think about it, this exact problem has bugged me twice before, and I found two different solutions:
- I maintained a manual "bubble flag" so that I could track the flow of events cross-browser
- I just prevented event bubbling on all instances, so the issue never came up
But both of those are solutions of convenience - the first is an ugly hack, the second is not necessarily appropriate. What we really need is an IE-equivalent of currentTarget .... but of course we don't have that.
However IE does support the eventPhase property .. so that's probably the way in - using eventPhase discrimination at the point where the reference to "this" is derived ..
hmm ... need to think about this one :)
MikeFoster
05-23-2005, 06:47 PM
As you know, writing libraries is a whole different discipline from writing scripts for yourself, and for such scripting I generally go by the opposite principle - never use the DOM 0 event interface for event handling on pre-existing HTML elements - only ever use it on elements you created yourself in the script - or you risk conflict with other scripting.
Very good point - but that opens up another big can of worms similar to the one opened by the window.onload event, eh? ;)
However IE does support the eventPhase property ..
?!? are you sure?
hmm ... need to think about this one
Me too - this is a big can - it will take me a while to digest all the worms ;)
Oh, that's not true at all! ;) Great stuff!
I'll make an observation...
attachEvent is similar to addEventListener in that it allows you to add more than one listener for the same event to the same element object. However, attachEvent has a major drawback - in your listener 'this' will not reference the correct instance, in fact it will point to window or document.
So my advice is...
Always use the DOM0 Events interface unless you specifically need to add more than one listener for the same event to the same element.
If the reference to `this` is that important to you:
listener.htc
<!-- Licensed under Creative Commons Attribution 2.0
Original code by Jason Davis, www.jasonkarldavis.com -->
<PUBLIC:COMPONENT lightWeight="true">
<PUBLIC:METHOD NAME="addEventListener" />
<PUBLIC:METHOD NAME="removeEventListener" />
<SCRIPT LANGUAGE="JScript">
var listeners = new Object();
function addEventListener(type, listener, useCapture) {
if (!(type in listeners))
listeners[type] = new Object();
listeners[type][listener] = function(event) { listener.call(element, event) };
attachEvent("on" + type, listeners[type][listener]);
}
function removeEventListener(type, listener, useCapture) {
detachEvent("on" + type, listeners[type][listener]);
delete listeners[type][listener];
}
</SCRIPT>
</PUBLIC:COMPONENT>
:)
And brothercake, the reason for using the 3rd useCapture argument is consistency: document.addEventListener.arity == 3, so might as well keep it the same in IE.
MikeFoster
05-24-2005, 09:11 PM
Cool stuff, jkd!
I played around with similar ideas several years ago - implemented DOM2 Events interface in Javascript for IE4+ as well as NN4 :D, but I had my fun with those toys and now I don't play with them any more ;)
Cool stuff, jkd!
I played around with similar ideas several years ago - implemented DOM2 Events interface in Javascript for IE4+ as well as NN4 :D, but I had my fun with those toys and now I don't play with them any more ;)
Well, everybody and their grandma had their fun with this stuff during IE4/NS4 days. But the difference between now and then is that this is using native methods, providing an incredibly light-weight and next to zero-cost implementation (and certainly more of a bug-free experience), as opposed to reimplementing everything from scratch.
Or at least, that's how I justify the bit of time I spent writing this.
liorean
05-25-2005, 05:29 AM
Ah, thanks for pointing that out. These issues are why I advise to only use the DOM0 event interface - except in a few cases.
brothercake, in the thread you linked to I notice liorean said this:
Perhaps I'm misunderstanding something by his reference to Event.prototype. I thought event.currentTarget was equivalent to this. There are situations where event.target is not equivalent to this for example during the BUBBLING_PHASE. My understanding was that event.target is only equivalent to this during the AT_TARGET phase. Perhaps I am misunderstanding liorean's statement?By using Event.prototype I mean that all instances of the Event object have it, but not the Event object itself. This instance would be the first argument to the event listener.
In a correct implementation, Event.prototype.target is always the element the innermost listener is attached to in all phases, but Event.prototype.currentTarget is the element the current listener is attached to in bubble and capture phases. And the this keyword always points to the element the current listener is attached to, not the element the innermost listener is attached to. So yes, you're right about capturing and bubbling phases, and that Event.prototype.currentTarget points to the same as this in those cases.
But then of course, we have two problems I can see: Incorrect implementations (Safari 1.2, maybe later ones too) and implementations where Event.prototype.currentTarget is not present or plainly wrong (Different Opera versions exhibit both these properties. Haven't tested recent builds.) I also recall problems in some other mac browser but can't remember which. OW5 or MSN/OSX maybe?
Of course, the real problem is that using Element.prototype.attachEvent there is no way to get a reference to the element that Event.prototype.currentTarget or this would point to except through encapsulating that in the event registration function. And doing that you've suddenly opened that whole can of worms with iew leaking memory because elements contain references to functions that contain references to elements that... well you know.
DHTML Kitchen
05-26-2005, 09:27 PM
Mike - "event.currentTarget was equivalent to this"
Mike is right!
Now, If someone is using a script with this faux addEventListener, and there is another script that has:
if(document.addEventListener) {
document.addEventListener("mousedown", documentMouseDown, true);
}
documentMouseDown = function(e) {
alert(e.pageX);
alert(e.currentTarget);
alert(this.nodeType == this.DOCUMENT_NODE);
if(e.target.nodeType == this.TEXT_NODE) {
alert("you clicked a text node.");
}
}
javascript:alert(document.DOCUMENT_NODE); // nothing in IE.
Faking addEventListener on node for IE is a bad approach.
Unless you're going to support the WHOLE dom event model. (I'll leave that up to Dean Edwards).
Agreed. However, for internal DHTML development (where you write everything yourself), this saves me from code-branching.
MikeFoster
05-31-2005, 04:59 PM
Sorry I dropped out of the conversation. Was out of town for a bit - now trying to catch up ;)
There's some good stuff in this thread!
WhiteShark
10-30-2006, 09:50 PM
I'm having a problem right now where
var oNode = Event.target; //for FireFox
and
var oNode = Event.srcElement; //for IE
are not producing the same behavior. I've seen a few places say not to use "this" but when I replace my Firefox line with:
var oNode = this;
Everything seems to work. The larger problem is that prototype.js uses the Event.target as an equivalent so when I try to use that library I do not get the DOM behavior I need.
Question: Does anyone know the "right" way to use Event.target so that I get all of the DOM information?
WhiteShark
10-31-2006, 04:42 PM
I don't know if anyone besides me is still reading this thread but I fixed my problem by creating a function that returns the node I actually want in botth browsers. Does anyone see anything glaringly wrong with this code?
function getEventOrigin (event)
{
return event.currentTarget || event.srcElement;
}
More importantly is this really returning the same thing in both browsers or does it just happen to work?
vBulletin® v3.8.2, Copyright ©2000-2010, Jelsoft Enterprises Ltd.