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.

Next, we do a very typical process of assigning value to a document element's text content. we use .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.