Skip to content

自定义书签 UI 组件

本节主要介绍书签组件的自定义方法,由于书签组件基于 tree 组件开发, 在开始前,请先了解 tree 组件的 用法 以及相关 API文档

提示:点击 这里, 了解 PDF 书签 API。

书签组件结构

本小节介绍书签相关组件的模板结构,涉及如下几个组件:

书签面板

在内置的布局模板中,<bookmark-v2:bookmark-tree></bookmark-v2:bookmark-tree> 指的是左侧边栏书签面板,它实际上等同于下面的复杂模板:

html

<sidebar-panel
        name="sidebar-bookmark-v2"
        class="fv__ui-sidebar-bookmark-v2"
        @tooltip
        tooltip-placement="right"
        tooltip-title="sidebar.bookmark.tooltip"
        title="sidebar.bookmark.title"
        icon-class="fv__icon-sidebar-bookmark"
        @lazy-content="active"
>
    <bookmark-v2:bookmark-tree></bookmark-v2:bookmark-tree>
</sidebar-panel>

在应用中,复杂模板可以替换内置的精简模板, 并修改其属性:

html

<html>
<div id="pdf-ui"></div>
<template id="custom-bookmark-panel-template">
    <sidebar-panel
            name="sidebar-bookmark-v2"
            class="fv__ui-sidebar-bookmark-v2"
            @tooltip
            tooltip-placement="right"
            tooltip-title="Custom Bookmark Sidebar Panel Tooltip Title"
            title="Custom Bookmark Sidebar Panel Title"
            icon-class="fv__icon-sidebar-bookmark"
            @lazy-content="active"
    >
        <bookmark-v2:bookmark-tree></bookmark-v2:bookmark-tree>
    </sidebar-panel>
</template>
</html>
<style>
    html {
        overflow: hidden;
    }

    body {
        height: 100vh;
    }

    #pdf-ui {
        position: relative;
        top: 50px;
    }
</style>
<script>
    const customBookmarkPanelTemplate = document.getElementById('custom-bookmark-panel-template').innerHTML;
    const CustomAppearance = UIExtension.appearances.adaptive.extend({
        getDefaultFragments() {
            return [{
                target: 'sidebar-bookmark-v2',
                action: UIExtension.UIConsts.FRAGMENT_ACTION.REPLACE,
                template: customBookmarkPanelTemplate
            }];
        }
    });
    const libPath = window.top.location.origin + '/lib';
    const pdfui = new UIExtension.PDFUI({
        viewerOptions: {
            libPath: libPath,
            jr: {
                licenseSN: licenseSN,
                licenseKey: licenseKey
            }
        },
        renderTo: '#pdf-ui',
        appearance: CustomAppearance,
        addons: []
    });
</script>
json
{
  "iframeOptions": {
    "style": "height: 500px"
  }
}

书签右键菜单

在内置布局模板中,<bookmark-v2:bookmark-contextmenu @lazy></bookmark-v2:bookmark-contextmenu> 指的是书签右键菜单,其相当于下面的模板:

html

<contextmenu name="fv--bookmark-contextmenu-v2">
    <bookmark-v2:add-bookmark name="fv--bookmark-contextmenu-item-add"></bookmark-v2:add-bookmark>
    <contextmenu-separator @bookmark-v2:hide-if-no-bookmark></contextmenu-separator>
    <bookmark-v2:goto-bookmark @bookmark-v2:hide-if-no-bookmark
                               name="fv--bookmark-contextmenu-item-goto"></bookmark-v2:goto-bookmark>
    <contextmenu-separator @bookmark-v2:hide-if-no-bookmark></contextmenu-separator>
    <bookmark-v2:cut-bookmark @bookmark-v2:hide-if-no-bookmark
                              name="fv--bookmark-contextmenu-item-cut"></bookmark-v2:cut-bookmark>
    <bookmark-v2:paste-under-selected-bookmark @bookmark-v2:hide-if-no-bookmark
                                               name="fv--bookmark-contextmenu-item-paste-under"></bookmark-v2:paste-under-selected-bookmark>
    <bookmark-v2:paste-after-selected-bookmark @bookmark-v2:hide-if-no-bookmark
                                               name="fv--bookmark-contextmenu-item-paste-after"></bookmark-v2:paste-after-selected-bookmark>
    <bookmark-v2:delete-bookmark @bookmark-v2:hide-if-no-bookmark
                                 name="fv--bookmark-contextmenu-item-delete"></bookmark-v2:delete-bookmark>
    <bookmark-v2:set-destination @bookmark-v2:hide-if-no-bookmark
                                 name="fv--bookmark-contextmenu-item-set-destination"></bookmark-v2:set-destination>
    <bookmark-v2:rename-bookmark @bookmark-v2:hide-if-no-bookmark
                                 name="fv--bookmark-contextmenu-item-rename"></bookmark-v2:rename-bookmark>
