javascript product price calculator

Hi,

I’ve set up a simple javascript addition calculator that returns the value underneath. What I’m trying to do is then take that value and then automatically work out the VAT 20 % of it and then add these two values ( total & vat ) together to make a grand total.

I’ve tried to add an event listener to work out the grand total but I can’t seem to get it to work,

Here’s the code I have below, I’ve also added to a js fiddle here https://jsfiddle.net/z91my0wn/

html

<head>
  <meta charset="UTF-8">
  <title>Price Calculator</title>
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous">
</head>

<body>

  <div class="container">
    <div class="row">
      <div class="col-sm-12 col-md-8 offset-md-2 col-lg-6 offset-lg-3 ">
        <h1>Price Calculator</h1>
        <div class="form-group">
          <div class="row mt-3">
            <div class="col-md-10"> <label>Product 1 </label></div>
            <div class="col-md-2">  <input type="text" class="form-control" id="num-one"></div>
          </div>
          <div class="form-group mt-3">
            <div class="row">
            <div class="col-md-10"> <label>Product 2 </label></div>
            <div class="col-md-2">  <input type="text" class="form-control" id="num-two"></div>
          </div>
          </div>
          <div class="row">
           <div class="col-md-3 offset-md-7 pr-0" style="text-align:right;">Total Ex Vat :</div><div class="col-md-2" style="text-align:right;" id="add-sum"></div>
          </div>
           <div class="row">
           <div class="col-md-2 offset-md-8 pr-0" style="text-align:right;">Vat :</div><div class="col-md-2" id="add-vat" style="text-align:right;"></div>
          </div>
           <div class="row">
           <div class="col-md-2 offset-md-8 pr-0"  style="text-align:right;">Total :</div><div class="col-md-2" id="total" style="text-align:right;"></div>
          </div>

    	   
        </div>
      </div>
    </div>
   
</body>

Javascript

<script>
  var numOne = document.getElementById('num-one');
  var numTwo = document.getElementById('num-two');
  var addSum = document.getElementById('add-sum');
  var numVat = document.getElementById('add-vat');
  var numTotal = document.getElementById('total');
  numOne.addEventListener('input', add);
  numTwo.addEventListener('input', add);
  addSum.addEventListener(total);
  numVat.addEventListener(total);

  function add() {
    var one = parseFloat(numOne.value) || 0;
    var two = parseFloat(numTwo.value) || 0;
    addSum.innerText = one + two;
  }

  function total() {
    var three = parseFloat(addSum.value) || 0;
    var four = parseFloat(numVat.value) || 0;
    total.innerText = three + four;
  }
  //this is to work out vat but I'm not sure how to integrate it 
function percentage(partialValue, totalValue) {
   return (100 * partialValue) / totalValue;
} 
</script>

Any help would be much appreciated : )

Investigating the browser console, I see Uncaught TypeError: Failed to execute 'addEventListener' on 'EventTarget': 2 arguments required, but only 1 present.

Let’s get that taken care of first, by removing those event listeners and calling the total function from the end of the add one.

  // addSum.addEventListener(total);
  // numVat.addEventListener(total);
  function add() {
    ...
    total();
  }

You also have a percentage function that isn’t being used, and doesn’t seem to give a percentage. As VAT is 20%, you can multiply by 0.20 to get that amount.

// function percentage(partialValue, totalValue) {
//    return (100 * partialValue) / totalValue;
// } 
function getVat(amount) {
    return amount * 0.20;
}

We can then use that getVat function from within the total function.

  function total() {
    numVat.value = getVat(addSum.value);
    var three = parseFloat(addSum.value) || 0;
    var four = parseFloat(numVat.value) || 0;
    total.innerText = three + four;
  }

But that doesn’t work. Why not? The getVat function is all good.

