The Code
// Entry Function
// IDs needed
// id="amortizationTemplate
// id="btnCalculate"
// id="interestRateInput"
// id="loanTermInput"
// id="loanAmountInput"
// Entry Function
// Initial Function that happens when we press Calculate button
function getValues()
{
// Get inputs, store in object
let inputsObject =
{
loanAmount: parseInt(document.getElementById('loanAmountInput').value),
loanTerm: parseInt(document.getElementById('loanTermInput').value),
interestRate: parseInt(document.getElementById('interestRateInput').value)
}
if (isNaN(inputsObject.loanAmount) || isNaN(inputsObject.loanTerm) || isNaN(inputsObject.interestRate))
{
Swal.fire
({
icon: 'error',
title: 'Whoops',
text: 'Please enter a valid number.'
})
return;
}
// Calculate Total Monthly Payment, assign to variable
let totalMonthlyPayment = totalMonthly(inputsObject);
// Calculate all of the necessary
let amortizationDataArray = balanceInterestPrin(inputsObject, totalMonthlyPayment);
displayCurrentMonth(amortizationDataArray, totalMonthlyPayment, inputsObject);
displayAmortization(amortizationDataArray, totalMonthlyPayment);
}
// Logic Functions
function totalMonthly(values)
{
// Calculate total monthly payment.
let totalMonthlyPayment = values.loanAmount * (values.interestRate / 1200) / (1 - (1 + values.interestRate / 1200) ** -values.loanTerm)
return totalMonthlyPayment;
}
function balanceInterestPrin(values, totalMonthlyPayment)
{
let loanTerm = values.loanTerm;
let month = 1;
let balance = (values.loanAmount);
let interest = 0;
let totalInterest = 0;
// Storage Variables
let amortizationArray = [];
let amortizationObject = {};
while (month <= loanTerm)
{
amortizationObject =
{
termMth: month,
bomBalance: balance,
get interest() { return this.bomBalance * (values.interestRate / 1200) },
get principal() { return totalMonthlyPayment - this.interest },
get eomBalance() { return this.bomBalance - this.principal },
}
interest = amortizationObject.interest;
balance = amortizationObject.eomBalance;
totalInterest += interest;
amortizationObject.totalInterest = totalInterest;
amortizationArray.push(amortizationObject)
month = month + 1;
}
return amortizationArray;
}
// Display onto Page Function
function displayCurrentMonth(amortizationArray, totalMonthlyPayment, inputsObject)
{
let firstMonthTMP = document.getElementById('firstMonthTMP');
let firstMonthPrincipal = document.getElementById('firstMonthPrincipal');
let firstMonthInterest = document.getElementById('firstMonthInterest');
let firstMonthTotalCost = document.getElementById('firstMonthTotalCost')
let totalInterest = (amortizationArray[inputsObject.loanTerm - 1].totalInterest);
firstMonthTMP.textContent = totalMonthlyPayment.toLocaleString('en-US', { style: 'currency', currency: 'USD' });
firstMonthPrincipal.textContent = amortizationArray[0].bomBalance.toLocaleString('en-US', { style: 'currency', currency: 'USD' });
firstMonthInterest.textContent = amortizationArray[inputsObject.loanTerm - 1].totalInterest.toLocaleString('en-US', { style: 'currency', currency: 'USD' });
firstMonthTotalCost.textContent = Number(inputsObject.loanAmount + totalInterest).toLocaleString('en-US', { style: 'currency', currency: 'USD' });
}
function displayAmortization(amortizationArray, totalMonthlyPayment)
{
let tableBody = document.getElementById('tableBody');
tableBody.innerHTML = '';
let bottomHalf = document.getElementById('bottomHalf')
bottomHalf.classList.remove('invisible');
for (i = 0; i < amortizationArray.length; i++)
{
let amortizationRow = document.importNode(amortizationTemplate.content, true);
let currentMth = amortizationArray[i];
let tableCells = amortizationRow.querySelectorAll("td");
tableCells[0].textContent = currentMth.termMth;
tableCells[1].textContent = Number(totalMonthlyPayment).toLocaleString('en-US', { style: 'currency', currency: 'USD' });
tableCells[2].textContent = Number(currentMth.principal).toLocaleString('en-US', { style: 'currency', currency: 'USD' });
tableCells[3].textContent = Number(currentMth.interest).toLocaleString('en-US', { style: 'currency', currency: 'USD' });
tableCells[4].textContent = currentMth.totalInterest.toLocaleString('en-US', { style: 'currency', currency: 'USD' });
tableCells[5].textContent = Math.abs(currentMth.eomBalance).toLocaleString('en-US', { style: 'currency', currency: 'USD' });
tableBody.appendChild(amortizationRow);
}
}
The code is structured in five functions. Called functions will be detailed in their own sections.
Click a headline()
to scroll to that section of the code. And click
the function name within the code to scroll to that write up section.
getValues()
getValues()
is our Entry Point function. First and foremost it does what it says on the
label. I create a variable and name it inputsObject
and assign to
it
three properties -
loanAmount:
loanTerm:
interestRate:
These properties are each assigned the value of an input from the page while also being converted
into an integer. This is done in a few parts. It starts with parseInt()
which is
a function that takes a string passed into it (everything grabbed from the document comes in
as a string) and
converts it into an integer. Then document.getElementById('')
is used to communicate
with our HTML page(document) and then grab an assigned ID. We conclude with a .value
to
grab the
user input (or assigned) value of the input field.
Sometimes however, a user does a thing you don't want them to do, so we have to check to make sure
that what they've input is a number. This brings us to our if statement.
if(conditons){code}
is the syntax for an if statement. We give it conditions within the
parentheses and code to execute in the curly brackets. For this one we give three possible
conditions. They all work the same and are separated by the logical OR operator(||). Let's go over
the syntax.
isNaN()
(is Not a Number) is a function that takes an argument and
determines if it is a number. We
pass in each of our inputs into one of these isNaN functions and check them. If either of them is
not
a number we give the user a Sweet Alert to keep them from being confused.
After all of that, we call in a few more functions and assign a few more variables to do the logic
and display of our amortization data. First we create the totalMonthlyPayment
variable
and assign to that the returned value from the
totalMonthly()
function
which has the
inputsObject
passed into it from above. Another variable,
amortizationDataArray
, is created. Assigned to it is the output returned from our
major calculation function balanceInterestPrin
which has the variables
inputsObject
and totalMonthlyPayment
passed into it. Much the same happens
with the next two called functions: displayCurrentMonth()
and
displayAmortization()
. These are our display functions. They are both passed the
recently created array of amortization data as well as some of the other data to fill out the table
and first month card. Once those have been called and completed, the program is done and the user
should have acces to all the information necessary.
totalMonthly();
totalMonthly(values)
is our foundational logic function. Without this we wouldn't have
the beginning value necessary to do the rest of our math. We take in one parameter,
values
, which is our object of inputs in this case. We then use the inputs to plug into
the Total Monthly Payment formula to determine what the user will pay back over their loan term.
Once the math
calculates, we send it back with a return of the variable totalMonthlyPayment.
balanceInterestPrin()
balanceInterestPrin(values,totalMonthlyPayment)
is the proverbial house built upon
totalMonthly
's foundation.
First, we declare the variables necessary to do the calculations for the different aspects of the
amortization schedule. These are:
loanTerm
This is our loan term, which is pulled directly from our inputsObject passed in
through
values.loanTerm
.
month
This is our month counter. It assigned the value of 1 because that is the beginning of the loan
term, and we will
use it for our while loop later.
balance
Balance is our total loan amount. This is assigned through values again and set to
values.loanAmount
.
interest
This is our interest variable. It will be used to store the interest payments outside of the while
loop. As well, later it is used to calculate total interest.
totalInterest
This is our totalInterest variable, it is used to hold the value of interest summed up over the term
and then pushed into the amortization object further on.
We have two more variables to declare here. These will be two storage variables that our function will push data into.
amortizationArray
This empty array will be where we store every object created by the while loop for each month of the
loan term.
amortizationObject
This empty object will be where we store all the data necessary to be displayed for amortization. It
was necessary to have this declared before the information was put into it for the way that we want
to calculate later.
The while loop is our assembly line, a small scale factory whose job is to calculate the different
aspects of the amortization schedule. A while loop executes over and over again as long as its
condition is true. For this one one, we used the condition (month <= loanTerm)
. This
says that so long as it is true that the variable month
is less than or equal to the
variable loanTerm
, keep executing. We use <=
to ensure that we do this for
every month of the loan term, including the last one.
Here is my favorite part, the amortizationObject. This object was initialized before and outside of the while loop for the following reasons:
- Creating it outside the while loop means we're not creating a new variable each time that we run it.
- Most importantly, I wanted the calculation to all be done within the object itself. There are perhaps better ways to do this but I wanted to experiment with having the majority of this contained within the object.
We can break this down further however. Let's take a look at each individual aspect of the
object.
termMth
This is the property used to store the number of the month that the object is gathering the
information for. This variable is assigned the value of month
, from outside of the
while loop.
bomBalance
bomBalance, or Beginning of Month Balance, pulls in a value from the balance
variable
outside of the while loop. This represents the value at, not shockingly, the beginning of the month.
bomBalance
is used to calculate the next three properties created in this object.
interest
Here is where the fun truly begins. We use the get syntax, or getter as it's known, to bind the
object's property to a function. This allows us to reference the other object's properties within
the object itself! With interest, we reference the bomBalance
, times it by the interest
rate and get
the interest part of the monthly payment. This value is returned and stored, then the code doesn't
run until it is called upon again.
principal
This is the exact same as interest, but now with the getter we are able to reference the interest
above. We return that math and that value is given to the property. This gets us the principal
portion of our total monthly payment.
eomBalance
Third verse same as the first. This one represents the end of month balance and further down we use
it to set the beginning of the month balance for the next iteration of the while loop.
The rest of the while loop is fairly simple. interest
and balance
update
the variables with what is inside the amortizationObject
. Then,
totalInterest
adds the value of interest to itself after interest is updated from the
object. We then add total interest paid so far to each instance of the amortization object. We push
that whole object into our amortizationArray
and then increase month by one. After the
while loop runs through the entire loan term, we return our amortizationArray
to be
used for all of our display.
displayCurrentMonth()
Here is the start of our display functions. displayCurrentMonth()
takes in a new
parameter and two familiar ones. amortizationArray
is the value passed in after we
gather that array from balanceInterestPrin
in getValues()
. We also pass
into this one our totalMonthlyPayment
which holds the value from our
totalMonthly()
function. Finally we pass in our inputsObject
which holds
our initial values, parsed into integers.
We have a number of variables in this section. They are:
firstMonthTMP
This variable represents the 'firstMonthTMP' element of our document.
firstMonthPrincipal
This variable represents the 'firstMonthPrincipal' element of our document.
firstMonthInterest
This variable represents the 'firstMonthInterest' element of our document.
firstMonthTotalCost
This variable represents the 'firstMonthTotalCost' element of our document.
.textContent
to fill in the text. The .toLocaleString
and its parameters are
there to stylize the numbers as currency, specifically USD with the US english language. We use
Number()
to construct a number based on the information passed into it.
displayAmortization()
Last but not least, we come to our major display function. displayAmortization()
is
what creates our entire table on the page. We pass in the parameters of
amortizationArray
and totalMonthlyPayment
.
tableBody
This variable represents the element of our document with the id of 'tableBody'.
We then clear/reset whatever HTML was inside of the tableBody
with
.innerHTML = ''
. This is just in case there is anything in there, we want a fresh start
for our amortization data.
bottomHalf
This variable represents the element of our document with the id of 'bottomHalf'. This is everything
below the parameters. We use .classList
to
access a list of all the classes associated with this element. .remove
then removes the
"invisible" class to make this element visible.
The major part of this function is our for loop. This will build each row of our amortization
schedule table. First, we assign amortizationRow
with an imported node which is our
amortization template. This was created on the html document. Then we assign currentMth
the value of our amortizationArray[i]
. This will be what grabs each object in our
array.
Next, we declare tableCells
and assign it the value of
amortizationRow.querySelectorAll("td")
. The .querySelectorAll("td")
grabs
every element matching the selector and returns it as a static NodeList. This is similar to an array
and is why we can push data into each of the <td>
with bracket notation below.
Next comes our table assembly. Each tableCells[]
entries is given a different index
value (0-5) of
the array of <td>
. We then used .textContent
to fill that
<td>
with an assigned value. We do this for the term month, the total monthly
payment,
the principal payment, the interest payment, the total interest paid so far, and the remaining
balance on the loan.
The last step is to put that back into our table with .appendChild
. This happens
every loop, so we end up with a populated table that is viewable on the page. Our calculations are
complete, our table is viewable, and our user can see how much they'll be paying on their loan.