//SOLVE: Cost calculator

## Creating an online spreadsheet or calculator

In this example we are creating a webpage that automatically calculates the cost of materials for creating a wooden table.

The primary data structure, table.materialArr, is an array of objects that stores the name and cost of each material. There is a second data structure, table.materialCostArr, that stores the total cost of each of the materials.

This example does not follow the strict seperation of input, output, and process. The input and output methods of table both contain loops to automate the process of reading in and displaying information.

The table.createTable() method uses both process and output functions. This has to do with creating code that generally directly manipulates the browser's Document Object Model (DOM), rather than relying on .innerHTML to display things.

Directly manipulating the DOM works much faster, and can sometimes be easier to troubleshoot than using .innerHTML.

Material Number
needed
Cost per
item
Cost per
material

## Source code

<!DOCTYPE html>
<html>
<meta charset="utf-8">
<title>Material request calculator</title>

<script>
// This program creates a page that automatically calculates a
// total cost of a table project depending on the amounts of the
// materials used.
// It is like a basic spreadsheet.

// table OBJECT
// properties:
//      table.materialArr
//      table.materialCostArr
//      table.totalCost
//
// methods:
//      table.createTable()
//      table.recalculateCosts()
//      table.displayTotals()
//
var table = {
// define the array of materials and their costs
materialArr: [
{ material: '16" long 2x4',             cost:   0.66},
{ material: '24" long 2x4',             cost:   0.99},
{ material: '32" long 2x4',             cost:   1.32},
{ material: '48" long 2x4',             cost:   1.98},
{ material: '16" long resawn 2x4',      cost:   0.33},
{ material: '24" long resawn 2x4',      cost:   0.50},
{ material: '32" long resawn 2x4',      cost:   0.66},
{ material: '48" long resawn 2x4',      cost:   0.99},
{ material: '32" long resawn 2x6',      cost:   1.05},
{ material: '48" long resawn 2x6',      cost:   1.57},
{ material: '6x16" plywood 1/4" thick', cost:   0.36},
{ material: '8x16" plywood 1/4" thick', cost:   0.50},

{ material: null,                       cost:   null},

{ material: '#0 biscuit',               cost:   0.06},
{ material: '1 1/4" #8 wood screw',     cost:   0.02},
{ material: 'table top clip',           cost:   0.02},
{ material: '1/4 x 1 1/4" dowel',       cost:   0.03},
{ material: 'woodworking glue (get 1)', cost:   0.25},

{ material: null,                       cost:   null},

{ material: '40 grit sandpaper',        cost:   0.22},
{ material: '60 grit sandpaper',        cost:   0.22},
{ material: '100 grit sandpaper',       cost:   0.22},
{ material: '150 grit sandpaper',       cost:   0.22},
{ material: '220 grit sandpaper',       cost:   0.22},
{ material: 'N95 dust mask',            cost:   1.73},
{ material: 'pair of gloves',           cost:   0.03},
{ material: 'varnish (choose 1)',       cost:   0.82},
],

// define an empty array with the cost of each material
materialCostArr: [],

// define a total cost of all materials
totalCost:      0,

//PROCESS+OUTPUT: displays the table
createTable: function () {
console.log('in table.createTable()');

// loop through each material in the array
for (i in this.materialArr) {

// if this material is null...
if ( this.materialArr[ i ].material == null ) {

// display a blank line in the table
var rowElement = document.createElement( "tr" );
rowElement.innerHTML
= "<td></td><td></td><td></td>"
+ "<td style='color: white;'>"
+ "= \$<span id='total"
+ i
+ "ID'>0.00</span>";
// OUTPUT: add this row to the table
document.getElementById('tbodyID').appendChild( rowElement );

// otherwise, if this material is not null....
} else {
// create a cell that has the name of the material inside it
// Example:     <td>wood</td>
var materialElement = document.createElement( "td" );
var materialText    = document.createTextNode( this.materialArr[ i ].material );
materialElement.appendChild( materialText );

// create a cell that is used for the user to input the number of each item
// Example:
//  <td>
//      <input
//          type='text'
//          id='cost2ID'
//          data-id='2'
//          onchange='calculateMaterial(this);'>
//      </input>
//  </td>
var numberElement       = document.createElement( "td" );
var inputElement        = document.createElement( "input" );
inputElement.type       = "text";
inputElement.id         = 'cost' + i + 'ID';
inputElement.onchange   = function () { table.recalculateCosts(); };
numberElement.appendChild( inputElement );

// create a cell element that contains the cost of the material
// Example:     <td>× \$0.99</td>
// note that I had to use toFixed(2) in order to have the costs display as currency
var eachElement     = document.createElement( "td" );
var eachText        = document.createTextNode(
'× \$' + this.materialArr[ i ].cost.toFixed(2)
);
eachElement.appendChild( eachText );

// create a cell that will display the current total calculation for this material
// Example:     <td>= \$<span id='total2ID'>0.00</span></td>
var totalElement        = document.createElement( "td" );
totalElement.innerHTML  = "= \$<span id='total" + i + "ID'>0.00</span>";

// create a table row that contains the material, number, each cost, and total cells inside it
var rowElement          = document.createElement( "tr" );
rowElement.appendChild( materialElement );
rowElement.appendChild( numberElement );
rowElement.appendChild( eachElement );
rowElement.appendChild( totalElement );

// OUTPUT: add this row to the table
document.getElementById('tbodyID').appendChild( rowElement );
}
}

// create the final row that contains the total cost
var rowElement          = document.createElement( "tr" );
rowElement.innerHTML
= "<td></td><td></td>"
+ "<td>Total</td>"
+ "<th>= \$<span id='totalID'>0.00</span></th>";

// OUTPUT: add this row to the table
document.getElementById('tbodyID').appendChild( rowElement );
},

//INPUT: read in the number of each material needed

var numberOfEachMaterialArr = [];

// loop through each material
for (i in this.materialArr) {
// if this material is null (blank line), then use 0 as number needed
if ( this.materialArr[ i ].material == null ) {
numberOfEachMaterialArr[ i ]    = 0;
}
// otherwise, if not null, read it the number typed and convert to an integer
else {
numberOfEachMaterialArr[ i ] = parseInt( 0 + document.getElementById( 'cost' + i + 'ID' ).value );
}
}

return numberOfEachMaterialArr;
},

