# easy-invoice/2.3.6/assets/js/easy-invoice.js

Easy Invoice – Invoice Generator, PDF Quotes &amp; Payments, version 2.3.6. 425 lines.

- Page: https://pluginprobe.com/plugins/easy-invoice/2.3.6/code/assets/js/easy-invoice.js
- Raw: https://pluginprobe.com/plugins/easy-invoice/2.3.6/raw/assets/js/easy-invoice.js
- Modified: 2025-08-14T09:51:16+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/easy-invoice/2.3.6/code/assets/js/easy-invoice.js#L10-L20`.

```javascript
jQuery(document).ready(function($) {
    // Check if we're on the builder page to avoid conflicts with invoice-builder.js
    const isBuilderPage = window.location.href.indexOf('easy-invoice-new') !== -1 || 
                         window.location.href.indexOf('page=easy-invoice-new') !== -1 ||
                         window.location.href.indexOf('easy-invoice-edit') !== -1 ||
                         window.location.href.indexOf('page=easy-invoice-edit') !== -1;
    

    
    // If we're on the builder page, don't initialize the old invoice builder
    // as it will conflict with the new invoice-builder.js
    if (isBuilderPage) {
        return;
    }
    
    // Initialize invoice builder
    function initInvoiceBuilder() {
        // Wait for the template to be available
        const template = document.getElementById('invoice-item-template');
        if (!template) {
            // Invoice item template not found
            return;
        }

        // Initialize tabs
        initTabs();

        // Add event listener for "Add Item" button - now handled in invoice-builder.js

        // Add event listener for removing items
        $(document).on('click', '.remove-item', function() {
            const item = $(this).closest('.invoice-item');
            item.addClass('opacity-0');
            setTimeout(() => {
                item.remove();
                updatePreview();
            }, 200);
        });

        // Add event listener for collapsing/expanding items
        $(document).on('click', '.item-collapse-toggle', function() {
            const item = $(this).closest('.invoice-item');
            const itemContent = item.find('.item-content');
            const summaryElement = item.find('.item-collapsed-summary');
            const icon = $(this).find('i');
            
            // Update the summary before collapsing
            if (itemContent.is(':visible')) {
                updateItemSummary(item);
                // Collapse
                itemContent.slideUp(200);
                summaryElement.slideDown(200);
                icon.removeClass('fa-chevron-down').addClass('fa-chevron-right');
            } else {
                // Expand
                itemContent.slideDown(200);
                summaryElement.slideUp(200);
                icon.removeClass('fa-chevron-right').addClass('fa-chevron-down');
            }
        });

        // Add event listeners for all form inputs
        $('#invoice-form').on('input', 'input, textarea', function() {
            updatePreview();
        });

        // Add event listeners for taxable item clicking
        $(document).on('click', '.taxable-item-container', function(e) {
            // Don't toggle if clicking directly on the checkbox (let the default behavior handle it)
            if (e.target.type === 'checkbox') {
                return;
            }
            
            // Prevent default behavior if this is a label click to avoid double toggling
            if (e.target.tagName.toLowerCase() === 'label') {
                e.preventDefault();
            }
            
            const checkbox = $(this).find('input[type="checkbox"]');
            checkbox.prop('checked', !checkbox.prop('checked'));
            updatePreview();
        });

        // Add event listener for taxable checkbox changes
        $(document).on('change', 'input[name="item-taxable[]"]', function() {
            updatePreview();
        });

        // Add auto-calculate for quantity and price
        $(document).on('input', 'input[name="item-quantity[]"], input[name="item-price[]"]', function() {
            const item = $(this).closest('.invoice-item');
            calculateItemTotal(item);
        });

        // Add keyboard shortcuts
        $(document).on('keydown', function(e) {
            // Ctrl/Cmd + Enter to save
            if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
                e.preventDefault();
                $('button:contains("Save Draft")').click();
            }
            
            // Ctrl/Cmd + Shift + Enter to send
            if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === 'Enter') {
                e.preventDefault();
                $('button:contains("Send Invoice")').click();
            }
        });

        // Initialize with one item
    }

    // Initialize tab functionality
    function initTabs() {
        // Check if we're on an invoice page - if so, let invoice-builder.js handle tabs
        if (window.location.href.includes('easy-invoice-builder') || 
            window.location.href.includes('easy-invoice-new') ||
            $('.invoice-item').length > 0) {
            return;
        }
        
        // Add click event listeners to tab buttons
        $('.tab-button').on('click', function() {
            const tabId = $(this).data('tab');
            
            // Remove active class from all tabs and buttons
            $('.tab-button').removeClass('active');
            $('.tab-button').removeClass('text-indigo-600').addClass('text-gray-500');
            $('.tab-button').removeClass('border-indigo-500').addClass('border-transparent');
            $('.tab-content').removeClass('active').addClass('hidden');
            
            // Add active class to clicked tab and its content
            $(this).addClass('active');
            $(this).removeClass('text-gray-500').addClass('text-indigo-600');
            $(this).removeClass('border-transparent').addClass('border-indigo-500');
            $('#' + tabId).removeClass('hidden').addClass('active');
            
            // If the items tab is active, make sure the items are properly sized
            if (tabId === 'items-tab') {
                updateItemSummaries();
            }
        });
        
        // Handle tab navigation via keyboard
        $('.tab-button').on('keydown', function(e) {
            // Arrow right or arrow down
            if (e.key === 'ArrowRight' || e.key === 'ArrowDown') {
                e.preventDefault();
                const nextTab = $(this).next('.tab-button');
                if (nextTab.length) {
                    nextTab.click();
                    nextTab.focus();
                }
            }
            
            // Arrow left or arrow up
            if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') {
                e.preventDefault();
                const prevTab = $(this).prev('.tab-button');
                if (prevTab.length) {
                    prevTab.click();
                    prevTab.focus();
                }
            }
        });
    }

    // Update all item summaries
    function updateItemSummaries() {
        $('.invoice-item').each(function() {
            updateItemSummary($(this));
        });
    }

    // Add a new invoice item
     

    // Calculate total for a single item
    function calculateItemTotal(item) {
        const quantity = parseFloat(item.find('input[name="item-quantity[]"]').val()) || 0;
        const price = parseFloat(item.find('input[name="item-price[]"]').val()) || 0;
        const isTaxable = item.find('input[name="item-taxable[]"]').is(':checked');
        const pricesIncludeTax = $('#prices-include-tax').val() === 'yes';
        const taxRate = parseFloat($('#tax-rate').val()) || 0;
        
        let total = quantity * price;
        
        // We don't need to adjust the displayed total in the item form
        // The tax/non-tax calculation is handled in the updatePreview function
        
        item.find('input[name="item-total[]"]').val(total.toFixed(2));
        
        // Update the summary fields
        updateItemSummary(item);
        
        updatePreview();
    }

    // Update the collapsed summary of an item
    function updateItemSummary(item) {
        const quantity = parseFloat(item.find('input[name="item-quantity[]"]').val()) || 0;
        const price = parseFloat(item.find('input[name="item-price[]"]').val()) || 0;
        const total = quantity * price;
        
        item.find('.quantity-summary').text(quantity);
        item.find('.price-summary').text(price.toFixed(2));
        item.find('.total-summary').text(total.toFixed(2));
    }

    // Update the preview panel
    function updatePreview() {
        // Add loading state
        $('#preview-items').addClass('loading');
        
        // Update customer info
        $('#preview-customer-name').text($('#customer-name').val() || 'Customer Name');
        $('#preview-customer-email').text($('#customer-email').val() || 'customer@example.com');
        
        // Update invoice details
        $('#preview-invoice-number').text($('#invoice-number').val());
        $('#preview-invoice-date').text(formatDate($('#invoice-date').val()));
        $('#preview-due-date').text(formatDate($('#due-date').val()));
        $('#preview-invoice-title').text($('#invoice-title').val() || 'Invoice Title');
        $('#preview-invoice-description').text($('#invoice-description').val() || 'Invoice description will appear here.');
        
        // Get tax settings
        const taxRate = parseFloat($('#tax-rate').val()) || 0;
        const pricesIncludeTax = $('#prices-include-tax').val() === 'yes';
        
        // Update items
        let subtotal = 0;
        let taxableSubtotal = 0;
        const previewItemsContainer = $('#preview-items');
        previewItemsContainer.empty();
        
        $('.invoice-item').each(function() {
            const title = $(this).find('input[name="item-title[]"]').val() || 'Item Title';
            const description = $(this).find('textarea[name="item-description[]"]').val() || '';
            const quantity = parseFloat($(this).find('input[name="item-quantity[]"]').val()) || 0;
            let price = parseFloat($(this).find('input[name="item-price[]"]').val()) || 0;
            const isTaxable = $(this).find('input[name="item-taxable[]"]').is(':checked');
            
            // If prices include tax and this item is taxable, we need to extract the tax
            // to get the real pre-tax price for calculations
            let displayPrice = price;
            if (pricesIncludeTax && isTaxable) {
                price = price / (1 + (taxRate / 100));
            }
            
            const total = quantity * price;
            
            subtotal += total;
            if (isTaxable) {
                taxableSubtotal += total;
            }
            
            const itemRow = `
                <tr class="border-b border-gray-200">
                    <td class="px-3 py-4">
                        <div class="font-medium text-gray-900 flex items-center">
                            ${title}
                            ${isTaxable ? '<i class="fas fa-percentage ml-2 text-xs text-green-500" title="Taxable item"></i>' : ''}
                        </div>
                        ${description ? `<div class="text-sm text-gray-500">${description}</div>` : ''}
                    </td>
                    <td class="px-3 py-4 whitespace-nowrap text-sm text-gray-500 text-right">${quantity}</td>
                    <td class="px-3 py-4 whitespace-nowrap text-sm text-gray-500 text-right">$${pricesIncludeTax ? displayPrice.toFixed(2) : price.toFixed(2)}</td>
                    <td class="px-3 py-4 whitespace-nowrap text-sm text-gray-500 text-right">$${(quantity * (pricesIncludeTax ? displayPrice : price)).toFixed(2)}</td>
                </tr>
            `;
            previewItemsContainer.append(itemRow);
        });
        
        // Get discount and tax values
        const discountValue = parseFloat($('#discount').val()) || 0;
        const discountType = $('#discount-type').val();
        const calculationMethod = $('#calculation-method').val();
        
        // Calculate discount amount based on type
        let discountAmount = 0;
        if (discountType === 'percentage') {
            discountAmount = subtotal * (discountValue / 100);
        } else { // fixed amount
            discountAmount = Math.min(discountValue, subtotal); // Can't discount more than subtotal
        }
        
        // Calculate tax and total based on calculation method
        let taxableAmount = 0;
        let tax = 0;
        let total = 0;
        
        if (calculationMethod === 'before_tax') {
            // Apply discount before calculating tax
            // Distribute discount proportionally between taxable and non-taxable items
            let taxableDiscount = 0;
            if (subtotal > 0) {
                taxableDiscount = discountAmount * (taxableSubtotal / subtotal);
            }
            taxableAmount = taxableSubtotal - taxableDiscount;
            tax = taxableAmount * (taxRate / 100);
            total = subtotal - discountAmount + tax;
        } else { // after_tax
            // Calculate tax first, then apply discount
            tax = taxableSubtotal * (taxRate / 100);
            total = subtotal + tax - discountAmount;
        }
        
        // For display purposes, if prices already include tax, we need to adjust the subtotal and tax display
        let displaySubtotal = subtotal;
        let displayTax = tax;
        
        if (pricesIncludeTax) {
            // If prices include tax, the subtotal shown should be the sum of entered prices
            // which already include tax for taxable items
            displaySubtotal = 0;
            $('.invoice-item').each(function() {
                const quantity = parseFloat($(this).find('input[name="item-quantity[]"]').val()) || 0;
                const price = parseFloat($(this).find('input[name="item-price[]"]').val()) || 0;
                displaySubtotal += quantity * price;
            });
            
            // The tax shown is the portion of the entered prices that represents tax
            displayTax = taxableSubtotal * (taxRate / 100);
            
            // Adjust the total if we're using the "before tax" calculation method
            if (calculationMethod === 'before_tax') {
                // The total remains the same, but the displayed components change
                total = displaySubtotal - discountAmount;
            }
        }
        
        // Ensure total is not negative
        total = Math.max(0, total);
        
        // Update preview elements
        $('#preview-subtotal').text('$' + displaySubtotal.toFixed(2));
        $('#preview-discount').text('$' + discountAmount.toFixed(2));
        $('#preview-tax-rate').text(taxRate);
        $('#preview-tax').text('$' + displayTax.toFixed(2));
        $('#preview-total').text('$' + total.toFixed(2));
        
        // Update notes and terms
        $('#preview-notes').text($('#notes').val() || 'Thank you for your business!');
        $('#preview-terms').text($('#terms').val() || 'Payment is due within 30 days. Please make checks payable to Company Name.');
        
        // Remove loading state
        setTimeout(() => {
            $('#preview-items').removeClass('loading');
        }, 300);
    }

    // Format date as MMMM D, YYYY
    function formatDate(dateString) {
        if (!dateString) return '';
        const date = new Date(dateString);
        return date.toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' });
    }

    // Initialize all components
    function init() {
        initInvoiceBuilder();
    }

    // Start initialization
    init();
});

function initSettingsForm() {
    const $ = jQuery;
    
    // Handle company logo upload
    $('.settings-page').on('click', '.upload-logo', function(e) {
        e.preventDefault();
        
        const image = wp.media({
            title: 'Upload Company Logo',
            multiple: false
        }).open()
        .on('select', function() {
            const uploadedImage = image.state().get('selection').first();
            const imageUrl = uploadedImage.toJSON().url;
            
            // Update logo preview
            $('.company-logo-preview').attr('src', imageUrl);
            
            // Store image URL in hidden field
            $('#company-logo-url').val(imageUrl);
        });
    });
    
    // Handle form submission
    $('.settings-form').on('submit', function(e) {
        e.preventDefault();
        
        const formData = $(this).serialize();
        
        $.ajax({
            url: easyInvoice.ajaxUrl,
            type: 'POST',
            data: {
                action: 'save_easy_invoice_settings',
                nonce: easyInvoice.nonce,
                ...formData
            },
            success: function(response) {
                if (response.success) {
                    // Show success message
                    if (typeof EasyInvoiceToast !== 'undefined') {
                        EasyInvoiceToast.show('success', 'Settings saved successfully!');
                    }
                } else {
                    // Show error message
                    if (typeof EasyInvoiceToast !== 'undefined') {
                        EasyInvoiceToast.show('error', 'Error saving settings. Please try again.');
                    }
                }
            },
            error: function() {
                if (typeof EasyInvoiceToast !== 'undefined') {
                    EasyInvoiceToast.show('error', 'Error saving settings. Please try again.');
                }
            }
        });
    });
} 
```
