1

I'm working on creating a table of cells that will change color when each cell is clicked on.

I've created the table and have unique Ids for 'td' element - how do I return the Id of the cell that was clicked so that I can change its background color?

Is it possible to do this with pure JS?

const grid = function makeGrid() {
    var body = document.getElementById('pixelCanvas');
    var tbl = document.createElement('table');
    var tblBody = document.createElement('tbody');

    for (let i = 0; i < 5; i++) {
        let row = document.createElement('tr');
        for (j = 0; j < 5; j++) {
            var cell = document.createElement('td')
            row.appendChild(cell)
            cell.setAttribute('id', 'makeId' + i + j);
        }
        tblBody.appendChild(row);
    }
    tbl.appendChild(tblBody);
    body.appendChild(tbl)
} 
grid();
Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
  • 3
    Possible duplicate of [How to know element clicked within a table?](https://stackoverflow.com/questions/18734736/how-to-know-element-clicked-within-a-table) – Striped Mar 08 '18 at 23:38

3 Answers3

4

Rather than set event listeners on every single td element, use event delegation.

Set a listener on the table body. It will hear the clicks on the td elements. Use the event object's target property to determine which td was clicked. That is the one the change:

document.querySelector('tbody').addEventListener('click', (e)=>{
      e.target.style.backgroundColor = 'pink';
}
Randy Casburn
  • 13,840
  • 1
  • 16
  • 31
  • This will bind the `click` event to every element within the `tbody` element. – Ele Mar 08 '18 at 23:45
  • No, it won't. Please expain what you see. – Randy Casburn Mar 08 '18 at 23:46
  • Yes, it will bind **only** to the `tbody` element. What do you think document.querySelector() returns? – Randy Casburn Mar 08 '18 at 23:48
  • 1
    Your answer is perfect, however the code needs to show how to discard elements different than `TD`. – Ele Mar 08 '18 at 23:51
  • Good idea to suggest event delegation, but also you need to check the *event.target* an if not a TD, go up the *parentNode* tree until you find one (now implemented as [*Element.closest*](https://developer.mozilla.org/en-US/docs/Web/API/Element/closest)). That's the TD the OP wants. ;-) – RobG Mar 08 '18 at 23:55
  • @RobG - I don't disagree, if there were childElements within the TDs (as presented above), you are correct. But given the OP's code, the only possible elements to click upon is a TD or the TextNode inside the TD, which does not have to be isolated. – Randy Casburn Mar 09 '18 at 00:05
  • Awesome, Glad to help. Also glad you accepted Ele's answer - that'll make him feel better :-) – Randy Casburn Mar 09 '18 at 01:15
  • @RandyCasburn—there was a time when some browsers returned text nodes as the event target if that's what was clicked on. Actually seemed like a good idea to me, but things have moved on… – RobG Mar 09 '18 at 02:40
  • You're dating yourself :-) – Randy Casburn Mar 09 '18 at 02:41
2

You need to bind the click event to the created cell

EDITED: using Event delegation

tblBody.addEventListener('click', (e)=>{
    if (e.target.nodeName.toUpperCase() === 'TD') {
       console.log(e.target.id)
       e.target.classList.add('bg');
    }
});

const grid = function makeGrid() {

  var body = document.getElementById('pixelCanvas');

  var tbl = document.createElement('table')
  var tblBody = document.createElement('tbody')

  tblBody.addEventListener('click', (e)=>{
      if (e.target.nodeName.toUpperCase() === 'TD') {
         console.log(e.target.id)
         e.target.classList.add('bg');
      }
  });

  for (let i = 0; i < 5; i++) {
    let row = document.createElement('tr')

    for (let j = 0; j < 5; j++) {
      var cell = document.createElement('td')
      cell.innerHTML = 'makeId' + i + j;
      row.appendChild(cell)
      cell.setAttribute('id', 'makeId' + i + j);
    }
    tblBody.appendChild(row);
  }
  tbl.appendChild(tblBody);
  body.appendChild(tbl)
}
grid();
.bg {
  background-color: lightgreen;
}
<div id="pixelCanvas">
</div>
Ele
  • 33,468
  • 7
  • 37
  • 75
1

As suggested by Randy, rather than put a listener on every TD you can use event delegation and put the listener on a suitable ancestor, either a parent table section (tbody, thead, tfoot) or the table itself. Then use the event object passed to the listener to get the target of the event. If it's not a TD, go up the parent nodes until you reach a TD.

There is now Element.closest implemented in most browsers in use, but not IE. So you might want to use a polyfill or simple "upTo" function for those users.

Also, you can take advantage of the insertRow and insertCell methods of table and table section elements rather than the more long winded createElement. Lastly, you can combine creating the element and appending it in one go.

Anyhow, like this:

 
// Create a table with rows rows and cells columns
function genTable(rows, cells) {
  var table = document.createElement('table');
  // Create tbody and append in one go
  var tbody = table.appendChild(document.createElement('tbody'));
  tbody.addEventListener('click',highlightCell);
  var row, cell;

  // Use insert methods for less code
  for (var i=0; i<rows; i++) {
    row = tbody.insertRow();

  for (var j=0; j<cells; j++) {
      cell = row.insertCell();
      cell.textContent = 'row ' + i + ' cell ' + j;
    }
  }
  // Add entire table to document in one go, easier on host
  document.body.appendChild(table);
}

// Highlight cell that was clicked on
function highlightCell(e){
  // Remove highlight from all cells that have it
  document.querySelectorAll('.highlight').forEach(
    node => node.classList.remove('highlight')
  );
  // Add highlight to cell of td in which event occurred
  var cell = e.target.closest('td');
  cell.classList.add('highlight');
}

window.onload = function(){genTable(3, 3);};
table {
  border-collapse: collapse;
  border-left: 1px solid #bbbbbb;
  border-top: 1px solid #bbbbbb;
}
td {
  border-right: 1px solid #bbbbbb;
  border-bottom: 1px solid #bbbbbb;
}
.highlight {
  background-color: red;
}
RobG
  • 142,382
  • 31
  • 172
  • 209