Easily launch and manage multiple documents concurrently with optimized performance across multiple tabs.
This demo allows you to:
Implementation steps
To add multi-tab capability to WebViewer:
Step 1: Choose your preferred web stack
Step 2: Download any required modules listed in the Demo Dependencies section below
Step 3: Add the ES6 JavaScript sample code provided in this guide
Demo Dependencies
This sample uses the following:
1// ES6 Compliant Syntax
2// GitHub Copilot v1, Claude Sonnet 3.5, 2025-08-11
3// File: multi-tab-support/index.js
4
5import WebViewer from '@pdftron/webviewer';
6
7const element = document.getElementById('viewer');
8let theInstance = null;
9
10const onLoad = async (instance) => {
11 theInstance = instance;
12 // Enable multi-tab support
13 instance.UI.enableFeatures([instance.UI.Feature.MultiTab]);
14 // Add default tabs
15 instance.UI.TabManager.addTab(firstDefaultDocument.path, firstDefaultDocument.options);
16 instance.UI.TabManager.addTab(secondDefaultDocument.path, secondDefaultDocument.options);
17 instance.UI.TabManager.addTab(thirdDefaultDocument.path, thirdDefaultDocument.options);
18 const allTabs = instance.UI.TabManager.getAllTabs();
19 instance.UI.TabManager.setActiveTab(allTabs?.[0]?.id || 1);
20
21 // Set up event listeners for tab management
22 instance.UI.addEventListener(instance.UI.Events.TAB_ADDED, updateTabs);
23 instance.UI.addEventListener(instance.UI.Events.TAB_DELETED, tabDeleted);
24 instance.UI.addEventListener(instance.UI.Events.TAB_MOVED, tabMoved);
25 instance.UI.addEventListener(instance.UI.Events.BEFORE_TAB_CHANGED, changeActiveTab);
26 // Update the tabs UI (buttons to select and delete tabs)
27 updateTabs();
28 // Add the Add Tab button at the end of the controls container
29 controlsContainer.appendChild(buttonNewTab);
30};
31
32WebViewer(
33 {
34 path: '/lib',
35 licenseKey: 'YOUR_LICENSE_KEY',
36 enableFilePicker: true, // Enable file picker to open files. In WebViewer -> menu icon -> Open File
37 },
38 element
39).then((instance) => {
40 onLoad(instance);
41});
42
43// UI elements corresponding to document tabs
44// Each tab will have a button to select (activate) it and a button to delete it
45let tabButtons = [];
46
47// preserve the active tab ID to restore the active tab after deletion or movement
48let activeTabID = -1; // -1 means no active tab
49
50// listener for moving tab event
51function tabMoved() {
52 activeTabID = theInstance.UI.TabManager.getActiveTab().id;
53 updateTabs();
54}
55
56// listener for tab deletion
57function tabDeleted(){
58 const tabManager = theInstance.UI.TabManager;
59 try {
60 activeTabID = tabManager.getActiveTab().id; // preserve the active tab ID if it exists
61 } catch (error) {
62 activeTabID = -1; // if the user deletes the active tab, we reset the active tab ID
63 }
64 updateTabs();
65 const allTabs = tabManager.getAllTabs()
66 if(allTabs.length > 1) { //force refreshing after deletion
67 const saveActiveTabID = tabManager.getActiveTab().id;
68 allTabs.forEach(tab => {
69 if(tab.id != saveActiveTabID) {
70 tabManager.setActiveTab(tab.id);
71 }
72 });
73 tabManager.setActiveTab(saveActiveTabID);
74 }
75}
76
77// common function to update the tab buttons in the UI
78// This function will be called for any change affecting tabs, which includes:
79// 1. After default tabs are created
80// 2. When the active tab changes
81// 3. When a tab is added, deleted, or moved
82function updateTabs(){
83 // Remove all tab elements from container and reset the array
84 // Rebuilding the buttons array is an easy way to keep the buttons in sync with the tabs
85 tabButtons.forEach(element => element.remove());
86 tabButtons = [];
87 const tabManager = theInstance.UI.TabManager;
88 const allTabs = tabManager.getAllTabs();
89
90 allTabs.forEach(tab => {
91 // Create a button to select the tab (make it active)
92 const buttonSelectTab = document.createElement('button');
93 tabButtons.push(buttonSelectTab);
94 buttonSelectTab.textContent = tab.options.filename;
95 buttonSelectTab.className = 'btn-select';
96 // The container inlucdes a line break to separate the tab buttons from the AddTab button.
97 // Insert the button before the line break
98 controlsContainer.insertBefore(buttonSelectTab, lineBreak);
99 // Add an event handler to select the tab when clicked
100 buttonSelectTab.onclick = async () => {
101 theInstance.UI.TabManager.setActiveTab(tab.id);
102 };
103 // Create a button to delete the tab
104 const buttonDelTab = document.createElement('button');
105 tabButtons.push(buttonDelTab);
106 buttonDelTab.textContent = 'x';
107 buttonDelTab.className = 'btn-del';
108 controlsContainer.insertBefore(buttonDelTab, lineBreak);
109 // Add an event handler to delete the tab when clicked
110 buttonDelTab.onclick = async () => {
111 activeTabID = theInstance.UI.TabManager.getActiveTab().id;
112 theInstance.UI.TabManager.deleteTab(tab.id);
113 };
114 });
115
116 if(allTabs.length < 1)
117 return; // No tabs to display
118
119 let activeTabIndex = 0;
120 if(activeTabID < 0) // if no active tab ID is preserved, try to find the active tab
121 {
122 try{
123 activeTabIndex = allTabs.indexOf(tabManager.getActiveTab());
124 } catch (error){
125 // activeTabIndex will remain 0 if no active tab is found
126 }
127 } else {
128 // If we have an active tab ID, find its index and set the button class accordingly
129 activeTabIndex = allTabs.findIndex(obj => obj.id === activeTabID);
130 }
131 // Set the button styles based on the active tab index
132 setButtonStyles(activeTabIndex);
133}
134
135// Function to set styles for the tab buttons based on the active tab index
136// This function will be called whenever the active tab changes or tabs are added/deleted/moved
137// It updates the class of the buttons to indicate which tab is active
138function setButtonStyles(activeTabIndex) {
139 tabButtons.forEach((button, index) => {
140 if(index % 2 === 0){ // Only update the select buttons (even indices)
141 button.className = (activeTabIndex * 2 === index) ? 'btn-active' : 'btn-select';
142 }
143 });
144}
145
146// Listener for tab change events
147function changeActiveTab(e) {
148 const { nextTab } = e.detail;
149 const tabManager = theInstance.UI.TabManager;
150 const allTabs = tabManager.getAllTabs();
151 const activeTabIndex = allTabs.findIndex(obj => obj.id === nextTab.id);
152 setButtonStyles(activeTabIndex);
153}
154
155// default documents to load
156const firstDefaultDocument = {
157 path: 'https://apryse.s3.amazonaws.com/public/files/samples/WebviewerDemoDoc.pdf',
158 options: {
159 extension: 'pdf',
160 filename: 'Demo PDF',
161 setActive: true,
162 saveCurrentActiveTabState: true,
163 },
164};
165
166const secondDefaultDocument = {
167 path: 'https://apryse.s3.amazonaws.com/public/files/samples/sales_tracker.xlsx',
168 options: {
169 extension: 'xlsx',
170 filename: 'Sales Tracker (xlsx)',
171 setActive: false,
172 saveCurrentActiveTabState: false,
173 },
174};
175
176const thirdDefaultDocument = {
177 path: 'https://apryse.s3.amazonaws.com/public/files/samples/Jupiter_Poster_Raster.png',
178 options: {
179 extension: 'png',
180 filename: 'Jupiter Poster (PNG)',
181 setActive: false,
182 saveCurrentActiveTabState: false,
183 },
184};
185
186// UI section
187//
188// Helper code to add controls to the viewer holding the buttons and dropdown
189// This code creates a container for the buttons and dropdown, styles them, and adds them to the viewer
190
191const buttonNewTab = document.createElement('button');
192buttonNewTab.textContent = 'Add New Tab';
193
194buttonNewTab.onclick = async () => {
195 theInstance.UI.openElements(['OpenFileModal']);
196};
197
198// Create a break element to separate controls into two lines
199const lineBreak = document.createElement('br');
200
201// Create a container for all controls (label, dropdown, and buttons)
202const controlsContainer = document.createElement('div');
203
204buttonNewTab.className = 'btn-style';
205
206controlsContainer.className = 'control-container';
207controlsContainer.appendChild(lineBreak);
208element.insertBefore(controlsContainer, element.firstChild);
Did you find this helpful?
Trial setup questions?
Ask experts on DiscordNeed other help?
Contact SupportPricing or product questions?
Contact Sales