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 modecreateDesignModeContextMenu: Context menu in design modeshowContextMenu: 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.hidehides when signed;@form:if-signed.showshows 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,
//...
});