# 自定义表单域右键菜单
Foxit PDF SDK for Web 提供了两种灵活的方式帮助开发者实现自定义表单域的右键菜单,以增强用户的交互体验。
# 方式一:方法重写(Method Override)
通过 ViewerAnnotManager.registerMatchRule
(opens new window) 方法注册 mixin,继承 WidgetAnnot
类并重写以下关键方法:
createFillerModeContextMenu
:自定义表单填写模式下的右键菜单createDesignModeContextMenu
:自定义表单设计模式下的右键菜单showContextMenu
: 自定义右键菜单的显示逻辑
此方案适用于需要深度自定义菜单项和交互逻辑的场景。无论是基于 UIExtension 还是基于 PDFViewCtrl 开发的用户,都可以支持这种方案。
# 方式二:UI Fragments 配置
通过使用 Foxit PDF SDK for Web 提供的 UI Fragments 机制,开发者可以实现以下操作:
- 替换现有的右键菜单
- 扩展现有菜单项
- 调整菜单项的顺序和分组
此方案依赖于 UIExtension 实现,且仅支持替换或扩展已有的菜单项。
# 示例
# 方法重写
首先,创建一个继承自 IContextMenu
(opens new window) 的右键菜单实现类。该类定义了右键菜单的显示、隐藏、禁用、启用和销毁的行为。
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();
}
}
注意: Foxit PDF SDK for Web 不会主动隐藏 IContextMenu
菜单,开发者需要在 CustomContextMenu
中自行处理隐藏逻辑。通常是通过监听点击菜单项或点击菜单以外的位置来实现隐藏菜单。
在完成自定义菜单实现类后,您可以注册一个 mixin 函数,以重写 createFillerModeContextMenu
或 createDesignModeContextMenu
方法:
// 注册自定义实现
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();
}
}
}
});
如果是签名表单域,开发者还需要注意其签名状态。根据签名表单域的不同状态,需要显示不同的菜单或菜单项:
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 可以控制显示不同的菜单或菜单项。其具体行为最终由应用层来决定。
if (isSigned) {
return new CustomSignedSignatureContextMenu();
} else {
return new CustomSignatureContextMenu();
}
}
return new CustomContextMenu();
}
}
}
});
当然,您也可以选择不通过 createFillerModeContextMenu
和 createDesignModeContextMenu
来实现创建逻辑。您可以直接通过重写 showContextMenu
方法来创建和显示右键菜单:
pdfViewer.getAnnotManager().registerMatchRule(function(annot, AnnotClass) {
if(annot.getType() === 'widget') {
return class CustomWidgetAnnot extends AnnotClass {
async showContextMenu(x, y) {
if(this.isDesignMode) { // 表示设计模式下显示内置的右键菜单
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: 显示填表模式且已签名的签名域右键菜单
} else {
// TODO: 显示填表模式且未签名的签名域右键菜单
}
} else {
// TODO: 显示填表模式且非签名的表单域右键菜单
}
}
}
}
});
# UI Fragments 配置
以下是目前 SDK 中内置的表单域右键菜单组件:
<form:signature-contextmenu @lazy></form:signature-contextmenu>
: 填表模式下的签名表单域右键菜单。<form-designer-v2:widget-contextmenu @lazy=""></form-designer-v2:widget-contextmenu>
: 设计模式下的表单域右键菜单。
# 填表模式下的签名表单域右键菜单
以下是其实现模板:
<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>
菜单项说明:
@form:if-signed
指令:可以根据当前右键的目标签名域是否已签名来控制组件是否显示。比如,@form:if-signed.hide
表示如果目标签名域已签名,则隐藏该组件。相对应的,@form:if-signed.show
则表示如果目标签名域已签名,则显示该组件。fv--contextmenu-item-signature-sign
: 触发签名功能,在未签名的签名域上右键时显示。fv--contextmenu-item-signature-verify
: 触发验签功能,在已签名的签名域上右键时显示。fv--contextmenu-item-signature-properties
: 显示签名属性,在已签名的签名域上右键时显示。
# 示例1:插入菜单项
首先,实现一个自定义菜单项组件:
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,
//...
});
# 示例2:替换菜单项
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,
//...
});