Skip to content

Extensible components guide

This section describes extensible components, how to extend them, and how to customize behavior and appearance. It introduces the concepts, extension via Components API and fragment configuration, and a detailed list of components you can modify.

Prerequisites

To get the most from this document, familiarize yourself with:

  1. Fragment configuration: see UI fragments.
  2. Appearance: see Appearance.
  3. Layout templates: see layout template.
  4. Component selectors: see component selectors.
  5. Components API: See API Reference.
  6. @controller directive: see @controller.
  7. @tooltip directive: see @tooltip.
  8. @on directive: see @on.

The rest of this section assumes the topics above; they are not repeated here.

Extensible components overview

  1. Extensible components can be customized and extended in behavior and appearance via fragment configuration or the Components API.

  2. Non-extensible components cannot be modified individually; they are deeply encapsulated in the SDK. Some may be replaced as a whole (for example, the annotation properties dialog). Later sections give details.

  3. Note: Some components lack unique names; use component selector syntax to target them.

Extensible components

Extensible areas include:

Header area

Tab components

Tab components are placed in a div container named toolbar-tabs, and tab panels are placed in a div container named toolbar-tab-bodies. To add or remove a tab, update the components in both containers. The following is an example (click Run to see the result):

  1. Add a tab at the end

    In this example, a new tab named custom-tab is appended to the tab bar, and a div component named fv--custom-tab-body is appended at the same time.

    Details
    html
    <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>' // Custom tab panels are not limited to a specific component type, but make sure the name matches the `body` attribute of the related `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>
    json
    {
        "iframeOptions": {
            "style": "height: 300px;"
        }
    }
  2. Remove a specific tab

    In this example, the edit-tab and fv--edit-tab-paddle components are removed, so the edit tab will no longer appear in the UI.

    Details
    html
    <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; they 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>
    json
    {
        "iframeOptions": {
            "style": "height: 300px;"
        }
    }
  3. Replace a specific tab

    In this example, the edit-tab tab is replaced with a custom tab.

    Details
    html
    <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>
    json
    {
        "iframeOptions": {
            "style": "height: 300px;"
        }
    }
  4. Insert a tab at a specific position

    The following example inserts a custom tab after a specified component. There is also a FRAGMENT_ACTION.BEFORE action, which inserts the component before the specified target.

    Details
    html
    <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: the order here does not matter because only one tab panel is shown at a time; this is for demonstration only.
                    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>
    json
    {
        "iframeOptions": {
            "style": "height: 300px;"
        }
    }
  5. Use the Components API

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

    Details
    html
    <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>
    json
    {
        "iframeOptions": {
            "style": "height: 300px;"
        }
    }

    Compared with editing components via Fragment configuration (which can only be done at initialization), the Components API lets you edit components dynamically at any time after PDFUI is initialized.

Tab panel components

A tab panel is the panel shown after a tab is selected. In the SDK's default layout template, tab panel components are all paddle components. The paddle component shows buttons on the left and right when the browser viewport is narrow, so users can scroll the components inside the panel.

A paddle component can be treated as a regular ContainerComponent. You can append child components to it with Fragment configuration via FRAGMENT_ACTION.APPEND, or insert children with the Components API. The examples below illustrate both approaches.

  1. Insert a component with Fragment configuration

    This example uses Fragment configuration to append a Hand tool button to the paddle component by default.

    Details
    html
    <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>
    json
    {
        "iframeOptions": {
            "style": "height: 300px;"
        }
    }
  2. Insert a component with the Components API

    This example uses the Components API to insert a Hand tool button. The result is the same as the previous example.

    Details
    html
    <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>
    json
    {
        "iframeOptions": {
            "style": "height: 300px;"
        }
    }

In the default layout template, the paddle component uses a group-list to group child components. In practice, you more often edit the group-list component itself. The following example uses group-list and group:

Details
html

<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>
json
{
  "iframeOptions": {
    "style": "height: 300px;"
  }
}

In this example, a new custom-group is inserted. The group contains two Hand tool buttons, and after collapsing it shows only one component (retain-count="1").

Buttons in tab panels (ribbon-button)

