Closure trouble: passing "event" to named function - javascript

I'm debugging a bit of Javascript that was suffering from a little closure trouble - but don't seem to be able to pass the "event" argument into the function.
Here's the problem (in shorthand):
// let's say links.length == 3
for(var i = 0; i < links.length; i++){
links[i].onclick = function(e){
alert(i); //closure! all links alert "3"
// do something with "e"
}
}
Here's my solution
//workaround
// define function outside of loop
function outer(e,i){
return function(){
alert(i); //closure! all links alert "3"
// do something with "e"
}
}
for(var i = 0; i < links.length; i++){
links[i].onclick = outer(e,i); //uh oh! e = undefined???
}
In my workaround, I've defined a function outside the loop to prevent closure - but I am unable to pass the "e" argument to it. Can someone point me in the right direction?

Define it in the returned function.
function outer(i){
// ------------v-- event object is passed when this function is invoked
return function( e ){
alert(i);
}
}
for(var i = 0; i < links.length; i++){
links[i].onclick = outer(i);
}
The event object gets passed when the event occurs, so it needs to be defined as a parameter to the function that is ultimately assigned as the handler (the function you're returning from outer()).

Related

alert -1 instead of their respective counter inside of the loop?

Why do the anchors, when clicked on, alert -1 instead of their respective counter inside of the loop? How can you fix the code so that it does alert the right number?
text<br>link
<script>
var as = document.getElementsByTagName('a');
for ( var i = as.length; i--; ) {
as[i].onclick = function() {
alert(i);
return false;
}
}
</script>
That's a closure problem: every anonymous function you define inside your loop reads the value of "i" at the end of the for loop.
You need another scope, for example calling a function that sets the onclick handler.
function add_onclick(el, i) {
el.onclick = function() {
alert(i);
return false;
}
}
var as = document.getElementsByTagName('a');
for ( var i = as.length; i--; ) {
add_onclick(as[i],i);
}
An alternative way to achieve the same result as Keeper's answer is to use a function expression for the closure, which you immediately invoke, using your value as a parameter to create the closure
var as = document.getElementsByTagName('a');
for ( var i = as.length; i--; ) (function (i) {
as[i].onclick = function() {
alert(i);
return false;
}
}(i));
This way requires minimal code refactoring
You are printing the value of variable i. After the loop, it's value is -1.

Canot get the href value

Hi I need to valid the href is empty or not on my page using javascript. I searched the site and found some example, but it didn't worked for me. I must miss something that I didn't notice. Would someone point me the good direction and my mistake. I got the error" Unable to get property 'getattribute' of undefined or null reference. The <a> element is like that <a name="playback" href=""> on html file.
Thanks in advance.
There is my code which is run on load event:
var anchors = document.getElementsByTagName("a");
for (var i = 0; i < anchors.length; i++)
{
anchors[i].onclick = function() {
if (anchors == null) {
alert('null');
}
else {
var link = anchors[i].getAttribute("href");
//var link= anchors[i].attributes['href'] this line doesn't work too.
}
}
}
}
In your code, the call to getAttribute is inside a closure (that is, a function defined "inline" without a name) that is assigned to the onlick event handler of the link. Therefore that code isn't execxuted right away - it doesn't run before the onclick handler triggers.
When the onclick header triggers, two things are passed to the callback function: the element on which the event was triggered is assigned to the this variable of the functions context - and the event itself is passed as first parameter. anchors however is undefined in the scope of that callback.
So, use either of those:
anchors[i].onclick = function () {
var link = this.getAtrribute("href");
}
 
