// ES6 Compliant Syntax
// Copilot name: GitHub Copilot, version: 1.0.0, model: GPT-4, version: 2024-06, date: 2025-09-18
// File: pdf-form/index.js
import WebViewer from '@pdftron/webviewer';
const licenseKey = 'YOUR_WEBVIEWER_LICENSE_KEY';
const initialDoc = 'https://apryse.s3.us-west-1.amazonaws.com/public/files/samples/form-1040.pdf';
// Sample JSON data to fill the form-1040.pdf
const sampleJSONData = {
"topmostSubform[0].Page1[0].f1_04[0]": "Jennifer F.",
"topmostSubform[0].Page1[0].f1_05[0]": "Ng",
"topmostSubform[0].Page1[0].f1_06[0]": "0100101",
"topmostSubform[0].Page1[0].f1_07[0]": "Dana",
"topmostSubform[0].Page1[0].f1_08[0]": "York",
"topmostSubform[0].Page1[0].SpouseSSN[0]": "1111111",
"topmostSubform[0].Page1[0].SpouseSSN[0].f1_09[0]": "1111111",
"topmostSubform[0].Page1[0].Address[0]": "hi",
"topmostSubform[0].Page1[0].Address[0].f1-10[0]": "123 Broadway Avenue",
"topmostSubform[0].Page1[0].Address[0].f1-11[0]": "24",
"topmostSubform[0].Page1[0].Address[0].f1-12[0]": "Los Angeles, California, 90210",
"topmostSubform[0].Page1[0].Address[0].f1_13[0]": "Canada",
"topmostSubform[0].Page1[0].Address[0].f1_14[0]": "British Columbia",
"topmostSubform[0].Page1[0].Address[0].f1_15[0]": "V6T1PX",
"topmostSubform[0].Page1[0].c1_01[0]": "1",
"topmostSubform[0].Page1[0].c1_02[0]": "Off",
"topmostSubform[0].Page1[0].Lines1-3[0]": "2",
"topmostSubform[0].Page1[0].Lines1-3[0].c1_03[0]": "Off",
"topmostSubform[0].Page1[0].Lines1-3[0].c1_03[1]": "Off",
"topmostSubform[0].Page1[0].Lines1-3[0].c1_03[2]": "Off",
"topmostSubform[0].Page1[0].Lines1-3[0].f1-16[0]": "Off"
};
// Temporary JSON data to hold the live form data
let tempJSONData = {};
// The list of registered panels in the main webviewer
let viewerPanels = null;
// The tab panel, representing the webviewer left panel
const tabPanel = {
handle: null,
dataElement: 'tabPanel'
};
// The custom form sub-panel to be registered
const formPanel = {
handle: null,
dataElement: 'formPanel',
render: null,
};
WebViewer(
{
path: '/lib',
fullAPI: true, // This is required to download the pdf with flattening contents.
initialDoc: initialDoc,
enableFilePicker: true, // Enable file picker to open files. In WebViewer -> menu icon -> Open File
enableMeasurement: true,
licenseKey: licenseKey, // Replace with your license key
},
document.getElementById('viewer')
).then((instance) => {
const { documentViewer, annotationManager } = instance.Core;
// Customize the main webviewer left panel after the load completion.
// The left panel will display the template sub-panel with fields.
documentViewer.addEventListener('documentLoaded', () => {
customizeUI(instance);
// Reset the temporary JSON data when a new document is loaded
tempJSONData = {};
});
// Capture form field changes and update the temporary JSON data
annotationManager.addEventListener('fieldChanged', (field, value) => {
if (tempJSONData[field.name])
tempJSONData[field.name] = value;
else
tempJSONData = { ...tempJSONData, [field.name]: value };
// Update the scroll box text content with the latest JSON data
const scrollBoxText = formPanel.render.querySelector("#scrollBox").querySelector("pre");
scrollBoxText.textContent = JSON.stringify(tempJSONData, null, 2);
// Enable the "Open in Dialog" button
enableButton(formPanel.render.querySelector("#jsonDataDialogButton"), true);
});
console.log('✅ WebViewer loaded successfully.');
}).catch((error) => {
console.error('❌ Failed to initialize WebViewer:', error);
});
// Customize the main webviewer left panel after the load completion
const customizeUI = (instance) => {
const { UI } = instance;
// Close the tab panel (if it's open) for refreshment.
UI.closeElements([tabPanel.dataElement]);
// Get the list of registered panels in the main webviewer
viewerPanels = UI.getPanels();
// Find the Tab Panel to modify. The template sub-panel will be added to this Tab panel.
tabPanel.handle = viewerPanels.find((panel) => panel.dataElement === tabPanel.dataElement);
// Register the custom form sub-panel
RegisterFormPanel(instance);
// Add the new custom form sub-panel to list of sub-panels under the Tab Panel
formPanel.handle = { render: formPanel.dataElement };
tabPanel.handle.panelsList = [formPanel.handle, ...tabPanel.handle.panelsList];
UI.openElements([tabPanel.dataElement]);
UI.setPanelWidth(tabPanel.dataElement, 400);
};
// Register the custom form sub-panel
const RegisterFormPanel = (instance) => {
formPanel.render = createFormPanelElements(instance);
instance.UI.addPanel({
dataElement: formPanel.dataElement,
location: 'left',
icon: '<svg width="18px" height="18px" viewBox="0 0 24 24" id="圖層_1" data-name="圖層 1" xmlns="http://www.w3.org/2000/svg"><defs><style>.cls-1{fill:#080808;}</style></defs><title>template</title><path class="cls-1" d="M21,.5H3a2,2,0,0,0-2,2V22a2,2,0,0,0,2,2H21a2,2,0,0,0,2-2V2.5A2,2,0,0,0,21,.5Zm0,2v2H3v-2ZM3,22V6.5H21V22Z"/><path class="cls-1" d="M12.5,4H20a.5.5,0,0,0,0-1H12.5a.5.5,0,0,0,0,1Z"/><path class="cls-1" d="M4.5,4a.43.43,0,0,0,.19,0,.35.35,0,0,0,.16-.11A.47.47,0,0,0,5,3.5a.43.43,0,0,0,0-.19.36.36,0,0,0-.11-.16.5.5,0,0,0-.7,0A.35.35,0,0,0,4,3.31.43.43,0,0,0,4,3.5a.51.51,0,0,0,.5.5Z"/><path class="cls-1" d="M5.65,3.85A.36.36,0,0,0,5.81,4,.44.44,0,0,0,6,4a.47.47,0,0,0,.35-.15.36.36,0,0,0,.11-.16.6.6,0,0,0,0-.19.51.51,0,0,0-.15-.35A.49.49,0,0,0,5.81,3a.36.36,0,0,0-.16.11.47.47,0,0,0-.15.35.4.4,0,0,0,0,.19A.35.35,0,0,0,5.65,3.85Z"/><path class="cls-1" d="M8,8H4.5a1,1,0,0,0,0,2H8A1,1,0,0,0,8,8Z"/><path class="cls-1" d="M8,11.67H4.5a1,1,0,0,0,0,2H8a1,1,0,0,0,0-2Z"/><path class="cls-1" d="M8,15.33H4.5a1,1,0,0,0,0,2H8a1,1,0,0,0,0-2Z"/><path class="cls-1" d="M8,19H4.5a1,1,0,0,0,0,2H8a1,1,0,0,0,0-2Z"/><path class="cls-1" d="M14,8H10.5a1,1,0,0,0,0,2H14a1,1,0,0,0,0-2Z"/><path class="cls-1" d="M14,11.67H10.5a1,1,0,0,0,0,2H14a1,1,0,0,0,0-2Z"/><path class="cls-1" d="M14,15.33H10.5a1,1,0,0,0,0,2H14a1,1,0,0,0,0-2Z"/><path class="cls-1" d="M14,19H10.5a1,1,0,0,0,0,2H14a1,1,0,0,0,0-2Z"/><path class="cls-1" d="M19.5,8h-3a1,1,0,0,0,0,2h3a1,1,0,0,0,0-2Z"/><path class="cls-1" d="M19.5,11.67h-3a1,1,0,0,0,0,2h3a1,1,0,0,0,0-2Z"/><path class="cls-1" d="M19.5,15.33h-3a1,1,0,0,0,0,2h3a1,1,0,0,0,0-2Z"/><path class="cls-1" d="M19.5,19h-3a1,1,0,0,0,0,2h3a1,1,0,0,0,0-2Z"/></svg>',
title: 'Form',
render: () => formPanel.render,
});
};
// Create the form panel elements.
// The elements will be created dynamically
// when the form panel is registered after
// loading the document.
const createFormPanelElements = (instance) => {
let panelDiv = document.createElement('div');
panelDiv.id = 'form';
let paragraph = document.createTextNode('A demo of the PDF form capabilities in WebViewer, a JavaScript-based PDF SDK for web apps. Programmatically fill and extract data from forms with JavaScript.');
panelDiv.appendChild(paragraph);
let dividerDiv = document.createElement('div');
dividerDiv.style.borderTop = '1px solid #ccc';
dividerDiv.style.margin = '10px 0';
panelDiv.appendChild(dividerDiv);
// Format and insert JSON data
const scrollBox = document.createElement("div");
scrollBox.id = 'scrollBox';
scrollBox.style.width = "350px";
scrollBox.style.height = "350px";
scrollBox.style.border = "2px solid #444";
scrollBox.style.overflow = "scroll"; // Enables both vertical and horizontal scroll
scrollBox.style.whiteSpace = "nowrap"; // Prevents wrapping for horizontal scroll
scrollBox.style.padding = "10px";
scrollBox.style.fontFamily = "monospace";
scrollBox.style.backgroundColor = "black";
scrollBox.style.color = "white";
const jsonContent = document.createElement("pre");
jsonContent.textContent = 'Change a form field to see live data!';
scrollBox.appendChild(jsonContent);
panelDiv.appendChild(scrollBox);
// Open JSON data dialog button
let jsonDataDialogButton = document.createElement('button');
jsonDataDialogButton.textContent = 'Open in Dialog';
jsonDataDialogButton.id = 'jsonDataDialogButton';
jsonDataDialogButton.onclick = () => openJsonDataDialog(JSON.stringify(tempJSONData, null, 2));
panelDiv.appendChild(jsonDataDialogButton);
panelDiv.appendChild(document.createElement('p'));
enableButton(jsonDataDialogButton, false);
// If the loaded document is form-1040.pdf, show the Sample JSON Data button
const doc = instance.Core.documentViewer.getDocument();
if (doc && doc.filename === 'form-1040.pdf') {
panelDiv.appendChild(dividerDiv.cloneNode());
paragraph = document.createTextNode('Alternatively, fill your form with pre-existing JSON data.');
panelDiv.appendChild(paragraph);
// Sample JSON Data button
let sampleJSONDataButton = document.createElement('button');
sampleJSONDataButton.textContent = 'Sample JSON Data';
sampleJSONDataButton.style.backgroundColor = 'blue';
sampleJSONDataButton.style.color = 'white';
sampleJSONDataButton.onclick = () => fillJsonDataDialog(instance);
panelDiv.appendChild(sampleJSONDataButton);
}
// Downloading with flattening button
let downloadingFlatteningButton = document.createElement('button');
downloadingFlatteningButton.textContent = 'Downloading with flattening';
downloadingFlatteningButton.style.backgroundColor = 'blue';
downloadingFlatteningButton.style.color = 'white';
downloadingFlatteningButton.onclick = () => instance.UI.downloadPdf({
flags: instance.Core.SaveOptions.LINEARIZED,
includeAnnotations: true,
flatten: true
});
// Download only button
let downloadButton = document.createElement('button');
downloadButton.textContent = 'Download only';
downloadButton.style.backgroundColor = 'blue';
downloadButton.style.color = 'white';
downloadButton.onclick = () => instance.UI.downloadPdf({
flags: instance.Core.SaveOptions.LINEARIZED,
includeAnnotations: true
});
panelDiv.appendChild(dividerDiv.cloneNode());
paragraph = document.createElement('p');
// Create a text node for the first part of the text
const textBeforeLink = document.createTextNode('Optionally apply ');
// Create an anchor (link) element
const link = document.createElement('a');
link.href = 'https://docs.apryse.com/web/guides/annotation/flatten-annotations';
link.textContent = 'flattening';
link.target = '_blank'; // Opens the link in a new tab
// Create a text node for the text after the link
const textAfterLink = document.createTextNode(' to permanently add field data.');
// Append the text and link to the paragraph
paragraph.appendChild(textBeforeLink);
paragraph.appendChild(link);
paragraph.appendChild(textAfterLink);
panelDiv.appendChild(paragraph);
panelDiv.appendChild(document.createElement('p'));
panelDiv.appendChild(downloadingFlatteningButton);
panelDiv.appendChild(document.createElement('p'));
panelDiv.appendChild(downloadButton);
return panelDiv;
};
// Enable or disable a button based on the state
const enableButton = (button, state) => {
button.disabled = !state;
button.style.backgroundColor = (state) ? 'blue' : 'gray';
button.style.color = (state) ? 'white' : 'darkgray';
};
// Open JSON data in a dialog box with zoom in/out and close buttons
const openJsonDataDialog = (jsonText) => {
let fontSize = 14;
// Create overlay
const overlay = document.createElement("div");
overlay.className = "modal-overlay";
overlay.onclick = (e) => {
if (e.target === overlay) {
document.body.removeChild(overlay);
}
};
// Modal box
const modal = document.createElement("div");
modal.className = "modal-box";
// Controls
const controls = document.createElement("div");
controls.className = "modal-controls";
const title = document.createElement("span");
title.textContent = "Form Data";
title.style.fontWeight = "bold";
title.style.marginRight = "auto";
controls.appendChild(title);
const zoomInBtn = document.createElement("button");
zoomInBtn.textContent = "+";
zoomInBtn.onclick = () => {
fontSize += 2;
content.style.fontSize = fontSize + "px";
};
const zoomOutBtn = document.createElement("button");
zoomOutBtn.textContent = "-";
zoomOutBtn.onclick = () => {
fontSize = Math.max(10, fontSize - 2);
content.style.fontSize = fontSize + "px";
};
const closeBtn = document.createElement("button");
closeBtn.textContent = "Close";
closeBtn.className = "modal-close";
closeBtn.onclick = () => {
document.body.removeChild(overlay);
};
controls.appendChild(zoomInBtn);
controls.appendChild(zoomOutBtn);
controls.appendChild(closeBtn);
// Content
const content = document.createElement("pre");
content.className = "modal-content";
content.style.fontSize = fontSize + "px";
content.innerHTML = jsonText;
modal.appendChild(controls);
modal.appendChild(content);
overlay.appendChild(modal);
document.body.appendChild(overlay);
}
// Fill the form with sample JSON data in a dialog box with Fill and Close buttons
const fillJsonDataDialog = (instance) => {
// Create overlay
const overlay = document.createElement("div");
overlay.className = "modal-overlay";
overlay.onclick = (e) => {
if (e.target === overlay) {
document.body.removeChild(overlay);
}
};
// Modal box
const modal = document.createElement("div");
modal.className = "modal-box";
// Controls
const controls = document.createElement("div");
controls.className = "modal-controls";
const title = document.createElement("span");
title.textContent = "Sample JSON Data";
title.style.fontWeight = "bold";
title.style.marginRight = "auto";
controls.appendChild(title);
const fillBtn = document.createElement("button");
fillBtn.textContent = "Fill";
fillBtn.style.marginLeft = "475px";
fillBtn.onclick = () => {
const fieldManager = instance.Core.annotationManager.getFieldManager();
Object.keys(sampleJSONData).forEach((key) => {
const field = fieldManager.getField(key);
field && field.setValue(sampleJSONData[key]);
});
document.body.removeChild(overlay);
};
const closeBtn = document.createElement("button");
closeBtn.textContent = "Close";
closeBtn.className = "modal-close";
closeBtn.onclick = () => {
document.body.removeChild(overlay);
};
controls.appendChild(fillBtn);
controls.appendChild(closeBtn);
// Content
const content = document.createElement("pre");
content.className = "modal-content";
content.style.fontSize = "14px";
content.innerHTML = JSON.stringify(sampleJSONData, null, 2);
modal.appendChild(controls);
modal.appendChild(content);
overlay.appendChild(modal);
document.body.appendChild(overlay);
}