I need some help as I'm fairly new to JavaScript.
I wish to create a function that calculates a membership fee
I tried making a function in JavaScript that checks whether only one option has been selected, but I have no idea how to make it so that I can calculate the fee if a user is eligible for more than one or all discounts. There is no current JS for the age condition yet (between 60 and 80) as I am unsure how to do it.
function feeCalc() {
var ans = document.getElementById("answer");
if (document.getElementById('medicalCond-yes').checked) {
ans.value = calculate('medicalCond-yes');
}
if (document.getElementById('empstatus-yes').checked) {
ans.value = calculate('empstatus-yes');
}
if (document.getElementById('empstatus-no').checked) {
ans.value = calculate('empstatus-no');
}
if (document.getElementById('medicalCond-no').checked) {
ans.value = calculate('medicalCond-no');
}
}
function calculate(action) {
var standardRate = 10;
var ageRate = 0.1;
var medicalRate = 0.4;
var unemployedRate = 0.3;
var result;
switch (action) {
case 'medicalcond-yes':
discount = (standardRate * studentRate);
result = standardRate - discount;
break;
case 'empstatus-yes':
discount = (standardRate * unemployedRate);
result = standardRate - discount;
break;
case 'empstatus-no':
result = standardRate;
break;
case 'medicalcond-no':
result = standardRate;
break;
}
return result;
}
<div class="form">
<label>
Age
</label>
<input type="range" value="50" min="1" max="100" class="slider" id="age"/>
</div>
<div class="form">
<label>
Do you have any long-term medical conditions
that can affect daily life
</label>
<br/>
<input type="radio" name="status" value="yes" id="medicalCond-yes"/>Yes
<input type="radio" name="status" value="no" id="medicalCond-no"/>No
</div>
<div class="form">
<label>
Are you currently employed?
</label>
<br/>
<input type="radio" name="empstatus" value="yes" id="empstatus-yes"/>Yes
<input type="radio" name="empstatus" value="no" id="empstatus-no"/>No
</div>
<div class="form">
<label>
Membership Fee
</label>
<br/>
Total Fee:
<input type="text" id="answer" readonly/>
<input type="button" value="Calculate" onclick="feeCalc()"/>
</div>
Instead of switch-case routing into a single branch, you probably want to check through all cases in one go.
function feeCalc() {
var ans = document.getElementById("answer");
ans.value = calculateRate();
}
function calculateRate() {
let discount = 0;
const age = Number(document.getElementById('age').value);
if (age >= 60 && age <= 80) {
discount += 0.1;
}
if (document.getElementById('medicalCond-yes').checked) {
discount += 0.4;
}
if (document.getElementById('empstatus-no').checked) {
discount += 0.3;
}
return 1 - discount;
}
<div class="form">
<label>
Age
</label>
<input type="range" value="50" min="1" max="100" class="slider" id="age"/>
</div>
<div class="form">
<label>
Do you have any long-term medical conditions
that can affect daily life
</label>
<br/>
<input type="radio" name="status" value="yes" id="medicalCond-yes"/>Yes
<input type="radio" name="status" value="no" id="medicalCond-no"/>No
</div>
<div class="form">
<label>
Are you currently employed?
</label>
<br/>
<input type="radio" name="empstatus" value="yes" id="empstatus-yes"/>Yes
<input type="radio" name="empstatus" value="no" id="empstatus-no"/>No
</div>
<div class="form">
<label>
Membership Fee
</label>
<br/>
Total Fee:
<input type="text" id="answer" readonly/>
<input type="button" value="Calculate" onclick="feeCalc()"/>
</div>
Even though for the OP's problem it looks like over-engineering, the OP's provided code is nevertheless small enough in order to demonstrate the advantages of (more) generic approaches which are ...
Especially the JavaScript code which implements a generic approach/behavior of cause is larger from the beginning than its very explicitly written competitor. But the former does not tend to grow or does not even need to get touched for new discount options or changed discount values. This part gets covered by cleaner more generic (hence reusable) substructures of the also generic calculator superstructure.
In addition such a generic component based approach automatically enables the usage of more than just one component at/within one and the same document.
function parseJson(str) {
let result;
try {
result = JSON.parse(str);
} catch (exc) {
result = null;
}
return result;
}
function getDevaluationFactorFromRange(formControl, range) {
let factor = 0;
range = parseJson(range);
if (range !== null) {
const controlValue = parseFloat(formControl.value);
Object
.entries(range)
.some(([devaluationKey, { min, max }]) => {
let isStopIteration = false;
if (
(controlValue >= parseFloat(min)) &&
(controlValue <= parseFloat(max))
) {
factor = parseFloat(devaluationKey);
isStopIteration = true;
}
return isStopIteration
});
}
return Number.isFinite(factor) ? factor : 0;
}
function getDevaluationFactor(formControl) {
const { dataset } = formControl;
let rawRange = dataset.devaluationRange ?? null;
let rawFactor = dataset.devaluationFactor ?? null;
let factor = (rawRange !== null)
? getDevaluationFactorFromRange(formControl, rawRange)
: 0;
factor = (
(factor === 0) && (rawFactor !== null) && parseFloat(rawFactor)
) || factor;
factor = Number.isFinite(factor) ? factor : 0;
if (factor !== 0) {
const { type } = formControl;
if ((type === 'radio') || (type === 'checkbox')) {
factor = formControl.checked ? factor : 0;
}
}
return factor;
}
function computeCurrentFee(rootNode, elmFee, baseFee) {
return Array
// array from `HTMLFormControlsCollection`
.from(rootNode.elements)
// calculate currrent fee from each form element's data
.reduce((currentFee, formControl) => {
return currentFee - (baseFee * getDevaluationFactor(formControl));
}, baseFee);
}
function updateCurrentValueAtBoundFeeContext(/*evt*/) {
const { rootNode, elmFee, baseFee } = this;
elmFee.value = computeCurrentFee(rootNode, elmFee, baseFee);
}
function displayCurrentValueAtBoundAgeContext(/*evt*/) {
const { elmRange, elmOutput } = this;
elmOutput.value = elmRange.value;
}
function initializeCurrentAgeDisplay(rootNode) {
const ageNode = rootNode.querySelector('[data-age-range]');
if (ageNode) {
const elmRange = ageNode.querySelector('input[type="range"]');
const elmOutput = ageNode.querySelector('output');
if (elmRange && elmOutput) {
const target = { elmRange, elmOutput };
const boundContextHandler =
displayCurrentValueAtBoundAgeContext.bind(target);
elmRange.addEventListener('input', boundContextHandler);
rootNode.addEventListener('reset', () =>
// decouple custom dom refresh from the system's one.
setTimeout(boundContextHandler, 0)
);
// display initial age value.
// // displayCurrentValueAtBoundAgeContext.call(target);
boundContextHandler();
}
}
}
function initializeMembershipFeeCalculator(rootNode) {
const DEFAULT_BASE_FEE = 10;
initializeCurrentAgeDisplay(rootNode);
const elmFeeValue = rootNode.querySelector('[data-fee-value]');
if (elmFeeValue) {
const baseFee = parseFloat(rootNode.dataset.baseFee);
const target = {
rootNode,
elmFee: elmFeeValue,
baseFee: Number.isFinite(baseFee) ? baseFee : DEFAULT_BASE_FEE,
};
const boundContextHandler =
updateCurrentValueAtBoundFeeContext.bind(target);
rootNode.addEventListener('input', boundContextHandler);
rootNode.addEventListener('reset', () =>
// decouple custom dom refresh from the system's one.
setTimeout(boundContextHandler, 0)
);
// compute initial fee value.
// // updateCurrentValueAtBoundFeeContext.call(target);
boundContextHandler();
rootNode.addEventListener('submit', evt => {
evt.preventDefault();
return false;
});
}
}
function main() {
document
.querySelectorAll('form[data-membership-fee-calculator]')
.forEach(initializeMembershipFeeCalculator);
}
main();
body, form {
margin: 0;
padding: 0;
}
form {
float: left;
width: 50%;
margin-top: -2px;
}
fieldset {
position: relative;
margin: 0 0 2px 0;
padding: 0 10px 2px 10px;
}
fieldset p {
margin: 1px 0 2px 0;
}
fieldset output {
color: #333;
font-weight: bolder;
}
label {
display: inline-block;
}
input[type="range"] {
width: 70%;
}
[data-age-range] output {
display: inline-block;
overflow: hidden;
max-width: 25%;
max-height: 1.2em;
position: relative;
top: 1px;
}
[type="reset"] {
position: absolute;
right: 4px;
top: -4px;
}
<form data-membership-fee-calculator data-base-fee="10">
<fieldset data-age-range>
<legend>
Age
</legend>
<input
type="range"
name="age" id="age"
value="50" min="1" max="100"
data-devaluation-range='{"0.1":{"min":60,"max":80}}'
/>
<output for="age">### not yet computed ###</output>
</fieldset>
<fieldset>
<p>
Do you have any long-term medical conditions
that can affect daily life?
</p>
<label>
<input
type="radio"
name="status"
value="yes"
data-devaluation-factor="0.4"
/>
<span class="label-copy">
Yes
</span>
</label>
<label>
<input type="radio" name="status" value="no" />
<span class="label-copy">
No
</span>
</label>
</fieldset>
<fieldset>
<p>
Are you currently employed?
</p>
<label>
<input
type="radio"
name="empstatus"
value="yes"
data-devaluation-factor="0.3"
/>
<span class="label-copy">
Yes
</span>
</label>
<label>
<input type="radio" name="empstatus" value="no" />
<span class="label-copy">
No
</span>
</label>
</fieldset>
<fieldset>
<legend>
Membership Fee
</legend>
<label>
<span class="label-copy">
Total Fee:
</span>
<output data-fee-value>### not yet computed ###</output>
</label>
<button type="reset">Restore base fee</button>
</fieldset>
</form>
<form data-membership-fee-calculator data-base-fee="20">
<fieldset data-age-range>
<legend>
Age
</legend>
<input
type="range"
name="age" id="age"
value="21" min="1" max="100"
data-devaluation-range=
'{"0.05":{"min":60,"max":69},"0.1":{"min":70,"max":79},"0.2":{"min":80,"max":120}}'
/>
<output for="age">### not yet computed ###</output>
</fieldset>
<fieldset>
<p>
Do you have any long-term medical conditions
that can affect daily life?
</p>
<label>
<input
type="radio"
name="status"
value="yes"
data-devaluation-factor="0.3"
/>
<span class="label-copy">
Yes
</span>
</label>
<label>
<input type="radio" name="status" value="no" />
<span class="label-copy">
No
</span>
</label>
</fieldset>
<fieldset>
<p>
Are you currently employed?
</p>
<label>
<input
type="radio"
name="empstatus"
value="yes"
data-devaluation-factor="0.3"
/>
<span class="label-copy">
Yes
</span>
</label>
<label>
<input type="radio" name="empstatus" value="no" />
<span class="label-copy">
No
</span>
</label>
</fieldset>
<fieldset>
<legend>
Membership Fee
</legend>
<label>
<span class="label-copy">
Total Fee:
</span>
<output data-fee-value>### not yet computed ###</output>
</label>
<button type="reset">Restore base fee</button>
</fieldset>
</form>