Add PDF search functionality with configurable search methods that allow users to refine or broaden string match criteria.
This demo allows you to:
To add PDF Search capability with WebViewer:
Step 1: Get started with WebViewer in 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-09-23
3// File: pdf-search/index.js
4
5import WebViewer from '@pdftron/webviewer';
6
7const licenseKey = 'YOUR_WEBVIEWER_LICENSE_KEY';
8
9WebViewer(
10 {
11 path: '/lib',
12 initialDoc: 'https://apryse.s3.us-west-1.amazonaws.com/public/files/samples/section-508.pdf',
13 enableFilePicker: true, // Enable file picker to open files. In WebViewer -> menu icon -> Open File
14 enableMeasurement: true,
15 licenseKey: licenseKey, // Replace with your license key
16 },
17 document.getElementById('viewer')
18).then((instance) => {
19
20 // Customize the main webviewer left panel after the load completion
21 instance.Core.documentViewer.addEventListener('documentLoaded', () => {
22 customizeUI(instance);
23 });
24
25 // Define the search listener to capture search results
26 // This listener is called when search results are returned
27 const searchListener = (searchPattern, options, results) => {
28
29 // Update the checkbox states on the custom search panel based on the search options used
30 checkBoxControl(checkBoxData[0]).checked = options.caseSensitive;
31 checkBoxControl(checkBoxData[1]).checked = options.wholeWord;
32
33 searchResults = formattedSearchResults(results);
34 updateResultsViewer();
35 };
36
37 // Add search listener to capture search results
38 instance.UI.addSearchListener(searchListener);
39
40 console.log('✅ WebViewer loaded successfully.');
41}).catch((error) => {
42 console.error('❌ Failed to initialize WebViewer:', error);
43});
44
45// Store the search results
46let searchResults = null;
47
48// The list of registered panels in the main webviewer
49let viewerPanels = null;
50
51// The tab panel, representing the webviewer left panel
52const tabPanel = {
53 handle: null,
54 dataElement: 'tabPanel'
55};
56
57// The custom search sub-panel to be registered
58const leftSearchPanel = {
59 handle: null,
60 dataElement: 'leftSearchPanel',
61 render: null,
62};
63
64const checkBoxData = [
65 { label: "Case sensitive" },
66 { label: "Whole word" },
67 { label: "Wild card" },
68 { label: "Regular expression" }
69];
70
71// Customize the main webviewer left panel
72const customizeUI = (instance) => {
73 const { UI } = instance;
74
75 // Close the tab panel (if it's open) for refreshment.
76 UI.closeElements([tabPanel.dataElement]);
77
78 // Get the list of registered panels in the main webviewer
79 viewerPanels = UI.getPanels();
80
81 // Find the Tab Panel to modify. The search sub-panel will be added to this Tab panel.
82 tabPanel.handle = viewerPanels.find((panel) => panel.dataElement === tabPanel.dataElement);
83
84 // Register the custom search sub-panel
85 RegisterSearchPanel(instance);
86
87 // Add the new custom search sub-panel to list of sub-panels under the Tab Panel
88 leftSearchPanel.handle = { render: leftSearchPanel.dataElement };
89 tabPanel.handle.panelsList = [leftSearchPanel.handle, ...tabPanel.handle.panelsList];
90
91 UI.openElements([tabPanel.dataElement]);
92 UI.setPanelWidth(tabPanel.dataElement, 400);
93};
94
95// Register the custom search sub-panel
96const RegisterSearchPanel = (instance) => {
97 leftSearchPanel.render = createSearchPanelElements(instance);
98 instance.UI.addPanel({
99 dataElement: leftSearchPanel.dataElement,
100 location: 'left',
101 icon: '<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="18" height="18" viewBox="0 0 24 24"><path d="M 10 2 C 5.5935644 2 2 5.5935677 2 10 C 2 14.406432 5.5935644 18 10 18 C 11.844022 18 13.540969 17.365427 14.896484 16.310547 L 20.292969 21.707031 A 1.0001 1.0001 0 1 0 21.707031 20.292969 L 16.310547 14.896484 C 17.365427 13.540969 18 11.844021 18 10 C 18 5.5935677 14.406436 2 10 2 z M 10 4 C 13.325556 4 16 6.674446 16 10 C 16 13.325554 13.325556 16 10 16 C 6.6744439 16 4 13.325554 4 10 C 4 6.674446 6.6744439 4 10 4 z"></path></svg>',
102 title: 'Search',
103 render: () => leftSearchPanel.render,
104 });
105};
106
107// Create the search panel elements.
108const createSearchPanelElements = (instance) => {
109 let panelDiv = document.createElement('div');
110 panelDiv.id = 'search';
111 let paragraph = document.createTextNode('A demo of the PDF search and indexing capabilities in WebViewer, a JavaScript-based PDF SDK for web apps. Found words are highlighted throughout the PDF document.');
112 panelDiv.appendChild(paragraph);
113
114 let dividerDiv = document.createElement('div');
115 dividerDiv.style.borderTop = '1px solid #ccc';
116 dividerDiv.style.margin = '10px 0';
117 panelDiv.appendChild(dividerDiv);
118
119 // Search input field
120 const inputSearch = document.createElement('input');
121 inputSearch.id = 'inputSearch';
122 inputSearch.type = 'text';
123 inputSearch.placeholder = 'Search here ...';
124 inputSearch.addEventListener("input", () => {
125 enableButton(searchButton, inputSearch.value.trim() !== '');
126 });
127 inputSearch.addEventListener("keydown", function (event) {
128 if (event.key === "Enter") {
129 searchPDF(instance);
130 }
131 });
132 panelDiv.appendChild(inputSearch);
133
134 // Search button
135 const searchButton = document.createElement('button');
136 searchButton.textContent = 'Search';
137 enableButton(searchButton, false); // Initially disable the search button
138 searchButton.style.marginLeft = '10px';
139 searchButton.onclick = () => searchPDF(instance); // Search the PDF document
140 panelDiv.appendChild(searchButton);
141
142 // Create checkboxes for items in the checkBoxData array
143 checkBoxData.forEach(item => {
144
145 // Checkbox input
146 const checkbox = document.createElement('input');
147 checkbox.type = 'checkbox';
148 checkbox.id = labelToId(item.label);
149
150 // Checkbox label
151 const label = document.createElement('label');
152 label.textContent = item.label;
153
154 panelDiv.appendChild(document.createElement('p'));
155 panelDiv.appendChild(checkbox);
156 panelDiv.appendChild(label);
157 });
158
159 // Display search results
160 let jsonDiv = document.createElement('div');
161 jsonDiv.id = 'json';
162 const jsonTitle = document.createElement("h3");
163 jsonTitle.textContent = "Search Results";
164 jsonDiv.appendChild(jsonTitle);
165 jsonDiv.appendChild(document.createElement('p'));
166
167 const span = document.createElement("span");
168 span.id = 'resultsText';
169 let resultsText = document.createTextNode('');
170 span.appendChild(resultsText);
171 jsonDiv.appendChild(span);
172 jsonDiv.appendChild(document.createElement('p'));
173
174 const scrollBox = document.createElement("div");
175 scrollBox.style.width = "350px";
176 scrollBox.style.height = "350px";
177 scrollBox.style.border = "2px solid #444";
178 scrollBox.style.overflow = "scroll"; // Enables both vertical and horizontal scroll
179 scrollBox.style.whiteSpace = "nowrap"; // Prevents wrapping for horizontal scroll
180 scrollBox.style.padding = "10px";
181 scrollBox.style.fontFamily = "monospace";
182 scrollBox.style.backgroundColor = "black";
183 scrollBox.style.color = "white";
184
185 // Format and insert results JSON data
186 const jsonContent = document.createElement("pre");
187 scrollBox.appendChild(jsonContent);
188 jsonDiv.appendChild(scrollBox);
189
190 // Open JSON data dialog button
191 let jsonDataDialogButton = document.createElement('button');
192 jsonDataDialogButton.textContent = 'Open in Dialog';
193 jsonDataDialogButton.id = 'jsonDataDialogButton';
194 enableButton(jsonDataDialogButton, false); // Initially disable the button
195 jsonDataDialogButton.onclick = () => openJsonDataDialog();
196 jsonDiv.appendChild(jsonDataDialogButton);
197 jsonDiv.appendChild(document.createElement('p'));
198
199 panelDiv.appendChild(dividerDiv.cloneNode());
200 panelDiv.appendChild(jsonDiv);
201
202 return panelDiv;
203};
204
205// Format the search results for better readability
206const formattedSearchResults = (results) => {
207 if (results === null || results.length === 0) {
208 return [];
209 }
210
211 const formatted = [];
212 results.forEach((result) => {
213 const element = {
214 ambient_str: result.ambient_str,
215 result_str: result.result_str,
216 result_str_start: result.result_str_start,
217 result_str_end: result.result_str_end,
218 page_num: result.page_num,
219 resultCode: result.resultCode,
220 quads: result.quads,
221 };
222
223 formatted.push(element);
224 });
225
226 return formatted;
227};
228
229// Update the results viewer with the latest search results
230const updateResultsViewer = () => {
231 const resultsText = leftSearchPanel.render.querySelector('#resultsText');
232 resultsText.textContent = (searchResults && searchResults.length > 0) ? `${searchResults.length} results found. (Data has been formatted for readability)` : 'No results found';
233
234 const jsonContent = leftSearchPanel.render.querySelector('pre');
235 jsonContent.textContent = (searchResults && searchResults.length > 0) ? JSON.stringify(searchResults, null, 1) : '';
236
237 const jsonDataDialogButton = leftSearchPanel.render.querySelector('#jsonDataDialogButton');
238 enableButton(jsonDataDialogButton, (searchResults && searchResults.length > 0));
239};
240
241// Open search results in a viewer with zoom in/out and close buttons
242const openJsonDataDialog = () => {
243 let fontSize = 14;
244
245 // Create overlay
246 const overlay = document.createElement("div");
247 overlay.className = "modal-overlay";
248 overlay.onclick = (e) => {
249 if (e.target === overlay) {
250 document.body.removeChild(overlay);
251 }
252 };
253
254 // Modal box
255 const modal = document.createElement("div");
256 modal.className = "modal-box";
257
258 // Controls
259 const controls = document.createElement("div");
260 controls.className = "modal-controls";
261
262 const zoomInBtn = document.createElement("button");
263 zoomInBtn.textContent = "+";
264 zoomInBtn.onclick = () => {
265 fontSize += 2;
266 content.style.fontSize = fontSize + "px";
267 };
268
269 const zoomOutBtn = document.createElement("button");
270 zoomOutBtn.textContent = "-";
271 zoomOutBtn.onclick = () => {
272 fontSize = Math.max(10, fontSize - 2);
273 content.style.fontSize = fontSize + "px";
274 };
275
276 const closeBtn = document.createElement("button");
277 closeBtn.textContent = "Close";
278 closeBtn.className = "modal-close";
279 closeBtn.onclick = () => {
280 document.body.removeChild(overlay);
281 };
282
283 controls.appendChild(zoomInBtn);
284 controls.appendChild(zoomOutBtn);
285 controls.appendChild(closeBtn);
286
287 // Content
288 const content = document.createElement("pre");
289 content.className = "modal-content";
290 content.style.fontSize = fontSize + "px";
291 content.innerHTML = JSON.stringify(searchResults, null, 1);
292
293 modal.appendChild(controls);
294 modal.appendChild(content);
295 overlay.appendChild(modal);
296 document.body.appendChild(overlay);
297}
298
299// Convert a label to a valid ID by removing spaces
300const labelToId = (label) => label.replace(/\s+/g, '');
301
302// Get the checkbox control based on the label
303const checkBoxControl = (item) => {
304 const checkbox = leftSearchPanel.render.querySelector(`#${labelToId(item.label)}`);
305 return checkbox;
306};
307
308// Search the PDF document based on the search pattern and options
309const searchPDF = (instance) => {
310
311 instance.Core.documentViewer.clearSearchResults();
312
313 // Get the search pattern from the search input field value in the search panel
314 // searchPattern can be something like "search*m" with "wildcard" option set to true
315 // searchPattern can be something like "search1|search2" with "regex" option set to true
316 const searchPattern = leftSearchPanel.render.querySelector('#inputSearch').value.trim();
317
318 // Set search options values based on the checkbox states
319 const searchOptions = {
320 caseSensitive: checkBoxControl(checkBoxData[0]).checked, // match case
321 wholeWord: checkBoxControl(checkBoxData[1]).checked, // match whole words only
322 wildcard: checkBoxControl(checkBoxData[2]).checked, // allow using '*' as a wildcard value
323 regex: checkBoxControl(checkBoxData[3]).checked, // string is treated as a regular expression
324 ambientString: true, // return ambient string as part of the result
325 };
326
327 instance.UI.searchTextFull(searchPattern, searchOptions);
328};
329
330// Enable or disable a button based on the state
331const enableButton = (button, state) => {
332 button.disabled = !state;
333 button.style.backgroundColor = (state) ? 'blue' : 'gray';
334 button.style.color = (state) ? 'white' : 'darkgray';
335};
1/* Modal styles for pdf-search demo */
2
3.modal-overlay {
4 position: fixed;
5 top: 0;
6 left: 0;
7 width: 100%;
8 height: 100%;
9 background-color: rgba(0, 0, 0, 0.5);
10 z-index: 1001;
11 display: flex;
12 justify-content: center;
13 align-items: center;
14}
15
16.modal-box {
17 background: white;
18 padding: 20px;
19 border-radius: 8px;
20 width: 80%;
21 max-width: 800px;
22 height: 80%;
23 max-height: 600px;
24 display: flex;
25 flex-direction: column;
26 box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
27 position: relative;
28}
29
30.modal-controls {
31 margin-bottom: 15px;
32 display: flex;
33 gap: 10px;
34 align-items: center;
35}
36
37.modal-controls button {
38 padding: 8px 16px;
39 border: none;
40 border-radius: 4px;
41 cursor: pointer;
42 font-size: 14px;
43 transition: background-color 0.2s ease;
44}
45
46.modal-controls button:not(.modal-close) {
47 background: #007cba;
48 color: white;
49}
50
51.modal-controls button:not(.modal-close):hover {
52 background: #005a8b;
53}
54
55.modal-close {
56 background: #dc3545 !important;
57 color: white !important;
58 margin-left: auto;
59}
60
61.modal-close:hover {
62 background: #b02a37 !important;
63}
64
65.modal-content {
66 background: #f8f9fa;
67 padding: 15px;
68 border-radius: 4px;
69 overflow: auto;
70 flex: 1;
71 font-family: 'Courier New', monospace;
72 white-space: pre-wrap;
73 word-wrap: break-word;
74 border: 1px solid #dee2e6;
75 margin: 0;
76 color: #000000;
77}
78
Did you find this helpful?
Trial setup questions?
Ask experts on DiscordNeed other help?
Contact SupportPricing or product questions?
Contact Sales