</contextmenu>

在模板中,对于菜单项,我们提供了一个 @bookmark-v2:hide-if-no-bookmark 指令。这个指令的作用是:当文档没有书签时,右键点击书签面板,我们需要隐藏一些菜单项,只留下 <bookmark-v2:add-bookmark> 一个组件。

根据这个模板,我们只需要定义一个name 为 fv--bookmark-contextmenu-v2 的右键菜单,替换内置的书签右键菜单,就可以实现覆盖内置书签右键菜单的效果。以下是一个示例:

html

<html>
<div id="pdf-ui"></div>
<template id="custom-contextmenu-template">
    <contextmenu name="fv--bookmark-contextmenu-v2">
        <bookmark-v2:add-bookmark name="fv--bookmark-contextmenu-item-add"></bookmark-v2:add-bookmark>
        <contextmenu-separator @bookmark-v2:hide-if-no-bookmark></contextmenu-separator>
        <bookmark-v2:goto-bookmark @bookmark-v2:hide-if-no-bookmark
                                   name="fv--bookmark-contextmenu-item-goto"></bookmark-v2:goto-bookmark>
        <contextmenu-separator @bookmark-v2:hide-if-no-bookmark></contextmenu-separator>
        <bookmark-v2:cut-bookmark @bookmark-v2:hide-if-no-bookmark
                                  name="fv--bookmark-contextmenu-item-cut"></bookmark-v2:cut-bookmark>
        <bookmark-v2:paste-under-selected-bookmark @bookmark-v2:hide-if-no-bookmark
                                                   name="fv--bookmark-contextmenu-item-paste-under"></bookmark-v2:paste-under-selected-bookmark>
        <bookmark-v2:paste-after-selected-bookmark @bookmark-v2:hide-if-no-bookmark
                                                   name="fv--bookmark-contextmenu-item-paste-after"></bookmark-v2:paste-after-selected-bookmark>
        <bookmark-v2:delete-bookmark @bookmark-v2:hide-if-no-bookmark
                                     name="fv--bookmark-contextmenu-item-delete"></bookmark-v2:delete-bookmark>
        <bookmark-v2:set-destination @bookmark-v2:hide-if-no-bookmark
                                     name="fv--bookmark-contextmenu-item-set-destination"></bookmark-v2:set-destination>
        <bookmark-v2:rename-bookmark @bookmark-v2:hide-if-no-bookmark
                                     name="fv--bookmark-contextmenu-item-rename"></bookmark-v2:rename-bookmark>
    </contextmenu>
</template>
</html>
<style>
    html {
        overflow: hidden;
    }

    body {
        height: 100vh;
    }

    #pdf-ui {
        position: relative;
        top: 50px;
    }
</style>
<script>
    const customBookmarkContextmenuTemplate = document.getElementById('custom-contextmenu-template').innerHTML;
    const CustomAppearance = UIExtension.appearances.adaptive.extend({
        getDefaultFragments() {
            return [{
                target: 'fv--bookmark-contextmenu-v2',
                action: UIExtension.UIConsts.FRAGMENT_ACTION.REPLACE,
                template: customBookmarkContextmenuTemplate
            }];
        }
    });
    const libPath = window.top.location.origin + '/lib';
    const pdfui = new UIExtension.PDFUI({
        viewerOptions: {
            libPath: libPath,
            jr: {
                licenseSN: licenseSN,
                licenseKey: licenseKey
            }
        },
        renderTo: '#pdf-ui',
        appearance: CustomAppearance,
        addons: []
    });
