User1899289003
User1899289003

Reputation: 872

How to sum values from table column and update when remove/add new row

I'm trying to sum the values of one specific column but honestly I dont know how to it, also I want to refresh that total value when I add or remove some row, what can I do to make this? I'm triying with the anwsers of similar question here on SO but they sum values from all columns and I only want to do that for an specific column! Here is what I have:

function deleteRow(btn) {
  var row = btn.parentNode.parentNode;
  row.parentNode.removeChild(row);
}

$('#xd').click(function() {
  var lines = "";

  lines += '<td>3</td>';
  lines += '<td>3</td>';
  lines += '<td>15</td>';
  lines += '<td>Credit</td>';
  lines += '<td>1</td>';
  lines += '<td>100.00</td>';
  lines += '<td><input type="button" value="Delete" onclick="deleteRow(this)"/></td>';

  $('#TableBody').append(lines);
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<table id="Table">
  <thead>
    <tr>
      <td>ID</td>
      <td>Code</td>
      <td>Client</td>
      <td>Debit/Credit</td>
      <td>Quantity</td>
      <td>Price</td>
      <td>Options</td>
    </tr>
  </thead>
  <tbody id="TableBody">
    <tr>
      <td>1</td>
      <td>1</td>
      <td>3</td>
      <td>Debit</td>
      <td>10</td>
      <td>12.00</td>
      <td>
        <input type="button" value="Delete" onclick="deleteRow(this)" />
      </td>
    </tr>
    <tr>
      <td>2</td>
      <td>2</td>
      <td>12</td>
      <td>Debit</td>
      <td>5</td>
      <td>10.00</td>
      <td>
        <input type="button" value="Delete" onclick="deleteRow(this)" />
      </td>
    </tr>
  </tbody>
  <tfoot id="TableFooter">
    <tr>
      <td colspan="4">Total</td>
      <td>15</td>
      <td>170.00</td>
    </tr>
  </tfoot>
</table>

<input type="button" id="xd" value="add row">

In the above code I added the Total columns (Price, Quantity) manually, I want to update total result when user add/remove a row.

Upvotes: 0

Views: 13332

Answers (5)

Bryan Dellinger
Bryan Dellinger

Reputation: 5304

wow lots of answers but here is a somewhat of a more object oriented approach.

function row(Id, Code, Client, DebitCredit, Quantity, Price) {
  this.Id = Id;
  this.Code = Code;
  this.Client = Client;
  this.DebitCredit = DebitCredit;
  this.Quantity = Quantity;
  this.Price = Price;
}

function model() {
  this.rows = [];
}

var mymodel = new model();

$(document).ready(function() {
  mymodel.rows.push(new row(1, 1, 3, 'Debit', 10, 12))
  mymodel.rows.push(new row(2, 2, 12, 'Debit', 5, 10))
  draw();

  $("body").on("click", ".delete", function() {
    var id = $(this).data('id');
    for (i = 0; i < mymodel.rows.length; i++) {
      console.log(mymodel.rows[i].Id);
      if (mymodel.rows[i].Id == id) {
        mymodel.rows.splice(i, 1);
      }
    }
    draw();
  });

  $('#add').click(function() {
    mymodel.rows.push(new row(
      $('#Id').val(),
      $('#Code').val(),
      $('#Client').val(),
      'Debit',
      Number($('#Quantity').val()),
      Number($('#Price').val())
    ))
    draw();
  });
})

function draw() {
  $('tbody').empty();
  var totalQuantity = 0;
  var totalPrice = 0;
  $.each(mymodel.rows, function(i, row) {
    totalQuantity += row.Quantity;
    totalPrice += row.Price;
    var myrow = '<tr>'
    $.each(row, function(key, value) {
      myrow += '<td>' + value + '</td>'
    });
    myrow += '<td><input type="button" class="btn btn-danger delete" data-id="' + row.Id + '" value="X"/></td>'
    myrow += '<tr>'
    $('tbody').append(myrow);
  });
  $('#totalQuantity').text(totalQuantity)
  $('#totalPrice').text(totalPrice)
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">

<!-- Optional theme -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">

<table class="table table-condensed">
  <thead>
    <tr>
      <td>ID</td>
      <td>Code</td>
      <td>Client</td>
      <td>Debit/Credit</td>
      <td>Quantity</td>
      <td>Price</td>
      <td>Delete</td>
    </tr>
  </thead>
  <tbody>

  </tbody>
  <tfoot>
    <tr>
      <td colspan=7>Total Quantity:
        <span id="totalQuantity"></span> Total Price:
        <span id="totalPrice"></span>
      </td>

    </tr>
  </tfoot>
</table>

<form class="form-inline">
  <div class="form-group">
    <label for="id">Id:</label>
    <input type="number" class="form-control" id="Id">
  </div>
  <div class="form-group">
    <label for="Code">Code:</label>
    <input type="number" class="form-control" id="Code">
  </div>
  <div class="form-group">
    <label for="Client">Client:</label>
    <input type="number" class="form-control" id="Client">
  </div>
  <div class="form-group">
    <label for="Quantity">Quantity:</label>
    <input type="number" class="form-control" id="Quantity">
  </div>
  <div class="form-group">
    <label for="Price">Price:</label>
    <input type="number" class="form-control" id="Price">
  </div>
  <input type="button" class="btn btn-info" value="add" id="add" />
</form>

Upvotes: 2

rkd
rkd

Reputation: 714

Your approach is a bit brittle for long term use, but as a proof of concept this may help.

The key technique for summing up an array of numbers is to use Array.reduce, which works like this:

var array = [1, 2, 6, 1, 5];
var total = array.reduce(function(total, number) {
  return total + number;
}, 0);

document.write('<h1>Total: <code>' + total + '</code></h1>');

Given an array of numbers, iterate over each of them and add number to total, with total starting at 0. Array.reduce takes two arguments: a function to execute over each item, and a starting value. The iterator function will receive two arguments, in your case the running total and the next number.

See the MDN documentation on Array.reduce for more details.

Some Tips

  • Break things down into smaller functions whenever possible.
  • Limit use of global variables, but when you do need them, be clean and consistent about it
  • Limit storing data on the DOM (I'm violating this slightly, but this is just sketch code)
  • Try and write code in a way that's reusable

The benefits of this approach are it makes it a bit easier to add new features/change what you built. For example, if we write a generic function getColumnTotal(selector), which would let you specify a jQuery selector for a column's cells (ex: .priceCell), then you can reuse that for other columns like quantity.

I assume you were working towards a grand total cell, that displays the total of all individual orders/rows. To do that, all we'd need to do is calculate the subtotal for each row, add a new column for that, then re-use that getColumnTotal function to sum up all the sub-totals. Voila, grand total.

Note that my code doesn't account for errors, so you may need to handle situations where invalid quantity or price data is input.

var $tableBody = $('#TableBody');
var $totalQuantityCell = $('#totalQuantityCell');
var $totalPriceCell = $('#totalPriceCell');
var $totalGrandCell = $('#grandTotalCell');

// Add a row with random values on "Add Row" button click
$('#xd').click(addRandomRow);

function addRandomRow(event) {
  var randomCode = Math.round(Math.random() * 4);
  var randomClient = Math.round(Math.random() * 15);
  var randomCharge = ( Math.round(Math.random()) ? 'Debit' : 'Credit' );
  var randomQuantity = Math.ceil(Math.random() * 5);
  var randomPrice = Math.ceil(Math.random() * 100).toFixed(2);
  
  addRow(randomCode, randomClient, randomCharge, randomQuantity, randomPrice);
};

// Add some rows to start
addRandomRow();
addRandomRow();

// Listen for clicks on ".deleteRowButton" within the table
$tableBody.on('click', '.deleteRowButton', function(event) {
  deleteRow( $(event.target).data('row') );
  updateTotals();
});

// --------------------------

function addRow(code, client, chargeType, quantity, price) {
  // Create a new row element
  var idNum = ( $tableBody.find('tr').length + 1 );
  var rowId = 'row-' + idNum;
  var $row = $('<tr id="' + rowId + '"></tr>');

  // Add the table cells
  $row.append('<td class="idCell">' + idNum + '</td>');
  $row.append('<td class="codeCell">' + code + '</td>');
  $row.append('<td class="clientCell">' + client + '</td>');
  $row.append('<td class="chargeTypeCell">' + chargeType + '</td>');
  $row.append('<td class="quantityCell">' + quantity + '</td>');
  $row.append('<td class="priceCell">' + price + '</td>');
  $row.append('<td class="orderTotalCell">' + getSubtotal(quantity, price) + '</td>');
  $row.append('<td><input type="button" value="Delete" class="deleteRowButton" data-row="#' + rowId + '" /></td>');

  // Append the row to the table body
  $tableBody.append($row);
  updateTotals();
}

function deleteRow(rowId) {
  $(rowId).remove();
}

function updateTotals() {
  var totalQuantity = getColumnTotal('.quantityCell');
  var totalPrice = getColumnTotal('.priceCell');
  var totalOrder = getColumnTotal('.orderTotalCell');
  
  $totalQuantityCell.text( totalQuantity );
  $totalPriceCell.text( toMoney(totalPrice) );
  $totalGrandCell.text( toMoney(totalOrder) );
}

/**
 A standard function to calaculate the subtotal of a row, this is
 where you could apply tax or other data transformations if need be.
*/
function getSubtotal(quantity, price) {
  return (quantity * price).toFixed(2);
}

/**
Takes a jQuery selector, finds all matching elements for it, and totals up their contents.
It works by converting the elements list to an Array and then using Array.reduce.
@see https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce
*/
function getColumnTotal(selector) {
  return Array.from( $(selector) ).reduce(sumReducer, 0);
}

/**
The reducer function that adds up a running total. This function parses the innerHTML content
of an element and converts it to a number so math works on it.
*/
function sumReducer(total, cell) {
  return total += parseInt(cell.innerHTML, 10);
}

function toMoney(number) {
  return '$' + number.toFixed(2);
}
#TableHead td {
  border-bottom: 1px #000 solid;
}

.orderTotalCell,
#grandTotalCell,
#totalPriceCell {
  text-align: right;
 }

#TableFooter tr:first-child td {
  border-top: 1px #000 solid;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<table id="Table">
  <thead id="TableHead">
    <tr>
      <td>ID</td>
      <td>Code</td>
      <td>Client</td>
      <td>Debit/Credit</td>
      <td>Quantity</td>
      <td>Price</td>
      <td>Order Total</td>
      <td>Options</td>
    </tr>
  </thead>
  <tbody id="TableBody">
  </tbody>
  <tfoot id="TableFooter">
    <tr>
      <td colspan="4">Sub-Total</td>
      <td id="totalQuantityCell">&ndash;</td>
      <td id="totalPriceCell">&ndash;</td>
      <td id="grandTotalCell">&ndash;</td>
    </tr>
  </tfoot>
</table>

<input type="button" id="xd" value="add row">

Upvotes: 2

Terry
Terry

Reputation: 66228

There are several flaws within your script, which I will want to walk you through so that you can better understand the process:

  1. Avoid using inline JS. If you want to bind events dynamically, you can use .on() instead. Since the table is present on DOM ready, you can use $('#Table').on(...) to listen to click events on the delete button

  2. Modularise sum computation into a single function. You can create a function, say computeSum(), which will be called every time you modify the table: be it when a table row is added, or a table row is deleted. You can also call this function at runtime, so that you do not have to use server-side languages to precompute the starting sums.

    • In my example below, I will fetch the text node in the 5th and 6th columns (which is 4 and 5 by zero-based index), and convert them to float by appending + in front of them
    • I have also used the .toFixed(2) function when printing the sums, so that its nicely showing two decimal places.
  3. Fix your HTML injection. Remember that for <td> elements to be valid, they have to be nested in <tr>. You seem to have left that out by accident.

So here is a completely functional example of your code snippet:

$(function() {
  // Function to compute sum
  var computeSum = function() {
    // Get the total quantity and price by column index
    var quantity = 0,
        price = 0;
    
    // Iterate through each row
    $('#TableBody tr').each(function() {
      quantity += +$(this).find('td').eq(4).text();
      price += (+$(this).find('td').eq(5).text() * +$(this).find('td').eq(4).text());
    });
    
    // Update sum
    $('#TableFooter td.total.quantity').text(quantity.toFixed(2));
    $('#TableFooter td.total.price').text(price.toFixed(2));
  };

  // Use on to bind click event handlers to input buttons with delete-row action
  $('#Table').on('click', 'input[type="button"][data-action="delete-row"]', function(e) {
    e.preventDefault();

    // Delete row
    $(this).closest('tr').remove();

    // Recompute sum
    computeSum();
  });

  $('#xd').click(function() {
    // Remember to wrap your cells within <tr>
    var lines = "<tr>";

    lines += '<td>3</td>';
    lines += '<td>3</td>';
    lines += '<td>15</td>';
    lines += '<td>Credit</td>';
    lines += '<td>1</td>';
    lines += '<td>100.00</td>';
    lines += '<td><input type="button" value="Delete" data-action="delete-row" /></td>';

    lines += "</tr>";

    // Append new table row
    $('#TableBody').append(lines);

    // Recompute sum
    computeSum();
  });
  
  // Compute sum when starting up
  computeSum();
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<table id="Table">
  <thead>
    <tr>
      <td>ID</td>
      <td>Code</td>
      <td>Client</td>
      <td>Debit/Credit</td>
      <td>Quantity</td>
      <td>Price</td>
      <td>Options</td>
    </tr>
  </thead>
  <tbody id="TableBody">
    <tr>
      <td>1</td>
      <td>1</td>
      <td>3</td>
      <td>Debit</td>
      <td>10</td>
      <td>12.00</td>
      <td>
        <input type="button" value="Delete" data-action="delete-row" />
      </td>
    </tr>
    <tr>
      <td>2</td>
      <td>2</td>
      <td>12</td>
      <td>Debit</td>
      <td>5</td>
      <td>10.00</td>
      <td>
        <input type="button" value="Delete" data-action="delete-row" />
      </td>
    </tr>
  </tbody>
  <tfoot id="TableFooter">
    <tr>
      <td colspan="4">Total</td>
      <td class="total quantity">15</td>
      <td class="total price">170.00</td>
    </tr>
  </tfoot>
</table>

<input type="button" id="xd" value="add row">

Further improvements

There are some minor improvements that you can make to my code above, but they are considered non-mission critical and hence I did not include them in my original answer.

  1. Extensibility. If you want to compute additional columns, it would be difficult to rewrite the same lines over and over again. Instead, I recommend you store the sums in an object instead.

  2. Value fetching. We are retrieving values based on the text node in the column. Sometimes, you do not want that—say you want to include currencies, or other texts in the quantity and/or price column. In that sense, you might want to store such data in a custom HTML5 data- attribute instead.

$(function() {
  // Function to compute sum
  var computeSum = function() {
    // Get the total quantity and price by column index
    var sums = { quantity: 0, price: 0 };

    // Iterate through each table cell
    $('#TableBody tr').each(function() {
      sums.quantity += +$(this).find('td').eq(4).data('value');
      sums.price += (+$(this).find('td').eq(4).data('value')*+$(this).find('td').eq(5).data('value'));
    });

    // Update sum
    $.each(sums, function(key, value) {
      $('#TableFooter td.total.'+key).text(value.toFixed(2));
    });
  };

  // Use on to bind click event handlers to input buttons with delete-row action
  $('#Table').on('click', 'input[type="button"][data-action="delete-row"]', function(e) {
    e.preventDefault();

    // Delete row
    $(this).closest('tr').remove();

    // Recompute sum
    computeSum();
  });

  $('#xd').click(function() {
    // Remember to wrap your cells within <tr>
    var lines = "<tr>";

    lines += '<td>3</td>';
    lines += '<td>3</td>';
    lines += '<td>15</td>';
    lines += '<td>Credit</td>';
    lines += '<td class="quantity" data-value="1">1</td>';
    lines += '<td class="price" data-value="100.00">100.00</td>';
    lines += '<td><input type="button" value="Delete" data-action="delete-row" /></td>';

    lines += "</tr>";

    // Append new table row
    $('#TableBody').append(lines);

    // Recompute sum
    computeSum();
  });

  // Compute sum when starting up
  computeSum();
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<table id="Table">
  <thead>
    <tr>
      <td>ID</td>
      <td>Code</td>
      <td>Client</td>
      <td>Debit/Credit</td>
      <td>Quantity</td>
      <td>Price</td>
      <td>Options</td>
    </tr>
  </thead>
  <tbody id="TableBody">
    <tr>
      <td>1</td>
      <td>1</td>
      <td>3</td>
      <td>Debit</td>
      <td class="quantity" data-value="10">10</td>
      <td class="price" data-value="12.00">12.00</td>
      <td>
        <input type="button" value="Delete" data-action="delete-row" />
      </td>
    </tr>
    <tr>
      <td>2</td>
      <td>2</td>
      <td>12</td>
      <td>Debit</td>
      <td class="quantity" data-value="5">5</td>
      <td class="price" data-value="10.00">10.00</td>
      <td>
        <input type="button" value="Delete" data-action="delete-row" />
      </td>
    </tr>
  </tbody>
  <tfoot id="TableFooter">
    <tr>
      <td colspan="4">Total</td>
      <td class="total quantity">15</td>
      <td class="total price">170.00</td>
    </tr>
  </tfoot>
</table>

<input type="button" id="xd" value="add row">

Upvotes: 0

HenryDev
HenryDev

Reputation: 4993

You are missing:

<tr> </tr>

Tags when you add a new row. Also, just add a class that will add up "Quantities" and "Prices". Here's a working solution. Hope it helps!

   function deleteRow(btn) {
        var row = btn.parentNode.parentNode;
        row.parentNode.removeChild(row);
        sumOfColumns();
    }

    function sumOfColumns(){

        var totalQuantity = 0;
        var totalPrice = 0;
        $(".someClass").each(function(){
            totalQuantity += parseInt($(this).html());
            $(".someTotalClass").html(totalQuantity);
        });

        $(".classPrice").each(function(){
            totalPrice += parseInt($(this).html());
            $(".someTotalPrice").html(totalPrice);
        });
    }

    $(document).ready(function () {
        
        $('#xd').click(function() {
            var lines = "";

            lines += '<tr>';
            lines += '<td>3</td>';
            lines += '<td>3</td>';
            lines += '<td>15</td>';
            lines += '<td>Credit</td>';
            lines += '<td class = "someClass">1</td>';
            lines += '<td class = "classPrice">100.00</td>';
            lines += '<td><input type="button" value="Delete" onclick="deleteRow(this)"/></td>';
            lines += '</tr>';

            $('#TableBody').append(lines);
            sumOfColumns();
        });
    });
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table id="Table">
    <thead>
    <tr>
        <td>ID</td>
        <td>Code</td>
        <td>Client</td>
        <td>Debit/Credit</td>
        <td>Quantity</td>
        <td>Price</td>
        <td>Options</td>
    </tr>
    </thead>
    <tbody id="TableBody">
    <tr>
        <td>1</td>
        <td>1</td>
        <td>3</td>
        <td>Debit</td>
        <td class = "someClass">10</td>
        <td class = "classPrice">12.00</td>
        <td>
            <input type="button" value="Delete" onclick="deleteRow(this)" />
        </td>
    </tr>
    <tr>
        <td>2</td>
        <td>2</td>
        <td>12</td>
        <td>Debit</td>
        <td class = "someClass">5</td>
        <td class = "classPrice">10.00</td>
        <td>
            <input type="button" value="Delete" onclick="deleteRow(this)" />
        </td>
    </tr>
    </tbody>
    <tfoot id="TableFooter">
    <tr>
        <td colspan="4">Total</td>
        <td class = "someTotalClass">15</td>
        <td class = "someTotalPrice"">170.00</td>
    </tr>
    </tfoot>
</table>

<input type="button" id="xd" value="add row">

Upvotes: 1

Hakunamatata
Hakunamatata

Reputation: 1275

You can create a function to calculate total and call it after you add each line and on page load if you have some initial value.

function setTotal()
{
var totalPrice=0;
var totalQty=0;
    $('#TableBody').find('tr').each(
  function(){
  totalQty +=parseFloat($(this).find('td').eq(4).text());
    totalPrice +=parseFloat($(this).find('td').eq(5).text());
    //console.log(totalPrice);
});
$('#TableFooter').find('tr td').eq(1).text(totalQty);
$('#TableFooter').find('tr td').eq(2).text(totalPrice);
}
$(function(){
    setTotal();
})

$('#ID').find('tr') will find all the rows of table with id 'ID'. then you iterate through each tr using each function. Then in each row you find all the td similarly and get to specific td using eq function. eq takes index of the element.

Here is running fiddler : https://jsfiddle.net/8a4umvdr/

Upvotes: 0

Related Questions