Assigning EventListener in a loop - javascript

I use this line of code to add an event listener to my div's that are created through a forloop:
for(var i in mail){
//create div
parent.addEventListener("click",function(){read_msg(mail[i].id);},false);
//append to parent
}
This is causing the problem of mail[i].id being the last id for all of them. I've read some examples of how to solve it but i find it still very confusing.
I was suggested the solution of :
(function(){read_msg(mail[this].id)}).bind(i);
But am told this is not a great solution to use, was hoping someone could explain how you get read_msg to hold the correct value of id ? It always seems a bit messy in terms of a solution.

It is because you are using a closure variable i in your event handler function.
The variable i resides in the scope of the outer function, ie there is only one instance of the variable i. When the handler method is called and i is accessed javascript will look at the scope of the handler function first, if it does not find the variable there it will look at the parent closure scopes, then it will find the variable in the parent scope.
In the parent scope the value keep changing the value of i as the loop is executing that is why all the callback have the same value for i.
The solution here is to create a local closure
for(var i in mail){
(function(myvar){
parent.addEventListener("click",function(){read_msg(mail[myvar].id);},false);
//append to parent
})(i);
}
Here what we does is we have a Immediately Invoked Function Expression, to which we are passing the value of i as parameter myvar. So each iteration in the loop will create a independent closure.

you can use Object+Array methods in most browsers to side-step the pesky loop scope "bug" in js:
function addClick(key){
//create div
parent.addEventListener("click",function(){read_msg(mail[key].id);},false);
//append to parent
}
Object.keys(mail).forEach(addClick);
since functions have scope and forEach eats functions, you don't need the extra anon wrapper when you use Array methods.
if you want to go all out new JS hotness:
function addClick(key){
parent.addEventListener("click", this.method.bind( this.elm, this.source[key].id ), false);
}
Object.keys(mail).forEach(addClick, {elm:parent, source: mail, method:read_msg });
where you invert the source object of the key to allow using objects other than "mail", elements other than "parent" to attach events upon, and methods other than "read_msg", all without having to touch the logic or use the word "return"... Basically, whatever you setup in the traditional C-style for-loop initializer, you move to this.something and re-apply the logic as the need arises.

because each anonymous function you define as an event handler with each loop iteration will share the same scope as all the others, they will all reference the same var (i) as the array address for the message you are trying to display. Because your are redefining the var i with each loop, you will always see the last message in your message array displayed on each click event because the last value assigned to i will have been the length of your "mail" array.
heres how to fix it:
var helper = function(index) {
parent.addEventListener("click", function(){read_msg(mail[index].id);},false);
}
for(var i in mail) {
helper(i);
}

Related

Why is it not encouraged to create functions within a loop in JavaScript? [duplicate]

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 6 years ago.
I am an absolute newbie, and I just read this in JavaScript: The Good Parts.
In the chapter talking about scope, it says "It is important to understand that the inner function has access to the actual variables of the outer functions and not copies in order to avoid the following problem." And then the two following examples look like this:
//BAD EXAMPLE
var add_the_handlers = function (nodes) {
var i;
for (i = 0; i < nodes.length; i += 1) {
nodes[i].onclick = function (e) {
alert(i);
};
}
};
//END BAD EXAMPLE
var add_the_handlers = function (nodes) {
var helper = function (i) {
return function (e) {
alert(i);
};
};
var i;
for (i = 0; i < nodes.length; i += 1) {
modes[i].onclick = helper(i);
}
};
According to the author the second example is better because it doesn't use a loop inside the function, otherwise it could be wasteful computationally. But I am at loss and don't know what to do with them. How do I put his theory in real application? Can anyone illustrate these two examples combine HTML?
The problem is with closure. The inner functions have access to the variable i defined outside of these functions. After all iterations of the loop have been executed, the variable i will hold the value of nodes.length. So when you click on nodes[0], the alert will say nodes.length, which is not what you'd expect. (You would expect the alert to say 0.) The same holds when you click on nodes[1], nodes[2], etc. The alert for all of them will say nodes.length.
Firstly, in the bad example, a function is created for each event handler; the loop creates multiple function objects. Whereas in the second example, a single function is created and referenced from inside the loop. So you save a lot of memory.
Secondly, in the bad example, as the value of "i" runs, the function does not retain the value, and when it runs, it will always return the last value of "i". In the good example however, as "i" is passed into the function, this value is retained as the lexical environment of the function, and when it is called, it will return the correct value.
Thirdly, as mentioned by #Gary Hayes, we might want to use the function elsewhere too. So it's best to keep it independent of the loop.
You can check it with HTML working here: https://jsfiddle.net/vdrr4519/.
'multifunc' elements are inited with example with many functions, 'singlefunc'—with a single one. See, we take all the elements with a class and pass them to the function.
multifunc(document.querySelectorAll('.multifunc'));
Function runs 'for' loop and adds 'click' event listener. So the element should alert its index on click (beginning from 0). But in example with many function a wrong value is produced (because of closure, other answers also highlight the issue).
I think I should say also that it's not issue of single function/mutliple functions—it's a question of working with closures. You see, I can implement a working example WITH many closures: https://jsfiddle.net/pr7gqtdr/1/. I do basically the same thing that you do in a single-function handler, but every time call the new 'helper' function:
nodes[i].onclick = function (i) {
return function (e) {
alert(i);
};
}(i);
See, this (i) at the end is an immediate function call, so onclick gets a function with i variable set in closure.
But, the single function options is a bit better, because it's more memory efficient, I guess. Functions are objects. If you create many of them, you take more memory, in general. So, choosing from these, I'd stick with 'handler' function option.
The bad example creates a lot of event handlers; One per event. The good example create a single event handler and assigns it to all the events.
With the bad example, you've created lots of separate functions, instead of just one. That can be a lot of extra overhead and a lot of potential scope problems. These include closure issues such as an event only firing for the last item in the loop.
Additionally, the good example allows you to more easily unsubscribe the events because you have access to the original function pointer.
The good example is also just easier to read and understand. The loop is only used for creating the elements and binding their events; The handling of those events is done elsewhere.
As Soviut mentions, you are creating lots of event handlers in the bad example. Moreover, it is important to point out that the bad example functions refer to the same i variable, which means all of them will have the last value of nodes.length when they execute.
This is because a closure is created. You can read more about it in Closures.

