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
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