Javascript/jQuery attach different function args to .click() method - javascript

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);
};

Related

setInterval calling function with an undefined parameter

This question has been flagged as already answered with a link provided above. However, I already read that answer and it only answered how to use setInterval in a for loop. There were no functions being called with parameters passed to them in that solution, and that is my situation, so I couldn't use it to fix my situation.
I'm fairly new to programming, so I'll try to describe as best as I can. In setInterval, I am passing a parameter to the function toggleClusters which setInterval calls. The debugger shows the parameter as being correct. It is a reference to an array position that holds an object literal that contains map marker objects. I seem to be misunderstanding something about what values stay around and what do not when using setInterval, because the debugger shows the correct object literal being passed as an arg, but when the function is called, the debugger shows the obj that is supposed to be passed as undefined. Is it that this passed value no longer exists when the function is called?
function setClusterAnimations() {
for (var i = 0; i < clusters.length; i++) {
//intervalNames stores handle references for stopping any setInterval instances created
intervalNames.push(setInterval(function () {
//clusters[i] will hold an object literal containing marker objects
toggleClusters(clusters[i]);
}, 1000));
}
}
//cObj is coming back as undefined in debugger and bombing
function toggleClusters(cObj) {
var propCount = Object.keys(cObj).length;
for (var prop in cObj){
if (prop.getZIndex() < 200 || prop.getZIndex() == 200 + propCount) {
prop.setZIndex(200);
}
else {
prop.setZindex(prop.getZIndex() + 1)
}
}
}
This is typically the issue with such asynchronous calls as with setInterval(). You can solve this in different ways, one of which is using bind():
for (var i = 0; i < clusters.length; i++) {
//intervalNames stores handle references for stopping any setInterval instances created
intervalNames.push(setInterval(function (i) {
//clusters[i] will hold an object literal containing marker objects
toggleClusters(clusters[i]);
}.bind(null, i), 1000));
}
The toggleClusters(clusters[i]) statement will only be executed when your loop has finished, at which time i will be beyond the correct range (it will be clusters.length). With bind(), and mostly with the function parameter i, you create a separate variable in the scope of the call back function, which gets its value defined at the moment you execute bind(). That i is independent from the original i, and retains the value you have given it via bind().
that is because your "i" variable is not captured in the function passed as an argument to setInverval.
Therefore , when this function is invoked, i is always equal to clusters.length.
consider the differences between the two following pieces of code:
var arr = [1, 2, 3];
var broken = function() {
for(var i = 0; i < arr.length; ++i) {
setInterval(function() {
console.log("broken: " + arr[i]);
}, 1000);
// logs broken: undefined
}
};
var fixed = function() {
for(var i = 0; i < arr.length; ++i) {
setInterval((function(k) {
return function() {
console.log("fixed: " + arr[k]);
}
}(i)), 1000); // i is captured here
}
};

Javascript closure access with callbacks inside loop

