The Code
Click the headers below to hide/show the corresponding code excerpts.
function calculateFormLoan(formId, tbodyId, monthlyId, principalId, interestId, costId) {
let formInput = getFormInput(formId);
if (!formInput) {
document.getElementById(tbodyId).innerHTML = '';
} else {
let monthlyPct = formInput.interestRate / 1200;
let monthlyPmt = getMonthlyPayment(formInput.loanAmount, formInput.termMonths, monthlyPct);
let loanData = {
balance: formInput.loanAmount,
monthNumber: 1,
monthlyRate: monthlyPct,
monthlyPayment: monthlyPmt,
totalPrincipal: 0,
totalInterest: 0
};
addNewPayments(tbodyId, loanData);
outputSummary(loanData, monthlyId, principalId, interestId, costId);
}
}
This function is essentially the entry point for the app's primary operation, which is to calculate and display loan payment data based on the user's input. It is called when the user clicks the Calculate button on the app's form.
When called, this function first retrieves the form data from the form possessing the passed-in ID. If this form data is determined to be invalid, the app will display a SweetAlert message instructing the user on how to fix their form inputs; execution will then end.
If the form data is found to be valid, the app will perform calculations related to the loan. The monthly interest rate will be calculated based on the annual rate in the user's form input. Then the monthly payment amount will be calculated according to the formula for ammortized loans.
Next, a detailed monthly payment schedule will be written to a table body on the app page specified by a passed-in ID. Finally, subtotals and the calculated monthly payment will be printed to elements on the app page specified by passed-in IDs.
function getFormInput(formId) {
let loanForm = document.getElementById(formId);
let formData = new FormData(loanForm);
let formInput = Object.fromEntries(formData);
formInput.loanAmount = parseFloat(formInput.loanAmount);
if (!isValidLoanAmount(formInput.loanAmount)) {
formInput = null;
} else {
formInput.termMonths = parseInt(formInput.termMonths);
if (!isValidMonthCount(formInput.termMonths)) {
formInput = null;
} else {
formInput.interestRate = parseFloat(formInput.interestRate);
if (!isValidInterestRate(formInput.interestRate)) {
formInput = null;
}
}
}
return formInput;
}
This function is used to retrieve the user input for a loan's parameters (amount, term, and interest rate) from a form specified by the passed-in ID. It is called by the function that fires when the user clicks the Calculate button on the app form.
After retrieving the three inputs from the form and inserting them as properties into a single object, the inputs are validated via separate functions. If any input fails validation, the function returns a null value; otherwise, the created object will be returned.
function isValidLoanAmount(value) {
if (Number.isNaN(value) || value <= 0) {
Swal.fire({
icon: 'error',
title: 'Invalid Loan Amount',
text: `Please change Loan Amount to a number greater than 0.`,
});
return false;
}
return true;
}
This function is used to validate user input for loan amounts. It is called by the function that retrieves the user's input from the app's form.
If the user input is not a number above zero, a value of false is returned; otherwise, a value of true is returned. In the event of a false return value, a SweetAlert error message is fired in the app beforehand. The message instructs the user on valid input for a loan amount.
function isValidMonthCount(value) {
if (!Number.isInteger(value) || value < 1) {
Swal.fire({
icon: 'error',
title: 'Invalid Month Count',
text: `Please change Term (Months) to an integer greater than 0.`,
});
return false;
}
return true;
}
This function is used to validate user input for the duration of a loan (in months). It is called by the function that retrieves the user's input from the app's form.
If the input is not an integer (i.e. a whole number) above zero, a value of false is returned; otherwise, a value of true is returned. In the event of a false return value, a SweetAlert error message is fired in the app beforehand. The message instructs the user on valid input for a loan duration.
function isValidInterestRate(value) {
if (Number.isNaN(value) || value < 0) {
Swal.fire({
icon: 'error',
title: 'Invalid Interest Rate',
text: `Please change Interest Rate to a number greater than or equal to 0.`,
});
return false;
}
return true;
}
This function is used to validate user input for the interest rate of a loan (as a percentage). It is called by the function that retrieves the user's input from the app's form.
If the user input is not a number above zero, a value of false is returned; otherwise, a value of true is returned. In the event of a false return value, a SweetAlert error message is fired in the app beforehand. The message instructs the user on valid input for a loan interest rate.
function getMonthlyPayment(balance, monthsLeft, monthlyRate) {
if (monthlyRate == 0) return balance / monthsLeft;
return (balance * monthlyRate / (1 - Math.pow(1 + monthlyRate, -monthsLeft)));
}
This function calculates the monthly payment amount for a loan, given the balance, the number of payment months, and the monthly rate of the loan. It is called by the function that gets fired when the user fills the app form with valid input and clicks the form's Calculate button.
The payment amount is constant and calculated such that the loan amount and all interest will be paid off exactly after the number of months has passed. When there is no interest rate, the payment is simply the loan amount divided by the number of months. Otherwise, it is calculated according to the formula for monthly amortized loan payments.
function addNewPayments(tbodyId, loanData) {
const formatter = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
});
let tbody = document.getElementById(tbodyId);
tbody.innerHTML = '';
let newRow, newCell;
let monthlyPayment, interestPayment, principalPayment;
while (loanData.balance > 0) {
newRow = document.createElement('tr');
// Month
newCell = document.createElement('td');
newCell.innerText = loanData.monthNumber++;
newRow.appendChild(newCell);
interestPayment = loanData.balance * loanData.monthlyRate;
monthlyPayment = Math.min(loanData.monthlyPayment, loanData.balance + interestPayment);
principalPayment = monthlyPayment - interestPayment;
// Payment
newCell = document.createElement('td');
newCell.innerText = formatter.format(monthlyPayment);
newRow.appendChild(newCell);
// Principal
newCell = document.createElement('td');
newCell.innerText = formatter.format(principalPayment);
newRow.appendChild(newCell);
// Interest
newCell = document.createElement('td');
newCell.innerText = formatter.format(interestPayment);
newRow.appendChild(newCell);
// Total Interest
loanData.totalInterest += interestPayment;
newCell = document.createElement('td');
newCell.innerText = formatter.format(loanData.totalInterest);
newRow.appendChild(newCell);
// Balance
loanData.balance -= principalPayment;
newCell = document.createElement('td');
newCell.innerText = formatter.format(loanData.balance);
newRow.appendChild(newCell);
// Total Principal -- Keep hidden
loanData.totalPrincipal += principalPayment;
newCell = document.createElement('td');
newCell.classList.add('d-none');
newCell.innerText = formatter.format(loanData.totalPrincipal);
newRow.appendChild(newCell);
tbody.appendChild(newRow);
}
}
This function is used to populate a specified table body with rows containing details on the monthly payments of a loan. It is called by the function fired when the user clicks the Calculate button on the app form.
When called, this function clears the table body of its contents and
adds a new row to it for every monthly payment needed to pay off the
full balance of the loan. The data in the table rows is copied from
the passed-in loanData object containing properties
representing the balance, starting month number, monthly payment
amount, monthly interest rate, total principal, and total interest.
The exact number of rows created is determined by updating the
loanData object after adding each row: specifically,
the balance is decreased with every row until it reaches zero; no
more rows are created at that point. Other properties - like the
starting month number, total principal, and total interest - are
updated with each new row as well.
function outputSummary(loanData, monthlyId, principalId, interestId, costId) {
const formatter = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
});
// Monthly payment
let monthlyPayment = document.getElementById(monthlyId);
monthlyPayment.innerText = formatter.format(loanData.monthlyPayment);
// Total principal
let totalPrincipal = document.getElementById(principalId);
totalPrincipal.innerText = formatter.format(loanData.totalPrincipal);
// Total interest
let totalInterest = document.getElementById(interestId);
totalInterest.innerText = formatter.format(loanData.totalInterest);
// Total cost
let totalCost = document.getElementById(costId);
totalCost.innerText = formatter.format(
loanData.totalPrincipal + loanData.totalInterest
);
}
This function is used to display a summary of a loan's payment schedule to the app. It is called by the function that fires when the user fills the app form with valid data and clicks the Calculate button.
The summary printed is based on data from a passed-in
loanData object. Property values from this object are
printed into elements on the app page specified by passed-in element
IDs. The specific values printed out are the monthly payment amount,
total principal paid on the loan (which is the original loan amount),
the total interest paid on top off the principal, and the total
cost paid for the loan (i.e. the sum of the principal and interest).