Iterate over form fields and widgets

You can iterate through all form fields and their associated widgets to update their appearance programmatically.

When to use

Use this iteration pattern when you need to:

  • Apply bulk styling changes to multiple form fields
  • Highlight empty or invalid fields for validation
  • Conditionally format widgets based on field values
  • Perform custom operations on all widgets in a document

Example

The following example demonstrates an optimized approach that caches widget annotations for better performance when working with documents that have many pages. This pattern uses Promise.all() to load annotations from all pages simultaneously, significantly improving performance compared to sequential loading. This example applies a solid border to empty form fields:

function updateWidgetBorder(formField, widget) {
if (formField.value) {
// Reset the widget border properties when form field has a value.
return widget
.set("borderColor", undefined)
.set("borderWidth", undefined)
.set("borderStyle", undefined);
} else {
// Set a solid border if the form field does not have a value.
return widget
.set("borderColor", NutrientViewer.Color.LIGHT_RED)
.set("borderWidth", 1)
.set("borderStyle", "solid");
}
}
async function refreshWidgets() {
// Retrieve all form fields.
const formFields = await instance.getFormFields();
if (formFields.size === 0) {
console.warn("No form fields found in document");
return;
}
// Retrieve all widget annotations in the document.
const widgetAnnotations = (
await Promise.all(
Array.from({
length: instance.totalPageCount,
}).map(async (_, pageIndex) =>
(await instance.getAnnotations(pageIndex)).filter(
(it) => it instanceof NutrientViewer.Annotations.WidgetAnnotation,
),
),
)
).flatMap((pageAnnotations) => pageAnnotations.toArray());
// Iterate over all form fields and their widgets to update their appearance.
const widgetsToUpdate = formFields.flatMap((formField) => {
// Find all widget annotations for a given form field.
const widgets = widgetAnnotations.filter((annotation) =>
formField.annotationIds.includes(annotation.id),
);
// Update all widgets based on the form field value.
return widgets.map((widget) => updateWidgetBorder(formField, widget));
});
if (widgetsToUpdate.length === 0) {
console.warn("No widgets found to update");
return;
}
// Now perform the batch update with your updated widget annotations.
console.log(`Updating ${widgetsToUpdate.length} widget(s)`);
return instance.update(widgetsToUpdate);
}

How this works

The code above demonstrates an efficient pattern for iterating over form fields and widgets:

  1. Parallel loading — Uses Promise.all() to load annotations from all pages simultaneously, reducing total loading time.
  2. Widget caching — Stores all widget annotations in memory to avoid repeated API calls.
  3. Efficient filtering — Uses .flatMap() to find and update only relevant widgets in a single pass.
  4. Batch update — Applies all changes in a single instance.update() call for better performance.

This caching approach is particularly valuable for documents with many pages, as it minimizes the number of API calls and processes all updates efficiently.

This example behaves similarly to what you can achieve with required form fields. If you only need to style required fields, refer to the instructions in the highlight required fields guide.