# # Extensible Components and Usage Guide

This document provides detailed information and usage guide for extensible components, listing the methods to extend extensible components and helping developers understand and use the custom features of components better. In the following document, we will first introduce the concept of extensible components and the methods to extend them using Component API and Fragment configuration. Then, we will provide a detailed list of extensible components, so that developers can have a clear understanding of which components can be modified.

## # Prerequisites

To better understand this document, you may need to be familiar with the following concepts:

1. For Fragment configuration: please refer to UI Fragments section.
2. For Appearance usage: please refer to Appearance section.
3. For layout templates: please refer to Layout Template section.
4. For component selector syntax: please refer to Component Selector section.
5. For Component API: please refer to the API Reference
   (opens new window).
6. For the `@controller` directive: please refer to the @controller directive section.
7. For the `@tooltip` directive: please refer to the @tooltip directive section.
8. For the `@on` directive: please refer to the @on directive section.

The upcoming content will utilize the knowledge mentioned above. Please familiarize yourself with them before reading this document, as they will not be repeated here.

## # Overview of Extensible Components

1. What are extensible components: Components that can be customized and extended in terms of functionality and appearance through Fragment configuration or Component API.
2. What are non-extensible components: The opposite of extensible components, which cannot be modified. These components are deeply encapsulated within the SDK, so they cannot be broken down and combined at a granular level. However, some of them can be replaced as a whole (e.g., Annotation Property Dialog). More specific details will be provided in subsequent sections.
3. Note: Some components do not have unique name identifiers, so they cannot be directly targeted by name. In such cases, component selector syntax can be used.

## # Extensible Components

### # Header Area

#### # Tab Component

The tab component is wrapped in a div container named `toolbar-tabs`, while the tab panel is wrapped in a div container named `toolbar-tab-bodies`. If you want to add or remove tabs, you need to modify both of these containers. Here is an example (click the `run` button to see the effect):

1. Add a tab at the end

   In this example, we inserted a new tab called `custom-tab` at the end of the tab bar, and also inserted a div component called `fv--custom-tab-body`.

       <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>' // The custom tab panel does not restrict the component type, but make sure that the name should be the same as the body property value specified in `gtab`.
                   }];
               }
           });
           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. Remove a specific tab

   In this example, we remove both the `edit-tab` and `fv--edit-tab-paddle` components, and then the `edit` tab will not be visible in the view.

       <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', // Multiple component names can be separated by commas, and these components will be removed together.
                       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. Replace a specific tab

   In this example, the `edit-tab` tab is replaced with a custom 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. Insert a tab at a specified position

   The following example inserts a custom tab after a specified position. Similarly, there is a `FRAGMENT_ACTION.BEFORE` operation, which means that the component is inserted before the specified target component.

       <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, // Insert the custom tab 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', // Note that the order is not important here because only one tab panel is displayed at a time, this operation is just for demonstration purposes.
                       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. Use Component API

   The following example uses selector syntax and the `Component.after` method to insert more tab components into the page.

       <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;"
           }
       }

   Compared to editing components with the Fragment configuration, which can only be done during initialization, using the Component API allows you to dynamically edit components at any time after PDFUI initialization.

#### # Tab Panel Components

The tab panel component is the panel displayed after selecting a tab. Currently, the default layout template used by the SDK uses the `paddle` component as the tab panel component. The purpose of using this component is to display buttons on the left and right sides when the browser view width is small, which can be used to scroll the components within the panel.

The paddle component can be seen as a normal `ContainerComponent`. You can use the Fragment configuration to insert child components into it using the `FRAGMENT_ACTION.APPEND` operation, or you can use the Component API to insert child components. The following examples will illustrate this.

1. Insert components using the Fragment Configuration

   This example uses the Fragment configuration to insert a hand button into the paddle component by default.

       <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. Insert components using the Component API

   This example uses the Component API to insert a hand button. The effect will be the same as the previous example.

       <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;"
           }
       }

