0

I try to dynamically generate <table> with jQuery and I want to set click handlers to each cell, so when cell clicked the popup will appear with current index of cell. How I can access to CURRENT i and j variables in loop?

  for(var i = 0; i < 5; i++) {
      var tr = $('<tr></tr>');
      for (var j = 0; j < 5; j++) {
          var td = $('<td></td>');
          td.click(function() {
            alert(i + ' ' + j);  // here I want to access to CURRENT i, j variables
          })
          td.appendTo(tr);
      }
  }    
WelcomeTo
  • 19,843
  • 53
  • 170
  • 286
  • I'd delegate event to table instead – A. Wolff Sep 01 '13 at 17:22
  • 1
    FYI, you can always get the index of table rows and cells right from the element. So in your handler, you could do `this.cellIndex` to get the index for the ``. For the row, you could do `this.parentNode.rowIndex` as long as your loop indices align with the row indices. – user2736012 Sep 01 '13 at 17:29
  • @user2736012 ah, thanks for the reminder about `.cellIndex` and `.rowIndex` - I had forgetten that tables have those, whereas other elements require you to count back through the siblings. – Alnitak Sep 01 '13 at 17:42

5 Answers5

2

Introducing them into a new scope will capture their current value:

(function(i, j) {
    td.click(function() {
        alert(i + ' ' + j);  // here I want to access to CURRENT i, j variables
    });
})(i, j);
Blender
  • 289,723
  • 53
  • 439
  • 496
2

You can create a new scope for the 'current' value of (i, j) by executing a function inside the loop like this

td.click((function (i, j) {
    return function (event) {
        console.log(i, j);
    }
})(i, j));

You could make this a little more succinct by using Function.prototype.bind

td.click(function(i, j, event){
  console.log(i, j);
}.bind(td, i, j));

Note This .bind solution requires ECMAScript 5, so if you want to support older browsers, please look into es5-shim


See this demo

Mulan
  • 129,518
  • 31
  • 228
  • 259
zs2020
  • 53,766
  • 29
  • 154
  • 219
2

The other answers mostly explain how to fix the closure problem, although such methods are not particularly memory efficient since they end up creating a new closure for each possible combination of i and j.

However, since you're using jQuery you have a number of other options available to you:

Pass data parameters to the .on call

td.on('click', { i: i, j: j }, function(event) {
    var i = event.data.i;
    var j = event.data.j;
    alert(i + ' ' + j);
});

The i and j values as you can see above will be available in event.data

Use $.each to iterate instead of a for

$.each(Array(5), function(i) {
    // i is already bound here because it's a function parameter
    $.each(Array(5), function(j) {
        // and j available here
        td.on('click', ...);
    });
});

Use event delegation instead of a per-element handler

$('#myTable').on('click', 'td', function() {
    // use jQuery methods to determine the index, e.g.
    var j = this.cellIndex
    var i = this.parentNode.rowIndex
    ...
});

which is more efficient than binding a separate handler to each <td> individually.

and more generically, use .data() to store per-cell information

You can store data values directly on elements, which would work very well if you wanted to retrieve values other than the cell and row indexes:

td = $('<td>').data({i: i, j: j});

and then extract these row and column values directly from the clicked element's .data:

for (var i = 0; i < 5; i++) {
    var tr = $('<tr></tr>');
    for (var j = 0; j < 5; j++) {
        $('<td></td>').data({i: i, j: j}).appendTo(tr);
        td.appendTo(tr);
    }
}

$('#myTable').on('click', 'td', function() {
    var data = $(this).data();
    var i = data.i;
    var j = data.j;
    alert(i + ' ' + j);
});
Alnitak
  • 334,560
  • 70
  • 407
  • 495
0

Other option can be storing data within the elements.

  for(var i = 0; i < 5; i++)
  {
      var tr = $('<tr></tr>');
      for (var j = 0; j < 5; j++) 
      {
          var td = $('<td></td>');
          this.data("position",{row:i, column:j});

          td.click(function() 
          {
            var position = this.data("position");
            alert(position.row + ' ' + position.column);  // here I want to access to CURRENT i, j variables
          })

          td.appendTo(tr);
      }
  } 
Tomas Santos
  • 560
  • 4
  • 12
0

I would store the indexes as attributes and add a single click handler to the table :

var tableHtml = '';

for(var i=0; i<5; i++) {
    tableHtml += '<tr>';

    for (var j=0; j<5; j++) {
        tableHtml += '<td data-i="' + i + '" data-j="' + j + '"></td>';
    }

    tableHtml += '</tr>';
}

$('table').append(tableHtml).on('click', 'td', function(ev) {
    var i = this.getAttribute('data-i'),
        j = this.getAttribute('data-j');
});
adjogima
  • 169
  • 3
  • nice idea, poor implementation (IMHO). jQuery provides perfectly good methods to directly access per-element data, and there's no need for the data to ever appear in the serialised HTML representation. – Alnitak Sep 01 '13 at 17:33
  • True. I would happily use html5's dataset but it's not available in IE8-, that's why jQuery uses custom data(), for the sake of retro-compatiblity. Data-attributes are perfectly standard and integrated with HTML5's dataset. jQuery's data() can leave some dirt when deleting elements. Or it used to... Maybe not anymore ? – adjogima Sep 01 '13 at 17:51
  • The only association between HTML5 `data-XXX` attributes and jQuery's `.data()` is that the initial values of the former get copied into the latter. Apart from that there's no linkage. – Alnitak Sep 01 '13 at 17:56
  • Not what I mean : dataset is not available in older browsers so jQuery uses it's own custom storage (which we access through data()) to provide a similar feature to every browser. – adjogima Sep 01 '13 at 18:05
  • I'm kind of a use-standard-native-methods maniac sometimes ;) – adjogima Sep 01 '13 at 18:17