# # 可扩展组件与使用指南

本节提供了可扩展组件的详细信息和使用指南，列举了可扩展组件的扩展方法，并帮助开发者更好地理解和使用组件的自定义功能。在以下文档中，我们将首先介绍可扩展组件的概念以及使用 Component API 和 Fragment 配置来扩展它们的方法。然后，我们将提供一个详细的可扩展组件列表，以便开发者可以清楚地了解哪些组件可以被修改。

## # 前置知识

为了更好地理解本文档，您可能需要先了解以下知识点：

1. 关于 Fragment 配置： 请参阅 UI Fragments 小节。
2. 关于 Appearance 的使用：请参阅 Appearance 小节。
3. 关于布局模板：请参阅 布局模板 小节。
4. 关于组件选择器语法： 请参阅 组件选择器 小节。
5. 关于 Component API： 请参阅 API Reference
   (opens new window) 小节。
6. 关于 `@controller` 指令： 请参阅 @controller 小节。
7. 关于 `@tooltip` 指令: 请参阅 @tooltip 小节。
8. 关于 `@on` 指令： 请参阅 @on 小节。

接下来的内容将运用上述提到的知识。在阅读本节之前，请先了解这些知识，这里将不再重复介绍。

## # 可扩展组件概述

1. 什么是可扩展组件：通过 Fragment 配置或 Component API，可以在功能和外观上进行自定义和扩展的组件。

2. 什么是不可扩展组件：与可扩展组件相反，这些组件不能被修改。这些组件在 SDK 内部进行了深度封装，因此无法对其进行细粒度的拆解和组合。但是其中一些组件可以作为一个整体进行替换 (例如，Annotation 属性对话框)。后续章节将提供更多具体细节。

3. 注意事项: 有些组件没有唯一的名称标识符，因此无法直接通过名称来指定目标组件。在这种情况下，可以使用 组件选择器语法。

## # 可扩展组件

### # 头部区域

#### # 标签组件

标签组件放在名称为 `toolbar-tabs` 的 div 容器中，而标签面板放在名称为 `toolbar-tab-bodies` 的 div 容器中。如果要新增或删除标签，需要同时修改这两个容器中组件。以下是一个示例 (点击 `run` 按钮查看效果)：

1. 在末尾增加标签

   在这个示例中，我们在标签栏末尾插入了一个名为 `custom-tab` 的新标签，并同时插入了一个名为 `fv--custom-tab-body` 的 div 组件。

       <html>
       </html>
       <script>
           var FRAGMENT_ACTION = UIExtension.UIConsts.FRAGMENT_ACTION
           var CustomAppearance = UIExtension.appearances.adaptive.extend({
               getDefaultFragments: function() {
                   return [{
                       target: 'toolbar-tabs',
                       action: FRAGMENT_ACTION.APPEND,
                       template: '<gtab name="custom-tab" group="toolbar-tab" body="fv--custom-tab-body" text="Custom Tab" @aria:toolbar.tab></gtab>'
                   }, {
                       target: 'toolbar-tab-bodies',
                       action: FRAGMENT_ACTION.APPEND,
                       template: '<div name="fv--custom-tab-body" style="padding:2em 1em;">Custom Tab Body</div>' // 自定义标签面板不限制组件类型，但请确保名称与 `gtab` 中指定的 `body` 属性值相同。
                   }];
               }
           });
           var libPath = window.top.location.origin + '/lib';
           var pdfui = new UIExtension.PDFUI({
                   viewerOptions: {
                       libPath: libPath,
                       jr: {
                           licenseSN: licenseSN,
                           licenseKey: licenseKey
                       }
                   },
                   renderTo: document.body,
                   appearance: CustomAppearance,
                   addons: [] // No addon is loaded
           });
       </script>

       {
           "iframeOptions": {
               "style": "height: 300px;"
           }
       }

2. 删除指定标签

   在这个示例中，我们删除了 `edit-tab` 和 `fv--edit-tab-paddle` 两个组件，然后 `edit` 标签将不会在界面中显示。

       <html>
       </html>
       <script>
           var FRAGMENT_ACTION = UIExtension.UIConsts.FRAGMENT_ACTION
           var CustomAppearance = UIExtension.appearances.adaptive.extend({
               getDefaultFragments: function() {
                   return [{
                       target: 'edit-tab,fv--edit-tab-paddle', // 多个组件名可以使用逗号隔开，这些组件将被一起删除。
                       action: FRAGMENT_ACTION.REMOVE
                   }];
               }
           });
           var libPath = window.top.location.origin + '/lib';
           var pdfui = new UIExtension.PDFUI({
                   viewerOptions: {
                       libPath: libPath,
                       jr: {
                           licenseSN: licenseSN,
                           licenseKey: licenseKey
                       }
                   },
                   renderTo: document.body,
                   appearance: CustomAppearance,
                   addons: [] // No addon is loaded
           });
       </script>

       {
           "iframeOptions": {
               "style": "height: 300px;"
           }
       }

3. 替换指定标签

   在这个示例中，`edit-tab` 标签被替换为一个自定义标签。

       <html>
       </html>
       <script>
           var FRAGMENT_ACTION = UIExtension.UIConsts.FRAGMENT_ACTION
           var CustomAppearance = UIExtension.appearances.adaptive.extend({
               getDefaultFragments: function() {
                   return [{
                       target: 'edit-tab',
                       action: FRAGMENT_ACTION.REPLACE,
                       template: '<gtab name="custom-tab" group="toolbar-tab" body="fv--edit-tab-paddle" text="Custom Tab" @aria:toolbar.tab></gtab>'
                   }, {
                       target: 'fv--edit-tab-paddle',
                       action: FRAGMENT_ACTION.REPLACE,
                       template: '<div style="padding:2em 1em;">Custom Tab Body</div>'
                   }];
               }
           });
           var libPath = window.top.location.origin + '/lib';
           var pdfui = new UIExtension.PDFUI({
                   viewerOptions: {
                       libPath: libPath,
                       jr: {
                           licenseSN: licenseSN,
                           licenseKey: licenseKey
                       }
                   },
                   renderTo: document.body,
                   appearance: CustomAppearance,
                   addons: [] // No addon is loaded
           });
       </script>

       {
           "iframeOptions": {
               "style": "height: 300px;"
           }
       }

