Calculating total values in generated HTML tables

Hi,

I have created a dynamically created table that contain values. I would like the ‘Total’ row have the sum of the columns when the page is loaded. Currently the ‘Total’ row cells are summed up when value is changed in a cell and you click away. Any help would be much appreciated. Below is my codepen:

Any help will be much appreciated. I have been struggling with this for weeks.

Well, lets see if we can inspire a thought with a command reference.
trigger()

Hint: It’s not when the page is loaded that you want this to happen…

I’m not seeing how trigger() will have total calculated when the user initially opens the page. Can you give another hint. I looked at the docs.

Well lets be a little clearer about the timing.

When the user initially opens the page, the table is empty.

Your script then makes a second request to the server for data to fill the table with. This is NOT the same thing as ‘when the user initially opens the page’. This is the definition of asynchronous loading.

Your script, when it receives the data, populates the table.

At THAT point, your script should trigger a notification that the table has changed, and to do a calculation of the totals. When the script is done loading the data.

So, I should probably add the trigger() in the .then of the Ajax request? Also, I think I’m confused as to what my event should be that needs to be an argument of trigger().

Here’s my code that does nothing:

function getData(listName) {
    var root = ../dashboard/posts';
    $.ajax({
        url: root,
        method: 'GET'
    }).then(function(data) {
        if (listName == 'Advisor Staffing') {
            buildTable(data, "#AdvStaffing", listName);
          $('#AdvStaffing').change(function(){
             $('#AdvStaffing').trigger('change');
          });            
        }     
    });
}

The sum needs to be calculated after the data has been added to the html page. Right now the sum or calculate() function is being called right when the page loads which is incorrect. A simple solution would be to call calculate after buildTable() and replace calculate() in the onload with getData() call instead. You can calculate after buildTable() since buildTable is a synchronous function.

What @m_hutley is getting at, is that after populating the table with figures, you can trigger a calculation event to calculate the total.

That means setting up a custom calculation event first, populating the table, and then triggering the calculation.

Here’s the example from the trigger page to help get you started:

$( "#foo" ).on( "custom", function( event, param1, param2 ) {
  alert( param1 + "\n" + param2 );
});
$( "#foo").trigger( "custom", [ "Custom", "Event" ] );

I’ve been reading about trigger() all day and what I am trying to figure out is which event would I use to trigger the calculation?

It won’t work because the calculation are triggered due to an change event. I’m not sure how to use load to trigger the calculation. That’s where I’m stuck.

You can create and use a custom event, such as tablePopulated

I don’t quite understand why you are complicating the problem when the totals can be calculated immediately after building the table. The code isn’t very flexible or reusable anyway so why complicate it with triggering events when simply calculating totals after building the table will work. Its not like a reusable table/data grid component is being built where it would make sense to add outputs to allow the outside world to interact with actions.

@motor_man, I’m honestly not sure how to calculate after building the table because once in the calculate function, an eventlistener is listening for a ‘change’ in one of the cells. That’s how the calculation is done.

   calculate(AdvStaffing, "AdvStaffing");

function calculate(tbl, tblID) {
    tbl.addEventListener("change", function(e) {   //<------- if I replace with 'load', it doesn't work
        if (e.target.tagName === "INPUT") {
            var rowNode = e.target.parentNode.parentNode;
            //console.log(e);
            //console.log(e.target.getAttribute('data-id'));

            var colID = e.target.getAttribute('data-id');
            var colName = e.target.getAttribute('data-columnName');

            var newColValue = e.target.value;

            var rowInputs = rowNode.getElementsByTagName('input');

            if (tblID == "AdvStaffing") {
                var tableNode = e.target.parentNode.parentNode.parentNode;
                var columnInputs = tableNode.getElementsByClassName('column1');

                var columnInputs2 = tableNode.getElementsByClassName('column2');

                var columnInputs3 = tableNode.getElementsByClassName('column3');

                var colSum = getColSum(columnInputs);
                var colSum2 = getColSum(columnInputs2);
                var colSum3 = getColSum(columnInputs3);

                tableNode.rows[8].cells[1].innerHTML = colSum;

                tableNode.rows[8].cells[2].innerHTML = colSum2;

                tableNode.rows[8].cells[3].innerHTML = colSum3;
            }
        }
    })
}

Any ideas?

I passed the code through JSLint to help remove a majority of distracting issues, before using the CodePen tidy.

The jQuery loader adds the change event, because it doesn’t make good sense for the calculate function to both add the event listener, and do the calculation.

The e.target variable could be the table object, or an input field, so getting the closest table element helps to make things more flexible.

function tableChangeHandler(e) {
  calculate(
    $(e.target)
      .closest("table")
      .get(0)
  );
}

$(function() {
  $("#AdvStaffing").on("change", tableChangeHandler);
});

The next problem is that the total line has an input field that’s being added up too. We can remove that last line from being included in the summing:

function calculate(tableNode) {
  var inputs = [
    tableNode.querySelectorAll("tr:not(:last-child) .column1"),
    tableNode.querySelectorAll("tr:not(:last-child) .column2"),
    tableNode.querySelectorAll("tr:not(:last-child) .column3")
  ];
  inputs.forEach(function(input, index) {
    var colSum = getColSum(input);
    var totalRow = tableNode.querySelector("tr:last-child");
    totalRow.cells[index + 1].innerHTML = colSum;
  });
}

I also removed the need for column1/column2/column3 as well:

  var inputs = [
    tableNode.querySelectorAll("tr:not(:last-child) td:nth-child(2) input"),
    tableNode.querySelectorAll("tr:not(:last-child) td:nth-child(3) input"),
    tableNode.querySelectorAll("tr:not(:last-child) td:nth-child(4) input")
  ];

I was going to move on to preventing the input being added to the bottom total line, but but stopped short before doing that as I seem to be moving beyond the scope of the desired thing to be fixed.

Which leaves us with the working code shown at:

@Paul_Wilkins, thank you. This makes some sense. Question: What line in your code calculates the totals before hand?

Would this still work if I had a table with 6 or 7 columns? It looks like I would just need to modify the inputs array in calculate()?

That is line 97, near the end of the getData function.

Yes.

Thank you! Much appreciated.

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.