adding the sum of two separate functions - javascript

(ETA: I'm working on this for a class and the teacher wants everything to be "oninput"...yes, it's annoying :p )
I'm working on a form where each function miltiplies a number and gives me a "subtotal" on input. I'd like to take the two "subtotal" answers from the two functions and add them togething into a "total" amount. I feel like this should be simple but nothing I've tried works.
Here's what I've got in the javascript that works to give me the two subtotals:
function myCalculator() {
var qty1 = document.getElementById('qty1').value;
document.getElementById('subTotalOne').innerHTML = '$ ' + qty1 * 19.99;
}
function myCalculatorTwo() {
var qty2 = document.getElementById('qty2').value;
document.getElementById('subTotalTwo').innerHTML = '$ ' + qty2 * 37.99;
}
Here's the important parts of the html:
<div class="qty">
<label for="qty">Qty</label><br>
<input type="number" id="qty1" placeholder="0" oninput="myCalculator()"/><br>
<input type="number" id="qty2" placeholder="0" oninput="myCalculatorTwo()"/><br>
</div>
<div class="price">
<label for="price">Price</label>
<p>$19.99</p>
<p>$37.99</p>
</div>
<div class="subtotal">
<label for="subTotal">Total</label><br>
<span class="subTotalOne" id="subTotalOne">$</span><br>
<span class="subTotalTwo" id="subTotalTwo">$</span><br>
</div>
<div class="total">
<label for="total">Order Total</label><br>
<span class="orderTotal" id="orderTotal" oninput="orderTotal()">$</span><br>
</div>
I'm trying to add the subTotalOne and subTotalTwo and have them output at orderTotal, essentially. :)
Thanks!

//Global variables (concidering ID is unique)
let subTotalOne, subTotalTwo, qty1, qty2, orderTotal;
const setup = () => {
subTotalOne = document.getElementById('subTotalOne');
subTotalTwo = document.getElementById('subTotalTwo');
qty1 = document.getElementById('qty1');
qty2 = document.getElementById('qty2');
orderTotal = document.getElementById('orderTotal');
myCalculator();
myCalculatorTwo();
};
const updateTotal = (target, value) => {
if(target == null || value == null || Number.isNaN(value)) return;
target.textContent = `$ ${value.toFixed(2)}`;
target.setAttribute('data-value', value.toFixed(2));
}
const getTotal = () => {
if(subTotalOne == null || subTotalTwo == null) return 0;
const [value1, value2] = [
Number.parseFloat((subTotalOne.dataset?.value ?? 0), 10),
Number.parseFloat((subTotalTwo.dataset?.value ?? 0), 10)
];
if(Number.isNaN(value1) || Number.isNaN(value2)) return;
else return value1 + value2;
};
const updateOrderTotal = () => updateTotal(orderTotal, getTotal());
const myCalculator = () => {
const value = Number.parseFloat(qty1.value || 0, 10) * 19.99;
updateTotal(subTotalOne, value);
updateOrderTotal();
}
const myCalculatorTwo = () => {
const value = Number.parseFloat(qty2.value || 0, 10) * 37.99;
updateTotal(subTotalTwo, value);
updateOrderTotal();
}
window.addEventListener('load', setup);
<div class="qty">
<label for="qty">Qty</label><br>
<input type="number" id="qty1" placeholder="0" oninput="myCalculator()" min="0"><br>
<input type="number" id="qty2" placeholder="0" oninput="myCalculatorTwo()" min="0"><br>
</div>
<div class="price">
<label for="price">Price</label>
<p data-value="19.99">$19.99</p>
<p data-value="37.99">$37.99</p>
</div>
<div class="subtotal">
<label for="subTotal">Total</label><br>
<span class="subTotalOne" id="subTotalOne">$</span><br>
<span class="subTotalTwo" id="subTotalTwo">$</span><br>
</div>
<div class="total">
<label for="total">Order Total</label><br>
<span class="orderTotal" id="orderTotal" oninput="orderTotal()">$</span><br>
</div>

Here's how you do it:
function orderTotal() {
const qty1 = document.getElementById('qty1').value;
const qty2 = document.getElementById('qty2').value;
const total = parseInt(qty1) + parseInt(qty2);
document.getElementById('orderTotal').innerHTML = '$ ' + total;
}
Remove the oninput="orderTotal()" in your span element and trigger the above function using a button click e.g. <button onClick="orderTotal()">Calculate Total</button> or maybe when either of your two inputs' value changes. Also consider using const and let instead of var.
https://www.freecodecamp.org/news/var-let-and-const-whats-the-difference/

Instead of querying the DOM in Ray's answer--as DOM queries should generally be avoided since they are slow W3 Wiki, you could also consider using a shared variable between the two functions.
Also, consider using something else in place of innerHTML, mostly because of efficiency why-is-element-innerhtml-bad-code.
var total1;
var total2;
function myCalculator() {
var qty1 = document.getElementById('qty1').value;
total1 = qty1 * 19.99
document.getElementById('subTotalOne').textContent = '$ ' + total1;
}
function myCalculatorTwo() {
var qty2 = document.getElementById('qty2').value;
total2 = qty2 * 37.99;
document.getElementById('subTotalTwo').textContent = '$ ' + total2;
}
function orderTotal() {
document.getElementById('orderTotal').innerHTML = '$ ' + (total1 + total2);
//parentheses because '$' isn't a number so the numbers total1 and total2 will be treated like strings and joined together
}

Related

JavaScript array application

