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>
1 Answer 1
A few notes:
- The function
clearContents(tag)
can be simplified totag.innerHTML = ''
- You probably shouldn't be naming your function
main
, as it doesn't describe what it does. - You should use
const
andlet
when they are more appropriate thanvar
. - In your HTML, all of your
<input>
's aretype='text'
, at least#year-count
should betype='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.