Field Detection Showcase Demo Code Sample

Effortlessly detect and classify Form Fields. Transform your flat PDF into an interactive form with editable fields using AI-powered recognition.

This demo allows you to:

  • Upload a flat PDF and automatically detect form elements such as:
    • Text Fields
    • Check Boxes
    • Radio Buttons
    • List Boxes
    • Combo Boxes
    • Buttons
    • Digital Signatures
  • Convert detected elements into editable form fields.
  • Edit and customize the newly created fields, then save your updated document.


Implementation steps

To add PDF form field detection capability with WebViewer:

Step 1: Choose your preferred web stack
Step 2: Download any required modules listed in the Demo Dependencies section below
Step 3: Add the ES6 JavaScript sample code provided in this guide

Demo Dependencies
This sample uses the following:

Want to see a live version of this demo?

Try the PDF Form Field Detection demo

1// ES6 Compliant Syntax
2// Copilot: GitHub Copilot, Version: 1.0, Model: GPT-4, Version: 2024-06, Date: 2025-08-31
3// File: client/index.js
4
5
6// **Important**
7// You must get a license key from Apryse for the server to run.
8// A trial key can be obtained from:
9// https://docs.apryse.com/core/guides/get-started/trial-key
10const licenseKey = 'YOUR_LICENSE_KEY';
11const viewerElement = document.getElementById('viewer');
12const initialDoc = 'https://apryse.s3.us-west-1.amazonaws.com/public/files/samples/form_fields_flattened.pdf';
13let jsonData = null;
14const formFieldMap = [
15 { type: 'formTextField', title: 'Text Field', color: { R: 51, G: 101, B: 251, A: 1 } },
16 { type: 'formCheckBox', title: 'Check Box', color: { R: 0, G: 128, B: 0, A: 1 } },
17 { type: 'formRadioButton', title: 'Radio Button', color: { R: 128, G: 0, B: 128, A: 1 } },
18 { type: 'formListBox', title: 'List Box', color: { R: 144, G: 238, B: 144, A: 1 } },
19 { type: 'formComboBox', title: 'Combo Box', color: { R: 255, G: 192, B: 203, A: 1 } },
20 { type: 'formButton', title: 'Button', color: { R: 255, G: 165, B: 0, A: 1 } },
21 { type: 'formDigitalSignature', title: 'Digital Signature', color: { R: 3, G: 219, B: 252, A: 1 } },
22 { type: 'formTextArea', title: 'Text Area', color: { R: 255, G: 0, B: 0, A: 1 } },
23];
24
25// The list of registered panels in the main webviewer
26let viewerPanels = null;
27
28// The tab panel, representing the webviewer left panel
29const tabPanel = {
30 handle: null,
31 dataElement: 'tabPanel'
32};
33
34// The custom form sub-panel to be registered
35const formPanel = {
36 handle: null,
37 dataElement: 'formPanel',
38 render: null,
39};
40
41// Customize the main webviewer left panel after the load completion
42const customizeUI = (instance) => {
43 const { UI } = instance;
44
45 // close the tab panel (if it's open) for refreshment.
46 UI.closeElements([tabPanel.dataElement]);
47
48 // Get the list of registered panels in the main webviewer
49 viewerPanels = UI.getPanels();
50
51 // Find the Tab Panel to modify. The form sub-panel will be added to this Tab panel.
52 tabPanel.handle = viewerPanels.find((panel) => panel.dataElement === tabPanel.dataElement);
53
54 // Register the custom form sub-panel
55 RegisterFormPanel(instance);
56
57 // Add the new custom form sub-panel to list of sub-panels under the Tab Panel
58 formPanel.handle = { render: formPanel.dataElement };
59 tabPanel.handle.panelsList = [formPanel.handle, ...tabPanel.handle.panelsList];
60
61 UI.openElements([tabPanel.dataElement]);
62 UI.setPanelWidth(tabPanel.dataElement, 400);
63};
64
65// Register the custom form sub-panel
66const RegisterFormPanel = (instance) => {
67 formPanel.render = createFormPanelElements(instance);
68 instance.UI.addPanel({
69 dataElement: formPanel.dataElement,
70 location: 'left',
71 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>form</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>',
72 title: 'Form',
73 render: () => formPanel.render,
74 });
75};
76
77// Create the form panel elements.
78const createFormPanelElements = (instance) => {
79 let panelDiv = document.createElement('div');
80 panelDiv.id = 'form';
81 let paragraph = document.createTextNode('A demo of Apryse SDK Smart Data Extraction and the form field detection and classification. Take a flat PDF and automatically find form fields using AI.');
82 panelDiv.appendChild(paragraph);
83
84 const span = document.createElement("span");
85 span.style.color = 'orange';
86 span.appendChild(document.createTextNode('NOTE: Only the first page will be processed.'));
87 panelDiv.appendChild(document.createElement('p'));
88 panelDiv.appendChild(span);
89
90 let dividerDiv = document.createElement('div');
91 dividerDiv.style.borderTop = '1px solid #ccc';
92 dividerDiv.style.margin = '10px 0';
93 panelDiv.appendChild(dividerDiv);
94
95 // Detect form fields button
96 let detectFieldsButton = document.createElement('button');
97 detectFieldsButton.textContent = 'Detect Form Fields';
98 detectFieldsButton.id = 'detectFieldsButton';
99 detectFieldsButton.disabled = true;
100 detectFieldsButton.style.backgroundColor = 'gray';
101 detectFieldsButton.style.color = 'darkgray';
102 detectFieldsButton.onclick = async () => {
103
104 detectFieldsButton.style.cursor = "not-allowed"; // Changes cursor for the button itself
105 formPanel.render.style.cursor = "not-allowed"; // Changes cursor for the button itself
106
107 enableButton(detectFieldsButton, false);
108 await detectFormFields(instance); // Detect form fields
109
110 detectFieldsButton.style.cursor = "default";
111 formPanel.render.style.cursor = "default";
112 }
113
114 panelDiv.appendChild(detectFieldsButton);
115 panelDiv.appendChild(document.createElement('p'));
116
117 return panelDiv;
118};
119
120// Open JSON data in a viewer with zoom in/out and close buttons
121// This viewer will be viewed below the PDF document viewer.
122const openJsonDataDialog = (jsonText) => {
123 let fontSize = 14;
124
125 // Create overlay
126 const overlay = document.createElement("div");
127 overlay.className = "modal-overlay";
128 overlay.onclick = (e) => {
129 if (e.target === overlay) {
130 document.body.removeChild(overlay);
131 }
132 };
133
134 // Modal box
135 const modal = document.createElement("div");
136 modal.className = "modal-box";
137
138 // Controls
139 const controls = document.createElement("div");
140 controls.className = "modal-controls";
141
142 const zoomInBtn = document.createElement("button");
143 zoomInBtn.textContent = "+";
144 zoomInBtn.onclick = () => {
145 fontSize += 2;
146 content.style.fontSize = fontSize + "px";
147 };
148
149 const zoomOutBtn = document.createElement("button");
150 zoomOutBtn.textContent = "-";
151 zoomOutBtn.onclick = () => {
152 fontSize = Math.max(10, fontSize - 2);
153 content.style.fontSize = fontSize + "px";
154 };
155
156 const closeBtn = document.createElement("button");
157 closeBtn.textContent = "Close";
158 closeBtn.className = "modal-close";
159 closeBtn.onclick = () => {
160 document.body.removeChild(overlay);
161 };
162
163 controls.appendChild(zoomInBtn);
164 controls.appendChild(zoomOutBtn);
165 controls.appendChild(closeBtn);
166
167 // Content
168 const content = document.createElement("pre");
169 content.className = "modal-content";
170 content.style.fontSize = fontSize + "px";
171 content.innerHTML = jsonText;
172
173 modal.appendChild(controls);
174 modal.appendChild(content);
175 overlay.appendChild(modal);
176 document.body.appendChild(overlay);
177}
178
179// draw annotations on the PDF when forms fields are detected
180const drawAnnotations = (instance) => {
181 const formFieldData = JSON.parse(jsonData);
182 const { annotationManager, Annotations } = instance.Core;
183
184 formFieldData.pages[0].formElements.forEach((element) => {
185 const color = formFieldMap.find(field => field.type === element.type).color;
186 const annot = new Annotations.RectangleAnnotation({
187 PageNumber: formFieldData.pages[0].properties.pageNumber,
188 X: element.rect[0],
189 Y: element.rect[1],
190 Width: element.rect[2] - element.rect[0],
191 Height: element.rect[3] - element.rect[1],
192 StrokeColor: new Annotations.Color(color.R, color.G, color.B, color.A),
193 StrokeThickness: 2,
194 });
195 annotationManager.addAnnotation(annot);
196 annotationManager.redrawAnnotation(annot);
197 });
198};
199
200const downloadPdf = async (instance) => {
201 const options = {
202 flags: instance.Core.SaveOptions.LINEARIZED,
203 downloadType: 'pdf'
204 };
205
206 instance.UI.downloadPdf(options);
207};
208
209const buildForm = (instance) => {
210 const { annotationManager, Annotations } = instance.Core;
211 const { WidgetFlags } = Annotations;
212 let flags = null;
213 let field = null;
214 let widgetAnnot = null;
215 const font = new Annotations.Font({ name: 'Helvetica', size: 12 });
216
217 annotationManager.deleteAnnotations(annotationManager.getAnnotationsList());
218
219 const formFieldData = JSON.parse(jsonData);
220
221 formFieldData.pages[0].formElements.forEach((formField, index) => {
222
223 // Sets generic flags for the widget.
224 flags = new WidgetFlags();
225 flags.set(WidgetFlags.REQUIRED, true);
226
227 switch (formField.type) {
228 case 'formTextField':
229 flags.set(WidgetFlags.MULTILINE, true);
230
231 // Creates a text form field.
232 field = new Annotations.Forms.Field(`TextField ${index}`, {
233 type: 'Tx',
234 defaultValue: 'Default Value',
235 flags,
236 });
237
238 // Creates a text widget annotation.
239 widgetAnnot = new Annotations.TextWidgetAnnotation(field);
240 break;
241 case 'formRadioButton':
242 flags.set(WidgetFlags.RADIO, true);
243 flags.set(WidgetFlags.NO_TOGGLE_TO_OFF, true);
244
245 // Creates a radio button form field.
246 field = new Annotations.Forms.Field(`RadioField ${index}`, {
247 type: 'Btn',
248 value: 'Off',
249 flags,
250 font: font,
251 });
252
253 // Create a radio widget button.
254 widgetAnnot = new Annotations.RadioButtonWidgetAnnotation(field, {
255 appearance: 'Off',
256 appearances: {
257 Off: {},
258 First: {},
259 },
260 backgroundColor: new Color(255, 0, 0),
261 });
262 break;
263 case 'formComboBox':
264 // Sets flags for the combobox widget.
265 flags.set(WidgetFlags.COMBO, true);
266
267 // Define the available options.
268 const comboOptions = [
269 { value: '1', displayValue: 'one' },
270 { value: '2', displayValue: 'two' },
271 { value: '3', displayValue: 'three' }
272 ];
273
274 // Creates a combobox form field.
275 field = new Annotations.Forms.Field(`ComboBoxField ${index}`, {
276 flags,
277 font: font,
278 type: 'Ch',
279 options: comboOptions,
280 value: comboOptions[0].value,
281 });
282
283 // Creates a combobox widget annotation.
284 widgetAnnot = new Annotations.ChoiceWidgetAnnotation(field);
285 break;
286 case 'formCheckBox':
287 // Creates a checkbox form field.
288 field = new Annotations.Forms.Field(`CheckBoxField ${index}`, {
289 type: 'Btn',
290 value: 'Off',
291 flags,
292 });
293
294 // Creates a checkbox widget annotation.
295 widgetAnnot = new Annotations.CheckButtonWidgetAnnotation(field, {
296 appearance: 'Off',
297 appearances: {
298 Off: {},
299 Yes: {},
300 },
301 captions: {
302 Normal: '' // Uses the check symbol for selected caption.
303 }
304 });
305 break;
306 case 'formDigitalSignature':
307 // Creates a signature form field.
308 field = new Annotations.Forms.Field(`SignatureField ${index}`, {
309 type: 'Sig',
310 flags,
311 });
312
313 // Creates a signature widget annotation.
314 widgetAnnot = new Annotations.SignatureWidgetAnnotation(field, {
315 appearance: '_DEFAULT',
316 appearances: {
317 _DEFAULT: {
318 Normal: {
319 // Optionally can pass image data to appearance.
320 // data: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAYdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjEuMWMqnEsAAAANSURBVBhXY/j//z8DAAj8Av6IXwbgAAAAAElFTkSuQmCC',
321 offset: {
322 x: formField.rect[0],
323 y: formField.rect[1],
324 },
325 },
326 },
327 },
328 });
329 break;
330 case 'formButton':
331 flags.set(WidgetFlags.PUSH_BUTTON, true);
332 // Creates a button form field.
333 field = new Annotations.Forms.Field(`ButtonField ${index}`, {
334 type: 'Btn',
335 tooltipName: 'this is a button',
336 flags,
337 });
338
339 // Creates a checkbox widget annotation.
340 widgetAnnot = new Annotations.PushButtonWidgetAnnotation(field, {
341 border: new Annotations.Border({
342 color: new Annotations.Color(255, 0, 0),
343 width: 1,
344 style: 'solid',
345 }),
346 });
347 break;
348 default:
349 break;
350 }
351
352 // set the widget properties
353 widgetAnnot.PageNumber = formFieldData.pages[0].properties.pageNumber;
354 widgetAnnot.X = formField.rect[0];
355 widgetAnnot.Y = formField.rect[1];
356 widgetAnnot.Width = formField.rect[2] - formField.rect[0];
357 widgetAnnot.Height = formField.rect[3] - formField.rect[1];
358
359 // Add form field to field manager and widget annotation to annotation manager.
360 annotationManager.getFieldManager().addField(field);
361 annotationManager.addAnnotation(widgetAnnot);
362 annotationManager.drawAnnotationsFromList([widgetAnnot]);
363 });
364}
365
366// Detect form fields in the PDF document
367// This function will send fetch GET message to the server,
368// to receive the detected form fields as JSON object.
369const detectFormFields = async (instance) => {
370
371 const doc = instance.Core.documentViewer.getDocument();
372
373 // Make a GET request to get the extracted JSON data of form fields of the current PDF.
374 return new Promise(function (resolve) {
375 fetch(`http://localhost:5050/server/handler.js?filename=${doc.filename}`, {
376 method: 'GET'
377 }).then(function (response) {
378 if (response.status === 200) {
379 response.text().then(function (json) {
380 jsonData = json;
381 let jsonText = JSON.stringify(jsonData, null, 2);
382 jsonText = jsonText.replace(/\\r\\n/g, '\n');
383 jsonText = jsonText.replace(/\\"/g, '"');
384
385 // Build form button
386 let buildFormButton = document.createElement('button');
387 buildFormButton.textContent = 'Build Form';
388 buildFormButton.id = 'buildFormButton';
389 buildFormButton.disabled = true;
390 buildFormButton.style.backgroundColor = 'gray';
391 buildFormButton.style.color = 'darkgray';
392 buildFormButton.onclick = () => {
393 enableButton(buildFormButton, false);
394 buildForm(instance); // Build form
395 }
396
397 formPanel.render.appendChild(buildFormButton);
398 formPanel.render.appendChild(document.createElement('p'));
399
400 // Download button
401 let downloadButton = document.createElement('button');
402 downloadButton.textContent = 'Download PDF';
403 downloadButton.id = 'downloadButton';
404 downloadButton.style.backgroundColor = 'blue';
405 downloadButton.style.color = 'white';
406 downloadButton.onclick = () => downloadPdf(instance); // Download PDF
407
408 formPanel.render.appendChild(downloadButton);
409
410 // Display the detected form fields
411 let colorsDiv = document.createElement('div');
412 colorsDiv.id = 'json';
413 colorsDiv.className = "listContainer";
414 const colorsTitle = document.createElement("h3");
415 colorsTitle.textContent = "Color Legend";
416 colorsDiv.appendChild(colorsTitle);
417 colorsDiv.appendChild(document.createElement('p'));
418
419 // Create list items
420 formFieldMap.forEach(field => {
421 const color = new instance.Core.Annotations.Color(field.color.R, field.color.G, field.color.B, field.color.A);
422 const listItem = document.createElement("div");
423 listItem.className = "listItem";
424 listItem.textContent = field.text;
425 listItem.style.setProperty("--bullet-color", color);
426 listItem.style.setProperty("color", color);
427 listItem.style.setProperty("font-weight", "bold");
428
429 // Set bullet color using ::before
430 listItem.style.setProperty("--bullet-color", color);
431 listItem.style.setProperty("position", "relative");
432 listItem.style.setProperty("padding-left", "20px");
433 listItem.style.setProperty("margin", "8px 0");
434
435 // Add custom bullet using inline style
436 listItem.style.setProperty("list-style", "none");
437 listItem.style.setProperty("display", "block");
438 listItem.style.setProperty("line-height", "1.5");
439 listItem.style.setProperty("font-size", "14px");
440
441 // Create bullet manually
442 const bullet = document.createElement("span");
443 bullet.style.width = "10px";
444 bullet.style.height = "10px";
445 bullet.style.borderRadius = "50%";
446 bullet.style.backgroundColor = color;
447 bullet.style.display = "inline-block";
448 bullet.style.marginRight = "10px";
449 bullet.style.verticalAlign = "middle";
450
451 // Insert bullet before text
452 listItem.textContent = ""; // Clear text
453 listItem.appendChild(bullet);
454 listItem.appendChild(document.createTextNode(field.title));
455
456 colorsDiv.appendChild(listItem);
457 });
458
459 formPanel.render.appendChild(colorsDiv);
460
461 // Display the detected form fields
462 let jsonDiv = document.createElement('div');
463 jsonDiv.id = 'json';
464 const jsonTitle = document.createElement("h3");
465 jsonTitle.textContent = "JSON Data";
466 jsonDiv.appendChild(jsonTitle);
467 jsonDiv.appendChild(document.createElement('p'));
468
469 const scrollBox = document.createElement("div");
470 scrollBox.style.width = "350px";
471 scrollBox.style.height = "350px";
472 scrollBox.style.border = "2px solid #444";
473 scrollBox.style.overflow = "scroll"; // Enables both vertical and horizontal scroll
474 scrollBox.style.whiteSpace = "nowrap"; // Prevents wrapping for horizontal scroll
475 scrollBox.style.padding = "10px";
476 scrollBox.style.fontFamily = "monospace";
477 scrollBox.style.backgroundColor = "black";
478 scrollBox.style.color = "white";
479
480 // Format and insert JSON data
481 const jsonContent = document.createElement("pre");
482 jsonContent.textContent = jsonText;
483 scrollBox.appendChild(jsonContent);
484 jsonDiv.appendChild(scrollBox);
485
486 // Open JSON data dialog button
487 let jsonDataDialogButton = document.createElement('button');
488 jsonDataDialogButton.textContent = 'Open in Dialog';
489 jsonDataDialogButton.id = 'jsonDataDialogButton';
490 jsonDataDialogButton.style.backgroundColor = 'blue';
491 jsonDataDialogButton.style.color = 'white';
492 jsonDataDialogButton.onclick = () => openJsonDataDialog(jsonText);
493 jsonDiv.appendChild(jsonDataDialogButton);
494 jsonDiv.appendChild(document.createElement('p'));
495
496 formPanel.render.appendChild(jsonDiv);
497 drawAnnotations(instance);
498 resolve();
499 enableButton(formPanel.render.querySelector('#buildFormButton'), true);
500 })
501 }
502 else if (response.status === 500) {
503 jsonData = null;
504 resolve();
505 }
506 });
507 });
508};
509
510// Enable or disable a button based on the state
511const enableButton = (button, state) => {
512 button.disabled = !state;
513 button.style.backgroundColor = (state) ? 'blue' : 'gray';
514 button.style.color = (state) ? 'white' : 'darkgray';
515};
516
517WebViewer({
518 path: '/lib',
519 initialDoc: initialDoc,
520 enableFilePicker: true, // Enable file picker to open files. In WebViewer -> menu icon -> Open File
521 enableMeasurement: true,
522 loadAsPDF: true,
523 licenseKey: licenseKey,
524}, viewerElement).then(instance => {
525
526 // Once the PDF document is loaded, send it to the server.
527 // The sent PDF document will be processed by the server,
528 // by extracting form fields JSON data when the user clicks the "Detect Form Fields" button.
529 instance.Core.documentViewer.addEventListener('documentLoaded', async () => {
530
531 // Customize the main webviewer left panel after the load completion
532 customizeUI(instance);
533
534 // Reset JSON data
535 jsonData = null;
536
537 // Preparation of the PDF blob to be sent to the server
538 const doc = instance.Core.documentViewer.getDocument();
539 const xfdfString = await instance.Core.annotationManager.exportAnnotations(); // obtaining annotations in the loaded document
540 const data = await doc.getFileData({ xfdfString });
541 const arr = new Uint8Array(data);
542 const blob = new Blob([arr], { type: 'application/pdf' });
543 const formData = new FormData();
544 formData.append(doc.filename, blob, doc.filename);
545
546 // Send the PDF blob to the server for processing
547 new Promise(function (resolve, reject) {
548 console.log('🚀 Sending PDF to server for initial processing...');
549
550 fetch(`http://localhost:5050/server/handler.js?filename=${doc.filename}`, {
551 method: 'POST',
552 body: formData,
553 }).then(function (response) {
554 console.log(`📡 Server response status: ${response.status}`);
555
556 if (response.status === 200) {
557 console.log('✅ PDF successfully sent to server');
558
559 // enable Detect Form Fields button
560 const detectButton = formPanel.render.querySelector('#detectFieldsButton');
561 if (detectButton) {
562 console.log('🔓 Enabling Detect Form Fields button');
563 enableButton(detectButton, true);
564 } else {
565 console.warn('⚠️ Could not find detectFieldsButton in DOM');
566 }
567 resolve();
568 } else {
569 console.error(`❌ Server responded with status: ${response.status}`);
570 reject(new Error(`Server error: ${response.status}`));
571 }
572 }).catch(function (error) {
573 console.error('❌ Failed to connect to server:', error);
574 console.error('📍 Attempted URL: http://localhost:5050/server/handler.js');
575 console.error('🔍 This likely means the field-detection server is not running on port 5050');
576 reject(error);
577 });
578 }).catch(function (error) {
579 console.error('❌ Error in PDF upload promise:', error);
580 });
581 });
582
583 console.log('✅ WebViewer loaded successfully.');
584}).catch((error) => {
585 console.error('❌ Failed to initialize WebViewer:', error);
586});
587

Did you find this helpful?

Trial setup questions?

Ask experts on Discord

Need other help?

Contact Support

Pricing or product questions?

Contact Sales