How scope works in a for loop? - javascript

When I created 5 buttons in a loop and click the value of i is always 6
function createButtons() {
for (var i = 1; i <= 5; i++) {
var body = document.getElementsByTagName("BODY")[0];
var button = document.createElement("BUTTON");
button.innerHTML = 'Button ' + i;
(function(num) {
button.onclick = function() {
alert('This is button ' + num);
}
})(i);
body.appendChild(button);
}
}
but when I change the scope of i to block scope(using IIFE or let keyword) it gives the right value of i. How does it working under the hood of javascript?

I have seperated your functions into incorrect and correct one.
The incorrect version is what you're asking. The correct version is what you've already figured out but don't know why it work.
In the incorrect version, the value of i will always be updated to the most recent value because i belongs to the createButtons function and is shared with all onclick handler, and it is changing with the loop.
In the correct version, the value of i is given to the IIFE as num, and num belongs to the IIFE and not to createButtons.
Because of that, num is fixed because a new num is created for every loop thus is not shared with the other onclick handler.
Why? It is how closure works in JavaScript.
Read this for deeper understanding on JavaScript closure.
function createButtons_incorrect() {
for (var i = 1; i <= 5; i++) {
var body = document.getElementsByTagName("BODY")[0];
var button = document.createElement("BUTTON");
button.innerHTML = 'Bad ' + i;
button.onclick = function() {
alert('This is button ' + i);
}
body.appendChild(button);
}
}
function createButtons_correct() {
for (var i = 1; i <= 5; i++) {
var body = document.getElementsByTagName("BODY")[0];
var button = document.createElement("BUTTON");
button.innerHTML = 'Good ' + i;
(function(num){
button.onclick = function() {
alert('This is button ' + num);
}
})(i);
body.appendChild(button);
}
}
createButtons_incorrect();
document.body.appendChild(document.createElement('br'));
createButtons_correct();

Related

Make buttons which call same event handler using standard DOM manipulations

What am I essentially trying to do is to produce container with buttons, which have the same handler but get different arguments like this:
<div id="container">
<button onclick="onclick_handler(1)">1</button>
<button onclick="onclick_handler(2)">2</button>
<button onclick="onclick_handler(3)">3</button>
</div>
Using this:
function onload_handler() {
var container = document.getElementById("container");
var i;
for (i = 0; i < 3; i++) {
var button = document.createElement('button');
button.innerHTML = i;
button.addEventListener('click', function() {
onclick_handler(i);
});
container.appendChild(button);
}
}
function onclik_handler(val) {
console.log(val);
}
And when I click buttons I get 4 in my console. What am I doing wrong?
Could it also be done without usage of anonymous functions?
Try to resolve the problem created by closure, by creating a scope per iteration,
function(i){
button.addEventListener('click', function() {
onclick_handler(i);
});
})(i);
And your full code would be,
function onload_handler() {
var container = document.getElementById("container");
var i;
for (i = 0; i < 3; i++) {
var button = document.createElement('button');
button.innerHTML = i;
(function(i){
button.addEventListener('click', function() {
onclick_handler(i);
});
})(i)
container.appendChild(button);
}
}
function onclik_handler(val) {
console.log(val);
}

Passing One's Self to OnClick Event JavaScript

