PDF Search Showcase Demo Code Sample

Requirements
View Demo

Add PDF search functionality with configurable search methods that allow users to refine or broaden string match criteria.

This demo allows you to:

  • Choose your own PDF file
  • Type a search string
  • Configure the search options:
    • Case Sensitive
    • Whole Word
    • Wild Card
    • Regular Expression
  • Highlight matching string in the document

Implementation steps

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

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

Did you find this helpful?

Trial setup questions?

Ask experts on Discord

Need other help?

Contact Support

Pricing or product questions?

Contact Sales