Forum Moderators: open

Message Too Old, No Replies

Problem with mouse events.

IE bubbling issue...I think...

         

Dabrowski

12:01 am on Jun 5, 2007 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member



I have this:

<div><a>Something</a></div>

I assign an onMouseOver event to the DIV, but the even fires also then the A tag gets mouseovered.

I know IE bubbles up the DOM tree, does it bubble down too? Anyone know how to stop this?

I've also checked the tag name of the element being targeted, even though the event if firing on the A tag, it reports the DIV, so can't check for it that way.

[edited by: Dabrowski at 12:02 am (utc) on June 5, 2007]

Arno_Adams

1:17 pm on Jun 5, 2007 (gmt 0)

10+ Year Member



Hi,

Bubbling down doesn't exist. How do you assign the event to the element? Maybe there's another solution.

AA

Fotiman

5:16 pm on Jun 5, 2007 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month




<div><a>Something</a></div>

I assign an onMouseOver event to the DIV, but the even fires also then the A tag gets mouseovered.

I know IE bubbles up the DOM tree, does it bubble down too? Anyone know how to stop this?

Events bubble up and that is exactly why your event handler is firing.

Suppose your div and a elements look like this:


+- div -----------------+
¦.......................¦
¦..+- a -------------+..¦
¦..¦`````````````````¦..¦
¦..+-----------------+..¦
¦.......................¦
+-----------------------+

Bubbling up means that the event will "bubble up" through all of the ancestors of the element. So when you mouse over the <a> element, the onmouseover event will fire there, then it will bubble up to the parent <div> element, then it will bubble up to the parent of your div, and so on until it reaches the root.

In your case, you can could either:
1. add an event handler to the <a> element to "stop" the event from bubbling up, or
2. you could have the event handler on the div check to see what the target or srcElement of the event was to determine if it's the current node.

Here's an example script that does the latter method using the Yahoo UI Library's Event Utility:


<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset:utf-8">
<title></title>
<style type="text/css">
div { background-color: #ccc; padding: 2em;}
a { background-color: #c00; }
</style>
<script type="text/javascript" src="http://yui.yahooapis.com/2.2.2/build/yahoo/yahoo-min.js" ></script>
<script type="text/javascript" src="http://yui.yahooapis.com/2.2.2/build/event/event-min.js" ></script>
<script type="text/javascript">
YAHOO.util.Event.on(window, 'load', function () {
// Attach event handler to div
YAHOO.util.Event.on('container', 'mouseover', function (e) {
// Return true if 'container' is the target of the event
alert(YAHOO.util.Event.getTarget(e) == this);
});
});
</script>
</head>
<body>
<div id="container">
<a href="#">Hello</a>
</div>
</body>
</html>

[edited by: Fotiman at 5:18 pm (utc) on June 5, 2007]

Dabrowski

10:32 pm on Jun 5, 2007 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member



Bubbling up means that the event will "bubble up" through all of the ancestors of the element. So when you mouse over the <a> element, the onmouseover event will fire there, then it will bubble up to the parent <div> element, then it will bubble up to the parent of your div, and so on until it reaches the root.

This I understand. Your diagram of my nested <a> is correct. The problem is that I have assigned the events, using addEvent, to the DIV, and they are firing not only on the <div> but also on the <a>. The bubble has gone down, rather than up.

Is it a case of it will affect any element within the element also?

Fotiman

2:41 pm on Jun 6, 2007 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month




This I understand. Your diagram of my nested <a> is correct. The problem is that I have assigned the events, using addEvent, to the DIV, and they are firing not only on the <div> but also on the <a>. The bubble has gone down, rather than up.

No, you have your directions wrong. "Bubbling up" in this case means the event starts at the inner <a> element, and then works it's way "up" through the ancestors until it reaches the root node. It's not going down, it's going up. This is exactly how event bubbling works.


+- html
^¦+- body
^¦¦+- div
^¦¦¦+- a

The event starts at the <a>, then travels "up" to the <div>, then travels "up" to the <body>, then travels "up" to the <html>.


Is it a case of it will affect any element within the element also?

Yes. When an event occurs in any element, that event bubbles up through all of that element's ancestors. All you need to do is check whether the event was triggered by the node that you've attached your listener to. Like I said, this is simple if you use the YUI Library's Event Utility. Otherwise, you'll have to write your own cross-browser event handling code.

Dabrowski

3:14 pm on Jun 6, 2007 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member



No, you have your directions wrong. "Bubbling up" in this case means the event starts at the inner <a> element, and then works it's way "up" through the ancestors until it reaches the root node. It's not going down, it's going up. This is exactly how event bubbling works.

No, I don't have my directions wrong. I understand entirely that the event will travel right up to the <html>, but I'm assigning the event to the <div>, not the <a>. So to get to the <a> it has to travel down.

Now look, you made me use an italic.

See this test page and you'll see what I'm doing. Actually, this is vaguely reminiscent of another similar problem, could it be that the <a> tag fires an onMouseOver by default as it has hover states and such, and this event is triggering my <div>?

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>

<head>
<title>Test Page</title>
<style>

div {
margin-bottom: 1em;
width: 200px;
padding: 10px;
background: gainsboro;
border: 1px solid black;
}

#test {
text-align: center;
text-transform: uppercase;
font-weight: bold;
}

div span {
float: right;
}
</style>

<script>
function over( evt) {
var e = evt ¦¦ event;
var obj = e.target ¦¦ e.srcElement;

var div = document.getElementById( "event");
div.children[0].innerHTML = "<font color='red'><b>FIRED!</b></font>";
div.children[2].innerHTML = obj.tagName;
div.children[4].innerHTML = e.srcElement.tagName;

setTimeout( "clear();", 750);
}

function clear() {
var div = document.getElementById( "event");
div.children[0].innerHTML = "";
div.children[2].innerHTML = "";
div.children[4].innerHTML = "";
}

</script>

</head>

<body>

<div id='test' onMouseOver='over();'><a href='#'>Link</a></div>

<div id='event'>
<span></span>OnMouseOver:<br>
<span></span>Tag Name:<br>
<span></span>Src Tag Name:<br>
</div>
</body>

</html>

Bernard Marx

6:23 pm on Jun 6, 2007 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member



I think there are some crossed wires here.

Restating... In the case of the link, the mouseover event sourced at the link bubbles up to the div where it triggers the onmouseover handler attached to it. As you have shown, the only difference (compared to mouseover on the div) is that the evt object's src¦target property is a reference to the link element, not the div itself.

There are a few ways to get around the problem, but here's the most immediate (though probably not the most scaleable).

1) Change the event handler..

onMouseOver='over(event,this);'

2) Amend the function...

[pre]
function over( evt, triggerElm)
{
var e = evt ¦¦ event;
var obj = e.target ¦¦ e.srcElement;
if(obj!=triggerElm) return;
...
[/pre]

-------------------------
(a little aside)

Since IE creates global variables out of all element ids, it's generally best not to use ids that are Javascript or browser API globals, like "event".

[edited by: Bernard_Marx at 6:24 pm (utc) on June 6, 2007]

Fotiman

6:30 pm on Jun 6, 2007 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month





No, you have your directions wrong. "Bubbling up" in this case means the event starts at the inner <a> element, and then works it's way "up" through the ancestors until it reaches the root node. It's not going down, it's going up. This is exactly how event bubbling works.

No, I don't have my directions wrong. I understand entirely that the event will travel right up to the <html>, but I'm assigning the event to the <div>, not the <a>. So to get to the <a> it has to travel down.

No. You're assigning your EVENT LISTENER to the div (just to keep terminology correct).

Here's what happens:
When you mouse over the <div>, a mouseover event is fired at that node. The <div> has an event listener for mouseover that gets called, and then the event will be fired at the parent node of <div>, continuing up until it gets to the root (we're all clear on that).
Next, when you mouse over the <a>, a mouseover event is fired at that node. In this particular case, <a> does not have any event listeners. The EVENT then travels UP to the parent node of <a> (your div). Now, your <div> DOES have an event listener for the mouseover event, so that method is triggered. Next, the EVENT continues to move UP the DOM tree to the next parent node, and so on.


Now look, you made me use an italic.

LOL!

See this test page and you'll see what I'm doing. Actually, this is vaguely reminiscent of another similar problem, could it be that the <a> tag fires an onMouseOver by default as it has hover states and such, and this event is triggering my <div>?

YES! That's what I've been trying to tell you! (Now look, you made me use exclamation points). ;-)

Whenever you mouse over ANY element, whether you've assigned an event listener or not, there is still an event that gets fired at that element, which then works its way up the DOM tree. These events are always happening, it's just a matter of listening for them.

I've reworked your example slightly. I've attached the listener to the <body> and I made it create a list of the elements that it has received the event for. This is just to show that those events fire for all of those elements, it's just a matter of if you want to listen for them or not.


<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>Test Page</title>
<style>
div {
margin-bottom: 1em;
width: 200px;
padding: 10px;
background: gainsboro;
border: 1px solid black;
}
#test {
text-align: center;
text-transform: uppercase;
font-weight: bold;
}
div span {
float: right;
}
</style>
<script>
function over(evt) {
var e = evt ¦¦ event;
var obj = e.target ¦¦ e.srcElement;
var div = document.getElementById( "event");
div.children[0].innerHTML = "<font color='red'><b>FIRED!</b></font>";
div.children[2].innerHTML = obj.tagName;
div.children[4].innerHTML = e.srcElement.tagName;
var ul = document.getElementById('eventHistory');
var li = document.createElement('li');
li.innerHTML = obj.tagName;
ul.appendChild(li);
setTimeout( "clear();", 750);
}
function clear() {
var div = document.getElementById( "event");
div.children[0].innerHTML = "";
div.children[2].innerHTML = "";
div.children[4].innerHTML = "";
}
</script>
</head>
<body onMouseOver='over();'>
<div id='test' ><a href='#'>Link</a></div>
<div id='event'>
<span></span>
OnMouseOver:<br>
<span></span>Tag Name:<br>
<span></span>Src Tag Name:<br>
<ul id='eventHistory'>
</ul>
</div>
</body>
</html>

[edited by: Fotiman at 6:32 pm (utc) on June 6, 2007]

Dabrowski

7:34 pm on Jun 6, 2007 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member



Bernard:
Since IE creates global variables out of all element ids, it's generally best not to use ids that are Javascript or browser API globals, like "event".

Didn't know that, won't do it again. Thanks.

onMouseOver='over(event,this);'

This is not acceptable for me, I never release production code that has JavaScript in the HTML like this. I only did it here for the puspose of the example.

Fotiman:

No. You're assigning your EVENT LISTENER to the div (just to keep terminology correct).

Next, when you mouse over the <a>, a mouseover event is fired at that node. In this particular case, <a> does not have any event listeners

Suddenly everything becomes clear.

ok then, the question I need to ask is how do we tell if the event has bubbled, or actually ocurred on the element?

Without modifying the <a>, as we don't know what content may be inside our <div> in reality.

Also without Yahoo's help please! ;)

Fotiman

8:34 pm on Jun 6, 2007 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month




Suddenly everything becomes clear.

Yay! :-)


ok then, the question I need to ask is how do we tell if the event has bubbled, or actually ocurred on the element?

My original post with the YUI example did exactly that. It looked to see if the target of the event was the same as the node listening for the event. So it really depends on how you'll actually be assigning your event listener. If, in the scope of the listener, the 'this' variable refers to the node that you've assigned the event to, then just compare that against the obj. Otherwise, you could always use getElementById (though it's not exactly efficient to have to do that over and over each time the event listener is called). Below is an example.


Also without Yahoo's help please! ;)

<DarthVaderVoice>
If you only knew the power of the dark side!
</DarthVaderVoice>
This is SOOOOOOO easy with the YUI (see my original post). However, here's how you *could* do it without the YUI:


<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>Test Page</title>
<style>
div {
margin-bottom: 1em;
width: 200px;
padding: 10px;
background: gainsboro;
border: 1px solid black;
}
#test {
text-align: center;
text-transform: uppercase;
font-weight: bold;
}
div span {
float: right;
}
</style>
<script type="text/javascript">
function over( evt) {
var e = evt ¦¦ event;
var obj = e.target ¦¦ e.srcElement;
var divtest = document.getElementById('test');
if (divtest == obj) {
alert("This event came from me!");
}
else {
alert("I ain't fallin' for that! This ain't my event!");
}
var div = document.getElementById( "event");
div.children[0].innerHTML = "<font color='red'><b>FIRED!</b></font>";
div.children[2].innerHTML = obj.tagName;
div.children[4].innerHTML = e.srcElement.tagName;
var ul = document.getElementById('eventHistory');
var li = document.createElement('li');
li.innerHTML = obj.tagName;
ul.appendChild(li);
setTimeout( "clear();", 750);
}
function clear() {
var div = document.getElementById( "event");
div.children[0].innerHTML = "";
div.children[2].innerHTML = "";
div.children[4].innerHTML = "";
}
</script>
</head>
<body>
<div id='test' onMouseOver='over();'><a href='#'>Link</a></div>
<div id='event'>
<span></span>OnMouseOver:<br>
<span></span>Tag Name:<br>
<span></span>Src Tag Name:<br>
<ul id='eventHistory'>
</ul>
</div>
</body>
</html>

Bernard Marx

9:25 pm on Jun 6, 2007 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member




Dabrowski
I never release production code that has JavaScript in the HTML like this

In that case the solution is actually simpler. Using a basic

onload
for instance:

onload = function()
{
document.getElementById("test").onmouseover = over;
}

//
function over( evt)
{
var e = evt ¦¦ event;
var obj = e.target ¦¦ e.srcElement;
if(obj!=[blue]this[/blue]) return;
...

This is nice because the

[blue]this[/blue]
will refer to any element to which the event handler has been applied, but it will only work using the old-style event attachment (as above). IE is the problem here. When
attachEvent
is used, any resulting function is called in a global context, so the keyword,
this
, does not apply to the mouseovered element.

We still haven't stopped the extra mouseover when the mouse leaves the link. For most requirements this won't matter. If this extra mouseover really needs to be dealt with then it really is time for browser specific tricks and nonsense (

toElement
,
fromElement
etc - euugh!).

[edited by: Bernard_Marx at 9:27 pm (utc) on June 6, 2007]

Dabrowski

2:18 am on Jun 7, 2007 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member



Fotiman:
Suddenly everything becomes clear.


Yay! :-)

Yeah, sorry. Didn't realise the difference between an "event" and an "event listener". Got it now though. No probs.

This is SOOOOOOO easy with the YUI (see my original post).

But then I don't learn how to do it!

var divtest = document.getElementById('test');

I see how that works, but it seems a little inelegnt somehow. How does the YUI do it? Does it use the old event thing like Bernie suggested?

Bernie:
Thanks for that, but still not acceptable. I run all sorts of scripts and I can't risk a conflict in that instance.

I do need to use an additive function.

Can't I use something like:

element.onMouseOver = over +element.onMouseOver;

Would that still retain the 'this'? Would that even work?

Fotiman

3:28 pm on Jun 7, 2007 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month





This is SOOOOOOO easy with the YUI (see my original post).

But then I don't learn how to do it!


Fair point. You won't learn the really low level and browser specific stuff with the YUI, but the overall goals and acheivements remain the same whether you use the YUI or not. The YUI just provides a nice, cross browser way to do it, taking care of most of the leg work for you. For example, you still need to know that you have to listen for an event, and you still need to check to see if the node that you assign the listener to is the node where the event was originally fired from. The YUI just provides a much easier way to get that information.



var divtest = document.getElementById('test');

I see how that works, but it seems a little inelegnt somehow. How does the YUI do it? Does it use the old event thing like Bernie suggested?

The YUI code automatically adjusts the scope so that inside your event listener function, 'this' will refer to the node you attached the event to. For example, suppose I want to attach an event listener to the mouseover event on a div with the id 'myDiv':


YAHOO.util.Event.on( 'myDiv', 'mouseover', function(e){
// Inside this anonymous function, 'this' refers to
// The node that this is attached to (the node
// with id 'myDiv'), and the passed in parameter e
// is the event.
});

[edited by: Fotiman at 3:30 pm (utc) on June 7, 2007]

Dabrowski

6:20 pm on Jun 7, 2007 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member



ok, I think I know how they do it.

Quirksmode apparently had an 'addEvent recoding contest'. I'm sure you're familiar with the original, here's the winner, I should say by John Resig:

function addEvent( obj, type, fn ) {
if ( obj.attachEvent ) {
obj['e'+type+fn] = fn;
obj[type+fn] = function(){obj['e'+type+fn]( window.event );}
obj.attachEvent( 'on'+type, obj[type+fn] );
} else
obj.addEventListener( type, fn, false );
}

function removeEvent( obj, type, fn ) {
if ( obj.detachEvent ) {
obj.detachEvent( 'on'+type, obj[type+fn] );
obj[type+fn] = null;
} else
obj.removeEventListener( type, fn, false );
}

It first makes the function a child of the object, then creates another child function that calls it, and then assigns the listener. Now 'this' apparently refers to the object as that's where it's now being called from.

Fotiman

6:52 pm on Jun 7, 2007 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



Dabrowski, I've just sticky mailed you a post by Dustin Diaz titled "Forget addEvent, use Yahoo!'s Event Utility". It's a good read for anyone using addEvent. :)

Dabrowski

7:14 pm on Jun 7, 2007 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member



Just read it, it's a good article. And yes the YUI Events does lots of cool things, of which I can think of a use for 1 - the add events to multiple objects, which is easy enough to implement.

I just prefer doing things myself, that way I learn much more. And I also don't like the idea of letting someone else control the content of my, or customer's sites. If you import their JS, you import whatever else they decide to impose on you.

Just cos they are a large, trusted company doesn't mean they're not trying for global domination! We don't want another Microsoft now do we?

When Yahoo decide to make everyone pay for this, or make the library auto-download Yahoo toolbar, I won't have any sympathy for people who have to rewrite 50 sites! ;)

Anyways, thanks for explaining everything, I actually solved the problem ages ago by removing the <div>'s and setting the <a>'s to display block. The <div> was only used to size/colour the box anyway, but I'll know for next time.

Fotiman

10:08 pm on Jun 7, 2007 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month




And I also don't like the idea of letting someone else control the content of my, or customer's sites. If you import their JS, you import whatever else they decide to impose on you.

You don't need to import it from their servers. You can download the library and keep a local copy. That way, as long as you are comfortable with the version of the library that you download, you don't have to pull from the Yahoo servers every time (and therefore you can rest easy that they won't be delivering some evil code to your site).


When Yahoo decide to make everyone pay for this, or make the library auto-download Yahoo toolbar, I won't have any sympathy for people who have to rewrite 50 sites! ;)

That will never happen. They're very much about open source and progressing JavaScript development. Plus, as I said, you can already download a copy of the library and use it locally.

Dabrowski

11:47 am on Jun 8, 2007 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member



Nah, I still prefer to reinvent the wheel, at last then I can say it's my wheel.

Thanks again for your help on this one.

Fotiman

3:51 pm on Jun 8, 2007 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month




Nah, I still prefer to reinvent the wheel, at last then I can say it's my wheel.

Nothing wrong with that. :-) Once you feel comfortable that you know how to do it yourself, though, you may want to consider using one of the JavaScript frameworks (like YUI).


Thanks again for your help on this one.

Always a pleasure. :)