5

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>
Peter Seliger
  • 11,747
  • 3
  • 28
  • 37
jooookra
  • 53
  • 5

2 Answers2

1

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>
Peter Seliger
  • 11,747
  • 3
  • 28
  • 37
hackape
  • 18,643
  • 2
  • 29
  • 57
  • Don’t change discount, instead change last line to `return 10 - discount` – hackape Aug 30 '21 at 10:57
  • Thank you! I got it to work. Also to note you used 'empstatus-no' and I believe it's meant to be 'empstatus-yes'? – jooookra Aug 30 '21 at 11:02
  • I’m kinda confused. How come the “standard rate is 10”? By “rate” I presume it means percentage and percentage added up to 1. Or do you mean the “standard price is 10” – hackape Aug 30 '21 at 11:03
  • Yes, you are right. I meant standard price is $10 per month! This code is meant to reflect a membership calculator. Sorry for the confusion! :) – jooookra Aug 30 '21 at 11:06
  • You said “Unemployed = You get a 30% discount” `'empstatus-no'` means unemployed. – hackape Aug 30 '21 at 11:07
  • Well then you should `return 10 * (1 - discount)` to get final correct price. – hackape Aug 30 '21 at 11:08
1

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

  1. Being forced to work with a clean HTML markup/structure as DOM and/or data base.
  2. Decoupling (validation) code from very (business) case specific data like ... not depending ​anymore ...
    • on very specific DOM-element queries,
    • on "baked in" data for e.g. validation and edge case handling.
  3. On long term base easier to maintain (in terms of changed business data) and to adapt to e.g. new discount options.

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>
Peter Seliger
  • 11,747
  • 3
  • 28
  • 37
  • 1
    Good demo of web platform’s capabilities. These days I’m so spoiled by UI libs that I feel so alienated to these APIs. Good refresher! Thanks. – hackape Aug 31 '21 at 01:28