</script>
json
{
  "iframeOptions": {
    "style": "height: 500px"
  }
}

当然,除了整体替换以外, 您也可以单独删除或增加菜单项:

html

<html>
<div id="pdf-ui"></div>
<template id="custom-contextmenu-template">
    <contextmenu name="fv--bookmark-contextmenu-v2">
        <bookmark-v2:add-bookmark name="fv--bookmark-contextmenu-item-add"></bookmark-v2:add-bookmark>
        <contextmenu-separator @bookmark-v2:hide-if-no-bookmark></contextmenu-separator>
        <bookmark-v2:goto-bookmark @bookmark-v2:hide-if-no-bookmark
                                   name="fv--bookmark-contextmenu-item-goto"></bookmark-v2:goto-bookmark>
        <contextmenu-separator @bookmark-v2:hide-if-no-bookmark></contextmenu-separator>
        <bookmark-v2:cut-bookmark @bookmark-v2:hide-if-no-bookmark
                                  name="fv--bookmark-contextmenu-item-cut"></bookmark-v2:cut-bookmark>
        <bookmark-v2:paste-under-selected-bookmark @bookmark-v2:hide-if-no-bookmark
                                                   name="fv--bookmark-contextmenu-item-paste-under"></bookmark-v2:paste-under-selected-bookmark>
        <bookmark-v2:paste-after-selected-bookmark @bookmark-v2:hide-if-no-bookmark
                                                   name="fv--bookmark-contextmenu-item-paste-after"></bookmark-v2:paste-after-selected-bookmark>
        <bookmark-v2:delete-bookmark @bookmark-v2:hide-if-no-bookmark
                                     name="fv--bookmark-contextmenu-item-delete"></bookmark-v2:delete-bookmark>
        <bookmark-v2:set-destination @bookmark-v2:hide-if-no-bookmark
                                     name="fv--bookmark-contextmenu-item-set-destination"></bookmark-v2:set-destination>
        <bookmark-v2:rename-bookmark @bookmark-v2:hide-if-no-bookmark
                                     name="fv--bookmark-contextmenu-item-rename"></bookmark-v2:rename-bookmark>
    </contextmenu>
</template>
</html>
<style>
    html {
        overflow: hidden;
    }

    body {
        height: 100vh;
    }

    #pdf-ui {
        position: relative;
        top: 50px;
    }
</style>
<script>
    const customBookmarkContextmenuTemplate = document.getElementById('custom-contextmenu-template').innerHTML;

    class CustomContextMenuItemComponent extends UIExtension.SeniorComponentFactory.createSuperClass({
        template: `
            <contextmenu-item @on.click="$component.handleClick()">Custom Bookmark Contextmenu Item</contextmenu-item>
        `
    }) {
        static getName() {
            return 'custom-bookmark-contextmenu-item';
        }

        handleClick() {
            const contextmenu = this.parent;
            const target = contextmenu.getCurrentTarget();
            if (target instanceof UIExtension.components.widgets.TreeNodeComponent) {
                alert('Click on bookmark item: ' + target.title);
            }
        }
    }

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

    const CustomAppearance = UIExtension.appearances.adaptive.extend({
        getDefaultFragments() {
            return [{
                target: '@bookmark-v2:rename-bookmark',
                action: UIExtension.UIConsts.FRAGMENT_ACTION.REMOVE
            }, {
                target: 'fv--bookmark-contextmenu-v2',
                action: UIExtension.UIConsts.FRAGMENT_ACTION.APPEND,
                template: `<custom:custom-bookmark-contextmenu-item></custom:custom-bookmark-contextmenu-item>`
            }];
        }
    });
    const libPath = window.top.location.origin + '/lib';
    const pdfui = new UIExtension.PDFUI({
        viewerOptions: {
            libPath: libPath,
            jr: {
                licenseSN: licenseSN,
                licenseKey: licenseKey
            }
        },
        renderTo: '#pdf-ui',
        appearance: CustomAppearance,
        addons: []
    });
