Hello:

I need some assistance with a jquery/javascript code. I have a form with chain-linked list box of product, and prices. Each product is inside a <tr> which are added as needed. So there can be many items added to the form. The total of the items cost (qty*cost) from all added <tr> goes inside an input box called subtotal. Once I have the total, another checkbox if check adds a percentage of tax based on that subtotal.

the code to calculate my subtotal of all added <tr> is:

function update() {
var subtotal = 0;
var errorMsg = "N/A";

$('.price' , $item_rows_container).each(function(i) {
var row = $(this).closest('tr');
var price = row.find('.costbox').val().fromCurrency() * Number(row.find('.qtybox').val());
$(this).html(isNaN(price) ? errorMsg : price.toCurrency());//Display value or 'error' if calculation failed
subtotal += price.toCurrency().fromCurrency();

});

$('#subtotal').html(isNaN(subtotal) ? errorMsg : subtotal.toCurrency());

code to calculate the tax on the subtotal:

var tax = ($('#taxbox').attr('checked')) ? (subtotal * taxRate) : 0;
    $('#tax').val(isNaN(tax) ? errorMsg : tax.toCurrency());

Now inside each <tr>, i have a <td> with a checkbox which when checked, I would like to exclude the total of this <tr> from the form's subtotal. I would then want a separate inputbox with the total of all items that have been check as tax exempted.

To finalize the form's transaction, I would have a box of a sutotal of taxable items, a box for the total of non-taxable items, a box of the tax amount and then add all three for my grand total.

Can anyone help me on this, I'm appreciative of any assistance.

Best,
Mossa

Recommended Answers

All 25 Replies

Well it seems like you already have some code. Is that not working correctly or? Checking if a checkbox is checked, by the way, can be done properly and easily by using .is(':checked') (jQuery, returns true or false).

minitauros, I appreciate you chiming in! The current code --as posted, once checked adds a percentage of sales tax to the subtotal.

I want to be able to have an additional checkbox inside every <tr> of items that can be added in the form, and once this checkbox is checked, it omits any tax calculation on that item(s) only.

var tax = ($('#taxbox').attr('checked')) ? (subtotal * taxRate) : 0;
    $('#tax').val(isNaN(tax) ? errorMsg : tax.toCurrency());

the above code from my initial posting performs the tax calculation on the subtotal of the form. So essential, I want to modify that code to check and see if any checkboxes named "nontaxable" has (or have) been checked, if so keep the total of those items from tax calculations.

I trust that I'm making a little more sense.

Any further thoughts!

Mossa

Well let's say the name of these checkboxes will be omit_from_tax_calculation. You could then traverse your <tr> elements and find this checkbox, and see if it is checked. Like so:

var taxes = [];
var is_checked = false;

$('tr.your_selector').each(function()
{
    is_checked = $(this).find('input[name=omit_from_tax_calculation]').is(':checked') || false;
    if(!is_checked)
    {
        // This <tr> should be included in the tax calculation.
        taxes.push($(this).find('input.your_selector').val());
    }
});

// "taxes" now contains all the values of the <input> elements of your choosing
// which should be included in the tax calculation

I'm not sure I understand your problem 100% correctly (yet), but I think we're making progress :p.

My apology for still making this ambiguous; perhaps a link to the form would be more helpful in understanding:

https://autotechpro.net/autotechPro_AppFiles/invoice_generator/job_bill_Production.php

On the form, select from the two drown down menus, within that <tr> will be a checkbox for making the item taxable or nontaxable. at the bottom left, there is an icon for a register, click to get the option of calculating the sales on the order and then click on "Tax?".

That is the logic I am attempting to achieve. if the item is not selected as taxable, once the second checkbox in the register is checked, there shouldn't be any tax calculations.

Note: I appreciate the suggested code, but I'm not sure how to incorporate in my existing logic.

Your thoughts!

Well your form is not being displayed in the way it should be, I think, in my browser (Firefox, 1920x1080px), but I think I get the point of it!

What about using code like this?:

var total_tax;

$('table tbody#item_rows tr.item-row').each(function()
{
    // Do we need to calculate tax for this row?
    var is_taxable = $(this).find('input.taxable').is(':checked');

    if(is_taxable)
    {
        // Get the subtotal amount that must be payed for this row.
        var subtotal_price = $(this).find('textarea[name^=costbox]').html();
        subtotal_price = parseInt(subtotal_price);

        // Calculate tax.
        var tax_percentage = 0.2;
        var tax = subtotal_price * tax_percentage;

        // Remember the total amount of tax that needs to be payed.
        total_tax += tax;
    }
});

I don't think this code will work straight away; it's just to give you an idea of what I think you could do. Am I understanding what you mean correctly now? ^^

I am still can't quite figure how to omit sales calculation on the <tr> with the checkbox. Here is what have:

var rate =0.2;
var taxRate = rate;
function update() {
var subtotal = 0;
var errorMsg = "Error";

$('.price' , $item_rows_container).each(function(i) {
   var row = $(this).closest('tr');


    var price = row.find('.costbox').val().fromCurrency() * Number(row.find('.qtybox').val());
    $(this).html(isNaN(price) ? errorMsg : price.toCurrency());//Display value or 'error' if calculation failed
   subtotal += price.toCurrency().fromCurrency();//"double-shuffle" to ensure the calculation is consistent with the displayed data.

});

$('#subtotal').html(isNaN(subtotal) ? errorMsg : subtotal.toCurrency());//Display value or 'error' if calculation failed
var taxes = [];
var is_checked = false;
$($item_rows_container).each(function(i)
{
  is_checked = $(this).find('.taxable').is(':checked') || false;
if(!is_checked)
{
$('table tbody#item_rows tr.item-row').each(function(i) {
var row = $(this).closest('tr');
    // This <tr> should be included in the tax calculation.
            var price = row.find('.costbox').val().fromCurrency() * Number(row.find('.qtybox').val());
    taxes.push(price);

});
}
});

//doMath (tax)  $('#taxable').is(':checked')    

//omission
//var notax =100;
var tax = ($('#taxbox').attr('checked')) ? (subtotal * taxRate) : 0;
$('#tax').val(isNaN(tax) ? errorMsg : tax.toCurrency());//Display value or 'error' if calculation failed

var total_cpy =price2 + subtotal + tax.toCurrency().fromCurrency();//here we add labor total to the rest to get grand total
$('#total_cpy').html(isNaN(total_cpy) ? errorMsg : total_cpy.toCurrency());//Display value or 'error' if calculation failed

var total =price2 + subtotal + tax.toCurrency().fromCurrency();//"double-shuffle" to ensure the calculation is consistent with the displayed data.
$('#total').html(isNaN(total) ? errorMsg : total.toCurrency());//Display value or 'error' if calculation failed

//update_balance
var due = total - $('#paid').val().fromCurrency();
$('.due').html(isNaN(due) ? errorMsg : due.toCurrency());//Display value or 'error' if calculation failed

//update_balance2
var due_box = total - $('#paid').val().fromCurrency();
$('.due_box').html(isNaN(due_box) ? errorMsg : due_box.toCurrency());//Display value or 'error' if calculation failed

//nontaxable item total
//update_balance2
var notaxableItems_ttl = price2 + notax;
$('.nonTaxesTtl').html(isNaN(notaxableItems_ttl) ? errorMsg : notaxableItems_ttl.toCurrency());//Display value or 'error' if calculation failed
}

$item_rows_container.on('blur keyup', '.costbox, .qtybox', update);



$(document).ready(function() {
$('input').click(function(){
    $(this).select();
});

$('#paid').blur(update);//changed from $("#paid").blur(update_balance)
$('#paid').keyup(update);//added
$('#tax').blur(update);//Should the be user-enterable? It's a calculated value.
$('#taxbox').click(update);
$('.taxable').click(update);
$('#taxRate').html(taxRate * 100);//Added. This ensures the rate show to the user is the same as the rate used in the calcs
//bind();

any thoughts!

Hi Mossa, I've given the code a good once over all through. There's a lot of uncertainty in it so I may have broken more than I've fixed, however the overall structure should now be correct. You will need to rework various lines to ensure it does exactly what you want (see comments).

The main changes are :-

  • to call updatelabor() from within update()
  • to introduce into update() a totals object in which taxable, taxExempt and tax are accrued separately.
  • to return from updatelabor() a totals object of identical structure as the totals object in update().

This allows taxable, taxExempt and tax values to be kept separate for display purposes, and added together where necessary.

As you will see below, my main uncertainties lie in how the data is to be displayed once the raw row totals have been calculated and accrued. You will have a much better idea of exactly what is required here, and it should be reasonably simple for you to fix.

I also rationalised the .delete_row handler and added a line to the #addRow handler.

$(function() {
    var data = ....;//very long = removed for posting in DaniWeb
    var $row_template = $('#row_template tr'),
    var $item_rows_container = $("#item_rows");

    var taxRate = 0.2;// this is pulled from percentage.php file included 

    var errorMsg = "Error";
    var errorMsg1 = "N/A";
    var errorMsg_3 = "N/A";

    $("#add_row").on('click', function() {
        if ($item_rows_container.find("tr").length < 40) {// 40 is the number of row limit
            $row_template.clone().fadeIn("slow").appendTo($item_rows_container);
            $(".delete").show();
        }
        return false;
    }).trigger('click');

    $item_rows_container.on('click', '.delete_row', function() {
        if (confirm("Deleting Item, Are You Sure?")) {
            $(this).closest("tr").remove();
            if ($(".delete").length <= 1) {
                $(".delete").hide();
            }
            update();
        }
    }).on('change', '.categories', function() { //this anonymous function was addfield()
        var $this = $(this);
        var d = data[$this.val()] || {};
        var $row = $this.closest("tr");
        var $subcat_menu = $('.service_subcat', $row).empty().append('<option name="specify">------SPECIFY-----</option>');
        $.each(d, function(key, value) {
            $subcat_menu.append('<option value="' + key + '">' + value['subcat_label'] + '</option>'); 
            $row.find(".cattext").val($this.find("option:selected").text()); //add this line to pass the selection value to hidden form
        });
        $(".categories, .service_subcat").click(function() {// we use this function to get the text value of the selection box
            var selected = $(this).find("option:selected").text(); 
            //alert(selected+' clicked!'); 
        });
        $("textarea").inputexpander();// auto texarea expander need jquery.textareaexpander.js
        $subcat_menu.trigger('change');
    }).on('change', '.service_subcat', function() {
        var $this = $(this);
        var $row = $this.closest("tr");
        var category = $row.find(".categories").val();
        var d = (category && data[category] && data[category][$this.val()]) ? data[category][$this.val()] : {};
       // $('.taxable', $row).html(d['taxable'] || '');
        $(".invtbox", $row).html(d['invt'] || '');
        $(".descbox", $row).html(d['product_desc'] || '');
        $(".qtybox", $row).html(d['qty'] || '');
        $(".costbox", $row).html(d['cost'] || '');
        $(".sub_cattext", $row).val($this.find("option:selected").text()); //add this line to pass the selection value to hidden form
    });

    function updatelabor() {//non-taxable items --labor
        var $this, $row, labor, laborTtl,
            totals = {taxable: 0, taxExempt: 0, tax: 0};//object of the same structure as in update(). Only relevant totals are incremented, others remain at zero.
        $('.price2').each(function() {
            $this = $(this);
            $row = $this.closest('tr');
            labor = $row.find('.cost').val().fromCurrency() * Number($row.find('.qty').val());
            $this.html(isNaN(labor) ? errorMsg1 : labor.toCurrency());
            totals.taxExempt += labor.toCurrency().fromCurrency();//assume .taxExempt
        });
        var laborTtl = totals.taxable + totals.taxExempt + totals.tax;//some of the terms will be zero but no harm in including them
        $('#laborTtl').html(isNaN(laborTtl) ? errorMsg1 : .toCurrency(laborTtl));
        return totals;
    }

    function update() {
        var $this, $row, rowUnitPrice, rowQty, rowRawPrice, rowTax, rowIsTaxable, rowPrice,
            totals = {taxable: 0, taxExempt: 0, tax: 0 };
        $('.price', $item_rows_container).each(function() {
            $this = $this;
            $row = $this.closest('tr');
            rowUnitPrice = $row.find('.costbox').val().fromCurrency() || 0;
            rowQty = Number($row.find('.qtybox').val()) || 0;
            rowRawPrice = (rowUnitPrice * rowQty).toCurrency().fromCurrency();
            rowTax = (rowRawPrice * taxRate).toCurrency().fromCurrency();
            rowIsTaxable = !$row.find('.taxable').is(':checked');//Not too sure about the sense here. Remove ! if it's wrong.
            if(rowIsTaxable) {
                rowPrice = rowRawPrice + rowTax;
                totals.taxable += rowRawPrice;
                totals.tax += rowTax;
            }
            else {
                rowPrice = rowRawPrice;
                totals.taxExempt += rowRawPrice;
            }
            $this.html(isNaN(rowPrice) ? errorMsg : rowPrice.toCurrency());
        });

        //LOTS OF UNCERTAINTY FROM THIS POINT ON.

        //This line possibly needs to be split into two, in order to display totals.taxable and totals.taxExempt separately.
        $('#subtotal').html(isNaN(totals.taxable + totals.taxExempt) ? errorMsg : subtotal.toCurrency());

        //Assume this is the correct place to add in the labor totals
        var laborTotals = updatelabor();
        totals.taxable += laborTotals.taxable;
        totals.taxExempt += laborTotals.taxExempt;
        totals.tax += laborTotals.tax;

        $('#tax').val(isNaN(totals.tax) ? errorMsg : $('#taxbox').attr('checked') ? totals.tax.toCurrency() : (0).toCurrency());
        $('#nonTaxesTtl').html(isNaN(totals.taxExempt) ? errorMsg_3 : totals.taxExempt.toCurrency());

        var total_cpy = totals.taxable + totals.taxExempt;//??
        $('#total_cpy').html(isNaN(total_cpy) ? errorMsg : total_cpy.toCurrency());

        var total = total_cpy + totals.tax;
        $('#total').html(isNaN(total) ? errorMsg : total.toCurrency());

        var due = total - $('#paid').val().fromCurrency();
        $('.due').html(isNaN(due) ? errorMsg : due.toCurrency());//update_balance
        $('.due_box').html(isNaN(due) ? errorMsg : due.toCurrency());//update_balance2
    }

    //Attach event handlers
    //Note: updatelabor() is not attached directly as it is now called from inside update().
    $('input').click(function() {
        $(this).select();
    });
    $item_rows_container.on('blur keyup', '.cost, .qty, .costbox, .qtybox', update);
    $item_rows_container.on('click', '.taxable', update);
    $('#paid').on('blur keyup', update);
    $('#tax').on('blur', update);
    $('#taxbox').on('click', update);

    $('#taxRate').html(taxRate * 100);//This ensures the rate shown to the user is the same as the rate used in the calcs

    $("#date").val(print_today());
});

Hi Airshow,

My sincere thanks for the eval and the restructuring of the code. After a few minor modifications on my end --as you correctly noted, the concept seems to be working as expected. I'm still tweaking! Please Have a look at the live form
Here

Advise of any necessary notewordy improvement/changes.

Again, Thank you!
Mossa

Hi Mossa,

My line $this = $this; should be $this = $(this);. You can then use $this in a couple of lines below instead of $(this).

My totals object was intended to supplant subtotal but I made a mistake in not purging subtotal completely from the code. I think that with your changes lower down, the line var subtotal = 0; can go now.

Most of your mods are in my area of great uncertainty, and the lines you've changed are the ones I most expected to have been be wrong.

I'm amazed you haven't needed to change more, though from what you say, maybe there's a way to go yet.

I guess you're into testing-testing-testing now?

Good day Airshow,

My minimal tweaking is simply a testament to your attention to the details in your coding. The form is working as expected. The performance has indeed improved.

Now I'm attempting to add an option for providing discount to each line item. I'm thinking of an implementation involving a dropdown box (containing either % or $) discount. Something like:

<td >
<input type="text" id="discountVal" maxlength="15" name="discountVal" value="">&nbsp;<label for="discountOpt">Discount type</label><select id="discountOpt" name="discountOpt"><option value="dollar">$</option><option value="percentage" selected="">%</option></select>
</td>

I'm however, wondering if it is best to provide that option on the subtotals vs on the line items total <tr>. The issue with apply the discount on the subtotals would be providing that option twice. An option if the discount is to be applied to nontaxable total or the taxable total.

Any thoughts on this?

Mossa

Hi Mossa,

I think it depends entirely on how your discount structure is going to work. Mathmatically, a reasonably complete solution would allow each line entry to have its own quantity discount and then for the subtotal/total to have a overall "whole order" discount (eg free shipping for orders over $50). Of course, either of these two dicount elements could (and often would) equate to no-discount, as determined by the actual values involved.

I wonder if the best way to approach discounts might be use a spreadsheet to sheme out how an order might work, allowing you to concentrate on the necessary math rather than the programming. After all, the HTML/javascript at issue here is very spreadsheet-like. I have adopted this approach before and found a spredsheet to very liberating in the free-thought phase, and the subsequent conversion to HTML/javascript was fairly trivial.

Hi Airshow,

Thanks for your thoughts on this. My thinking for the purpose of the form on which this is to be applied is to provide two boxes on the same line as the item being added --smiliar to what we did with the taxable option.

The form user would enter in box 1, the amount of the discount and then in box 2, choose either $ or %. Once those two boxes have been populated the discount is calculated on the line item and the total (qty*itemCost -(discount ttl) will be passed to the overall form calculations. This structure will be the same on each line item that is added to the form.

I need help with intergrating that concept into the existing code.

Thanks
Mossa--

Mossa, I'm struggling very slightly with the concept.

For the end user, discount would normally be something that's calculated and offered automatically by an underlying algorithm. An end user wouldn't normally be allowed to choose what discount applies.

So is this form the mechanism by which Administrators will establish items' discount rules? Or maybe my notion "end user" is wrong.

My apology! By end-user I mean a shop owner who uses the form to generate a bill/invoice for his customers. The form is part of an enterprise system that a shop manager uses. The discount option will be available to that owner to determine whether to offer his/her customers a discount on the bill/invoice.

I trust this makes a bit more sense...

Mossa, please don't apologise - I should know this stuff about your project but it's been a while and my head is partly elsewhere right now as you know.

What you want is reasonably trivial. I'll see if I can code it up for you. Might be a couple of days.

my head is partly elsewhere right now as you know.

More of a reason why I feel bad making any request from you. I appreciate your willingness to assist in spite of...Thank you!

A couple of days is abosultely fine...

Mossa--

Mossa, I will need to grab the latest version of your script but the link you posted earier is currently giving "In Development ...". Any chance you could post an alternative link please.

Hi Mossa,

I have modified update() to address discounts as you defined them. You will need to :

  • modify the row template to add the two discount elements (class="discountVal" and class="discountBasis").
  • (if appropriate) modify the row template to include a class="discount" element to display the calculated discount.
  • (if appropriate) similarly modify updatelabor().

Here's update() :

function update() {
    var $this, $row, rowUnitPrice, rowQty, rowRawPrice, rowTax, rowIsTaxable, rowPrice,
        rowDiscountValue, rowDiscountBasis, rowDiscount,
        totals = {taxable: 0, taxExempt: 0, tax: 0 };
    $('.price', $item_rows_container).each(function() {
        $this = $(this);
        $row = $this.closest('tr');
        rowUnitPrice = $row.find('.costbox').val().fromCurrency() || 0;
        rowQty = Number($row.find('.qtybox').val()) || 0;
        rowRawPrice = (rowUnitPrice * rowQty).toCurrency().fromCurrency();

        // *** start discount calcs ***
        //assume each row's two discount input elements have 
        //   class="discountVal" (a number)
        //   class="discountBasis" (% or $)
        rowDiscountValue = Number($row.find('.discountVal').val());
        rowDiscountBasis = $row.find('.discountBasis').val();
        if(rowDiscountBasis == '%') {
            rowDiscountValue = Math.max(Math.min(rowDiscountValue, 100), 0);//apply sensible limits
            rowDiscount = rowRawPrice * rowDiscountValue / 100;
        }
        else {
            rowDiscountValue = Math.max(Math.min(rowDiscountValue, rowRawPrice), 0);//apply sensible limits
            rowDiscount = rowDiscountValue;
        }
        rowDiscount = rowDiscount.toCurrency().fromCurrency() || 0;
        rowDiscountedPrice = rowRawPrice - rowDiscount;
        // *** end discount calcs ***

        rowTax = (rowDiscountedPrice * taxRate).toCurrency().fromCurrency();
        rowIsTaxable = $row.find('.taxable').is(':checked');

        //In this block, rowRawPrice is now replaced by rowDiscountedPrice.
        if(rowIsTaxable) {
            rowPrice = rowDiscountedPrice + rowTax;
            totals.taxable += rowDiscountedPrice;
            totals.tax += rowTax;
        }
        else {
            rowPrice = rowDiscountedPrice;
            totals.taxExempt += rowDiscountedPrice;
        }

        // *** start discount presentation ***
        //if you want to display the row's discount (especially useful for percentage).
        $row.find(".discount").html(isNaN(rowDiscount) ? errorMsg : rowDiscount.toCurrency());
        // *** end discount presentation ***

        $this.html(isNaN(rowPrice) ? errorMsg : rowPrice.toCurrency());
    });

    //This line possibly needs to be split into two, in order to display totals.taxable and totals.taxExempt separately.
    // $('#subtotal').html(isNaN(totals.taxable + totals.taxExempt) ? errorMsg : subtotal.toCurrency());
    //Assume this is the correct place to add in the labor totals
    var laborTotals = updatelabor();
    totals.taxable += laborTotals.taxable;
    totals.taxExempt += laborTotals.taxExempt;
    totals.tax += laborTotals.tax;
    $('#tax').val(isNaN(totals.tax) ? errorMsg : $('#taxbox').attr('checked') ? totals.tax.toCurrency() : (0).toCurrency());
    $('#nonTaxesTtl').html(isNaN(totals.taxExempt) ? errorMsg_3 : totals.taxExempt.toCurrency());// total non-taxable amount
    $('#TaxableTtl').html(isNaN(totals.taxable) ? errorMsg_3 : totals.taxable.toCurrency()); // total taxable amount
    //$('#subtotal').html(isNaN(totals.taxable + totals.taxExempt) ? errorMsg : subtotal.toCurrency());

    //var totalTax =totals.tax;
    var total_cpy = totals.taxable + totals.taxExempt + totals.tax;//??
    $('#total_cpy').html(isNaN(total_cpy) ? errorMsg : total_cpy.toCurrency());
    var total = total_cpy ;//+ totalTax;
    $('#total').html(isNaN(total) ? errorMsg : total.toCurrency());
    var due = total - $('#paid').val().fromCurrency();
    $('.due').html(isNaN(due) ? errorMsg : due.toCurrency());//update_balance
    $('.due_box').html(isNaN(due) ? errorMsg : due.toCurrency());//update_balance2
}

You also need to extend the .on('blur keyup', ...) expression to attach update as an event handler to the new discount input elements, as follows:

$item_rows_container.on('blur keyup', '.cost, .qty, .costbox, .qtybox, .discountVal, .discountBasis', update);

I've left you some work to do with regard to the HTML and updateLabor() but hopefully what I've given you above is complete and correct.

commented: Great Stuff Airshow, Thanks! +2

Airshow,

You are awesome! The code looks great! I will begin implementing and testing shortly.

I'll advise momentarily.

Thank you
Mossa

Works great! Thank you! see it live

Mossa

Hi Mossa,

I think a few little tweaks might be necessary to finish off this little segment - but nothing too tricky. The overall size and shape of the code seems about right.

Airshow

Hi Airshow,

Overall, the code works fine. I have incorporated a total discount field, but it appears that the field only captures the discount of the first inserted line item. The total discount of all subsequent items is not added into the total discount field. However, the calculation is performed correctly on that item as well as the grand total. I'm hunting for the culprit.

Mossa--

Mossa,

You should have something like this ...

  1. Extend totals to include discount:

    totals = {taxable: 0, taxExempt: 0, tax: 0, discount: 0 };

  2. Accumulate .discount inside the loop :

    //find
    rowDiscount = rowDiscount.toCurrency().fromCurrency() || 0;
    //insert after
    totals.discount += rowDiscount;

  3. Present totals.discount in your "total discount" field

Airshow, your fix works great!

Thank you very much!
Mossa

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.