Its the addSum and numVat that are bad. They don’t use the value property, because they are not input fields. Instead innerText can be used.

  function total() {
    numVat.value = getVat(addSum.innerText);
    var three = parseFloat(addSum.innerText) || 0;
    var four = parseFloat(numVat.innerText) || 0;
    total.innerText = three + four;
  }

That works now, but does result in lots of decimal points for the VAT, such as 2.4000000000000004. To fix that, we should not restrict the decimal places when values are being calculated. Instead, it’s only when its being displayed to the screen that we should affect the decimal values.

We can update the total function to use the toFixed method to achieve that.

  function total() {
    var vat = getVat(addSum.innerText);
    numVat.innerText = Number(vat).toFixed(2);
    var three = parseFloat(addSum.innerText) || 0;
    var four = parseFloat(numVat.innerText) || 0;
    total.innerText = Number(three + four).toFixed(2);
  }

The last problem is as to why the total isn’t being updated, and that’s because total is not a reference to the total section on the page. Instead, it’s numTotal.

  var numTotal = document.getElementById('total');
...
  function total() {
    ...
    // total.innerText = Number(three + four).toFixed(2);
    numTotal.innerText = Number(three + four).toFixed(2);
  }

And that all now works.

1 Like

Now that the code is all working, that’s not the end of the process. We should leave the code better than we found it too.

We can use beautifier.io to clean up the formatting of the code, and then run the code through jslint.com to find obvious problems.

  • Double quotes are preferred for strings instead of single quotes.
  • Next is that out-of-scope code occurs. That is where a function is used before it is declared.

And JSLint is now happy with that code.

function calcVat(amount) {
    return amount * 0.20;
}
function total() {
    var vat = calcVat(addSum.innerText);
    numVat.innerText = Number(vat).toFixed(2);
    var three = parseFloat(addSum.innerText) || 0;
    var four = parseFloat(numVat.innerText) || 0;
    numTotal.innerText = Number(three + four).toFixed(2);
}
function add() {
    var one = parseFloat(numOne.value) || 0;
    var two = parseFloat(numTwo.value) || 0;
    addSum.innerText = one + two;
    total();
}

var numOne = document.getElementById("num-one");
var numTwo = document.getElementById("num-two");
var addSum = document.getElementById("add-sum");
var numVat = document.getElementById("add-vat");
var numTotal = document.getElementById("total");
numOne.addEventListener("input", add);
numTwo.addEventListener("input", add);

That’s not the end though.

Function names should typically be in the form of verb-noun. For example, getVat, calcTotal, addProducts. So I’ll rename the functions to more closely follow those examples.

In the calcTotal function, the three and fourvariable names are quite terrible names. Instead of using those names, we can clarify at the start of the function that it is the sum and the vat that we are using.

function total() {
    var sum = parseFloat(addSum.innerText);
    var vat = calcVat(sum);
    numVat.innerText = Number(vat).toFixed(2);
    numTotal.innerText = Number(sum + vat).toFixed(2);
}

Also, it causes more problems when functions reach out to change other things. To avoid those troubles, its best if the functions receive information as arguments to the function.

function calcTotal(sumEl, vatEl, totalEl) {
    var sum = parseFloat(sumEl.innerText);
    var vat = calcVat(sum);
    vatEl.innerText = Number(vat).toFixed(2);
    totalEl.innerText = Number(sum + vat).toFixed(2);
}
function addProducts() {
    ...
    calcTotal(addSum, numVat, numTotal);
}

The total function is now easily seen to do three big things. One is to get the values from the elements, one is to calculate the total, and the other is to update the totals. Functions tend to be more stable when you separate those different things out to separate functions.

I don’t want a separate function to update each value though, as that seems overboard. Instead I’ll use a function that receives two arguments - the place to update and the value to update it with. I’ve called it updateDollarText to help make it clear that it’s showing values to two decimal places.

function updateDollarText(value, el) {
    el.innerText = Number(value).toFixed(2);
}
function calcTotal(sumEl, vatEl, totalEl) {
    var sum = parseFloat(sumEl.innerText);
    var vat = calcVat(sum);
    updateDollarText(vat, vatEl);
    updateDollarText(sum + vat, totalEl);
}

