Create accessible navigation with document outlines or table of contents. Add new outlines or edit them.
This demo allows you to:
Implementation steps
To add Table of Contents or Outlines capability in WebViewer:
Step 1: Choose your preferred web stack.
Step 2: Add the ES6 JavaScript sample code provided in this guide.
Once you generate your license key, it will automatically be included in your sample code below.
Apryse collects some data regarding your usage of the SDK for product improvement.
The data that Apryse collects include:
For clarity, no other data is collected by the SDK and Apryse has no access to the contents of your documents.
If you wish to continue without data collection, contact us and we will email you a no-tracking trial key for you to get started.
1// ES6 Compliant Syntax
2// Copilot name: GitHub Copilot, version: 1.0.0, model: GPT-4, version: 2024-06, date: 2025-10-23
3// File: showcase-demos/edit-table-of-contents/index.js
4
5import WebViewer from '@pdftron/webviewer';
6
7function initializeWebViewer() {
8 WebViewer(
9 {
10 path: '/lib',
11 initialDoc: 'https://apryse.s3.us-west-1.amazonaws.com/public/files/samples/Report_2011.pdf',
12 enableFilePicker: true, // Enable file picker to open files. In WebViewer -> menu icon -> Open File.
13 fullAPI: true, // Enable the full PDFNet API.
14 licenseKey: 'YOUR_LICENSE_KEY', // Replace with your license key.
15 },
16 document.getElementById('viewer')
17 ).then((instance) => {
18
19 // Set the toolbar group to the Annotate tools.
20 instance.UI.setToolbarGroup('toolbarGroup-Annotate');
21
22 instance.Core.documentViewer.addEventListener('documentLoaded', () => {
23 UIElements.pageNumber = 1;
24
25 // Set default coordinates.
26 setDefaultCoordinates(instance);
27
28 // Customize the webviewer left panel.
29 UIElements.customizeUI(instance);
30 });
31
32 console.log('WebViewer loaded successfully.');
33 }).catch((error) => {
34 console.error('Failed to initialize WebViewer:', error);
35 });
36}
37
38// Function to get the iframe context of WebViewer.
39const getIframeContext = () => {
40 return (
41 document.getElementById('viewer')?.getElementsByTagName('iframe')?.[0]?.contentWindow
42 .instance || window.WebViewer.getInstance()
43 );
44};
45
46// Function to add a new outline to the PDF document.
47window.addOutline = (instance) => {
48 const iframe = getIframeContext();
49 const PDFNet = iframe.Core.PDFNet;
50
51 const documentViewer = iframe.Core.documentViewer;
52
53 return PDFNet.runWithCleanup(async () => {
54 const doc = await documentViewer.getDocument().getPDFDoc();
55 const newOutline = await PDFNet.Bookmark.create(doc, UIElements.outlineName);
56
57 const page = await doc.getPage(UIElements.pageNumber);
58
59 const zoom = 1;
60
61 const dest = await PDFNet.Destination.createXYZ(page, UIElements.xCoordinate, UIElements.yCoordinate, zoom);
62
63 newOutline.setAction(await PDFNet.Action.createGoto(dest));
64
65 await doc.addRootBookmark(newOutline);
66
67 instance.UI.reloadOutline();
68
69 instance.UI.setActiveTabInPanel({ tabPanel: UIElements.tabPanel.dataElement, tabName: 'outlinesPanel' });
70 });
71};
72
73// Helper function to set default coordinates based on the page size.
74window.setDefaultCoordinates = (instance) => {
75 const doc = instance.Core.documentViewer.getDocument();
76 const pageInfo = doc.getPageInfo(UIElements.pageNumber);
77 UIElements.xCoordinate = 0;
78 UIElements.yCoordinate = pageInfo.height;
79}
80
81// Helper function to load the ui-elements.js script.
82function loadUIElementsScript() {
83 return new Promise((resolve, reject) => {
84 if (window.UIElements) {
85 console.log('UIElements already loaded');
86 resolve();
87 return;
88 }
89 const script = document.createElement('script');
90 script.src = '/showcase-demos/edit-table-of-contents-in-a-pdf/ui-elements.js';
91 script.onload = function () {
92 console.log('✅ UIElements script loaded successfully');
93 resolve();
94 };
95 script.onerror = function () {
96 console.error('Failed to load UIElements script');
97 reject(new Error('Failed to load ui-elements.js'));
98 };
99 document.head.appendChild(script);
100 });
101}
102
103// Load UIElements script first, then initialize WebViewer.
104loadUIElementsScript().then(() => {
105 initializeWebViewer();
106}).catch((error) => {
107 console.error('Failed to load UIElements:', error);
108});
1// ES6 Compliant Syntax
2// Copilot name: GitHub Copilot, version: 1.0.0, model: GPT-4, version: 2024-06, date: 2025-10-23
3// File: showcase-demos/edit-table-of-contents/ui-elements.js
4
5// Class with static UI elements and related functions for the table of contents demo.
6
7class UIElements {
8
9 // The list of registered panels in the webviewer.
10 static viewerPanels = null;
11
12 // The tab panel, representing the webviewer left panel.
13 static tabPanel = {
14 handle: null,
15 dataElement: 'tabPanel'
16 };
17
18 // The table of contents sub-panel to be registered.
19 static tableOfContentsPanel = {
20 handle: null,
21 dataElement: 'tableOfContentsPanel',
22 render: null,
23 };
24
25 static outlineName = 'My First Outline';
26 static pageNumber = 1;
27 static xCoordinate = 0;
28 static yCoordinate = 0;
29
30 // Customize the webviewer left panel.
31 static customizeUI = (instance) => {
32 const { UI } = instance;
33
34 // Close the tab panel (if it's open) for refreshment.
35 UI.closeElements([UIElements.tabPanel.dataElement]);
36
37 // Get the list of registered panels in the WebViewer.
38 UIElements.viewerPanels = UI.getPanels();
39
40 // Find the Tab Panel to modify. The table of contents sub-panel will be added to this Tab panel.
41 UIElements.tabPanel.handle = UIElements.viewerPanels.find((panel) => panel.dataElement === UIElements.tabPanel.dataElement);
42
43 // Register the table of contents sub-panel.
44 UIElements.RegisterTableOfContentsPanel(instance);
45
46 // Add the new table of contents sub-panel to list of sub-panels under the Tab Panel.
47 UIElements.tableOfContentsPanel.handle = { render: UIElements.tableOfContentsPanel.dataElement };
48 UIElements.tabPanel.handle.panelsList = [UIElements.tableOfContentsPanel.handle, ...UIElements.tabPanel.handle.panelsList];
49
50 UI.openElements([UIElements.tabPanel.dataElement]);
51 };
52
53 // Register the table of contents sub-panel.
54 static RegisterTableOfContentsPanel = (instance) => {
55 UIElements.tableOfContentsPanel.render = UIElements.createTableOfContentsPanelElements(instance);
56 instance.UI.addPanel({
57 dataElement: UIElements.tableOfContentsPanel.dataElement,
58 location: 'left',
59 icon: '<svg xmlns="http://www.w3.org/2000/svg" width="18px" height="18px" viewBox="0 0 103.19 122.88"><path d="M17.16 0h82.72a3.32 3.32 0 013.31 3.31v92.32c-.15 2.58-3.48 2.64-7.08 2.48H15.94c-4.98 0-9.05 4.07-9.05 9.05s4.07 9.05 9.05 9.05h80.17v-9.63h7.08v12.24c0 2.23-1.82 4.05-4.05 4.05H16.29C7.33 122.88 0 115.55 0 106.59V17.16C0 7.72 7.72 0 17.16 0zm3.19 13.4h2.86c1.46 0 2.66.97 2.66 2.15v67.47c0 1.18-1.2 2.15-2.66 2.15h-2.86c-1.46 0-2.66-.97-2.66-2.15V15.55c.01-1.19 1.2-2.15 2.66-2.15z" fill-rule="evenodd" clip-rule="evenodd"/></svg>',
60 title: 'Table of Contents',
61 render: () => UIElements.tableOfContentsPanel.render,
62 });
63 };
64
65 // Create the table of contents panel elements.
66 static createTableOfContentsPanelElements = (instance) => {
67 let panelDiv = document.createElement('div');
68 panelDiv.id = 'tableOfContentsPanel';
69
70 let paragraph = document.createTextNode('Add or edit the existing document outline or table of contents to create accessible navigation of your documents.');
71 panelDiv.appendChild(paragraph);
72
73 let dividerDiv = document.createElement('div');
74 dividerDiv.style.borderTop = '1px solid #ccc';
75 dividerDiv.style.margin = '10px 0';
76 panelDiv.appendChild(dividerDiv);
77
78 // Outline details
79 let outlineDetails = document.createElement("h3");
80 outlineDetails.textContent = "Outline Details";
81 panelDiv.appendChild(outlineDetails);
82
83 // Outline name label
84 let outlineTitle = document.createTextNode('Name:');
85 panelDiv.appendChild(outlineTitle);
86
87 // Outline name input field
88 const inputName = document.createElement('input');
89 inputName.id = 'inputName';
90 inputName.type = 'text';
91 inputName.value = UIElements.outlineName;
92 inputName.addEventListener("input", () => UIElements.outlineName = inputName.value);
93 inputName.addEventListener("keydown", () => UIElements.outlineName = inputName.value);
94 panelDiv.appendChild(document.createElement('p'));
95 panelDiv.appendChild(inputName);
96
97 // Outline destination page label
98 let outlineDestination = document.createTextNode('Destination page:');
99 panelDiv.appendChild(document.createElement('p'));
100 panelDiv.appendChild(outlineDestination);
101
102 // Outline destination page input field
103 const pageCount = instance.Core.documentViewer.getPageCount();
104 const inputDestination = document.createElement('input');
105 inputDestination.id = 'inputDestination';
106 inputDestination.type = 'number';
107 inputDestination.min = '1';
108 inputDestination.max = pageCount.toString();
109 inputDestination.step = '1';
110 inputDestination.value = UIElements.pageNumber.toString();
111 inputDestination.addEventListener("input", () => {
112 UIElements.inputFieldLimitations(inputDestination);
113 UIElements.pageNumber = parseInt(inputDestination.value);
114 });
115 inputDestination.addEventListener("keydown", () => {
116 UIElements.inputFieldLimitations(inputDestination);
117 UIElements.pageNumber = parseInt(inputDestination.value);
118 });
119 panelDiv.appendChild(document.createElement('p'));
120 panelDiv.appendChild(inputDestination);
121
122 // Full page checkbox
123 const checkbox = document.createElement('input');
124 checkbox.type = 'checkbox';
125 checkbox.checked = true;
126 checkbox.onchange = () => {
127 const inputX = UIElements.tableOfContentsPanel.render.querySelector('#inputX');
128 const inputY = UIElements.tableOfContentsPanel.render.querySelector('#inputY');
129 if (checkbox.checked) {
130 setDefaultCoordinates(instance);
131 inputX.value = UIElements.xCoordinate.toString();
132 inputY.value = UIElements.yCoordinate.toString();
133 }
134
135 inputX.disabled = checkbox.checked;
136 inputY.disabled = checkbox.checked;
137 };
138
139 // Full page checkbox label
140 const label = document.createElement('label');
141 label.textContent = 'Full page';
142
143 panelDiv.appendChild(document.createElement('p'));
144 panelDiv.appendChild(checkbox);
145 panelDiv.appendChild(label);
146
147 // X and Y coordinate input fields
148 UIElements.createCoordinateInput(panelDiv, 'X');
149 UIElements.createCoordinateInput(panelDiv, 'Y');
150
151 // Add Outline button.
152 let button = document.createElement("button");
153 button.textContent = 'Add New Outline';
154 button.style.backgroundColor = 'blue';
155 button.style.color = 'white';
156 button.style.border = 'none';
157 button.style.padding = '10px 15px';
158 button.style.borderRadius = '12px';
159 button.onmouseover = () => button.style.opacity = '0.8';
160 button.onmouseout = () => button.style.opacity = '1.0';
161 button.style.cursor = 'pointer';
162 button.onclick = () => {
163 addOutline(instance);
164 button.style.opacity = '1.0';
165 };
166 panelDiv.appendChild(document.createElement('p'));
167 panelDiv.appendChild(button);
168
169 return panelDiv;
170 };
171
172 // Create coordinate input fields.
173 static createCoordinateInput(panelDiv, axis) {
174
175 // Coordinate title field.
176 let title = document.createTextNode(`${axis} Coordinate:`);
177 title.id = `title${axis.toUpperCase()}`;
178 panelDiv.appendChild(document.createElement('p'));
179 panelDiv.appendChild(title);
180
181 // Coordinate input field.
182 const input = document.createElement('input');
183 input.id = `input${axis.toUpperCase()}`;
184 input.type = 'number';
185 input.min = '0';
186 input.disabled = true;
187 input.value = axis === 'X' ? UIElements.xCoordinate.toString() : UIElements.yCoordinate.toString();
188 input.addEventListener("input", () => {
189 UIElements.inputFieldLimitations(input);
190 axis === 'X' ? UIElements.xCoordinate = parseInt(input.value) : UIElements.yCoordinate = parseInt(input.value);
191 });
192 input.addEventListener("keydown", () => {
193 UIElements.inputFieldLimitations(input);
194 axis === 'X' ? UIElements.xCoordinate = parseInt(input.value) : UIElements.yCoordinate = parseInt(input.value);
195 });
196 panelDiv.appendChild(document.createElement('p'));
197 panelDiv.appendChild(input);
198 }
199
200 // Limitations for input fields.
201 // Ensures values are non-negative and within min/max range.
202 // Used for page number and coordinate input fields.
203 static inputFieldLimitations = (element) => {
204 element.value = Math.abs(parseInt(element.value));
205 const min = parseInt(element.min);
206 const max = parseInt(element.max);
207 if (element.value > max)
208 element.value = max;
209 else if (element.value < min)
210 element.value = min;
211 };
212}
Did you find this helpful?
Trial setup questions?
Ask experts on DiscordNeed other help?
Contact SupportPricing or product questions?
Contact Sales