0

I have a little problem: slideHelpers.total = 4

for (i=1;i <= slideHelpers.total; i++) {
    $('<a href="#">' + i + '</a>').bind('click', function(){ alert('go to the ' + i + ' slide')}).appendTo('.slideaccess')
}

the alert gives out 5 what is logic, because when the function click triggers i is actually 5. But i would like to have the same i as in my <a> tag. What is the best way to handle this?

I could put i in the data() of the <a> tag for example but i am sure there is a easier way.

meo
  • 30,872
  • 17
  • 87
  • 123
  • 1
    Welcome to today's Closure Loop Problem question! See eg. http://stackoverflow.com/questions/2568966/how-do-i-pass-the-value-not-the-reference-of-a-js-variable-to-a-function and many more for background. – bobince Apr 22 '10 at 08:42

5 Answers5

2
for (i=1;i <= slideHelpers.total; i++) {
    $('<a href="#">' + i + '</a>').bind('click',
        (function(i){
            // Capture i in closure
            return function(){
                alert('go to the ' + i + ' slide')
            };
        })(i)
    ).appendTo('.slideaccess')
}

Optimised:

var ary = [], i = 0, n = slideHelpers.total,
    open = '<a class="index" href="#">',
    close = '</a>';

// Fill array with numbers: 1,2,3,4,5...
while (++i < n) ary[i] = i + 1;

$('.slideaccess').append(
    open + ary.join(close + open) + close
).delegate('a.index', 'click', function() {
    var index = $.text(this);
    alert('go to the ' + index  + ' slide');
});
James
  • 109,676
  • 31
  • 162
  • 175
  • 1
    `(function(){...})()` acts as a lexical wrapper (or "scope") which is given the **current** value of `i` as an argument. The inner (returned) function is technically a closure, and can see the captured `i`. – James Apr 22 '10 at 08:05
2

You can use an additional function that returns your function:

for (i=1;i <= slideHelpers.total; i++) {
    $('<a href="#">' + i + '</a>').bind('click',
        (function(i) {
            return function() {
                alert('go to the ' + i + ' slide');
            };
         })(i)
     ).appendTo('.slideaccess');
}

With this additional function, the inner i in your alert refers to the argument i of that function and not to the i of the outer scope.

Gumbo
  • 643,351
  • 109
  • 780
  • 844
1

You need to create a new scope, otherwise every function will reference the same i. In JavaScript variables are scoped to functions.

var make_alert_message = function make_alert_message(num) {
    return function () { 
        alert('go to the ' + num + ' slide');
    };
}

for (var i = 1; i <= slideHelpers.total; i++) {
    $('<a href="#">' + i + '</a>').bind(
        'click', make_alert_message(i)
        ).appendTo('.slideaccess')
}
Quentin
  • 914,110
  • 126
  • 1,211
  • 1,335
1

In your code example i is basically a global variable. By the time the alert() code executes, i has the maximum value of the for loop. The standard way to fix this problem in JavaScript is to create a new function which has its own scope to "hold" the variable around. Take for instance this code which returns your event handling function:

(function(i) { // using i as an argument here 'scopes' it
   var something = i; // also within this function scope.
   // inside here, both i and something will only ever reference the "local scope"

   return function() {
     alert(i);
   };
})(i); // and here we are calling that function with i = 1,2,3,...
gnarf
  • 105,192
  • 25
  • 127
  • 161
0

You could use the eventData of the bind function:

for (var i = 1; i <= slideHelpers.total; i++) {
    $('<a href="#">' + i + '</a>').bind('click', { index: i }, function(arg) { 
        alert('go to the ' + arg.data.index + ' slide');
    }).appendTo('.slideaccess');
}
Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • like i told in my question, i am looking for a solution without having to use data(). – meo Apr 22 '10 at 08:09