I would like to create some objects dynamically and bind events to them (not important what events).
I'm passing a number to the event in order to distinguish those items. There is my code:
$('#add_btn').click(function() {
var cont = $('#buttons');
for(var i=0; i<5; i++) {
var new_btn = $('<input>').attr('type', 'button').val(i);
new_btn.click(function() {
alert(i);
});
cont.append(new_btn);
}
});
When I click on any from newly created buttons, displayed number is 5.
I think that i variable is passing by reference, but the question is: how to avoid passing variable by reference? More, even if I crate new variable before binding event (so the reference should point to another object, for example new_val = i.toString()), value is still same for all buttons (then its 4, understandable).
I know that I can attach new_btn.data() and read it in event, but I'm not sure if it won't be an overhead.
Link to jsfiddle: http://jsfiddle.net/Jner6/5/.
Since you are using a closure scoped variable in a loop, inside the loop you need to create a private closure.
$('#add_btn').click(function () {
var cont = $('#buttons');
for (var i = 0; i < 5; i++) {
(function (i) {
var new_btn = $('<input>').attr('type', 'button').val(i);
new_btn.click(function () {
alert(i);
});
cont.append(new_btn);
})(i)
}
});
Seems like you run into closures issue, try this:
(function( i ) {
new_btn.click(function() {
alert(i);
});
})( i );
This will create immediate invoked function that will closure your variable i so you can use it in future. For now you just overriding i variable in your for-loop so you will have always same value that will equal last for-loop iteration.
Don't make functions within a loop.
DEMO
var cont = $('#buttons');
$('#add_btn').click(function() {
for(var i=0; i<5; i++) {
$('<input>', {type:'button', value:i}).appendTo( cont );
}
});
cont.on('click', ':button', function() {
alert( this.value );
});
Related
What's wrong with this code? Reading this it seems that for each loop the jquery should attach a function with assigned values for each iteration. Instead it's attaching i = 2 to every object. Why is it doing that and how can I get it to attach the expected values (e.g., 0, 1, ...)?
//data.length is 2.
for (i=0; i<data.length; i++) {
// Attach the click function
linkId = 'a#' + pk;
$(linkId).click(function(e) {
e.preventDefault();
console.log(i, pk, data);
});
};
console.log -- each link has the same parameters
2 "52fef25e391a56206f03be6e" [object Array]
You're assuming that a block creates a new variable scope. It doesn't in JavaScript. Only a function execution does.
If you use $.each() instead, the callback you give it will be invoked for each iteration, and so you'll have a new scope for every one.
$.each(data, function(i,item) {
// ^---^---function parameters are local to this scope
// v--declare a variable local to this scope
var linkId = 'a#' + pk;
// v--the function made in this scope can access the local vars
$(linkId).click(function(e) {
e.preventDefault();
console.log(i, pk, data, linkId, data[i]);
});
});
console will be displayed only when you click the $(linkId) but at the time is is already equal to data.length (aka 2 in your case) so it will always display 2
Try to isolate the variable's scope. Declare a var j = i; inside the loop context so it enters the click-callback's scope.
So in order to create a separate scope for each iteration you can also use ordinary closure-functions, and pass current i into it:
for (var i = 0; i < data.length; i++) {
(function(j) {
$('a#' + pk).click(function(e) {
e.preventDefault();
console.log(j, pk, data);
});
})(i);
};
I have the following code that adds an onmouseover event to a bullet onload
for (var i = 0; i <= 3; i++) {
document.getElementById('menu').getElementsByTagName('li')[i].onmouseover = function () { addBarOnHover(i); };
}
This is the function that it is calling. It is supposed to add a css class to the menu item as the mouse goes over it.
function addBarOnHover(node) {
document.getElementById('menu').getElementsByTagName('li')[node].className = "current_page_item"; }
When the function is called, I keep getting the error:
"document.getElementById("menu").getElementsByTagName("li")[node] is
undefined"
The thing that is stumping me is I added an alert(node) statement to the addBarOnHover function to see what the value of the parameter was. The alert said the value of the parameter being passed was 4. I'm not sure how this could happen with the loop I have set up.
Any help would be much appreciated.
This is a common problem when you close over an iteration variable. Wrap the for body in an extra method to capture the value of the iteration variable:
for (var i = 0; i <= 3; i++) {
(function(i){ //here
document.getElementById('menu').getElementsByTagName('li')[i].onmouseover = function () { addBarOnHover(i); };
})(i); //here
}
an anonymous function is created each time the loop is entered, and it is passed the current value of the iteration variable. i inside the anonymous function refers to the argument of this function, rather than the i in the outer scope.
You could also rename the inner variable for clarity:
for(var i=0; i<=3; i++){
(function(ii){
//use ii as i
})(i)
}
Without capturing the iteration variable, the value of i when it is finally used in the anonymous handler has been already changed to 4. There's only one i in the outer scope, shared between all instances of the handler. If you capture the value by an anonymous function, then the argument to that function is used instead.
i is being passed as a reference (not by value), so once the onmouseover callback is called, the value of i has already become 4.
You'll have to create your callback function using another function:
var menu = document.getElementById('menu');
var items = menu.getElementsByTagName('li');
for (var i = 0; i <= 3; i++) {
items[i].onmouseover = (function(i) {
return function() {
addBarOnHover(i);
};
})(i);
}
You could make it a little more readable by making a helper function:
var createCallback = function(i) {
return function() {
addBarOnHover(i);
};
};
for (var i = 0; i <= 3; i++) {
items[i].onmouseover = createCallback(i);
}
I insert elements to the DOM, after that I want to bind a click function to these elements.
This works, but for some reason the links that were created all return the same value, which
is the highest value 'px_amount' has after looping. Very strange :) The first console.log();
does return the right value, and I can see it increment after each iteration. I added a simple console.log() to the click function, for sake of simplicity.
for(var i=1; i<=bullet_amount; i++)
{
$('<a id="bullet-'+i+'">'+i+' </a>').appendTo('#bullet-nav');
px_amount = (i-1)*ratio*3450;
console.log(px_amount);
$("#bullet-"+i).live('click', function() {
console.log(px_amount);
});
}
This is a very frequent problem : the variable i is the same for all callbacks, that is the one of the enclosing scope.
A common solution is this :
for(var i=1; i<=bullet_amount; i++)
{
(function(i){
$('<a id="bullet-'+i+'">'+i+' </a>').appendTo('#bullet-nav');
px_amount = (i-1)*ratio*3450;
console.log(px_amount);
$("#bullet-"+i).live('click', function() {
console.log(px_amount);
});
})(i);
}
You should use a closure to capture the value of i reather than the reference:
for(var i=1; i<=bullet_amount; i++) {
(function(iVal) {
$('<a id="bullet-'+iVal+'">'+iVal+' </a>').appendTo('#bullet-nav');
var px_amount = (iVal-1)*ratio*3450;
console.log(px_amount);
$("#bullet-"+iVal).live('click', function() {
console.log(px_amount);
});
})(i);
}
i have the following code :
$(document).ready(function () {
for (i = 1; i <= number_of_banners; i++) {
var selector = "#link_" + i;
$(selector).click(function () {
alert(i);
});
}
});
but the alert() can't get the "i" variable from the for loop.
How can I use the i variable of for loop inside the .click function ?
you can use this code :
$(document).ready(function () {
for (var i = 1; i <= number_of_banners; i++) {
var selector = "#link_" + i;
$(selector).on('click', {id: i}, function (e) {
alert(e.data.id);
});
}
});
you should use on method and use click argument in it instead of using onclick method
Using jQuery .on(event, data, handler) you can do it easily.
$(document).ready(function () {
for (var i = 1; i <= number_of_banners; i++) {
var selector = "#link_" + i;
$(selector).on('click', {id: i}, function (e) {
alert(e.data.id);
});
}
});
Working sample
Might this happen be due the fact of the JavaScript hoisting JavaScript Scoping mechanism??
For instance:
example of wrong loop variable binding
doesn't work as JavaScript uses function scope rather than block scope as we're usually accustomed from other languages like Java and C#. In order to make it work, one has to create a new scope by explicitly creating a new anonymous function which then binds the according variable:
example of correct loop variable binding
I know this doesn't directly answer the question above, but might still be useful though for others stumbling over this question.
I think you can pass it as a parameter into the anonymous function as long as the function is declared within a scope that can access i.
function (i) {
alert(i);
}
a quick solution would be to use the eventData and store the current i in that:
$(document).ready(function () {
for (var i = 1; i <= number_of_banners; i++) {
var selector = "#link_" + i;
$(selector).bind('click', i, function (e) {
alert(e.data);
});
}
});
if you are using jquery 1.7+ then use on instead of bind
I know this kind of question gets asked alot, but I still haven't been able to find a way to make this work correctly.
The code:
function doStuff () {
for (var i = 0; i< elementsList.length; i++) {
elementsList[i].previousSibling.lastChild.addEventListener("click", function(){
toggle(elementsList[i])}, false);
}
} // ends function
function toggle (element) {
alert (element);
}
The problem is in passing variables to the toggle function. It works with the this keyword (but that sends a reference to the clicked item, which in this case is useless), but not with elementsList[i] which alerts as undefined in Firefox.
As I understood it, using anonymous functions to call a function is enough to deal with closure problems, so what have I missed?
Try:
function startOfFunction() {
for (var i = 0; i< elementsList.length; i++) {
elementsList[i].previousSibling.lastChild.addEventListener(
"click",
(function(el){return function(){toggle(el);};})(elementsList[i]),
false
);
}
} // ends function
function toggle (element) {
alert (element);
}
The Problem is, that you want to use the var i! i is available in the onClick Event, (since closure and stuff). Since you have a loop, i is counted up. Now, if you click on any of the elements, i will always be elementsList.length (since all event functions access the same i )!
using the solution of Matt will work.
As an explanation: the anonymous function you use in the for loop references the variable "i" to get the element to toggle. As anonymous functions use the "live" value of the variable, when somebody clicks the element, "i" will always be elementsList.length+1.
The code example from Matt solves this by sticking the i into another function in which it is "fixated". This always holds true:
If you iterate over elements attaching events, do not use simple anonymous functions as they screw up, but rather create a new function for each element. The more readable version of Matts answer would be:
function iterate () {
for (var i = 0; i < list.length; i++) {
// In here, i changes, so list[i] changes all the time, too. Pass it on!
list[i].addEventListener(createEventFunction(list[i]);
}
}
function createEventFunction (item) {
// In here, item is fixed as it is passed as a function parameter.
return function (event) {
alert(item);
};
}
Try:
function doStuff () {
for (var i = 0; i< elementsList.length; i++) {
(function(x) {
elementsList[x].previousSibling.lastChild.addEventListener("click", function(){
toggle(elementsList[x])}, false);
})(i);
}
} // ends function
I think it might be an issue with passing elementsList[i] around, so the above code has a closure which should help.