anchors[i].onclick = function (event) {
var link = event.target.getAttribute("href");
}
You have got a scope problem.
The following code will output 3:
for (var i = 0; i < 3; i++) {
}
console.log(i); // 3
Similar to the example above your onclick is fired after the loop is done.
So i in your example would equal to anchors.length.
And anchors[anchors.length] === undefined.
To solve this problem you have to create a new scope.
For example you could use an Immediately-Invoked Function Expression (IIFE):
var anchors = document.getElementsByTagName("a");
for (var i = 0; i < anchors.length; i++)
{
(function(j){
anchors[j].onclick = function() {
if (anchors == null) {
alert('null');
}
else {
var link = anchors[j].getAttribute("href");
}
}
}
}(i));
}
You need to use closure if you want to do it this way since you are using the shared i variable which would have been having last value of iteration when your handler runs on click. But since you are looking at that particular anchor, try binding it with bind an event listener and access it using this.href:
You can use addEventListener and for older browser support attachEvent
var anchors = document.getElementsByTagName("a");
for (var i = 0; i < anchors.length; i++) {
anchors[i].addEventListener('click', function () {
var link = this.getAttribute("href");
})
};
Demo
Or :
var anchors = document.getElementsByTagName("a");
for (var i = 0; i < anchors.length; i++) {
anchors[i].onclick = getHandler(i);
}
function getHandler(i) {
return function () { //Now each of your handler has its own `i`
var link = anchors[i].getAttribute("href");
}
}
Demo
I have never seen getAttribute before so I performed some tests on it. It turns out that href and getAttribute("href") are quite different. Namely href is the absolute url and getAttribute("href") is the relative url to the page. See this fiddle.
The problem with your code is that the var is captured in the closure of onclick and when the onclick function runs the value will of i will be anchors.length.
Solution, Scratch that use the code from Johannes H. His is better
var anchors = document.getElementsByTagName("a");
for (var i = 0; i < anchors.length; i++) {
(function () {
var current = anchors[i]; //Capture the anchor element
current.onclick = function() {
var link = current.getAttribute("href");
};
} ());
}
See this w3 schools page for how to get the href attribute from anchor tags.
http://www.w3schools.com/jsref/prop_anchor_href.asp
var anchors = document.getElementsByTagName("a");
for (var i = 0; i < anchors.length; i++)
{
anchors[i].onclick = function() {
if (anchors == null) {
alert('null');
}
else {
var link = this.href;
}
}
}

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

Scope troubles in Javascript when passing an anonymous function to a named function with a local variable

Sorry about the title - I couldn't figure out a way to phrase it.
Here's the scenario:
I have a function that builds a element:
buildSelect(id,cbFunc,...)
Inside buildSelect it does this:
select.attachEvent('onchange',cbFunc);
I also have an array that goes:
var xs = ['x1','x2','x3'...];
Given all of these, I have some code that does this:
for(var i = 0; i < xs.length; i++)
{
buildSelect(blah,function(){ CallBack(xs[i],...) },...);
}
The issue is that when onchange gets fired on one of those selects it correctly goes to CallBack() but the first parameter is incorrect. For example if I change the third select I expect CallBack() to be called with xs[2] instead I get some varying things like xs[3] or something else.
If I modify it slightly to this:
for(var i = 0; i < xs.length; i++)
{
var xm = xs[i];
buildSelect(blah,function(){ CallBack(xm,...) },...);
}
I'm still getting incorrect values in CallBack(). Something tells me this is scope/closure related but I can't seem to figure out what.
I simply want the first select to call CallBack for onchange with the first parameter as xs[0], the second select with xs[1] and so on. What could I be doing wrong here?
I should clarify that xs is a global variable.
Thanks
You need to capture that xm value by closing around it in its own scope.
To do this requires a separate function call:
buildCallback( curr_xm ) {
// this function will refer to the `xm` member passed in
return function(){ CallBack(curr_xm,...) },...);
}
for(var i = 0; i < xs.length; i++)
{
var xm = xs[ i ];
buildSelect(blah,buildCallback( xm ),...);
}
Now the xm that the callback refers to is the one that you passed to buildCallback.
If you have other uses for i that need to be retained, you could send that instead:
buildCallback( curr_i ) {
// this function will refer to the `i` value passed in
return function(){ CallBack( xs[ curr_i ],...) },...);
}
for(var i = 0; i < xs.length; i++)
{
buildSelect(blah,buildCallback( i ),...);
}
The problem is indeed scope-related -- JavaScript has only function scope, not block scope or loop scope. There is only a single instance of the variables i and xm, and the value of these variables changes as the loop progresses. When the loop is done, you're left with only the last value that they held. Your anonymous functions capture the variables themselves, not their values.
To capture the actual value of a variable, you need another function where you can capture the local variable:
function makeCallback(value) {
return function() { CallBack(value, ...) };
}
Each call to makeCallback gets a new instance of the value variable and if you capture this variable, you essentially capture the value:
for(var i = 0; i < xs.length; i++)
{
buildSelect(blah,makeCallback(xs[i]),...);
}
Yes, I think a closure would help:
for(var i = 0, l = xs.length; i < l; i++)
{
buildSelect(
blah,
function(xm){
return function(){
CallBack(xm,...)
};
}(xs[i]),
...
);
}
Edit: I also optimised your for loop slightly.
Edit: I guess I'll add an explanation. What you're doing is creating an anonymous function which takes one argument (xm) and calling the function straight away (with the parenthesis right after). This anonymous function must also return your original function as an argument of buildSelect().
Apparently there is a new let keyword that does what you want:
for(var i = 0; i < xs.length; i++)
{
let xm = xs[i];
buildSelect(blah,function(){ CallBack(xm,...) },...);
}

Arguments to JavaScript Anonymous Function

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

Categories

Resources