Passing checkbox value to array in Angular - javascript

I'm working on a solution where each of the inputs, generated depending on the passenger number, gets additional checkboxes. I want to pass the value of each checkbox and bind it to the input next to it. The plan is to eventually end up with an array in which I have the value of the input field + each checkbox value. I am not sure how to move forward with this. For now, I have:
COMPONENT.HTML:
<li *ngFor="let passenger of createRange(showStorage.passengersNumber); let i=index"><input type="text" [(ngModel)]="passengersData[i]" placeholder="Name and surname">
<input type="checkbox" (click)="addChild($event)">Child
<input type="checkbox" (click)="addLuggage($event)">Luggage
</li>
COMPONENT.TS
constructor() {
this.passengersData = [];
}
public passengersData: any[];
public luggageCounter: number = 0;
public childrenCounter: number = 0;
createRange(number){
var items: number[] = [];
for(var i = 1; i <= number; i++){
items.push(i);
}
return items;
}
addLuggage(event) {
console.log("Added luggage");
this.luggageCounter++
}
addChild(event) {
this.childrenCounter++
console.log(event);
}
Thanks for any help!
STACKBLITZ: https://stackblitz.com/edit/flight-date-picker-with-service-done-qfmn6p

Use an array with a name property and boolean properties for child and luggage and regenerate the array only when the number of passengers is updated retaining the previous passengers.
Here is a working StackBlitz https://stackblitz.com/edit/angular-ivy-s7dtu4?file=src%2Fapp%2Fapp.component.ts
Number of passengers <input type="text" [ngModel]="passengers.length" (ngModelChange)="updateNumOfPassengers($event)">
<li *ngFor="let passenger of passengers; let i=index">
<input type="text" [name]="'name_' + 1" [(ngModel)]="passenger.name" placeholder="Name and surname">
<input type="checkbox" [checked]="passenger.child" (change)="passenger.child = !passenger.child">Child
<input type="checkbox" [checked]="passenger.luggage" (change)="passenger.luggage = ! passenger.luggage">Luggage
</li>
passengers = [];
updateNumOfPassengers(input: string) {
const numOfPassengers = parseInt(input);
if (!isNaN(numOfPassengers) && numOfPassengers >= 0) {
const passengersToAdd = numOfPassengers - this.passengers.length;
if (passengersToAdd > 0) {
this.passengers = [...this.passengers, ...Array.from({ length: passengersToAdd }, _ => ({ name: '', child: false, luggage: false }))];
} else if (passengersToAdd < 0) {
this.passengers = this.passengers.filter((_, index) => index < numOfPassengers);
}
}
}

Related

Vue.js: Input formatting using computed property is not applying when typing quick

