# Customize Context Menu for Form Widgets

Foxit PDF SDK for Web provides two flexible methods to help developers implement custom context menus for form widgets, enhancing the user interaction experience.

# Option 1: Method Override

Use the ViewerAnnotManager.registerMatchRule (opens new window) method to register a mixin, inherit the WidgetAnnot class, and override the following essential methods:

  • createFillerModeContextMenu:Customizes the context menu in form filling mode
  • createDesignModeContextMenu:Customizes the context menu in form design mode
  • showContextMenu: Customizes the display logic for the context menu

This solution is suitable for scenarios that require deep customization of menu items and interaction logic. It is supported for users developing based on either UIExtension or PDFViewCtrl.

# Option 2: UI Fragments Configuration

By utilizing the UI Fragments mechanism provided by Foxit PDF SDK for Web, developers can achieve the following:

  • Replace existing context menus
  • Extend existing menu items
  • Adjust the order and grouping of menu items

This solution relies on the UIExtension implementation and only supports replacing or extending existing menu items.


# Example

# Method Override

First, create a context menu implementation class that inherits from IContextMenu (opens new window). This class defines the behavior for displaying, hiding, disabling, enabling, and destroying the context menu.

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 automatically hide the IContextMenu menu. Developers need to implement the hiding logic themselves within the CustomContextMenu. Typically, this is achieved by listening for clicks on menu items or clicks outside the menu to hide it.

After completing the custom menu implementation class, you can register a mixin function to override the createFillerModeContextMenu or createDesignModeContextMenu methods:

// Register the 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();
            }
        }
    }
});

If it is a signature form widget, developers also need to consider its signature status. Depending on the different states of the signature form widget, different menus or menu items need to be displayed:

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 can be used to control the display of different menus or different menu items. Its specific behavior is ultimately determined by the application layer
                    if (isSigned) {
                        return new CustomSignedSignatureContextMenu();
                    } else {
                        return new CustomSignatureContextMenu();
                    }
                }
                return new CustomContextMenu();
            }
        }
    }
});

Of course, you can also choose not to implement the creation logic through createFillerModeContextMenu and createDesignModeContextMenu. Instead, you can directly create and display the context menu by overriding the showContextMenu method.

pdfViewer.getAnnotManager().registerMatchRule(function(annot, AnnotClass) {
    if(annot.getType() === 'widget') {
        return class CustomWidgetAnnot extends AnnotClass {
            async showContextMenu(x, y) {
                if(this.isDesignMode) { // Indicates displaying the built-in context 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: Display the context menu for signed signature widgets in filler mode
                    } else {
                        // TODO: Display the context menu for unsigned signature widgets in filler mode
                    }
                } else { 
                    // TODO: Display the context menu for unsigned form widgets in filler mode
                }
            }
        }
    }
});

# UI Fragments Configuration

Below are the built-in context menu components for form widgets in the current SDK:

  • <form:signature-contextmenu @lazy></form:signature-contextmenu>: Context menu for signature form widgets in fill mode.
  • <form-designer-v2:widget-contextmenu @lazy=""></form-designer-v2:widget-contextmenu>: Context menu for form widgets in design mode.

# Context Menu for Signature Form Widgets in Filler Mode

Below is the implementation template:

<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 item description:

  • @form:if-signed directive:Controls the visibility of components based on whether the target signature widget (accessed via right-click) is signed. For example, @form:if-signed.hide means the component will be hidden if the target signature widget is signed. Conversely, @form:if-signed.show means the component will be displayed if the target signature widget is signed.
  • fv--contextmenu-item-signature-sign: Triggers the signature functionality, displayed when right-clicking on an unsigned signature widget.
  • fv--contextmenu-item-signature-verify: Triggers the signature verification functionality, displayed when right-clicking on a signed signature widget.
  • fv--contextmenu-item-signature-properties: Displays the signature properties, displayed when right-clicking on a signed signature widget.
# Example 1: Adding a Menu Item

First, create a custom menu item component:

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);
    }
}
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: Replacing a Menu Item
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,
    //...
});