The addProducts function is where the last of our cleanup takes place.

function addProducts() {
    var one = parseFloat(numOne.value) || 0;
    var two = parseFloat(numTwo.value) || 0;
    addSum.innerText = one + two;
    calcTotal(addSum, numVat, numTotal);
}

The one and two variables can be renamed to be more clear to price1 and price2.

function addProducts() {
    var price1 = parseFloat(numOne.value) || 0;
    var price2 = parseFloat(numTwo.value) || 0;
    addSum.innerText = price1 + price2;
    calcTotal(addSum, numVat, numTotal);
}

We can also use updateDollarText on the sum.

    // addSum.innerText = price1 + price2;
    updateDollarText(one + two, addSum);

When we add the products, there are three main things that get done. One is to update the exVat total, one is to update the vat total, and the other is the update the overall total.

Because that’s how we think about things, it helps if the code matches how we think about things. The addProducts function should only have three functions calls, that each match those described activities.

Here’s the updated code for updating the exVat total.

function getFieldPrice(inputField) {
    return parseFloat(inputField.value) || 0;
}
function updateExVat(price1Field, price2Field, exVatEl) {
    var price1 = getFieldPrice(price1Field);
    var price2 = getFieldPrice(price2Field);
    updateDollarText(price1 + price2, exVatEl);
}
function addProducts() {
    updateExVat(numOne, numTwo, addSum);
    ...
}

Here is the code for updating the vat

function getElPrice(el) {
    return parseFloat(el.innerText) || 0;
}
function updateVat(exVatEl, vatEl) {
    var sum = getElPrice(exVatEl);
    var vat = calcVat(sum);
    updateDollarText(vat, vatEl);
}

And lastly, calcTotal we’ll replace with being updateTotal.

function updateTotal(sumEl, vatEl, totalEl) {
    var exVat = getElPrice(sumEl);
    var vat = getElPrice(vatEl);
    updateDollarText(exVat + vat, totalEl);
}

That leaves us with a much updated and cleaner addProducts function:

function addProducts() {
    updateExVat(numOne, numTwo, addSum);
    updateVat(addSum, numVat);
    updateTotal(addSum, numVat, numTotal);
}

Here is the fully updated code, that takes care of things in as simple as practical a manner.

var numOne = document.getElementById("num-one");
var numTwo = document.getElementById("num-two");
var addSum = document.getElementById("add-sum");
var numVat = document.getElementById("add-vat");
var numTotal = document.getElementById("total");

function getFieldPrice(inputField) {
    return parseFloat(inputField.value) || 0;
}
function getElPrice(el) {
    return parseFloat(el.innerText) || 0;
}
function updateDollarText(value, el) {
    el.innerText = Number(value).toFixed(2);
}
function updateExVat(price1Field, price2Field, exVatEl) {
    var price1 = getFieldPrice(price1Field);
    var price2 = getFieldPrice(price2Field);
    updateDollarText(price1 + price2, exVatEl);
}
function calcVat(amount) {
    return amount * 0.20;
}
function updateVat(exVatEl, vatEl) {
    var sum = getElPrice(exVatEl);
    var vat = calcVat(sum);
    updateDollarText(vat, vatEl);
}
function updateTotal(sumEl, vatEl, totalEl) {
    var exVat = getElPrice(sumEl);
    var vat = getElPrice(vatEl);
    updateDollarText(exVat + vat, totalEl);
}
function addProducts() {
    updateExVat(numOne, numTwo, addSum);
    updateVat(addSum, numVat);
    updateTotal(addSum, numVat, numTotal);
}

numOne.addEventListener("input", addProducts);
numTwo.addEventListener("input", addProducts);

The main benefit here is that each function is nice and simple, easy to understand, and easy to update when additional features are required.

1 Like

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