1

I have built a dropdown menu system, everything works when tested independently, the problem I have is in the code below. I use the jQuery ready function to build the menu bar from an external array (menubar[]). Here I am trying to get the mouseover event to call the dropdown() function, but using a different argument for each anchor tag.

So rolling over the first should call dropdown(0), the second dropdown(1) and so on.

$(document).ready(function () {
    for (i in menubar) {
        var declaration = '<a href="' + baseurl + '/' + menubar[i].url +
                          '" class="menutitle">' + menubar[i].name + '</a>';
        var a = $(declaration).mouseover(function () {
            dropdown(i);
        }).mouseout(function () {
            activeTimer = setTimeout("removedropdowns()", 100);
        });
        $("#menu").append(a);
    }
});

The code is calling dropdown(6); on each rollover. How can I pass the loop variable (i) into the mouseover function as a literal/static value!

I got this working fine in FF by using

.attr('onMouseOver','javascript:dropdown('+i+');')

but that wasn't firing for some versions of IE, so I switched to the jQuery mouseover, which fires, but I have the issue above :(

Šime Vidas
  • 182,163
  • 62
  • 281
  • 385
lynks
  • 13
  • 3

6 Answers6

2

Your actual problem is that each of your mouseover callbacks uses the same i you increase i all the way up to 6, the callbacks still point to the same i and therefore all use 6 as the value.

You need to make a copy of the value of i, you can do this by using an anonymous function.

$(document).ready(function () {
    // you should use (for(var i = 0, l = menubar.length; i < l; i++) here in case menubar is an array
    for (var i in menubar) {
        var declaration = '<a href="' + baseurl + '/' + menubar[i].url +
                          '" class="menutitle">' + menubar[i].name + '</a>';


        (function(e) { // e is a new local variable for each callback
            var a = $(declaration).mouseover(function () {
                dropdown(e);

            }).mouseout(function () {
                activeTimer = setTimeout(removedropdowns, 100); // don't use strings for setTimeout, since that calls eval
            });
            $("#menu").append(a);
        })(i); // pass in the value of i
    }
});
Ivo Wetzel
  • 46,459
  • 16
  • 98
  • 112
  • I don't think that an additional anonymous function is needed here. The mouseover handler already is an anonymous function, so we can capture the current value of `i` via that function: `mouseover(function(i) { dropdown(i); })` – Šime Vidas Dec 13 '10 at 21:43
  • It still uses the same `i` "reference" and therefore gets the current value of `i` which, after the loop is 6. – Ivo Wetzel Dec 13 '10 at 22:06
  • @Šime Vidas: it's not just using an additional anonymous function, it's that the anonymous function is *executed* (see the line with comment "pass in the value of i") – Nathan Hughes Dec 14 '10 at 13:47
  • @Nathan @Ivo Yes, I see. My approach does not work (because the event object is assigned to the first parameter of the handler function) – Šime Vidas Dec 14 '10 at 14:20
1
$(function() {
    $(menubar).each(function(i){
        $("#menu").append('<a href="' + baseurl + '/' + menubar[i].url + '" class="menutitle">' + menubar[i].name + '</a>');
    });

    $("#menu a").hover(
        function(){
            dropdown($(this).index());
        },
        function(){
            activeTimer = setTimeout("removedropdowns()", 100);
        }
    );
});
hunter
  • 62,308
  • 19
  • 113
  • 113
0

In JavaScript, if you don't declare your variable, it is defined globally. To fix this, add "var" in front of your i looping variable like this. UPDATE: As Sime noticed (see comment), you also need to pass the variable into the function, otherwise you form a closure on the i.

$(document).ready(function() {
    for(var i in menubar) {
        var declaration = '<a href="' + baseurl + '/' + menubar[i].url + '" class="menutitle">' + menubar[i].name + '</a>';
        var a = $(declaration).mouseover(function(i) { dropdown(i); }).mouseout(function() { activeTimer = setTimeout("removedropdowns()", 100); });
        $("#menu").append(a);
    }
});
James Kovacs
  • 11,549
  • 40
  • 44
  • I don't thinks that's enough. As @Alberto said, to capture the current value, you have to pass it as a parameter ... like `function(i) { dropdown(i); }` – Šime Vidas Dec 13 '10 at 21:27
  • @Sime - Good catch. I hadn't noticed that the variable was being captured by the anonymous function. Updated. – James Kovacs Dec 13 '10 at 21:38
  • Yea, for every step in the loop, a new mouseover handler function is created which captures the current value of `i` – Šime Vidas Dec 13 '10 at 21:44
0

First, don't use for..in but rather ordinary loop.

Second, I would just append the links first then apply the events later:

$(document).ready(function() {
    for (var i = 0; i < menubar.length; i++) {
        $("#menu").append('<a href="' + baseurl + '/' + menubar[i].url + '" class="menutitle">' + menubar[i].name + '</a>');
    }

    $("#menu a").each(function(index) {
        $(this).mouseover(function() { dropdown(index); }).mouseout(function() { activeTimer = setTimeout("removedropdowns()", 100); });
    });
});
Shadow The GPT Wizard
  • 66,030
  • 26
  • 140
  • 208
0

Have a look here and here.

To capture the current value of i, you need to pass it as a parameter to another function where it can be captured as a local variable:

Community
  • 1
  • 1
Alberto Zaccagni
  • 30,779
  • 11
  • 72
  • 106
0

Try using jQuery's each() function:

jQuery(function() {
  jQuery.each(menubar, function(index, element) {
    var declaration = '<a href="' + baseurl + '/' + element.url + '" class="menutitle">' + element.name + '</a>';
    var a = $(declaration).mouseover(function() { dropdown(index); }).mouseout(function() { activeTimer = setTimeout("removedropdowns()", 100); });
    $("#menu").append(a);
  });
});
ncuesta
  • 760
  • 4
  • 12