// ES6 Compliant Syntax
// GitHub Copilot - GPT-4 Model - October 14, 2025
// File: invoice-generation/index.js
const licenseKey = 'YOUR_WEBVIEWER_LICENSE_KEY';
// Global variables
const element = document.getElementById('viewer');
let documentViewer = null;
let sampleData = {};
let templateApplied = false;
const defaultDoc = 'https://apryse.s3.us-west-1.amazonaws.com/public/files/samples/invoice_template.docx';
// Initialize WebViewer
WebViewer({
path: '/lib',
licenseKey: licenseKey,
}, element).then((instance) => {
documentViewer = instance.Core.documentViewer;
loadTemplateDocument(); // Load the default template document, initialize sample data and generate input fields
});
// Load default template document and initialize sample data
const loadTemplateDocument = async () => {
// Load DOCX template
await documentViewer.loadDocument(defaultDoc, {extension: 'docx'});
templateApplied = false;
// Initialize sample data
sampleData = {
invoice_number: '3467821',
bill_to_name: 'Victoria Guti\u00e9rrez',
ship_to_name: 'Mar\u00eda Rosales',
items: [
{ description: 'Item 1', qty: '1', price: '10.00', total: '10.00' },
{ description: 'Item 2', qty: '20', price: '20.00', total: '400.00' },
{ description: 'Item 3', qty: '1', price: '0.00', total: '0.00' },
{ description: 'Item 4', qty: '1', price: '0.00', total: '0.00' },
],
subtotal: '410.00',
sales_tax_rate: '5.0%',
sales_t: '20.50',
total_t: '500.00',
};
generateInputFields(); // Generate input fields based on default sampleData values
};
const fillTemplate = async () => {
// Update sampleData from the input field values
// Each field is identified by its unique ID
// The ID was originally generated from sampleData key for the fixed fields, or from (key, index, subKey) for array items
Object.keys(sampleData).forEach(key => {
if (Array.isArray(sampleData[key])) {
// array field items
sampleData[key].forEach((item, index) => {
Object.keys(item).forEach(subKey => {
const input = document.getElementById(`${key}_${index}_${subKey}`.toLowerCase());
sampleData[key][index][subKey] = input.value;
});
});
}
else {
// non-array (fixed) fields
const input = document.getElementById(key.toLowerCase());
sampleData[key] = input.value;
}
});
// Apply JSON data to the PDF
if(templateApplied) // reload the template document if it has already been applied
await documentViewer.loadDocument(defaultDoc, {extension: 'docx'});
await documentViewer.getDocument().applyTemplateValues(sampleData);
templateApplied = true;
};
// Generate input fields based on the sampleData structure
// The left div contains the fixed text inputs for non-array fields
// The right div contains the dynamic text inputs for array fields (items).
// Each array item corresponds to a row in the table (loop in the template)
const generateInputFields = () => {
// clear previous controls from the 2 divs
leftDiv.innerHTML = rightDiv.innerHTML = '';
Object.keys(sampleData).forEach(key => {
if (Array.isArray(sampleData[key])) {
// array field - create multiple text inputs for each item in the array in the right div
sampleData[key].forEach((item, index) => {
// create a header row before the first row
if (index === 0) {
Object.keys(item).forEach(subKey => {
const desc = document.createElement('input');
desc.type = 'text';
desc.disabled = true;
desc.value = subKey || '';
rightDiv.appendChild(desc);
});
rightDiv.appendChild(document.createElement('br'));
}
// create the rows that correspond to each item in the array
Object.keys(item).forEach(subKey => {
const input = document.createElement('input');
// generate a unique ID for each input based on the key, index, and subKey
input.id = `${key}_${index}_${subKey}`.toLowerCase();
input.type = 'text';
input.value = item[subKey] || '';
rightDiv.appendChild(input);
});
// add a delete button for each row
const deleteButton = document.createElement('button');
deleteButton.textContent = '\u2716'; // Unicode for '✖' symbol
deleteButton.className = 'btn-delete';
deleteButton.onclick = () => {
sampleData[key].splice(index, 1);
generateInputFields(); // regenerate the input fields to reflect the deletion
}
rightDiv.appendChild(deleteButton);
rightDiv.appendChild(document.createElement('br'));
});
}
else { // create text input for each field in the left div
const desc = document.createElement('input');
desc.disabled = true;
desc.type = 'text';
desc.value = key + ': ';
leftDiv.appendChild(desc);
const input = document.createElement('input');
input.type = 'text';
input.value = sampleData[key] || '';
// use the key as the ID for easy lookup later
input.id = key.toLowerCase();
leftDiv.appendChild(input);
leftDiv.appendChild(document.createElement('br'));
}
});
// create a button to add a new item to the items array
const addRowButton = document.createElement('button');
addRowButton.textContent = 'Add Row';
addRowButton.className = 'btn';
addRowButton.onclick = () => {
const randQty = Math.floor(Math.random() * 20) + 1;
sampleData.items.push({ description: `Item ${sampleData.items.length + 1}`, qty: randQty.toString(), price: '0.00', total: '0.00' });
generateInputFields(); // regenerate the input fields to reflect the new row
};
addRowButton.disabled = sampleData.items.length >= 10; // limit to 10 items
rightDiv.appendChild(addRowButton);
}
// UI section
// Create a container for the controls
const controlsContainer = document.createElement('div');
// Create 2 divs inside the container for left and right sections
const leftDiv = document.createElement('div');
const rightDiv = document.createElement('div');
leftDiv.className = rightDiv.className = 'vertical-container'; // side-by-side divs using (display: inline-block) and (vertical-align: top)
leftDiv.style.width = "40%";
rightDiv.style.width = "60%"; // right div is wider to accommodate table rows
controlsContainer.appendChild(leftDiv);
controlsContainer.appendChild(rightDiv);
const fillInvoiceButton = document.createElement('button');
fillInvoiceButton.className = 'btn';
fillInvoiceButton.textContent = 'Fill Template';
fillInvoiceButton.onclick = async () => {
await fillTemplate(); // generate the invoice by filling the template with data
};
controlsContainer.appendChild(fillInvoiceButton);
const resetDocumentButton = document.createElement('button');
resetDocumentButton.className = 'btn';
resetDocumentButton.textContent = '🗘 Reset Document';
resetDocumentButton.onclick = async () => {
await loadTemplateDocument(); // reset document, data and input fields
};
controlsContainer.appendChild(resetDocumentButton);
element.insertBefore(controlsContainer, element.firstChild);