I have a DownDownButton that I've populated from an array containing project names and ids. The list shows the project name, but I'd like to get the project id. The variable "projects" looks like this:
[Object { name="Project A", id="1325"}, Object { name="Project B", id="5241"}, Object { name="Project C", id="3224"}]
This code creates the MenuItem for the button correctly, but how do I set the variable projId in the onClick event?
for (i = 0; i < projects.length; i++) {
menuProjects.addChild(new MenuItem({
label: projects[i].name,
onClick: function () {
projId = ?;
}
}));
}
I've tried using "projId= projects[i].id;", but that gives me an error since i is now 3. What's the correct syntax to do this?
-- Edit --
This is how I got it to work using both cookie's and Merrick's answers.
for (i = 0; i < projects.length; i++)
(function (x) {
menuProjects.addChild(new MenuItem({
label: projects[i].name,
onClick: function () {
projId = projects[x].id;
}
}));
} (i));
Since, the onClick callback is async and javascript is functionally scoped i will be hoisted and by the time the click event happens i will be the last value of i. To maintain the scope you can simply leverage a IIFE to properly scope i.
// Block scoping will all lazily evaluate to 10
for (var i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i);
}, 100);
}
// Block scoping will capture the current value for each function invocation
for (var i = 0; i < 10; i++) (function(i) {
setTimeout(function() {
console.log(i);
}, 100);
})(i)
Here is an example: http://jsbin.com/eyeqiy/1/edit
I don't know if it is the correct/best way to do it, but you could create a closure to give each iteration a new variable scope like so:
for (i = 0; i < projects.length; i++) {
(function(x) {
menuProjects.addChild(new MenuItem({
label: projects[i].name,
onClick: function () {
projId = x;
}
}));
}(i));
}
This creates an anonymous function (with its own scope) that is immediatly evaluated.
Related
I am trying to assign a handler to every child element that I loop over. My problem is that the: target = child.items[i].id; will have the id of the last element that I loop over. So this part:
fn: function() {
isoNS.injectKey(target);
}
will always have the the id (target) of the last child. How can I do this?
I have tried put this in front, like this: isoNS.injectKey(this.target);
var arr=[];
for(var i = 0; i < obj.items.length; ++i) {
target = child.items[i].id;
arr.push({
key: sHolder.charAt(i),
fn: function() {
isoNS.injectKey(target);
},
});
}
So my main problem, is that each different value of: target = child.items[i].id; is overwritten with the latest element each time. I hope I am making myself understood.
In case you are wondering what obj and child is... I left them out to make the code shorter and more understandable. just know that they do have values in them, and are never null
You could do this
var arr = Array.prototype.map.call(obj.items, function(obj, i) {
return {
key: sHolder.charAt(i),
fn: function() {
isoNS.injectKey(child.items[i].id);
}
};
});
the Array map function provides the closure, and a nicer way to build up your arr
I use Array.prototype.map.call because there's no indication if obj.items is a TRUE array ... if it is, then it's a bit simpler
var arr = obj.items.map(function(obj, i) {
return {
key: sHolder.charAt(i),
fn: function() {
isoNS.injectKey(child.items[i].id);
}
};
});
The problem is your function is a closure and it has captured a reference to the target variable and this gets changed by your loop before the call back is invoked. A simple way to get around this is to wrap your function in another closure that captures the value of the target variable.
You can do this like so:
(function(capturedValue){
return function () {
// do something with the capturedValue
};
}(byRefObject));
The first function is part of an Immediately Invoked Function Expression (IIFE). It serves to capture the value of the byRefObject. You can read more about IIFE here.
Your code could look like this:
var arr=[];
for(var i = 0; i < obj.items.length; ++i) {
target = child.items[i].id;
arr.push({
key: sHolder.charAt(i),
fn: (function(target) {
return function() {
isoNS.injectKey(target);
};
})(target)
},
});
}
This has to do with closures:
var arr=[];
function getFunc(t){
return function() {
isoNS.injectKey(t);
}};
for(var i = 0; i < obj.items.length; ++i) {
target = child.items[i].id;
arr.push({
key: sHolder.charAt(i),
fn: getFunc(target),
});
}
Please see the jsfiddle:
http://jsfiddle.net/LsNCa/2/
function MyFunc() {
for (var i = 0; i < 2; i++) { // i= 0, 1
var myDiv = $('<div>');
myDiv.click(function(e) {
alert(i); // both the two divs alert "2", not 0 and 1 as I expected
});
$('body').append(myDiv);
}
}
var myFunc = new MyFunc();
I want the divs to alert "0" and "1" respectively when I click them, but both of them alert "2".
When I click the divs and the event is triggered, how and where do the handler find the value of the variable i?
I'm aware that adding a closure achieves my goal. But why?
function MyFunc() {
for (var i = 0; i < 2; i++) { // i= 0, 1
(function(j) {
var myDiv = $('<div>');
myDiv.click(function(e) {
alert(j);
});
$('body').append(myDiv);
})(i);
}
}
var myFunc = new MyFunc();
The code above is how you get it work correctly. Without an closure, you always the the last value of i. What we do is to post i into the closure and let the runtime "remember" the value of that very moment.
You need a closure because all your event handler functions are referencing the same variable i. The for loop updates this, and when the loop is done the variable contains 2. Then when someone clicks on one of the DIVs, it accesses that variable.
To solve this, each event handler needs to be a closure with its own variable i that contains a snapshot of the value at the time the closure was created.
I suggest that you read this article
JavaScript hoists declarations. This means that both var statements
and function declarations will be moved to the top of their enclosing
scope.
As #Barmar said in his answer above, the variable i is being referenced by both the event handlers.
You should avoid declaring functions inside loops. Below there is some code that does what you need.
I assume that you're using jQuery.
function MyFunc() {
for (var i = 0; i < 2; i++) { // i= 0, 1
var myDiv = $('<div>');
$('body').append(myDiv);
}
$('div').on('click', function() {
alert($(this).index());
});
}
var myFunc = new MyFunc();
The "alert()" call happens after the for-loop completed, which means that the value of "i" will be the last value for anything after that. In order to capture individual values of "i", you must create a closure for each value by creating a new function:
function MyFunc() {
function alertFn(val) {
return function () {
alert(val);
};
}
for (var i = 0; i < 2; i++) {
var myDiv = $('<div>');
myDiv.click(alertFn(i));
$('body').append(myDiv);
}
}
var myFunc = new MyFunc();
The closure captures the value of "i" at the time it was passed into the function, allowing alert() to show the value you expect.
I can't figure out this scope issue:
var menuLinks = new Array("about.php", "contact.php");
function setClickListeners()
{
for(var i=0; i<menuItems.length; i++)
{
$("#" + menuItems[i]).click( function () {
window.alert(menuLinks[i]);
});
}
}
Notes: menuItems and menuLink is the same length. This code is stripped down to make understanding it easier.
The outcome of this code when an item is clicked is an alert "undefined". It should be the data from menuLinks.
Help!!!!
Frankie
for (var i=0; i < menuItems.length; i++) {
(function(i) {
$("#"+menuItems[i]).click(function() {
alert(menuLinks[i]);
});
}(i));
}
You need to make the current value of i local to your anonymous function in .click.
JavaScript only has function scope. So if you don't make i local then whenever you press click the value of i is the current value which in this case is menuItems.length - 1.
What your doing above is creating a new functional scope and passing the value of i into it so that the current value of i stays constant in that function scope. That way your click function picks up the constant value of i from the closure.
jslint
Let's over complicate the code and satisfy jslint.
var wrapper = function(i) {
$("#"+menuItems[i]).click(function() {
alert(menuLinks[i]);
});
};
for (var i=0; i < menuItems.length; i++) {
wrapper(i);
}
A cleaner code:
var menuLinks = new Array("about.php", "contact.php");
function setClickListeners()
{
$.each(menuLinks, function(i, element)
{
$("#" + menuItems[i]).click( function (e) {
alert(menuItems[i]);
e.preventDefault();
});
}
}
on a website i want to do this: (simplified)
myHandlers = new Array();
for(var i = 0; i < 7; i++) {
myHandlers.push(new Handler({
handlerName: 'myHandler'+i, // works, e.g. ->myHandler1, 2, 3 etc.
handlerFunc: function(bla) { /*...*/ alert(i); } // doesn't work,all return 7
}
}
I could set the counter as another attribute of my Handler (which would copy the current value) and use it inside my function, but I guess, there is also a way to actually copy this value, no?
When handlerFunc is called, the i inside the function refers to the i of the for loop. But that i does probably not have the same value any more.
Use a closure to bind the current value of i in the scope of an anonymous function:
handlerFunc: (function(i) { return function(bla) { /*...*/ alert(i); }; })(i)
Here an anonymous function (function(i) { … })(i) is used and called immediately. This function binds the value of i of the for loop to the local i. That i is then independent from the i of the for loop.
var myHandlers = new Array();
for (var i = 0; i < 7; i++) {
myHandlers.push(new Handler({
handlerName: 'myHandler'+i, // works, e.g. ->myHandler1, 2, 3 etc.
handlerFunc:
(function(i) {
return function(blah) {
alert(i)
}
})(i)
}))
}
Use a closure to bind the i so the value stays intact
In your example, i in the functions is the same variable as i outside the functions. As i is incremented in the loop, so is it incremented within the functions. As a result, if the functions are called after the loop has finished, they will all alert "7".
You need to create a new variable with appropriate scope and copy the value of i into it.
Something like this would create the desired effect.
...
var pushHandler = function(i) {
myHandlers.push(new Handler({
handlerName: 'myHandler'+i, // works, e.g. ->myHandler1, 2, 3 etc.
handlerFunc: function(bla) { /*...*/ alert(i); } // doesn't work,all return 7
}
}
...
for(var i = 0; i < 7; i++) {
pushHandler(i);
}
...
for (var i = 0; i < somearray.length; i++)
{
myclass.foo({'arg1':somearray[i][0]}, function()
{
console.log(somearray[i][0]);
});
}
How do I pass somearray or one of its indexes into the anonymous function ?
somearray is already in the global scope, but I still get somearray[i] is undefined
The i in the anonymous function captures the variable i, not its value. By the end of the loop, i is equal to somearray.length, so when you invoke the function it tries to access an non-existing element array.
You can fix this by making a function-constructing function that captures the variable's value:
function makeFunc(j) { return function() { console.log(somearray[j][0]); } }
for (var i = 0; i < somearray.length; i++)
{
myclass.foo({'arg1':somearray[i][0]}, makeFunc(i));
}
makeFunc's argument could have been named i, but I called it j to show that it's a different variable than the one used in the loop.
How about a closure:
for (var i = 0; i < somearray.length; i++) {
var val = somearray[i][0];
myclass.foo({'arg1': val}, function(v) {
return function() {console.log(v) };
}(val) );
}
for (var i = 0; i < somearray.length; i++)
{
myclass.foo({'arg1':somearray[i][0]}, function(somearray)
{
console.log(somearray[i][0]);
});
}
And then in method foo call anonymous function with param.
You can pass variables values to annoymous function by using callback,
something like
myclass.foo(function(variable){
return function(){
console.log(variable);
}
})(variableValue);
);
check this post: https://shahpritesh.wordpress.com/2013/09/06/javascript-function-in-loop-passing-dynamic-variable-value/
All the functions/methods can be used as callbacks only. When you call the callback function you pass variables to it.
var myclass = {
foo: function(params, callback){
// do some stuff
callback(variable1, variable1, variableN);
}
}