views:

73

answers:

2

I seem to have an issue when creating copies of a template and tying the .click() method to them properly. Take the following javascript for example:

function TestMethod() {
    var test = Array();
    test[0] = 0;
    test[1] = 1;
    test[2] = 2;

    // Insert link into the page
    $("#test_div").html("<a href=\"#\"></a><br>");
    var list;
    for (x = 0; x < test.length; x++) {
        var temp = $("#test_div").clone();
        temp.find('a').html("Item #" + test[x]);
        temp.click(function () { alert(x); });

        if (list == undefined)
            list = temp;
        else
            list = list.append(temp.contents());
    }
    $("#test_div2").append(list);
 }

The problem I am seeing with this is that no matter which item the user clicks on, it always runs alert(2), even when you click on the first few items.

How can I get this to work?

Edit: I have made a very simple example that should show the problem much clearer. No matter what item you click on, it always shows an alert box with the number 2 on it.

+1  A: 

Correct me if I'm wrong, .valueOf() in JS returns the primitive value of a Boolean object.....

this would not happen ShowObject(5,'T');... ShowObject(objectVal.valueOf(), 'T');

why not use objects[x].Value directly? ShowObject(objects[x].Value, 'T');

WOOOOOSSSHHHH!

after searching deeply... I found a solution...

because it's a closure, it won't really work that way...
here's a solution,

temp.find('a').bind('click', {testVal: x},function (e) { 
   alert(e.data.testVal);
   return false;
});

for best explanation, please read this... in the middle part of the page where it says Passing Event Data
a quick demo of above code

Reigel
I tried objectVal, objectVal.ValueOf(), etc.. It all seems to produce the same result, only the last objectVal value is used for all of the cloned objects.
KallDrexx
is there a difference between `objects[x].Value` and `objects[x].Text` in your codes?
Reigel
Yes, value returns my Id number and Text returns the name of the object. Both are being read correctly (tested in Chrome's debugger)
KallDrexx
Ah I see, I knew there had to be a better way to pass the value in, I just didn't know I could do it that way!
KallDrexx
+1  A: 

I think your issue arises from a misunderstanding of scopes in JavaScript. (My apologies if I'm wrong.)

function () {
    for (...) {
        var foo = ...;

        $('<div>').click(function () { alert(foo); }).appendTo(...);
    }
}

In JavaScript, only functions create a new scope (commonly referred to as a closure).

So, every round of the for loop will know the same foo, since its scope is the function, not the for. This also applies to the events being defined. By the end of looping, every click will know the same foo and know it to be the last value it was assigned.

To get around this, either create an inner closure with an immediately-executing, anonymous function:

function () {
    for (...) {
        (function (foo) {
            $('<div>').click(function () { alert(foo); }).appendTo(...);
        })(...);
    }
}

Or, using a callback-based function, such as jQuery.each:

function () {
    $.each(..., function (i, foo) {
        $('<div>').click(function () { alert(foo); }).appendTo(...);
    });
}

For your issue, I'd go with the latter (note the changes of objects[x] to just object):

var list;
jQuery.each(data.objects, function (x, object) {

    // Clone the object list item template
    var item = $("#object_item_list_template").clone();

    // Setup the click action and inner text for the link tag in the template
    var objectVal = object.Value;
    item.find('a').click(function () { ShowObject(objectVal.valueOf(), 'T'); }).html(object.Text);

    // add the html to the list
    if (list == undefined)
        list = item;
    else
        list.append(item.contents());
});
Jonathan Lonowski
Ok this makes a lot of sense! I will try this when I get home from work, thanks!
KallDrexx