I am trying to format data in this format m:ss, and limit the input textbox to only show 3 or fewer characters. (m = minute, s = seconds)
I have the below code, it formats properly in m:ss format. But if I type many characters at once, it shows more than 3 characters or it gets stuck.
I.E) If I type 11111 at once, I get 1:1111. But I want the textbox to show 1:11.
If I type 1234, sometimes it gets stuck at 1:2. Then If I wait and type again, there are more than 3 characters.
new Vue({
el:"#app",
data () { return { value: "" } },
computed: {
fValue: {
// getter
get () {
if (this.value.length > 3) { return this.value.substr(0, 3); }
return this.value;
},
// setter
set (newValue) {
this.formatTime(newValue);
}
}
},
methods: {
formatTime (str) {
let totalLength = str.length;
if (totalLength > 3) { totalLength = 3; }
let a = str.replace(/[^0-9]/g, "").substr(0, 1);
let b = str.replace(/[^0-9]/g, "").substr(1, 1);
if (b > 5) { b = 5; }
const c = str.replace(/[^0-9]/g, "").substr(2, 1);
if (totalLength >= 2) { a = `${a.substring(0, 1)}:${b}${c}`; }
const result = a;
this.value = result;
return result;
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<input
v-model="fValue"
id="format-value"
class="input"
type="text"
/>
</div>
------ EDIT Question 2 -----
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div
v-for="(index) in valueInputs" <-- index
:key="index"
>
<input
v-model="value" // <-- I want to track what index I'm in
#input="formatTime" // <-- so I can set it in an array later
maxLength="4" // I tried formatTime[index] or value[index]
id="format-value"
class="input"
type="text"
/>
</div>
</div>
data () {
return {
valueInputs: [], // a list of inputs
allFormatValues: [] // want to store all the formatted values here by the index
}
}
Want to set an array that stores all the formatted values:
this.allFormatValues[index] = this.value;
I'm not sure how to associate the index with the formatted string value?
You can use value as the v-model and call formatTime on input. Here is a demo with some refactoring/improvements:
new Vue({
el:"#app",
data () { return { value: "" } },
methods: {
formatTime () {
const numericValue = this.value.replace(/[^0-9]/g, "");
let [a = '', b = '', c = ''] = numericValue.substr(0, 3).split('');
b = b > 5 ? 5 : b;
this.value = numericValue.length >= 2 ? `${a}:${b}${c}` : a;
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<input
v-model="value"
#input="formatTime"
maxLength="4"
id="format-value"
class="input"
type="text"
/>
</div>

Javascript loop array for form validation

I have a table form with some rows, that are controlled by user. Meaning they can add as more as they want. Let's pretend user requested 5 rows and i need to check if they all have values.
function validateForm() {
var lastRowInserted = $("#packageAdd tr:last input").attr("name"); // gives me "packageItemName5"
var lastCharRow = lastRowInserted.substr(lastRowInserted.length - 1); // gives me 5
var i;
for (i = 1; i <= lastCharRow; i++) {
var nameValidate[] = document.forms["packageForm"]["packageItemName"].value;
if(nameValidate[i].length<1){
alert('Please fill: '+nameValidate[i]);
return false;
}
}
}
How can i receive packageItemName1 to 5 values in a loop so then I can use to validate them. Want the loop to process this code
var nameValidate[] = document.forms["packageForm"]["packageItemName1"].value;
var nameValidate[] = document.forms["packageForm"]["packageItemName2"].value;
var nameValidate[] = document.forms["packageForm"]["packageItemName3"].value;
var nameValidate[] = document.forms["packageForm"]["packageItemName4"].value;
var nameValidate[] = document.forms["packageForm"]["packageItemName5"].value;
Like this
const validatePackageItems = () => {
const nameValidate = $("form[name=packageForm] input[name^=packageItemName]"); // all fields with name starting with packageItemName
const vals = nameValidate.map(function() { return this.value }).get(); // all values
const filled = vals.filter(val => val.trim() !== ""); // all values not empty
console.log("Filled", filled, "= ", filled.length, "filled of", vals.length)
return filled.length === vals.length
};
$("[name=packageForm]").on("submit",(e) => {
if (!validatePackageItems()) {
alert("not valid");
e.preventDefault();
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<form name="packageForm">
<input type="text" name="packageItemName1" value="one" /><br/>
<input type="text" name="packageItemName2" value="two" /><br/>
<input type="text" name="packageItemName3" value="" /><br/>
<input type="text" name="packageItemName4" value="four" /><br/>
<input type="submit">
</form>
You can use string interpolation to get the key dynamically:
for (let i = 1; i < 6; i++) {
const currentValue = document.forms.packageForm[`packageItemName${i}`]
console.log('current value:', currentValue)
}

Why is Javascript equating 5 == 8 as true?

So I have 2 check-boxes:
var statusList = [];
function updateStatusString(x) {
if (statusList != null) {
if (statusList.length > 0) {
for (var i = 0; i < statusList.length; i++) {
if (parseInt(statusList[i]) == parseInt(x)) {
statusList[i] = 123;
} else {
statusList.push(x);
}
}
} else {
statusList.push(x);
}
}
alert(statusList);
}
<label> <input type="checkbox" name="Active" value="5" onchange=updateStatusString("5")> "Active"</label>
<label> <input type="checkbox" name="NonActive" value="8" onchange=updateStatusString("8")> "Active"</label>
When I click a checkbox it adds it to a JavaScript list, if it's already in the list I want to overwrite it with another value (123 in this example).
But when I click the second one (doesn't matter the order, the 2nd element is always 123 for some reason.
Where as I would expect if I click the top check-box it would be a list containing '5', then clicking the second check-box I would expect 5,8 but it alerts as 5,123, don't really see why it's doing this as 5==8 is false... any ideas?
Updated algorithm to fix underlying issue:
In-case anyone ever finds this useful I changed the algorithm to a better alternative:
var statusList = [];
function updateStatusString(x) {
if (statusList.length > 0) {
if (statusList.includes(x)) {
var idx = statusList.indexOf(x);
if (idx != -1) {
statusList.splice(idx, 1);
}
}
else {
statusList.push(x);
}
} else {
statusList.push(x);
}
alert(statusList);
}
first iteration:
since status list is empty you are adding 5 in it,
second iteration:
statulsList = [5]
you are adding 8 so now the statusList value is [5,8] which means also the length is 2,
so we have a third iteration which in this case 8 === 8 .
if you want to have it different save the length of the status list before adding the other item on the list.
var statusList = [];
function updateStatusString(x) {
if (statusList != null) {
if (statusList.length > 0) {
var lengthStat = statusList.length;
for (var i = 0; i < lengthStat; i++) {
if (parseInt(statusList[i]) == parseInt(x)) {
statusList[i] = 123;
} else {
if(! (statusList.indexOf(x) != -1))
statusList.push(x);
}
}
} else {
statusList.push(x);
}
}
alert(statusList);
}
<label> <input type="checkbox" name="Active" value="5" onchange=updateStatusString("5")> "Active"</label>
<label> <input type="checkbox" name="NonActive" value="8" onchange=updateStatusString("8")> "Active"</label>
Because you are iterating over the statusList. At first iteration you check if 5 == 8, then moves to else part and inserts 8 in statusList. Your statusList is = [5, 8]. For next iteration, this becomes true statuslist[i] will be 8 and 8===8 and your statement - statusList[i] = 123; will replace last inserted 8value with 123. Therefore, your statusList array will have ["5", 123].
var statusList = [];
function updateStatusString(x) {
const input = parseInt(x);
if (statusList != null) {
if (statusList.includes(input)) {
const idx = statusList.indexOf(input);
statusList[idx] = 123;
} else {
statusList.push(input);
}
alert(statusList);
}
}
<label> <input type="checkbox" name="Active" value="5" onchange=updateStatusString("5")> "Active"</label>
<label> <input type="checkbox" name="NonActive" value="8" onchange=updateStatusString("8")> "Active"</label>
It looks like the loop was causing your issue.
You were checking for the existence of x, which on the first loop was false
You pushed it to the array
Second loop, it existed and was replaced with 123
You can dramatically simplify your code by removing one of the if checks, and using array.prototype.includes instead of looping and checking equality.
Edit: Added a third input to demonstrate 123 being added.
var statusList = [];
function updateStatusString(x) {
if (statusList != null) {
if (statusList.includes(x)) {
statusList[statusList.indexOf(x)] = 123;
} else {
statusList.push(x);
}
}
alert(statusList);
}
<label> <input type="checkbox" name="Active" value="5" onchange=updateStatusString("5")> "Active"</label>
<label> <input type="checkbox" name="NonActive" value="8" onchange=updateStatusString("8")> "Active"</label>
<label> <input type="checkbox" name="NonActive" value="8" onchange=updateStatusString("5")> "Active"</label>

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>

Populate input field with checkboxes jQuery

I am trying to populate two text fields with checkbox values. It will get the min and max data attribute values from the checkboxes, find the highest and lowest numbers, and set input fields with this values. If all checkboxes are unchecked, default values will be set.
For example if 50-100 and 200-300 options are checked, min input will be set to 50 and max input field will be set to 300
var lowest = +Infinity;
var highest = -Infinity;
$("input[type='checkbox']").on('click', function () {
if ($(this).filter(':checked').length > 0) {
lowest = Math.min(lowest, parseFloat($(this).attr("data-min")));
highest = Math.max(highest, parseFloat($(this).attr("data-max")));
$("#price-min").val(lowest);
$("#price-max").val(highest);
} else {
$("#price-min").val(1);
$("#price-max").val(150000);
}
});
here is my html :
<input id="price-min" type="text" value="0">
<input id="price-max" type="text" value="0"><br><br>
<input type="checkbox" data-min="50" data-max="100">50-100<br>
<input type="checkbox" data-min="100" data-max="200">100-200<br>
<input type="checkbox" data-min="200" data-max="300">200-300
https://jsfiddle.net/fpooysad/
In the change event of :checkbox take all checked min and max value in two array using map() method and then sort these two array using sort().
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input id="price-min" type="text" value="0">
<input id="price-max" type="text" value="0">
<br>
<br>
<input type="checkbox" data-min="50" data-max="100">50-100
<br>
<input type="checkbox" data-min="100" data-max="200">100-200
<br>
<input type="checkbox" data-min="200" data-max="300">200-300
<script>
$("input[type='checkbox']").change(function () {
var min = $('input:checkbox:checked').map(function () { return $(this).data('min') }).get().sort(function (a, b) { return a - b });
var max = $('input:checkbox:checked').map(function () { return $(this).data('max') }).get().sort(function (a, b) { return b - a });
if (min.length) {
$("#price-min").val(min[0]);
$("#price-max").val(max[0]);
} else {
$("#price-min").val(1);
$("#price-max").val(150000);
}
});
</script>
$(document).ready(function () {
$("input[type='checkbox']").on('click', function () {
var allChecked = $("input[type='checkbox']").filter(':checked');
if (allChecked.length > 0) {
var lowest = null;
var highest = null;
allChecked.each(function() {
var low = parseFloat($(this).attr("data-min"));
var high = parseFloat($(this).attr("data-max"));
if (lowest == null || low < lowest) {
lowest = low;
}
if (highest == null || high > highest) {
highest = high;
}
});
$("#price-min").val(lowest);
$("#price-max").val(highest);
} else {
$("#price-min").val(1);
$("#price-max").val(150000);
}
});
$(document).ready(function () {
$("input[type=checkbox]").click(function() {
var maxArr = [], minArr = [];
var getMax = 15000, getMin = 0;
$("input[type=checkbox]:checked").each(function() {
var getIndMax = $(this).attr("data-max");
var getIndMin = $(this).attr("data-min");
maxArr.push(getIndMax);
minArr.push(getIndMin);
});
if(minArr.length) {
getMin = Math.min.apply(null, minArr);
}
if(maxArr.length) {
getMax = Math.max.apply(null, maxArr);
}
$("#price-min").val(getMin);
$("#price-max").val(getMax);
});
});
https://jsfiddle.net/fpooysad/2/
Terms Utilized
.push is used in order to add a value in an array, in this case array vars are maxArr and minArr
Math.min & .max is to consider max and minimum values within an array
This must help you out
$(document).ready(function() {
$("input[type='checkbox']").on('change', function() {
var lowest =+Infinity;
var highest = 0;
$.each($("input[type='checkbox']:checked"),function(){
lowest = parseInt($(this).data("min")) < lowest ?parseInt($(this).data("min")) : lowest;
highest = parseInt($(this).data("max")) > highest ?parseInt($(this).data("max")) : highest;
});
if (lowest ==0 && highest ==0) {
$("#price-min").val(1);
$("#price-max").val(150000);
} else {
$("#price-min").val(lowest);
$("#price-max").val(highest);
}
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input id="price-min" type="text" value="0">
<input id="price-max" type="text" value="0">
<br>
<br>
<input type="checkbox" data-min="50" data-max="100">50-100
<br>
<input type="checkbox" data-min="100" data-max="200">100-200
<br>
<input type="checkbox" data-min="200" data-max="300">200-300
And here is Working JSfiddle

Categories

Resources