> For the complete documentation index, see [llms.txt](https://docs.apryse.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.apryse.com/web/get-started/samples/interactiveformstest.md).

# Interactive Forms - Sample Code for JavaScript

Sample JavaScript code for using WebViewer, Apryse Web SDK, with interactive forms (also known as AcroForms). Capabilities include programatically creating new fields and widget annotations, form fill

{% hint style="info" %}
**Requirements**

*These packages are required to use these features in production. Trial keys have unlimited access to all features*

<a href="/web/get-started/readme.md" class="button primary">Web SDK</a><a href="/web/full-api/full-api-overview.md" class="button primary">Full API</a>
{% endhint %}

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](https://apryse.com/capabilities/extraction).

### **Implementation steps**

Step 1: Follow [get started in your preferred web stack for WebViewer](/web/get-started/readme.md) Step 2: [Enable the full API ](/web/full-api/full-api-overview.md)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.](/web/get-started/manually.md#1-download-webviewer)

<pre class="language-js" data-line-numbers><code class="lang-js">//---------------------------------------------------------------------------------------
// Copyright (c) 2001-2023 by Apryse Software Inc. All Rights Reserved.
// Consult legal.txt regarding legal and license information.
//---------------------------------------------------------------------------------------
(exports => {
  exports.runInteractiveFormsTest = () => {
    const PDFNet = exports.Core.PDFNet;

    PDFNet.CheckStyle = {
      e_check: 0,
      e_circle: 1,
      e_cross: 2,
      e_diamond: 3,
      e_square: 4,
      e_star: 5,
    };

    const RenameAllFields = async (doc, name) => {
      let itr = await doc.getFieldIterator(name);
      for (let counter = 0; await itr.hasNext(); itr = await doc.getFieldIterator(name), ++counter) {
        const f = await itr.current();
        f.rename(name + counter);
      }
    };

    // Note: The visual appearance of check-marks and radio-buttons in PDF documents is
    // not limited to CheckStyle-s. It is possible to create a visual appearance using
    // arbitrary glyph, text, raster image, or path object. Although most PDF producers
    // limit the options to the above 'standard' styles, using PDFNetJS you can generate
    // arbitrary appearances.
    const CreateCheckmarkAppearance = async (doc, style) => {
      const builder = await PDFNet.ElementBuilder.create();
      const writer = await PDFNet.ElementWriter.create();
      writer.begin(doc);
      writer.writeElement(await builder.createTextBegin());

      let symbol;
      switch (style) {
        case PDFNet.CheckStyle.e_circle:
          symbol = '\x6C';
          break;
        case PDFNet.CheckStyle.e_diamond:
          symbol = '\x75';
          break;
        case PDFNet.CheckStyle.e_cross:
          symbol = '\x35';
          break;
        case PDFNet.CheckStyle.e_square:
          symbol = '\x6E';
          break;
        case PDFNet.CheckStyle.e_star:
          symbol = '\x48';
          break;
        // ...
        // See section D.4 "ZapfDingbats Set and Encoding" in PDF Reference Manual
        // (http://www.pdftron.com/downloads/PDFReference16.pdf) for the complete
        // graphical map for ZapfDingbats font. Please note that all character codes
        // are represented using the 'octal' notation.
        default:
          // e_check
          symbol = '\x34';
      }

      const zapfDingbatsFont = await PDFNet.Font.create(doc, PDFNet.Font.StandardType1Font.e_zapf_dingbats);
      const checkmark = await builder.createTextRun(symbol, zapfDingbatsFont, 12);
      writer.writeElement(checkmark);
      writer.writeElement(await builder.createTextEnd());

      const stm = await writer.end();
      await stm.putRect('BBox', -0.2, -0.2, 1, 1); // Clip
      await stm.putName('Subtype', 'Form');
      return stm;
    };

    const CreateButtonAppearance = async (doc, buttonDown) => {
      // Create a button appearance stream ------------------------------------

      const builder = await PDFNet.ElementBuilder.create();
      const writer = await PDFNet.ElementWriter.create();
      writer.begin(doc);

      // Draw background
      let element = await builder.createRect(0, 0, 101, 37);
      element.setPathFill(true);
      element.setPathStroke(false);

      let elementGState = await element.getGState();
      elementGState.setFillColorSpace(await PDFNet.ColorSpace.createDeviceGray());
      elementGState.setFillColorWithColorPt(await PDFNet.ColorPt.init(0.75));
      writer.writeElement(element);

      // Draw 'Submit' text
      writer.writeElement(await builder.createTextBegin());

      const text = 'Submit';
      const HelveticaBoldFont = await PDFNet.Font.create(doc, PDFNet.Font.StandardType1Font.e_helvetica_bold);
      element = await builder.createTextRun(text, HelveticaBoldFont, 12);
      elementGState = await element.getGState();
      elementGState.setFillColorWithColorPt(await PDFNet.ColorPt.init(0));

      if (buttonDown) {
        element.setTextMatrixEntries(1, 0, 0, 1, 33, 10);
      } else {
        element.setTextMatrixEntries(1, 0, 0, 1, 30, 13);
      }
      writer.writeElement(element);

      writer.writeElement(await builder.createTextEnd());

      const stm = await writer.end();

      // Set the bounding box
      await stm.putRect('BBox', 0, 0, 101, 37);
      await stm.putName('Subtype', 'Form');
      return stm;
    };

    const main = async () => {
      let docBuffer = null;

      try {
        console.log('Beginning Test 1');

        // Relative path to the folder containing test files.
        // eslint-disable-next-line @typescript-eslint/no-unused-vars
        const inputPath = '../TestFiles/';

        const doc = await PDFNet.PDFDoc.create();
        doc.initSecurityHandler();
        doc.lock();
        console.log('PDF document initialized and locked');

        const blankPage = await doc.pageCreate();

        // create new fields
        const empFirstName = await doc.fieldCreateFromStrings('employee.name.first', PDFNet.Field.Type.e_text, 'John', '');
        const empLastName = await doc.fieldCreateFromStrings('employee.name.last', PDFNet.Field.Type.e_text, 'Doe', '');
        const empLastCheck1 = await doc.fieldCreateFromStrings('employee.name.check1', PDFNet.Field.Type.e_check, 'Yes', '');

        const submit = await doc.fieldCreate('submit', PDFNet.Field.Type.e_button);

        // Create page annotations for the above fields.

        // Create text annotation
        const annot1 = await PDFNet.WidgetAnnot.create(doc, await PDFNet.Rect.init(50, 550, 350, 600), empFirstName);
        const annot2 = await PDFNet.WidgetAnnot.create(doc, await PDFNet.Rect.init(50, 450, 350, 500), empLastName);

        // create checkbox annotation
        const annot3 = await PDFNet.WidgetAnnot.create(doc, await PDFNet.Rect.init(64, 356, 120, 410), empLastCheck1);
        // Set the annotation appearance for the "Yes" state
        // NOTE: if we call refreshFieldAppearances after this the appearance will be discarded
        const checkMarkApp = await CreateCheckmarkAppearance(doc, PDFNet.CheckStyle.e_check);
        // Set the annotation appearance for the "Yes" state...
        annot3.setAppearance(checkMarkApp, PDFNet.Annot.State.e_normal, 'Yes');

        // Create button annotation
        const annot4 = await PDFNet.WidgetAnnot.create(doc, await PDFNet.Rect.init(64, 284, 163, 320), submit);
        // Set the annotation appearances for the down and up state...
        const falseButtonApp = await CreateButtonAppearance(doc, false);
        const trueButtonApp = await CreateButtonAppearance(doc, true);
        await annot4.setAppearance(falseButtonApp, PDFNet.Annot.State.e_normal);
        await annot4.setAppearance(trueButtonApp, PDFNet.Annot.State.e_down);

        // Create 'SubmitForm' action. The action will be linked to the button.
        const url = await PDFNet.FileSpec.createURL(doc, 'http://www.apryse.com');
        const buttonAction = await PDFNet.Action.createSubmitForm(url);

        // Associate the above action with 'Down' event in annotations action dictionary.
        const annotAction = await (await annot4.getSDFObj()).putDict('AA');
        annotAction.put('D', await buttonAction.getSDFObj());

        blankPage.annotPushBack(annot1); // Add annotations to the page
        blankPage.annotPushBack(annot2);
        blankPage.annotPushBack(annot3);
        blankPage.annotPushBack(annot4);

        doc.pagePushBack(blankPage); // Add the page as the last page in the document.

        // If you are not satisfied with the look of default auto-generated appearance
        // streams you can delete "AP" entry from the Widget annotation and set
        // "NeedAppearances" flag in AcroForm dictionary:
        //    doc.GetAcroForm().PutBool("NeedAppearances", true);
        // This will force the viewer application to auto-generate new appearance streams
        // every time the document is opened.
        //
        // Alternatively you can generate custom annotation appearance using ElementWriter
        // and then set the "AP" entry in the widget dictionary to the new appearance
        // stream.
        //
        // Yet another option is to pre-populate field entries with dummy text. When
        // you edit the field values using PDFNet the new field appearances will match
        // the old ones.

        // doc.GetAcroForm().PutBool("NeedAppearances", true);
        // NOTE: refreshFieldAppearances will replace previously generated appearance streams
        doc.refreshFieldAppearances();

        docBuffer = await doc.saveMemoryBuffer(0);
        saveBufferAsPDFDoc(docBuffer, 'forms_test1.pdf');

        console.log('Example 1 complete and everything deallocated.');
      } catch (err) {
        console.log(err.stack);
      }
      //----------------------------------------------------------------------------------
      // Example 2:
      // Fill-in forms / Modify values of existing fields.
      // Traverse all form fields in the document (and print out their names).
      // Search for specific fields in the document.
      //----------------------------------------------------------------------------------

      try {
        console.log('Beginning Test 2');

        // we use the forms test doc from the previous sample
        // Buffers passed into PDFNetJS functions are made invalid afterwards due to the functions taking ownership.
        // If you are using the same buffer to initialize multiple documents, pass in a copy of the buffer.
        const copyOfBuffer = new Uint8Array(docBuffer.buffer.slice(0));
        const doc2 = await PDFNet.PDFDoc.createFromBuffer(copyOfBuffer);

        doc2.initSecurityHandler();
        doc2.lock();
        console.log('Sample 2 PDF document initialized and locked');
        const itr = await doc2.getFieldIteratorBegin();

        for (; await itr.hasNext(); itr.next()) {
          const currentItr = await itr.current();
          console.log('Field name: ' + (await currentItr.getName()));
          console.log('Field partial name: ' + (await currentItr.getPartialName()));

          console.log('Field type: ');
          const type = await currentItr.getType();
          const strVal = await currentItr.getValueAsString();

          switch (type) {
            case PDFNet.Field.Type.e_button: {
              console.log('Button');
              break;
            }
            case PDFNet.Field.Type.e_radio: {
              console.log('Radio button: Value = ' + strVal);
              break;
            }
            case PDFNet.Field.Type.e_check: {
              const currItr = await itr.current();
              currItr.setValueAsBool(true);
              console.log('Check box: Value = ' + strVal);
              break;
            }
            case PDFNet.Field.Type.e_text: {
              console.log('Text');
              // Edit all variable text in the document
              const currItr = await itr.current();
              currItr.setValueAsString('This is a new value. The old one was: ' + strVal);
              break;
            }
            case PDFNet.Field.Type.e_choice: {
              console.log('Choice');
              break;
            }
            case PDFNet.Field.Type.e_signature: {
              console.log('Signature');
              break;
            }
          }
          console.log('-----------------------');
        }
        const f = await doc2.getField('employee.name.first');
        if (f) {
          console.log('Field search for ' + (await f.getName()) + ' was successful');
        } else {
          console.log('Field search failed');
        }
        // Regenerate field appearances.
        doc2.refreshFieldAppearances();

        const docBuffer2 = await doc2.saveMemoryBuffer(0);
        saveBufferAsPDFDoc(docBuffer2, 'forms_test_edit.pdf');
        console.log('Example 2 complete and everything deallocated.');
      } catch (err) {
        console.log(err);
      }
      //----------------------------------------------------------------------------------
      // Sample 3: Form templating
      // Replicate pages and form data within a document. Then rename field names to make
      // them unique.
      //----------------------------------------------------------------------------------
      try {
        // we still keep using our original forms test doc.
        // If you are using the same buffer to initialize multiple documents, pass in a copy of the buffer.
        const copyOfBuffer3 = new Uint8Array(docBuffer.buffer.slice(0));
        const doc3 = await PDFNet.PDFDoc.createFromBuffer(copyOfBuffer3);
        doc3.initSecurityHandler();
        doc3.lock();
        console.log('Sample 3 PDF document initialized and locked');
        const srcPage = await doc3.getPage(1);
        doc3.pagePushBack(srcPage); // Append several copies of the first page
        doc3.pagePushBack(srcPage); // Note that forms are successfully copied
        doc3.pagePushBack(srcPage);
        doc3.pagePushBack(srcPage);

        // Now we rename fields in order to make every field unique.
        // You can use this technique for dynamic template filling where you have a 'master'
        // form page that should be replicated, but with unique field names on every page.
        await RenameAllFields(doc3, 'employee.name.first');
        await RenameAllFields(doc3, 'employee.name.last');
        await RenameAllFields(doc3, 'employee.name.check1');
        await RenameAllFields(doc3, 'submit');

        const docBuffer3 = await doc3.saveMemoryBuffer(0);
        saveBufferAsPDFDoc(docBuffer3, 'forms_test1_cloned.pdf');
        console.log('Example 3 complete and everything deallocated.');
      } catch (err) {
        console.log(err);
      }

      //----------------------------------------------------------------------------------
      // Sample:
      // Flatten all form fields in a document.
      // Note that this sample is intended to show that it is possible to flatten
      // individual fields. PDFNet provides a utility function PDFDoc.FlattenAnnotations()
      // that will automatically flatten all fields.
      //----------------------------------------------------------------------------------

      try {
        const copyOfBuffer4 = new Uint8Array(docBuffer.buffer.slice(0));
        const doc4 = await PDFNet.PDFDoc.createFromBuffer(copyOfBuffer4);
        doc4.initSecurityHandler();
        doc4.lock();
        console.log('Sample 4 PDF document initialized and locked');

        // Flatten all pages
        // eslint-disable-next-line no-constant-condition
        if (true) {
          doc4.flattenAnnotations();
        } else {
          // Manual flattening
          for (let pitr = await doc4.getPageIterator(); await pitr.hasNext(); await pitr.next()) {
            const page = await pitr.current();
            const annots = await page.getAnnots();

            if (annots) {
              // Look for all widget annotations (in reverse order)
              for (let i = parseInt(await annots.size(), 10) - 1; i >= 0; --i) {
                const annotObj = await annots.getAt(i);
                const annotObjSubtype = await annotObj.get('Subtype');
                // eslint-disable-next-line @typescript-eslint/no-unused-vars
                const annotObjVal = await annotObjSubtype.value();
                const annotObjName = await (await (await annotObj.get('Subtype')).value()).getName();

                if (annotObjName === 'Widget') {
                  const field = await PDFNet.Field.create(annotObj);
                  field.flatten(page);

                  // Another way of making a read only field is by modifying
                  // field's e_read_only flag:
                  //    field.SetFlag(Field::e_read_only, true);
                }
              }
            }
          }
        }

        const docBuffer4 = await doc4.saveMemoryBuffer(0);
        saveBufferAsPDFDoc(docBuffer4, 'forms_test1_flattened.pdf');
        console.log('done - Example 4 complete and everything deallocated.');
      } catch (err) {
        console.log(err);
      }
    };

    // add your own license key as the second parameter, e.g. PDFNet.runWithCleanup(main, '<code class="expression">visitor.claims.wvKey || "YOUR_LICENSE_KEY"</code>')
    PDFNet.runWithCleanup(main);
  };
})(window);
// eslint-disable-next-line spaced-comment
//# sourceURL=InteractiveFormsTest.js
</code></pre>


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.apryse.com/web/get-started/samples/interactiveformstest.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
