// getValues() collects user input and directs the flow of data
                function getValues() {
                  // grab inputs
                  let principal = document.getElementById('principal').value;
                  let term = document.getElementById('term').value;
                  let rate = document.getElementById('rate').value;

                  // validate inputs
                  principal = parseFloat(principal); // money can be float, but only 2 decimals
                  term = parseInt(term); // must be int - i.e. fixed # of months
                  rate = parseFloat(rate); // typically is a float

                  if (Number.isNaN(principal) || Number.isNaN(term) || Number.isNaN(rate)) {
                    // one of the inputs is wrong
                    let errorMsg = "Please only use numbers for:<br>";
                    if (Number.isNaN(principal)) errorMsg += "<br>Loan Amount";
                    if (Number.isNaN(term)) errorMsg += "<br>Term";
                    if (Number.isNaN(rate)) errorMsg += "<br>Interest Rate";

                    Swal.fire({
                      backdrop: false,
                      title: "Error!",
                      html: errorMsg
                    });

                    return;
                  }

                  principal = principal.toFixed(2); // if we call this before validating, it will always be a number

                  // give inputs a formatted placeholder to look nicer
                  document.getElementById('principal').value = '';
                  document.getElementById('term').value = '';
                  document.getElementById('rate').value = '';
                  
                  document.getElementById('principal').placeholder = formatCurrency(principal);
                  document.getElementById('term').placeholder = term;
                  document.getElementById('rate').placeholder = `${rate}%`;
                  

                  let paymentData = calculatePayments(principal, rate, term);
                  displayData(paymentData);
                }

                // calculatePayments() takes in data and generates values for each month
                // of the loan term as well as totals for the overview
                function calculatePayments(principal, rate, term) {
                  let totalMonthlyPayment = (principal * (rate / 1200)) / (1 - Math.pow((1 + (rate / 1200)), -term));
                  let totalCost = totalMonthlyPayment * term;
                  
                  let monthlyPaymentArr = [
                  // placeholder object for calculations
                  {
                    month: 0,
                    monthlyPayment: totalMonthlyPayment,
                    principalPayment: 0,
                    interestPayment: 0,
                    totalInterestPaid: 0,
                    balance: principal,
                    }];

                  for (let month = 1; month <= term; month++) {
                    let prevBalance = monthlyPaymentArr[month - 1].balance;
                    let prevTotalInterest = monthlyPaymentArr[month - 1].totalInterestPaid;

                    let interestPayment = prevBalance * (rate / 1200);
                    let principalPayment = totalMonthlyPayment - interestPayment;
                    let balance = prevBalance - principalPayment;
                    let totalInterest = prevTotalInterest + interestPayment;

                    if (balance < 0) balance = 0;

                    monthlyPaymentArr.push({
                      month: month,
                      monthlyPayment: totalMonthlyPayment,
                      principalPayment: principalPayment,
                      interestPayment: interestPayment,
                      totalInterestPaid: totalInterest,
                      balance: balance
                      });
                  }
                  
                  // remove placeholder object
                  monthlyPaymentArr.shift();


                  // totals for displayTotals()
                  let totals = {
                    monthlyPayments: totalMonthlyPayment,
                    totalPrincipal: principal,
                    totalInterest: totalCost - principal,
                    totalCost: totalCost
                  }

                  return {
                    totals: totals,
                    payments: monthlyPaymentArr
                  }
                }

                // separates the data and sends it to each view
                function displayData(data) {
                  // display totals to provide a summary
                  displayTotals(data.totals);

                  // display each month of payments in a table
                  displayPayments(data.payments);
                }

                // displayTotals() presents the overview at the top of the page
                function displayTotals(totals) {
                  // get stats
                  let totalMonthly = totals.monthlyPayments;
                  let totalPrincipal = totals.totalPrincipal;
                  let totalInterest = totals.totalInterest;
                  let totalCost = totals.totalCost;

                  // grab template
                  let statsTemplate = document.getElementById('totals-template');
                  let container = document.getElementById('totals-container')

                  // clear container
                  container.innerHTML = '';

                  // create a copy
                  let statsNode = document.importNode(statsTemplate.content, true);

                  // put values in the copy
                  statsNode.getElementById('total-principal').textContent = formatCurrency(totalPrincipal);
                  statsNode.getElementById('total-interest').textContent = formatCurrency(totalInterest);
                  statsNode.getElementById('total-cost').textContent = formatCurrency(totalCost);
                  statsNode.getElementById('total-monthly').textContent = formatCurrency(totalMonthly);

                  // attach it to the page
                  container.appendChild(statsNode);
                }

                // displayPayments() presents a table of each month of payments
                function displayPayments(payments) {
                  // grab table row template
                  let rowTemplate = document.getElementById('payment-row-template');
                  let paymentTable = document.getElementById('payment-table');
                  // make the table visible now that we have data to show
                  document.querySelector('table').classList.remove('d-none');
                  // clear the table in case it was already populated
                  paymentTable.innerHTML = '';

                  // generate a table row for each month of data, matching
                  // the object keys to 'data-key` in the template
                  payments.forEach(payment => {
                    let rowNode = document.importNode(rowTemplate.content, true);

                    Object.keys(payment).forEach(key => {
                      let value = payment[key];
                      if (key != 'month') value = formatCurrency(value);
                      rowNode.querySelector(`[data-key="${key}"]`).textContent = value;
                    });

                    paymentTable.appendChild(rowNode);
                  });
                }

                // formatCurrency() takes a number and returns a string formatted as money
                // which is used for convenience in the display functions
                function formatCurrency(value) {
                  let formatter = new Intl.NumberFormat(undefined, { style: 'currency', currency: 'USD' });
                  return formatter.format(Number(value));
                }
              
            

The Code

The code is broken into functions to separate concerns in the MVC model.

getValues acts as our controller for the function - it runs when the user confirms their input with the "calculate" button and collects their inputs, validates that we received suitable data, and sends it to our Model for calculation. Once it has received the caluclated data, the model hands that data off to a View function.

calculateValues receives user input of principal, rate, and term according to their mortgage details. Then, we use a for loop to calculate each month of payments based upon the length of the loan's term and pushes an object with that month of information into an array. Finally, calculateValues returns an object with property totals that contains information for our loan's overview and property payments for our array containing each month of payment dta.

displayData receives the object that was returned from calculatePayments and sends the totals off to displayTotals and the array of monthly payments to displayPayments. displayTotals simply accesses each piece of information from its argument and uses a template to place it onto the page in our overview window. displayData uses a template for each row of our table and loops through our array of monthly payments, then matches the object's property keys to the templates data-key attribute and attaches that row to our data table.