How to reference a looped element inside a jQuery event [duplicate] - javascript

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 8 years ago.
Script:
for (var i = 0; i < products.length; i++) {
for (var j = 0; j < products.length; j++) {
$(document).on('change', $(products[i][j].checkbox) , function () {
products[i][j].checked ? products[i][j].checked = false : products[i][j].checked = true;
};
}
}
How I cant get products[i][j] in my event function?

Use an anonymous function that's invoked immediately to create a new scope.
This way the anonymous function of the change event becomes a closure.
for (var i = 0; i < products.length; i++) {
for (var j = 0; j < products.length; j++) {
//anonymous function (outer function)
(function() {
var product = products[i][j];
//now the anonymous function of the change event will be called
//within this new scope, with each unique product in it
$(document).on('change', function () {
//this is function is now a closure with scope of the outer function
product.checked ? product.checked = false : product.checked = true;
};
})();
}
}
The scope of the product variable will fall inside the anonymous function closure and each change event will have a unique product value associated.
Fiddle: http://jsfiddle.net/mattdlockyer/t2h50aun/1/
Closures: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Closures
Immediately Invoked Functions: http://en.wikipedia.org/wiki/Immediately-invoked_function_expression
There seems to be another issue with your code, why are you checking document for change?

Related

How to fix scope issues when using an onclick event in a for loop [duplicate]

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 3 years ago.
I am curretly experiencing difficulties implementing an onclick event within a for loop. Instead of alerting the respective value it always returns undefined (presumably a scope problem, because the iteration itself works fine)
Until now I tried to pass on the i variable to the onclick function; however, with little success
for (var i = 0; i < timeSpanLength; i++) {
// creating the wrap for the month
var month = document.createElement("div");
month.className = 'month_element';
var reference_month = document.createElement("span");
reference_month.innerHTML = time_span[i];
//onclick event
reference_month.onclick = function(i) {
var month_beginning = signup_date;
var month_end = time_span[i];
alert(month_end);
//searchForData(month_beginning, month_end);
};
//append to container
month.appendChild(reference_month);
document.getElementById('time_container').appendChild(month);
}
The expected outcome is to trigger an alert which displays the same month which is displayed in the span element above. I need the variable to pass it on to another function.
Any help is highly appreciated since I am beginner in javascript.
for (var i = 0; i < timeSpanLength; i++) {
(function (index) {
// creating the wrap for the month
var month = document.createElement("div");
month.className = 'month_element';
var reference_month = document.createElement("span");
reference_month.innerHTML = time_span[index];
//onclick event
reference_month.onclick = function() {
var month_beginning = signup_date;
var month_end = time_span[index];
alert(month_end);
//searchForData(month_beginning, month_end);
};
//append to container
month.appendChild(reference_month);
document.getElementById('time_container').appendChild(month);
})(i);
}
This callback function handler is forming a closure with respect to the outer scope. Also var has a function scope, so in essence the block of code can be re-written as:
var i;
for (i = 0; i < timeSpanLength; i++) {
...
//onclick event
reference_month.onclick = function(i) {
var month_beginning = signup_date;
var month_end = time_span[i];
alert(month_end);
//searchForData(month_beginning, month_end);
};
...
}
So the var i is hoisted to the top and when the loop completes the value of i is timeSpanLength.length and this is what you use to access time_span[i] and that returns undefined.
Since with var the binding remains the same, the handlers registered will be referring the last value of i in the loop.
So you either need to use let in the for-loop:
for (let i = 0; i < timeSpanLength; i++) { ... }
Or an IIFE which forms a new scope bound to each new value of i from the loop:
for (var i = 0; i < timeSpanLength; i++) {
(function(i){
reference_month.onclick = function(i) {
var month_beginning = signup_date;
var month_end = time_span[i];
alert(month_end);
//searchForData(month_beginning, month_end);
};
})(i)
}

How to get index (or value) of DropDownButton selection

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.

javascript: localise variables in block [duplicate]

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 9 years ago.
I try to understand variable scope in Javascript. Here is what I am doing:
<script>
for (var i = 0; i < 3; i++) {
document.getElementById(i).onclick = function() {
console.log(i);
}
}
</script>
The output is always 3, and I understand that's because i has been retained by reference. How do I localise i so it can log incremented value?
Thanks!
update
Thanks guys for quick and decent responses. the solutions are indeed of help!
Initially, I was trying a similar approach to #GrailsGuy, here it is:
<script>
for (var i = 1; i <= 3; i++) {
document.getElementById(i).onclick = function() {
console.log(logCall(i));
}
}
function logCall(i) {
return i;
}
</script>
But it looks like i isn't being localised. Cannot figure out why!
Create a new scope
for (var i = 0; i < 3; i++) {
(function(i) {
document.getElementById(i).onclick = function() {
console.log(i);
}
}(i));
}
In Javascript, scope is not created by brackets (contrary to the C-like syntax). Scope is however, created in functions, so one way is to extract the call into a function (updated):
<script>
for (var i = 0; i < 3; i++) {
logCall(i);
}
function logCall(i) {
document.getElementById(i).onclick = function() {
console.log(i);
}
}
</script>
This has the added benefit of making the code a bit more reusable.

How to properly pass argument in loop to multiple event handlers? [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Javascript closure inside loops - simple practical example
I add event handlers to multiple hrefs on my website with JS like this:
function addButtonListener(){
var buttons = document.getElementsByClassName("selLink");
for (var i = 0; i < buttons.length; i++)
{
button.addEventListener('click',function() { addTosel(i); },true);
}
}
}
But unfortunately to addTosel is passed the last i not the i from the loop. How to pass i accordingly to the object being processed in this moment?
You need to create a closure:
function addButtonListener(){
var buttons = document.getElementsByClassName("selLink");
for (var i = 0; i < buttons.length; i++) {
button.addEventListener('click', function(index) {
return function () {
addTosel(index);
};
}(i), true);
}
}
This way the scope of the handler is bound to the proper context of i.
See this article for more information on this subject.
You need to bind the i variable to the function when its declared. like so
for (var i = 0; i < buttons.length; i++) {
button.addEventListener('click',(function() { addTosel(this); }).bind(i) ,true);
}
Note: I just wrote the code from memory so it may not be perfect, but it is the sulution you're needing, for reference as to the proper way, ie with cross browser shims etc look at:
https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/bind
If you're going to take the .bind approach, do it like this.
for (var i = 0; i < buttons.length; i++) {
button.addEventListener('click', addTosel.bind(null, i), true);
}
This makes a new function with null bound as the this value since your function doesn't seem to need it, and the current i bound as the first argument.
Or make your own binder function
var _slice = Array.prototype.slice;
function _binder(func, ctx /*, arg1, argn */) {
var bound_args = _slice.call(arguments, 2);
return function() {
return func.apply(ctx, bound_args.concat(_slice.call(arguments)));
}
}
And then do this.
for (var i = 0; i < buttons.length; i++) {
button.addEventListener('click', _binder(addTosel, null, i), true);
}

javascript: Using the current for-loop counter-value inside a function() { }?

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

Categories

Resources