Exercise 2
Using data tables - Exercise 2
Exercise 2 - Using data tables and localStorage
The idea for this exercise is to create a table that can hold data for calculating a loan payment. It would be a relatively easy task to make some input text boxes for the annual interest rate, the months for the loan and the amount borrowed. Then, when a button is clicked, the monthly payment for that loan could be displayed.
We could choose to display these numbers in a HTML table. Where localStorage comes into play, is that we could store these numbers. This would make it so that if the numbers would still be there if a user is on the same computer using the same web browser. So, the user would not have to reenter the same set of numbers, and would not have to memorize the results either.
Creating a simple prototype
Let’s start with the same kind of template we used in Exercise 1. Using that template, here is the file exercise2.html:
<!DOCTYPE html>
<html>
<head>
<script>
addEventListener('DOMContentLoaded', init);
function init() {
// code here
}
</script>
</head>
<body>
</body>
</html>
Now, let’s create a simple prototype by just seeing if we can do the monthly payment calculation. Before we do that, let’s go to an online monthly payment calculator. Here is one at the following site: Calculator.net
As you can see, if you were to borrow $37,123.99 for a 5 year (60 month) loan at 5.5% annual interest rate, the monthly payment would be $709.11
So, for a prototype you could start by seeing if you can duplicate this calculation. Here is some simple code that just tests if you can do the monthly payment calculation.
<!DOCTYPE html>
<html>
<head>
<script>
addEventListener('DOMContentLoaded', init);
function init() {
const rate = 0.055; // divide by 12 to get monthly interest rate
const principal = 37123.99;
const n = 60; // number of months
let num = principal*rate/12*(1 + rate/12)**n;
let denom = (1 + rate/12)**n - 1;
let pmt = num/denom;
console.log(pmt);
}
</script>
</head>
<body>
</body>
</html>
The new lines are lines 8-14. Here is the formula for calculating the payment per period for a loan:
\$"pmt" = A*r*(1 + r)^n/((1 + r)^n - 1)\$
\$"pmt " = " payment per period."\$
\$"where " A = "amount borrowed."\$
\$r = " interest rate per period."\$
\$n = " number of periods."\$
Line 8 sets the annual interest rate. On lines 11 and 12, that rate is divided by 12 to get the monthly interest rate. Line 9 sets the amount borrowed (the principal). Line 10 sets n to be the number of months (number of periods). Line 11 calculates the numerator (num), and line 12 calculates the denominator (denom). This makes the expression on line 13 simpler to write. That helps to avoid possible syntax errors. Note that the (**) double-asterisk is used to raise a number to some power.
If we load exercise2.html into the browser and open up DevTools, this is what we see in the console:
As you can see, the monthly payment of 709.11 has been calculated. Now that we know that we can make the important calculations, let’s work on the HTML markup in the <body> to make a HTML table that we can use to hold our numbers and calculations. Let’s assume that we will create three table rows. The first table row will be the headers for each column, and the next two table rows will hold rows of loan payment numbers. This will allow our simple app to make a comparison between two loan payments. That will allow looking at the effect of changing one or more of the parameters of the loan.
Here is the new version of exercise2.html that adds some of the markup for the HTML table.
<!DOCTYPE html>
<html>
<head>
<script>
addEventListener('DOMContentLoaded', init);
function init() {
const rate = 0.055; // divide by 12 to get monthly interest rate
const principal = 37123.99;
const n = 60; // number of months
let num = principal*rate/12*(1 + rate/12)**n;
let denom = (1 + rate/12)**n - 1;
let pmt = num/denom;
console.log(pmt);
}
</script>
<style>
th {
word-wrap: break-word;
}
</style>
</head>
<body>
<table border="1" width="500px">
<thead>
<tr>
<th>Annual Interest Rate</th>
<th>Months for Loan</th>
<th>Amount Borrowed</th>
<th>Monthly Payment</th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
</body>
</html>
With this added markup, here is what the page looks like in the browser:
Note that the last two rows of the table don’t contain anything yet, so they have almost no height.
Lines 24-47 define a HTML <table> element. On line 24, we set the width of the table to be 500 pixels. Lines 25-32 form the <thead> element. This consists of a <tr> (table row) element going from line 26 - line 31. Within the <tr> element are four <th> elements. So, at this point, the table will have four columns.
Lines 17-20 define a <style> element that will allow for text wrapping inside of <th> elements. That can be seen in the screen shot above.
Lines 33-46 define the <tbody> element. This element is where the data rows of the table will go. As you can see, there are two <tr> elements that will have four <td> elements each. That is, the data for this table will be on two rows consisting of 4 columns each.
Allowing user input inside the <td> elements
We want the user to be able to enter numbers inside the table cells. So, for each of the <td> elements, we will place an <input type="text"> element. Each of the input text boxes will be given a unique id attribute, so we can readily access the value for each input text box.
Here is the new version of exercise2.html, that adds the <input> elements to the <td> elements. The size="12" attribute was chosen so that the size of the <input> element fit well within the table cell.
<!DOCTYPE html>
<html>
<head>
<script>
addEventListener('DOMContentLoaded', init);
function init() {
const rate = 0.055; // divide by 12 to get monthly interest rate
const principal = 37123.99;
const n = 60; // number of months
let num = principal*rate/12*(1 + rate/12)**n;
let denom = (1 + rate/12)**n - 1;
let pmt = num/denom;
console.log(pmt);
}
</script>
<style>
th {
word-wrap: break-word;
}
</style>
</head>
<body>
<table border="1" width="500px">
<thead>
<tr>
<th>Annual Interest Rate</th>
<th>Months for Loan</th>
<th>Amount Borrowed</th>
<th>Monthly Payment</th>
</tr>
</thead>
<tbody>
<tr>
<td><input type="text" id="rate_1" size="12"></td>
<td><input type="text" id="periods_1" size="12"></td>
<td><input type="text" id="principal_1" size="12"></td>
<td><input type="text" id="pmt_1" size="12"></td>
</tr>
<tr>
<td><input type="text" id="rate_2" size="12"></td>
<td><input type="text" id="periods_2" size="12"></td>
<td><input type="text" id="principal_2" size="12"></td>
<td><input type="text" id="pmt_2" size="12"></td>
</tr>
</tbody>
</table>
</body>
</html>
The modified lines are lines 35-38 and lines 41-44. This just places an <input> element inside each <td> element. Each <input> element has a unique id to make the value of the <input> accessible.
Here is a screen shot showing some values entered into the table.
Using localStorage
Now that we have a table to enter the loan parameters. Let’s start to set things up so that we can store those loan parameters. To help out with this task, we will create a class called Loan. Here is a modified version of exercise2.html that does this and makes some use of localStorage:
<!DOCTYPE html>
<html>
<head>
<script>
addEventListener('DOMContentLoaded', init);
let rate_1,periods_1,principal_1,pmt_1;
let rate_2,periods_2,principal_2,pmt_2;
class Loan {
constructor(rate,periods,principal) {
this.rate = rate;
this.periods = periods;
this.principal = principal;
let r = this.rate/100; // convert to percent
let n = this.periods;
let A = this.principal
let num = A*r/12*(1 + r/12)**n;
let denom = (1 + r/12)**n - 1;
this.pmt = num/denom;
}
}
function handle_calc() {
let loan1 = new Loan(5.5,60,37123.99);
let loan2 = new Loan(6.5,60,37123.99);
let loan1_str = JSON.stringify(loan1);
let loan2_str = JSON.stringify(loan2);
localStorage.setItem("loan1", loan1_str);
localStorage.setItem("loan2", loan2_str);
}
function init() {
const calc = document.getElementById('calc');
calc.addEventListener('click', handle_calc);
if (localStorage.getItem("loan1") != null) {
let loan1 = JSON.parse(localStorage.getItem("loan1"));
console.log(loan1);
}
if (localStorage.getItem("loan2") != null) {
let loan2 = JSON.parse(localStorage.getItem("loan2"));
console.log(loan2);
}
/*
const rate = 0.055; // divide by 12 to get monthly interest rate
const principal = 37123.99;
const n = 60; // number of months
let num = principal*rate/12*(1 + rate/12)**n;
let denom = (1 + rate/12)**n - 1;
let pmt = num/denom;
console.log(pmt);
*/
}
</script>
<style>
th {
word-wrap: break-word;
}
</style>
</head>
<body>
<table border="1" width="500px">
<thead>
<tr>
<th>Annual Interest Rate</th>
<th>Months for Loan</th>
<th>Amount Borrowed</th>
<th>Monthly Payment</th>
</tr>
</thead>
<tbody>
<tr>
<td><input type="text" id="rate_1" size="12"></td>
<td><input type="text" id="periods_1" size="12"></td>
<td><input type="text" id="principal_1" size="12"></td>
<td><input type="text" id="pmt_1" size="12"></td>
</tr>
<tr>
<td><input type="text" id="rate_2" size="12"></td>
<td><input type="text" id="periods_2" size="12"></td>
<td><input type="text" id="principal_2" size="12"></td>
<td><input type="text" id="pmt_2" size="12"></td>
</tr>
</tbody>
</table>
<button id="calc">Calculate</button>
</body>
</html>
The new lines are lines 6-7, 9-21, 23-30, 33-34, 36-43, 44-52 and 86. Lines 44-52 just comment out the lines that I used to perform the loan monthly payment calculation. Those lines will be removed in the next version of the code.
Lines 6-7 define document level variables that will be used to hold references to the <input> elements inside our HTML table. I defined them as document level variables so that once I assign values to them inside the init() function, they will be accessible from anywhere in the <script> element.
Lines 9-21 define the Loan class. The constructor for the Loan class takes the rate (interest rate), periods and principal as parameters. Lines 11-13 store those parameters in the class instance variables. Line 14 converts the rate into a percent, as the interest rate from the <input> will be entered as 5.5 for a 5.5% interest loan. Lines 14-16 just store the values in short variable names, so that the formulas are easier to see on lines 17 and 18. The calculations on lines 17, 18 and 19 are the same calculations that were carried out in the commented out code (lines 44-52). Line 19 stores the pmt (monthly payment) in the class variable, this.pmt.
Lines 23-30 define the handle_calc() function. At this point, this function is not complete. All that is done is it hard-codes the values for some Loan objects on lines 24-25. Lines 26-27 stringify the Loan objects into strings that can be parsed into the original objects. Lines 28-29 store the stringified values into localStorage. So, at this point, all the handle_calc() function is doing is placing some Loan objects into localStorage. In the next version of the code, we will need to get the loan parameters from the appropriate <input> values. This will create Loan objects that are the ones the user intends to use for calculation purposes.
Lines 33-34 get a reference to the Calculate button (defined on line 86) and make it so that the handle_calc() function is called when that button is clicked. Lines 36-39 check to see if the key named loan1 is found inside localStorage. If that key is found, line 37 parses that item into a Loan object. Line 38 prints that Loan object to the console. Lines 40-42 do the same thing as lines 36-39, except that they do this for the loan2 key. This means that when we open exercise2.html in the browser, the console should show the objects that were stored inside localStorage. Those objects will remain there until localStorage is cleared. As a reminder, localStorage is maintained by the browser. If localStorage is not cleared, then as long as the user is using the same computer and same browser to reach the page, the values placed in localStorage will be there.
Line 86 adds the markup for a <button> that has an id="calc" attribute, and is labeled "Calculate".
If you open up this version of exercise2.html in the browser this is what you will see in the console:
You can try reloading this page over and over. You will see that the values from localStorage will be there (unless you clear localStorage).
Using user input for the loan parameters
Now that our storage mechanism works, we can go through the steps to use the user input values from the table for our Loan objects. Here is the next version of exercise2.html:
<!DOCTYPE html>
<html>
<head>
<script>
addEventListener('DOMContentLoaded', init);
let rate_1,periods_1,principal_1,pmt_1;
let rate_2,periods_2,principal_2,pmt_2;
class Loan {
constructor(rate,periods,principal) {
this.rate = rate;
this.periods = periods;
this.principal = principal;
let r = this.rate/100; // convert to percent
let n = this.periods;
let A = this.principal
let num = A*r/12*(1 + r/12)**n;
let denom = (1 + r/12)**n - 1;
this.pmt = num/denom;
}
}
function handle_calc() {
//let loan1 = new Loan(5.5,60,37123.99);
//let loan2 = new Loan(6.5,60,37123.99);
// get user input values
let rate = Number(rate_1.value);
let periods = Number(periods_1.value);
let principal = Number(principal_1.value);
let loan1 = new Loan(rate, periods, principal);
rate = Number(rate_2.value);
periods = Number(periods_2.value);
principal = Number(principal_2.value);
let loan2 = new Loan(rate, periods, principal);
// Store pmt values in table
pmt_1.value = loan1.pmt;
pmt_2.value = loan2.pmt;
let loan1_str = JSON.stringify(loan1);
let loan2_str = JSON.stringify(loan2);
localStorage.setItem("loan1", loan1_str);
localStorage.setItem("loan2", loan2_str);
}
function init() {
const calc = document.getElementById('calc');
calc.addEventListener('click', handle_calc);
// get references to all <input> elements
rate_1 = document.getElementById('rate_1');
periods_1 = document.getElementById('periods_1');
principal_1 = document.getElementById('principal_1');
pmt_1 = document.getElementById('pmt_1');
rate_2 = document.getElementById('rate_2');
periods_2 = document.getElementById('periods_2');
principal_2 = document.getElementById('principal_2');
pmt_2 = document.getElementById('pmt_2');
if (localStorage.getItem("loan1") != null) {
let loan1 = JSON.parse(localStorage.getItem("loan1"));
// use stored values to populate the <input> elements
rate_1.value = loan1.rate;
periods_1.value = loan1.periods;
principal_1.value = loan1.principal;
}
if (localStorage.getItem("loan2") != null) {
let loan2 = JSON.parse(localStorage.getItem("loan2"));
rate_2.value = loan2.rate;
periods_2.value = loan2.periods;
principal_2.value = loan2.principal;
}
}
</script>
<style>
th {
word-wrap: break-word;
}
</style>
</head>
<body>
<table border="1" width="500px">
<thead>
<tr>
<th>Annual Interest Rate</th>
<th>Months for Loan</th>
<th>Amount Borrowed</th>
<th>Monthly Payment</th>
</tr>
</thead>
<tbody>
<tr>
<td><input type="text" id="rate_1" size="12"></td>
<td><input type="text" id="periods_1" size="12"></td>
<td><input type="text" id="principal_1" size="12"></td>
<td><input type="text" id="pmt_1" size="12"></td>
</tr>
<tr>
<td><input type="text" id="rate_2" size="12"></td>
<td><input type="text" id="periods_2" size="12"></td>
<td><input type="text" id="principal_2" size="12"></td>
<td><input type="text" id="pmt_2" size="12"></td>
</tr>
</tbody>
</table>
<button id="calc">Calculate</button>
</body>
</html>
The new lines are lines 24-25, 26-37, 49-57, 61-64 and 68-70. Lines 24 and 25 just comment out the hard-coded Loan objects. Line 26 is just a comment that says the next lines will be used to get the user input values. Lines 27-29 obtain the rate, periods and principal from the corresponding boxes in the first data row. Line 30 constructs a new Loan object based on those parameters. Note that the Number() function is used to convert the input text into numbers. Lines 31-34 do the same thing but for the second row of data. Lines 36 and 37 insert the calculated loan payments into the <input> boxes where the monthly payments should be displayed.
Lines 50-57 are the lines inside of the init() function that gets references to all of the <input> elements in the table. Lines 62-63 now use the Loan parameters from localStorage to populate the first three columns of the first data row of the table. Lines 68-70 do the same thing for the first three columns of the second data row of the table. If the user hits the Calculate button, the monthly payment will be placed into the last column of the data rows.
The following screen shot shows what the page looks like when it is first loaded (and before Calculate has been hit):
This screen shot shows what the page looks like after the Calculate button has been hit:
Formatting the pmt values to have 2 decimal places
In JavaScript, the toFixed(n) function is used to format a number to have n decimal places. If you add this in to the code and take out lines that were just commented out, this is the final version of the code:
<!DOCTYPE html>
<html>
<head>
<script>
addEventListener('DOMContentLoaded', init);
let rate_1,periods_1,principal_1,pmt_1;
let rate_2,periods_2,principal_2,pmt_2;
class Loan {
constructor(rate,periods,principal) {
this.rate = rate;
this.periods = periods;
this.principal = principal;
let r = this.rate/100; // convert to percent
let n = this.periods;
let A = this.principal
let num = A*r/12*(1 + r/12)**n;
let denom = (1 + r/12)**n - 1;
this.pmt = num/denom;
}
}
// get user input values
let rate = Number(rate_1.value);
let periods = Number(periods_1.value);
let principal = Number(principal_1.value);
let loan1 = new Loan(rate, periods, principal);
rate = Number(rate_2.value);
periods = Number(periods_2.value);
principal = Number(principal_2.value);
let loan2 = new Loan(rate, periods, principal);
// Store pmt values in table
pmt_1.value = loan1.pmt.toFixed(2);
pmt_2.value = loan2.pmt.toFixed(2);
let loan1_str = JSON.stringify(loan1);
let loan2_str = JSON.stringify(loan2);
localStorage.setItem("loan1", loan1_str);
localStorage.setItem("loan2", loan2_str);
}
function init() {
const calc = document.getElementById('calc');
calc.addEventListener('click', handle_calc);
// get references to all <input> elements
rate_1 = document.getElementById('rate_1');
periods_1 = document.getElementById('periods_1');
principal_1 = document.getElementById('principal_1');
pmt_1 = document.getElementById('pmt_1');
rate_2 = document.getElementById('rate_2');
periods_2 = document.getElementById('periods_2');
principal_2 = document.getElementById('principal_2');
pmt_2 = document.getElementById('pmt_2');
if (localStorage.getItem("loan1") != null) {
let loan1 = JSON.parse(localStorage.getItem("loan1"));
// use stored values to populate the <input> elements
rate_1.value = loan1.rate;
periods_1.value = loan1.periods;
principal_1.value = loan1.principal;
}
if (localStorage.getItem("loan2") != null) {
let loan2 = JSON.parse(localStorage.getItem("loan2"));
rate_2.value = loan2.rate;
periods_2.value = loan2.periods;
principal_2.value = loan2.principal;
}
}
</script>
<style>
th {
word-wrap: break-word;
}
</style>
</head>
<body>
<table border="1" width="500px">
<thead>
<tr>
<th>Annual Interest Rate</th>
<th>Months for Loan</th>
<th>Amount Borrowed</th>
<th>Monthly Payment</th>
</tr>
</thead>
<tbody>
<tr>
<td><input type="text" id="rate_1" size="12"></td>
<td><input type="text" id="periods_1" size="12"></td>
<td><input type="text" id="principal_1" size="12"></td>
<td><input type="text" id="pmt_1" size="12"></td>
</tr>
<tr>
<td><input type="text" id="rate_2" size="12"></td>
<td><input type="text" id="periods_2" size="12"></td>
<td><input type="text" id="principal_2" size="12"></td>
<td><input type="text" id="pmt_2" size="12"></td>
</tr>
</tbody>
</table>
<button id="calc">Calculate</button>
</body>
</html>