0

I have written a javascript in which i am using a loop. I want to display and hide tables on mouseover and mouseout event on div tag. All divs are named as info1,info2,....info6 and table to be displayed are named as table12,table22,....table62 and table to be hide are named as table11,table21,...table61.... I have 7 tables out of which I am using 6 to use this function. So I wrote a code but whenever I point mouse on any div it changes the display property of 7th table. Below is my code. Please point me out my mistake.

<script type='text/javascript'>
    window.onload = function() {
        for (var index = 1; index<= 6; index++){
            document.getElementById("info" + index).onmouseover = function() {
                document.getElementById ("table" + index + "1").style.display = "none";         
                document.getElementById ("table" + index + "2").style.display = "block";
                return false;
            };
            document.getElementById("info" + index).onmouseout = function() {
                document.getElementById ("table"+ index +"1").style.display = "block";
                document.getElementById ("table"+ index +"2").style.display = "none";
                return false;
            };
        }
    };
</script>
Chris Martin
  • 30,334
  • 10
  • 78
  • 137
MukulAgr
  • 229
  • 2
  • 4
  • 13
  • 1
    Post your HTML and CSS as well. – Rahul Desai Dec 01 '14 at 05:44
  • Asked and answered about a dozen times. Also, please give your question a more descriptive name. –  Dec 01 '14 at 06:09
  • possible duplicate of [JavaScript closure inside loops – simple practical example](http://stackoverflow.com/questions/750486/javascript-closure-inside-loops-simple-practical-example) –  Dec 01 '14 at 06:10

3 Answers3

3

Aha, I guess its a closure issue. What you have to do is :

for (var index = 1; index <=6 ; ++index) {
    document.getElementById('info' + index).addEventListener('mouseover', (function(i) { return function() {
        document.getElementById('table' + i + "1").style.display = 'none';
    }}(index)));
}

And I would try to explain, In your code,

for(var index=1 ..) {
  document..onmouseover = function() { document.getElementById ("table" + index + "1")..
  ...
}

the index inside the function() is a free-variable, (defined in the parent scope and accessible in the child scope), that creates a closure, ability to access other variables local to the scope it was created in.

You are passing a reference to the variable instead of the value stored in the variable. The last and actual value of the i variable that gets iterated over equals in fact the integer

Fiddle for demonstration.

argentum47
  • 2,385
  • 1
  • 18
  • 20
1

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!

Jack
  • 9,151
  • 2
  • 32
  • 44
0

I was going to suggest to put the variable outside of the for loop function:

    window.onload = function() {
    var index = 1;
            for ( index<= 6; index++){
            document.getElementById("info" + index).onmouseover = function() {
                    document.getElementById ("table" + index + "1").style.display = "none";         
                    document.getElementById ("table" + index + "2").style.display = "block";
                    return false;
                };
                document.getElementById("info" + index).onmouseout = function() {

                    document.getElementById ("table"+ index +"1").style.display = "block";
                    document.getElementById ("table"+ index +"2").style.display = "none";
                    return false;
                };
            }
};
Frankenmint
  • 1,570
  • 3
  • 18
  • 33