</script>
json
{
  "iframeOptions": {
    "style": "height: 500px"
  }
}

书签右键菜单项

  1. 添加书签菜单项:
    • 精简模板: <bookmark-v2:add-bookmark name="fv--bookmark-contextmenu-item-add"></bookmark-v2:add-bookmark>
    • 完整模板:<contextmenu-item @controller="AddBookmarkController">contextmenu.bookmark.add</contextmenu-item>
  2. 跳转功能菜单项:
    • 精简模板: <bookmark-v2:goto-bookmark @bookmark-v2:hide-if-no-bookmark name="fv--bookmark-contextmenu-item-goto"></bookmark-v2:goto-bookmark>
    • 完整模板: <contextmenu-item @controller="GotoBookmarkController" @bookmark-v2:hide-if-no-bookmark name="fv--bookmark-contextmenu-item-goto">contextmenu.bookmark.goTo</contextmenu-item>
  3. 剪切功能菜单项:
    • 精简模板: <bookmark-v2:cut-bookmark @bookmark-v2:hide-if-no-bookmark name="fv--bookmark-contextmenu-item-cut"></bookmark-v2:cut-bookmark>
    • 完整模板: <contextmenu-item @controller="CutBookmarkController" @bookmark-v2:hide-if-no-bookmark name="fv--bookmark-contextmenu-item-cut">contextmenu.bookmark.cut</contextmenu-item>
  4. 粘贴书签到所选书签下面的功能菜单项:
    • 精简模板: <bookmark-v2:paste-under-selected-bookmark @bookmark-v2:hide-if-no-bookmark name="fv--bookmark-contextmenu-item-paste-under"></bookmark-v2:paste-under-selected-bookmark>
    • 完整模板: <contextmenu-item @controller="PasteUnderSelectedBookmarkController as ctrl" @show="ctrl.pasteAvailable" @bookmark-v2:hide-if-no-bookmark name="fv--bookmark-contextmenu-item-paste-under">contextmenu.bookmark.pasteUnder</contextmenu-item>
  5. 粘贴书签到所选书签后面的功能菜单项:
    • 精简模板: <bookmark-v2:paste-after-selected-bookmark @bookmark-v2:hide-if-no-bookmark name="fv--bookmark-contextmenu-item-paste-after"></bookmark-v2:paste-after-selected-bookmark>
    • 完整模板: <contextmenu-item @controller="PasteAfterSelectedBookmarkController as ctrl" @show="ctrl.pasteAvailable" @bookmark-v2:hide-if-no-bookmark name="fv--bookmark-contextmenu-item-paste-after">contextmenu.bookmark.pasteAfter</contextmenu-item>
  6. 删除书签功能菜单项:
    • 精简模板: <bookmark-v2:delete-bookmark @bookmark-v2:hide-if-no-bookmark name="fv--bookmark-contextmenu-item-delete"></bookmark-v2:delete-bookmark>
    • 完整模板: <contextmenu-item @controller="DeleteBookmarkController" @bookmark-v2:hide-if-no-bookmark name="fv--bookmark-contextmenu-item-delete">contextmenu.bookmark.delete</contextmenu-item>
  7. 设置书签目标位置菜单项:
    • 精简模板: <bookmark-v2:set-destination @bookmark-v2:hide-if-no-bookmark name="fv--bookmark-contextmenu-item-set-destination"></bookmark-v2:set-destination>
    • 完整模板: <contextmenu-item @controller="SetDestinationBookmarkController" @bookmark-v2:hide-if-no-bookmark name="fv--bookmark-contextmenu-item-set-destination">contextmenu.bookmark.destination</contextmenu-item>
  8. 重命名书签菜单项:
    • 精简模板: <bookmark-v2:rename-bookmark @bookmark-v2:hide-if-no-bookmark name="fv--bookmark-contextmenu-item-rename"></bookmark-v2:rename-bookmark>
    • 完整模板: <contextmenu-item @controller="RenameBookmarkController" @bookmark-v2:hide-if-no-bookmark name="fv--bookmark-contextmenu-item-rename">contextmenu.bookmark.rename</contextmenu-item>