ribbon-button is a versatile component that can act as a regular button or a dropdown. This section first covers customizing a ribbon-button as a regular button.

As a regular button, you can customize a ribbon-button in these ways:

  1. Text: the text shown on the button.
  2. Icon: the button icon, set via the icon-class attribute (requires additional CSS).
  3. Tooltip: floating tip shown on hover, implemented with the @tooltip directive and the tooltip-title attribute.
  4. Controller: usually used for business logic, so you can reuse built-in SDK controllers on custom buttons.

The following example creates a custom button that reuses a built-in SDK controller:

Details
html

<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>
json
{
  "iframeOptions": {
    "style": "height: 300px;"
  }
}

As this example shows, when reusing a controller you can use the @controller="" directive to specify which Controller to reuse. Besides states:HandController, many other controllers can be reused. For details, see Controllers.

In addition to reusing logic on custom buttons, you can modify built-in SDK components directly. The main approach is Fragment configuration. The following example shows how to customize the content of the built-in hand-tool ribbon-button:

Details
html

<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>
json
{
  "iframeOptions": {
    "style": "height: 300px;"
  }
}

On the toolbar, the dropdown component is usually used together with a ribbon-button, but it can also be used alone. The only difference between the two approaches is the visual appearance. The following example uses a ribbon-button together with a dropdown:

Details
html

<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>
json
{
  "iframeOptions": {
    "style": "height: 300px;"
  }
}

For more business components, see Pre-configured components. That section describes the default configuration of many business components, which you can use as a reference when adjusting them with Fragment configuration.

In the default layout, the left sidebar is built with a sidebar component, and its children are sidebar-panel components wrapped by different business modules.

This section covers common ways to customize the left sidebar and related details.

Create a sidebar-panel

The following is a sidebar-panel template with basic attributes:

html

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

If needed, you can add directives such as @tooltip:

html

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

To listen for the active event (fired when the sidebar-panel is expanded), use the @on.active directive. See this example:

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

Here is a runnable example:

Details
html

<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>
json
{
  "iframeOptions": {
    "style": "height: 600px;"
  }
}

Remove a sidebar-panel

If you want to remove a built-in SDK sidebar-panel, prefer Fragment configuration over the Components API unless you specifically need the API. Initializing a sidebar-panel can be relatively expensive; removing it via Fragment configuration avoids unnecessary initialization.

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

Customize built-in SDK sidebar-panel components

1. commentlist-sidebar-panel

In the commentlist sidebar panel, you can customize CommentCardComponent and ReplyCardComponent. Unlike other components, they are created dynamically based on the annotation types and counts in the document, so you cannot change their details through Fragment configuration. The SDK provides events so you can obtain the component objects when they are created or destroyed, then modify them with the Components API.

Related events:

  1. UIExtension.UIEvents.appendCommentListComment: Fired when a CommentCardComponent is inserted. The callback receives commentCardComponent and annot.

  2. UIExtension.UIEvents.destroyCommentListComment: Fired when a CommentCardComponent is destroyed. The callback parameters are the same as for appendCommentListComment.

  3. UIExtension.UIEvents.appendCommentListReply: Fired when a ReplyCardComponent is inserted. The callback receives replyCardComponent and replyAnnot.

  4. UIExtension.UIEvents.destroyCommentListReply: Fired when a ReplyCardComponent is destroyed. The callback parameters are the same as for appendCommentListReply.

See the API Reference for usage details:

  1. CommentCardComponent
  2. ReplyCardComponent
2. thumbnail-sidebar-panel

For the thumbnail-sidebar-panel, see Customize thumbnail UI.

3. bookmark-sidebar-panel

For the bookmark-sidebar-panel, see Customize bookmark UI.

Others

Besides the three panels above, layer-sidebar-panel, attachment-sidebar-panel, and field-sidebar-panel do not currently provide customization APIs.

Context menu

A context menu is a component based on ContextMenuComponent. All customizable context menus follow the same principles:

  1. Each feature-specific context menu has a fixed name. To replace a context menu, target that name. After replacement, do not change the name—doing so may prevent the menu from showing, or override another context menu and break other features.

  2. You can add, remove, and replace context menu items with Fragment configuration and the Components API.

  3. Leading, trailing, or consecutive contextmenu-separator separators are hidden automatically; you usually do not need to control their visibility.

