Creating an Invoice Generator with HTML and Javascript

In this blog, we'll walk through creating a simple yet powerful invoice maker application using HTML, CSS, and JavaScript. In the first step, we'll focus on setting up the HTML structure and styling with CSS. In the second step, we'll dive into the JavaScript functions, discuss the benefits of this approach, and address potential security concerns.

HTML Structure

The HTML file sets up the structure of our invoice maker. We include Bootstrap for styling and create a form where users can enter the shop name, address, phone number, date, and item details.

Here's the complete HTML file for our Invoice Maker:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Invoice Maker</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
    <style>
        .invoice-table th, .invoice-table td {
            border: 1px solid #dee2e6;
            padding: 8px;
        }
        .invoice-container {
            margin-top: 20px;
        }
        @media print {
            body * {
                visibility: hidden;
            }
            #invoice-print-section, #invoice-print-section * {
                visibility: visible;
            }
            #invoice-print-section {
                position: absolute;
                width: 80%;
                left: 10%;
                top: 0;
            }
        }
    </style>
</head>
<body class="container">
    <h1 class="my-4 text-center">Invoice Maker</h1>
    <form id="invoice-form" class="mb-4">
        <div class="form-group">
            <label for="shop-name">Shop Name:</label>
            <input type="text" class="form-control" id="shop-name" name="shop-name">
        </div>

        <div class="form-group">
            <label for="address">Address:</label>
            <input type="text" class="form-control" id="address" name="address">
        </div>

        <div class="form-group">
            <label for="phone-number">Phone Number:</label>
            <input type="tel" class="form-control" id="phone-number" name="phone-number">
        </div>

        <div class="form-group">
            <label for="date">Date:</label>
            <input type="date" class="form-control" id="date" name="date">
        </div>

        <h2 class="my-4">Items</h2>
        <table id="items-table" class="table invoice-table">
            <thead>
                <tr>
                    <th>Item Name</th>
                    <th>Quantity</th>
                    <th>Rate</th>
                    <th>Total</th>
                    <th>Action</th>
                </tr>
            </thead>
            <tbody>
            </tbody>
        </table>
        <button type="button" class="btn btn-primary mb-3" id="add-item">Add Item</button>
        <br>
        <input type="submit" class="btn btn-success" value="Generate Invoice">
    </form>

<div id="invoice-print-section" class="invoice-container" style="visibility: hidden;">
        <h1 class="text-center" style="text-decoration: underline;">Invoice</h1>
        <p><strong><span id="print-shop-name"></span></strong> <br>
        <strong><span id="print-address"></span></strong> <br>
        <strong>Phone Number:</strong> <span id="print-phone-number"></span><br>
        <strong>Date:</strong> <span id="print-date"></span></p>
        <table class="table invoice-table">
            <thead>
                <tr>
                    <th>Item Name</th>
                    <th>Quantity</th>
                    <th>Rate</th>
                    <th>Total</th>
                </tr>
            </thead>
            <tbody id="print-items-table">
            </tbody>
        </table>
        <p id="print-total-sum"><strong>Total Sum:</strong></p>
    </div>
</body>
</html>
    

CSS Styling

We use Bootstrap for overall styling and some additional CSS for specific elements like the table and print layout. The media query ensures the invoice prints correctly by making only the print section visible.

Step 2: Implementing JavaScript Functions and Understanding Their Usefulness

In this second step, we'll dive into the JavaScript functions that make our Invoice Maker interactive and functional. We'll also discuss the benefits of this approach and address potential security concerns.

JavaScript Functions Explained

1. DOMContentLoaded Event Listener

document.addEventListener("DOMContentLoaded", function() {
    // Initialization and event listeners go here
});

This ensures our code runs only after the entire DOM is fully loaded, preventing any issues with element selection.

2. Loading Saved Data from Local Storage

const savedData = JSON.parse(localStorage.getItem("invoiceData")) || {};
if (savedData.shopName) shopNameInput.value = savedData.shopName;
if (savedData.address) addressInput.value = savedData.address;
if (savedData.phoneNumber) phoneNumberInput.value = savedData.phoneNumber;
if (savedData.date) dateInput.value = savedData.date;
else dateInput.value = new Date().toISOString().split("T")[0];

if (savedData.items) {
    savedData.items.forEach(item => addItemRow(item.name, item.quantity, item.rate, item.total));
}

This block loads previously saved invoice data from the browser's local storage and fills the form inputs accordingly. If no data is available, it sets the current date by default.

3. Save Data to Local Storage

function saveData() {
    const items = [];
    document.querySelectorAll("#items-table tbody tr").forEach(row => {
        const cells = row.getElementsByTagName("td");
        items.push({
            name: cells[0].getElementsByTagName("input")[0].value,
            quantity: cells[1].getElementsByTagName("input")[0].value,
            rate: cells[2].getElementsByTagName("input")[0].value,
            total: cells[3].innerText
        });
    });

    const data = {
        shopName: shopNameInput.value,
        address: addressInput.value,
        phoneNumber: phoneNumberInput.value,
        date: dateInput.value,
        items: items
    };
    localStorage.setItem("invoiceData", JSON.stringify(data));
}

