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:
- Sidebar panel: Bookmark panel
- Bookmark context menu: Bookmark context menu, Bookmark context menu items, and Custom menu item example
- Bookmark tree node: Bookmark tree node
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 ​
- 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>
- Shorthand:
- 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>
- Shorthand:
- 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>
- Shorthand:
- 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>
- Shorthand:
- 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>
- Shorthand:
- 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>
- Shorthand:
- 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>
- Shorthand:
- 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>
- Shorthand:
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.
- See Reusing controllers for more on controller reuse.
- See Controller directive for the
@controllerdirective.
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:
create-tree-node: Fired when a node is created—use it to change appearance or attach handlers.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"
}
}