6
\$\begingroup\$

The code below is a quick, working example of a compound interest table I wrote. My concern is mainly about separating the application data/logic from the UI code. I didn't use any frameworks on purpose, just because this is an exercise.

Here's the code

(function() {
  "use strict";

  function clearContents(tag) {
    while (tag.firstChild) {
      tag.removeChild(tag.firstChild);
    }
  }


  function addValueToRow(rowTag, value) {
    var newValue = document.createElement("td");
    newValue.appendChild(document.createTextNode(value));
    rowTag.appendChild(newValue);
  }


  function formatMoney(value) {
    return value.toFixed(2).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
  }


  function main() {
    var inputBoxes = document.getElementsByTagName("input");
    var table = document.getElementById("results");
    var monthlyContributionInput = document.getElementById("monthly-contribution");
    var numberOfYearsInput = document.getElementById("year-count");
    var annualInterestRateInput = document.getElementById("annual-interest-rate");
    var updateTable = function(event) {
      var rowcount = Number(numberOfYearsInput.value);
      var monthlyContribution = Number(monthlyContributionInput.value);
      var annualInterestRate = Number(annualInterestRateInput.value) / 100.0;
      var interestFactor = 1 + annualInterestRate / 12.0;
      var balance = 0;
      var totalDeposit = 0;
      var monthCount = 0;
      var yearInterest = 0;
      var perviousYearInterest = 0;
      clearContents(table);
      for (var row = 1; row <= rowcount; row += 1) {
        var newRow = document.createElement("tr");
        monthCount = 12 * row;
        totalDeposit = 12.0 * monthlyContribution * row;
        balance = monthlyContribution * (
          (Math.pow(interestFactor, monthCount + 1) - 1) /
          (interestFactor - 1) - 1);
        perviousYearInterest = yearInterest;
        yearInterest = balance - totalDeposit;
        addValueToRow(newRow, "" + row);
        addValueToRow(newRow, formatMoney(monthlyContribution * 12.0));
        addValueToRow(newRow, formatMoney(yearInterest - perviousYearInterest));
        addValueToRow(newRow, formatMoney(totalDeposit));
        addValueToRow(newRow, formatMoney(balance - totalDeposit));
        addValueToRow(newRow, formatMoney(balance));
        table.appendChild(newRow);
      }
    };

    for (var i = 0; i < inputBoxes.length; i += 1) {
      inputBoxes[i].oninput = updateTable;
    }
  }

  window.addEventListener("load", main, false);

})();
:root {
  --system-font-stack: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
}

body {
  font-family: var(--system-font-stack);
  margin: 0;
  padding: 1.5rem;
}

#user-inputs>div {
  display: flex;
  flex-direction: column;
  margin-bottom: 0.75rem;
}

#user-inputs label {
  margin-bottom: 0.25rem;
}

#user-inputs input {
  width: 15rem;
}

#interest-table,
#interest-table thead,
#interest-table tr {
  width: 100%;
}

#interest-table>thead>tr>td {
  font-weight: bold;
}

#interest-table td {
  font-size: 12px;
}
<!DOCTYPE html>
<html>

<head>
  <title>Compound Interest Calculator</title>
</head>

<body>
  <h1>Compound Interest Calculactor</h1>
  <div id="user-inputs">
    <div>
      <label for="monthly-contribution">Monthly Contribution</label>
      <input type="text" id="monthly-contribution" />
    </div>
    <div>
      <label for="annual-interest-rate">Annual Interest Rate (%)</label>
      <input type="text" id="annual-interest-rate" />
    </div>
    <div>
      <label for="year-count">Number of Years</label>
      <input type="text" id="year-count" />
    </div>
    <table id="interest-table">
      <thead>
        <tr>
          <td>Year</td>
          <td>Year Deposit</td>
          <td>Year Interest</td>
          <td>Total Deposit</td>
          <td>Total Interest</td>
          <td>Balance</td>
        </tr>
      </thead>
      <tbody id="results">
      </tbody>
    </table>
    <script src="_scripts/compound-interest.js"></script>
</body>

</html>

\$\endgroup\$

1 Answer 1

6
\$\begingroup\$

A few notes:

  • The function clearContents(tag) can be simplified to tag.innerHTML = ''
  • You probably shouldn't be naming your function main, as it doesn't describe what it does.
  • You should use const and let when they are more appropriate than var.
  • In your HTML, all of your <input>'s are type='text', at least #year-count should be type='number'
  • In perviousYearInterest you spelled "previous" wrong, unless you meant "pervious".
  • Try scoping your variables to an object when they have a repeated part of their names.

For example, take this:

monthlyContributionInput = ...
numberOfYearsInput = ...
annualInterestRateInput = ...

It becomes:

inputs = {
  monthlyContribution: ...,
  numberOfYears: ...,
  annualInterestRate: ...
}
  • Instead of doing .appendChild .createElement and .removeChild, why not just use .innerHTML

Here's the function main but using .innerHTML to render all of the HTML elements at once, in what I would consider a much more readable manner.

function main() {
  // turn input boxes into an Array
  const inputBoxes = Array.from(document.getElementsByTagName("input"));
  const table = document.getElementById("results");

  // store the inputs in an object to group them together
  const inputs = {
    monthlyContribution: document.getElementById("monthly-contribution"),
    numberOfYears: document.getElementById("year-count"),
    annualInterestRate: document.getElementById("annual-interest-rate")
  };

  const updateTable = function(event) {
    const years = Number(numberOfYearsInput.value);

    const monthlyContribution = Number(inputs.monthlyContribution.value);
    const yearlyContribution = monthlyContribution * 12;
    // No need to recalculate yearly contribution every loop

    const annualInterestRate = Number(annualInterestRateInput.value) / 100.0;
    const interestFactor = 1 + annualInterestRate / 12.0;
    let yearInterest = 0;
    let previousYearInterest = 0;

    // no need to clear table since we're replacing all of the `.innerHTML`

    table.innerHTML = [...Array(years)]
      .map(year => {
        ++year;
        const totalDeposit = yearlyContribution * year;
        const balance = monthlyContribution * ((Math.pow(interestFactor, monthCount + 1) - 1) / (interestFactor - 1) - 1);

        previousYearInterest = yearInterest;
        yearInterest = balance - totalDeposit;

        return `
          <tr>
            <td>${year}</td>
            <td>${formatMoney(yearlyContribution)}</td>
            <td>${formatMoney(yearInterest - previousYearInterest)}</td>
            <td>${formatMoney(totalDeposit)}</td>
            <td>${formatMoney(yearInterest)}</td>
            <td>${formatMoney(balance)}</td>
          </tr>
        `;
      }).join('')
  };

  // iterate over input boxes, instead of for loop
  inputBoxes.forEach(input => input.oninput = updateTable)
}

This also makes it somewhat easier to move over to a framework that uses JSX if you ever wish to, because this style is very similar to JSX.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.