For loop unexpected increment value - javascript

So I understand why clicking on any button would pop up "Button [last value of i in loop] but why does i == 5 and not 4?
function myFn() {
var elems = document.getElementsByTagName('button');
var len = elems.length;
for (var i = 0; i < len; i++) {
elems[i].onclick = function() {
alert ("Button " + i);
};
}
alert ("Button " + i);
}
myFn();
http://jsfiddle.net/ka_tee_jean/fCtC8/

The last value of i variable is 5 at the end of the loop. So the alert statement inside myFn() will say "Button 5" on page load. The alert statements inside functions inside the for loop refer to the same variable via closure scope. Hence the clicks on the buttons also alert "Button 5".
Hope it helps! See below function which prints what you want. The caveat is introducing another closure scope by duplicating the value (pass-by-value mechanism in JavaScript) of i so that the onclick functions refer to another variable (i) which has a copy of the value.
function myFn() {
var elems = document.getElementsByTagName('button');
var len = elems.length;
for (var i = 0; i < len; i++) {
(function(i) {
elems[i].onclick = function() {
alert ("Button " + i);
};
})(i);
}
alert ("Button " + i);
}
myFn();

You have to change the function like:
js
function myFn() {
var elems = document.getElementsByTagName('button');
var len = elems.length;
for (var i = 0; i < len; i++) {
elems[i].onclick = function() {
alert (this.innerHTML);
};
}
alert ("Button " + i);
}
myFn();
As #Hunter McMillen mention i==5 is the condition that stops the loop.
Here is a fiddle

Related

Event listeners and closures on HTML collection in for loop

The code is
//Logic which works when the desired element is clicked
function changeArtistPhotoAndBio(prop) {
var artistPhoto = document.getElementsByClassName("artist-photo")[0];
var artistBio = document.getElementsByClassName("artist-bio")[0];
var i = prop.getAttribute("src").indexOf(".jpg");
var photoName = prop.getAttribute("src").slice (0, i);
artistPhoto.style.background="url(" + photoName + "-large.jpg";
console.log("it happened");
};
//Setting listeners for the click event in the loop
var artists = document.getElementsByClassName("gallery")[0].getElementsByTagName("img");
for (var i = 0; i < artists.length; i++) {
artists[i].addEventListener("click", changeArtistPhotoAndBio(artists[i]));
}
And the console output is
7x it happened
And the event handler for the click function does not work. I've tried isolating handler in the closure, like this:
for (var i = 0; i < artists.length; i++) {(function(i) {
artists[i].addEventListener("click", changeArtistPhotoAndBio(artists[i]));
}(i))
}
but the output is still the same. So there are two questions:
1) Why does the console output contain results of seven handler invocations if I did not invoke the function, just set it as a handler?
2) How can I set handlers in the "for" loop for HTML collection?
You have to use closures:
var artists = document.getElementsByClassName("gallery")[0].getElementsByTagName("img");
for (var i = 0; i < artists.length; i++) {
artists[i].addEventListener("click", function(index) {
return function() {
//You can use index for the current clicked item index
// console.log(index);
var artistPhoto = document.getElementsByClassName("artist-photo")[0];
var artistBio = document.getElementsByClassName("artist-bio")[0];
var i = this.getAttribute("src").indexOf(".jpg");
var photoName = this.getAttribute("src").slice (0, i);
artistPhoto.style.background="url(" + photoName + "-large.jpg";
console.log("it happened");
}
}(i));
}
$('body *').on('mouseover',function(){console.log(this.tagName)});
$('body *') selects all elements within the body.

Javascript For Loop index returning "[object Object] - undefined"

