//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.


Cost calculator for table project

Material Number
needed
Cost per
item
Cost per
material


Source code

<!DOCTYPE html>
<html>
    <head>
        <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.readMaterialNumbers()
            //      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
                readMaterialNumbers: function () {
                    console.log('in table.readMaterialNumbers()');
                    
                    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
                    var numberOfEachMaterialArr = this.readMaterialNumbers();

                    // 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 {
                padding:            2px;
                vertical-align:     bottom;
                background-color:   lightGrey;
                border-bottom:      1px solid black;
            }
            input {
                width:              70px;
                text-align:         center;
            }
        </style>
    </head>
    <body onload="table.createTable();">
        <h2>Side table material request calculator</h2>
        
        <table>
            <thead>
                <tr>
                    <th>Material</th>
                    <th>Number<br />needed</th>
                    <th>Cost per<br />item</th>
                    <th>Cost per<br />material</th>
                </tr>
            </thead>
            <tbody id="tbodyID"></tbody>
        </table>
        <hr />
        <p>
            <button onclick="table.recalculateCosts();">Calculate total</button>
        </p>
    </body>
</html>