Dojo "On" Checkbox Change Inside For Loop Scope Issue - javascript

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.

Related

Changing a variable reference in JavaScript

I am having a hard time figuring out how to reassign a variable to a new function.
I have two anonymous functions that are assigned to variables and I want "first" to assign itself to "after" after it is called once.
When using .toSource in Firefox it seems like it is definitely reassigning first to after, but the "first in the click handler is still referencing or calling the first that was created at runtime.
Why is this?
JS
var after = function() {
alert("AFTER");
}
var first = function() {
alert("FIRST");
//alert(first.toSource());
first = after;
}
$(".order").click( first );
HTML
<button type="button" class="order">Order</button>
http://jsfiddle.net/E2R2R/2/
Following up on #MichaelGeary s answer, if you want to make the function work by referencing like that, put your function call inside an anonymous function:
$(".order").click( function() {
first();
});
Demo: http://jsfiddle.net/E2R2R/4/
When you make this call:
$(".order").click( first );
you are using the value of first at the time of this call. Its value at that time is what is passed into the click() function.
Changing the value of the first variable later won't affect this.
Another way to look at it: think about the code inside jQuery's click() method. It doesn't know that you used the first variable in your call. The only information it has is the function that first refers to at the time of the call.
See #tymeJV's answer for a way to do what you want. Note what's different about it: the function you're passing into .click() (the anonymous function) doesn't have to change. When that function calls first(), it always uses the current value of that name.

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.

Clarification of JavaScript variable scope

I already know how to make this code work, but my question is more about why does it work like this, as well as am I doing stuff right.
The simplest example I can make to showcase my issue is this :
Lets say I have a function that increments the value of an input field by 10 on the press of a button.
var scopeTest = {
parseValue : function( element, value ) {
value = parseInt( element.val(), 10 );
//Why does this not return the value?
return value;
},
incrementValue : function( element, button, value ) {
button.on('mousedown', function (e) {
//Execute the parseValue function and get the value
scopeTest.parseValue( element, value );
//Use the parsed value
element.val( value + 10 );
e.preventDefault();
});
},
init : function () {
var element = $('#test-input'),
button = $('#test-button'),
value = '';
this.incrementValue( element, button, value );
}
};
scopeTest.init();
The above code doesnt work because the parseValue method doesn't properly return the value var when executed inside the incrementValue method.
To solve it apparently I have to set the scopeTest.parseValue( element, value ); parameter to the value variable like this:
value = scopeTest.parseValue( element, value );
Than the code works.
But my question is why? Why is this extra variable assignment step necessary, why the return statement is not enough? Also I am doing everything right with my functions/methods, or is this just the way JavaScript works?
Working example here => http://jsfiddle.net/Husar/zfh9Q/11/
Because the value parameter to parseValue is just a reference. Yes, you can change the object, because you have a reference, but if you assign to the reference it now points at a different object.
The original version is unchanged. Yes, the return was "enough", but you saved the new object in a variable with a lifetime that ended at the next line of code.
People say that JavaScript passes objects by reference, but taking this too literally can be confusing. All object handles in JavaScript are references. This reference is not itself passed by reference, that is, you don't get a double-indirect pointer. So, you can change the object itself through a formal parameter but you cannot change the call site's reference itself.
This is mostly a scope issue. The pass-by-* issue is strange to discuss because the sender variable and the called functions variable have the same name. I'll try anyway.
A variable has a scope in which it is visible. You can see it as a place to store something in. This scope is defined by the location of your function. Meaning where it is in your source code (in the global scope or inside a function scope). It is defined when you write the source code not how you call functions afterwards.
Scopes can nest. In your example there are four scopes. The global scope and each function has a scope. The scopes of your functions all have the global scope as a parent scope. Parent scope means that whenever you try to access a name/variable it is searched first in the function scope and if it isn't found the search proceeds to the parent scope until the name/variable is found or the global scope has been reached (in that case you get an error that it can't be found).
It is allowed to define the same name multiple times. I think that is the source of your confusion. The name "value" for your eyes is always the same but it exists three times in your script. Each function has defined it: parseValue and incrementValue as parameter and init as local var. This effect is called shadowing. It means that all variables with name 'value' are always there but if you lookup the name one is found earlier thus making the other invisible/shadowed.
In this case "value" is treated similar in all three functions because the scope of a local var and a parameter is the same. It means that as soon as you enter one of the methods you enter the function scope. By entering the scope the name "value" is added to the scope chain and will be found first while executing the function. And the opposite is true. If the function scope is left the "value" is removed from the scope chain and gets invisible and discarded.
It is very confusing here because you call a function that takes a parameter "value" with something that has the name "value" and still they mean different things. Being different there is a need to pass the value from one "value" to the other. What happens is that the value of the outer "value" is copied to the inner "value". That what is meant with pass-by-value. The value being copied can be a reference to an object which is what most people make believe it is pass-by-reference. I'm sorry if that sounds confusing but there is too much value naming in here.
The value is copied from the outer function to the called function and lives therefor only inside the called function. If the function ends every change you did to it will be discarded. The only possibility is the return your "side effect". It means your work will be copied back to a variable shortly before the function gets discarded
To other alternative is indeed leaving of parameters and work with the scope chain, e.g. the global scope. But I strongly advize you not to do that. It seems to be easy to use but it produces a lot of subtle errors which will make your life much harder. The best thing to do is to make sure variables have the most narrow scope (where they are used) and pass the values per function parameters and return values.
This isn't a scoping issue, it's a confusion between pass-by-reference and pass-by-value.
In JavaScript, all numbers are passed by value, meaning this:
var value = 10;
scopeTest.parseValue( element, value );
// value still == 10
Objects, and arrays are passed by reference, meaning:
function foo( obj ){
obj.val = 20;
}
var value = { val: 10 }
foo( value );
// value.val == 20;
As others have said it's a pass-by-ref vs pass-by-val.
Given: function foo (){return 3+10;} foo();
What happens? The operation is performed, but you're not setting that value anywhere.
Whereas: result = foo();
The operation performs but you've stored that value for future use.
It is slightly a matter of scope
var param = 0;
function foo( param ) {
param = 1;
}
foo(param);
console.log(param); // still retains value of 0
Why?
There is a param that is global, but inside the function the name of the argument is called param, so the function isn't going to use the global. Instead param only applies the local instance (this.param). Which, is completely different if you did this:
var param = 0;
function foo() { // notice no variable
param = 1; // references global
}
foo(param);
console.log(param); // new value of 1
Here there is no local variable called param, so it uses the global.
You may have a look at it.
http://snook.ca/archives/javascript/javascript_pass

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

Categories

Resources