The on click event that I add to an input in javascript isn't working in the proper manner.
My code so far looks like so:
function order(option) {
if(option.checked) {
document.getElementId("col_order").value = document.getElementById("col_order").value + " " + option.value;
}
}
...//somewhere further down
for(var i = 0; i < options.length; i++) {
var check = document.createElement("input");
var label = document.createElement("label");
var description = document.createTextNode(options[i]);
check.type = "checkbox";
check.name = "order_list[]";
check.value = options[i];
check.onclick = "order(check)"; //Problem here
label.appendChild(check);
label.appendChild(description);
element.appendChild(label);
}
I have also tried:
check.onclick = (function() { var option = check; return function() {order(option);}})();
The problem that I am having is the check.onlick line of code. When I add this with normal HTML:
<input type = "checkbox" name = "order_list[]" onclick = "order(this)" value = "randVal">randVal</input>
I don't have any problem whatsoever; the method executes with the intended results. Any thoughts?
Let me clarify: I make it to the order function just fine, but I never get into the if statement, even though the checkbox was just clicked
Use addEventListener instead, and even if it looks like it should work, you're overwriting the same variables on each iteration as there is no closure in for loops, so I would probably add a closure to avoid issues.
For a checkbox you would listen for the change event, not click
for(var j = 0; j < options.length; j++) {
(function(i) {
var check = document.createElement("input");
var label = document.createElement("label");
var description = document.createTextNode(options[i]);
check.type = "checkbox";
check.name = "order_list[]";
check.value = options[i];
check.addEventListener('change', function() {
if (this.checked) {
var col_order = document.getElementById("col_order");
col_order.value = col_order.value + " " + this.value;
}
}, false);
label.appendChild(check);
label.appendChild(description);
element.appendChild(label);
})(j);
}
FIDDLE
check.onclick = "order(check)"; assigns a String as an on-click handler. That doesn't work; the browser expects a function there:
check.onclick = function() {
order(check);
}

For loop unexpected increment value

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

javascript closure not working as it should

see the first code:
var count = 0;
(function addLinks() {
var count = 0;//this count var is increasing
for (var i = 0, link; i < 5; i++) {
link = document.createElement("a");
link.innerHTML = "Link " + i;
link.onclick = function () {
count++;
alert(count);
};
document.body.appendChild(link);
}
})();
When the link gets clicked the counter variable keeps on increasing for each link element. This is the expected result.
Second:
var count = 0;
$("p").each(function () {
var $thisParagraph = $(this);
var count = 0;//this count var is increasing too.so what is different between them .They both are declared within the scope in which closure was declared
$thisParagraph.click(function () {
count++;
$thisParagraph.find("span").text('clicks: ' + count);
$thisParagraph.toggleClass("highlight", count % 3 == 0);
});
});
Here the closure function is not working as expected. On each click on the paragraph element, the counter var is increased but that increment is not displayed on click on second paragraph element? What is the reason for this? Why is this happening? The count variable is not increasing for each paragraph element.
do you mean:
var count = 0;
$("p").each(function() {
var $thisParagraph = $(this);
//var count = 0; //removed this count, as it re-inits count to 0
$thisParagraph.click(function() {
count++;
$thisParagraph.find("span").text('clicks: ' + count);
$thisParagraph.toggleClass("highlight", count % 3 == 0);
});
});

Passing parameters in Javascript onClick event