实现自定义菜单项功能代码示例

以上是 SDK 支持的内置书签右键菜单项。应用层可以复用这些菜单项的 @controller 实现,替换内置菜单项来实现自定义菜单项功能。

  • 点击 这里,了解更多 Controller 复用。
  • 点击 这里,了解 Controller 指令。

示例代码:

html

<html>
<div id="pdf-ui"></div>
</html>
<style>
    html {
        overflow: hidden;
    }

    body {
        height: 100vh;
    }

    #pdf-ui {
        position: relative;
        top: 50px;
    }
</style>
<script>

    class CustomContextMenuItemComponent extends UIExtension.SeniorComponentFactory.createSuperClass({
        template: `
            <contextmenu-item @controller="bookmark-v2:RenameBookmarkController" @bookmark-v2:hide-if-no-bookmark name="fv--bookmark-contextmenu-item-rename" @on.click="$component.handleClick()">contextmenu.bookmark.rename</contextmenu-item>
        `
    }) {
        static getName() {
            return 'custom-bookmark-contextmenu-item';
        }

        handleClick() {
            const contextmenu = this.parent;
            const target = contextmenu.getCurrentTarget();
            if (target instanceof UIExtension.components.widgets.TreeNodeComponent) {
                console.log('Rename bookmark item: ' + target.title);
            }
        }
    }

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

    const CustomAppearance = UIExtension.appearances.adaptive.extend({
        getDefaultFragments() {
            return [{
                target: '@bookmark-v2:rename-bookmark',
                action: UIExtension.UIConsts.FRAGMENT_ACTION.REPLACE,
                template: `<custom:custom-bookmark-contextmenu-item></custom:custom-bookmark-contextmenu-item>`
            }];
        }
    });
    const libPath = window.top.location.origin + '/lib';
    const pdfui = new UIExtension.PDFUI({
        viewerOptions: {
            libPath: libPath,
            jr: {
                licenseSN: licenseSN,
                licenseKey: licenseKey
            }
        },
        renderTo: '#pdf-ui',
        appearance: CustomAppearance,
        addons: []
    });
</script>
json
{
  "iframeOptions": {
    "style": "height: 500px"
  }
}

书签树节点

书签树节点,也就是 TreeNodeComponent。它与 PDF 文档的书签数据同步,随着书签数据的创建或删除移动而被创建或销毁。因此,要自定义书签树节点,需要通过事件来完成。TreeComponent 组件开放了这两个事件:

  1. create-tree-node 事件,在创建树节点时触发,可以用于修改树节点外观以及注册树节点上的事件等。
  2. destroy-tree-node 事件,在树节点被销毁时触发,可以用于释放一些数据,避免内存泄露。

以下是一个示例, 展示了如何将树节点的文本内容替换为链接:

html

<html>
<div id="pdf-ui"></div>
</html>
<style>
    html {
        overflow: hidden;
    }

    body {
        height: 100vh;
    }

    #pdf-ui {
        position: relative;
        top: 50px;
    }
</style>
<script>
    const libPath = window.top.location.origin + '/lib';
    const pdfui = new UIExtension.PDFUI({
        viewerOptions: {
            libPath: libPath,
            jr: {
                licenseSN: licenseSN,
                licenseKey: licenseKey
            }
        },
        renderTo: '#pdf-ui',
        appearance: UIExtension.appearances.adaptive,
        addons: []
    });
    pdfui.getRootComponent().then(root => {
        return root.querySelector('@bookmark-v2:bookmark-tree').getTree()
    }).then(tree => {
        tree.on('create-tree-node', treeNode => {
            const newTitleElement = document.createElement('a');
            treeNode.titleElement.replaceWith(newTitleElement);
            treeNode.titleElement = newTitleElement;
            newTitleElement.target = "about:blank";
            newTitleElement.href = "https://foxit.com"
        })
    })
</script>
json
{
  "iframeOptions": {
    "style": "height: 500px"
  }
}