4. 在指定位置插入标签

   以下示例在指定位置之后插入一个自定义标签。类似地，还有一个 `FRAGMENT_ACTION.BEFORE` 操作，表示组件将插入到指定目标组件之前。

       <html>
       </html>
       <script>
           var FRAGMENT_ACTION = UIExtension.UIConsts.FRAGMENT_ACTION
           var CustomAppearance = UIExtension.appearances.adaptive.extend({
               getDefaultFragments: function() {
                   return [{
                       target: 'edit-tab',
                       action: FRAGMENT_ACTION.AFTER, // 将自定义标签插入到 fv--edit-tab 之后。
                       template: '<gtab name="custom-tab" group="toolbar-tab" body="fv--custom-tab-body" text="Custom Tab" @aria:toolbar.tab></gtab>'
                   }, {
                       target: 'fv--edit-tab-paddle', // 请注意，这里的顺序并不重要，因为标签面板一次只显示一个，此操作仅用于演示目的。
                       action: FRAGMENT_ACTION.AFTER,
                       template: '<div name="fv--custom-tab-body" style="padding:2em 1em;">Custom Tab Body</div>'
                   }];
               }
           });
           var libPath = window.top.location.origin + '/lib';
           var pdfui = new UIExtension.PDFUI({
                   viewerOptions: {
                       libPath: libPath,
                       jr: {
                           licenseSN: licenseSN,
                           licenseKey: licenseKey
                       }
                   },
                   renderTo: document.body,
                   appearance: CustomAppearance,
                   addons: [] // No addon is loaded
           });
       </script>

       {
           "iframeOptions": {
               "style": "height: 300px;"
           }
       }

5. 使用 Component API

   以下示例使用了选择器语法和 `Component.after` 接口，向页面中插入更多的标签组件。

       <html>
       </html>
       <script>
           var libPath = window.top.location.origin + '/lib';
           var pdfui = new UIExtension.PDFUI({
                   viewerOptions: {
                       libPath: libPath,
                       jr: {
                           licenseSN: licenseSN,
                           licenseKey: licenseKey
                       }
                   },
                   renderTo: document.body,
                   appearance: UIExtension.appearances.adaptive,
                   addons: [] // No addon is loaded
           });
           pdfui.getRootComponent().then(function(root){
               var editTab = root.querySelector('edit-tab')
               var editTabBody = root.querySelector('fv--edit-tab-paddle');
               editTab.after('<gtab name="custom-tab" group="toolbar-tab" body="fv--custom-tab-body" text="Custom Tab" @aria:toolbar.tab></gtab>');
               editTabBody.after('<div name="fv--custom-tab-body" style="padding:2em 1em;">Custom Tab Body</div>')
           });
       </script>

       {
           "iframeOptions": {
               "style": "height: 300px;"
           }
       }

   与使用 Fragment 配置编辑组件相比（其只能在初始化时编辑），使用 Component API 允许您在 PDFUI 初始化后的任何时候动态编辑组件。

#### # 标签面板组件

标签面板组件是在选择一个标签页后显示的面板。目前， SDK 默认的布局模板使用的标签面板组件都是 `paddle` 组件。使用这个组件的目的是在浏览器视图宽度较小时，可以在左右两侧显示按钮，用于滚动面板内的组件。

paddle 组件可以被视为一个普通的 `ContainerComponent`。您可以使用 Fragment 配置通过 `FRAGMENT_ACTION.APPEND` 操作将子组件插入到其中，或者使用 Component API 插入子组件。下面将举例进行说明。

1. 使用 Fragment 配置插入组件

   该示例使用 Fragment 配置向 paddle 组件默认插入了一个手型按钮。

       <html>
       </html>
       <script>
           var FRAGMENT_ACTION = UIExtension.UIConsts.FRAGMENT_ACTION
           var CustomAppearance = UIExtension.appearances.adaptive.extend({
               getDefaultFragments: function() {
                   return [{
                       target: 'fv--edit-tab-paddle',
                       action: FRAGMENT_ACTION.APPEND,
                       template: '<ribbon-button text="toolbar.tooltip.hand.title" name="hand-tool" icon-class="fv__icon-toolbar-hand" @controller="states:HandController"></ribbon-button>'
                   }];
               }
           });
           var libPath = window.top.location.origin + '/lib';
           var pdfui = new UIExtension.PDFUI({
                   viewerOptions: {
                       libPath: libPath,
                       jr: {
                           licenseSN: licenseSN,
                           licenseKey: licenseKey
                       }
                   },
                   renderTo: document.body,
                   appearance: CustomAppearance,
                   addons: [] // No addon is loaded
           });
       </script>

       {
           "iframeOptions": {
               "style": "height: 300px;"
           }
       }

2. 使用 Component API 插入组件

   该示例使用 Component API 插入一个手形按钮。效果与前一个示例相同。

       <html>
       </html>
       <script>
           var libPath = window.top.location.origin + '/lib';
           var pdfui = new UIExtension.PDFUI({
                   viewerOptions: {
                       libPath: libPath,
                       jr: {
                           licenseSN: licenseSN,
                           licenseKey: licenseKey
                       }
                   },
                   renderTo: document.body,
                   appearance: UIExtension.appearances.adaptive,
                   addons: [] // No addon is loaded
           });
           pdfui.getRootComponent().then(function(root) {
               var editTabBody = root.querySelector('fv--edit-tab-paddle')
               editTabBody.append('<ribbon-button text="toolbar.tooltip.hand.title" name="hand-tool" icon-class="fv__icon-toolbar-hand" @controller="states:HandController"></ribbon-button>')
           })
       </script>

       {
           "iframeOptions": {
               "style": "height: 300px;"
           }
       }

在默认的布局模板中，我们在 `paddle` 组件中使用了 `group-list` 组件对子组件进行分组。在实际应用中，更常见的是对 `group-list` 组件进行编辑。以下示例将使用 `group-list` 和 `group` 进行说明：

    <html>
    </html>
    <script>
        var libPath = window.top.location.origin + '/lib';
        var pdfui = new UIExtension.PDFUI({
                viewerOptions: {
                    libPath: libPath,
                    jr: {
                        licenseSN: licenseSN,
                        licenseKey: licenseKey
                    }
                },
                renderTo: document.body,
                appearance: UIExtension.appearances.adaptive,
                addons: [] // No addon is loaded
        });
        pdfui.getRootComponent().then(function(root) {
            var editTabBody = root.querySelector('fv--edit-tab-paddle>@group-list')
            editTabBody.append('<group name="custom-group" retain-count="1"><ribbon-button text="toolbar.tooltip.hand.title" name="hand-tool" icon-class="fv__icon-toolbar-hand" @controller="states:HandController"><ribbon-button text="toolbar.tooltip.hand.title" name="hand-tool" icon-class="fv__icon-toolbar-hand" @controller="states:HandController"></ribbon-button></group>')
        })
    </script>

    {
        "iframeOptions": {
            "style": "height: 300px;"
        }
    }