I'm trying to run one function to many elements,
so I'm using a for loop. I dont understand why i'm not getting any values.
var i;
var aFields = ["#business1A","#business1B","#business1C","#business1D","#business2A","#business2B","#business2C","#business2D",
"#business3A","#business3B","#business3C","#business3D","#business4A","#business4B","#business4C","#business4D",
"#business5A","#business5B","#business5C","#business5D","#business6A","#business6B","#business6C","#business6D"];
for (i = 0; i < aFields.length; i++) {
$(aFields[i]).keyup(function(){
alert($(aFields[i]+'Warning') + " - " +$(aFields[i]).val());
});
}
Try this:
var i;
var aFields = ["#business1A","#business1B","#business1C","#business1D","#business2A","#business2B","#business2C","#business2D",
"#business3A","#business3B","#business3C","#business3D","#business4A","#business4B","#business4C","#business4D",
"#business5A","#business5B","#business5C","#business5D","#business6A","#business6B","#business6C","#business6D"];
for (i = 0; i < aFields.length; i++) {
(function(j){
$(aFields[j]).keyup(function(){
alert(($(this).attr('id')+'Warning') + " - " +$(this).val());
});
})(i);
}
That's because you are using the variable i in the callback function for the event handler. The event happens when the loop has finished, so the variable contains an index that is beyond the last item in the array.
To use the value of the variable from the iteration where you bind the event, you can create a variable for each iteration by creating a scope, using an immediately executed function expression:
var i;
var aFields = ["#business1A","#business1B","#business1C","#business1D","#business2A","#business2B","#business2C","#business2D",
"#business3A","#business3B","#business3C","#business3D","#business4A","#business4B","#business4C","#business4D",
"#business5A","#business5B","#business5C","#business5D","#business6A","#business6B","#business6C","#business6D"];
for (i = 0; i < aFields.length; i++) {
(function(j){
$(aFields[j]).keyup(function(){
alert($(aFields[j]+'Warning') + " - " +$(aFields[j]).val());
});
})(i);
}
The problem here is the wrong usage of the closure variable i, the variable is shared between all the keyup handlers so when the loop ends i will have the value aFields.length so aFields[i] will return undefined.
Try
var aFields = ["#business1A", "#business1B", "#business1C", "#business1D", "#business2A", "#business2B", "#business2C", "#business2D",
"#business3A", "#business3B", "#business3C", "#business3D", "#business4A", "#business4B", "#business4C", "#business4D",
"#business5A", "#business5B", "#business5C", "#business5D", "#business6A", "#business6B", "#business6C", "#business6D"];
$.each(aFields, function (i, val) {
$(val).keyup(function () {
alert($(val + 'Warning') + " - " + $(val).val());
});
})
The other answers are good explanations of how closures work, but the cleanest solution is to use this to refer to the selected object:
var i;
var aFields = ["#business1A","#business1B","#business1C","#business1D","#business2A","#business2B","#business2C","#business2D",
"#business3A","#business3B","#business3C","#business3D","#business4A","#business4B","#business4C","#business4D",
"#business5A","#business5B","#business5C","#business5D","#business6A","#business6B","#business6C","#business6D"];
for (i = 0; i < aFields.length; i++) {
$(aFields[i]).keyup(function(){
alert(this.id + ' Warning - ' + this.value);
});
}

Attaching event in loop

What am doing is attaching event on a class using a loop and index values are being used in the event handler code. Here is my code:
var classElements=document.getElementsByClassName("a");
for(var i=0; i<4; i++)
{
classElements[i].onClick=function(){
alert("Clicked button : "+i);
}
}
Whenever I click any of the buttons, it alerts:
Clicked Button : 4
What could be the problem?
JavaScript closes over the object and evaluates it later when it is called. At the time it is called, i is 4.
I think you want something like:
var classElements=document.getElementsByClassName("a");
for(var i=0; i<4; i++)
{
classElements[i].onClick=function(j) {
return function(){
alert("Clicked button : "+j);
};
}(i);
}
EDIT: shown with named functions to make the code more clear
var classElements=document.getElementsByClassName("a");
for(var i=0; i<4; i++)
{
var makeFn = function(j) {
return function(){
alert("Clicked button : "+j);
};
};
classElements[i].onClick = makeFn(i);
}
You need a closure in order to capture the changes of i. As Lou stated this is due to post evaluation.
var classElements=document.getElementsByClassName("a");
for(var i=0; i<4; i++)
classElements[i].onclick = (function(i){
return function(){ alert("Clicked button : " + i) };
})(i);