I'm trying to create a sample accounting system, the checkbox can be add to the total after it's checked and the input text is the amount of the money.
but my result keep getting zero, I can't figure it out.
Anyone can help me handle this problem?
I've test that the length of total_ary is 0, I think that is the mainly problem
function Totalamount() {
var input_cb = document.getElementsByName('cb');
var amount = [];
var total_ary = [];
var total = 0;
var price = [10, 20, 30];
var i = 0;
for (i = 0; i < input_cb.length; i++) {
if (input_cb[i].checked) {
amount.push(document.getElementsByName("amount").value); //get amounts of the products
} else {
amount.push(0); //If there is no input, add 0 to the array
}
}
for (i = 0; i < total_ary.length; i++) {
total_ary.push(parseInt(amount[i] * price[i])); // Add the products' total price to array
total += parseInt(total_ary[i]); //Counting the total money
}
document.getElementById("result").innerHTML = "$" + 0;
document.getElementById("result").innerHTML = "$" + total ;
}
<fieldset>
<input type="checkbox" name="cb" checked>$10:<input type="text" name="amount"><br>
<input type="checkbox" name="cb" checked>$20:<input type="text" name="amount"><br>
<input type="checkbox" name="cb" checked>$30:<input type="text" name="amount"><br>
</fieldset>
<button onclick="Totalamount()">Count</button>
<p>Total = <span id="result">
You do
document.getElementsByName("amount").value
but getElementsByName returns a collection, not an element.
You do
var total_ary = [];
// ... code that doesn't reference total_ary
for (i = 0; i < total_ary.length; i++) {
total_ary.push(parseInt(amount[i] * price[i])); // Add the products' total price to array
total += parseInt(total_ary[i]); //Counting the total money
}
But since the code in between doesn't reference total_ary, the total ends up being 0.
From a selected checkbox, you need to navigate to the associated input:
document.getElementsByName("amount")[i].value
since i is the cb index you're iterating over, the same i in the amount collection will refer to the input you need.
Or, more elegantly, just navigate to the next element in the DOM when a checkbox is checked, and take the number for each product's price from the DOM too. You can also select only the checked checkboxes immediately with a :checked selector, and attach the event listener using addEventListener (instead of an inline handler; inline handlers should be avoided)
document.querySelector('button').addEventListener('click', () => {
let total = 0;
for (const input of document.querySelectorAll('[name=cb]:checked')) {
const price = input.nextSibling.textContent.match(/\d+/)[0];
const amount = input.nextElementSibling.value;
total += price * amount;
}
document.getElementById("result").innerHTML = total + "元";
});
<fieldset>
<input type="checkbox" name="cb" checked>$10:<input><br>
<input type="checkbox" name="cb" checked>$20:<input><br>
<input type="checkbox" name="cb" checked>$30:<input><br>
</fieldset>
<button>Count</button>
<p>Total = <span id="result">
document.getElementsByName() returns a collection of elements. so calling value property will not work there as it does not have such property.
You can hold input elements with amount_inputs variable and iterate over it (in the example below by using spread syntax and Array.reduce())
And with Array.reduce() you can calculate the sum of the prices. There is no need for var amount = [] and var total_ary = [] variables.
Hope this helps
function Totalamount() {
var input_cb = document.getElementsByName('cb');
var amount_inputs = document.getElementsByName("amount")
var total = 0;
var price = [10, 20, 30];
total = [...input_cb].reduce((total, cb, i) => {
if(cb.checked){
total += (parseInt(amount_inputs[i].value) || 0) * price[i]
// ^^^^^^^^^ This is to avoid NaN multiplication
}
return total
},0);
document.getElementById("result").innerHTML = "$" + 0;
document.getElementById("result").innerHTML = total + "元";
}
<fieldset>
<input type="checkbox" name="cb" checked>$10:<input type="text" name="amount"><br>
<input type="checkbox" name="cb" checked>$20:<input type="text" name="amount"><br>
<input type="checkbox" name="cb" checked>$30:<input type="text" name="amount"><br>
</fieldset>
<button onclick="Totalamount()">Count</button>
<p>Total = <span id="result">
Use Index while retrieving the element from document.getElementsByName("amount");
Use for loop on amount array not on total_ary
function Totalamount() {
var input_cb = document.getElementsByName('cb');
var amount = [];
var total_ary = [];
var total = 0;
var price = [10, 20, 30];
var i = 0;
for (i = 0; i < input_cb.length; i++) {
if (input_cb[i].checked) {
amount.push(document.getElementsByName("amount")[i].value); //get amounts of the products
} else {
amount.push(0); //If there is no input, add 0 to the array
}
}
for (i = 0; i < amount.length; i++) {
total_ary.push(parseInt(amount[i] * price[i])); // Add the products' total price to array
total += isNaN(parseInt(total_ary[i])) ? 0 : parseInt(total_ary[i]); //Counting the total money
}
document.getElementById("result").innerHTML = "$" + 0;
document.getElementById("result").innerHTML = "$" + total ;
}
<fieldset>
<input type="checkbox" name="cb" checked>$10:<input type="text" name="amount"><br>
<input type="checkbox" name="cb" checked>$20:<input type="text" name="amount"><br>
<input type="checkbox" name="cb" checked>$30:<input type="text" name="amount"><br>
</fieldset>
<button onclick="Totalamount()">Count</button>
<p>Total = <span id="result">
You have made a few mistakes:
(1) If you want to keep all the checkboxes checked at initial stage
use checked="true" in place of checked
(2) getElementsByName("amount") returns an array, so you should use the index as well
(3) total_ary length is 0 initially.. therefore, you should run the loop with input_cb. (Here, you can do both the task with a single loop: refer code below)
Refer the code with corrections:
<!DOCTYPE html>
<html>
<head>Order sys
<script>
function Totalamount() {
var input_cb = document.getElementsByName('cb');
var amount = [];
var total = 0;
var price = [10,20,30];
var i=0;
for (i = 0; i < input_cb.length; i++) {
if (input_cb[i].checked){
amount.push(parseInt(document.getElementsByName("amount")[i].value)); //get amounts of the products
}
else{
amount.push(0); //If there is no input, add 0 to the array
}
total += parseInt(amount[i] * price[i]) //Counting the total money
}
document.getElementById("result").innerHTML = "$" + 0;
document.getElementById("result").innerHTML = total + "元";
}
</script>
</head>
<body>
<fieldset>
<input type = "checkbox" name="cb" checked="true">$10:<input type="text" id="amount_milk" name="amount" ><br>
<input type = "checkbox" name="cb" checked="true">$20:<input type="text" id="amount_soymlik" name="amount"><br>
<input type = "checkbox" name="cb" checked="true">$30:<input type="text" id="amount_blacktea" name="amount" ><br>
</fieldset>
<button onclick="Totalamount()">Count</button>
<p>Total = <span id="result">
</body>
</html>
You can refactor your code:
Fist use inputs of type number <input type="number" name="amount"> to accept only numbers from your end users
Then, you can work with indexed arrays like [...document.querySelectorAll('input[name="cb"]')] and loop only one time with Array.prototype.reduce() to get the total
Code example:
function Totalamount() {
const inputNumberArr = [...document.querySelectorAll('input[name="cb"]')]
const inputAmountArr = [...document.querySelectorAll('input[name="amount"]')]
const priceArr = [10, 20, 30]
const total = inputNumberArr.reduce((a, c, i) => {
const num = c.checked ? +inputAmountArr[i].value : 0
return a + num * priceArr[i]
}, 0)
document.getElementById('result').innerHTML = '$' + 0
document.getElementById('result').innerHTML = '$' + total
}
<fieldset>
<input type="checkbox" name="cb" checked> $10:
<input type="number" name="amount"><br>
<input type="checkbox" name="cb" checked> $20:
<input type="number" name="amount"><br>
<input type="checkbox" name="cb" checked> $30:
<input type="number" name="amount"><br>
</fieldset>
<button onclick="Totalamount()">Count</button>
<p>Total = <span id="result">
Is this what you are looking for?
Errors that I identified.
Making use of document.getElementsByName("amount").value instead of making the respective amount field you were making use of the global selector.
Trying to loop total_ary array instead of amount array.
function Totalamount() {
var input_cb = document.getElementsByName('cb');
var amountInput = document.getElementsByName('amount');
var amount = [];
var total_ary = [];
var total = 0;
var price = [10,20,30];
var i=0;
for (i = 0; i < input_cb.length; i++) {
if (input_cb[i].checked && amountInput[i].value){
amount.push(parseInt(amountInput[i].value)); //get amounts of the products
}
else{
amount.push(0); //If there is no input, add 0 to the array
}
}
for (i = 0; i < amount.length; i++) {
total_ary.push(parseInt(amount[i] * price[i])); // Add the products' total price to array
total += parseInt(total_ary[i]); //Counting the total money
}
document.getElementById("result").innerHTML = "$" + 0;
document.getElementById("result").innerHTML = total + "元";
}
<fieldset>
<input type = "checkbox" name="cb" checked>$10
<input type="text" id="amount_milk" name="amount" ><br>
<input type = "checkbox" name="cb" checked>$20
<input type="text" id="amount_soymlik" name="amount"><br>
<input type = "checkbox" name="cb" checked>$30
<input type="text" id="amount_blacktea" name="amount" ><br>
</fieldset>
<button onclick="Totalamount()">Count</button>
<p>Total = <span id="result">

How to use HTML form input as JS variable

I am trying to make some element change color according to the number received from the form.
e.g. When a player types 2 in the form, there should be 2 colors from the 'color' array.
function intro() {
var num = document.getElementById('quant').value;
var color = ["#FCB711", "#F37021", "#CC004C", "#6460AA", "#0080D0", "#0DB14B"];
var i;
for (i = 1; i <= 42; i++) {
document.getElementById('s' + i).style.backgroundColor = color[getRndInteger(0, num)];
}
}
<form id="tion" action="" method="get">
Player (between 2 and 6):
<input type="number" name="quant" min="2" max="6">
<input type="button" value="start" onclick="intro()">
</form>
Because you're not showing relevant parts of your code, I've simplified it. Your input was missing an id="quant" which I added.
function colorTheDiv() {
const color = ["#FCB711", "#F37021", "#CC004C", "#6460AA", "#0080D0", "#0DB14B"];
document.getElementById('colorMe').style.backgroundColor = color[parseInt(document.getElementById('quant').value)];
}
<label for="quant">Player (between 2 and 6)</label>
<input type="number" name="quant" id="quant" min="2" max="6" onchange="colorTheDiv()">
<hr />
<div id="colorMe">colorMe</div>
I've also replaced the button and execute the colorTheDiv function whenever the value of quant changes (if you prefer you can still keep the button instead).
Start by giving your field an ID of quant.
You are missing 42 elements too
I assume you ACTUALLY meant this
var color = ["#FCB711", "#F37021", "#CC004C", "#6460AA", "#0080D0", "#0DB14B"];
function getRndInteger(max) {
return Math.floor(Math.random() * max);
}
function intro() {
var container = document.getElementById("container");
container.innerHTML = "";
var num = document.getElementById('quant').value;
if (!num) {
alert("Give a value")
return;
}
for (var i = 1; i <= 42; i++) {
var col = color[getRndInteger(num)];
var span = document.createElement("span");
span.innerText = i;
span.id = "s" + i;
span.style.backgroundColor = col;
container.appendChild(span)
}
}
<form id="tion" action="" method="get">
Player (between 2 and 6):
<input type="number" id="quant" min="2" max="6">
<input type="button" value="start" onclick="intro()">
</form>
<div id="container"></div>
you need to give your field an ID of "quant" not just a name, otherwise getElementByID will return null