在此示例中，我们插入了一个新的 `custom-group`，其中 group 包含两个手形按钮组件，并设置在收缩后只显示一个组件。

#### # 标签面板中的按钮组件（ribbon-button）

`ribbon-button` 是一个功能强大的组件，可以作为普通按钮或下拉组件使用。首先探讨一下将 ribbon-button 作为普通按钮的自定义方法。

作为普通按钮，`ribbon-button` 组件可以通过以下方式进行自定义：

1. 文本内容: 按钮上显示的文本内容。
2. 图标: 按钮图标，通过 `icon-class` 属性指定，需要额外的 CSS 样式。
3. 工具提示: 当鼠标悬停在按钮上时，通过 `@tooltip` 指令和 `tooltip-title` 属性实现显示的浮动工具提示内容。
4. Controller: 通常用于业务实现，在自定义按钮上复用 SDK 内部实现逻辑。

以下是一个使用 SDK 内置 controller 实现业务逻辑复用的自定义按钮的示例：

    <html>
    </html>
    <script>
        var FRAGMENT_ACTION = UIExtension.UIConsts.FRAGMENT_ACTION
        var CustomAppearance = UIExtension.appearances.adaptive.extend({
            getDefaultFragments: function() {
                return [{
                    target: 'fv--edit-tab-paddle',
                    action: FRAGMENT_ACTION.APPEND,
                    template: '<ribbon-button text="Hand button" name="custom-hand-tool" icon-class="fv__icon-toolbar-hand" @controller="states:HandController"></ribbon-button>' // Reuse states:HandController
                }];
            }
        });
        var libPath = window.top.location.origin + '/lib';
        var pdfui = new UIExtension.PDFUI({
                viewerOptions: {
                    libPath: libPath,
                    jr: {
                        licenseSN: licenseSN,
                        licenseKey: licenseKey
                    }
                },
                renderTo: document.body,
                appearance: CustomAppearance,
                addons: [] // No addon is loaded
        });
    </script>

    {
        "iframeOptions": {
            "style": "height: 300px;"
        }
    }

从这个示例可以看出，在复用 controller 时，可以使用 `@controller=""` 指令来指定需要复用的 `Controller`。除了 `states:HandController` 以外，还有许多其它的 controller 可以被复用。更多详情，请参阅 Controllers 小节。

除了可以使用自定义按钮复用逻辑外，还可以直接修改 SDK 的内置组件。主要的方法是通过配置 Fragment 实现。例如，以下示例充分展示了如何自定义 `hand-tool` (ribbon-button) 的内容：

    <html>
    </html>
    <style>
        .custom-hand-icon {
            background-image: linear-gradient(180deg, rgba(0,245,12,1) 0%, rgba(0,212,255,1) 100%);
        }
    </style>
    <script>
        var FRAGMENT_ACTION = UIExtension.UIConsts.FRAGMENT_ACTION
        var CustomAppearance = UIExtension.appearances.adaptive.extend({
            getDefaultFragments: function() {
                return [{
                    target: 'hand-tool',
                    action: FRAGMENT_ACTION.EXT,
                    config: {
                        'icon-class': 'custom-hand-icon',
                        text: 'Custom Button Text',
                        attrs: {
                            '@tooltip': '',
                            'tooltip-title': 'Custom Tooltip Title'
                        }
                    }
                }];
            }
        });
        var libPath = window.top.location.origin + '/lib';
        var pdfui = new UIExtension.PDFUI({
                viewerOptions: {
                    libPath: libPath,
                    jr: {
                        licenseSN: licenseSN,
                        licenseKey: licenseKey
                    }
                },
                renderTo: document.body,
                appearance: CustomAppearance,
                addons: [] // No addon is loaded
        });
    </script>

    {
        "iframeOptions": {
            "style": "height: 300px;"
        }
    }

#### # 工具栏标签面板中的下拉 (dropdown) 组件

在工具栏上，`dropdown` 组件通常与 `ribbon-button` 组件一起使用。但是，也可以单独使用它。这两种使用方法之间的唯一区别是显示效果。以下是一个 `ribbon-button` 和 `dropdown` 配合使用的示例：

    <html>
    </html>
    <script>
        var FRAGMENT_ACTION = UIExtension.UIConsts.FRAGMENT_ACTION
        var CustomAppearance = UIExtension.appearances.adaptive.extend({
            getDefaultFragments: function() {
                return [{
                    target: 'hand-tool',
                    action: FRAGMENT_ACTION.AFTER,
                    template: `
                    <ribbon-button name="dropdown-zoom" @var.self="$component" @tooltip >
                        <dropdown name="fv--inner-zoom-ribbon-dropdown" @aria:label.list="aria:labels.setzoom" icon-class="fv__icon-toolbar-zoom-in" class="fv__ui-dropdown-hide-text" selected="0" @on.selected="self.select($args[0])">
                            <dropdown-button name="dropdown-zoom-in" action="zoomin" @controller="zoom:DropdownZoomInAndOutController" icon-class="fv__icon-toolbar-zoom-in" ribbon-icon="fx-icon-ribbon_home_zoomin-32" tooltip-title="toolbar.buttons.zoomin" tabindex="0">toolbar.buttons.zoomin</dropdown-button>
                            <dropdown-button name="dropdown-zoom-out" action="zoomout" @controller="zoom:DropdownZoomInAndOutController" icon-class="fv__icon-toolbar-zoom-out" ribbon-icon="fx-icon-ribbon_home_zoomout-32" tooltip-title="toolbar.buttons.zoomout" tabindex="0">toolbar.buttons.zoomout</dropdown-button>
                        </dropdown>
                    </ribbon-button>
                    `
                }];
            }
        });
        var libPath = window.top.location.origin + '/lib';
        var pdfui = new UIExtension.PDFUI({
                viewerOptions: {
                    libPath: libPath,
                    jr: {
                        licenseSN: licenseSN,
                        licenseKey: licenseKey
                    }
                },
                renderTo: document.body,
                appearance: CustomAppearance,
                addons: [] // No addon is loaded
        });
    </script>

    {
        "iframeOptions": {
            "style": "height: 300px;"
        }
    }