In the default layout template, we use the `group-list` component to group the child components within the `paddle` component. In practice, it is more common to edit the `group-list` component. The following example will use `group-list` and `group` to illustrate:

    <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;"
        }
    }

In this example, we insert a new `custom-group` where the group contains two hand button components, and set it to only display one component when collapsed.

#### # Button Components in Tab Panels (ribbon-button)

The `ribbon-button` is a powerful component that can be used as a regular button or as a dropdown component. Let's first explore the custom methods for using ribbon-button as a regular button.

As a regular button, the `ribbon-button` component can be customized with the following:

1. Text Content: the text displayed on the button.

2. Icon: the button icon, specified through the `icon-class` attribute, which requires additional CSS styles.

3. Tooltips: the floating tooltip content displayed when the mouse hovers over the button, implemented through the `@tooltip` directive and `tooltip-title` attribute.

4. Controller: usually used for business implementation, and generally used to reuse internal SDK implementation logic on custom buttons.

The following is an example of a custom button that reuses business logic using the SDK's built-in 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;"
        }
    }

From this example, we can see that when reusing a controller, the `@controller=""` directive can be used to specify the `Controller` that needs to be reused. Apart from `states:HandController`, there are many other controllers that can be reused. For more details, please refer to the Controllers section.

In addition to reusing logic with custom buttons, you can also directly modify the built-in components of the SDK. The main way to do this is through configuring Fragments. For example, the following example fully demonstrates how to customize the content of the `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 Components in the Toolbar Tab Panel

On the toolbar, the `dropdown` component is usually used in conjunction with the `ribbon-button` component. However, you can also use it separately. The only difference between these two usage methods is the display effect. The following is an example of using `ribbon-button` and `dropdown` together:

    <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;"
        }
    }

For more business components, you can refer to the pre-configured components section. This section describes the default configurations of multiple business components, and we can adjust them using Fragment configuration based on these configuration options.

### # Left Panel

In the default layout, the left panel is constructed using the `sidebar` component, and its child components correspond to different business modules encapsulated in the `sidebar-panel` component.

In this section, we will learn about some customization methods and details of the left panel.

#### # Create a `sidebar-panel`

Here is a template for the `sidebar-panel` component that includes basic properties:

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

If needed, you can add directives to it, such as the `@tooltip` directive:

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

If you need to listen to the `active` event (when the sidebar-panel is expanded), you can use the `@on.active` directive to listen to it. Please refer to the following example:

    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')
        }
    }

Finally, let's take a look at a runnable example:

    <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;"
        }
    }

#### # Delete a `sidebar-panel`

If you want to delete the built-in `sidebar-panel` component in the SDK, we recommend using Fragment configuration instead of the Component API to delete it, unless it is necessary. This is because initializing a `sidebar-panel` component may require a significant amount of resources, and deleting it through Fragment configuration can avoid unnecessary initialization.

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

#### # Customize the internal components of the built-in `sidebar-panel` in the SDK

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

In the commentlist sidebar panel, the CommentCardComponent and ReplyCardComponent can be customized. However, unlike other components, they are dynamically generated based on the current type and number of comments in the document, and their details cannot be modified through Fragment configuration. But we provide events that allow you to obtain the component objects when they are created or deleted, and then modify the details of these components using the Component API.

Here are the descriptions of the relevant events:

1). `UIExtension.UIEvents.appendCommentListComment`: Triggered when the CommentCardComponent component is inserted. The event callback can obtain the `commentCardComponent` and `annot` parameters.

2). `UIExtension.UIEvents.destroyCommentListComment`: Triggered when the CommentCardComponent component is destroyed. The callback parameters are the same as those of the `appendCommentListComment` event.

3). `UIExtension.UIEvents.appendCommentListReply`: Triggered when the ReplyCardComponent component is inserted. The event callback can obtain the `replyCardComponent` and `replyAnnot` parameters.

4). `UIExtension.UIEvents.destroyCommentListReply`: Triggered when the ReplyCardComponent component is destroyed. The callback parameters are the same as those of the `appendCommentListReply` event.

You can refer to the API Reference for usage information:

> 1). CommentCardComponent
> (opens new window)

> 2). ReplyCardComponent
> (opens new window)

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

You can refer to the section: Customize Thumbnail

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

You can refer to the section: Custom Bookmark

##### # 4. Others

Apart from the three mentioned above, `layer-sidebar-panel`, `attachment-sidebar-panel`, and `field-sidebar-panel` currently do not provide customization capabilities.

### # Context Menu

The context menu refers to the component implemented based on `ContextMenuComponent`
(opens new window). All customizable context menus are implemented based on the same principle:

1. Each context menu for a specific functionality has a fixed name, and replacing a context menu requires specifying the name. After replacement, the name cannot be changed, as it may cause the context menu to not be displayed or overwrite other context menus, affecting the display of other functionalities.

2. Context menu items can be added, deleted, and replaced through Fragment configuration and the Component API.

3. The `contextmenu-separator` separators at the beginning, end, or in a continuous sequence will be automatically hidden and generally do not need to be specifically controlled for showing or hiding.

Next, the context menus for each functional module will be introduced one by one.

#### # 1. PDF Page Context Menu

Please refer to the section Customizing Page Context Menu.

#### # 2. Annotation Context Menu

Please refer to the section Customizing Annotation Context Menu.

It should be noted that since the Annotation property dialog does not support customization, when implementing a custom property dialog, the implementation of `<contextmenu-item-properties>` needs to be replaced. You can refer to the following example:

    <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. Other Context Menus

Customizing other context menus is similar to the Page context menu and Annotation context menu described above. You can refer to these documents for self-expansion. The templates for relevant menu components can be found in the section Pre-configured Components.

### # Modals

If it is a pop-up within the SDK, including alert, prompt, confirm, and loading overlay, we can define these components triggered by the SDK internally through Viewer UI. For specific usage, please refer to the section Viewer UI.

## # Controller Reusability

Below is a list utilizing the `@controller` directive syntax, enumerating the currently reusable controller implementations within the SDK. Module names are listed before the colon, and controller names are listed after the colon.

### # Built-in Controllers in UIExtension

Built-in controllers can be directly accessed using `UIExtension.modular.module('module name').getController('controller name')` to retrieve the class.

1. `states:HandController`: Switches the current StateHandler to `STATE_HANDLER_NAMES.STATE_HANDLER_HAND`.
2. `marquee:MarqueeToolController`: Switches the current StateHandler to `STATE_HANDLER_NAMES.STATE_HANDLER_MARQUEE`.
3. `loupe:LoupeController`: Switches the current StateHandler to `STATE_HANDLER_NAMES.STATE_HANDLER_LOUPE`.
4. `states:SnapshotToolController`: Switches the current StateHandler to `STATE_HANDLER_NAMES.STATE_HANDLER_SNAPSHOT_TOOL`.
5. `file:DownloadFileController`: Used to download the currently open document.
6. `zoom:ZoomInAndOutController`: Controls document view zooming, and needs to be used with the `action` attribute. Optional values are: `action="zoomin"` and `action="zoomout"`.
7. `pagemode:SinglePageModeController`: Switches page mode to single page mode.
8. `pagemode:ContinuousPageModeController`: Switches page mode to continuous page mode.
9. `pagemode:FacingPageModeController`: Switches page mode to facing page mode.
10. `pagemode:ContinuousFacingPageModeController`: Switches page mode to continuous facing page mode.
11. `states:CreateTextController`: Switches the current StateHandler to `STATE_HANDLER_NAMES.STATE_HANDLER.STATE_HANDLER_CREATE_TEXT`.
12. `states:CreateFileAttachmentController`: Switches the current StateHandler to `STATE_HANDLER_NAMES.STATE_HANDLER.STATE_HANDLER_CREATE_FILE_ATTACHMENT`.
13. `states:CreateHighlightController`: Switches the current StateHandler to `STATE_HANDLER_NAMES.STATE_HANDLER.STATE_HANDLER_CREATE_HIGHLIGHT`.
14. `states:CreateStrikeoutController`: Switches the current StateHandler to `STATE_HANDLER_NAMES.STATE_HANDLER.STATE_HANDLER_CREATE_STRIKE_OUT`.
15. `states:CreateUnderlineController`: Switches the current StateHandler to `STATE_HANDLER_NAMES.STATE_HANDLER.STATE_HANDLER_CREATE_UNDERLINE`.
16. `states:CreateSquigglyController`: Switches the current StateHandler to `STATE_HANDLER_NAMES.STATE_HANDLER.STATE_HANDLER_CREATE_SQUIGGLY`.
17. `states:CreateReplaceController`: Switches the current StateHandler to `STATE_HANDLER_NAMES.STATE_HANDLER.STATE_HANDLER_CREATE_REPLACE`.
18. `states:CreateCaretController`: Switches the current StateHandler to `STATE_HANDLER_NAMES.STATE_HANDLER.STATE_HANDLER_CREATE_CARET`.
19. `states:CreateTypewriterController`: Switches the current StateHandler to `STATE_HANDLER_NAMES.STATE_HANDLER.STATE_HANDLER_CREATE_TYPEWRITER`.
20. `states:CreateCalloutController`: Switches the current StateHandler to `STATE_HANDLER_NAMES.STATE_HANDLER.STATE_HANDLER_CREATE_FREETEXT_CALLOUT`.
21. `states:CreateTextboxController`: Switches the current StateHandler to `STATE_HANDLER_NAMES.STATE_HANDLER.STATE_HANDLER_CREATE_FREETEXT_BOX`.
22. `states:CreateAreaHighlightController`: Switches the current StateHandler to `STATE_HANDLER_NAMES.STATE_HANDLER.STATE_HANDLER_CREATE_AREA_HIGHLIGHT`.
23. `states:CreatePencilController`: Switches the current StateHandler to `STATE_HANDLER_NAMES.STATE_HANDLER.PENCIL`.
24. `states:EraserController`: Switches the current StateHandler to `STATE_HANDLER_NAMES.STATE_HANDLER.STATE_HANDLER_ERASER`.
25. `states:CreateLinkController`: Switches the current StateHandler to `STATE_HANDLER_NAMES.STATE_HANDLER.STATE_HANDLER_CREATE_LINK`.
26. `states:CreateImageController`: Switches the current StateHandler to `STATE_HANDLER_NAMES.STATE_HANDLER.STATE_HANDLER_CREATE_IMAGE`.
27. `zoom:DropdownZoomInAndOutController`: Same as `zoom:ZoomInAndOutController`, but can only be used in dropdown subcomponents.
28. `zoom:ZoomActionController`: Controls document view zooming, and is different from `zoom:ZoomInAndOutController` in that it needs to specify the target zoom level (non-numeric): `action='fitHeight'`, `action='fitWidth'`, `action='fitVisible'`.
29. `gotoview:GotoFirstPageController`: Jumps to the first page.
30. `gotoview:GotoPrevPageController`: Jumps to the previous page.
31. `gotoview:GotoNextPageController`: Jumps to the next page.
32. `gotoview:GotoLastPageController`: Jumps to the last page.
33. `states:CreateSquareController`: Switches the current StateHandler to `STATE_HANDLER_NAMES.STATE_HANDLER.STATE_HANDLER_CREATE_SQUARE`.
34. `states:CreateCircleController`: Switches the current StateHandler to `STATE_HANDLER_NAMES.STATE_HANDLER.STATE_HANDLER_CREATE_CIRCLE`.
35. `states:CreatePolygonController`: Switches the current StateHandler to `STATE_HANDLER_NAMES.STATE_HANDLER.STATE_HANDLER_CREATE_POLYGON`.
36. `states:CreatePolygonCloudController`: Switches the current StateHandler to `STATE_HANDLER_NAMES.STATE_HANDLER.STATE_HANDLER_CREATE_POLYGON_CLOUD`.
37. `states:CreateArrowController`: Switches the current StateHandler to `STATE_HANDLER_NAMES.STATE_HANDLER.STATE_HANDLER_CREATE_ARROW`.
38. `states:CreateLineController`: Switches the current StateHandler to `STATE_HANDLER_NAMES.STATE_HANDLER.STATE_HANDLER_CREATE_LINE`.
39. `states:CreatePolylineController`: Switches the current StateHandler to `STATE_HANDLER_NAMES.STATE_HANDLER.STATE_HANDLER_CREATE_POLYLINE`.
40. `distance:CreateDistanceController`: Switches the current StateHandler to `STATE_HANDLER_NAMES.STATE_HANDLER.STATE_HANDLER_CREATE_DISTANCE`.
41. `distance:CreatePerimeterController`: Switches the current StateHandler to `STATE_HANDLER_NAMES.STATE_HANDLER.STATE_HANDLER_CREATE_PERIMETER`.
42. `distance:CreateAreaController`: Switches the current StateHandler to `STATE_HANDLER_NAMES.STATE_HANDLER.STATE_HANDLER_CREATE_AREA`.
43. `distance:CreateCircleAreaController`: Switches the current StateHandler to `STATE_HANDLER_NAMES.STATE_HANDLER.STATE_HANDLER_CREATE_CIRCLE_AREA`.
44. `comment-list:ExportCommentController`: Exports the comments of the current document in a specified format, with the format specified by the format attribute: `format="XFDF"`, which supports XFDF, FDF, and JSON formats.
45. `text-sel:CopySelectedTextController`: Copies the selected text.
46. `text-sel:CreateTextHighlightOnSelectedTextController`: Creates a text highlight annotation within the selected text range.
47. `text-sel:CreateStrikeoutOnSelectedTextController`: Creates a Strikeout annotation within the selected text range.
48. `text-sel:CreateUnderlineOnSelectedTextController`: Creates a Underline annotation within the selected text range.
49. `text-sel:CreateBookmarkOnSelectedTextController`: Adds the selected text to the bookmark list.
50. `states:RibbonSelectTextImageController`: Switches the current StateHandler to `STATE_HANDLER_NAMES.STATE_HANDLER_SELECT_TEXT_IMAGE`.
51. `states:RibbonSelectTextAnnotationController`: Switches the current StateHandler to `STATE_HANDLER_NAMES.STATE_HANDLER_SELECT_ANNOTATION`.
52. `change-color:ChangeColorController`: Used to change the document background color.
53. `comment-list:ImportCommentButtonController`: Used to import comment data into the document.
54. `states:SelectTextImageController`: Switches the current StateHandler to `STATE_HANDLER_NAMES.STATE_HANDLER_SELECT_TEXT_IMAGE`.
55. `states:SelectAnnotationController`: Switches the current StateHandler to `STATE_HANDLER_NAMES.STATE_HANDLER_SELECT_ANNOTATION`.
56. `zoom:ContextMenuZoomActionController`: Controls document view zooming, and needs to specify the `action` attribute. Available values are: `fitWidth`, `fitHeight`, `fitVisible`, and numeric values.
57. `ui-rotation:RotateRightController`: Controls document view rotation to the right by 90 degrees.
58. `ui-rotation:RotateLeftController`: Controls document view rotation to the left by 90 degrees.
59. `annot-opr:ShowAnnotReplyController`: Used to open the commentlist on the left panel, locate the selected annotation, and focus on the reply input box.
60. `annot-opr:DeleteAnnotController`: Deletes the selected annotation.
61. `annot-opr:ShowAnnotPropertiesController`: Displays the annotation properties dialog.
62. `annot-opr:SetPropsDefault`: Sets the current annotation properties as default, which will be used for creating annotations next time.

### # Controllers in UIExtension Addons

To retrieve the controller classes in Addons, use the same method as retrieving the built-in controller classes in UIExtensions, but make sure that the Addons have been loaded before retrieval.

1. page-template addon:
   1. `page-template:ShowPageTemplateDialogController`: Switches the current StateHandler to `STATE_HANDLER_NAMES.STATE_HANDLER`.
2. form-designer addon:
   1. `form-designer:CreatePushButtonController`: Switches the current StateHandler to `STATE_HANDLER_NAMES.STATE_HANDLER_CREATE_FIELD_PUSH_BUTTON`.
   2. `form-designer:CreateCheckBoxController`: Switches the current StateHandler to `STATE_HANDLER_NAMES.STATE_HANDLER_CREATE_FIELD_CHECK_BOX`.
   3. `form-designer:CreateRadioButtonController`: Switches the current StateHandler to `STATE_HANDLER_NAMES.STATE_HANDLER_CREATE_RADIO_BUTTON`.
   4. `form-designer:CreateComboBoxController`: Switches the current StateHandler to `STATE_HANDLER_NAMES.STATE_HANDLER_CREATE_FIELD_COMBO_BOX`.
   5. `form-designer:CreateListBoxController`: Switches the current StateHandler to `STATE_HANDLER_NAMES.STATE_HANDLER_CREATE_FIELD_LIST_BOX`.
   6. `form-designer:CreateTextController`: Switches the current StateHandler to `STATE_HANDLER_NAMES.STATE_HANDLER_CREATE_FIELD_TEXT`.
   7. `form-designer:CreateSignController`: Switches the current StateHandler to `STATE_HANDLER_NAMES.STATE_HANDLER_CREATE_FIELD_SIGNATURE`.
   8. `form-designer:CreateImageController`: Switches the current StateHandler to `STATE_HANDLER_NAMES.STATE_HANDLER_CREATE_FIELD_IMAGE`.
   9. `form-designer:CreateDateController`: Switches the current StateHandler to `STATE_HANDLER_NAMES.STATE_HANDLER_CREATE_FIELD_DATE`.
3. page-editor addon:
   1. `page-editor:EditObjectController`: Switches the current StateHandler to "Page Object Editing" tool, including text, image, shape, and gradient objects.
   2. `page-editor:AddImageAdvController`: Switches the current StateHandler to "Add Image from File" tool.
   3. `page-editor:AddShapesController`: Switches the current StateHandler to "Create Shape" tool.
   4. `page-editor:AddTextController`: Switches the current StateHandler to "Add Text" tool.
4. fullscreen addon:
   1. `full-screen:FullscreenController`: Toggles the full screen mode.
5. print addon:
   1. `print:ShowPrintDialogController`: Displays the print dialog.
6. file-property addon:
   1. `fpmodule:FileInfoCallbackController`: Displays the document property dialog.
7. h-continuous addon:
   1. `h-continuous:HContinuousViewModeController`: Switches the page mode to horizontal continuous page mode.
8. export-form addon:
   1. `export-form-module:ExportToXMLController`: Exports the form of the current document to an XML format file.
   2. `export-form-module:ExportToFDFController`: Exports the form of the current document to an FDF format file.
   3. `export-form-module:ExportToXFDFController`: Exports the form of the current document to an XFDF format file.
   4. `export-form-module:ExportToCSVController`: Exports the form of the current document to a CSV format file.
   5. `export-form-module:ExportToTXTController`: Exports the form of the current document to a TXT format file.
9. comparison addon:
   1. `comparison:ShowCompareFileDialogButtonController`: Displays the document comparison dialog.
10. redaction addon:
    1. `redaction:RedactionTextAndImageController`: Switches the current StateHandler to the Redaction tool for creating marked text and images.
    2. `redaction:RedactionController`: Switches the current StateHandler to the Redaction tool for creating marked areas.
    3. `redaction:RedactionPageController`: Switches the current StateHandler to the Redaction tool for creating marked pages.

With these controllers, we can rewrite the components while reusing the built-in implementation logic of the SDK. Let's take an example of `states:HandController`, which can switch the current StateHandler tool to the Hand tool and can determine whether the current tool is the hand tool to activate/deactivate the component:

    <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;"
        }
    }

Of course, some controllers are not as simple as `states:HandController`. Here are some of the more special controllers. When using them, we need to follow the methods provided in the examples:

    <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;"
        }
    }