Variable keeps changing after function is assigned to event handler - javascript

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

Related

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.

Assigning EventListener in a loop

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

removeEventListener with Unique Anonymous Function

I have an object that generates HTML elements that are also connected with an array of the object, and let us say we have one instance of it. So as it creates the elements it also assigns the following event listener to a nested part of the element (the class being uploadDelete).
Now this event listener needs to call the delete method of the instance of the object that created it, with the value of i assigned at its creation. Because events are under Window, the instance needed to be passed to an anonymous function along with the i value.
This therefore assigns a very unique function to the event, and because the delete method will be destroying the element containing the listener I would like to remove it first; from what I've read it could cause leaks otherwise(?). I'm also using Strict Mode, so not arguments.callee.
file.display.getElementsByClassName('uploadDelete')[0].addEventListener('click',
(function(that,i){
return function() {
that.delete(i);
};
})(this,i), false);
I've tried many different things, but when I started having an anonymous function inside of a function inside of a function which is then called in the listener, I figured I should just ask on here. I might have a solution to the overall problem, changing other code, but it would still help if this could be answered.
Here is what I ended up doing, with the help of Norguard's answer. Since the uniqueness was stemming from an object called file, I just created a new property of file to store the function:
file.deleteFunction = (function(that,i){
return function() {
that.delete(i);
};
})(this,i);
file.display.getElementsByClassName('uploadDelete')[0].addEventListener('click',file.deleteFunction, false);
The delete function that is called then removes the event listener.
A relatively-painless way of doing this might be to create an object in the scope that's responsible for adding and deleting listeners, which builds an ID, serial or non, and will store whatever the listener is in an object, with that ID, returning the ID to whichever object/module requested it (or passing the anonymous function back to them).
// trivial example
var listeners = {},
i = 0,
add = function (context, func, closure) {
var enclosed = (function (closure) {
return function () { /* ... */; func(); };
}(closure)),
index = i;
context.addEventListener("...", enclosed, false);
listeners[index] = enclosed;
i += 1;
return index;
};
add will now add your listener, but will also store the function that you're passing into addEventListener into the listeners object.
If you need a reference to i, you've already got it in the closure, if you want it.
So now when you remove stuff, you can just look for the function saved at listeners[i].
An alternate, if you don't want to save a table full of these in one spot, for whatever reason, would be to catch the return statement, and instead of returning i, return the function;
// inside of your module
// I'm not usually crazy about `this`, without doing something particular with it,
// but hopefully it illustrates my point
this.cached_func = add(this.el, this.callback.bind(this), this.secret);
So now, when it comes time to delete everything, and you want to shut down your listener...
remove(this.cached_func);
All of that said, the leaks that you've read about are still possible, but the major culprit was IE6/7 (and earlier).
As people steer further from bad browsers, this becomes less important.
In fact, encouraging memory-dumps in IE6 is probably just a good way to encourage people to not use IE6.

Why does dynamically adding .onclick to an img element, when in a loop, require return function()?

