Interactive Forms - Sample Code for JavaScript

Requirements

Sample JavaScript code for using WebViewer with interactive forms (also known as AcroForms). Capabilities include programatically creating new fields and widget annotations, form filling, modifying existing field values, form templating, and flattening form fields. Learn more about our full PDF Data Extraction SDK Capabilities.

Implementation steps

Step 1: Follow get started in your preferred web stack for WebViewer
Step 2: Enable the full API by passing the fullAPI option into the WebViewer constructor
Step 3: Add the sample code provided in this guide

This full sample is one of many included in the manual download of WebViewer.

1//---------------------------------------------------------------------------------------
2// Copyright (c) 2001-2023 by Apryse Software Inc. All Rights Reserved.
3// Consult legal.txt regarding legal and license information.
4//---------------------------------------------------------------------------------------
5(exports => {
6 exports.runInteractiveFormsTest = () => {
7 const PDFNet = exports.Core.PDFNet;
8
9 PDFNet.CheckStyle = {
10 e_check: 0,
11 e_circle: 1,
12 e_cross: 2,
13 e_diamond: 3,
14 e_square: 4,
15 e_star: 5,
16 };
17
18 const RenameAllFields = async (doc, name) => {
19 let itr = await doc.getFieldIterator(name);
20 for (let counter = 0; await itr.hasNext(); itr = await doc.getFieldIterator(name), ++counter) {
21 const f = await itr.current();
22 f.rename(name + counter);
23 }
24 };
25
26 // Note: The visual appearance of check-marks and radio-buttons in PDF documents is
27 // not limited to CheckStyle-s. It is possible to create a visual appearance using
28 // arbitrary glyph, text, raster image, or path object. Although most PDF producers
29 // limit the options to the above 'standard' styles, using PDFNetJS you can generate
30 // arbitrary appearances.
31 const CreateCheckmarkAppearance = async (doc, style) => {
32 const builder = await PDFNet.ElementBuilder.create();
33 const writer = await PDFNet.ElementWriter.create();
34 writer.begin(doc);
35 writer.writeElement(await builder.createTextBegin());
36
37 let symbol;
38 switch (style) {
39 case PDFNet.CheckStyle.e_circle:
40 symbol = '\x6C';
41 break;
42 case PDFNet.CheckStyle.e_diamond:
43 symbol = '\x75';
44 break;
45 case PDFNet.CheckStyle.e_cross:
46 symbol = '\x35';
47 break;
48 case PDFNet.CheckStyle.e_square:
49 symbol = '\x6E';
50 break;
51 case PDFNet.CheckStyle.e_star:
52 symbol = '\x48';
53 break;
54 // ...
55 // See section D.4 "ZapfDingbats Set and Encoding" in PDF Reference Manual
56 // (http://www.pdftron.com/downloads/PDFReference16.pdf) for the complete
57 // graphical map for ZapfDingbats font. Please note that all character codes
58 // are represented using the 'octal' notation.
59 default:
60 // e_check
61 symbol = '\x34';
62 }
63
64 const zapfDingbatsFont = await PDFNet.Font.create(doc, PDFNet.Font.StandardType1Font.e_zapf_dingbats);
65 const checkmark = await builder.createTextRun(symbol, zapfDingbatsFont, 12);
66 writer.writeElement(checkmark);
67 writer.writeElement(await builder.createTextEnd());
68
69 const stm = await writer.end();
70 await stm.putRect('BBox', -0.2, -0.2, 1, 1); // Clip
71 await stm.putName('Subtype', 'Form');
72 return stm;
73 };
74
75 const CreateButtonAppearance = async (doc, buttonDown) => {
76 // Create a button appearance stream ------------------------------------
77
78 const builder = await PDFNet.ElementBuilder.create();
79 const writer = await PDFNet.ElementWriter.create();
80 writer.begin(doc);
81
82 // Draw background
83 let element = await builder.createRect(0, 0, 101, 37);
84 element.setPathFill(true);
85 element.setPathStroke(false);
86
87 let elementGState = await element.getGState();
88 elementGState.setFillColorSpace(await PDFNet.ColorSpace.createDeviceGray());
89 elementGState.setFillColorWithColorPt(await PDFNet.ColorPt.init(0.75));
90 writer.writeElement(element);
91
92 // Draw 'Submit' text
93 writer.writeElement(await builder.createTextBegin());
94
95 const text = 'Submit';
96 const HelveticaBoldFont = await PDFNet.Font.create(doc, PDFNet.Font.StandardType1Font.e_helvetica_bold);
97 element = await builder.createTextRun(text, HelveticaBoldFont, 12);
98 elementGState = await element.getGState();
99 elementGState.setFillColorWithColorPt(await PDFNet.ColorPt.init(0));
100
101 if (buttonDown) {
102 element.setTextMatrixEntries(1, 0, 0, 1, 33, 10);
103 } else {
104 element.setTextMatrixEntries(1, 0, 0, 1, 30, 13);
105 }
106 writer.writeElement(element);
107
108 writer.writeElement(await builder.createTextEnd());
109
110 const stm = await writer.end();
111
112 // Set the bounding box
113 await stm.putRect('BBox', 0, 0, 101, 37);
114 await stm.putName('Subtype', 'Form');
115 return stm;
116 };
117
118 const main = async () => {
119 let docBuffer = null;
120
121 try {
122 console.log('Beginning Test 1');
123
124 // Relative path to the folder containing test files.
125 // eslint-disable-next-line @typescript-eslint/no-unused-vars
126 const inputPath = '../TestFiles/';
127
128 const doc = await PDFNet.PDFDoc.create();
129 doc.initSecurityHandler();
130 doc.lock();
131 console.log('PDF document initialized and locked');
132
133 const blankPage = await doc.pageCreate();
134
135 // create new fields
136 const empFirstName = await doc.fieldCreateFromStrings('employee.name.first', PDFNet.Field.Type.e_text, 'John', '');
137 const empLastName = await doc.fieldCreateFromStrings('employee.name.last', PDFNet.Field.Type.e_text, 'Doe', '');
138 const empLastCheck1 = await doc.fieldCreateFromStrings('employee.name.check1', PDFNet.Field.Type.e_check, 'Yes', '');
139
140 const submit = await doc.fieldCreate('submit', PDFNet.Field.Type.e_button);
141
142 // Create page annotations for the above fields.
143
144 // Create text annotation
145 const annot1 = await PDFNet.WidgetAnnot.create(doc, await PDFNet.Rect.init(50, 550, 350, 600), empFirstName);
146 const annot2 = await PDFNet.WidgetAnnot.create(doc, await PDFNet.Rect.init(50, 450, 350, 500), empLastName);
147
148 // create checkbox annotation
149 const annot3 = await PDFNet.WidgetAnnot.create(doc, await PDFNet.Rect.init(64, 356, 120, 410), empLastCheck1);
150 // Set the annotation appearance for the "Yes" state
151 // NOTE: if we call refreshFieldAppearances after this the appearance will be discarded
152 const checkMarkApp = await CreateCheckmarkAppearance(doc, PDFNet.CheckStyle.e_check);
153 // Set the annotation appearance for the "Yes" state...
154 annot3.setAppearance(checkMarkApp, PDFNet.Annot.State.e_normal, 'Yes');
155
156 // Create button annotation
157 const annot4 = await PDFNet.WidgetAnnot.create(doc, await PDFNet.Rect.init(64, 284, 163, 320), submit);
158 // Set the annotation appearances for the down and up state...
159 const falseButtonApp = await CreateButtonAppearance(doc, false);
160 const trueButtonApp = await CreateButtonAppearance(doc, true);
161 await annot4.setAppearance(falseButtonApp, PDFNet.Annot.State.e_normal);
162 await annot4.setAppearance(trueButtonApp, PDFNet.Annot.State.e_down);
163
164 // Create 'SubmitForm' action. The action will be linked to the button.
165 const url = await PDFNet.FileSpec.createURL(doc, 'http://www.apryse.com');
166 const buttonAction = await PDFNet.Action.createSubmitForm(url);
167
168 // Associate the above action with 'Down' event in annotations action dictionary.
169 const annotAction = await (await annot4.getSDFObj()).putDict('AA');
170 annotAction.put('D', await buttonAction.getSDFObj());
171
172 blankPage.annotPushBack(annot1); // Add annotations to the page
173 blankPage.annotPushBack(annot2);
174 blankPage.annotPushBack(annot3);
175 blankPage.annotPushBack(annot4);
176
177 doc.pagePushBack(blankPage); // Add the page as the last page in the document.
178
179 // If you are not satisfied with the look of default auto-generated appearance
180 // streams you can delete "AP" entry from the Widget annotation and set
181 // "NeedAppearances" flag in AcroForm dictionary:
182 // doc.GetAcroForm().PutBool("NeedAppearances", true);
183 // This will force the viewer application to auto-generate new appearance streams
184 // every time the document is opened.
185 //
186 // Alternatively you can generate custom annotation appearance using ElementWriter
187 // and then set the "AP" entry in the widget dictionary to the new appearance
188 // stream.
189 //
190 // Yet another option is to pre-populate field entries with dummy text. When
191 // you edit the field values using PDFNet the new field appearances will match
192 // the old ones.
193
194 // doc.GetAcroForm().PutBool("NeedAppearances", true);
195 // NOTE: refreshFieldAppearances will replace previously generated appearance streams
196 doc.refreshFieldAppearances();
197
198 docBuffer = await doc.saveMemoryBuffer(0);
199 saveBufferAsPDFDoc(docBuffer, 'forms_test1.pdf');
200
201 console.log('Example 1 complete and everything deallocated.');
202 } catch (err) {
203 console.log(err.stack);
204 }
205 //----------------------------------------------------------------------------------
206 // Example 2:
207 // Fill-in forms / Modify values of existing fields.
208 // Traverse all form fields in the document (and print out their names).
209 // Search for specific fields in the document.
210 //----------------------------------------------------------------------------------
211
212 try {
213 console.log('Beginning Test 2');
214
215 // we use the forms test doc from the previous sample
216 // Buffers passed into PDFNetJS functions are made invalid afterwards due to the functions taking ownership.
217 // If you are using the same buffer to initialize multiple documents, pass in a copy of the buffer.
218 const copyOfBuffer = new Uint8Array(docBuffer.buffer.slice(0));
219 const doc2 = await PDFNet.PDFDoc.createFromBuffer(copyOfBuffer);
220
221 doc2.initSecurityHandler();
222 doc2.lock();
223 console.log('Sample 2 PDF document initialized and locked');
224 const itr = await doc2.getFieldIteratorBegin();
225
226 for (; await itr.hasNext(); itr.next()) {
227 const currentItr = await itr.current();
228 console.log('Field name: ' + (await currentItr.getName()));
229 console.log('Field partial name: ' + (await currentItr.getPartialName()));
230
231 console.log('Field type: ');
232 const type = await currentItr.getType();
233 const strVal = await currentItr.getValueAsString();
234
235 switch (type) {
236 case PDFNet.Field.Type.e_button: {
237 console.log('Button');
238 break;
239 }
240 case PDFNet.Field.Type.e_radio: {
241 console.log('Radio button: Value = ' + strVal);
242 break;
243 }
244 case PDFNet.Field.Type.e_check: {
245 const currItr = await itr.current();
246 currItr.setValueAsBool(true);
247 console.log('Check box: Value = ' + strVal);
248 break;
249 }
250 case PDFNet.Field.Type.e_text: {
251 console.log('Text');
252 // Edit all variable text in the document
253 const currItr = await itr.current();
254 currItr.setValueAsString('This is a new value. The old one was: ' + strVal);
255 break;
256 }
257 case PDFNet.Field.Type.e_choice: {
258 console.log('Choice');
259 break;
260 }
261 case PDFNet.Field.Type.e_signature: {
262 console.log('Signature');
263 break;
264 }
265 }
266 console.log('-----------------------');
267 }
268 const f = await doc2.getField('employee.name.first');
269 if (f) {
270 console.log('Field search for ' + (await f.getName()) + ' was successful');
271 } else {
272 console.log('Field search failed');
273 }
274 // Regenerate field appearances.
275 doc2.refreshFieldAppearances();
276
277 const docBuffer2 = await doc2.saveMemoryBuffer(0);
278 saveBufferAsPDFDoc(docBuffer2, 'forms_test_edit.pdf');
279 console.log('Example 2 complete and everything deallocated.');
280 } catch (err) {
281 console.log(err);
282 }
283 //----------------------------------------------------------------------------------
284 // Sample 3: Form templating
285 // Replicate pages and form data within a document. Then rename field names to make
286 // them unique.
287 //----------------------------------------------------------------------------------
288 try {
289 // we still keep using our original forms test doc.
290 // If you are using the same buffer to initialize multiple documents, pass in a copy of the buffer.
291 const copyOfBuffer3 = new Uint8Array(docBuffer.buffer.slice(0));
292 const doc3 = await PDFNet.PDFDoc.createFromBuffer(copyOfBuffer3);
293 doc3.initSecurityHandler();
294 doc3.lock();
295 console.log('Sample 3 PDF document initialized and locked');
296 const srcPage = await doc3.getPage(1);
297 doc3.pagePushBack(srcPage); // Append several copies of the first page
298 doc3.pagePushBack(srcPage); // Note that forms are successfully copied
299 doc3.pagePushBack(srcPage);
300 doc3.pagePushBack(srcPage);
301
302 // Now we rename fields in order to make every field unique.
303 // You can use this technique for dynamic template filling where you have a 'master'
304 // form page that should be replicated, but with unique field names on every page.
305 await RenameAllFields(doc3, 'employee.name.first');
306 await RenameAllFields(doc3, 'employee.name.last');
307 await RenameAllFields(doc3, 'employee.name.check1');
308 await RenameAllFields(doc3, 'submit');
309
310 const docBuffer3 = await doc3.saveMemoryBuffer(0);
311 saveBufferAsPDFDoc(docBuffer3, 'forms_test1_cloned.pdf');
312 console.log('Example 3 complete and everything deallocated.');
313 } catch (err) {
314 console.log(err);
315 }
316
317 //----------------------------------------------------------------------------------
318 // Sample:
319 // Flatten all form fields in a document.
320 // Note that this sample is intended to show that it is possible to flatten
321 // individual fields. PDFNet provides a utility function PDFDoc.FlattenAnnotations()
322 // that will automatically flatten all fields.
323 //----------------------------------------------------------------------------------
324
325 try {
326 const copyOfBuffer4 = new Uint8Array(docBuffer.buffer.slice(0));
327 const doc4 = await PDFNet.PDFDoc.createFromBuffer(copyOfBuffer4);
328 doc4.initSecurityHandler();
329 doc4.lock();
330 console.log('Sample 4 PDF document initialized and locked');
331
332 // Flatten all pages
333 // eslint-disable-next-line no-constant-condition
334 if (true) {
335 doc4.flattenAnnotations();
336 } else {
337 // Manual flattening
338 for (let pitr = await doc4.getPageIterator(); await pitr.hasNext(); await pitr.next()) {
339 const page = await pitr.current();
340 const annots = await page.getAnnots();
341
342 if (annots) {
343 // Look for all widget annotations (in reverse order)
344 for (let i = parseInt(await annots.size(), 10) - 1; i >= 0; --i) {
345 const annotObj = await annots.getAt(i);
346 const annotObjSubtype = await annotObj.get('Subtype');
347 // eslint-disable-next-line @typescript-eslint/no-unused-vars
348 const annotObjVal = await annotObjSubtype.value();
349 const annotObjName = await (await (await annotObj.get('Subtype')).value()).getName();
350
351 if (annotObjName === 'Widget') {
352 const field = await PDFNet.Field.create(annotObj);
353 field.flatten(page);
354
355 // Another way of making a read only field is by modifying
356 // field's e_read_only flag:
357 // field.SetFlag(Field::e_read_only, true);
358 }
359 }
360 }
361 }
362 }
363
364 const docBuffer4 = await doc4.saveMemoryBuffer(0);
365 saveBufferAsPDFDoc(docBuffer4, 'forms_test1_flattened.pdf');
366 console.log('done - Example 4 complete and everything deallocated.');
367 } catch (err) {
368 console.log(err);
369 }
370 };
371
372 // add your own license key as the second parameter, e.g. PDFNet.runWithCleanup(main, 'YOUR_LICENSE_KEY')
373 PDFNet.runWithCleanup(main);
374 };
375})(window);
376// eslint-disable-next-line spaced-comment
377//# sourceURL=InteractiveFormsTest.js

Did you find this helpful?

Trial setup questions?

Ask experts on Discord

Need other help?

Contact Support

Pricing or product questions?

Contact Sales