The following sections describe the context menu for each feature module.

1. PDF page context menu

See Customize page context menu.

2. Annotation context menu

See Customize annotation context menu.

Note: the Annotation properties dialog itself is not customizable. To provide a custom properties dialog, replace the <contextmenu-item-properties> implementation. See the following example:

Details
html

<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>
json
{
  "iframeOptions": {
    "style": "height: 600px;"
  }
}

3. Other context menus

Customizing other context menus is similar to the page and annotation context menus above. Use those documents as a guide for your own extensions. For related menu component templates, see Pre-configured components.

For dialogs triggered inside the SDK—including alert, prompt, confirm, and the loading overlay—you can define the components shown by the SDK through the Viewer UI. For details, see Viewer UI.

Reusing Controllers

The following lists reusable controller implementations currently available in the SDK, using @controller directive syntax. The name before the colon is the module name; the name after the colon is the controller name. Details follow:

Built-in UIExtension controllers

How to obtain:

Built-in controllers can be obtained as classes with UIExtension.modular.module('module name').getController('controller name').

List:

  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: Downloads the currently opened document.
  6. zoom:ZoomInAndOutController: Controls document view zoom. Must be used with the action attribute. Optional values: action="zoomin" and action="zoomout".
  7. pagemode:SinglePageModeController: Switches the page mode to single-page mode.
  8. pagemode:ContinuousPageModeController: Switches the page mode to continuous page mode.
  9. pagemode:FacingPageModeController: Switches the page mode to facing-page mode.
  10. pagemode:ContinuousFacingPageModeController: Switches the 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 child components.
  28. zoom:ZoomActionController: Controls document view zoom. Unlike zoom:ZoomInAndOutController, it requires a target scale mode (non-numeric): action='fitHeight', action='fitWidth', action='fitVisible'.
  29. gotoview:GotoFirstPageController: Goes to the first page.
  30. gotoview:GotoPrevPageController: Goes to the previous page.
  31. gotoview:GotoNextPageController: Goes to the next page.
  32. gotoview:GotoLastPageController: Goes 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 annotations from the current document in the format specified by the format attribute: format="XFDF". Supported formats: XFDF, FDF, and JSON.
  45. text-sel:CopySelectedTextController: Copies the selected text.
  46. text-sel:CreateTextHighlightOnSelectedTextController: Creates a text highlight annotation over the selected text.
  47. text-sel:CreateStrikeoutOnSelectedTextController: Creates a Strikeout annotation over the selected text.
  48. text-sel:CreateUnderlineOnSelectedTextController: Creates an Underline annotation over the selected text.
  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: Changes the document background color.
  53. comment-list:ImportCommentButtonController: Imports annotation 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 zoom and requires an action attribute. Optional values: fitWidth, fitHeight, fitVisible, and numeric scales.
  57. ui-rotation:RotateRightController: Rotates the document view 90 degrees to the right.
  58. ui-rotation:RotateLeftController: Rotates the document view 90 degrees to the left.
  59. annot-opr:ShowAnnotReplyController: Opens the left commentlist panel, locates the selected annotation, and focuses the reply input.
  60. annot-opr:DeleteAnnotController: Deletes the selected annotation.
  61. annot-opr:ShowAnnotPropertiesController: Shows the annotation properties dialog.
  62. annot-opr:SetPropsDefault: Sets the current annotation properties as defaults for the next annotation the user creates.

Controllers in UIExtension Addons

How to obtain:

Obtaining controller classes from Addons uses the same method as for built-in UIExtension controllers, but make sure those Addons are already loaded. See How to use controllers in addons for scenarios and limitations.

