0

If I create a bunch of objects in a loop like so:

for(var i = 0; i < 5; i++)
{
    var data = { val: i * 2 };
    var obj = $('<li>Item</li>');

    $("ul").append(obj);

    obj.click(function()
    {
        alert(data.val);
        // Output is 8 for all items.

    });
}

I am only able to access the last created data object within the .click() handler.

How can I refer to the relevant data object within my handler - the one that was created at the same time as the jQuery object being clicked?

I've been able to get around this in the past by assigning random attributes on the jQuery object via .attr() to hold data, rather than a separate object, but I'd like to move away from that approach.

I've also tried this but with no luck:

var data = { val: i * 2 };
var obj = $('<li>Item</li>');

obj.data = data;

obj.click(function()
{
    // 'data' is undefined here i.e. not associated with $(this).
    alert($(this).data.val);

});
Marty
  • 39,033
  • 19
  • 93
  • 162

2 Answers2

2

The problem with the first example is obscured by the scope of the variables. In JS all variables have function scope so rewriting the code to show that more explicitly would result in:

var i,data,obj;
for(i = 0; i < 5; i++) {
    data = { val: i * 2 };
    obj = $('<li>Item</li>');

    $("ul").append(obj);
    obj.click(function() {
        alert(data.val);
    });
}

Now it's more obvious that data inside the loop is just one variable and not a new for each iteration (as in would be in Java, C# and the like). The anonymous function passed as the click handler uses this variable, it closes over it. The result of that is that the current value of the variable when the function is executed is used, not the value the variable had when the function is assigned as a click handler.

To get around this you can either attach the data to the element itself.

In your code you do obj.data = ... however that attaches the value to the selection and not the element. Next time you select the same element you have a new object without the data.property. Instead you can use the jQuery .data() method.

obj.data('key',data);
obj.click(function() {
    alert($(this).data('key').val);
});

or if you would want to embed the value into the function you can do that with a self executing function. When you pass the variable as an argument to a function you are using the current value instead of closing over the variable.

var i,
    data,
    obj,
    createClick = function(data) {
        return function(){
           alert(data.val);
        };
    };
for(i = 0; i < 5; i++) {
    data = { val: i * 2 };
    obj = $('<li>Item</li>');

    $("ul").append(obj);
    obj.click(createClick(data));
}

Note As an aside you should get into the habit of putting { at the end of a line not the begining. Semi colon insertion can have some unexpected results if { is on the next line. E.g

return 
    {
      val : 1
    }

might return undefined because a semi colon might be inserted after return ignoring the object literal

Rune FS
  • 21,497
  • 7
  • 62
  • 96
2

You can use the jQuery .data() method. http://api.jquery.com/data/

For example:

for(var i = 0; i < 5; i++){
    var obj = $('<li>Item</li>');
    obj.data('myData', {first: i * 2, second: i * 3});

    $("ul").append(obj);

    obj.click(function(){
        alert($(this).data('myData').first);
        alert($(this).data('myData').second);
    });
}
Matthew Blancarte
  • 8,251
  • 2
  • 25
  • 34