Forum Moderators: open
<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]
<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]
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?
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.
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>
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] 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]
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.
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.
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>?
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]
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! ;)
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?
Also without Yahoo's help please! ;)
<!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>
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]
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?
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?
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]
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.
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.
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! ;)
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.