0

I'm trying to create a bunch of buttons via loop, and use the loop iterator as an argument for each of the button onclick functions.

My code:

var testFnc = function(i) { alert("Arg: " + i); }

mainDiv.append(create_button(name, "buttonCSS", testFnc(i)));

However the functions are called automatically as the page loads and the buttons are placed (i.e. I see the alerts right away).

I'm sure there's some common design pattern for this.

Thanks!

JDS
  • 16,388
  • 47
  • 161
  • 224
  • possible duplicate of [Pass extra parameter to jQuery getJSON() success callback function](http://stackoverflow.com/questions/6129145/pass-extra-parameter-to-jquery-getjson-success-callback-function) – ErikE Jul 31 '12 at 20:57

3 Answers3

3

One approach is to, for each button, call a "self-executing" function that creates a separate function.

mainDiv.append(create_button(name, "buttonCSS", (function(i) {

    // For the function below, i comes from
    // this function's scope, not the outside scope.

    return function() {
        testFnc(i);
    };

})(i) ));

This will allow you to change the value of i outside the function and leave existing buttons unaffected.

(If you are creating a lot of buttons (perhaps thousands), it might be better to change the create_button function to add a property to the button element itself and have your function check that. Or if your code does not need to work in Internet Explorer 8 or below, you can use the bind() function instead of the above code.)

PleaseStand
  • 31,641
  • 6
  • 68
  • 95
  • Thank you. And this can be extended easily to multiple arguments, right? – JDS Jul 31 '12 at 21:10
  • @YoungMoney: Yes, just add the additional parameters. In the above code, you would need to replace all three occurrences of `(i)` with `(i, j)` to add a new parameter `j`. – PleaseStand Jul 31 '12 at 21:27
2

Note: for a very detailed explanation of this, please see my previous answer to an identical question.

  1. By using parentheses, you're calling testFnc and passing its return value to the create_button function, so of course it alerts right away. Instead, you need to pass in an actual function object without invoking it. Do this by wrapping testFnc in an anonymous function, ala function() { testFunc(i); }. Can you see how this returns a function that will be run later rather than immediately?
  2. This by itself still won't work, since i participates in a closure around it, so that when the click event runs it uses the most recent value of i (the value it was at the end of the loop), rather than the one at the time of binding. To fix this, create a new closure around a different variable—positioned only in the immediate scope of your callback and no higher—that will be given the value of i so that the click event has the value as of the time of binding, not the time of execution of the bound function.

    Here's how to do this:

    for (i = 0; i < len; i++) {
       mainDiv.append(
          create_button(name, "buttonCSS", (function(val) {
             return function() {
                testFnc(val);
             };
          }(i)))
       );
    }
    

    I'm sure that looks a bit confusing, but the point is that wrapping the function that is called (the one that is returned) in another function creates a new scope with a variable val that isn't used in any outer function. This makes the closure only over the immediate value, detaching it from i, which is what you want. There is a deeper explanation of this here.

Please note that one other answer on this page reuses i which could be confusing. That will work, but then it is unclear inside of the inner function which i is being referred to! I think it is better practice to avoid any confusion by using a different variable name.

Community
  • 1
  • 1
ErikE
  • 48,881
  • 23
  • 151
  • 196
0

Are you using jQuery? You could reference the class of a button

<button class='my-custom-class' data-index='0'>This button</button>

and in document.ready:

$(document).ready(function () {   
  buildButtons();

  $('.my-custom-class').on('click', function () { 
    var index = $(this).attr('data-index');
    alert('Arg: ' + index);
  });
});

I suggest using a custom data-* attribute since you can use the attr property in jQuery to access the custom attribute. This is one way you could implement it.

GitaarLAB
  • 14,536
  • 11
  • 60
  • 80
curiousdork
  • 1,034
  • 2
  • 12
  • 13