List:

  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. edit-graphics addon(module name edit-graphics):
    1. edit-graphics:ShowGraphicsObjectPropertiesController: Opens graphics object properties.
    2. edit-graphics:MoveFrontGraphicsObjectController: Moves the graphics object to the front.
    3. edit-graphics:MoveBackGraphicsObjectController: Moves the graphics object to the back.
    4. edit-graphics:DeleteGraphicsObjectController: Deletes the graphics object.
  4. path-objects addon(Std Adv, module name edit-pageobjects):
    1. edit-pageobjects:EditAllObjectsController: Switches to the edit-all state-handler to edit currently supported page objects.
    2. edit-pageobjects:AddLinePathObjectController: Adds a line path object.
    3. edit-pageobjects:AddSquarePathObjectController: Adds a rectangle path object.
    4. edit-pageobjects:AddCirclePathObjectController: Adds a circle path object.
    5. edit-pageobjects:AddRoundRectPathObjectController: Adds a rounded-rectangle path object.
  5. text-object addon(Std Adv, module name edit-text-object):
    1. edit-text-object:AddTextStateController: Enters the add-text related state-handler.
    2. edit-text-object:EditTextStateController: Handles edit-text-object state-related logic.
    3. edit-text-object:BoldStyleController: Bold style.
    4. edit-text-object:ItalicStyleController: Italic style.
    5. edit-text-object:UnderlineStyleController: Underline style
    1. edit-text-object:FontColorController: Text color.
    2. edit-text-object:FontFamilySizeController: Font family and size.
  6. find-replace addon(Adv Edit, find-replace module):
    1. find-replace:FindReplaceController: Opens the find-and-replace flow (same callback as used in the find-replace-button fragment).
  7. page-editor addon(Adv Edit, page-editor module):
    1. page-editor:EditObjectController: Switches the current StateHandler to page-object editing (text, image, path, gradient, and so on).
    2. page-editor:AddImageAdvController: Switches to the add-image-from-file tool.
    3. page-editor:AddShapesController: Switches to the create graphics/shapes tool.
    4. page-editor:AddTextController: Switches to the add-text tool.
    5. page-editor:EditTextController: Touchup edit-text entry (used with the page-editor:edit-text pre-configured component).
    6. page-editor:JoinSplitController: Entry for text-block join/split and related sub-modes.
    7. page-editor:PropsDialogController: Logic for the page-object properties dialog.
    8. page-editor:joinpage-editor:splitpage-editor:linkpage-editor:unlinkpage-editor:select-nonepage-editor:close: Sub-actions in the Join/Split flow (inline-registered controllers; names match getName()).
  8. fullscreen addon:
    • full-screen:FullscreenController: Toggles fullscreen mode.
  9. print addon:
    • print:ShowPrintDialogController: Shows the print dialog.
  10. file-property addon:
    • fpmodule:FileInfoCallbackController: Shows the document properties dialog.
  11. h-continuous addon:
    • h-continuous:HContinuousViewModeController: Switches the page mode to horizontal continuous page mode.
  12. export-form addon:
    1. export-form-module:ExportToXMLController: Exports the current document form to an XML file.
    2. export-form-module:ExportToFDFController: Exports the current document form to an FDF file.
    3. export-form-module:ExportToXFDFController: Exports the current document form to an XFDF file.
    4. export-form-module:ExportToCSVController: Exports the current document form to a CSV file.
    5. export-form-module:ExportToTXTController: Exports the current document form to a TXT file.
  13. comparison addon:
    1. comparison:ShowCompareFileDialogButtonController: Shows the document comparison dialog.
  14. redaction addon:
    1. redaction:RedactionTextAndImageController: Switches the current StateHandler to the Redaction tool for marking text and images.
    2. redaction:RedactionController: Switches the current StateHandler to the Redaction tool for marking regions.
    3. redaction:RedactionPageController: Switches the current StateHandler to the Redaction tool for marking pages.

For pre-configured component tags and descriptions related to the edit modules, also see Pre-configured components — components in page-object edit addons and PDF edit modules.

With these controllers, you can reuse built-in SDK logic while rewriting components.

Controller reuse examples

Take states:HandController as an example: it switches the current StateHandler to the Hand tool and can activate or deactivate the component based on whether the Hand tool is current:

Details
html

<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>
json
{
  "iframeOptions": {
    "style": "height: 600px;"
  }
}

Not all controllers are as simple as states:HandController. The following example shows several special controllers; use them as shown:

Details
html

<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>
json
{
  "iframeOptions": {
    "style": "height: 600px;"
  }
}