This solution works, but I don't understand what the second return function() does?
for (var i = 0; i < photos.length; i ++) {
img.onclick = (function(photo) {
return function() {
hotLink(photo); //window.location = '/pics/user/' + photo.user_id;
};
})(photos[i]);
Also, why do I have to include the (photos[i]); at the end?
Before, I had this, and the onclick would always link to the last photo[i].
for (var i = 0; i < photos.length; i ++) {
img.onclick = function() {
window.location = 'pics/user/' + photo.user_id
};
}
When you do this (assuming there's a photo = photos[i] there that you left out in your question):
img.onclick = function() { window.location = 'pics/user/' + photo.user_id };
The variable photo inside the function refers to the same variable as photo outside the function. It's not a snapshot that gets the current value of the variable at the time you define the function; it's just a reference to the same variable. The surrounding loop changes the value of that variable on every iteration, but it doesn't create a new variable each time; it's reusing the same one. So all the functions you generate reference that exact same variable - the one and only photo.
By the time anyone actually clicks on the image and calls the function, the loop has long since terminated, and photo is gone from the main program's scope, but it's still out there in memory because all those functions still have references to it. And they will find it still pointing to the last item in the list, because that was the last thing assigned to it.
So you need to give each onclick function its very own variable that won't change once the function is created. The way to do that in Javascript, since it doesn't have block scope, is to call a function and pass the value in as a parameter. Function parameters and variables declared inside a function (as opposed to photo in the non-working example above, which is used inside the function but declared outside it) are created fresh on every function invocation. When photo is declared as a function parameter, each onclick gets its very own copy that nothing else can modify, so it still has the right value when someone finally clicks the image.
It might be clearer if it used a static function-generator function; there's really no reason to do the inline declare-and-call thing. You could declare this once, outside the loop:
function makeOnclick(somePhoto) {
return function() { hotlink(somePhoto); }
}
And then the loop body could do this:
img.onclick = makeOnclick(photo)
You're calling makeOnclick and passing it photo as a parameter. The makeOnclick function is declared far away, where it couldn't use photo directly even if you wanted it to; it can't see that variable at all. Instead, all it has is its local parameter somePhoto - which is created as a brand new variable every time you call makeOnclick. It's initialized with the value of photo at the point of the call, but it's just a copy, so when photo changes on the next loop iteration, that particular instance of somePhoto will stay the same. When the next iteration calls makeOnclick, it will create a new instance of somePhoto initialized to the new value of photo, and so on. So even though the inner function that makeOnClick is returning is inheriting the somePhoto var, that var was just created especially for that instance of makeOnClick; every one of those returned functions gets its own private somePhoto.
Your working code above is doing exactly the same thing in a slightly different way. Instead of declaring the makeOnclick function once, outside the loop, and calling it a bunch of times, it's redeclaring it every time through the loop as an anonymous function which it then calls immediately. This code:
img.onclick = (function(x) { blah(x); })(photo);
is the same as this:
function foo(x) { blah(x); }
img.onclick = foo(photo);
without having to give the function a name. In JavaScript in general, this:
(function (x,y,z) { doSomething(x,y,z); })(a,b,c);
is the same as this:
function callDoSomething(x,y,z) { doSomething(x,y,z); }
callDoSomething(a,b,c);
except the function has no name and is not saved anywhere; it goes away right after it's called.
So declaring the onclick-generator function every time through the loop and calling it immediately all at once is nice and concise, but not terribly efficient.
The returned function is a closure. When you're looping through like that i is updating on each loop until the end of the loop where you're stuck with the last image. Adding the self executing function and passing photo[i] in it will permanently enclose the current value within the returned function as photo.
Here is more information on closures: How do JavaScript closures work?
And here for more information on your current issue: JavaScript closure inside loops – simple practical example
Because a function invocation is the only way to create a new variable scope in JavaScript.
So you pass photos[i] to that function, and it becomes local to the scope of that invocation.
Then you also create the handler function in that same scope, so the handler is referencing that specific photo.
So in the end, if the loop iterates 10 times, you're invoking 10 functions, creating 10 new separate variable scopes, which reference each individual photo and create and return the handler from each separate scope.
These things are sometimes clearer if you don't inline the function like that.
for (var i = 0; i < photos.length; i ++) {
img.onclick = createHandler(photos[i]); // pass individual photos to createHandler
}
function createHandler(photo) {
// In here, the individual photo is referenced
// And we create (and return) a function that works with the given photo
return function() {
hotLink(photo); //window.location = '/pics/user/' + photo.user_id;
};
}
So if the loop runs for 10 iterations, we invoke createHandler() 10 times, each time passing an individual photo.
Because each function invocation creates a variable scope, and because we create the event handler inside each scope, what we end up with is all 10 functions being created in 10 variable scopes, each of which reference whatever photo was passed.
Without the per-iteration function invocation, all the handler functions are created in the same variable scope, which means they all share the same variables, which are likely being overwritten in each loop iteration.

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