有关更多业务组件，您可以参阅 预配置组件 小节。本节描述了多个业务组件的默认配置，我们可以参考这些配置项使用 Fragment 配置来进行调整。

### # 左侧面板

在默认布局中，左侧面板是使用 `sidebar` 组件构建的，其子组件对应不同业务模块封装的 `sidebar-panel` 组件。

在本节中，我们将学习左侧面板的一些自定义方式和细节。

#### # 创建一个 `sidebar-panel`

以下是一个包含基本属性的 `sidebar-panel` 组件模板：

    <sidebar-panel
        class="custom-sidebar-panel"
        icon-class="custom-sidebar-icon"
        title="Custom"
    >
    </sidebar-panel>

如果需要，您可以向其添加一些指令，比如 `@tooltip` 指令：

    <sidebar-panel
        class="custom-sidebar-panel"
        icon-class="custom-sidebar-icon"
        title="Custom"
        @tooltip
        tooltip-placement="right"
        tooltip-title="Custom Sidebar"
    >
    </sidebar-panel>

如果您需要监听 `active` 事件（当 sidebar-panel 被展开时），您可以使用 `@on.active` 指令进行监听。请参考以下示例：

    class CustomSidebarPanel extends SeniorComponentFactory.createSuperClass({
        template: `
        <sidebar-panel
            class="custom-sidebar-panel"
            icon-class="custom-sidebar-icon"
            title="Custom"
            @tooltip
            tooltip-placement="right"
            tooltip-title="Custom Sidebar"
            @on.active="$component.handleActiveEvent()"
        >
        </sidebar-panel>
        `
    }) {
        static getName() {
            return 'custom-sidebar-panel'
        }
        handleActiveEvent(){
            console.log('hello world')
        }
    }

以下是一个可运行的示例：

    <html>
    </html>
    <style>
        .custom-sidebar-icon {
            background-image: linear-gradient(180deg, rgba(0,245,12,1) 0%, rgba(0,212,255,1) 100%);
            width: 24px;
            height: 24px;
            margin-top: 12px;
            margin-left: 4px;
        }
    </style>
    <script>
        const { modular, SeniorComponentFactory } = UIExtension;

        class CustomSidebarPanel extends SeniorComponentFactory.createSuperClass({
            template: `
            <sidebar-panel
                class="custom-sidebar-panel"
                icon-class="custom-sidebar-icon"
                title="Custom"
                @tooltip
                tooltip-placement="right"
                tooltip-title="Custom Sidebar"
                @on.active="$component.handleActiveEvent()"
            >
                <div>Custom Sidebar Panel Body</div>
            </sidebar-panel>
            `
        }) {
            static getName() {
                return 'custom-sidebar-panel'
            }
            handleActiveEvent(){
                console.log('hello world')
            }
        }
        modular.module('custom', []).registerComponent(CustomSidebarPanel);

        var FRAGMENT_ACTION = UIExtension.UIConsts.FRAGMENT_ACTION
        var CustomAppearance = UIExtension.appearances.adaptive.extend({
            getDefaultFragments: function() {
                return [{
                    target: '@sidebar',
                    action: FRAGMENT_ACTION.APPEND,
                    template: `
                        <custom:custom-sidebar-panel></custom:custom-sidebar-panel>
                    `
                }];
            }
        });
        var libPath = window.top.location.origin + '/lib';
        var pdfui = new UIExtension.PDFUI({
                viewerOptions: {
                    libPath: libPath,
                    jr: {
                        licenseSN: licenseSN,
                        licenseKey: licenseKey
                    }
                },
                renderTo: document.body,
                appearance: CustomAppearance,
                addons: [] // No addon is loaded
        });
    </script>

    {
        "iframeOptions": {
            "style": "height: 600px;"
        }
    }

#### # 删除一个 `sidebar-panel`

如果您想要删除 SDK 中内置的 `sidebar-panel` 组件，我们建议使用 Fragment 配置而不是 Component API 进行删除，除非确实有必要。这是因为初始化 `sidebar-panel` 组件可能需要比较多的资源，通过 Fragment 配置进行删除可以避免不必要的初始化。

    {
        target: '@layer-sidebar-panel'，
        action: UIExtension.UIConsts.FRAGMENT_ACTION.REMOVE
    }

#### # 自定义 SDK 内置的 `sidebar-panel` 组件

##### # 1. commentlist-sidebar-panel

在 commentlist 侧边栏面板中，可以自定义 CommentCardComponent 和 ReplyCardComponent。但与其他组件不同的是，它们是根据文档中当前的注释类型和数量动态生成的，无法通过 Fragment 配置修改其细节。不过，我们提供了事件，允许您在组件创建或删除时获取组件对象，然后使用 Component API 修改这些组件的细节。

以下是相关的事件说明：

1. `UIExtension.UIEvents.appendCommentListComment`: CommentCardComponent 组件被插入时触发，事件回调可以获取到 `commentCardComponent` 和 `annot` 两个参数。

2. `UIExtension.UIEvents.destroyCommentListComment`: CommentCardComponent 组件被销毁时触发，回调参数和 `appendCommentListComment` 事件相同

3. `UIExtension.UIEvents.appendCommentListReply`: ReplyCardComponent 组件被插入时触发，事件回调可以获取到 `replyCardComponent` 和 `replyAnnot` 两个参数。

4. `UIExtension.UIEvents.destroyCommentListReply`: ReplyCardComponent 组件被销毁时触发，回调参数和 `appendCommentListReply` 事件相同。

您可以参考 API Reference 以获取使用信息：

> 1. CommentCardComponent
>    (opens new window)
> 2. ReplyCardComponent
>    (opens new window)

##### # 2. thumbnail-sidebar-panel

关于 thumbnail-sidebar-panel，请参阅: 自定义缩略图。

##### # 3. bookmark-sidebar-panel

关于 bookmark-sidebar-panel，请参阅：自定义书签。

##### # 其它

除了上面提到的三个之外，`layer-sidebar-panel`、 `attachment-sidebar-panel` 和 `field-sidebar-panel` 目前暂未提供自定义功能。

### # 右键菜单

右键菜单是指基于 `ContextMenuComponent`
(opens new window) 实现的组件。所有可自定义的右键菜单都是基于相同原理实现的：