// PROCESS: calculate the current totals
recalculateCosts: function () {
console.log('in table.recalculateCosts()');

this.totalCost = 0;

// create the array of the number of each material needed

// PROCESS: loop through each material
for (i in this.materialArr) {
// calculate the cost of each material
this.materialCostArr[ i ] = numberOfEachMaterialArr[ i ] * this.materialArr[ i ].cost;

// PROCESS: add this material to the running total
this.totalCost = this.totalCost + this.materialCostArr[ i ];
}

// now display the totals
this.displayTotals();
},

// OUTPUT: display the current totals
displayTotals: function () {
console.log('in table.displayTotals()');

// loop through each material
for (i in this.materialArr) {
// output the total, formatting to two decimals
document.getElementById('total' + i + 'ID').innerHTML = this.materialCostArr[ i ].toFixed(2);
}

// display the running total, formatted to two decimal
document.getElementById('totalID').innerHTML = this.totalCost.toFixed(2);
}
};

</script>
<style>
body {
font-family:        "Open Sans", "Arial", sans-serif;
}
th {
vertical-align:     bottom;
background-color:   lightGrey;
border-bottom:      1px solid black;
}
input {
width:              70px;
text-align:         center;
}
</style>
<h2>Side table material request calculator</h2>

<table>
<tr>
<th>Material</th>
<th>Number<br />needed</th>
<th>Cost per<br />item</th>
<th>Cost per<br />material</th>
</tr>