Dojo "On" Checkbox Change Inside For Loop Scope Issue

I am working with Esri's Javascript Library 3.10, which is based on Dojo. I'm having an issue with scope, and despite trying different variations, I'm still having the same result. There is probably a much better way to do this, but I can't seem to figure it out.
I want to iterate through an object containing keys to a set of checkboxes, then assign an event handler using dojo/on to set a value based on the key, however, the key, "setting" inside the On(...) function is the same for all four iterations.
for (var setting in this.appSettings) {
console.log(setting); //prints four different things
if (this.hasOwnProperty(setting)) {
this.checkboxHandles[setting] =
On(this[setting], 'change', function (checked) {
//this.setValue(setting, checked)
console.log(setting) //prints the last 'setting' whenever checkbox is changed
});
}
}
So setting inside the On function is always the same value, even though the for loop is looping through four unique keys. First, why does this happen, and what are some suggestions to solving this?
That's one of the JavaScriptessentials. Closures always refer to their outer scope variables by reference. This means that the event handler references to the setting variable. However, when the for-loop continues, the setting variable is referencing the next value in the array.
The solution to this problem is what we call an IIFE or an Immediately Invoked Function Expression.
If you replace the event handler with a function that returns a function, for example:
On(appSettings[setting], 'change', function (setting) {
return function(checked) {
//this.setValue(setting, checked)
console.log(setting) //prints the last 'setting' whenever checkbox is changed
}
}(setting));
So, what happens here is that the setting variable is passed to the function wrapper, the advantage of this is that they are passed by value, not by reference.
So, if you access the setting variable from inside that function, it will refer to the locally scoped setting variable which is passed to that function.
How weird it may look, it works: http://jsfiddle.net/8sxqn53d/
An interesting slidedeck that explains this behavior can be found here.