1. 每个具有特定功能的右键菜单都有一个固定的名称，替换右键菜单需要通过指定名称实现。替换后，名称不可更改，因为这可能导致右键菜单无法显示或覆盖其他右键菜单，影响其他功能的菜单显示。

2. 通过 Fragment 配置和 Component API，可以添加、删除和替换右键菜单项。

3. 开头，末尾或者连续的 `contextmenu-separator` 分割线会自动隐藏，一般不需要特别对其做显示隐藏控制。

接下来，将逐一介绍每个功能模块的右键菜单。

#### # 1. PDF 页面右键菜单

请参阅 自定义页面右键菜单 小节。

#### # 2. Annotation 右键菜单

请参阅 自定义 Annotation 右键菜单 小节。

需要注意的是，由于 Annotation 属性对话框不支持自定义，因此在实现自定义属性对话框时，需要替换 `<contextmenu-item-properties>` 的实现。您可以参考以下示例：

    <html>
    </html>
    <style>
        .custom-sidebar-icon {
            background-image: linear-gradient(180deg, rgba(0,245,12,1) 0%, rgba(0,212,255,1) 100%);
            width: 24px;
            height: 24px;
            margin-top: 12px;
            margin-left: 4px;
        }
    </style>
    <script>
        const { modular, SeniorComponentFactory } = UIExtension;

        class CustomAnnotationPropertiesDialog extends SeniorComponentFactory.createSuperClass({
            template: `
                <layer
                    class="center"
                    @var.self="$component"
                >
                    <layer-header @draggable="{'type': 'parent'}" title="Annotation Properties"></layer-header>
                    <layer-view style="padding: 1em">
                        <form-group label="Opacity">
                            <slider @model="self.properties.opacity" min="0" max="100" step="1" @on.change="self.updateOpacity($args[2])"></slider>
                        </form-group>
                    </layer-view>
                </layer>
            `
        }) {
            static getName() {
                return 'annotation-properties-dialog'
            }
            init() {
                super.init();
                this.properties = {
                    opacity: 100
                };
            }
            updateOpacity(newOpacity) {
                this.getPDFUI().getAllActivatedElements().then(([element]) => {
                    element.annot.setOpacity(newOpacity / 100);
                })
            }
            doShown() {
                super.doShown();
                this.getPDFUI().getAllActivatedElements().then(([element]) => {
                    this.properties.opacity = element.annot.getOpacity() * 100;
                    this.digest();
                });
            }
        }
        class CustomAnnotationPropertiesContextmenuItem extends SeniorComponentFactory.createSuperClass({
            template: `<contextmenu-item @on.click="$component.showPropertiesDialog()">Show Properties</contextmenu-item>`
        }) {
            static getName() {
                return 'contextmenu-item-show-properties'
            }
            showPropertiesDialog() {
                const dialog = this.getRoot().querySelector('@custom:annotation-properties-dialog');
                if(dialog) {
                    dialog.show();
                }
            }
        }
        modular.module('custom', [])
            .registerComponent(CustomAnnotationPropertiesDialog)
            .registerComponent(CustomAnnotationPropertiesContextmenuItem)
            ;

        var FRAGMENT_ACTION = UIExtension.UIConsts.FRAGMENT_ACTION
        var CustomAppearance = UIExtension.appearances.adaptive.extend({
            getDefaultFragments: function() {
                return [{
                    target: 'template-container',
                    action: FRAGMENT_ACTION.APPEND,
                    template: `
                        <custom:annotation-properties-dialog></custom:annotation-properties-dialog>
                    `
                }, {
                    target: '@contextmenu-item-properties',
                    action: FRAGMENT_ACTION.REPLACE,
                    template: `
                        <custom:contextmenu-item-show-properties></custom:contextmenu-item-show-properties>
                    `
                }];
            }
        });
        var libPath = window.top.location.origin + '/lib';
        var pdfui = new UIExtension.PDFUI({
                viewerOptions: {
                    libPath: libPath,
                    jr: {
                        licenseSN: licenseSN,
                        licenseKey: licenseKey
                    }
                },
                renderTo: document.body,
                appearance: CustomAppearance,
                addons: [] // No addon is loaded
        });
    </script>

    {
        "iframeOptions": {
            "style": "height: 600px;"
        }
    }

#### # 3. 其它右键菜单

自定义其它右键菜单与上述的页面右键菜单和 Annotation 右键菜单类似。您可以参考这些文档进行自我扩展。相关的菜单组件模板可以参考 预配置组件 小节。

### # 模态框

如果是 SDK 内部的弹出窗口，包括 alert、prompt、confirm 以及 loading 加载遮罩层，我们可以通过 Viewer UI 定义这些由 SDK 内部触发显示的组件。具体用法，请参阅 Viewer UI 小节。

## # Controllers 复用

下面是一个使用 `@controller` 指令语法的列表，列举了当前 SDK 可复用的 controller 实现。其中冒号之前是模块名，冒号之后是 controller 名：

### # UIExtension 内置 controller

内置 controller 可以直接通过 `UIExtension.modular.module('module name').getController('controller name')` 来获取类。

