Search code examples
javascripthtmlcalculated-columns

Dynamically calculating total for each column based on selected rows for HTML table using Javascript


html table

From the picture, I am trying to calculate the total of each column for the selected rows a and c. The code works if I unwrap it from the function calculateCols. Is it possible to make it work in the wrapped function. I want to be able to choose a variation of rows to sum for each column without copy and pasting the code and editing it multiple times for each one.

function calculateCols(ID, calculate) {
  var final = 0
  var tbody = document.querySelector('tbody');
  var howManyCols = tbody.rows[0].cells.length;
  var totalRow = document.getElementById(ID);

  for (var j = 1; j < howManyCols; j++) {
    final = calculate;
    const check = document.createElement('td');
    check.innerText = final;
    totalRow.appendChild(check);
  }

  function getRow(rowID) {

    var result = 0;

    try {
      var check = document.getElementById(rowID)
      var thisNumber = parseInt(check.cells[j].childNodes.item(0).data);

      if (!isNaN(thisNumber))
        result += thisNumber;

    } finally {
      return result;
    }
  }

}

calculateCols('total', getRow('a') + getRow('c'));
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <table>
    <tbody>
      <tr>
        <td></td>
        <th scope="col">2013</th>
        <th scope="col">2014</th>
        <th scope="col">2015</th>
        <th scope="col">2016</th>
      </tr>
      <tr id="a">
        <th scope="row">a</th>
        <td>1</td>
        <td>2</td>
        <td>3</td>
        <td>4</td>
      </tr>
      <tr id="b">
        <th scope="row">b</th>
        <td>5</td>
        <td>6</td>
        <td>4</td>
        <td>5</td>
      </tr>
      <tr id="c">
        <th scope="row">c</th>
        <td>4</td>
        <td>5</td>
        <td>6</td>
        <td>7</td>
      </tr>
      <tr id="d">
        <th scope="row">d</th>
        <td>5</td>
        <td>6</td>
        <td>8</td>
        <td>5</td>
      </tr>

      <tr id="total">
        <th scope="row" id="Total">Total a + c</th>

      </tr>
    </tbody>
  </table>
  <script src="checkit.js"></script>

</body>

</html>


Solution

  • This should do it:

    function rpn(inps, D, i) { // Reverse Polish Notation calculator
      const st = [];
      inps.split(/\s+/).forEach(t => {
        let k = st.length - 2; // index of penultimate element on stack
        if (!isNaN(t)) st.push(+t);
        else switch (t) {
          case "+": st[k] += st.pop(); break;
          case "-": st[k] -= st.pop(); break;
          case "*": st[k] *= st.pop(); break;
          case "/": st[k] /= st.pop(); break;
          case "**": st[k] = st[k] ** st.pop(); break;
          default: st.push(+D[t][i]) // treat current value t as a "variable name" --> D[t][i]
        }
      });
      return st.pop()
    }
    // sample use (Pythagoras's theorem): rpn("3 2 ** 4 2 ** + .5 **") // = 5
    
    // get all table data into D first:
      const D = [...document.querySelectorAll("tr[id]")].reduce((da, tr) => {
          da[tr.id] = [...tr.children].slice(1).map(td => +td.textContent);
          return da;
        }, {});
      document.querySelectorAll("tr[data-eqn]").forEach(tr =>
        [...tr.children].slice(1).forEach((td,k)=>
          td.textContent=rpn(tr.dataset.eqn, D, k).toFixed(4).replace(/\.?0*$/,"")));
    td,th {text-align: right; padding:4px}
    td    {border:1px solid grey}
    table {border-collapse:collapse}
    <table id="tbl">
      <tbody>
        <tr>
          <th></th>
          <th scope="col">2013</th>
          <th scope="col">2014</th>
          <th scope="col">2015</th>
          <th scope="col">2016</th>
        </tr>
        <tr id="a">
          <th scope="row">a</th>
          <td>1</td><td>2</td><td>3</td><td>4</td>
        </tr>
        <tr id="b">
          <th scope="row">b</th>
          <td>5</td><td>6</td><td>4</td><td>5</td>
        </tr>
        <tr data-eqn="a 2 ** b 2 ** + .5 **">
          <th scope="row">sqrt(a²+b²)</th>
          <td></td><td></td><td></td><td></td>
        </tr>
        <tr id="c">
          <th scope="row">c</th>
          <td>4</td><td>5</td><td>6</td><td>7</td>
        </tr>
        <tr id="d">
          <th scope="row">d</th>
          <td>5</td><td>6</td><td>8</td><td>5</td>
        </tr>
        <tr data-eqn="2 a * d + c -">
          <th scope="row">2a + d - c</th>
          <td></td><td></td><td></td><td></td>
        </tr>
        <tr data-eqn="a b - a b + / c *">
          <th scope="row">(a - b) / (a + b) * c</th>
          <td></td><td></td><td></td><td></td>
        </tr>
        <tr data-eqn="b c /">
          <th scope="row">b/c</th>
          <td></td><td></td><td></td><td></td>
        </tr>
        <tr data-eqn="a b +">
          <th scope="row">a + b</th>
          <td></td><td></td><td></td><td></td>
        </tr>
        <tr data-eqn="c b +">
          <th scope="row">c + b</th>
          <td></td><td></td><td></td><td></td>
        </tr>
      </tbody>
    </table>

    I changed my answer completely again: The centre piece of the operation is now an RPN calculator (function rpn()) that goes through instructions strings provided by data-eqn attributes in all so identified result-<tr>s.

    Before the calculation can start I collect all provided table data in a global object D. All trs with an id-attribute contribute their td.textContent to a vector which becomes property D[id]. D ends up being an object of arrays.

    [[ Currently the results are limited to 3 digits. This can easily be switched off by removing .toFixed(3) again. ]]