JavaScript: Get number of edited/updated inputs

Scenario
Every semester my students need to take at least one science, one physics and one history test. The following form gives the right average grades as well as the final grade of a student:
document.getElementById('calcBtn').addEventListener('click', function() {
var scienceTest1 = document.getElementById('scienceTest1').value;
var scienceTest2 = document.getElementById('scienceTest2').value;
var scienceTest3 = document.getElementById('scienceTest3').value;
var physicsTest1 = document.getElementById('physicsTest1').value;
var physicsTest2 = document.getElementById('physicsTest2').value;
var physicsTest3 = document.getElementById('physicsTest3').value;
var historyTest1 = document.getElementById('historyTest1').value;
var historyTest2 = document.getElementById('historyTest2').value;
var historyTest3 = document.getElementById('historyTest3').value;
var scienceAverage = document.getElementById('scienceAverage');
var physicsAverage = document.getElementById('physicsAverage');
var historyAverage = document.getElementById('historyAverage');
var finalGrade = document.getElementById('finalGrade');
scienceAverage.value = (Number(scienceTest1) + Number(scienceTest2) + Number(scienceTest3)) / 3;
physicsAverage.value = (Number(physicsTest1) + Number(physicsTest2) + Number(physicsTest3)) / 3;
historyAverage.value = (Number(historyTest1) + Number(historyTest2) + Number(historyTest3)) / 3;
finalGrade.value = (scienceAverage.value * 5 + physicsAverage.value * 3 + historyAverage.value * 2) / 10;
});
<form>
Science: <input type="number" id="scienceTest1">
<input type="number" id="scienceTest2">
<input type="number" id="scienceTest3">
<output id="scienceAverage"></output>
<br> Physics: <input type="number" id="physicsTest1">
<input type="number" id="physicsTest2">
<input type="number" id="physicsTest3">
<output id="physicsAverage"></output>
<br> History: <input type="number" id="historyTest1">
<input type="number" id="historyTest2">
<input type="number" id="historyTest3">
<output id="historyAverage"></output>
<br>
<input type="button" value="Calculate" id="calcBtn">
<output id="finalGrade"></output>
</form>
The problem is it only works if all the fields are edited. If the student doesn't take some tests, the average grades won't show the correct values. I know it's because of dividing by the fixed number 3 when it calculates the average grades:
scienceAverage.value = (Number(scienceTest1) + Number(scienceTest2) + Number(scienceTest3)) / 3;
physicsAverage.value = (Number(physicsTest1) + Number(physicsTest2) + Number(physicsTest3)) / 3;
historyAverage.value = (Number(historyTest1) + Number(historyTest2) + Number(historyTest3)) / 3;
Question
What is a simple approach to get the number of changed input fields in the following single row? I'll try to understand your method and then develop my form to multiple rows.
document.getElementById('calcBtn').addEventListener('click', function() {
var test1 = document.getElementById('test1').value;
var test2 = document.getElementById('test2').value;
var test3 = document.getElementById('test3').value;
var average = document.getElementById('average');
average.value = (Number(test1) + Number(test2) + Number(test3)) / 3;
});
<form>
<input type="number" id="test1">
<input type="number" id="test2">
<input type="number" id="test3">
<output id="average"></output>
<br>
<input type="button" value="Calculate" id="calcBtn">
</form>
It looks like you need to check the values of inputs are valid numbers before using them in the arithmetic that calculates the per-course averages. One way to do this would be via the following check:
if (!Number.isNaN(Number.parseFloat(input.value))) {
/* Use input.value in average calculation */
}
You might also consider adjusting your script and HTML as shown below, which would allow you to generalize and re-use the average calculation for each of the three classes as detailed below:
document.getElementById('calcBtn').addEventListener('click', function() {
/* Generalise the calculation of updates for specified course type */
const calculateForCourse = (cls) => {
let total = 0
let count = 0
/* Select inputs with supplied cls selector and iterate each element */
for (const input of document.querySelectorAll(`input.${cls}`)) {
if (!Number.isNaN(Number.parseFloat(input.value))) {
/* If input value is non-empty, increment total and count for
subsequent average calculation */
total += Number.parseFloat(input.value);
count += 1;
}
}
/* Cacluate average and return result */
return { count, average : count > 0 ? (total / count) : 0 }
}
/* Calculate averages using shared function for each class type */
const calcsScience = calculateForCourse('science')
const calcsPhysics = calculateForCourse('physics')
const calcsHistory = calculateForCourse('history')
/* Update course averages */
document.querySelector('output.science').value = calcsScience.average
document.querySelector('output.physics').value = calcsPhysics.average
document.querySelector('output.history').value = calcsHistory.average
/* Update course counts */
document.querySelector('span.science').innerText = `changed:${calcsScience.count}`
document.querySelector('span.physics').innerText = `changed:${calcsPhysics.count}`
document.querySelector('span.history').innerText = `changed:${calcsHistory.count}`
/* Update final grade */
var finalGrade = document.getElementById('finalGrade');
finalGrade.value = (calcsScience.average * 5 + calcsPhysics.average * 3 + calcsHistory.average * 2) / 10;
});
<!-- Add class to each of the course types to allow script to distinguish
between related input and output fields -->
<form>
Science:
<input type="number" class="science" id="scienceTest1">
<input type="number" class="science" id="scienceTest2">
<input type="number" class="science" id="scienceTest3">
<output id="scienceAverage" class="science"></output>
<span class="science"></span>
<br> Physics:
<input type="number" class="physics" id="physicsTest1">
<input type="number" class="physics" id="physicsTest2">
<input type="number" class="physics" id="physicsTest3">
<output id="physicsAverage" class="physics"></output>
<span class="physics"></span>
<br> History:
<input type="number" class="history" id="historyTest1">
<input type="number" class="history" id="historyTest2">
<input type="number" class="history" id="historyTest3">
<output id="historyAverage" class="history"></output>
<span class="history"></span>
<br>
<input type="button" value="Calculate" id="calcBtn">
<output id="finalGrade"></output>
</form>
Update
To extend on the first answer, please see the documentation in the snippet below responding to your question's update:
document.getElementById('calcBtn').addEventListener('click', function() {
var test1 = document.getElementById('test1').value;
var test2 = document.getElementById('test2').value;
var test3 = document.getElementById('test3').value;
var average = document.getElementById('average');
/* This variable counts the number of inputs that have changed */
var changesDetected = 0;
/* If value of test1 field "not equals" the empty string, then
we consider this a "changed" field, so we'll increment our
counter variable accordinly */
if(test1 != '') {
changesDetected = changesDetected + 1;
}
/* Apply the same increment as above for test2 field */
if(test2 != '') {
changesDetected = changesDetected + 1;
}
/* Apply the same increment as above for test3 field */
if(test3 != '') {
changesDetected = changesDetected + 1;
}
/* Calculate average from changesDetected counter.
We need to account for the case where no changes
have been detected to prevent a "divide by zero" */
if(changesDetected != 0) {
average.value = (Number(test1) + Number(test2) + Number(test3)) / changesDetected;
}
else {
average.value = 'Cannot calculate average'
}
/* Show a dialog to box to display the number of fields changed */
alert("Detected that " + changesDetected + " inputs have been changed")
});
<form>
<input type="number" id="test1">
<input type="number" id="test2">
<input type="number" id="test3">
<output id="average"></output>
<br>
<input type="button" value="Calculate" id="calcBtn">
</form>
Update 2
The prior Update can be simplified with a loop like so:
document.getElementById('calcBtn').addEventListener('click', function() {
let changesDetected = 0;
let total = 0;
const ids = ['test1', 'test2', 'test3'];
for(const id of ids) {
const value = document.getElementById(id).value;
if(value != '') {
changesDetected += 1;
total += Number(value);
}
}
var average = document.getElementById('average');
if(changesDetected != 0) {
average.value = total / changesDetected;
}
else {
average.value = 'Cannot calculate average'
}
alert("Detected that " + changesDetected + " inputs have been changed")
});
<form>
<input type="number" id="test1">
<input type="number" id="test2">
<input type="number" id="test3">
<output id="average"></output>
<br>
<input type="button" value="Calculate" id="calcBtn">
</form>
Update 3
Another concise approach based on your JSFiddle would be the following:
document.getElementById('calculator').addEventListener('click', function() {
var physicsAverage = document.getElementById('physicsAverage'),
historyAverage = document.getElementById('historyAverage');
physicsAverage.value = calculateAverageById('physics')
historyAverage.value = calculateAverageById('history');
});
function calculateAverageById(id) {
/* Get all input descendants of element with id */
const inputs = document.querySelectorAll(`#${id} input`);
/* Get all valid grade values from selected input elements */
const grades = Array.from(inputs)
.map(input => Number.parseFloat(input.value))
.filter(value => !Number.isNaN(value));
/* Return average of all grades, or fallback message if no valid grades present */
return grades.length ? (grades.reduce((sum, grade) => (sum + grade), 0) / grades.length) : 'No assessment made!'
}
<form>
<p id="physics">
Physics:
<input type="number">
<input type="number">
<input type="number">
<output id="physicsAverage"></output>
</p>
<p id="history">
History:
<input type="number">
<input type="number">
<input type="number">
<output id="historyAverage"></output>
</p>
<button type="button" id="calculator">Calculate</button>
</form>
The main differences here are:
the use of document.querySelectorAll(#${id} input); with a template literal to extract the input elements of a element with id
the use of Array.from(inputs) for a more readable means of converting the result of the query to an array
the use of Number.parseFloat and Number.isNaN when transforming and filtering input elements to valid numeric values for the subsequent average calculation
Hope that helps!
A good start is to change your ID to Class to put your inputs into logical groups. The next step is to get the inputs from a particular group that has a value that is not null. We can do this by selecting for example .scienceTest and then filtering out empty string items.
I added a helper function values to extract the values from a nodelist and put them into a normal Array.
We can use a Boolean to test the empty strings. We also cast all strings to numbers using Number. This is done in the onlyNumbers function.
Next, we need to calculate the averages of each group. This is easy since we have a filtered list of numbers. All we do is calculate the sum and divide by the Array length. This is done with our little avrg function.
document.getElementById('calcBtn').addEventListener('click', function() {
var scienceTest = getGrades('.scienceTest')
var physicsTest = getGrades('.physicsTest')
var historyTest = getGrades('.historyTest')
var scienceAverage = document.getElementById('scienceAverage');
var physicsAverage = document.getElementById('physicsAverage');
var historyAverage = document.getElementById('historyAverage');
var finalGrade = document.getElementById('finalGrade');
scienceAverage.value = avrg(scienceTest)
physicsAverage.value = avrg(physicsTest)
historyAverage.value = avrg(historyTest)
finalGrade.value = (scienceAverage.value * 5 + physicsAverage.value * 3 + historyAverage.value * 2) / 10;
});
function avrg(list) {
return list.length ? list.reduce((acc, i) => acc + i, 0) / list.length : 0
}
function getGrades(selector) {
return onlyNumbers(values(document.querySelectorAll(selector)))
}
function onlyNumbers(list) {
return list.filter(Boolean).map(Number)
}
function values(nodelist) {
return Array.prototype.map.call(nodelist, (node) => node.value)
}
<form>
Science: <input type="number" class="scienceTest">
<input type="number" class="scienceTest">
<input type="number" class="scienceTest">
<output id="scienceAverage"></output>
<br> Physics: <input type="number" class="physicsTest">
<input type="number" class="physicsTest">
<input type="number" class="physicsTest">
<output id="physicsAverage"></output>
<br> History: <input type="number" class="historyTest">
<input type="number" class="historyTest">
<input type="number" class="historyTest">
<output id="historyAverage"></output>
<br>
<input type="button" value="Calculate" id="calcBtn">
<output id="finalGrade"></output>
</form>
Update: Simplified example
document.getElementById('calcBtn').addEventListener('click', function() {
var test1 = document.getElementById('test1').value;
var test2 = document.getElementById('test2').value;
var test3 = document.getElementById('test3').value;
var average = document.getElementById('average');
// Put all field values in array, Filter empty values out, cast values to Number
var rowValues = [test1, test2, test3].filter(Boolean).map(Number)
console.log('Number of changed fields', rowValues.length)
// calculate average by reducing the array to the sum of its remaining values then divide by array length
average.value = rowValues.reduce((sum, grade) => sum + grade, 0) / rowValues.length;
});
<form>
<input type="number" id="test1">
<input type="number" id="test2">
<input type="number" id="test3">
<output id="average"></output>
<br>
<input type="button" value="Calculate" id="calcBtn">
</form>
Update Extra: Based on OP's jsfiddle example in the comments
document.getElementById('calculator').addEventListener('click', function() {
var physicsAverage = document.getElementById('physicsAverage'),
historyAverage = document.getElementById('historyAverage');
physicsAverage.value = calculateAverageById('physics')
historyAverage.value = calculateAverageById('history');
});
function calculateAverageById(id) {
// Get all inputs under Id
var inputs = document.getElementById(id).getElementsByTagName('input')
var values =
Array.prototype.slice.call(inputs) // From HTMLCollection to Array
.map(e => e.value.trim()) // Return all .value from input elements
.filter(Boolean) // Filter out any empty strings ""
.map(Number) // convert remaining values to Numbers
return (values.length) ? // if length is greater then 0
values.reduce((sum, grade) => sum + grade, 0) / values.length // Return average
:
'No assessment made!' // else return this message
}
<form>
<p id="physics">
Physics:
<input type="number">
<input type="number">
<input type="number">
<output id="physicsAverage"></output>
</p>
<p id="history">
History:
<input type="number">
<input type="number">
<input type="number">
<output id="historyAverage"></output>
</p>
<button type="button" id="calculator">Calculate</button>
</form>
Instead of dividing it by 3 all the time, you can calculate this number dynamically based on number of input fields updated by the student in a row.
Here is the working code:
function getValueAndTotal(element){
var valueChanged = (element.defaultValue === element.value || element.value === "") ? 0 : 1;
return { value: Number(element.value), total: valueChanged };
}
document.getElementById('calcBtn').addEventListener('click', function() {
var scienceTest1 = getValueAndTotal(document.getElementById('scienceTest1'));
var scienceTest2 = getValueAndTotal(document.getElementById('scienceTest2'));
var scienceTest3 = getValueAndTotal(document.getElementById('scienceTest3'));
var physicsTest1 = getValueAndTotal(document.getElementById('physicsTest1'));
var physicsTest2 = getValueAndTotal(document.getElementById('physicsTest2'));
var physicsTest3 = getValueAndTotal(document.getElementById('physicsTest3'));
var historyTest1 = getValueAndTotal(document.getElementById('historyTest1'));
var historyTest2 = getValueAndTotal(document.getElementById('historyTest2'));
var historyTest3 = getValueAndTotal(document.getElementById('historyTest3'));
var scienceAverage = document.getElementById('scienceAverage');
var physicsAverage = document.getElementById('physicsAverage');
var historyAverage = document.getElementById('historyAverage');
var finalGrade = document.getElementById('finalGrade');
var scienceTotalTests = scienceTest1.total + scienceTest2.total + scienceTest3.total;
var physicsTotalTests = physicsTest1.total + physicsTest2.total + physicsTest3.total;
var historyTotalTests = historyTest1.total + historyTest2.total + historyTest3.total;
scienceAverage.value = (scienceTotalTests === 0 ? 0 : (scienceTest1.value + scienceTest2.value + scienceTest3.value) / scienceTotalTests);
physicsAverage.value = (physicsTotalTests === 0 ? 0 : (physicsTest1.value + physicsTest3.value + physicsTest3.value) / physicsTotalTests);
historyAverage.value = (historyTotalTests === 0 ? 0 : (historyTest1.value + historyTest2.value + historyTest3.value) / historyTotalTests);
finalGrade.value = (scienceAverage.value * 5 + physicsAverage.value * 3 + historyAverage.value * 2) / 10;
});
<form>
Science:
<input type="number" id="scienceTest1" class="scienceTest">
<input type="number" id="scienceTest2" class="scienceTest">
<input type="number" id="scienceTest3" class="scienceTest">
<output id="scienceAverage"></output>
<br>Physics:
<input type="number" id="physicsTest1">
<input type="number" id="physicsTest2">
<input type="number" id="physicsTest3">
<output id="physicsAverage"></output>
<br>History:
<input type="number" id="historyTest1">
<input type="number" id="historyTest2">
<input type="number" id="historyTest3">
<output id="historyAverage"></output>
<br>
<input type="button" value="Calculate" id="calcBtn">
<output id="finalGrade"></output>
</form>
It's a bit ugly but you can consider test scores as booleans: if there's any test score that's worth a 1, otherwise a 0.
Since input.value is of type string, converting it to a boolean would give as a result false when the input is empty ("") or true when there's any number in it.
Using OP's smaller snippet:
document.getElementById('calcBtn').addEventListener('click', function() {
var test1 = document.getElementById('test1').value;
var test2 = document.getElementById('test2').value;
var test3 = document.getElementById('test3').value;
var testCount = Boolean(test1) + Boolean(test2) + Boolean(test3);
// alternatively: var testCount = !!test1 + !!test2 + !!test3
var average = document.getElementById('average');
average.value = (Number(test1) + Number(test2) + Number(test3)) / testCount;
});
<form>
<input type="number" id="test1">
<input type="number" id="test2">
<input type="number" id="test3">
<output id="average"></output>
<br>
<input type="button" value="Calculate" id="calcBtn">
</form>
There are two major choke points in your code that you need to be aware of:
You are calculating the average of each subject regardless of their value. Technically, you only want to take into account the subject if it has a defined value. In this case, 0 will be counted, but an empty field will not (since a student can technically score a 0 on his/her test)
You are calculating the weighted average regardless of their value, too (see same logic as above).
Instead of trying to fix the code you have, I have actually refactored the logic so that all the calculations are abstracted into functions, based on the DRY (do not repeat yourself) principle. The functions are:
calculateSubjectAverage, which calculates the correct average of a given subject. It will take 0 into account, but ignore empty fields
setSubjectAverage, which will set the appropriate <output> element
Finally, instead of have to manually calculate the weighted average, you can easily store all that metadata in an array of objects, e.g.:
var subjects = [{
name: 'science',
weight: 5
}, {
name: 'physics',
weight: 3
}, {
name: 'history',
weight: 2
}];
This allows us to filter the subjects and calculate their correct weighted sum, and hence weighted average. Filtering is needed because there is a chance that an all-empty subject score will return undefined.
See proof-of-concept below:
function calculateSubjectAverage(className) {
var inputs = document.querySelectorAll('.' + className);
var scores = Array.prototype.map.call(inputs, function(input) {
if (input.value === '')
return;
return +input.value;
});
var count = 0;
var scoreSum = scores.reduce(function(acc, score) {
if (isNaN(score))
return acc;
count++;
return acc + score;
}, 0);
return scoreSum / count;
};
function setSubjectAverage(className, averageScore) {
if (isNaN(averageScore))
return;
document.getElementById(className + 'Average').value = averageScore;
}
document.getElementById('calcBtn').addEventListener('click', function() {
var subjects = [{
name: 'science',
weight: 5
}, {
name: 'physics',
weight: 3
}, {
name: 'history',
weight: 2
}];
var totalWeight = 0;
// Go through each subject and calculate & set average score
// Since we are iterating anyway, might want to calculate totalWeight, too
subjects.forEach(function(subject) {
var averageScore = calculateSubjectAverage(subject.name);
setSubjectAverage(subject.name, averageScore);
// Set average score to object
subject.average = averageScore;
if (!isNaN(averageScore))
totalWeight += subject.weight;
});
// Only compute weighted average from subject with valid averages
var weightedTotal = subjects.reduce(function(acc, subject) {
if (isNaN(subject.average))
return acc;
return acc + subject.average * subject.weight;
}, 0);
var weightedAverage = weightedTotal / totalWeight;
if (!isNaN(weightedTotal / totalWeight))
document.getElementById('finalGrade').value = weightedTotal / totalWeight;
});
<form>
Science: <input type="number" class="science">
<input type="number" class="science">
<input type="number" class="science">
<output id="scienceAverage"></output>
<br> Physics: <input type="number" class="physics">
<input type="number" class="physics">
<input type="number" class="physics">
<output id="physicsAverage"></output>
<br> History: <input type="number" class="history">
<input type="number" class="history">
<input type="number" class="history">
<output id="historyAverage"></output>
<br>
<input type="button" value="Calculate" id="calcBtn">
<output id="finalGrade"></output>
</form>
var tests = [
document.getElementById('test1').value || false,
document.getElementById('test2').value || false,
document.getElementById('test3').value || false
];
var average = 0,
length = 0;
for (var i = 0; i < tests.length; i++) {
if (tests[i] !== false) {
average += Number( tests[i] );
length ++;
}
}
average = average / length;
This is ES5 solution. You can do shorter but this in my opinion is intuitive.
You are dividing the value by 3 that's why its giving less result than expected.
Make the `html of the of your code dynamically.
Don't make too much variables(scienceTest1,scienceTest2.....) instead use loops are store the values in array
Writing like this Number(scienceTest1) + Number(scienceTest2) + Number(scienceTest3)) / 3 is bad because you can have more test and there are more chances of type error. Instead store the values in the array and at end use Array.prototype.reduce() to add them.
For the values array you need to check if the value !=='' before pushing it into array so it will get correct average.
The code is completely dynamic you can have any subjects and any
no of tests
//This is list of subjects. You can change it will work same
let subjects = ['science','physics','history'];
let noOfTests = 3;
//add <form> element to body
document.body.innerHTML = '<form></form>'
//getting that form as an element.
let form = document.querySelector('form')
//Creating the HTML dymamically
subjects.forEach(sub =>{
//setting the title of the subject
form.innerHTML += sub + ':' + '<br>';
for(let i = 0;i<noOfTests;i++){
//generating input feilds equal of 'noOfTests' for each subject
form.innerHTML += `<input type="number" id="${sub}Test${i+1}" /><br>`
}
//adding the output element to after addign all inputs.
form.innerHTML += `<output id="${sub}Average"></output><br>`
})
//Adding calculate button and finalOuput element.
form.innerHTML += `<br><input type="button" value="Calculate" id="calcBtn">
<output id="finalGrade"></output>`
document.getElementById('calcBtn').addEventListener('click', function() {
//'total' is array which will contain average of each subject
let total = [];
//looping thorugh each subject in 'subjects' array.
subjects.forEach(sub => {
//'vals' will store the values currect subject we are looping
let vals = []
for(let i = 0;i<noOfTests;i++){
//getting the value of each input feild of current subject
let val = document.getElementById(`${sub}Test${i+1}`).value;
//check if input have a value so we push it into the vals array.
if(val !== '') vals.push(val);
}
//getting average of all values using reduce
let result = vals.reduce((ac,a) => ac + Number(a),0)/vals.length;
//adding result(average) to the output of current subject.
document.getElementById(`${sub}Average`).innerHTML = result
//adding the average of current subject of the 'total' array.
total.push(result);
})
//At last find the average of total averages and add it to 'finalGrade'
total = total.filter(x => !isNaN(x));
document.getElementById('finalGrade').innerHTML = total.reduce((ac,a) => ac + a,0)/total.length;
});
input{
border-radius:5px;
padding:3px;
margin:5px;
font-size:20px;
}
form{
font-size:20px;
font-family:sans-serif;
text-transform:capitalize;
}
Introduce a counter to the function. After every click check if the input values is '' or not as it will be string before casting. If it is do nothing as the ternary operation will return false
test1!=''?num++:false;
If test1=='' then false else increment the counter. Before calculating the average check if the counter is 0, if it is set the counter to one. If it is zero the result of division by zero will be infinity and we will get NaN as the output, and counter set to 1 will give 0.
document.getElementById('calcBtn').addEventListener('click', function() {
let num=0;
var test1 = document.getElementById('test1').value;
test1!=''?num++:false;
var test2 = document.getElementById('test2').value;
test2!=''?num++:false;
var test3 = document.getElementById('test3').value;
test3!=''?num++:false;
var average = document.getElementById('average');
num==0?num++:false;
average.value = (Number(test1) + Number(test2) + Number(test3)) / num;
});
<form>
<input type="number" id="test1">
<input type="number" id="test2">
<input type="number" id="test3">
<output id="average"></output>
<br>
<input type="button" value="Calculate" id="calcBtn">
</form>
There are some solutions already. Here is mine.
There is something to optimize in your code and I think it is a good idea to create the HTML dynamically based on a simple configuration array like
const subjects = [{
name: 'science',
numberOfTests: 3
}, {
name: 'physics',
numberOfTests: 2
}, {
name: 'history',
numberOfTests: 3
}];
so if the subjects or the number of tests change, you don't need to change the code but only this configuration.
I tried to comment my code example to make understandable, what is done there. But that is not the important part. More important is
that you need to understand what's going on in the calculation part to be sure, the calculation is right. So I start with that part.
If you add name attributes to your test result inputs and give each input of the same subject the same name, you can easily retrieve a NodeList of that inputs, check the values for each Node and calculate based on it. So you know the values and the number of tests the student took for each subject.
Let's have a look
/* EventListener for the calculate button */
btn.addEventListener('click', function(e) {
e.preventDefault(); // don't submit the form
var totalAvSum = 0; // var for the total of all subject average totals
/* for all subjects in your configuration array */
subjects.forEach(function(subject) {
/* NodeList of all inputs with Name subject.name+'Test' */
let subjResInputs = document.getElementsByName(subject.name+'Test');
let testTotal = 0; // sum of test results
let testCnt = 0; // number of tests the student took
let tval; // value of input
/* for each input of the subject */
Array.prototype.map.call(subjResInputs, function(t){
tval = (t.value * 1); // make sure, value is treated as number
if (tval > 0) { // only if there is a value
testTotal += tval; // add test result
testCnt += 1; // increase test count
}
});
/* calculate average and show it in output */
totalAvSum += (testTotal/testCnt);
document.getElementById(subject.name+'Average').textContent = (testTotal/testCnt);
});
/* after calculating average per subject show total average */
document.getElementById('totalAverage').textContent = totalAvSum/subjects.length;
});
I use document.getElementsByName() here to get the NodeList of input elements for each subject. Then I use array.map() to walk through that list. To check, if an input has a value, I multiply the value by 1 (what gives a number) and calculate only, if the result is greater than 0.
The rest is the dynamical stuff for the HTML.
/* This is your configuration.
The form will be created based on that configuration.
So you don't need to change anything in the code if
subjects or number of tests change. */
const subjects = [{
name: 'science',
numberOfTests: 3
}, {
name: 'physics',
numberOfTests: 2
}, {
name: 'history',
numberOfTests: 3
}];
/* this functioncreates the form table */
function createFormTable() {
var tr, td, txt, outp, btn, frmTbl;
// frmTbl = document.getElementById('formTable');
/* create table */
frmTbl = document.createElement('table');
frmTbl.setAttribute('id', 'formTable'); // set id to 'formTable'
/* create table head */
tr = document.createElement('tr');
td = document.createElement('th');
txt = document.createTextNode('subject');
td.appendChild(txt);
tr.appendChild(td);
td = document.createElement('th');
txt = document.createTextNode('test results');
td.appendChild(txt);
tr.appendChild(td);
td = document.createElement('th');
txt = document.createTextNode('arith. mean');
td.appendChild(txt);
tr.appendChild(td);
/* add table head to table */
frmTbl.appendChild(tr);
/* create table row for each subject
the table row object is created in function createSubjectRow
and here added to the table */
subjects.forEach(function(subject) {
frmTbl.appendChild(createSubjectRow(subject)); // add tr to table
});
/* row with total average */
/* create tr element */
tr = document.createElement('tr');
td = document.createElement('th'); // td for text total
td.setAttribute('colspan', 2);
td.style.textAlign = 'right';
txt = document.createTextNode('total'); // textNode
td.appendChild(txt); // add textNode to td
tr.appendChild(td); // add td to tr
frmTbl.appendChild(tr); // add tr to table
td = document.createElement('td'); // td for total average output
outp = document.createElement('output'); // create output element
outp.setAttribute('id', 'totalAverage'); // set id
td.appendChild(outp); // add output to td
tr.appendChild(td); // add td to tr
frmTbl.appendChild(tr); // add tr to table
/* button */
btn = document.createElement('button');
btn.setAttribute('id', 'calcBtn');
txt = document.createTextNode('calculate');
btn.appendChild(txt);
// document.getElementById('gradesForm').appendChild(btn);
/* add button to last row in table */
tr = document.createElement('tr');
td = document.createElement('th'); // td for button
td.setAttribute('colspan', 3);
td.appendChild(btn); // add button to td
tr.appendChild(td); // add td to tr
frmTbl.appendChild(tr); // add tr to table
/* EventListener for the calculate button */
btn.addEventListener('click', function(e) {
e.preventDefault(); // don't submit the form
var totalAvSum = 0; // var for the total of all subject average totals
/* for all subjects in your configuration array */
subjects.forEach(function(subject) {
/* NodeList of all inputs with Name subject.name+'Test' */
let subjResInputs = document.getElementsByName(subject.name+'Test');
let testTotal = 0;
let testCnt = 0;
let tval;
/* for each input of the subject */
Array.prototype.map.call(subjResInputs, function(t){
tval = (t.value * 1); // make sure, value is treated as number
if (tval > 0) { // only if there is a value
testTotal += tval; // add test result
testCnt += 1; // increase test count
}
});
/* calculate average and show it in output */
totalAvSum += (testTotal/testCnt);
document.getElementById(subject.name+'Average').textContent = (testTotal/testCnt);
});
/* after calculating average per subject
show total average */
document.getElementById('totalAverage').textContent = totalAvSum/subjects.length;
});
return frmTbl;
}
function createSubjectRow(s) {
var tr, td, txt, inp, outp;
/* create tr element */
tr = document.createElement('tr');
/* create td elements for subject s */
td = document.createElement('td'); // td for subject name
txt = document.createTextNode(s.name); // textNode
td.appendChild(txt); // add textNode to td
tr.appendChild(td); // add td to tr
td = document.createElement('td'); // td for subject test results
for (var i = 0; i < s.numberOfTests; i += 1) {
inp = document.createElement('input'); // create input
inp.setAttribute('type', 'number'); // set input type
// inp.setAttribute('id', s.name + 'Test' + i); // set id
/* set name attribute of input to subject name + 'Test'
all test result inputs for the same subject will have the same name */
inp.setAttribute('name', s.name + 'Test');
inp.setAttribute('step', 0.1); // in case, you give grades like 3.5
inp.setAttribute('min', 1);
inp.setAttribute('max', 100);
td.appendChild(inp); // add input to td
}
tr.appendChild(td); // add td to tr
td = document.createElement('td'); // td for average output
outp = document.createElement('output'); // create output element
outp.setAttribute('id', s.name + 'Average'); // set id
td.appendChild(outp); // add output to td
tr.appendChild(td); // add td to tr
return tr; // return the resulting table row object
}
document.getElementById('gradesForm').appendChild(createFormTable());
#formTable td {
border: solid 1px #000;
padding: 6px;
border-spacing: 3px;
}
#formTable th {
border: none;
font-size:0.9em;
text-align: left;
}
input[type="number"] {
width: 4em;
border: solid 1px #999;
margin: 0 3px;
}
<form id="gradesForm">
</form>
Method
The issue you are having of dividing by the fixed number 3 can be solved by using the Array length property and make this value dynamic.
Step 1: Place all subjects into arrays and filter if the inputs have values.
Step 2: Get the average value by subject based.
Step 3: Calculate the final Score with weights.
There are two functions that are used multiple times.
isTruthy and average.
To slim down the average function I broke it up into sum and average
document.getElementById('calcBtn').addEventListener('click', function() {
// Helper Functions
function isTruthy (score) {
return !!score
}
function sum (scores) {
var total = 0;
for (var counter=0; counter<scores.length; counter++) {
total += (Number(scores[counter]) || 0);
}
return total
}
function average (scores) {
return (sum(scores) / scores.length) || 0
}
// Step 1
var scienceScores = [
document.getElementById('scienceTest1').value,
document.getElementById('scienceTest2').value,
document.getElementById('scienceTest3').value
].filter(isTruthy)
var physicsScores = [
document.getElementById('physicsTest1').value,
document.getElementById('physicsTest2').value,
document.getElementById('physicsTest3').value
].filter(isTruthy)
var historyScores = [
document.getElementById('historyTest1').value,
document.getElementById('historyTest2').value,
document.getElementById('historyTest3').value
].filter(isTruthy)
var scienceAverage = document.getElementById('scienceAverage');
var physicsAverage = document.getElementById('physicsAverage');
var historyAverage = document.getElementById('historyAverage');
var finalGrade = document.getElementById('finalGrade');
// Step 2
scienceAverage.value = average(scienceScores);
physicsAverage.value = average(physicsScores);
historyAverage.value = average(historyScores);
// Step 3
finalGrade.value = (scienceAverage.value * 5 + physicsAverage.value * 3 + historyAverage.value * 2) / 10;
});
<form>
Science: <input type="number" id="scienceTest1">
<input type="number" id="scienceTest2">
<input type="number" id="scienceTest3">
<output id="scienceAverage"></output>
<br> Physics: <input type="number" id="physicsTest1">
<input type="number" id="physicsTest2">
<input type="number" id="physicsTest3">
<output id="physicsAverage"></output>
<br> History: <input type="number" id="historyTest1">
<input type="number" id="historyTest2">
<input type="number" id="historyTest3">
<output id="historyAverage"></output>
<br>
<input type="button" value="Calculate" id="calcBtn">
<output id="finalGrade"></output>
</form>
You can create an object called tests = {} and keep your tests as an array inside it, to give an example I have written some code for only one subject below which you can modify later.
Also, I have used classes instead of id's to reduce the complexity of code and repetitiveness.
Update: Updated the below code to be more dynamic and flexible to multiple subjects.
document.getElementById('calcBtn').addEventListener('click', function() {
// create a tests object and have subject specific test inside it
var tests = {
"Science": [],
"Physics": [],
"History": []
};
//looping over all subjects
for (var key in tests) {
// this can be looped as well if you have multiple subjects
var test = document.getElementsByClassName(key);
for (i = 0; i < test.length; i++) {
if (test[i].value != null || test[i].value != "") {
tests[key].push(Number(test[i].value));
}
}
// populate average by using reduce function
document.getElementById(key).value = tests[key].reduce((prev, curr) => prev + curr) / tests[key].length;
}
});
<form>
<fieldset>
<legend>Science</legend>
<input type="number" class="Science">
<input type="number" class="Science">
<input type="number" class="Science">
<output id="Science"></output>
</fieldset>
<fieldset>
<legend>Physics</legend>
<input type="number" class="Physics">
<input type="number" class="Physics">
<input type="number" class="Physics">
<output id="Physics"></output>
</fieldset>
<fieldset>
<legend>History</legend>
<input type="number" class="History">
<input type="number" class="History">
<input type="number" class="History">
<output id="History"></output>
</fieldset>
<br>
<input type="button" value="Calculate" id="calcBtn">
</form>
document.getElementById('calcBtn').addEventListener('click', function() {
var testcount = [];
var count = 0;
testcount = Array.prototype.slice.call(document.getElementsByClassName('test1'))
for(var i=0;i<testcount.length;i++)
{
if(Number(testcount[i].value) > 0)
{
count=count+1;
}
}
var test1 = document.getElementById('test1').value;
var test2 = document.getElementById('test2').value;
var test3 = document.getElementById('test3').value;
var average = document.getElementById('average');
average.value = (Number(test1) + Number(test2) + Number(test3)) / count;
});
<form>
<input type="number" class="test1" id="test1">
<input type="number" class="test1" id="test2">
<input type="number" class="test1" id="test3">
<output id="average"></output>
<br>
<input type="button" value="Calculate" id="calcBtn">
</form>
in the above, output is achieved by adding a class attribute in the input tag(give same name for same group of controls).
Second thing is looping through this class object for getting count of non-empty or non-zero valued.
My recommendation is, to avoid multiple input fields in order to read in multiple inputs. In my case, I am using semicolons to separate the individual values inside the input field. By doing so I am able to enter as many values as I want (at least one value). Therefore my form looks like the following:
<!-- form.html -->
<form>
Science: <input type="text" id="scienceTest">
<output id="scienceAverage"></output>
<br> Physics: <input type="text" id="physicsTest">
<output id="physicsAverage"></output>
<br> History: <input type="text" id="historyTest">
<output id="historyAverage"></output>
<br>
<input type="button" value="Calculate" id="calcBtn">
<output id="finalGrade"></output>
</form>
<script src="script.js"></script>
And my JavaScript looks like this:
// script.js
(function() {
var scienceTest = document.getElementById('scienceTest');
var physicsTest = document.getElementById('physicsTest');
var historyTest = document.getElementById('historyTest');
var scienceAverage = document.getElementById('scienceAverage');
var physicsAverage = document.getElementById('physicsAverage');
var historyAverage = document.getElementById('historyAverage');
var finalGrade = document.getElementById('finalGrade');
function sumArray(sum, item) {
return sum + item;
}
document.getElementById('calcBtn').addEventListener('click', function() {
// fetch the string of the input and split into its separate numbers
var scienceGradeStrings = scienceTest.value.split(";");
var physicsGradeStrings = physicsTest.value.split(";");
var historyGradeStrings = historyTest.value.split(";");
// calculate the averages
scienceAverage.value = scienceGradeStrings
// convert the grades from strings to numbers
.map(Number)
// sum all grades together
.reduce(sumArray, 0)
// calculate the average grade
/ scienceGradeStrings.length;
physicsAverage.value = physicsGradeStrings.map(Number).reduce(sumArray, 0) / physicsGradeStrings.length;
historyAverage.value = historyGradeStrings.map(Number).reduce(sumArray, 0) / historyGradeStrings.length;
finalGrade.value = (scienceAverage.value * 5 + physicsAverage.value * 3 + historyAverage.value * 2) / 10;
});
})();
What is a simple approach to get the number of changed input fields
We can assign each input a custom data attribute equal to the input value and then exclude the empty ones when counting them. The following generic script can be used for any number of courses:
var form = document.querySelector('form');
function calculateAverage(fieldset) {
var total = 0;
var inputs = fieldset.querySelectorAll('input');
for (var input of inputs) {
total += Number(input.value);
input.dataset.value = input.value;
}
return total / fieldset.querySelectorAll('input:not([data-value=""])').length;
}
function displayAverages() {
var fieldsets = form.querySelectorAll('fieldset');
for (var fieldset of fieldsets) {
var avg = calculateAverage(fieldset);
var output = fieldset.querySelector('output');
if (isNaN(avg)) {
output.value = 'Please enter a grade.';
} else {
output.value = 'Average: ' + avg.toFixed(1);
}
}
}
form.querySelector('button').addEventListener('click', displayAverages);
body {
display: flex;
}
fieldset {
margin: 0 0 16px;
}
input {
width: 4em;
}
output {
display: block;
height: 1em;
margin: 8px 0 0 2px;
}
<form>
<fieldset>
<legend>Physics</legend>
<input type="number">
<input type="number">
<input type="number">
<output></output>
</fieldset>
<fieldset>
<legend>History</legend>
<input type="number">
<input type="number">
<input type="number">
<output></output>
</fieldset>
<button type="button">Calculate</button>
</form>

Javascript won't calculate

Can anyone point me in the right direction as to why my calculate button will not calculate. It doesn't even throw any of the error messages up to the screen, but my clear button does work. It's probably something small, but I cannot figure it out for the life of me -_-.
var $ = function(id) {
return document.getElementById(id);
}
var virusRemovalPrice = 20.00;
var websiteMakingCost = 75.00;
var computerServicingCost = 100.00;
var calculateTotal = function() {
var virusRemoval = parseFloat($("virusRemoval").value);
var websiteMaking = parseFloat($("websiteMaking").value);
var computerOptimizationAndSetUp = parseFloat($("computerOptimizationAndSetUp").value);
var totalCost = parseFloat(($("totalCost").value));
if (isNaN(virusRemoval) || virusRemoval < 0) {
alert("Value must be numeric and at least zero. ");
$("virusRemoval").focus()
} else if (isNaN(websiteMaking) || websiteMaking < 0) {
alert("Value must be numeric and at least zero. ");
$("websiteMaking").focus()
} else if (isNaN(computerOptimizationAndSetUp) || computerOptimizationAndSetUp < 0) {
alert("Value must be numeric and at least zero. ");
$("computerOptimizationAndSetUp").focus()
} else {
do {
var ii = 0;
var cost = ((virusRemovalPrice * virusRemoval) + (websiteMakingCost * websiteMaking) + (computerServicingCost * computerOptimizationAndSetUp));
$("cost").value = cost.toFixed(2); //total cost final
if (cost > 1) {
alert("Your total is " + cost + " hope to see you soon!");
}
} while (ii = 0)
}
};
var clearValues = function() {
var virusRemoval = parseFloat($("virusRemoval").value = "");
var websiteMaking = parseFloat($("websiteMaking").value = "");
var computerOptimizationAndSetUp = parseFloat($("computerOptimizationAndSetUp").value = "");
var totalCost = parseFloat($("totalCost").value = "");
}
<form class="anotheremoved">
<h2>Total Cost</h2>
<label for="virusRemoval">Virus Removal:</label>
<br />
<input type="text" id="virusRemoval">
<br />
<label for="websiteMaking">Website Design:</label>
<br />
<input type="text" id="websiteMaking">
<br />
<label for="computerOptimizationAndSetUp">Computer Setup:</label>
<br />
<input type="text" id="computerOptimizationAndSetUp">
<br />
<br />
<label for="totalCost">Your Total Cost is:</label>
<input type="text" id="TotalCost" disabled>
<br />
<input class="removed" type="button" id="calculateTotal" value="Calculate " onblur="calculateTotal()">
<input class="removed" type="button" id="clear" value="Clear" onclick="clearValues()">
</form>
The reason why the loop is in there is because we were required to have a loop and I couldn't find a good reason to have one, so I used one that would always be true to get it out of the way lol. Probably will throw an infinate loop at me or something, but I'll figure that out later, I'm just trying to get the dang on thing to do something here haha. I've tried to rewrite this 2 other times and still get to the same spot, so I realize it's probably something small, and I am new to Javascript. Thank you.
The problem is that you have id="calculateTotal" in the input button. Element IDs are automatically turned into top-level variables, so this is replacing the function named calculateTotal. Simply give the function a different name from the button's ID.
You also have a typo. The ID of the Total Cost field is TotalCost, but the code uses $('totalCost') and $('cost').
It's also better to do the calculation in onclick, not onblur. Otherwise you have to click on the button and then click on something else to see the result.
In the clearValues function, there's no need to assign variables and call parseFloat. Just set each of the values to the empty string. You could also just use <input type="reset">, that resets all the inputs in the form to their initial values automatically.
var $ = function(id) {
return document.getElementById(id);
}
var virusRemovalPrice = 20.00;
var websiteMakingCost = 75.00;
var computerServicingCost = 100.00;
var calculateTotal = function() {
var virusRemoval = parseFloat($("virusRemoval").value);
var websiteMaking = parseFloat($("websiteMaking").value);
var computerOptimizationAndSetUp = parseFloat($("computerOptimizationAndSetUp").value);
var totalCost = parseFloat(($("TotalCost").value));
if (isNaN(virusRemoval) || virusRemoval < 0) {
alert("Value must be numeric and at least zero. ");
$("virusRemoval").focus()
} else if (isNaN(websiteMaking) || websiteMaking < 0) {
alert("Value must be numeric and at least zero. ");
$("websiteMaking").focus()
} else if (isNaN(computerOptimizationAndSetUp) || computerOptimizationAndSetUp < 0) {
alert("Value must be numeric and at least zero. ");
$("computerOptimizationAndSetUp").focus()
} else {
do {
var ii = 0;
var cost = ((virusRemovalPrice * virusRemoval) + (websiteMakingCost * websiteMaking) + (computerServicingCost * computerOptimizationAndSetUp));
$("TotalCost").value = cost.toFixed(2); //total cost final
if (cost > 1) {
alert("Your total is " + cost + " hope to see you soon!");
}
} while (ii = 0)
}
};
var clearValues = function() {
$("virusRemoval").value = "";
$("websiteMaking").value = "";
$("computerOptimizationAndSetUp").value = "";
$("TotalCost").value = "";
}
<form class="anotheremoved">
<h2>Total Cost</h2>
<label for="virusRemoval">Virus Removal:</label>
<br />
<input type="text" id="virusRemoval">
<br />
<label for="websiteMaking">Website Design:</label>
<br />
<input type="text" id="websiteMaking">
<br />
<label for="computerOptimizationAndSetUp">Computer Setup:</label>
<br />
<input type="text" id="computerOptimizationAndSetUp">
<br />
<br />
<label for="totalCost">Your Total Cost is:</label>
<input type="text" id="TotalCost" disabled>
<br />
<input class="removed" type="button" id="calculateTotalButton" value="Calculate " onclick="calculateTotal()">
<input class="removed" type="button" id="clear" value="Clear" onclick="clearValues()">
</form>

java script functions for different textbox my code here

This is my code in html and java script. I coded same things thrice, I want to do it once... what to do...............
<input type="text" name="option1" id="option1" onblur="calc_amt(1);">
<input type="text" name="price1" id="price1" onblur="calc_amt(1);">
<input type="text" name="amount1" id="amount1" readonly>
<input type="text" name="option2" id="option2" onblur="calc_amt(2);">
<input type="text" name="price2" id="price2" onblur="calc_amt(2);">
<input type="text" name="amount2" id="amount2" readonly>
<input type="text" name="option3" id="option3" onblur="calc_amt(3);">
<input type="text" name="price3" id="price3" onblur="calc_amt(3);">
<input type="text" name="amount3" id="amount3" readonly>
<script>
function calc_amt(val){
if(val==1){
var option1 = document.getElementById("option1").value;
var pri1 = document.getElementById("price1").value;
....
document.getElementById("amount1").value=amoun1 ;
}
if(val==2){
var option2 = document.getElementById("option2").value;
var pri2 = document.getElementById("price2").value;
...
document.getElementById("amount2").value=amoun2;
}
if(val==3){
var option3 = document.getElementById("option3").value;
var pri3 = document.getElementById("price3").value;
....
document.getElementById("amount3").value=amoun3;
}
var amoun1=document.getElementById("amount1").value;
var amoun2=document.getElementById("amount2").value;
var amoun3=document.getElementById("amount3").value;
var tot = Number(amt1)+Number(amt2)+Number(amt3);
document.getElementById("amount").value=tot;
}
</script>
how do solve it by coding only once... I am beginner please help me.... any other ideas to solve this.. i need a solution like inheritance.
You can further reduce above script like this. Your amoun is unclear for though. However you can reduce the code like this. This is just an idea and make sure you match the variables with correct statement.
<script>
function calc_amt(val){
var option1 = document.getElementById("option"+val).value;
var pri1 = document.getElementById("price"+val).value;
....
document.getElementById("amount"+val).value=""+amount+val ;
var amoun1=document.getElementById("amount1").value;
var amoun2=document.getElementById("amount2").value;
var amoun3=document.getElementById("amount3").value;
var tot = Number(amt1)+Number(amt2)+Number(amt3);
document.getElementById("amount").value=tot;
}
</script>
Replace:
if(val==1){
var option1 = document.getElementById("option1").value;
var pri1 = document.getElementById("price1").value;
document.getElementById("amount1").value=amoun1 ;
}
with:
var amoun = document.getElementById("amount" + val).value;
var option = document.getElementById("option" + val).value;
var pri = document.getElementById("price" + val).value;
document.getElementById("amount" + val).value=amoun;
TRY...
Remove all inline handler and use blur handler like in demo
$("input[type=text]").on("blur", function () {
var id = this.id;
var last = id.charAt(id.length - 1); // get last id string value
var optionValue = $("#option" + last).val();
var priceValue = $("#price" + last).val();
var option = isNaN(optionValue) ? 0 : +optionValue; // check is nan
var price = isNaN(priceValue) ? 0 : +priceValue;
$("#amount" + last).val(option * price); // display multiply value
$("#amount").text($("input[type=text][id^=amount]").map(function () { // collect all amount1,2,3 values
var value = $(this).val();
return isNaN(value) ? 0 : +value;
}).get().reduce(function (a, b) { // add total value
return a + b;
}));
});
DEMO
OPTIMIZED CODE
$("input[type=text]:not([readonly])").on("blur", function () {
var obj = $();
obj = obj.add($(this)).add($(this).prevUntil('[readonly]')).add($(this).nextUntil('[readonly]'));
$(this).nextAll('[readonly]').first().val($.map(obj, function (val, i) {
return parseInt(val.value, 10) || 0;
}).reduce(function (a, b) {
return a * b
}, 1));
$("#amount").text($("input[type=text][id^=amount]").map(function () {
var value = $(this).val();
return isNaN(value) ? 0 : +value;
}).get().reduce(function (a, b) {
return a + b;
}));
});
DEMO

Categories

Resources