javascriptphphtml-tablepopup

Image in <tr> breaks my script and can't fetch tr's data


I'm making a dashboard on a website, where users will be able to interact with the database. The pages are made in PHP, so are the tables. And when a user clicks on a row, he can edit the data of the current row. This is handled by Javascript (code a bit further down) and it works as a charm.

BUT, since it may not be clear that people need to click on a row to edit it, I added a column with an SVG so people can click it. And when they click exactly on the svg, the data isn't retrieved. It should be, since I get the data from clicking on the whole <tr>. Note that if the user clicks anywhere else on the <tr>, it still works.

Here's the PHP code for the table :

<table class="table table-sm table-bordered table-striped table-hover">
   <thead class="configure-array">
      <?php
      $tableHead = "<tr>";
      foreach (array_keys($seals[0]) as $keys) {
         $tableHead .= "<th>$keys</th>";
      }
      $userPermissions['MODIFY_OPTION'] ? $tableHead .= '<th>Edit</th>' : '';
         $tableHead .= "</tr>";
            echo $tableHead;
      ?>
   </thead>
   <tbody>
      <?php
      $tableBody = "";
      foreach ($seals as $seal) {
         if ($userPermissions['MODIFY_OPTION']) {
            $tableBody .= '<tr class="data-seals">';
         } else {
            $tableBody .= "<tr>";
         }
         foreach ($seal as $key => $value) {
            $tableBody .= "<td>$value</td>";
         };
         if ($userPermissions['MODIFY_OPTION']) {
            $tableBody .= "<td><img style='user-select: none' src='ressources/edit-svgrepo-com.svg' alt='edit' width='20' /></td>";
         };
         $tableBody .= "</tr>";
      }
      echo $tableBody;
      ?>
   </tbody>
</table>

And here's the JS script :

document.addEventListener("DOMContentLoaded", (e) => {
    const sealRows = document.querySelectorAll('.data-seals');
    const deleteSealBtn = document.querySelector('#delete-seal-btn');
    sealRows.forEach(element => {
        element.addEventListener('click', (e) => {
            let dataSeal = Array.from(e.target.parentElement.childNodes).map((sealValues) => {
                return sealValues.innerHTML;
            });
            togglePopUp('update-seal-popup');
            // ID mise en input hidden pour le récupérer en $_POST
            document.querySelector('#update-seal-id').value = dataSeal[0];
            // Nom en placeholder
            document.querySelector('#update-seal-matiere').placeholder = dataSeal[1];
            // Remise en placeholder
            document.querySelector('#update-seal-type').placeholder = dataSeal[2];
            // Attribut formaction pour diriger l'utilisateur vers la suppresion
            deleteSealBtn.setAttribute('formaction', `deleteSeal.php?id=${dataSeal[0]}`)
        })
    });
});

Basically, when one clicks on a row, a popup appear and fill the inputs with the data of the clicked row. But when I click precisely on the image, the popup opens and the data is not there.

As you can see in the code, I tried to set the user-select: none in the image. I thought it could have been the cause of my problem, but it's not.

I also tried to replace the image with some text, and it works fine. But I want to put the image.


Solution

  • Your code expects e.target within the click event handler to be a reference to the tr element. You then use parentElement.childNodes to get the sibling tr to build the list of values.

    The problem is that when you click on the img element directly, e.target becomes a reference to that img, not the tr, so the parentElement is the td, and the map() fails to return what you expect.

    There's a few ways to fix this. One way would be to use e.currentTarget. This will ensure that the tr is always the reference point for your DOM traversal:

    let dataSeal = Array.from(e.currentTarget.parentElement.childNodes).map(sealValues => sealValues.innerHTML);
    

    Another would be to use closest() to find the parent tbody, then find the tr within that:

    let dataSeal = Array.from(e.target.closest('tbody').querySelectorAll('tr')).map(sealValues => sealValues.innerHTML);
    

    You could also cache the available tr elements in a variable when the page loads, then you can simply iterate this when required:

    // within DOMContentLoaded handler:
    const allRows = document.querySelectorAll('#yourTable tbody tr');
    const sealRows = document.querySelectorAll('.data-seals');
    const deleteSealBtn = document.querySelector('#delete-seal-btn');
    
    // within the click handler:
    let dataSeal = Array.from(allRows).map(sealValues => sealValues.innerHTML);
    

    Personally, I would use the last method, unless you are specifically loading/updating the .data-seals elements during the page lifecycle, otherwise the second approach using closest() is the more robust.