what shall I do to make the last row of code return a value?
$scope.runActionwithObjects = function() {
for (var i = 0; i < $scope.Objects.length; i++) {
console.log($scope.Objects[i]); //$scope is accessible
$http.get($scope.Objects[i]["Commit"]).success(function (data) {
console.log($scope.Objects[i]);//return undefined
The problem is due to asynchrony of ajax requests.
When the success callback is executed, your loop has already finished and the i variable is already equal to $scope.Objects.length.
Try forEach. This function will create different closures for items in the array.
$scope.Objects.forEach(function(currentObject){
console.log(currentObject); //$scope is accessible
$http.get(currentObject["Commit"]).success(function (data) {
console.log(currentObject);
});
});
The reason your $scope.Objects[i] is undefined because the variable i is always = $scope.Objects.lenth + 1, for example you got 5 elements, the i will be 6, because the at the time of callback, it already got the last value.
One solution is to bind needed object to that method, so we can access it via this(we can not reference directly by closure to ref variable, because it's still stored the last item), for example:
for (var i = 0; i < $scope.Objects.length; i++) {
var ref = $scope.Objects[i];
// console.log($scope.Objects[i]); //$scope is accessible
var successCallback = (function (data) {
console.log(this);//return the ref
}).bind(ref);
$http.get('').success(successCallback);
}
}

jQuery - passing variable to event by data

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 );
});

Defining anonymous functions in a loop including the looping variable?

I know that this code doesn't work and I also know why.
However, I do not know how to fix it:
JavaScript:
var $ = function(id) { return document.getElementById(id); };
document.addEventListener('DOMContentLoaded', function()
{
for(var i = 1; i <= 3; i++)
{
$('a' + i).addEventListener('click', function()
{
console.log(i);
});
}
});
HTML:
1
2
3
I want it to print the number of the link you clicked, not just "4".
I will prefer to avoid using the attributes of the node (id or content), but rather fix the loop.
Wrap the loop block in its own anonymous function:
document.addEventListener('DOMContentLoaded', function()
{
for(var i = 1; i <= 3; i++)
{
(function(i) {
$('a' + i).addEventListener('click', function() {
console.log(i);
})
})(i);
}
}
This creates a new instance of i that's local to the inner function on each invocation/iteration. Without this local copy, each function passed to addEventListener (on each iteration) closes over a reference to the same variable, whose value is equal to 4 by the time any of those callbacks execute.
The problem is that the inner function is creating a closure over i. This means, essentially, that the function isn't just remembering the value of i when you set the handler, but rather the variable i itself; it's keeping a live reference to i.
You have to break the closure by passing i to a function, since that will cause a copy of i to be made.
A common way to do this is with an anonymous function that gets immediately executed.
for(var i = 1; i <= 3; i++)
{
$('a' + i).addEventListener('click', (function(localI)
{
return function() { console.log(localI); };
})(i);
}
Since you're already using jQuery, I'll mention that jQuery provides a data function that can be used to simplify code like this:
for(var i = 1; i <= 3; i++)
{
$('a' + i).data("i", i).click(function()
{
console.log($(this).data("i"));
});
}
Here, instead of breaking the closure by passing i to an anonymous function, you're breaking it by passing i into jQuery's data function.
The closure captures a reference to the variable, not a copy, which is why they all result in the last value of the 'i'.
If you want to capture a copy then you will need to wrap it in yet another function.

jQuery click-function passing parameters

I would like to assign the jQuery click-function for all elements in an array. But additionally, I need to access the array from within the click-function. The source will hopefully make it clearer:
for (var i=0; i<mybox.myarray.length; i++) {
mybox.myarray[i].jqelem.click(function(event, mybox) {
event.preventDefault();
doStuffWithParameter(mybox);
});
}
// mybox is a JavaScript object (not jQuery-element!), myarray is an array, jqelem is a jQueryelement ala $("div.myclass");
The problem seems to be with function(event, mybox), apparently that doesn't work, i.e. mybox is unknown within the function. I think I 'kind of' understand why it cannot work this way, but how can this be achieved?
PS: I'm basically just doing it to save me from typing it manually for all array-elements.
Just remove the (useless) second callback function parameter named mybox.
If mybox is in scope in the outer block, it'll be in scope in the inner callback function too!
Should you need to know the appropriate value of i in the callback then you can do event registration-time binding:
for (var i=0; i<mybox.myarray.length; i++) {
mybox.myarray[i].jqelem.click({i: i}, function(event) {
// use "event.data.i" to access i's value
var my_i = event.data.i;
});
}
The map {i : i} corresponds with the eventData parameter in the jQuery .click() documentation.
When your click handler gets called, the first argument is the event data. jQuery doesn't pass in a second argument.
Update: Using closure to get to mybox object (notice I removed the 2nd argument)
for (var i=0; i<mybox.myarray.length; i++) {
mybox.myarray[i].jqelem.click(function(event) {
event.preventDefault();
// here's the trick to get the correct i
(function(item) {
return function() {
doStuffWithParameter(mybox.myarray[item]);
};
})(i);
// you can access any object from parent scope here
// (except i, to get to right i, you need another trick),
// effectively creating a closure
// e.g. doOtherStuff(myarray)
});
}
Read more on closures here: http://jibbering.com/faq/notes/closures/
and here: How do JavaScript closures work?
You can take help of jquery data attributes
for (var i=0; i<mybox.myarray.length; i++) {
mybox.myarray[i].jqelem.data("arrayIndex", i).click(function(event) {
event.preventDefault();
doStuffWithParameter(mybox.myarray[$(this).data("arrayIndex")]);
});
}

Categories