1. `states:HandController`: 切换当前 StateHandler 为 `STATE_HANDLER_NAMES.STATE_HANDLER_HAND`。
2. `marquee:MarqueeToolController`: 切换当前 StateHandler 为 `STATE_HANDLER_NAMES.STATE_HANDLER_MARQUEE`。
3. `loupe:LoupeController`: 切换当前 StateHandler 为 `STATE_HANDLER_NAMES.STATE_HANDLER_LOUPE`。
4. `states:SnapshotToolController`: 切换当前 StateHandler 为 `STATE_HANDLER_NAMES.STATE_HANDLER_SNAPSHOT_TOOL`。
5. `file:DownloadFileController`: 用于下载当前打开的文档。
6. `zoom:ZoomInAndOutController`: 控制文档视图缩放，需要与 `action` 属性搭配使用。可选值有：`action="zoomin"` 和 `action="zoomout"`。
7. `pagemode:SinglePageModeController`: 切换页面模式为单页模式。
8. `pagemode:ContinuousPageModeController`: 切换页面模式为连续页模式。
9. `pagemode:FacingPageModeController`: 切换页面模式为对开模式。
10. `pagemode:ContinuousFacingPageModeController`: 切换页面模式为连续对开模式。
11. `states:CreateTextController`: 切换当前 StateHandler 为 `STATE_HANDLER_NAMES.STATE_HANDLER.STATE_HANDLER_CREATE_TEXT`。
12. `states:CreateFileAttachmentController`: 切换当前 StateHandler 为 `STATE_HANDLER_NAMES.STATE_HANDLER.STATE_HANDLER_CREATE_FILE_ATTACHMENT`。
13. `states:CreateHighlightController`: 切换当前 StateHandler 为 `STATE_HANDLER_NAMES.STATE_HANDLER.STATE_HANDLER_CREATE_HIGHLIGHT`。
14. `states:CreateStrikeoutController`: 切换当前 StateHandler 为 `STATE_HANDLER_NAMES.STATE_HANDLER.STATE_HANDLER_CREATE_STRIKE_OUT`。
15. `states:CreateUnderlineController`: 切换当前 StateHandler 为 `STATE_HANDLER_NAMES.STATE_HANDLER.STATE_HANDLER_CREATE_UNDERLINE`。
16. `states:CreateSquigglyController`: 切换当前 StateHandler 为 `STATE_HANDLER_NAMES.STATE_HANDLER.STATE_HANDLER_CREATE_SQUIGGLY`。
17. `states:CreateReplaceController`: 切换当前 StateHandler 为 `STATE_HANDLER_NAMES.STATE_HANDLER.STATE_HANDLER_CREATE_REPLACE`。
18. `states:CreateCaretController`: 切换当前 StateHandler 为 `STATE_HANDLER_NAMES.STATE_HANDLER.STATE_HANDLER_CREATE_CARET`。
19. `states:CreateTypewriterController`: 切换当前 StateHandler 为 `STATE_HANDLER_NAMES.STATE_HANDLER.STATE_HANDLER_CREATE_TYPEWRITER`。
20. `states:CreateCalloutController`: 切换当前 StateHandler 为 `STATE_HANDLER_NAMES.STATE_HANDLER.STATE_HANDLER_CREATE_FREETEXT_CALLOUT`。
21. `states:CreateTextboxController`: 切换当前 StateHandler 为 `STATE_HANDLER_NAMES.STATE_HANDLER.STATE_HANDLER_CREATE_FREETEXT_BOX`。
22. `states:CreateAreaHighlightController`: 切换当前 StateHandler 为 `STATE_HANDLER_NAMES.STATE_HANDLER.STATE_HANDLER_CREATE_AREA_HIGHLIGHT。`
23. `states:CreatePencilController`: 切换当前 StateHandler 为 `STATE_HANDLER_NAMES.STATE_HANDLER.PENCIL`。
24. `states:EraserController`: 切换当前 StateHandler 为 `STATE_HANDLER_NAMES.STATE_HANDLER.STATE_HANDLER_ERASER`。
25. `states:CreateLinkController`: 切换当前 StateHandler 为 `STATE_HANDLER_NAMES.STATE_HANDLER.STATE_HANDLER_CREATE_LINK`。
26. `states:CreateImageController`: 切换当前 StateHandler 为 `STATE_HANDLER_NAMES.STATE_HANDLER.STATE_HANDLER_CREATE_IMAGE`。
27. `zoom:DropdownZoomInAndOutController`: 和 `zoom:ZoomInAndOutController` 功能相同, 但是只能在 dropdown 子组件中使用。
28. `zoom:ZoomActionController`: 控制文档视图缩放，与 `zoom:ZoomInAndOutController` 不同之处在于它需要指定目标缩放比例(非数字)： `action='fitHeight'`, `action='fitWidth'`, `action='fitVisible'`。
29. `gotoview:GotoFirstPageController`: 跳转到第一页。
30. `gotoview:GotoPrevPageController`: 跳转到上一页。
31. `gotoview:GotoNextPageController`: 跳转到下一页。
32. `gotoview:GotoLastPageController`: 跳转到最后一页。
33. `states:CreateSquareController`: 切换当前 StateHandler 为 `STATE_HANDLER_NAMES.STATE_HANDLER.STATE_HANDLER_CREATE_SQUARE`。
34. `states:CreateCircleController`: 切换当前 StateHandler 为 `STATE_HANDLER_NAMES.STATE_HANDLER.STATE_HANDLER_CREATE_CIRCLE`。
35. `states:CreatePolygonController`: 切换当前 StateHandler 为 `STATE_HANDLER_NAMES.STATE_HANDLER.STATE_HANDLER_CREATE_POLYGON`。
36. `states:CreatePolygonCloudController`: 切换当前 StateHandler 为 `STATE_HANDLER_NAMES.STATE_HANDLER.STATE_HANDLER_CREATE_POLYGON_CLOUD`。
37. `states:CreateArrowController`: 切换当前 StateHandler 为 `STATE_HANDLER_NAMES.STATE_HANDLER.STATE_HANDLER_CREATE_ARROW`。
38. `states:CreateLineController`: 切换当前 StateHandler 为 `STATE_HANDLER_NAMES.STATE_HANDLER.STATE_HANDLER_CREATE_LINE`。
39. `states:CreatePolylineController`: 切换当前 StateHandler 为 `STATE_HANDLER_NAMES.STATE_HANDLER.STATE_HANDLER_CREATE_POLYLINE`。
40. `distance:CreateDistanceController`: 切换当前 StateHandler 为 `STATE_HANDLER_NAMES.STATE_HANDLER.STATE_HANDLER_CREATE_DISTANCE`。
41. `distance:CreatePerimeterController`: 切换当前 StateHandler 为 `STATE_HANDLER_NAMES.STATE_HANDLER.STATE_HANDLER_CREATE_PERIMETER`。
42. `distance:CreateAreaController`: 切换当前 StateHandler 为 `STATE_HANDLER_NAMES.STATE_HANDLER.STATE_HANDLER_CREATE_AREA`。
43. `distance:CreateCircleAreaController`: 切换当前 StateHandler 为 `STATE_HANDLER_NAMES.STATE_HANDLER.STATE_HANDLER_CREATE_CIRCLE_AREA`。
44. `comment-list:ExportCommentController`: 将当前文档的注释导出为指定格式，格式由 format 属性指定：`format="XFDF"`， 支持 XFDF, FDF, JSON 三种格式。
45. `text-sel:CopySelectedTextController`: 复制选中文本。
46. `text-sel:CreateTextHighlightOnSelectedTextController`: 在选中的文本范围内创建文本高亮注释。
47. `text-sel:CreateStrikeoutOnSelectedTextController`: 在选中的文本范围内创建 Strikeout 注释。
48. `text-sel:CreateUnderlineOnSelectedTextController`: 在选中的文本范围内创建 Underline 注释。
49. `text-sel:CreateBookmarkOnSelectedTextController`: 将选中的文本添加到书签列表中。
50. `states:RibbonSelectTextImageController`: 切换当前 StateHandler 为 `STATE_HANDLER_NAMES.STATE_HANDLER_SELECT_TEXT_IMAGE`。
51. `states:RibbonSelectTextAnnotationController`: 切换当前 StateHandler 为 `STATE_HANDLER_NAMES.STATE_HANDLER_SELECT_ANNOTATION`。
52. `change-color:ChangeColorController`: 用于切换文档背景色。
53. `comment-list:ImportCommentButtonController`: 用于向文档中导入注释数据。
54. `states:SelectTextImageController`: 切换当前 StateHandler 为 `STATE_HANDLER_NAMES.STATE_HANDLER_SELECT_TEXT_IMAGE`。
55. `states:SelectAnnotationController`: 切换当前 StateHandler 为 `STATE_HANDLER_NAMES.STATE_HANDLER_SELECT_ANNOTATION`。
56. `zoom:ContextMenuZoomActionController`: 控制文档视图缩放，并需要指定 `action` 属性。可选值有：`fitWidth`, `fitHeight`, `fitVisible` 以及数字。
57. `ui-rotation:RotateRightController`: 控制文档视图向右旋转90度。
58. `ui-rotation:RotateLeftController`: 控制文档视图向左旋转90度。
59. `annot-opr:ShowAnnotReplyController`: 用于打开左侧栏 commentlist 面板，定位所选注释，并聚焦到回复输入框。
60. `annot-opr:DeleteAnnotController`: 删除选中的注释。
61. `annot-opr:ShowAnnotPropertiesController`: 显示注释属性对话框。
62. `annot-opr:SetPropsDefault`: 将当前注释属性设置为默认属性，用户下次创建注释时将使用该默认值。

