Take the 2-minute tour ×
Stack Overflow is a question and answer site for professional and enthusiast programmers. It's 100% free, no registration required.

I am trying to assign a series of objects stored in an array to jquery click event handlers.

The problem is , when the event fires, I only ever references the last object in the array.

I have put together a simple example to show the problem:

function dothis() {
    this.btns = new Array('#button1', '#button2');
}

// Add click handler to each button in array:
dothis.prototype.ClickEvents = function () {

    //get each item in array:
    for (var i in this.btns) {

        var btn = this.btns[i];
        console.debug('Adding click handler to button: ' + btn);

        $(btn).click(function () {
            alert('You clicked : ' + btn);
            return false;
        });
    }
}

var doit = new dothis();
doit.ClickEvents();

The HTML form contains a couple of buttons:

<input type="submit" name="button1" value="Button1" id="button1" />
<input type="submit" name="button2" value="Button2" id="button2" />

When button1 is clicked, it says "You clicked #Button2"

It seems that both button click handlers are pointing to the same object inside var btn.

Considering the variable is inside the for loop, I cannot understand why.

Any ideas?

share|improve this question
1  
JavaScript only have function scope, not block scope. Just because it looks like that variable is inside your loop, its really bound to the function's scope. So by the time your handlers get called btn will be pointing at whatever it was pointing at when the loop ended. The anonymous functions keep the ClickEvents function's scope alive. Once you understand that, it should be pretty obvious how to fix it. –  32bitkid Sep 23 '11 at 15:11
    
Also there are ton of questions/answers related to this all over StackOverflow. Its probably one of the first things that trip people up when starting functional programming in JS. –  32bitkid Sep 23 '11 at 15:12

5 Answers 5

up vote 3 down vote accepted

You need a function factory to close the loop variable, such as this:

//get each item in array:
for (var i=0; i<this.btns.length; i++) {

    $(this.btns[i]).click(function(item) { 
        return function () {
            alert('You clicked : ' + item);
            return false;
        }
    }(this.btns[i]));

}

Another good option is to let jquery help you. Use jQuery.each(). The variable btn here is local to the handler function, and so isn't reused between iterations. This allows you to close it and have it keep its value.

$.each(this.btns, function() {
    var btn = this;
    $(this).click(function () {
        alert('You clicked : ' + btn);
        return false;
    }
});
share|improve this answer
    
I couldnt get this to work. –  geo1701 Sep 23 '11 at 15:19
    
It requires little else but paste. jsfiddle.net/swVa7 –  Mike Haboustak Sep 23 '11 at 15:22
    
thanks, but i cant get it to work when i put real objets in the array. maybe a better example would be: jsfiddle.net/swVa7/2 –  geo1701 Sep 23 '11 at 15:40
1  
That's a pretty confusing example. jsfiddle.net/swVa7/3 –  Mike Haboustak Sep 23 '11 at 15:44
    
You are a genius, thank you so much. The real world code is much easier lol. –  geo1701 Sep 23 '11 at 15:58

within an event handler, 'this' usually refers to the element firing the event, in this case, it would be your button

so the solution to your problem is fairly easy, instead of referencing the btn variable, which lives in a higher scope and gets mutated long before the event handler fires, we simply reference the element that fired the event and grab its ID

$(btn).click(function () {
  alert('You clicked : #' + this.id);
  return false;
});

Note: if your array contains other selectors that just the ID, this will obviously not reflect that and simply continue to show the ID

Lucky, the click handler (and all other event handlers afaik) take an extra parameter for eventData, useful like so:

$(btn).click(btn, function (event) {
  alert('You clicked : #' + event.data);
  return false;
});

User an array if you're passing multiple things:

$(btn).click(['foo', 'bar'], function (event) {
  alert('this should be "foo": ' + event.data[0]);
  alert('this should be "bar": ' + event.data[1]);
  return false;
});
share|improve this answer
    
Thats why its important to read the docs carefully. thanks for the nice explanation –  Aamir Afridi Oct 28 '11 at 11:46

Your problem is here:

alert('You clicked : ' + btn);

btn retains the value from the last time it was called in the loop. Read the value from the button in the event.

    $(btn).data('selector', btn).click(function () {
        alert('You clicked : ' + $(this).data('selector'));
        return false;
    });

http://jsfiddle.net/Mc9Jr/1/

share|improve this answer
    
I am trying to get the value that is stored inside the array, not the actual button name. In my real world code , I store objects inside the array and I want to pass one of those objects to a function when the button is clicked. –  geo1701 Sep 23 '11 at 14:48
    
change .val() to .attr('id'). If they are not always ids, you will have to put a data item on the button with the value. –  BNL Sep 23 '11 at 14:49
    
jsfiddle.net/Mc9Jr/1 Gets the value in the array regardless of what selector type it is. –  BNL Sep 23 '11 at 14:51
    
The data method should work for you regardless of what you are actually storing. –  BNL Sep 23 '11 at 14:53
    
this doesnt work in my real world example because I am trying to get the value of the object in the array , not the button name. –  geo1701 Sep 23 '11 at 14:56

maybe you need to change just the click binding:

$(btn).click(function () {
    alert('You clicked : ' + $(this).attr('id'));
    return false;
});
share|improve this answer

you have to use closures for this. i'm not sure if i remember the correct syntax but you could try this:

$(btn).click(function () {
    return function() {
        alert('You clicked : ' + btn);
        return false;
    }
});
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.