Variable in wrong scope (maybe needs a closure?)

I have the following code that is in need of a closure:
var numItems = document.getElementsByClassName('l').length;
for (var i = 0; i < numItems; i++) {
document.getElementsByClassName('l')[i].onclick = function (e){
preview(this.href, i);
};
}
What happens is that whenever an item is clicked, preview always the same number for i
I suspect what I need to do is
function indexClosure(i) {
return function(e) {
preview(this.href, i);
}
}
And assign the onclick's like this:
document.getElementsByClassName('l')[i].onclick = indexClosure(i);
But then this would no longer refer to my link... how is this problem solved?
Use closure to capture the counter of the cycle:
var numItems = document.getElementsByClassName('l').length;
for (var i = 0; i < numItems; i++) {
(function(i){
document.getElementsByClassName('l')[i].onclick = function (e){
preview(this.href, i);
};
}(i))
}
doesn't onclick pass in (sender, eventArgs) allowing you to access this through sender?

How does JavaScript closure work in this case?

How does JavaScript closure work in this case and to be more specific: what does the (i) at the end do?
for(var i = 0; i < 10; i++) {
(function(e) {
setTimeout(function() {
console.log(e);
}, 1000);
})(i);
}
Also I'm trying to implement it in my code, and it seems I don't get it right
for (var i=0; i < len; i++) {
var formID = document.forms["form-" + i];
$(formID).bind("submit", validate);
$(formID).bind("change", function(i){
var divI = '#ind-' + i;
$(divI).css("background-color","green");
})(i);
}
This is a pattern used to create local scope around a variable. If this wasn't used then every call to console.log(i) would log the value of i (10) after the for loop finished.
for(var i = 0; i < 10; i++) {
// create new function
(function(e) {
// log each counter after 1 second.
setTimeout(function() {
console.log(e);
}, 1000);
// execute it with the counter
})(i);
}
The above is the same as this.
function foobar(e) {
setTimeout(function() {
console.log(e);
}, 1000);
}
for (var i = 0; i < 10; i++) {
(
foobar
)(i);
}
The real problem here is creating functions in a loop. don't do it :)
Your code
for (var i=0; i < len; i++) {
var formID = document.forms["form-" + i];
$(formID).bind("submit", validate);
// create a full closure around the block of code
(function() {
$(formID).bind("change", function(i){
var divI = '#ind-' + i;
$(divI).css("background-color","green");
})//(i); Don't call (i) here because your just trying to execute the
// jQuery element as a function. You can't do this, you need to wrap
// an entire function around it.
})(i);
}
But that is wrong, you want to delegate this job to something else.
function makeGreen(form, i) {
$(form).change(function() {
$("#ind-"+i).css("background-color", "green");
});
}
for (var i=0; i < len; i++) {
var formID = document.forms["form-" + i];
$(formID).bind("submit", validate);
// call a helper function which binds the change handler to the correct i
makeGreen(formID, i);
}
If you want to get a bit clever you can get rid of these anonymous functions
function makeGreen() {
var divId = $(this).data("div-id");
$(divId).css("background-color", "green");
}
for (var i=0; i < len; i++) {
$(document.forms["form-" + i])
.bind("submit", validate)
// store i on the form element
.data("div-id", "#ind-" + i)
// use a single event handler that gets the divId out of the form.
.change(makeGreen);
}
Edit
( // contain the function we create.
function(parameterA) {
window.alert(parameterA);
}
) // this now points to a function
("alertMessage"); // call it as a function.
Is the same as
( // contain the window.alert function
window.alert
) // it now points to a function
("alertMessage"); // call it as a function
Although not a direct answer to the closure question, here is my take on the issue.
I would re-write the logic to avoid the need for a closure (as it seems overcomplicated for the requirements)
The fact that there is a pattern in the naming of the forms makes things really easy
$('form[id^="form-"]').submit(validate)
.change(function(){
var divI = '#ind-' + this.id.replace('form-','');
$(divI).css("background-color","green");
});
demo http://jsfiddle.net/gaby/q8WxV/

Categories

Resources