### # UIExtension Addon 中的 controller

获取 Addons 中的 controller class 和获取 UIExtension 中内置 controller class 的方法相同，但是请确保这些 Addons 在获取之前已经加载完成。

1. page-template addon:
   1. `page-template:ShowPageTemplateDialogController`: 切换当前 StateHandler 为 `STATE_HANDLER_NAMES.STATE_HANDLER`。
2. form-designer addon:
   1. `form-designer:CreatePushButtonController`: 切换当前 StateHandler 为 `STATE_HANDLER_NAMES.STATE_HANDLER_CREATE_FIELD_PUSH_BUTTON`。
   2. `form-designer:CreateCheckBoxController`: 切换当前 StateHandler 为 `STATE_HANDLER_NAMES.STATE_HANDLER_CREATE_FIELD_CHECK_BOX`。
   3. `form-designer:CreateRadioButtonController`: 切换当前 StateHandler 为 `STATE_HANDLER_NAMES.STATE_HANDLER_CREATE_RADIO_BUTTON`。
   4. `form-designer:CreateComboBoxController`: 切换当前 StateHandler 为 `STATE_HANDLER_NAMES.STATE_HANDLER_CREATE_FIELD_COMBO_BOX`。
   5. `form-designer:CreateListBoxController`: 切换当前 StateHandler 为 `STATE_HANDLER_NAMES.STATE_HANDLER_CREATE_FIELD_LIST_BOX`。
   6. `form-designer:CreateTextController`: 切换当前 StateHandler 为 `STATE_HANDLER_NAMES.STATE_HANDLER_CREATE_FIELD_TEXT`。
   7. `form-designer:CreateSignController`: 切换当前 StateHandler 为 `STATE_HANDLER_NAMES.STATE_HANDLER_CREATE_FIELD_SIGNATURE`。
   8. `form-designer:CreateImageController`: 切换当前 StateHandler 为 `STATE_HANDLER_NAMES.STATE_HANDLER_CREATE_FIELD_IMAGE`。
   9. `form-designer:CreateDateController`: 切换当前 StateHandler 为 `STATE_HANDLER_NAMES.STATE_HANDLER_CREATE_FIELD_DATE`。
3. page-editor addon:
   1. `page-editor:EditObjectController`: 切换当前 StateHandler 为 "页面对象编辑" 工具，页面对象包含文本、图像、形状和渐变。
   2. `page-editor:AddImageAdvController`: 切换当前 StateHandler 为 "从文件添加图像" 工具。
   3. `page-editor:AddShapesController`: 切换当前 StateHandler 为 "创建图形" 工具。
   4. `page-editor:AddTextController`: 切换当前 StateHandler 为 "添加文本" 工具。
4. fullscreen addon:
   1. `full-screen:FullscreenController`: 切换全屏模式。
5. print addon:
   1. `print:ShowPrintDialogController`: 显示打印对话框。
6. file-property addon:
   1. `fpmodule:FileInfoCallbackController`: 显示文档属性对话框。
7. h-continuous addon:
   1. `h-continuous:HContinuousViewModeController`: 切换页面模式为横向连续页模式。
8. export-form addon:
   1. `export-form-module:ExportToXMLController`: 将当前文档的表单导出为 XML 格式文件。
   2. `export-form-module:ExportToFDFController`: 将当前文档的表单导出为 FDF 格式文件。
   3. `export-form-module:ExportToXFDFController`: 将当前文档的表单导出为 XFDF 格式文件。
   4. `export-form-module:ExportToCSVController`: 将当前文档的表单导出为 CSV 格式文件。
   5. `export-form-module:ExportToTXTController`: 将当前文档的表单导出为 TXT 格式文件。
9. comparison addon:
   1. `comparison:ShowCompareFileDialogButtonController`: 显示文档对比对话框。
10. redaction addon:
    1. `redaction:RedactionTextAndImageController`: 切换当前 StateHandler 为创建标记文本、图像的 Redaction 工具。
    2. `redaction:RedactionController`: 切换当前 StateHandler 为创建标记区域的 Redaction 工具。
    3. `redaction:RedactionPageController`: 切换当前 StateHandler 为创建标记页面的 Redaction 工具。

