Alejandro de Haro
Alejandro de Haro

Reputation: 1175

insertBefore not updating rowIndex/nextSibling properties

It may sound stupid or even trivial for most experienced users, but I just landed a few hours ago on front-end javascript and I must say I am a bit puzzled with the behavior of the insertBefore javascript function.

My intention here is plain and simple: I have a table with its rows and cells, and in each row I have a cell with a button with the only purpose of duplicating that cell (with all its contents) and place the new duplicated cell right next to the original one.

I have a javascript function for it such like this one:

// id -> the id of the table I want the row to be added
// caller -> the object of the element that called the function
function duplicateRow(id, caller)
{
    const table = document.getElementById(id);
    const row   = caller.parentNode.parentNode;  // Caller is always a button inside a cell inside a row
    const clone = row.cloneNode(true);

    table.insertBefore(clone, row.nextElementSibling);
}

This function is called like this (from an extract of my HTML):

<tr>
    <td>
        <input type="text" name="competence-name">
    </td>
    <td>
        <button name="duplicate-row-button" onclick="duplicateRow( 'competencies-table', this )"></button>
    </td>
</tr>

So, what I would expect from it is that, at each click on the duplicate row button, it would create an exact copy of the row where the button is being clicked and add it right after that row.

My problem here is not with the duplicating (that is done just right and smooth as one would expect) but with where the new row is placed:

Shouldn't the nextSibling and/or rowIndex properties be updated when adding a new node to the DOM? Is there a way of forcing them to update? What is it that I have wrong? My code, my understanding of how it should work?

I am surely open to any possible explanation/solution/alternative to achieve what I need, and thank you all in advance!

Upvotes: 1

Views: 316

Answers (1)

Andrew Svietlichnyy
Andrew Svietlichnyy

Reputation: 761

The problem is that initial table row is wrapped in a tbody element (for which you can omit both start and end tag), which is required according to the content model of tables. However, when you programmatically add more rows, they are inserted outside the tbody and your initial row is the only child of that implicit tbody, so the DOM tree looks like this:

<table>
  <tbody>
    <tr></tr>
  </tbody>
  <tr></tr>
  <tr></tr>
</table>

To solve it I suggest to add a clone to cloned row's parent:

function duplicateRow(caller){
  const row   = caller.parentNode.parentNode;  // Caller is always a button inside a cell inside a row
  const clone = row.cloneNode(true);

  row.parentNode.insertBefore(clone, row.nextElementSibling);
}
<table id="competencies-table">
  <tr>
    <td>
      <input type="text" name="competence-name">
    </td>
    <td>
      <button name="duplicate-row-button" onclick="duplicateRow( this )">Duplicate</button>
    </td>
  </tr>
</table>

Upvotes: 1

Related Questions