James
James

Reputation: 1706

jQuery invoice calculations for items and totals

I am new to JavaScript.

Current issues I'm facing:

  1. When you add a new row it's adding the row (great!) however the first new one added works fine, applies _0 at the end of the name / id however when you add more it does not do _1, _2 etc.. but instead _1_1, _2_2_2, _3_3_3_3 - any ideas how I can fix that?
  2. I need to calculate the sub total and totals from figures added on the invoice items, like price, qty, discount (figure or %) sub-total and then overall from everything at bottom. The VAT is set as a variable in PHP at the moment for example define('VAT_RATE','20') which would be 20%

UPDATE

I have attempted to start it, getting NaN though and its only taking anything with a class="calculate" and adding to sub-total but it's a start I guess. I added qty but does not seem to play ball, vat and total I have not added.

JSFiddle: http://jsfiddle.net/morgjvg6/4/

JS:

$(document).ready(function () {

    // copy customer details to shipping
    $('input.copy-input').on("change keyup paste", function () {
        var thisID = $(this).attr('id');
        $('input#' + thisID + "_ship").val($(this).val());
    });

    // add new product row on invoice
    $(".add-row").click(function () {
        $("#invoice_table").each(function () {
            var tds = '<tr>';
            jQuery.each($('tr:last td', this), function (index, element) {
                var html = $(this).html().replace(/invoice_product/g, 'invoice_product_' + index)
                    .replace(/invoice_product_desc/g, 'invoice_product_desc_' + index)
                    .replace(/invoice_product_qty/g, 'invoice_product_qty_' + index)
                    .replace(/invoice_product_price/g, 'invoice_product_price_' + index)
                    .replace(/invoice_product_sub/g, 'invoice_product_sub_' + index);
                tds += '<td>' + html + '</td>';
            });
            tds += '</tr>';
            if ($('tbody', this).length > 0) {
                $('tbody', this).append(tds);
            } else {
                $(this).append(tds);
            }
        });

        return false
    });

// Adding the change events for the Price and 
// quantity fields only..
// Changing the total should not affect anything
calculateTotal();

$('.calculate').on('change keyup paste', function() {
    updateTotals(this);
    calculateTotal();
});

function updateTotals(elem) {
    // This will give the tr of the Element Which was changed
    var $container = $(elem).parent().parent();
    var quantity = $container.find('.calculate-qty').val();
    var price = $container.find('.calculate').val();
    var subtotal = parseInt(quantity) * parseFloat(price);
    $container.find('.calculate-sub').text(subtotal.toFixed(2));
}

function calculateTotal(){
  // This will Itearate thru the subtotals and 
    // claculate the grandTotal and Quantity here

    var lineTotals = $('.calculate-sub');
    var grandTotal = 0.0;
    var totalQuantity = 0;
    $.each(lineTotals, function(i){
        grandTotal += parseFloat($(lineTotals[i]).text()) ;
    });

    $('.invoice-sub-total').text(parseFloat(grandTotal ).toFixed(2) );  
}

});

Upvotes: 1

Views: 5556

Answers (1)

Artur Filipiak
Artur Filipiak

Reputation: 9157

First, you have to use event delegation for input fields that were created dynamically:

// delegate events on #invoice_table:
$('#invoice_table').on('change keyup paste', '.calculate', function() {
    updateTotals(this);
    calculateTotal();
});

Next thing is that you used .text() to update input field:

$container.find('.calculate-sub').text(subtotal.toFixed(2));

while you should use .val() :

$container.find('.calculate-sub').val(subtotal.toFixed(2));

The same in calculateTotal() method, where you should also use .val().

Next, this part is unnecesary :

$("#invoice_table").each(function () {

since you have only one table (and only one element in DOM can have that id).

Finaly, I would not use id attributes, and give array names for input fields. That way you don't have to play with .replace() and you can easily send all the values to your back-end app as arrays.

Also, I'd clone() the whole tr element initially and use it later on, instead of setting loops for each click.


// copy customer details to shipping
$('input.copy-input').on("change keyup paste", function () {
    $('input#' + this.id + "_ship").val($(this).val());
});

// add new product row on invoice
var cloned = $('#invoice_table tr:last').clone();
$(".add-row").click(function (e) {
    e.preventDefault();
    cloned.clone().appendTo('#invoice_table'); 
});

calculateTotal();

$('#invoice_table').on('change keyup paste', '.calculate', function() {
    updateTotals(this);
    calculateTotal();
});

function updateTotals(elem) {
    var tr = $(elem).closest('tr'),
        quantity = $('[name="invoice_product_qty[]"]', tr).val(),
        price = $('[name="invoice_product_price[]"]', tr).val(),
        percent = $('[name="invoice_product_discount[]"]', tr).val(),
        subtotal = parseInt(quantity) * parseFloat(price);

    // CALCULATE DISCOUNT PERCENTAGE:
    if(percent && $.isNumeric(percent) && percent !== 0){
        subtotal = subtotal - ((parseInt(percent) / 100) * subtotal);
    }

    $('.calculate-sub', tr).val(subtotal.toFixed(2));
}

function calculateTotal(){
    var grandTotal = 0.0;
    var totalQuantity = 0;
    $('.calculate-sub').each(function(){
        grandTotal += parseFloat($(this).val()) ;
    }); 
    $('.invoice-sub-total').text(parseFloat(grandTotal ).toFixed(2) );  
}

JSFiddle (Note, I have removed id attributes and added [] to the input fields names)


An example of how you could retrieve values sent to PHP
(Each iteration inside loop will be analogue to each tr):

foreach($_POST['invoice_product'] as $key => $value){
    $name = $value;
    $qty = $_POST['invoice_product_qty'][$key];
    $price = $_POST['invoice_product_price'][$key];
    $discount = $_POST['invoice_product_discount'][$key];
    // ...
}

Upvotes: 5

Related Questions