Skip to content

Customize bookmark UI components ​

This section explains how to customize bookmark components. Bookmarks are built on the tree component—read the tree component usage and API reference first.

INFO

See PDF bookmarks for bookmark APIs.

Bookmark component structure ​

This section describes template structure for bookmark-related components:

Bookmark panel ​

In the built-in layout template, <bookmark-v2:bookmark-tree></bookmark-v2:bookmark-tree> is the left sidebar bookmark panel. It is equivalent to:

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

In your app, you can replace the built-in shorthand template with the full template and customize attributes:

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

    body {
        height: 100vh;
    }

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

Bookmark context menu ​

In the built-in layout template, <bookmark-v2:bookmark-contextmenu @lazy></bookmark-v2:bookmark-contextmenu> is the bookmark context menu. It is equivalent to:

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

The @bookmark-v2:hide-if-no-bookmark directive hides menu items when the document has no bookmarks, leaving only <bookmark-v2:add-bookmark> on right-click in an empty panel.

Define a context menu named fv--bookmark-contextmenu-v2 to replace the built-in bookmark context menu:

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

    body {
        height: 100vh;
    }

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

You can also add or remove individual menu items instead of replacing the whole menu:

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

    body {
        height: 100vh;
    }

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

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

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

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

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

Bookmark context menu items ​

  1. Add bookmark:
    • Shorthand: <bookmark-v2:add-bookmark name="fv--bookmark-contextmenu-item-add"></bookmark-v2:add-bookmark>
    • Full template: <contextmenu-item @controller="AddBookmarkController">contextmenu.bookmark.add</contextmenu-item>
  2. Go to bookmark:
    • Shorthand: <bookmark-v2:goto-bookmark @bookmark-v2:hide-if-no-bookmark name="fv--bookmark-contextmenu-item-goto"></bookmark-v2:goto-bookmark>
    • Full template: <contextmenu-item @controller="GotoBookmarkController" @bookmark-v2:hide-if-no-bookmark name="fv--bookmark-contextmenu-item-goto">contextmenu.bookmark.goTo</contextmenu-item>
  3. Cut bookmark:
    • Shorthand: <bookmark-v2:cut-bookmark @bookmark-v2:hide-if-no-bookmark name="fv--bookmark-contextmenu-item-cut"></bookmark-v2:cut-bookmark>
    • Full template: <contextmenu-item @controller="CutBookmarkController" @bookmark-v2:hide-if-no-bookmark name="fv--bookmark-contextmenu-item-cut">contextmenu.bookmark.cut</contextmenu-item>
  4. Paste under selected bookmark:
    • Shorthand: <bookmark-v2:paste-under-selected-bookmark @bookmark-v2:hide-if-no-bookmark name="fv--bookmark-contextmenu-item-paste-under"></bookmark-v2:paste-under-selected-bookmark>
    • Full template: <contextmenu-item @controller="PasteUnderSelectedBookmarkController as ctrl" @show="ctrl.pasteAvailable" @bookmark-v2:hide-if-no-bookmark name="fv--bookmark-contextmenu-item-paste-under">contextmenu.bookmark.pasteUnder</contextmenu-item>
  5. Paste after selected bookmark:
    • Shorthand: <bookmark-v2:paste-after-selected-bookmark @bookmark-v2:hide-if-no-bookmark name="fv--bookmark-contextmenu-item-paste-after"></bookmark-v2:paste-after-selected-bookmark>
    • Full template: <contextmenu-item @controller="PasteAfterSelectedBookmarkController as ctrl" @show="ctrl.pasteAvailable" @bookmark-v2:hide-if-no-bookmark name="fv--bookmark-contextmenu-item-paste-after">contextmenu.bookmark.pasteAfter</contextmenu-item>
  6. Delete bookmark:
    • Shorthand: <bookmark-v2:delete-bookmark @bookmark-v2:hide-if-no-bookmark name="fv--bookmark-contextmenu-item-delete"></bookmark-v2:delete-bookmark>
    • Full template: <contextmenu-item @controller="DeleteBookmarkController" @bookmark-v2:hide-if-no-bookmark name="fv--bookmark-contextmenu-item-delete">contextmenu.bookmark.delete</contextmenu-item>
  7. Set bookmark destination:
    • Shorthand: <bookmark-v2:set-destination @bookmark-v2:hide-if-no-bookmark name="fv--bookmark-contextmenu-item-set-destination"></bookmark-v2:set-destination>
    • Full template: <contextmenu-item @controller="SetDestinationBookmarkController" @bookmark-v2:hide-if-no-bookmark name="fv--bookmark-contextmenu-item-set-destination">contextmenu.bookmark.destination</contextmenu-item>
  8. Rename bookmark:
    • Shorthand: <bookmark-v2:rename-bookmark @bookmark-v2:hide-if-no-bookmark name="fv--bookmark-contextmenu-item-rename"></bookmark-v2:rename-bookmark>
    • Full template: <contextmenu-item @controller="RenameBookmarkController" @bookmark-v2:hide-if-no-bookmark name="fv--bookmark-contextmenu-item-rename">contextmenu.bookmark.rename</contextmenu-item>

Custom menu item example ​

The items above are built into the SDK. Your app can reuse their @controller implementations and replace built-in items for custom behavior.

Example:

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

    body {
        height: 100vh;
    }

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

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

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

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

Bookmark tree node ​

A bookmark tree node is a TreeNodeComponent. It stays in sync with PDF bookmark data and is created or destroyed as bookmarks change. Customize tree nodes through events. TreeComponent exposes:

  1. create-tree-node: Fired when a node is created—use it to change appearance or attach handlers.
  2. destroy-tree-node: Fired when a node is destroyed—use it to release resources and avoid leaks.

Example: replace node text with a link:

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

    body {
        height: 100vh;
    }

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