// ES6 Compliant Syntax
// GitHub Copilot v1, Claude Sonnet 3.5, 2025-08-11
// File: multi-tab-support/index.js
import WebViewer from '@pdftron/webviewer';
const licenseKey = 'YOUR_WEBVIEWER_LICENSE_KEY';
const element = document.getElementById('viewer');
let theInstance = null;
const onLoad = async (instance) => {
theInstance = instance;
// Enable multi-tab support
instance.UI.enableFeatures([instance.UI.Feature.MultiTab]);
// Add default tabs
instance.UI.TabManager.addTab(firstDefaultDocument.path, firstDefaultDocument.options);
instance.UI.TabManager.addTab(secondDefaultDocument.path, secondDefaultDocument.options);
instance.UI.TabManager.addTab(thirdDefaultDocument.path, thirdDefaultDocument.options);
const allTabs = instance.UI.TabManager.getAllTabs();
instance.UI.TabManager.setActiveTab(allTabs?.[0]?.id || 1);
// Set up event listeners for tab management
instance.UI.addEventListener(instance.UI.Events.TAB_ADDED, updateTabs);
instance.UI.addEventListener(instance.UI.Events.TAB_DELETED, tabDeleted);
instance.UI.addEventListener(instance.UI.Events.TAB_MOVED, tabMoved);
instance.UI.addEventListener(instance.UI.Events.BEFORE_TAB_CHANGED, changeActiveTab);
// Update the tabs UI (buttons to select and delete tabs)
updateTabs();
// Add the Add Tab button at the end of the controls container
controlsContainer.appendChild(buttonNewTab);
};
WebViewer(
{
path: '/lib',
licenseKey: licenseKey,
enableFilePicker: true, // Enable file picker to open files. In WebViewer -> menu icon -> Open File
},
element
).then((instance) => {
onLoad(instance);
});
// UI elements corresponding to document tabs
// Each tab will have a button to select (activate) it and a button to delete it
let tabButtons = [];
// preserve the active tab ID to restore the active tab after deletion or movement
let activeTabID = -1; // -1 means no active tab
// listener for moving tab event
function tabMoved() {
activeTabID = theInstance.UI.TabManager.getActiveTab().id;
updateTabs();
}
// listener for tab deletion
function tabDeleted(){
const tabManager = theInstance.UI.TabManager;
try {
activeTabID = tabManager.getActiveTab().id; // preserve the active tab ID if it exists
} catch (error) {
activeTabID = -1; // if the user deletes the active tab, we reset the active tab ID
}
updateTabs();
const allTabs = tabManager.getAllTabs()
if(allTabs.length > 1) { //force refreshing after deletion
const saveActiveTabID = tabManager.getActiveTab().id;
allTabs.forEach(tab => {
if(tab.id != saveActiveTabID) {
tabManager.setActiveTab(tab.id);
}
});
tabManager.setActiveTab(saveActiveTabID);
}
}
// common function to update the tab buttons in the UI
// This function will be called for any change affecting tabs, which includes:
// 1. After default tabs are created
// 2. When the active tab changes
// 3. When a tab is added, deleted, or moved
function updateTabs(){
// Remove all tab elements from container and reset the array
// Rebuilding the buttons array is an easy way to keep the buttons in sync with the tabs
tabButtons.forEach(element => element.remove());
tabButtons = [];
const tabManager = theInstance.UI.TabManager;
const allTabs = tabManager.getAllTabs();
allTabs.forEach(tab => {
// Create a button to select the tab (make it active)
const buttonSelectTab = document.createElement('button');
tabButtons.push(buttonSelectTab);
buttonSelectTab.textContent = tab.options.filename;
buttonSelectTab.className = 'btn-select';
// The container inlucdes a line break to separate the tab buttons from the AddTab button.
// Insert the button before the line break
controlsContainer.insertBefore(buttonSelectTab, lineBreak);
// Add an event handler to select the tab when clicked
buttonSelectTab.onclick = async () => {
theInstance.UI.TabManager.setActiveTab(tab.id);
};
// Create a button to delete the tab
const buttonDelTab = document.createElement('button');
tabButtons.push(buttonDelTab);
buttonDelTab.textContent = 'x';
buttonDelTab.className = 'btn-del';
controlsContainer.insertBefore(buttonDelTab, lineBreak);
// Add an event handler to delete the tab when clicked
buttonDelTab.onclick = async () => {
activeTabID = theInstance.UI.TabManager.getActiveTab().id;
theInstance.UI.TabManager.deleteTab(tab.id);
};
});
if(allTabs.length < 1)
return; // No tabs to display
let activeTabIndex = 0;
if(activeTabID < 0) // if no active tab ID is preserved, try to find the active tab
{
try{
activeTabIndex = allTabs.indexOf(tabManager.getActiveTab());
} catch (error){
// activeTabIndex will remain 0 if no active tab is found
}
} else {
// If we have an active tab ID, find its index and set the button class accordingly
activeTabIndex = allTabs.findIndex(obj => obj.id === activeTabID);
}
// Set the button styles based on the active tab index
setButtonStyles(activeTabIndex);
}
// Function to set styles for the tab buttons based on the active tab index
// This function will be called whenever the active tab changes or tabs are added/deleted/moved
// It updates the class of the buttons to indicate which tab is active
function setButtonStyles(activeTabIndex) {
tabButtons.forEach((button, index) => {
if(index % 2 === 0){ // Only update the select buttons (even indices)
button.className = (activeTabIndex * 2 === index) ? 'btn-active' : 'btn-select';
}
});
}
// Listener for tab change events
function changeActiveTab(prevTab, nextTab) {
if (activeTabID === nextTab.id)
return;
activeTabID = nextTab.id;
const allTabs = theInstance.UI.TabManager.getAllTabs();
const activeTabIndex = allTabs.findIndex(tab => tab.id === activeTabID);
setButtonStyles(activeTabIndex);
}
// default documents to load
const firstDefaultDocument = {
path: 'https://apryse.s3.amazonaws.com/public/files/samples/WebviewerDemoDoc.pdf',
options: {
extension: 'pdf',
filename: 'Demo PDF',
setActive: true,
saveCurrentActiveTabState: true,
},
};
const secondDefaultDocument = {
path: 'https://apryse.s3.amazonaws.com/public/files/samples/sales_tracker.xlsx',
options: {
extension: 'xlsx',
filename: 'Sales Tracker (xlsx)',
setActive: false,
saveCurrentActiveTabState: false,
},
};
const thirdDefaultDocument = {
path: 'https://apryse.s3.amazonaws.com/public/files/samples/Jupiter_Poster_Raster.png',
options: {
extension: 'png',
filename: 'Jupiter Poster (PNG)',
setActive: false,
saveCurrentActiveTabState: false,
},
};
// UI section
//
// Helper code to add controls to the viewer holding the buttons and dropdown
// This code creates a container for the buttons and dropdown, styles them, and adds them to the viewer
const buttonNewTab = document.createElement('button');
buttonNewTab.textContent = 'Add New Tab';
buttonNewTab.onclick = async () => {
theInstance.UI.openElements(['OpenFileModal']);
};
// Create a break element to separate controls into two lines
const lineBreak = document.createElement('br');
// Create a container for all controls (label, dropdown, and buttons)
const controlsContainer = document.createElement('div');
buttonNewTab.className = 'btn-style';
controlsContainer.className = 'control-container';
controlsContainer.appendChild(lineBreak);
element.insertBefore(controlsContainer, element.firstChild);