通过这些 controller，我们可以在重写组件的同时复用 SDK 内置的实现逻辑。以 `states:HandController` 为例，其允许将当前的 StateHandler 工具切换为手型工具，并且可以判断当前是否为手型工具来激活/取消激活组件：

    <html>
    </html>
    <style>
        .custom-hand-icon {
            background-image: linear-gradient(180deg, rgba(0,245,12,1) 0%, rgba(0,212,255,1) 100%);
        }
    </style>
    <script>
        var FRAGMENT_ACTION = UIExtension.UIConsts.FRAGMENT_ACTION
        var CustomAppearance = UIExtension.appearances.adaptive.extend({
            getDefaultFragments: function() {
                return [{
                    target: 'hand-tool',
                    action: FRAGMENT_ACTION.REPLACE,
                    template: `<xbutton @tooltip tooltip-title="toolbar.tooltip.hand.title" name="hand-tool" icon-class="fv__icon-toolbar-hand" @controller="states:HandController"></xbutton>`
                }];
            }
        });
        var libPath = window.top.location.origin + '/lib';
        var pdfui = new UIExtension.PDFUI({
                viewerOptions: {
                    libPath: libPath,
                    jr: {
                        licenseSN: licenseSN,
                        licenseKey: licenseKey
                    }
                },
                renderTo: document.body,
                appearance: CustomAppearance,
                addons: [] // No addon is loaded
        });
    </script>

    {
        "iframeOptions": {
            "style": "height: 600px;"
        }
    }

当然， 并非所有的 controller 都像 `states:HandController` 那样简单。以下列出了几个比较特殊的 controller，在使用它们的时候，要按照示例中提供的方法使用：

    <style>
        .fv__ui-paddle-horizontal .fv__ui-paddle-content {
            display: flex;
            align-items: center;
        }
        .custom-change-color-panel {
            display: inline-flex;
            align-items: center;
        }
    </style>
    <script>
        var FRAGMENT_ACTION = UIExtension.UIConsts.FRAGMENT_ACTION
        var CustomAppearance = UIExtension.appearances.adaptive.extend({
            getDefaultFragments: function() {
                return [{
                    target: 'fv--view-tab-paddle',
                    action: FRAGMENT_ACTION.APPEND,
                    template: '<group-list name="custom-component-group-list"></group-list>'
                },{
                    target: 'custom-component-group-list',
                    action: FRAGMENT_ACTION.APPEND,
                    template: `
                        <group>
                            <div class="custom-change-color-panel" name="change-color-panel" @controller="change-color:ChangeColorController as ctrl" @var.self="$component" >
                                <xbutton
                                    @foreach="color in colors track by background"
                                    @class="{'fv__ui-change-color-dropdown-color-round': true, 'moon': !!color.type, 'selected': selectedIndex === $index}"
                                    @sync.attr.style="color.type === 'moon' ? '' : ('background-color:' + color.background)"
                                    @on.click="ctrl.changeColor(color, $index)"
                                    @tooltip
                                    tooltip-title="@{color|json}"
                                ></xbutton>
                            </div>
                        </group>
                    `
                }, {
                    target: 'custom-component-group-list',
                    action: FRAGMENT_ACTION.APPEND,
                    template: `
                        <group>
                        <xbutton @controller="zoom:ZoomInAndOutController" action="zoomin" icon-class="fv__icon-toolbar-zoom-in"></xbutton>
                        </group>
                    `
                }, {
                    target: 'custom-component-group-list',
                    action: FRAGMENT_ACTION.APPEND,
                    template: `
                        <group>
                            <dropdown name="fv--inner-zoom-ribbon-dropdown" @aria:label.list="aria:labels.setzoom" icon-class="fv__icon-toolbar-zoom-in" class="fv__ui-dropdown-hide-text" selected="0" @on.selected="self.select($args[0])">
                                <xbutton name="dropdown-zoom-in" action="zoomin" @controller="zoom:DropdownZoomInAndOutController" icon-class="fv__icon-toolbar-zoom-in" ribbon-icon="fx-icon-ribbon_home_zoomin-32" tooltip-title="toolbar.buttons.zoomin">toolbar.buttons.zoomin</xbutton>
                                <xbutton name="dropdown-zoom-out" action="zoomout" @controller="zoom:DropdownZoomInAndOutController" icon-class="fv__icon-toolbar-zoom-out" ribbon-icon="fx-icon-ribbon_home_zoomout-32" tooltip-title="toolbar.buttons.zoomout">toolbar.buttons.zoomout</xbutton>
                                <xbutton name="dropdown-zoom-fitpage" action="fitHeight" @controller="zoom:ZoomActionController" icon-class="fv__icon-toolbar-fit-page" ribbon-icon="fx-icon-ribbon_home_fitpage-32" tooltip-title="toolbar.buttons.fitHeight">toolbar.buttons.fitHeight</xbutton>
                                <xbutton name="dropdown-zoom-fitwidth" action="fitWidth" @controller="zoom:ZoomActionController" icon-class="fv__icon-toolbar-fit-width" ribbon-icon="fx-icon-ribbon_home_fitwidth-32" tooltip-title="toolbar.buttons.fitWidth">toolbar.buttons.fitWidth</xbutton>
                                <xbutton name="dropdown-zoom-fitvisible" action="fitVisible" @controller="zoom:ZoomActionController" icon-class="fv__icon-toolbar-fit-visible" ribbon-icon="fx-icon-ribbon_home_visible-32" tooltip-title="toolbar.buttons.fitVisible">toolbar.buttons.fitVisible</xbutton>
                                <li class="fv__ui-dropdown-separator"></li>
                                <xbutton @foreach="scale in $pdfui.customScalingValues" @setter.text="scale|percent" @controller="zoom:ZoomToScaleValueController"></xbutton>
                            </dropdown>
                        </group>
                    `
                }, {
                    target: 'custom-component-group-list',
                    action: FRAGMENT_ACTION.APPEND,
                    // Note that comment-list:ImportCommentButtonController must be used together with the file-selector component.
                    template: `
                        <group>
                            <file-selector
                                @controller="comment-list:ImportCommentButtonController"
                                name="commentlist-import-comment"
                                icon-class="fv__icon-sidebar-import-comment"
                                @trigon.close.disable
                                @trigon.open.enable accept=".fdf,.xfdf,.json"
                            >sidebar.commentlist.dropdown.import-comment</file-selector>
                        </group>
                    `
                }];
            }
        });
        var libPath = window.top.location.origin + '/lib';
        var pdfui = new UIExtension.PDFUI({
                viewerOptions: {
                    libPath: libPath,
                    jr: {
                        licenseSN: licenseSN,
                        licenseKey: licenseKey
                    }
                },
                renderTo: document.body,
                appearance: CustomAppearance,
                addons: [] // No addon is loaded
        });
    </script>

    {
        "iframeOptions": {
            "style": "height: 600px;"
        }
    }