1

I have a table with some of its columns containing text that is gonna be copied quite often. Despite knowing no javascript, I adventured myself into the challenge of making the content copyable on click and managed to get this working:

function copy(event) {
  console.log("Triggered")
  var target = event.target || event.srcElement;
  console.log("Targeted")
  var copyText = target.innerText || target.textContent;
        navigator.clipboard.writeText(copyText)
}

var plist = document.querySelectorAll("p");
for (var i=0; i < plist.length; i++) {
  plist[i].addEventListener("click", copy)
}
<!DOCTYPE html>
<html>
<body>
  <h1>Copy selector Test</h1>
  <p>Hi There.</p>
  <p>I want to copy</p>
  <p>each of these p's</p>
  <p>by clicking them.</p>
</body>
</html>

Well, considering my javascript knowledge, I'm pretty sure that isn't the most efficient or correct way of doing it (looping each element and adding an eventlistener to each one seems a bit of unnecesary work to me). Any correction is welcome.

Now let's say I want each of the cells of the "Last" column to be copied in the same way that the p's were copied in my snippet. How could I get it done? It would be also cool to highlight the text that's been copied (I tried with .select() but apparently that only works with input elements). The table is gonna be populated dynamically with django, by the way.

<table class="table">
  <thead>
    <tr>
      <th scope="col">#</th>
      <th scope="col">First</th>
      <th scope="col">Last</th>
      <th scope="col">Handle</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th scope="row">1</th>
      <td>Mark</td>
      <td>Otto</td>
      <td>@mdo</td>
    </tr>
    <tr>
      <th scope="row">2</th>
      <td>Jacob</td>
      <td>Thornton</td>
      <td>@fat</td>
    </tr>
    <tr>
      <th scope="row">3</th>
      <td>Larry</td>
      <td>the Bird</td>
      <td>@twitter</td>
    </tr>
  </tbody>
</table>

Thanks a lot guys!

JP.

UPDATE: In case this is useful to someone, I've managed to make a column copyable on click by adding a "copyable" class to its . This is the code:

function indexInParent(node) {
  var children = node.parentNode.childNodes;
  var num = 0;
  for (var i=0; i<children.length; i++) {
    if (children[i]==node) return num+1;
    if (children[i].nodeType==1) num++;
  }
  return -1;
}

var columns = document.getElementsByClassName("copyable")

for (var i=0; i < columns.length; i++) {
  var column = columns[i];
  var index = indexInParent(column);
  document.querySelectorAll("td:nth-child("+index+")")
  .forEach(elem => elem.addEventListener("click", copy))
}
jpm92
  • 143
  • 1
  • 8

1 Answers1

1

You can use :last-child pseudo-class selector to match these last cells.

update: you can also use nth-child() to match any of the other cells. nth-child(3) matches the column named 'Last' in the example.

function copy(event) {
  console.log("Triggered")
  var target = event.target || event.srcElement;
  console.log("Targeted", target)
  var copyText = target.innerText || target.textContent;
  navigator.clipboard.writeText(copyText)

  // select the cell
  var range = document.createRange();
  range.selectNode(target);
  window.getSelection().removeAllRanges();
  window.getSelection().addRange(range);
}

document.querySelectorAll("td:nth-child(3)")
  .forEach(elem => elem.addEventListener("click", copy))
<table class="table">
  <thead>
    <tr>
      <th scope="col">#</th>
      <th scope="col">First</th>
      <th scope="col">Last</th>
      <th scope="col">Handle</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th scope="row">1</th>
      <td>Mark</td>
      <td>Otto</td>
      <td>@mdo</td>
    </tr>
    <tr>
      <th scope="row">2</th>
      <td>Jacob</td>
      <td>Thornton</td>
      <td>@fat</td>
    </tr>
    <tr>
      <th scope="row">3</th>
      <td>Larry</td>
      <td>the Bird</td>
      <td>@twitter</td>
    </tr>
  </tbody>
</table>
Tibebes. M
  • 6,940
  • 5
  • 15
  • 36
  • Wow, .foreach seems neat! Thanks for that! There was a confussion though, when I wrote "Last" column, I meant column with the name "Last", not actually the last column. How could that be achieved? I guess I should id somehow the elements and then get all td elements corresponding, shouldn't I? Also, how could I highlight the copied text (as if I was selecting it with the cursor)? Thanks again! – jpm92 Oct 29 '20 at 11:01
  • 1
    Oh, I must have mis-understood that, I'll update it. And I've added the highlighting logic there, check it out. – Tibebes. M Oct 29 '20 at 11:02
  • 1
    Awesome Tibebes, thanks a lot! Out of curiosity, is that more efficient than using id's and getting elements by id? Also, what would be the equivalent of the .select() method? That method selects the text as if it was clicked and dragged with the mouse, but I can't find that method outside of input elements... – jpm92 Oct 29 '20 at 11:16
  • 1
    IDs are expected to be unique for each element though, you shouldn't use ID in such case (classes are more suitable). But regarding the difference (performance, etc..), [this answer has more detailed explanation](https://stackoverflow.com/a/54819633/11984670). it should help – Tibebes. M Oct 29 '20 at 11:21
  • 1
    I managed to get this done more dynamically by adding "copiable" classes to the th tags. I've updated the question. – jpm92 Oct 29 '20 at 14:47