Generated row of table event listener not working instantly

Viewed 87

I have a table like this :

enter image description here

I have added an event listener assetTableEvent() to each text box in the table. My issue is when I add new row to the table, i also add the corresponding event listener to it assetTableEvent(), but the total value does not pop while entering value, it shows only when next row has values entered.

enter image description here

 function assetTableEvent() {
    let total = 0;

    for (var k = 0; k < document.getElementById("assetTable").rows.length; k++) {
        var a = document.getElementById("v" + k);
        var o = document.getElementById("o" + k);
        var t = document.getElementById("t" + k);

        if (a == null || o == null) {
            continue;
        }

        if (a.value.length > 0 && o.value.length > 0) {
            t.value = Number.parseInt(a.value - o.value);
            total = (Number.parseInt(t.value) + Number.parseInt(total));
            document.getElementById("totalAssets").value = Number.parseInt(total);
        }
    }

}

I even tried calling assetTableEvent() every time there is a change, but it just does not work. Can somebody help me in Javascript how to make dynamically added rows correspond to event listener like above rows.

HTML for Asset table:

<div id="calcContainer">

<div id = "headingText" >
Child Maintenance Calculator
</div>

<div id="startPage">

<button id="startBtn">Start</button>
</div>

<div id="asset">
 <table id="assetTable">
     <tr>

    <th>Asset</th>
    <th>Value</th>
    <th>Own</th>
    <th>Total</th>

</tr>
 </table>
 <div id="totalAssetsDiv">
 <Label for ="totalAssets">Total Assets</Label>
 <input type="number" id = "totalAssets" readonly="true">

 <br>
</div>
 <div id ="rowOps">
    <br> <button id="addRow" class = "addDel">Add Row</button>
   <button id="removeRow" class = "addDel1">Delete Row</button><br>
 </div>
</div>

And add row event listener :

   document.getElementById("addRow").addEventListener("click", function () {

    var table = document.getElementById("assetTable");
    var row = table.insertRow();

    for (let j = 0; j < 4; j++) {
        var tb = document.createElement("INPUT");
        var value = "", idNum = "";

        if (j == 0) {
            tb.setAttribute("type", "text");
            tb.value = value;

        }
        else {
            tb.setAttribute("type", "number");
        }


        //Setting textbox id
        switch (j) {
            case 0:
                idNum = "a";
                break;

            case 1:
                idNum = "v";
                break;

            case 2:
                idNum = "o";
                break;

            case 3:
                idNum = "t";
                break;

        }

        tb.id = idNum + (table.rows.length);

        if (tb.id.includes('t')) {
            tb.setAttribute("readOnly", "true");
        }

        tb.classList.add("assetTBox");
        let cell = row.insertCell(j);
        tb.addEventListener("input", assetTableEvent, false);
        cell.appendChild(tb);
     

    }
});
2 Answers

Trying to use incremental IDs is more work than it is worth, especially when you start removing rows.

I suggest you use classes instead and delegate the event listener to the table itself. When an input event occurs you get the closest row and query for the elements within that row for the row total, then query all of the rows totals for the master total

Basic example with functional add row

const table = document.querySelector('#myTable'),
  cloneRow = table.rows[0].cloneNode(true);


table.addEventListener('input',(e) => {
  if (e.target.matches('.qty, .price')) {
    const row = e.target.closest('tr'),
      price = row.querySelector('.price').valueAsNumber || 0,
      qty = row.querySelector('.qty').valueAsNumber || 0;
    
    row.querySelector('.amt').value = qty * price;
    setTotalAmt()
  }

});

document.querySelector('#add-row').addEventListener('click', (e) => {
  table.appendChild(cloneRow.cloneNode(true))
});

function setTotalAmt() {
  const sum = [...table.querySelectorAll('.amt')].reduce((a, el) => a + (+el.value || 0), 0)
  document.querySelector('#total').value = sum;
}
<button id="add-row">
  Add Row
</button>
Total:<input id="total" />
<table id="myTable">
  <tr>
    <td>Qty:
      <input type="number" class="qty" value="0" />
    </td>
    <td>Price:
      <input type="number" class="price" value="0" />
    </td>
    <td>Amt:
      <input class="amt" readonly value="0" />
    </td>
  </tr>
</table>

@charlietfl 's solition is more elegant but if you wonder what is the problem in your code, you should change the < to <= in k < document.getElementById("assetTable").rows.length; part

function assetTableEvent() {
    let total = 0;

    for (var k = 0; k <= document.getElementById("assetTable").rows.length; k++) {
        var a = document.getElementById("v" + k);
        var o = document.getElementById("o" + k);
        var t = document.getElementById("t" + k);

        if (a == null || o == null) {
            continue;
        }

        if (a.value.length > 0 && o.value.length > 0) {
            t.value = Number.parseInt(a.value - o.value);
            total = (Number.parseInt(t.value) + Number.parseInt(total));
            document.getElementById("totalAssets").value = Number.parseInt(total);
     }
  }
}

Here is the working example:

document.getElementById("addRow").addEventListener("click", function () {

    var table = document.getElementById("assetTable");
    var row = table.insertRow();

    for (let j = 0; j < 4; j++) {
        var tb = document.createElement("INPUT");
        var value = "", idNum = "";

        if (j == 0) {
            tb.setAttribute("type", "text");
            tb.value = value;

        }
        else {
            tb.setAttribute("type", "number");
        }


        //Setting textbox id
        switch (j) {
            case 0:
                idNum = "a";
                break;

            case 1:
                idNum = "v";
                break;

            case 2:
                idNum = "o";
                break;

            case 3:
                idNum = "t";
                break;

        }

        tb.id = idNum + (table.rows.length);

        if (tb.id.includes('t')) {
            tb.setAttribute("readOnly", "true");
        }

        tb.classList.add("assetTBox");
        let cell = row.insertCell(j);
        tb.addEventListener("input", assetTableEvent, false);
        cell.appendChild(tb);
     

    }
});


function assetTableEvent() {
    let total = 0;
    for (var k = 0; k <= document.getElementById("assetTable").rows.length; k++) {
        var a = document.getElementById("v" + k);
        var o = document.getElementById("o" + k);
        var t = document.getElementById("t" + k);

        if (a == null || o == null) {
            continue;
        }

        if (a.value.length > 0 && o.value.length > 0) {
            t.value = Number.parseInt(a.value - o.value);
            total = (Number.parseInt(t.value) + Number.parseInt(total));
            document.getElementById("totalAssets").value = Number.parseInt(total);
        }
    }
    }
<div id="calcContainer">

<div id = "headingText" >
Child Maintenance Calculator
</div>

<div id="startPage">

<button id="startBtn">Start</button>
</div>

<div id="asset">
 <table id="assetTable">
     <tr>

    <th>Asset</th>
    <th>Value</th>
    <th>Own</th>
    <th>Total</th>

</tr>
 </table>
 <div id="totalAssetsDiv">
 <Label for ="totalAssets">Total Assets</Label>
 <input type="number" id = "totalAssets" readonly="true">

 <br>
</div>
 <div id ="rowOps">
    <br> <button id="addRow" class = "addDel">Add Row</button>
   <button id="removeRow" class = "addDel1">Delete Row</button><br>
 </div>
</div>

Related