Home > front end >  Delete table row after adding it dynamically through Javascript
Delete table row after adding it dynamically through Javascript

Time:11-07

I have an AJAX script that adds an entry to a database, then updates a table shown on the page to include the new row.

The exact code used for adding the row to the table (once the database stuff has been successfully done and has returned data for the variables to be used below) is:

var tableis = document.getElementById("fields-table-append");
var last=(-1);
var Tr = tableis.insertRow(last);
var Td1 = Tr.insertCell(0);
Td1.innerHTML=newrecordname2;
Td1.className = 'cellStyle fields-table-row';
Tr.appendChild(Td1);

var Td2 = Tr.insertCell(1);
Td2.innerHTML="<span class='field-type-style-email'>" newcontenttype2 "</span>";
Td2.className = 'cellStyle fields-table-row';
Tr.appendChild(Td2);

var Td3 = Tr.insertCell(2);
Td3.innerHTML="<span class='field-type-required-no'>" newisrequired2 "</span>";
Td3.className = 'cellStyle fields-table-row';
Tr.appendChild(Td3);

var Td4 = Tr.insertCell(3);
Td4.innerHTML="<a onclick='javascript:deleteRow(" newrowid2 ")';><img class='fields-table-icon' alt='Delete' src='img/zef-delete.png' onm ouseover='' style='cursor: pointer;' /></a>";
Td4.className = 'cellStyle fields-table-row fields-table-column-4';
Tr.appendChild(Td4);

As you can see, each row has a delete icon at the end, which successfully deletes the row, again using a background AJAX query for the database stuff and then removes the appropriate row in the UI using the following code:

Javascript:

function deleteRow(field_id)
{
if (confirm("Are you sure you want to delete that? It can't be undone."))
{
 $.ajax({
 data:{todelete : field_id},
 type:'POST',
 url: 'background-delete.php',
 success: function (output) {
  var row = document.getElementById(field_id);
  row.parentNode.removeChild(row);
 }
}
}

This works perfectly for content that exists when the page is first loaded, but does not work for any extra rows dynamically added during that page load. To get the delete to work for these rows, I have to first refresh the page so that the new rows are pulled from the database properly rather than displaying dynamically.

I assume this is because the dynamic rows don't really exist in the page HTML (they don't show in view source, for example) so aren't properly recognised/registered?

Is there a way I can have these dynamic rows behave the same as rows created on page load, allowing me to show a working delete button without requiring a refresh?

EDIT WITH MORE INFO, AS REQUESTED:

On page load I just create the table by looping through MySQL results and outputting like this:

<?php
$sqlresultloop = "SELECT * FROM contacts_fields WHERE owner_id = '$user_id'";
$resultloop = $mysqli->query($sqlresultloop);

if(mysqli_num_rows($resultloop) > 0)   // checking if there is any row in the resultset
{
    while($row = mysqli_fetch_assoc($resultloop))  // Loop thru rows
    {

      ?>

<tr  id=<?php echo($row['unique_id']); ?>>
<td ><?php echo($row['field_label']); ?></td>
<td ><span ><?php echo($row['date_type']); ?></span></td> 
<td ><?php if(($row['is_required']) == 0) { ?><span ><?php print("Not required"); } else { ?><span ><?php print("Required"); }; ?></span></td>
<td ><a onclick="javascript:deleteRow(<?php print(($row['unique_id'])); ?>)";><img  alt="Delete" id="fields-table-icon" src="img/zef-delete.png" onm ouseover="" style="cursor: pointer;" /></a></td>
</tr> 

Then I have a button on the page that triggers a modal to add a new entry. I process that and do an Ajax request to add the data to the database. I get back JSON, and handle it in the "success" code like this:

$('form').on('submit', function (e) {

var $theform = $(this);
var theformid = $theform.attr('id');

e.preventDefault(); //stops page refresh and weird URL shenanigans

if(theformid === 'ex1')
    {
      document.getElementById("myLoader").style.display = "block";

          $.ajax({
            type: 'post',
            url: 'background-contact-fields.php',
            data: $('form').serialize(),
            success: function (output) {
       
              //do stuff here when successful
              let str = output;
              
              str = str.substring(1);
              str = str.slice(0, -1);
              const myObj = JSON.parse(str);
              newrecordname2 = myObj["fieldlabel"];
              newcontenttype2 = myObj["datatype"];
              newisrequired2 = myObj["isrequired"];
              newrowid2 = myObj["newrowid"];


It all works fine, I get back the information I expect. I then append it to the existing table using the first block of code I showed in this question, using the values as set above. It comes immediately after the newrowid2 = myObj["newrowid"]; line. That's it.

Everything works just as I want, except making the delete button work on the new row without a page refresh. It's not the end of the world, I can just not show it until page refresh. But it would be nice if it worked.

CodePudding user response:

The basic principle of event delegation is that DOM events "bubble up" the DOM. Instead of adding listeners to each element you can add one to a parent element that "catches" or listens out for events from its child elements. For example: instead of having listeners on every one of the rows that you add, add one to the table body to listen out for its child element events when they're fired, and then calls a function.

Here's a rather contrived example that might help you understand how it works. The example uses a lot of relatively new JS tech but don't worry too much about that - it's really the event delegation process that I'll going through, and I'll add links to documentation for everything at the bottom of the answer. Everything can be swapped out for code that you're more used to.

// We have a table in our markup already so we need to
// select the `tbody` element, and then assign one listener
// to it. The listener will catch events from its children
// (here: the buttons in the rows), and then call the
// `handleClick` function
const tbody = document.querySelector('tbody');
tbody.addEventListener('click', handleClick);

// Set up some data - this would be your data from your
// endpoint...
const data = [
  { id: 1, name: 'Rita', age: 18, location: 'Bradford' },
  { id: 2, name: 'Sue', age: 19, location: 'Bradford' },
  { id: 3, name: 'Bob', age: 35, location: 'Bradford' }
];

// ...but in this example we're using a "mock" API
// request to deliver a stringified version of that
// data to the app - when the function is called it waits
// for two seconds to mimic the latency of the request
function mockApi() {
  return new Promise(res => {
    setTimeout(() => {
      const json = JSON.stringify(data);
      res(json);
    }, 2000);
  });
}

// A function that creates some row HTML from
// the data (an array). It `map`s over the data
// and returns a string of row information (using a
// template string to add in the object values)
// The last cell includes the delete
// button. Because `map` itself returns a (transformed)
// array we need to join it up into a string at the end.
function createRows(data) {
  return data.map(obj => {
    return `
      <tr>
        <td>${obj.name}</td>
        <td>${obj.age}</td>
        <td>${obj.location}</td>
        <td>
          <img
            data-id="${obj.id}"
            
            src="https://dummyimage.com/100x25/bfa6bf/000000&text=Delete" />
        </td>
      </tr>
    `;
  }).join('');
}

// The function that is called when the `tbody`
// listener "catches" an event from one of its children.
// First it checks to see if the element that fired the event
// is a button. If it is a button we find the closest parent row
// to that button, and remove it from the DOM
function handleClick(e) {
  if (e.target.matches('img.delete')) {
    e.target.closest('tr').remove();
    console.log(`Deleting: ${e.target.dataset.id}`);
  }
}

// The main function that calls the mock API
// parses the JSON into an object, and then calls
// `createRows` with that data which returns the HTML.
// We then attach all the HTML to the `tbody` element.
async function main() {
  const response = await mockApi();
  const data = await JSON.parse(response);
  const html = createRows(data);
  tbody.insertAdjacentHTML('beforeend', html);
}

main();
table { border-collapse: collapse; width: 100%; }
thead { background-color: #efefef; text-transform: uppercase; }
td { padding: 0.25em; border: 1px solid #efefef; }
img.delete:hover { cursor: pointer; }
<table>
  <thead>
    <tr>
      <td>Name</td>
      <td>Age</td>
      <td>Location</td>
      <td></td>
    </tr>
  </thead>
  <tbody>
  </tbody>
</table>

Additional documentation

  • Related