JavaScript not writing variable as number (loop) [duplicate]

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 9 years ago.
I have a javascript code like this and this always gives me a problem
for(var i=1;i<9;i++){
document.getElementById('element'+i).onclick=function(){
theFunc(i)
}
}
It selects the right element and adds the onclick. But, when I type in console document.getElementById('element1").onclick it returns theFunc(i) (not theFunc(1))
So no matter which element is clicked it will always call theFunc(9) (at the end i is 9)
What's wrong with my code?
Your event handler function has an enduring reference to i, not a copy of its value, as you've discovered.
To prevent that, have the function close over something else that won't change:
for(var i=1;i<9;i++){
document.getElementById('element'+i).onclick=makeHandler(i);
}
function makeHandler(index) {
return function() {
theFunc(index);
};
}
makeHandler creates a function that closes over index, which is a copy of the value of i, and so doesn't change as the loop continues. Each event handler gets its own index.
That said, creating a bunch of event handler functions that are effectively identical usually means you can redesign a bit and use just one handler function. In this case, for instance, you could do this:
for(var i=1;i<9;i++){
document.getElementById('element'+i).onclick=theHandler;
}
function theHandler() {
func(parseInt(this.id.replace(/\D/g, ''));
}
...which grabs the value to use from the id of the element.
Another approach is delegation, where you actually hook the click event on an ancestor element (one that all of these elementX's have in common), and then when the click occurs, look at event.target and its ancestors to see what you should do.
TJ Crowder's answer is the best way around your problem. This "problem" you're experiencing in your closure is by design in many languages, and is referred to as scope.
Here's a good explanation of different scopes in JavaScript (including closures) and how to use them.
http://robertnyman.com/2008/10/09/explaining-javascript-scope-and-closures/
When you say theFunc(i) you are creating a closure around i, such that every function call refers to the same variable. You need to wrap the function inside an outer closure to ensure each function call is working with a unique variable:
for(var i=1;i<9;i++){
(function(i){
document.getElementById('element'+i).onclick=function(){
theFunc(i);
}
})(i);
}

Variable keeps changing after function is assigned to event handler

I have a bunch of elements on a page, all of whose ID's are stored in an array called ids[].
I have initialized a third-party DOM script for each of these divs that detects when the element has been dragged. The next step is to assign a function to the onDrag event of each element.
For simplicity's sake, I'd simply like to show a popup dialog that states the ID of the element that was dragged. I am iterating through my array as follows:
for (i=0;i<ids.length;i++)
{
document.getElementById(ids[i]).onDrag = function(){alert(ids[i])}
}
This all seems well and good, but toggling the drag event of any of my elements causes a dialog to popup that states the ID of the last element in the array. In other words, it looks like the above function in my iteration is always being evaluated for the last index in my array. I feel like I am missing something very simple here but this issue is driving me nuts.
The thing you've encountered is called closure and it is an essential part of Javascript.
A closure is an expression (typically a function) that can have free variables together with an environment that binds those variables (that "closes" the expression).
What happens is that the anonymous function assigned to ondrag closes over it's environment including variable i. As a consequence whenever i changes outside of this function, i will contain the new value inside accordingly.
The way you can workaround this behavior in the current context is to create another scope with an additional self-executing function.
for (var i=0; i<ids.length; i++) {
document.getElementById(ids[i]).onDrag = (function(i){ // <- i is a parameter
return function() {
alert(ids[i]); // <- i will be the inner value saved from outside
};
})(i); // <- invoke immidiately with the i from outside
}
You can read more on the topic: Use Cases for JavaScript Closures by #kangax
Do the following changes,
for (...){
SetOnDrag(ids[i]);
}
function SetOnDrag(id)
{
document.getElementById(id).onDrag = function() { alert(id); };
}

Why does this Javascript object not go out of scope after $(document).ready?

I have some working Javascript that manipulates the some DOM elements. The problem is, I don't understand why it works, which is never a good thing. I am trying to learn more about object oriented javascript and javascript best practices, so the organization may seems a little strange.
Basically, I wrap two methods that manipulate the DOM inside a CSContent object. I create an instance of that object, content in $(document).ready and bind some events to the functions in content. However, I am confused as to how these functions can still be called after $(document).ready exits. Doesn't that mean that content has gone out of scope, and its functions are not available? Anyway, here is the code:
function CSContent() {
var tweetTextArea = document.getElementById('cscontent-tweet'),
tweetTextElement = document.getElementById('edit-cscontent-cs-content-tweet'),
charCountElement = document.getElementById('cscontent-tweet-charactercount');
this.toggleTweetTextarea = function () {
$(tweetTextArea).slideToggle();
};
this.updateTweetCharacterCount = function () {
var numOfCharsLeft = 140 - tweetTextElement.value.length;
if (numOfCharsLeft < 0) {
$(charCountElement).addClass('cscontent-negative-chars-left');
}
else {
$(charCountElement).removeClass('cscontent-negative-chars-left');
}
charCountElement.innerHTML = '' + numOfCharsLeft + ' characters left.';
};
}
$(document).ready(function () {
var content = new CSContent();
//If the twitter box starts out unchecked, then hide the text area
if ($('#edit-cscontent-cs-content-twitter:checked').val() === undefined) {
$('#cscontent-tweet').hide();
}
$('#edit-cscontent-cs-content-twitter').change(content.toggleTweetTextarea);
//Seems wasteful, but we bind to keyup and keypress to fix some weird miscounting behavior when deleting characters.
$('#edit-cscontent-cs-content-tweet').keypress(content.updateTweetCharacterCount);
$('#edit-cscontent-cs-content-tweet').keyup(content.updateTweetCharacterCount);
content.updateTweetCharacterCount();
});
This, m'lord, is called a closure: the local variable content will remain in memory after $(document).ready exits. This is also a known cause of memory leaks.
In short, you bind this function to an event listener of a DOM element and then the JavaScript garbage collector knows that it should keep the local variable intact. You can't call it directly (outside of the function), unless the event is triggered. With some, you can do this ‘manually’, if you really want to call the function afterward (e.g., using element.click() to simulate a click).
I assume you wonder why the event handlers like
$('#edit-cscontent-cs-content-twitter').change(content.toggleTweetTextarea);
work?
Well you don't pass content as event handler but the function that is contained in content.toggleTweetTextarea. And this reference will still exist after content does not exist anymore. There is nothing special about it. You just assigned an object (the function) to another variable. As long as at least one reference to an object exists, the object won't be garbage collected.
Now you may ask why those functions have still access to e.g. tweetTextArea ? This is indeed a closure. When the functions are created via new CSContent(), the activation context of this function is added to the scope chain of the inner functions CSContent.toggleTweetTextarea and CSContent.updateTweetCharacterCount. So even if you don't have a reference to content anymore, the scope of this function is still contained in the scope chain of the other functions.
You won't be able to access the object contained in content anymore after ready() is finished, this indeed goes out of scope.
My brain is off today, but shouldn't you be using closures in this situation?
$('#edit-cscontent-cs-content-twitter').change(
function(){
content.toggleTweetTextarea();
}
);

Categories

Resources