Dynamic Invoice Generator
In the world of business, efficiency and professionalism are key. One essential aspect is the creation and management of invoices. To streamline this process, a dynamic invoice generator can be a valuable tool. In this article, we’ll explore a simple yet powerful Dynamic Invoice Generator created using HTML, CSS, and JavaScript.
Introduction
The Dynamic Invoice Generator is a web-based tool that allows users to create, edit, and preview professional-looking invoices. It comes with features like dynamic item addition, real-time total calculation, and the ability to generate a downloadable PDF.
HTML Structure
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Dynamic Invoice Generator</title> <script src="https://rawgit.com/eKoopmans/html2pdf/master/dist/html2pdf.bundle.js"></script> <link rel="stylesheet" type="text/css" href="style.css"> </head> <body> <div class="tab-switch"> <button class="edit btn-cmn" onclick="switchTab('edit-tab')">Edit Invoice</button> <button class="preview btn-cmn" onclick="switchTab('preview-tab')">Preview</button> <button class="btn-cmn" onclick="generatePDF()">Download PDF</button> </div> <div id="invoice"> <h2>Invoice</h2> <div id="invoice-details"> <div id="client-details"> <h3>Client Information</h3> <!--Client Image Upload --> <input type="file" class="cmn-input" id="client-image" accept="image/*" onchange="previewImage(this)"> <img id="client-preview" alt="Client Preview" class="hidden" src="https://via.placeholder.com/200x200"> <label for="client-name">Client Name</label> <input type="text" class="cmn-input" id="client-name" value="John Doe"> <label for="client-address">Client Address</label> <input type="text" class="cmn-input" id="client-address" value="123 Main St, Cityville"> </div> <div id="invoice-info"> <h3>Invoice Information</h3> <label for="invoice-number">Invoice Number</label> <input type="text" class="cmn-input" id="invoice-number" value="12345"> <label for="invoice-date">Invoice Date</label> <input type="text" class="cmn-input" id="invoice-date" value="2023-11-22"> </div> </div> <table> <thead> <tr> <th>Description</th> <th>Quantity</th> <th>Unit Price</th> <th>Total</th> <th class="btn-p">Action</th> </tr> </thead> <tbody id="invoice-items"> <!-- Invoice items will be added dynamically here --> </tbody> </table> <div id="invoice-total"> <label for="total-amount">Total Amount</label> <input type="text" class="total-input" id="total-amount" value="0.00" readonly> </div> <button class="btn-add" onclick="addRow()">Add More Item</button> </div> <script src="index.js"></script> </body> </html>
CSS for Styling
body { font-family: 'Arial', sans-serif; background-color: #f8f8f8; margin: 0; padding: 0; } .tab-switch { display: flex; max-width: 840px; margin: 0 auto; padding-top: 20px; } .btn-cmn { margin: 0; padding: 16px; font-size: 16px; border: none; margin-right: 10px; border-bottom-right-radius: 0; border-bottom-left-radius: 0; } .btn-rmv { display: block; margin: 10px auto; padding: 8px 16px; background-color: #af4c54; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 14px; transition: background-color 0.3s ease; } #total-amount { text-align: right; border: 1px solid #cccccc; border-bottom: none; border-left: none; border-right: none; border-radius: 0; } button.preview { background-color: #3498db; } button.edit { background-color: #2ecc71; } button:hover { background-color: #2980b9; } #edit-tab { display: none; } #invoice { max-width: 800px; margin: 20px auto; padding: 20px; border-radius: 8px; background-color: #fff; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); color: #555; margin-top: 0; border-top-right-radius: 0; border-top-left-radius: 0; } h2 { text-align: center; color: #333; font-size: 2em; margin-bottom: 20px; } #invoice-details { display: flex; justify-content: space-between; margin-bottom: 20px; } #client-details, #invoice-info { width: 48%; } label { display: block; margin-bottom: 5px; margin-top: 14px; color: #555; } input { width: 100%; box-sizing: border-box; padding: 10px; border: 1px solid #ccc; border-radius: 4px; font-size: 14px; } table { width: 100%; border-collapse: collapse; margin-bottom: 20px; color: #555; } th, td { border: 1px solid #ddd; padding: 4px 15px; text-align: left; } th { background-color: #f2f2f2; padding: 12px 15px; } #invoice-total { text-align: right; margin-top: 20px; } button { display: block; margin: 20px auto; padding: 10px 15px; background-color: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 18px; transition: background-color 0.3s ease; } button:hover { background-color: #45a049; } /* #client-image { display: none; } */ #client-preview { max-width: 100%; max-height: 150px; margin-bottom: 10px; border-radius: 4px; box-shadow: 0 0 5px rgba(0, 0, 0, 0.1); } .hidden { display: none; } /* Styles not applied in PDF */ @media print { input { border: none !important; background-color: transparent !important; box-shadow: none !important; } table, th, td { border: 1px solid #ddd !important; } button { display: none !important; } }
JavaScript for Functionality
// Function to populate the invoice details function populateInvoice() { // No need to update the text content, as input values will be used // Populate invoice items const itemsTable = document.getElementById("invoice-items"); itemsTable.innerHTML = ""; let totalAmount = 0; // Sample data for the invoice const invoiceData = { items: [ { description: "Item 1", quantity: 2, unitPrice: 10.00 }, { description: "Item 2", quantity: 1, unitPrice: 20.00 }, // Add more items as needed ] }; invoiceData.items.forEach(item => { const total = item.quantity * item.unitPrice; totalAmount += total; const row = `<tr> <td><input type="text" class="description cmn-input" value="${item.description}"></td> <td><input type="number" class="quantity cmn-input" value="${item.quantity}"></td> <td><input type="number" class="unit-price cmn-input" value="${item.unitPrice.toFixed(2)}"></td> <td><input type="text" class="total cmn-input" value="${total.toFixed(2)}" readonly></td> <td class="btn-p"><button class="btn-rmv" onclick="removeRow(this)">Remove</button></td> </tr>`; itemsTable.innerHTML += row; }); // Update total amount document.getElementById("total-amount").value = totalAmount.toFixed(2); } // Function to calculate total on input change function calculateTotal() { document.querySelector("tbody").addEventListener("input", function (event) { const target = event.target; if (target.classList.contains("quantity") || target.classList.contains("unit-price")) { const row = target.closest("tr"); const quantity = parseFloat(row.querySelector(".quantity").value) || 0; const unitPrice = parseFloat(row.querySelector(".unit-price").value) || 0; const total = quantity * unitPrice; row.querySelector(".total").value = total.toFixed(2); // Recalculate total amount let newTotalAmount = 0; document.querySelectorAll(".total").forEach(function (el) { newTotalAmount += parseFloat(el.value) || 0; }); document.getElementById("total-amount").value = newTotalAmount.toFixed(2); } }); } // Function to add a new row to the table function addRow() { const newRow = `<tr> <td><input type="text" class="description"></td> <td><input type="number" class="quantity"></td> <td><input type="number" class="unit-price"></td> <td><input type="text" class="total" readonly></td> <td class="btn-p"><button class="btn-rmv" onclick="removeRow(this)">Remove</button></td> </tr>`; // document.getElementById("invoice-items").innerHTML += document.getElementById("invoice-items").innerHTML += newRow; } // Function to remove a row from the table function removeRow(button) { const row = button.closest("tr"); row.remove(); recalculateTotal(); } // Function to recalculate total after removing a row function recalculateTotal() { let newTotalAmount = 0; document.querySelectorAll(".total").forEach(function (el) { newTotalAmount += parseFloat(el.value) || 0; }); document.getElementById("total-amount").value = newTotalAmount.toFixed(2); } // Function to generate and download PDF function generatePDF() { // Hide the button before generating PDF document.querySelector("button").style.display = "none"; const allInputElem = document.querySelectorAll("input") for (var index = 0; index < allInputElem.length; index++) { allInputElem[index].style.border = 'none'; allInputElem[index].style.pointerEvents = 'none'; allInputElem[index].style.padding = '10px 0'; } document.querySelector(".btn-add").style.display = 'none'; document.querySelector("#client-image").style.display = 'none'; const allElem = document.querySelectorAll(".btn-p") for (var index = 0; index < allElem.length; index++) { allElem[index].style.display = 'none'; } const element = document.getElementById("invoice"); // Set the page size to 'a4' and include 'html2pdf' configuration const pdfConfig = { margin: 10, filename: 'invoice.pdf', image: { type: 'jpeg', quality: 0.98 }, html2canvas: { scale: 2 }, jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' } }; html2pdf(element, pdfConfig); // Show the button again after PDF generation document.querySelector("button").style.display = "block"; } function previewImage(input) { const preview = document.getElementById('client-preview'); const file = input.files[0]; if (file) { const reader = new FileReader(); reader.onload = function (e) { preview.src = e.target.result; preview.classList.remove('hidden'); }; reader.readAsDataURL(file); } else { // If no file is selected, show a placeholder image preview.src = 'https://via.placeholder.com/200x200'; preview.classList.remove('hidden'); } } function switchTab(tabId) { const previewTab = document.getElementById('preview-tab'); const editTab = document.getElementById('edit-tab'); if (tabId === 'preview-tab') { const allInputElem = document.querySelectorAll("input") for (var index = 0; index < allInputElem.length; index++) { allInputElem[index].style.border = 'none'; allInputElem[index].style.pointerEvents = 'none'; allInputElem[index].style.padding = '10px 0'; } document.querySelector(".btn-add").style.display = 'none'; document.querySelector("#client-image").style.display = 'none'; const allElem = document.querySelectorAll(".btn-p") for (var index = 0; index < allElem.length; index++) { allElem[index].style.display = 'none'; } previewTab.style.display = 'block'; editTab.style.display = 'none'; } else if (tabId === 'edit-tab') { const allInputElem = document.querySelectorAll("input") for (var index = 0; index < allInputElem.length; index++) { allInputElem[index].style.border = '1px solid #cccccc'; allInputElem[index].style.pointerEvents = 'auto'; allInputElem[index].style.padding = '10px'; } document.querySelector(".btn-add").style.display = 'block'; document.querySelector("#client-image").style.display = 'block'; const allElem = document.querySelectorAll(".btn-p") for (var index = 0; index < allElem.length; index++) { allElem[index].style.display = 'block'; } previewTab.style.display = 'none'; editTab.style.display = 'block'; } } // Call the functions to populate initial invoice details, calculate total, and set up event listeners populateInvoice(); calculateTotal();
Technologies Used
- HTML: Provides the structure of the web page.
- CSS: Adds styling to enhance the visual appeal of the invoice.
- JavaScript: Implements the dynamic functionality, including item addition, removal, and PDF generation.
- html2pdf Library: Enables the conversion of the HTML content into a downloadable PDF.
Key Features
1. Tab Switching
The invoice generator has two tabs: “Edit Invoice” and “Preview.” Users can seamlessly switch between these tabs, allowing for both customization and a preview of the final invoice.
2. Client Information
Users can input client details, including name and address. An image upload feature is also available for the client’s logo or picture.
3. Invoice Information
Details such as the invoice number and date can be easily entered and edited.
4. Dynamic Item Management
The heart of the invoice generator lies in its ability to dynamically manage items. Users can add, remove, and edit individual line items, with the total amount being updated in real-time.
5. Real-time Total Calculation
As users input quantities and unit prices, the total amount is automatically calculated and updated.
6. PDF Generation
The “Download PDF” button allows users to generate a professional PDF version of the invoice. This PDF includes all the entered details and a clean layout suitable for sharing or printing.
How It Works
Populating Invoice Details
The populateInvoice()
function initializes the invoice with sample data. This data includes item descriptions, quantities, and unit prices.
Dynamic Item Management
The calculateTotal()
function dynamically calculates the total amount as users input quantities and unit prices. The addRow()
and removeRow()
functions allow users to manage line items.
PDF Generation
The generatePDF()
function uses the html2pdf library to convert the invoice’s HTML content into a downloadable PDF. Before generating the PDF, it adjusts the styling for a clean and professional look.
Client Image Preview
The previewImage()
function allows users to preview the client’s image before uploading it to the invoice.
Tab Switching
The switchTab()
function manages the visibility of tabs, allowing users to switch between editing and previewing modes.
Live Demo
[ You might also like: How to Convert Images to WebP Format Using HTML, CSS, and JavaScript ]
Conclusion
This Dynamic Invoice Generator provides a user-friendly interface for creating and managing professional invoices. Its dynamic features, real-time calculations, and PDF generation capabilities make it a valuable tool for businesses and freelancers alike. Feel free to customize and expand upon this code to suit your specific invoicing needs!