Task
Use loop to assign onclick handler for each table row in DOM. Use Closure.
function example4() {
let table = document.getElementById("tableid4");
let rows = table.rows;
for (let i = 0; i < rows.length; i++) {
// Using a closure in a modern way
((row, index) => {
row.addEventListener('click', () => {
alert(`row ${index} data=${row.querySelector('td').textContent}`);
});
})(rows[i], i);
}
}
window.addEventListener('DOMContentLoaded', example4);
<table id="tableid4" border="1">
<tbody>
<tr>
<td>Item one</td>
</tr>
<tr>
<td>Item two</td>
</tr>
<tr>
<td>Item three</td>
</tr>
</tbody>
</table>
However with let there is no need for a closure
function example4() {
let table = document.getElementById("tableid4");
let rows = table.rows;
for (let i = 0; i < rows.length; i++) { // the "let" does the job
rows[i].addEventListener('click', function() {
alert(`row ${i} data=${this.querySelector('td').textContent}`);
});
}
}
window.addEventListener('DOMContentLoaded', example4);
<table id="tableid4" border="1">
<tbody>
<tr>
<td>Item one</td>
</tr>
<tr>
<td>Item two</td>
</tr>
<tr>
<td>Item three</td>
</tr>
</tbody>
</table>
This brings me to a pet peeve of mine: looping eventListeners
If we delegate, the script is simpler and easier to read
const items = ["Item one", "Item two", "Item three"];
window.addEventListener('DOMContentLoaded', () => {
// create a table using an array. This can be as complex as needed depending on the data.
document.getElementById('container').innerHTML =
`<table id="tableid4" border="1">
<tbody>
${items
.map((item,i) => `<tr data-idx="${i}"><td>${item}</td></tr>`)
.join('')}
</tbody>
</table>`;
document.querySelector('#tableid4 tbody')
.addEventListener('click', (e) => {
const tgt = e.target.closest('tr');
if (!tgt) return; // not clicking in a row
alert(`row ${tgt.dataset.idx} data=${tgt.querySelector('td').textContent}`);
});
});
<div id="container"></div>
This was the canonical way in 2012
By using a closure (function() {...})(i), you're creating a new scope where the current value of i is passed and preserved in cnt for each iteration. This way, each click handler retains its own separate cnt value, which corresponds to the row index at the time the handler was created.
function example4() {
var table = document.getElementById("tableid4");
var rows = table.rows; // or table.getElementsByTagName("tr");
for (var i = 0; i < rows.length; i++) {
rows[i].onclick = (function() { // closure
var cnt = i; // save the counter to use in the function
return function() {
alert("row"+cnt+" data="+this.cells[0].innerHTML);
}
})(i);
}
}
window.onload = function() { example4(); }
@ParkerSuperstar suggested that the i in (i) is not needed.
for (var i = 0; i < rows.length; i++) {
rows[i].onclick = (function() {
var cnt = i;
return function() {
alert("row" + cnt + " data=" + this.cells[0].innerHTML);
}
})();
}
That is because the closure (function() {...})() is immediately invoked at each iteration of the loop. This creates a new execution context for each iteration.
Within this closure, var cnt = i; captures the current value of i for each iteration. Because a new closure is created for each iteration of the loop, each closure has its own execution context, and thus its own cnt variable.
onclickto call the returned function.