As others have stated, this is a closure issue. A very simple explanation is that the variable index
is referenced by all the onmouseover
handlers. So, on the final loop iteration, index
is set, and then referenced by all the handlers.
The easiest way to address the issue is to wrap the handler within a self executing function. The reason for this is because of function scope
. By passing index
in each iteration as an argument, you limit the scope to that function execution. There are other answers here that address that, so I won't spend too much time on it.
(Here's an updated fiddle that you linked to in the comment, which works: http://jsfiddle.net/k83wuwz5/1/)
And the JS (for documentation)
for (var index = 1; index<= 5; index++){
document.getElementById("info" + index).onmouseover = (function(index) {
return function() {
document.getElementById ("table" + index + "1").style.display = "none";
document.getElementById ("table" + index + "2").style.display = "block";
}
})(index);
document.getElementById("info" + index).onmouseout = (function(index) {
return function() {
document.getElementById ("table"+ index +"1").style.display = "block";
document.getElementById ("table"+ index +"2").style.display = "none";
}
})(index);
}
Instead, let's talk about a few other ways of addressing this: delegation, plain ol' CSS (possibly), and a combination of both!
First delegation
(since this is a JS question!) - Let's say we have this mark up here (just guessing, as I haven't seen your actual mark up):
<div id='container'>
<!-- Info DIVS -->
<div data-idx="1">Info 1</div>
<div data-idx="2">Info 2</div>
<div data-idx="3">Info 3</div>
<!-- Info Tables -->
<table><tr><td>table 1</td></tr></table>
<table><tr><td>table 2</td></tr></table>
<table><tr><td>table 3</td></tr></table>
<div>
What if, rather than listening for a mouseover at the individual info div level, we said "hey, containing
div? When a mouseover
occurs on your child elements, do something!"
Let's see how that might look:
var container = document.getElementById('container');
var tables = document.getElementsByTagName('table');
var currentlyVisible = null;
container.addEventListener('mouseover', function(e) {
var idx;
if (e.target.hasAttribute('data-idx')) {
if (currentlyVisible) {
currentlyVisible.style.display = 'none';
}
idx = parseInt(e.target.getAttribute('data-idx'), 10) - 1
currentlyVisible = tables[idx];
currentlyVisible.style.display = 'block';
}
});
container.addEventListener('mouseout', function(e) {
if (currentlyVisible) {
currentlyVisible.style.display = 'none';
}
});
Here's a fiddle that demonstrates:
http://jsfiddle.net/z3xkf9fs/
When a mouse over occurs on each div, the parent container will be "notified" through event bubbling. Information such as the "target
" (or source element
) are passed with the event object, which we can reference. In the above code, our info div is fairly basic (it's just text nodes), but if you had containing elements (say a span), you would need to loop up (or climb the DOM tree) to find the enclosing info div to get the index (otherwise, the target would be the enclosed span/div/etc).
(This is why jQuery and others are so popular - using event delegation, all that is done for you by the library).
Once we know the info div, we then query the attribute and use it. There are other ways of handling this (you could determine how many info divs there are and determine the index that way - or use a class), but data-attributes
are what I chose.
Finally, on mouseout
, we check if the currentlyVisible
variable is set, and if so, we hide the element.
That's very simple delegation, but what about the CSS?
Well, if browser support isn't a major issue (older versions of IE, basically), CSS is the way to go for something like this! Again, let's go with the basic mark up we have but updated with some class
attributes:
<div id='container'>
CSS ONLY!
<br />
<!-- Info DIVS -->
<div class="info info1">Info 1</div>
<div class="info info2">Info 2</div>
<div class="info info3">Info 3</div>
<!-- Info Tables -->
<table class="table1"><tr><td>Table 1</td></tr></table>
<table class="table2"><tr><td>Table 2</td></tr></table>
<table class="table3"><tr><td>Table 3</td></tr></table>
<div>
And now our CSS:
table {
display: none;
}
div > .info1:hover ~ .table1,
div > .info2:hover ~ .table2,
div > .info3:hover ~ .table3 {
display: table;
}
div > div {
cursor: pointer;
display: inline-block;
margin: 10px;
}
And that's it! No JS required to show/hide the tables. Fair warning, the ~
selector isn't well supported. Again, as I mentioned above, if you can modify the mark up structure (so the tables are nested), the CSS is greatly simplified (to just a nested div selector). In addition, if your mark up is so jumbled, this may not even work.
Anyway, here's a fiddle that demonstrates: http://jsfiddle.net/z3xkf9fs/1/
Finally, you could use a combination of this (say your mark up prevents you from doing pure CSS).
Let's say our mark up looks like this:
<div id='container'>
<!-- Info DIVS -->
<div data-idx="1">Info 1</div>
<div data-idx="2">Info 2</div>
<div data-idx="3">Info 3</div>
<!-- Info Tables -->
<table class="t1"><tr><td>table 1</td></tr></table>
<table class="t2"><tr><td>table 2</td></tr></table>
<table class="t3"><tr><td>table 3</td></tr></table>
<div>
Our JS looks like this:
(function(){
var container = document.getElementById('container');
var tables = document.getElementsByTagName('table');
var currentlyVisible = null;
container.addEventListener('mouseover', function(e) {
var idx;
if (e.target.hasAttribute('data-idx')) {
container.setAttribute('class', 'idx' + e.target.getAttribute('data-idx'));
}
});
container.addEventListener('mouseout', function(e) {
container.removeAttribute('class');
});
}())
And our CSS looks like this:
table {
display: none;
}
div > div {
cursor: pointer;
display: inline-block;
margin: 10px;
}
.idx1 .t1,
.idx2 .t2,
.idx3 .t3 {
display: table;
}
Good luck!