Reputation: 191
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
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
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
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