JavaScript array application - javascript

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">

Related

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>

Adding checkbox with multiple value

I have a set of books in checkboxes which a user can select. Every time a book is checked the price adds up. I also need to add its corresponding weight. I've modified this very useful example but to no avail.
<label class="checkbox" for="Checkbox1">
<input value="50" type="checkbox" class="sum" data-toggle="checkbox"> Instagram
</label>
<label class="checkbox">
<input value="50" bweight=".4" type="checkbox" class="sum" data-toggle="checkbox"> Review site monitoring
</label>
<label class="checkbox">
<input value="30" bweight=".2" type="checkbox" class="sum" data-toggle="checkbox"> Google+
</label>
<label class="checkbox">
<input value="20" bweight=".6" type="checkbox" class="sum" data-toggle="checkbox"> LinkedIn
</label>
<div class="card-charge-info">
<span id="payment-total">0</span>
</div>
var inputs = document.getElementsByClassName('sum'),
total = document.getElementById('payment-total');
totwgt = document.getElementById('payment-w');
for (var i = 0; i < inputs.length; i++) {
inputs[i].onchange = function() {
var add = this.value * (this.checked ? 1 : -1);
var add = this.wgt * (this.checked ? 1 : -1);
total.innerHTML = parseFloat(total.innerHTML) + add
totwgt.innerHTML = parseFloat(total1.innerHTML) + add
}
}
Heres the code https://jsfiddle.net/u8bsjegk/2/
There's several issues in your code. Firstly you define the add variable in two places, one for the value the other for weight so your calculation is broken from there. Also note that bweight is not a valid attribute on the input element. To add your own meta data you should use a data-* attribute instead.
Try this:
var inputs = document.getElementsByClassName('sum'),
totalValue = document.getElementById('payment-total'),
totalWeight = document.getElementById('payment-w');
for (var i = 0; i < inputs.length; i++) {
inputs[i].onchange = function() {
var value = parseFloat(this.value);
var weight = parseFloat(this.dataset.weight);
totalValue.innerHTML = parseFloat(totalValue.innerHTML) + value
totalWeight.innerHTML = parseFloat(totalWeight.innerHTML) + weight
}
}
As you've tagged this question with jQuery, here's a working implementation for that
var $inputs = $('.sum'),
$totalValue = $('#payment-total'),
$totalWeight = $('#payment-w');
$inputs.change(function() {
var value = parseFloat(this.value);
var weight = parseFloat($(this).data('weight'));
$totalValue.text(function(i, text) {
return parseFloat(text) + value;
});
$totalWeight.text(function(i, text) {
return parseFloat(text) + weight;
});
})
Working example
You have few issues:
You redeclare add variable
you have typo in total1 it should be totwgt
you don't have element with payment-w id
instead of this.wgt you should use this.getAttribute('bweight')
https://jsfiddle.net/u8bsjegk/6/

How do I get the sum of radio buttons groups using parameters instead of values

