Joon Park
Joon Park

Reputation: 191

Getting value on hover produces "undefined"

This jsfiddle demonstrates a basic mockup of what I'm trying to achieve. After clicking the link, I should be able to hover over the list elements and text should be appearing on the page, but they don't. When I print the values of the strings that should be appearing, they are "undefined." Why is this so?

Here's the js below, but I recommend looking at the fiddle.

$('#link1').click(function () {
    var foolist = ["foo1", "foo2", "foo3"];

    for (var i = 0; i < foolist.length; i++) {
        var li = document.createElement('li');
        li.innerHTML = "This is a link.";
        $(li).hover(function () {
            console.log(foolist[i]);
            $('#p1').append(foolist[i]);
        },
        function () {});
        $('#ul1').append(li);
    }
});

Upvotes: 2

Views: 697

Answers (3)

Jack Guy
Jack Guy

Reputation: 8523

You've discovered the need for something called closures! Congratulations - not many people get to see how cool they are. This is pulled from a great MSDN article - I highly recommend reading the rest of it.

The problem is that i variable no longer has the value it did when you called .hover - if you log it you'll see that i===3. Why would that be? The function that you're passing to .hover is a closure - which means it consists of the function itself and a sort of "snapshot" of the .click function's scope. You create a closure with each iteration of the loop, but they all share the same "snapshot". By the time you try to get access to i via the click event, the loop has already completed.

So how can you solve it? More closures!

function showText(i) {
   $('#p1').append(foolist[i]);
}

function makeTextCallback(i) {
   return function() {
      showText(i);
   };
}

for (var i = 0; i < foolist.length; i++) {
   $(li).hover(makeTextCallback.call(this,i));
}

This is called a "function factory". You send i to makeTextCallback which captures i within the closure that it returns.

https://jsfiddle.net/aLofhaxp/28/

Upvotes: 2

Arvind Vishwakarma
Arvind Vishwakarma

Reputation: 563

Use .data() method of jQuery to attach data of any type to DOM elements.

$('#link1').click(function () {
    var foolist = ["foo1", "foo2", "foo3"];
    
    for (var i = 0; i < foolist.length; i++) {
        var $li = $("<li/>");
        $li.data("VAL", foolist[i]);
        $li.html("This is a link.");
        $($li).hover(function () {
            var value = $(this).data("VAL");
            console.log(value);
            $('#p1').append(value);
        },
        function () {});
        $('#ul1').append($li);
    }
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<a id="link1" href="javascript:;">Click to show list</a>

<ul id="ul1"></ul>
<p id="p1"></p>

Upvotes: 0

Yeldar Kurmangaliyev
Yeldar Kurmangaliyev

Reputation: 34189

The simple rule is to avoid any closures within loops.

Another good way is to define such things in data attributes:

$('#link1').click(function () {
    var foolist = ["foo1", "foo2", "foo3"];

    foolist.forEach(function(x) {
        $("<li/>")
            .data('list', x)
            .text("This is a link");
            .hover(fooListItemOnHover, fooListItemOnHoverOff)
            .appendTo('#ul1');
    }
});

function fooListItemOnHover() {
    var data = $(this).data('list');
    console.log(data);
    $('#p1').append(data);
}

function fooListItemOnHoverOff() {
}

This will produce elements with additional data-list attribute, which will store your custom data:

<li data-list='foo1'>This it a link</li>

Then, your script will read this data from it using jQuery data().

Upvotes: 1

Related Questions