document.querySelectorAll("#invoice-form input, #invoice-form button").forEach(element => {
    element.addEventListener("input", saveData);
    element.addEventListener("click", saveData);
});

This function collects the current form data and items, then saves it to local storage. Event listeners are added to each form input and button to trigger data saving on any change.

4. Adding an Item Row

function addItemRow(name = "", quantity = "", rate = "", total = "") {
    const row = document.createElement("tr");

    row.innerHTML = 
        <td><input type="text" class="form-control" value="${name}"></td>
        <td><input type="number" class="form-control" value="${quantity}"></td>
        <td><input type="number" class="form-control" value="${rate}"></td>
        <td>${total}</td>
        <td><button type="button" class="btn btn-danger btn-sm delete-item">Delete</button></td>`;
    ;

    row.querySelectorAll("input").forEach(input => {
        input.addEventListener("input", updateTotal);
    });

    row.querySelector(".delete-item").addEventListener("click", function() {
        row.remove();
        updateTotal();
    });
    
    itemsTable.appendChild(row);
    updateTotal();
}

addItemButton.addEventListener("click", () => addItemRow());

This function adds a new row to the items table, complete with input fields for item name, quantity, rate, and a calculated total. Event listeners on inputs trigger updates to the total when values change.

5. Updating Totals

function updateTotal() {
    let totalSum = 0;
document.querySelectorAll("#items-table tbody tr").forEach(row => {
        const cells = row.getElementsByTagName("td");
        const quantity = cells[1].getElementsByTagName("input")[0].value;
        const rate = cells[2].getElementsByTagName("input")[0].value;
        const total = quantity * rate;
        cells[3].innerText = isNaN(total) ? "" : total;
        if (!isNaN(total)) {
            totalSum += total;
        }
    });
    document.getElementById("total-sum").innerText = "Total Sum: " + totalSum;
    document.getElementById("print-total-sum").innerText = "Total Sum: " + totalSum;
    saveData();
}

This function calculates the total for each item based on its quantity and rate, updates the corresponding cell, and computes the total sum of all items. It also saves the updated data to local storage.

6. Generating and Printing the Invoice

document.getElementById("invoice-form").addEventListener("submit", function(event) {
    event.preventDefault();

    document.getElementById("print-shop-name").innerText = shopNameInput.value;
    document.getElementById("print-address").innerText = addressInput.value;
    document.getElementById("print-phone-number").innerText = phoneNumberInput.value;
    document.getElementById("print-date").innerText = dateInput.value;

    const printItemsTable = document.getElementById("print-items-table");
    printItemsTable.innerHTML = "";
    document.querySelectorAll("#items-table tbody tr").forEach(row => {
        const cells = row.getElementsByTagName("td");
        const itemRow = document.createElement("tr");
        itemRow.innerHTML = 
            <td>${cells[0].getElementsByTagName("input")[0].value}</td>
            <td>${cells[1].getElementsByTagName("input")[0].value}</td>
            <td>${cells[2].getElementsByTagName("input")[0].value}</td>
            <td>${cells[3].innerText}</td>
        ;
        printItemsTable.appendChild(itemRow);
    });

    document.getElementById("print-total-sum").innerText = document.getElementById("total-sum").innerText;

    const invoicePrintSection = document.getElementById("invoice-print-section");
    invoicePrintSection.classList.remove("d-none");
    window.print();
});

This function transfers the form data and item details to the printable invoice section, making it visible and initiating the print dialog.

7. Deleting an unwanted row


        row.querySelector(".delete-item").addEventListener("click", function() {
            row.remove();
            updateTotal();
        });
    

This function removes the unwanted rows from the table.

8. Full JavaScript Code


        document.addEventListener("DOMContentLoaded", function() {
            const shopNameInput = document.getElementById("shop-name");
            const addressInput = document.getElementById("address");
            const phoneNumberInput = document.getElementById("phone-number");
            const dateInput = document.getElementById("date");
            const itemsTable = document.getElementById("items-table").querySelector("tbody");
            const addItemButton = document.getElementById("add-item");
            const totalSumElement = document.createElement("p");
            totalSumElement.id = "total-sum";
            document.body.appendChild(totalSumElement);

            // Load saved data from local storage if available
            const savedData = JSON.parse(localStorage.getItem("invoiceData")) || {};
            if (savedData.shopName) shopNameInput.value = savedData.shopName;
            if (savedData.address) addressInput.value = savedData.address;
            if (savedData.phoneNumber) phoneNumberInput.value = savedData.phoneNumber;

            if (savedData.date) dateInput.value = savedData.date;
            else dateInput.value = new Date().toISOString().split("T")[0];

            if (savedData.items) {
                savedData.items.forEach(item => addItemRow(item.name, item.quantity, item.rate, item.total));
            }

            // Save form data to local storage on change
            function saveData() {
                const items = [];
                document.querySelectorAll("#items-table tbody tr").forEach(row => {
                    const cells = row.getElementsByTagName("td");
                    items.push({
                        name: cells[0].getElementsByTagName("input")[0].value,
                        quantity: cells[1].getElementsByTagName("input")[0].value,
                        rate: cells[2].getElementsByTagName("input")[0].value,
                        total: cells[3].innerText
                    });
                });

                const data = {
                    shopName: shopNameInput.value,
                    address: addressInput.value,
                    phoneNumber: phoneNumberInput.value,
                    date: dateInput.value,
                    items: items
                };
                localStorage.setItem("invoiceData", JSON.stringify(data));
            }

            document.querySelectorAll("#invoice-form input, #invoice-form button").forEach(element => {
                element.addEventListener("input", saveData);
                element.addEventListener("click", saveData);
            });

            // Add item row
            function addItemRow(name = "", quantity = "", rate = "", total = "") {
                const row = document.createElement("tr");

                row.innerHTML = `<td><input type="text" class="form-control" value="${name}"></td>
                    <td><input type="number" class="form-control" value="${quantity}"></td>
                    <td><input type="number" class="form-control" value="${rate}"></td>
                    <td>${total}</td>
                    <td><button type="button" class="btn btn-danger btn-sm delete-item">Delete</button></td>`;

                row.querySelectorAll("input").forEach(input => {
                    input.addEventListener("input", updateTotal);
                });

                row.querySelector(".delete-item").addEventListener("click", function() {
                    row.remove();
                    updateTotal();
                });

                itemsTable.appendChild(row);
                updateTotal();
            }

            // Update total for each row and calculate sum
            function updateTotal() {
                let totalSum = 0;
                document.querySelectorAll("#items-table tbody tr").forEach(row => {
                    const cells = row.getElementsByTagName("td");
                    const quantity = cells[1].getElementsByTagName("input")[0].value;
                    const rate = cells[2].getElementsByTagName("input")[0].value;
                    const total = quantity * rate;
                    cells[3].innerText = isNaN(total) ? "" : total;
                    if (!isNaN(total)) {
                        totalSum += total;
                    }
                });
                document.getElementById("total-sum").innerText = "Total Sum: " + totalSum;
                document.getElementById("print-total-sum").innerText = "Total Sum: " + totalSum;
                saveData();
            }

            addItemButton.addEventListener("click", () => addItemRow());

            // Generate and print invoice
            document.getElementById("invoice-form").addEventListener("submit", function(event) {
                event.preventDefault();

                document.getElementById("print-shop-name").innerText = shopNameInput.value;
                document.getElementById("print-address").innerText = addressInput.value;
                document.getElementById("print-phone-number").innerText = phoneNumberInput.value;
                document.getElementById("print-date").innerText = dateInput.value;

                const printItemsTable = document.getElementById("print-items-table");
                printItemsTable.innerHTML = "";
                document.querySelectorAll("#items-table tbody tr").forEach(row => {
                    const cells = row.getElementsByTagName("td");
                    const itemRow = document.createElement("tr");
                    itemRow.innerHTML = 
                    `<td>${cells[0].getElementsByTagName("input")[0].value}</td>
                    <td>${cells[1].getElementsByTagName("input")[0].value}</td>
                    <td>${cells[2].getElementsByTagName("input")[0].value}</td>
                    <td>${cells[3].innerText}</td>`;
                    printItemsTable.appendChild(itemRow);
                });

                document.getElementById("print-total-sum").innerText = document.getElementById("total-sum").innerText;
                document.getElementById("print-total-sum").innerText = document.getElementById("total-sum").innerText;

const invoicePrintSection = document.getElementById("invoice-print-section");
invoicePrintSection.classList.remove("d-none");
window.print();
});
});
    

Usefulness of This Type of Invoice Generation

This type of invoice generation is stepicularly useful for small business owners, freelancers, and anyone needing quick, customizable invoices. It offers several benefits:

  • Convenience: Create and print invoices directly from your browser.
  • Customization: Easily modify invoice details and add items.
  • Data Persistence: Save invoice data in the browser for future use.

Security Concerns with Local Storage

While local storage is convenient for saving data between sessions, it comes with some security risks:

  • Data Exposure: Data stored in local storage is accessible through JavaScript, making it vulnerable to XSS (Cross-Site Scripting) attacks.
  • No Encryption: Local storage data is not encrypted, so sensitive information should be avoided.

To mitigate these risks, consider using secure storage solutions and sanitizing input data to prevent XSS attacks.

Live Demonstration

Click here for live preview.

Conclusion

Creating an invoice maker with HTML, CSS, and JavaScript is a practical solution for quick and customizable invoice generation. While local storage offers convenience, it's essential to be aware of and mitigate security risks. This tool can be invaluable for small business owners and freelancers looking to streamline their invoicing process.

0 Comment

Leave a Reply

Your email address will not be published. Required fields are marked *


You may like to read


Follow Us

Sponsored Ads

GranNino Ads

Newsletter

Subscribe to our Newsletter to read our latest posts at first

We would not spam your inbox! Promise
Get In Touch

© Fullstack Coding Tips. All Rights Reserved.