Skip to content

Customize the form field context menu ​

Foxit PDF SDK for Web offers two flexible ways to customize the form field context menu and improve interaction.

Option 1: Method override ​

Use ViewerAnnotManager.registerMatchRule to register a mixin, extend WidgetAnnot, and override:

  • createFillerModeContextMenu: Context menu in fill mode
  • createDesignModeContextMenu: Context menu in design mode
  • showContextMenu: How the context menu is shown

Use this when you need deep customization of menu items and behavior. It works with UIExtension and PDFViewCtrl-based apps.

Option 2: UI Fragments ​

With UI Fragments you can:

  • Replace the existing context menu
  • Extend existing menu items
  • Change item order and grouping

This depends on UIExtension and only supports replacing or extending existing menu items.

Examples ​

Method override ​

First, implement a context menu class that extends IContextMenu:

javascript
class CustomContextMenu extends PDFViewCtrl.viewerui.IContextMenu {
    constructor(widgetAnnotComponent) {
        super();
        this.element = document.createElement('div');
        this.element.className = 'custom-context-menu';
        this.element.innerHTML = `
            <div class="item" action="properties">Properties</div>
            <div class="item" action="delete">Delete</div>
        `;
        this.widgetAnnotComponent = widgetAnnotComponent;
        this.element.addEventListener('click', e => {
            const action = e.target.getAttribute('action');
            if (action == 'properties') {
                // Show properties
                pdfui.getStateHandlerManager().then(shm => {
                    shm.switchTo(PDFViewCtrl.STATE_HANDLER_NAMES.STATE_HANDLER_SELECT_ANNOTATION)
                    pdfui.activateElement(widgetAnnotComponent);
                    pdfui.getComponentByName('fv--form-designer-widget-properties-dialog').then(component => {
                        component.show();
                    })
                });
            } else if (action == 'delete') {
                const annot = this.widgetAnnotComponent.annot;
                const page = annot.page;
                page.removeAnnotByObjectNumber(annot.getObjectNumber());
            }
        });

    }

    showAt(x, y) {
        super.showAt(x, y);
        document.body.append(this.element);
        this.element.style.left = x + 'px';
        this.element.style.top = y + 'px';
        document.addEventListener('mouseup', async e => {
            await new Promise(resolve => setTimeout(resolve, 100));
            this.element.remove();
        }, {once: true});
    }

    disable() {
        super.disable();
        this.element.classList.add('disabled');
    }

    enable() {
        super.enable();
        this.element.classList.remove('disabled');
    }

    destroy() {
        super.destroy();
        this.element.remove();
    }
}

NOTE

Foxit PDF SDK for Web does not hide IContextMenu automatically. Handle hiding in CustomContextMenu, typically on menu item click or click outside the menu.

Register a mixin to override createFillerModeContextMenu or createDesignModeContextMenu:

javascript
// Register custom implementation
pdfViewer.getAnnotManager().registerMatchRule(function (annot, AnnotClass) {
    if (annot.getType() === 'widget') {
        return class CustomWidgetAnnot extends AnnotClass {
            async createFillerModeContextMenu() {
                return new CustomContextMenu();
            }

            async createDesignModeContextMenu() {
                return new CustomContextMenu();
            }
        }
    }
});

For signature fields, account for signature state and show different menus or items:

javascript
class CustomSignatureContextMenu extends PDFViewCtrl.viewerui.IContextMenu {
    // ...
}

class CustomSignedSignatureContextMenu extends PDFViewCtrl.viewerui.IContextMenu {
    // ...
}

pdfViewer.getAnnotManager().registerMatchRule(function (annot, AnnotClass) {
    if (annot.getType() === 'widget') {
        return class CustomWidgetAnnot extends AnnotClass {
            async createFillerModeContextMenu() {
                const field = annot.getField();
                const isSignature = field.getType() === FieldType.Sign;
                if (isSignature) {
                    const isSigned = await field.isSigned();
                    // isSigned controls which menu to show; behavior is up to your app
                    if (isSigned) {
                        return new CustomSignedSignatureContextMenu();
                    } else {
                        return new CustomSignatureContextMenu();
                    }
                }
                return new CustomContextMenu();
            }
        }
    }
});

You can also override showContextMenu instead of the create methods:

javascript
pdfViewer.getAnnotManager().registerMatchRule(function (annot, AnnotClass) {
    if (annot.getType() === 'widget') {
        return class CustomWidgetAnnot extends AnnotClass {
            async showContextMenu(x, y) {
                if (this.isDesignMode) { // Use built-in menu in design mode
                    return super.showContextMenu(x, y);
                }
                const field = annot.getField();
                const isSignature = field.getType() === FieldType.Sign;

                if (isSignature) {
                    const isSigned = await field.isSigned();
                    if (isSigned) {
                        // TODO: Context menu for signed signature field in fill mode
                    } else {
                        // TODO: Context menu for unsigned signature field in fill mode
                    }
                } else {
                    // TODO: Context menu for non-signature field in fill mode
                }
            }
        }
    }
});

UI Fragments ​

Built-in form field context menu components:

  • <form:signature-contextmenu @lazy></form:signature-contextmenu>: Signature field menu in fill mode.
  • <form-designer-v2:widget-contextmenu @lazy=""></form-designer-v2:widget-contextmenu>: Form field menu in design mode.

Signature field menu in fill mode ​

Template:

html
<contextmenu name="fv--field-signature-contextmenu">
    <contextmenu-item name="fv--contextmenu-item-signature-sign" @controller="form:SignatureSignDocController"
                      @form:if-signed.hide visible="false">signDocument.sign
    </contextmenu-item>
    <contextmenu-item name="fv--contextmenu-item-signature-verify" @controller="form:SignatureVerifyController"
                      @form:if-signed.show visible="false">verifySign.verify
    </contextmenu-item>
    <contextmenu-separator @form:if-signed.show visible="false"></contextmenu-separator>
    <contextmenu-item name="fv--contextmenu-item-signature-properties" @controller="form:SignaturePropertiesController"
                      @form:if-signed.show visible="false">signDocument.showSignProperty
    </contextmenu-item>
</contextmenu>

Menu items:

  • @form:if-signed: Show or hide based on whether the target signature field is signed. @form:if-signed.hide hides when signed; @form:if-signed.show shows when signed.
  • fv--contextmenu-item-signature-sign: Sign; shown for unsigned fields.
  • fv--contextmenu-item-signature-verify: Verify; shown for signed fields.
  • fv--contextmenu-item-signature-properties: Signature properties; shown for signed fields.
Example 1: Append a menu item ​

Custom menu item component:

javascript
const customModule = UIExtension.modular.module('custom', []);

class CustomContextMenuItem extends UIExtension.SeniorComponentFactory.createSuperClass({
    template: `<contextmenu-item @on.click="$component.onClick()"></contextmenu-item>`
}) {
    static getName() {
        return 'custom-signature-contextmenu';
    }

    onClick() {
        const currentWidget = this.parent.getCurrentTarget();
        console.log(currentWidget);
    }
}
javascript
const FRAGMENT_ACTION = UIExtension.UIConsts.FRAGMENT_ACTION;
const CustomAppearance = UIExtension.appearances.AdaptiveAppearance.extend({
    getDefaultFragments: function () {
        const isMobile = PDFViewCtrl.DeviceInfo.isMobile;
        if (isMobile) {
            return [];
        } else {
            return [{
                target: 'fv--field-signature-contextmenu',
                action: FRAGMENT_ACTION.APPEND,
                template: `<custom:custom-signature-contextmenu></custom:custom-signature-contextmenu>`
            }];
        }
    }
});

const pdfui = new UIExtension.PDFUI({
    appearance: CustomAppearance,
    //...
});
Example 2: Replace a menu item ​
javascript
const FRAGMENT_ACTION = UIExtension.UIConsts.FRAGMENT_ACTION;
const CustomAppearance = UIExtension.appearances.AdaptiveAppearance.extend({
    getDefaultFragments: function () {
        const isMobile = PDFViewCtrl.DeviceInfo.isMobile;
        if (isMobile) {
            return [];
        } else {
            return [{
                target: 'fv--contextmenu-item-signature-properties',
                action: FRAGMENT_ACTION.REPLACE,
                template: `<custom:custom-signature-contextmenu></custom:custom-signature-contextmenu>`
            }];
        }
    }
});

const pdfui = new UIExtension.PDFUI({
    appearance: CustomAppearance,
    //...
});