I'm trying to pass a parameter in the onclick event. Below is a sample code:
<div id="div"></div>
<script language="javascript" type="text/javascript">
var div = document.getElementById('div');
for (var i = 0; i < 10; i++) {
var link = document.createElement('a');
link.setAttribute('href', '#');
link.innerHTML = i + '';
link.onclick= function() { onClickLink(i+'');};
div.appendChild(link);
div.appendChild(document.createElement('BR'));
}
function onClickLink(text) {
alert('Link ' + text + ' clicked');
return false;
}
</script>
However whenever I click on any of the links the alert always shows 'Link 10 clicked'!
Can anyone tell me what I'm doing wrong?
Thanks
This happens because the i propagates up the scope once the function is invoked. You can avoid this issue using a closure.
for (var i = 0; i < 10; i++) {
var link = document.createElement('a');
link.setAttribute('href', '#');
link.innerHTML = i + '';
link.onclick = (function() {
var currentI = i;
return function() {
onClickLink(currentI + '');
}
})();
div.appendChild(link);
div.appendChild(document.createElement('BR'));
}
Or if you want more concise syntax, I suggest you use Nick Craver's solution.
This is happening because they're all referencing the same i variable, which is changing every loop, and left as 10 at the end of the loop. You can resolve it using a closure like this:
link.onclick = function(j) { return function() { onClickLink(j+''); }; }(i);
You can give it a try here
Or, make this be the link you clicked in that handler, like this:
link.onclick = function(j) { return function() { onClickLink.call(this, j); }; }(i);
You can try that version here
link.onclick = function() { onClickLink(i+''); };
Is a closure and stores a reference to the variable i, not the value that i holds when the function is created. One solution would be to wrap the contents of the for loop in a function do this:
for (var i = 0; i < 10; i++) (function(i) {
var link = document.createElement('a');
link.setAttribute('href', '#');
link.innerHTML = i + '';
link.onclick= function() { onClickLink(i+'');};
div.appendChild(link);
div.appendChild(document.createElement('BR'));
}(i));
Try this:
<div id="div"></div>
<script language="javascript" type="text/javascript">
var div = document.getElementById('div');
for (var i = 0; i < 10; i++) {
var f = function() {
var link = document.createElement('a');
var j = i; // this j is scoped to our anonymous function
// while i is scoped outside the anonymous function,
// getting incremented by the for loop
link.setAttribute('href', '#');
link.innerHTML = j + '';
link.onclick= function() { onClickLink(j+'');};
div.appendChild(link);
div.appendChild(document.createElement('br')); // lower case BR, please!
}(); // call the function immediately
}
function onClickLink(text) {
alert('Link ' + text + ' clicked');
return false;
}
</script>
or you could use this line:
link.setAttribute('onClick', 'onClickLink('+i+')');
instead of this one:
link.onclick= function() { onClickLink(i+'');};
Another simple way ( might not be the best practice) but works like charm. Build the HTML tag of your element(hyperLink or Button) dynamically with javascript, and can pass multiple parameters as well.
// variable to hold the HTML Tags
var ProductButtonsHTML ="";
//Run your loop
for (var i = 0; i < ProductsJson.length; i++){
// Build the <input> Tag with the required parameters for Onclick call. Use double quotes.
ProductButtonsHTML += " <input type='button' value='" + ProductsJson[i].DisplayName + "'
onclick = \"BuildCartById('" + ProductsJson[i].SKU+ "'," + ProductsJson[i].Id + ")\"></input> ";
}
// Add the Tags to the Div's innerHTML.
document.getElementById("divProductsMenuStrip").innerHTML = ProductButtonsHTML;
It is probably better to create a dedicated function to create the link so you can avoid creating two anonymous functions. Thus:
<div id="div"></div>
<script>
function getLink(id)
{
var link = document.createElement('a');
link.setAttribute('href', '#');
link.innerHTML = id;
link.onclick = function()
{
onClickLink(id);
};
link.style.display = 'block';
return link;
}
var div = document.getElementById('div');
for (var i = 0; i < 10; i += 1)
{
div.appendChild(getLink(i.toString()));
}
</script>
Although in both cases you end up with two functions, I just think it is better to wrap it in a function that is semantically easier to comprehend.
onclick vs addEventListener. A matter of preference perhaps (where IE>9).
// Using closures
function onClickLink(e, index) {
alert(index);
return false;
}
var div = document.getElementById('div');
for (var i = 0; i < 10; i++) {
var link = document.createElement('a');
link.setAttribute('href', '#');
link.innerHTML = i + '';
link.addEventListener('click', (function(e) {
var index = i;
return function(e) {
return onClickLink(e, index);
}
})(), false);
div.appendChild(link);
div.appendChild(document.createElement('BR'));
}
How abut just using a plain data-* attribute, not as cool as a closure, but..
function onClickLink(e) {
alert(e.target.getAttribute('data-index'));
return false;
}
var div = document.getElementById('div');
for (var i = 0; i < 10; i++) {
var link = document.createElement('a');
link.setAttribute('href', '#');
link.setAttribute('data-index', i);
link.innerHTML = i + ' Hello';
link.addEventListener('click', onClickLink, false);
div.appendChild(link);
div.appendChild(document.createElement('BR'));
}
This will work from JS without coupling to HTML:
document.getElementById("click-button").onclick = onClickFunction;
function onClickFunction()
{
return functionWithArguments('You clicked the button!');
}
function functionWithArguments(text) {
document.getElementById("some-div").innerText = text;
}

Categories

Resources