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.
Leave a Reply