0

Order entry form contains product name, price and quantity columns:

 <table id="order-products" class="mobileorder-table">
        <colgroup>
            <col style="width: 80%;">
            <col style="width: 10%;">
            <col style="width: 10%;">
        </colgroup>

        <tbody>
                <tr>
                    <td>
                       Product1
                    </td>
                    <td>
 <span class="mobileorder-price">0,98</span>
                    </td>
                    <td>
                        <input data-product="4750211645618" class="quantity" id="product_Soodkogus" name="product.Soodkogus"
                       type="number" min="0" max="999999" value=""
                       onblur="orderSumRefresh()" />
                    </td>
                </tr>
        </tbody>
    </table>
    Order total <p id="js-doksumma"></p>

If quantity is changed, order total value should updated. I tried

<script>
    function parseFloatFormatted(txt) {
    if (typeof txt !== 'string' || txt === null || txt === "") {
      return 0
      }
    return parseFloat(txt.replace(',', '.').replace(' ', ''))
    }

function orderSumRefresh() {
    let totalAmount = 0
    const table = document.getElementById("order-products")
    table.rows.forEach((row) => {
        //for (let i in table.rows) {
        //   const row = table.rows[i]
       const hind = row.cells[1].querySelector(".mobileorder-price").value
       const kogus = row.cells[2].querySelector(".quantity").value
       const rowSum = Math.round(parseFloatFormatted(hind)* parseFloatFormatted(kogus)  * 100) / 100
       totalAmount += rowSum
       });
    var dok = document.getElementById("js-doksumma")
    dok.innerText = totalAmount.toFixed(2)
    }

</script>

but got error

How to properly implement this ? Should pure CSS, javascript or query used?

Modern Chrome browser is used in mobile phone, ASP.NET 6 MVC Razor application.

Andrus
  • 26,339
  • 60
  • 204
  • 378
  • 1
    You have to be aware that `for-in` on `table.rows` also includes the property `length`. There is no `table.rows[length]`. Just stick to `[...table.rows].forEach((row) => console.log(row))` – Lain Apr 06 '22 at 07:45
  • I updated question to use ` table.rows.forEach((row)` but this throws error `forEach` is not function – Andrus Apr 06 '22 at 08:05
  • @Andrus table.rows.forEach gives always an error check this: https://stackoverflow.com/questions/35969974/foreach-is-not-a-function-error-with-javascript-array – Nicolò Rabellino Apr 06 '22 at 08:16
  • I have updated my answer to use `for (const row of table.rows) {` that is similar to what are you trying to do like Nick Vu said. – Nicolò Rabellino Apr 06 '22 at 08:18
  • @Andrus, `[...table.rows]` has `forEach` whereas `table.rows` does not. That is the reason for the spread. Alternatively you can use `querySelectorAll` and omit the spread, since it supports `forEach`. – Lain Apr 06 '22 at 08:57
  • `table.rows` itself is not an array object. You cannot use native methods (like `forEach`) of Array on `table.rows`, but we can loop through it with `for-of` (or normal `for` with index). Here is the document for `table.rows` https://www.w3schools.com/jsref/coll_table_rows.asp. Or another choice is that you should convert it to an array object like `[...table.rows]` or `Array.from(table.rows)`, but it may hit performance slightly due to an array cloning operation. @Andrus – Nick Vu Apr 06 '22 at 09:16
  • `document.querySelectorAll('#order-products tr').forEach()` avoids all those issues. – JavaScript Apr 06 '22 at 11:02

3 Answers3

1

As Nick Vu said a first problem is in the for loop and I changed to:

for (let i = 0; i < table.rows.length; i++) {

I find more problems in the code for example the index of the childNodes is wrong, using

console.log(row.cells[1].childNodes)

you can see there are 3 child and you are searching for the middle one (index: 1)

Then for accessing the data of the input element you need to use the .value property like this:

const kogus = row.cells[2].childNodes[1].value

********************* EDIT *******************

Changing the code as the answer has changed.

For accessing the data of the html element use .innerHTML property.

function parseFloatFormatted(txt) {
    if (typeof txt !== 'string' || txt === null || txt === "") {
        return 0
    }
    return parseFloat(txt.replace(',', '.').replace(' ', ''))
}

function orderSumRefresh() {
    let totalAmount = 0
    const table = document.getElementById("order-products")
    /*
    for (let i = 0; i < table.rows.length; i++) {
        const row = table.rows[i]
        const hind = row.cells[1].childNodes[1].innerHTML
        const kogus = row.cells[2].childNodes[1].value
        const rowSum = Math.round(parseFloatFormatted(hind) * parseFloatFormatted(kogus) * 100) / 100
        totalAmount += rowSum
    }
    */
    for (const row of table.rows) {
        const hind = row.cells[1].querySelector(".mobileorder-price").innerHTML
        const kogus = row.cells[2].querySelector(".quantity").value
        const rowSum = Math.round(parseFloatFormatted(hind)* parseFloatFormatted(kogus)  * 100) / 100
        totalAmount += rowSum
    }
    const dok = document.getElementById("js-doksumma")
    dok.innerText = totalAmount.toFixed(2)
}
<table id="order-products" class="mobileorder-table">
    <colgroup>
        <col style="width: 80%;">
        <col style="width: 10%;">
        <col style="width: 10%;">
    </colgroup>

    <tbody>
        <tr>
            <td>
                Product1
            </td>
            <td>
                <span class="mobileorder-price">0,98</span>
            </td>
            <td>
                <input data-product="4750211645618" class="quantity" id="product_Soodkogus" name="product.Soodkogus"
                    type="number" min="0" max="999999" value="" onblur="orderSumRefresh()" />
            </td>
        </tr>
    </tbody>
</table>
Order total <p id="js-doksumma"></p>

I suggest you to use the console.log() and log some variable to see if there is somethink wrong with the code.

0

Your problem is from here

for (let i in table.rows) {}

The value will be "0" and "length" (not index like your expectation), so it throws an error while trying to access row.cells[0].childNodes (row.cells is undefined)

I'd suggest you modify it to

for (const row of table.rows) {}

The full code can be

function parseFloatFormatted(txt) {
  if (typeof txt !== 'string' || txt === null || txt === "") {
    return 0
  }
  return parseFloat(txt.replace(',', '.').replace(' ', ''))
}

function orderSumRefresh() {
  let totalAmount = 0
  const table = document.getElementById("order-products")
  for (const row of table.rows) {
    const hind = row.cells[1].childNodes[0].innerHTML
    const kogus = row.cells[2].childNodes[0].innerText
    const rowSum = Math.round(parseFloatFormatted(hind) * parseFloatFormatted(kogus) * 100) / 100
    totalAmount += rowSum
  }
  const dok = document.getElementById("js-doksumma")
  dok.innerText = totalAmount.toFixed(2)
}
<table id="order-products" class="mobileorder-table">
  <colgroup>
    <col style="width: 80%;">
    <col style="width: 10%;">
    <col style="width: 10%;">
  </colgroup>

  <tbody>
    <tr>
      <td>
        Product1
      </td>
      <td>
        <span class="mobileorder-price">0,98</span>
      </td>
      <td>
        <input data-product="4750211645618" class="quantity" id="product_Soodkogus" name="product.Soodkogus" type="number" min="0" max="999999" value="" onblur="orderSumRefresh()" />
      </td>
    </tr>
  </tbody>
</table>
Order total
<p id="js-doksumma"></p>
Nick Vu
  • 14,512
  • 4
  • 21
  • 31
0

Nick is correct. Remember that table.rows is not an array but an HTMLCollection. You can fix the issue simply doing:

  const table = document.getElementById("order-products")
  for (const row of Array.from(table.rows)) {
  }

If you want to see for yourself that there is an "length" property being iterated over, open the dev tools, select the table from the elements tab, and run this snippet in the console:

for (let i in $0.rows) {
    console.log(i);
    console.log($0.rows[i].cells[0]);
}

You will see the last iteration print "length" and then throw an exception.

smallnoyd
  • 93
  • 7