I have multiple sets of radio buttons where the selected values of each set need to be added and displayed to the user. So far I have been changing the values in the function in a switch statement to handle the addition.
<form name="config" id="config">
<div class="row-fluid">
<h3>Memory</h3>
<input type="radio" name="section1" value="4gb" onclick="changePrice(0)" checked>4gb<br>
<input type="radio" name="section1" value="8gb" onclick="changePrice(100)">8gb (+$100)<br>
<input type="radio" name="section1" value="16gb" onclick="changePrice(200)">16gb (+$200)
</div>
<div class="row-fluid">
<h3>Primary Hard Drive</h3>
<input type="radio" name="section2" value="dell" onclick="changePrice(0)" checked>Dell<br>
<input type="radio" name="section2" value="asus" onclick="changePrice(100)">Asus(+$100)
</div>
</form>
<div id="price"></div>
The script I am using right now is
var displayPrice = document.getElementById("price");
var baseNum = 200;
displayPrice.innerHTML = baseNum;
function(changePrice){
var val1, val2;
switch(document.config.section1.value){
case "4gb":
val1 = 0;
break;
case "8gb":
val1 = 100;
break;
case "16gb":
val1 = 200;
break;
default:
val1 = 0;
}
switch(document.config.section2.value){
case "dell":
val1 = 0;
break;
case "asus":
val1 = 100;
break;
default:
val1 = 0;
}
var sum = val1 + val2 + baseNum;
displayPrice.innerHTML = sum;
}
Is there a way I can do these calculations using the parameters passed through the changePrice function (so I don't have to change the values in the switch statements)?
Here's how to do this without jQuery.
You'll need to tell the changePrice function which section it should change the price for so you'll need to change the calls to look like this changePrice(1, 100) where 1 is the section and 100 is the price change. Then you can collect all the section prices individually and sum them like so:
var displayPrice = document.getElementById("price");
var baseNum = 200;
displayPrice.innerHTML = baseNum;
var sections = {};
function changePrice(section,val){
// add or update the section value
sections[section] = val;
// start with the base price
var sum = baseNum;
// loop over all the sections and add them to the base price
for(var key in sections) {
sum = sections[key] + sum;
}
displayPrice.innerHTML = sum;
}
Here's a jsfiddle
If you change your function definition to the following, it will take in your parameter.
function changePrice(val1)
If you could change your the value attribute on each of your input fields to contain your increment value, it would make the process of calculating your sum much easier. (This may not be appropriate to the problem you are trying to solve.
Basic solution with jQuery
var sum = $("input[name=section1]").val() + $("input[name=section2]").val();
If your list is very long, you could iterate over your radio button sets with jQuery
var sum = 0;
$("input[type=radio]").each(function(){sum += $(this).val();});
<html>
<head>
<script type="text/javascript">
function DisplayPrice(price){
var val1 = 0;
for( i = 0; i < document.form1.R1.length; i++ ){
if( document.form1.R1[i].checked == true ){
val1 = document.form1.R1[i].value;
}
}
var val2 = 0;
for( i = 0; i < document.form2.R2.length; i++ ){
if(document.form2.R2[i].checked == true ){
val2 = document.form2.R2[i].value;
}
}
var sum=parseInt(val1) + parseInt(val2);
document.getElementById('totalSum').innerHTML=sum;
}
</script>
</head>
<body>
Choose a number:<br>
<form name="form1" id="form1">
<br>
R1 <input id="rdo_1" type="radio" value="5" name="R1" onClick="DisplayPrice(this.value);" checked>5
<br>
R1 <input id="rdo_2" type="radio" value="10" name="R1" onClick="DisplayPrice(this.value);">10
<br>
</form>
Choose another number:<br>
<form name="form2" id="form2">
<br>
R2 <input id="rdo_1" type="radio" value="15" name="R2" onClick="DisplayPrice(this.value);" checked>15
<br>
R2 <input id="rdo_2" type="radio" value="20" name="R2" onClick="DisplayPrice(this.value);">20
<br>
</form>
Your total is Rs = <span name="totalSum" id="totalSum" > 20</span>
</body>
</html>

Add all values in same class javascript

How can I get the total value of all the values that have a class called total
<script>
function gettotal()
{
var total = 12;
}
</script>
<input type=text class=total value=5>
<input type=text class=total value=7>
<input type=submit name=submit value="Add" onclick=gettotal();>
You can do:
var total = 0;
var totalElems = document.getElementsByClassName("total");
for (var i = 0; i < totalElems.length; i++) {
total += parseInt(totalElems[i].value, 10);
}

Calculating totals when clicking checkboxes

I have a list of radio buttons represented by this code:
<form id="menu4strombolis">
input1 <input type="radio" name="menu1"><br />
input2 <input type="radio" name="menu2"><br />
input3 <input type="radio" name="menu3"><br />
input4 <input type="radio" name="menu4"><br />
input5 <input type="radio" name="menu5"><br />
input6 <input type="radio" name="menu6"><br />
input7 <input type="radio" name="menu7"><br />
</form>
Whenever a button is selected I need the subtotal and total to be updated.
This is how i want it to look.
Subtotal: <br />
Tax:<br />
Total:<br />
Where tax is always %7 or .7
The prices of menu 1 through 7 increments by $5. Menu1 is $5, menu2 is $10 and so forth.
I was trying to figure out the JavaScript to this but the problem is that i don't want it to display right after the buttons I want it displayed on the bottom of the page.
If I do document.write the whole page gets overwritten. Please help me on this issue guys. I am sure it's really simple.
Preamble
This sounds like homework to me. However, I find that the best way to learn, is by example.
So here's me, leading by example, OK?
You didn't give me much to go on, so for the sake of this example, I'm assuming you've got a list of checkboxes or radiobuttons that say Menu 1... Menu n. Each checkbox that is checked will be added to the subtotal, and then the tax calculated on top of that.
Doing it with radio buttons is a little easier, so I added that example as well.
On the bottom of the post are references for future study on what is used in this example.
If you have any further questions please ask them in the comment area at the bottom of this post.
The Javascript (checkboxes) | JSFiddle example (see it in action)
//Set the tax and base cost
var f_tax = 0.07,
i_menu_base = 5;
//Declare all the variables that will be used
var e_menu = document.getElementById("menu"),
e_checkboxes = e_menu.getElementsByTagName("input"),
e_subtotal = document.getElementById("sub_total");
// Add event listeners for when any checkbox changes value
for(var i = 0; i < e_checkboxes.length; i++){
e_checkboxes[i].onchange = function(){
//Recalculate subtotal
get_subtotal();
}
}
//get_subtotal calculates the subtotal based on which checkboxes are checked
function get_subtotal(){
var f_sub_total = 0.0,
f_grand_total = 0.0;
var subtotal, tax, grandtotal;
for(var i = 1; i <= e_checkboxes.length; i++){
//If the checkbox is checked, add it to the total
if(e_checkboxes[i-1].checked){
f_sub_total += i * i_menu_base;
}
}
//Calculate the grand total
f_grand_total = f_sub_total*(1+f_tax);
//Format them
subtotal = (Math.round(f_sub_total*100)/100).toFixed(2);
tax = (Math.round(f_tax*10000)/100).toFixed(2);
grandtotal = (Math.round(f_grand_total*100)/100).toFixed(2);
//Add them to the display element
e_subtotal.innerHTML = "Subtotal: "+subtotal+"<br />";
e_subtotal.innerHTML += "Tax: "+tax+"%<br />";
e_subtotal.innerHTML += "Total: "+grandtotal;
}
The Javascript (radio buttons) | JSFiddle example (see it in action)
//Set the tax
var f_tax = 0.07,
i_menu_base = 5;
//Declare all the variables that will be used
var e_menu = document.getElementById("menu"),
e_radios = e_menu.getElementsByTagName("input"),
e_subtotal = document.getElementById("sub_total");
// Add event listeners for when any checkbox changes value
for(var i = 0; i < e_radios.length; i++){
e_radios[i].onchange = function(){
//Recalculate subtotal
get_subtotal(this);
}
}
//get_index gets the index of the element (1..n)
function get_index(element){
for(var i = 1; i <= e_radios.length; i++){
if(e_radios[i-1] == element){
return i;
}
}
}
//get_subtotal calculates the subtotal based on the radio that was changed
function get_subtotal(el){
var f_sub_total = 0.0,
f_grand_total = 0.0;
var subtotal, tax, grandtotal;
f_sub_total += get_index(el) * i_menu_base
//Calculate the grand total
f_grand_total = f_sub_total*(1+f_tax);
//Format them
subtotal = (Math.round(f_sub_total*100)/100).toFixed(2);
tax = (Math.round(f_tax*10000)/100).toFixed(2);
grandtotal = (Math.round(f_grand_total*100)/100).toFixed(2);
//Add them to the element
e_subtotal.innerHTML = "Subtotal: "+subtotal+"<br />";
e_subtotal.innerHTML += "Tax: "+tax+"%<br />";
e_subtotal.innerHTML += "Total: "+grandtotal;
}
References for future study
In order in which they appear
getElementById()
getElementsByTagName()
looping through an array
onchange
Math.round(), decimal trick and toFixed()
innerHTML
Despite any additonal input from you, and my better judgement, I was bored so I did it all.
HTML:
<form id="menu4strombolis">input1
<input type="radio" name="menu1" value="5">
<br>input2
<input type="radio" name="menu2" value="10">
<br>input3
<input type="radio" name="menu3" value="15">
<br>input4
<input type="radio" name="menu4" value="20">
<br>input5
<input type="radio" name="menu5" value="25">
<br>input6
<input type="radio" name="menu6" value="30">
<br>input7
<input type="radio" name="menu7" value="35">
<br>
</form>
<button id="getTotal">Total</button>
<div id="subtotal">Sub-Total:</div>
<div id="tax">Tax:</div>
<div id="total">Total:</div>
JS:
var button = document.getElementById("getTotal");
button.onclick = function () {
var subtotalField = document.getElementById("subtotal");
var radios = document.forms["menu4strombolis"].getElementsByTagName("input");
var subtotal = 0;
var tax = 0;
var total = 0;
for (var i = 0, length = radios.length; i < length; i++) {
if (radios[i].checked) {
subtotal += parseInt(radios[i].value);
}
}
tax = (subtotal * .07).toFixed(2); ;
total = subtotal + tax;
document.getElementById("subtotal").innerHTML = "Sub-Total: $" + subtotal;
document.getElementById("tax").innerHTML = "Tax: $" + tax;
document.getElementById("total").innerHTML = "Total: $" + total;
};
and the working fiddle:
http://jsfiddle.net/RGvTt/1/
Though on a side note, you should either group some of the radios together in the same group... or make them checkboxs.
Updated to fix the bug that the OP couldn't fix:
http://jsfiddle.net/RGvTt/4/
Need to use parseFloat.
iPhone fiddle programming FTW!
What you want is element.innerHTML
So it would look something like this:
<form id="menu4strombolis">
input1 <input type="radio" name="menu1"><br />
input2 <input type="radio" name="menu2"><br />
input3 <input type="radio" name="menu3"><br />
input4 <input type="radio" name="menu4"><br />
input5 <input type="radio" name="menu5"><br />
input6 <input type="radio" name="menu6"><br />
input7 <input type="radio" name="menu7"><br />
</form>
..... ..html stuff here
Subtotal: <span id="subtotal"></span><br/>
Tax: <span id="tax"></span><br/>
Total: <span id="total"></span><br/>
ETC...
<script>
var subtotal = //whatever menu item is checked then .value();
var span_subtotal = document.getElementById("subtotal);
var tax = document.getElementById("tax");
var total = document.getElementById("total");
var the_tax = subtotal * .07;
span_subtotal.innerHTML = subtotal;
tax.innerHTML = the_tax;
total.innerHTML = subtotal + the_tax;
<script>

Categories

Resources