Invoice Generation Showcase Demo Code Sample

Requirements
View Demo

Easily generate invoices using loops, denoted by {{loop var}} and {{endloop}} clauses. Loops are used to repeat content in a document and fill in unique data for each repetition - especially when building tables.

Note: The demo focuses on generating rows with the provided data. It does not perform calculations on invoice items.

This demo allows you to:

  • Add items to your invoice and automatically generate a new row for each entry.
  • Export your invoice as a PDF, PNG, or Office file format for easy sharing or further editing.
  • Customize input fields with your own data to tailor the invoice to your needs.

Implementation steps
To add Invoice Generation capability with WebViewer:

Step 1: Get started with WebViewer in your preferred web stack.
Step 2: Add the ES6 JavaScript sample code provided in this guide.

Once you generate your license key, it will automatically be included in your sample code below.

License Key

1// ES6 Compliant Syntax
2// GitHub Copilot - GPT-4 Model - October 14, 2025
3// File: invoice-generation/index.js
4
5const licenseKey = 'YOUR_WEBVIEWER_LICENSE_KEY';
6
7// Global variables
8const element = document.getElementById('viewer');
9let documentViewer = null;
10let sampleData = {};
11let templateApplied = false;
12const defaultDoc = 'https://apryse.s3.us-west-1.amazonaws.com/public/files/samples/invoice_template.docx';
13
14// Initialize WebViewer
15WebViewer({
16 path: '/lib',
17 licenseKey: licenseKey,
18}, element).then((instance) => {
19 documentViewer = instance.Core.documentViewer;
20 loadTemplateDocument(); // Load the default template document, initialize sample data and generate input fields
21});
22
23// Load default template document and initialize sample data
24const loadTemplateDocument = async () => {
25 // Load DOCX template
26 await documentViewer.loadDocument(defaultDoc, {extension: 'docx'});
27 templateApplied = false;
28 // Initialize sample data
29 sampleData = {
30 invoice_number: '3467821',
31 bill_to_name: 'Victoria Guti\u00e9rrez',
32 ship_to_name: 'Mar\u00eda Rosales',
33 items: [
34 { description: 'Item 1', qty: '1', price: '10.00', total: '10.00' },
35 { description: 'Item 2', qty: '20', price: '20.00', total: '400.00' },
36 { description: 'Item 3', qty: '1', price: '0.00', total: '0.00' },
37 { description: 'Item 4', qty: '1', price: '0.00', total: '0.00' },
38 ],
39 subtotal: '410.00',
40 sales_tax_rate: '5.0%',
41 sales_t: '20.50',
42 total_t: '500.00',
43 };
44 generateInputFields(); // Generate input fields based on default sampleData values
45};
46
47const fillTemplate = async () => {
48 // Update sampleData from the input field values
49 // Each field is identified by its unique ID
50 // The ID was originally generated from sampleData key for the fixed fields, or from (key, index, subKey) for array items
51 Object.keys(sampleData).forEach(key => {
52 if (Array.isArray(sampleData[key])) {
53 // array field items
54 sampleData[key].forEach((item, index) => {
55 Object.keys(item).forEach(subKey => {
56 const input = document.getElementById(`${key}_${index}_${subKey}`.toLowerCase());
57 sampleData[key][index][subKey] = input.value;
58 });
59 });
60 }
61 else {
62 // non-array (fixed) fields
63 const input = document.getElementById(key.toLowerCase());
64 sampleData[key] = input.value;
65 }
66 });
67 // Apply JSON data to the PDF
68 if(templateApplied) // reload the template document if it has already been applied
69 await documentViewer.loadDocument(defaultDoc, {extension: 'docx'});
70 await documentViewer.getDocument().applyTemplateValues(sampleData);
71 templateApplied = true;
72};
73
74// Generate input fields based on the sampleData structure
75// The left div contains the fixed text inputs for non-array fields
76// The right div contains the dynamic text inputs for array fields (items).
77// Each array item corresponds to a row in the table (loop in the template)
78const generateInputFields = () => {
79 // clear previous controls from the 2 divs
80 leftDiv.innerHTML = rightDiv.innerHTML = '';
81 Object.keys(sampleData).forEach(key => {
82 if (Array.isArray(sampleData[key])) {
83 // array field - create multiple text inputs for each item in the array in the right div
84 sampleData[key].forEach((item, index) => {
85 // create a header row before the first row
86 if (index === 0) {
87 Object.keys(item).forEach(subKey => {
88 const desc = document.createElement('input');
89 desc.type = 'text';
90 desc.disabled = true;
91 desc.value = subKey || '';
92 rightDiv.appendChild(desc);
93 });
94 rightDiv.appendChild(document.createElement('br'));
95 }
96 // create the rows that correspond to each item in the array
97 Object.keys(item).forEach(subKey => {
98 const input = document.createElement('input');
99 // generate a unique ID for each input based on the key, index, and subKey
100 input.id = `${key}_${index}_${subKey}`.toLowerCase();
101 input.type = 'text';
102 input.value = item[subKey] || '';
103 rightDiv.appendChild(input);
104 });
105 // add a delete button for each row
106 const deleteButton = document.createElement('button');
107 deleteButton.textContent = '\u2716'; // Unicode for '✖' symbol
108 deleteButton.className = 'btn-delete';
109 deleteButton.onclick = () => {
110 sampleData[key].splice(index, 1);
111 generateInputFields(); // regenerate the input fields to reflect the deletion
112 }
113 rightDiv.appendChild(deleteButton);
114 rightDiv.appendChild(document.createElement('br'));
115 });
116 }
117 else { // create text input for each field in the left div
118 const desc = document.createElement('input');
119 desc.disabled = true;
120 desc.type = 'text';
121 desc.value = key + ': ';
122 leftDiv.appendChild(desc);
123 const input = document.createElement('input');
124 input.type = 'text';
125 input.value = sampleData[key] || '';
126 // use the key as the ID for easy lookup later
127 input.id = key.toLowerCase();
128 leftDiv.appendChild(input);
129 leftDiv.appendChild(document.createElement('br'));
130 }
131 });
132 // create a button to add a new item to the items array
133 const addRowButton = document.createElement('button');
134 addRowButton.textContent = 'Add Row';
135 addRowButton.className = 'btn';
136 addRowButton.onclick = () => {
137 const randQty = Math.floor(Math.random() * 20) + 1;
138 sampleData.items.push({ description: `Item ${sampleData.items.length + 1}`, qty: randQty.toString(), price: '0.00', total: '0.00' });
139 generateInputFields(); // regenerate the input fields to reflect the new row
140 };
141 addRowButton.disabled = sampleData.items.length >= 10; // limit to 10 items
142 rightDiv.appendChild(addRowButton);
143}
144
145// UI section
146
147// Create a container for the controls
148const controlsContainer = document.createElement('div');
149
150// Create 2 divs inside the container for left and right sections
151const leftDiv = document.createElement('div');
152const rightDiv = document.createElement('div');
153leftDiv.className = rightDiv.className = 'vertical-container'; // side-by-side divs using (display: inline-block) and (vertical-align: top)
154leftDiv.style.width = "40%";
155rightDiv.style.width = "60%"; // right div is wider to accommodate table rows
156controlsContainer.appendChild(leftDiv);
157controlsContainer.appendChild(rightDiv);
158
159const fillInvoiceButton = document.createElement('button');
160fillInvoiceButton.className = 'btn';
161fillInvoiceButton.textContent = 'Fill Template';
162fillInvoiceButton.onclick = async () => {
163 await fillTemplate(); // generate the invoice by filling the template with data
164};
165controlsContainer.appendChild(fillInvoiceButton);
166
167const resetDocumentButton = document.createElement('button');
168resetDocumentButton.className = 'btn';
169resetDocumentButton.textContent = '🗘 Reset Document';
170resetDocumentButton.onclick = async () => {
171 await loadTemplateDocument(); // reset document, data and input fields
172};
173controlsContainer.appendChild(resetDocumentButton);
174element.insertBefore(controlsContainer, element.firstChild);
175

Did you find this helpful?

Trial setup questions?

Ask experts on Discord

Need other help?

Contact Support

Pricing or product questions?

Contact Sales