// ES6 Compliant Syntax
// GitHub Copilot v1, Claude Sonnet 3.5, 2025-08-03
// File: pdf-layers/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;
instance.Core.documentViewer.addEventListener('documentLoaded', () => {
// Initialize layers when the document is loaded
initLayers();
});
};
// sample PDF with multiple layers
const defaultLayersDoc = 'https://apryse.s3.us-west-1.amazonaws.com/public/files/samples/construction_drawing-final.pdf';
WebViewer(
{
path: '/lib',
licenseKey: licenseKey,
initialDoc: defaultLayersDoc,
enableFilePicker: true, // Enable file picker to open files. In WebViewer -> menu icon -> Open File
},
element
).then((instance) => {
onLoad(instance);
});
// Define variable to hold layers information
let docLayers = null;
let flatLayers = null;
// Array to hold labels, checkboxes and line breaks for layers
// This will be used to dynamically create UI elements for each layer and remove them when needed
let layerElements = [];
// Show or hide layers based on checkbox state.
// Set the value of the "visible" property for each layer in the layers array
// and update the document viewer to reflect the changes.
// The sender parameter is the actual checkbox that was changed.
// The checkbox's "value" property contains the nesting level of the layer
function updateLayersDisplay(sender) {
// check if layer has children
const layerLevel = +sender.value; // get the level as number
let startCheck = false;
let endCheck = false;
layerElements.forEach(el => {
if(endCheck)
return;
if(el.isSameNode(sender)){
console.log("sender same as el", sender);
startCheck = true; // found our checkbox. Start checking from next iteration
return; // from the forEach
}
if(el.type === 'checkbox' && startCheck){
if(+el.value <= layerLevel){ // Stop checking when we find first non-child
endCheck = true;
return;
}
// el is a child of sender. Set all children like sender
el.checked = sender.checked;
}
});
// Following code updates the layer's visibility to match the checkboxes
if (flatLayers && flatLayers.length > 0) {
let checkboxIndex = 0;
// loop to find the checkboxes, which are in the same order as their corresponding flat layers
layerElements.forEach(el => {
if(el.type === 'checkbox'){
flatLayers[checkboxIndex].visible = el.checked;
checkboxIndex++;
}
});
// reflect the layers' visibility on the document
const documentViewer = theInstance.Core.documentViewer;
const doc = documentViewer.getDocument();
doc.setLayersArray(flatLayers);
documentViewer.refreshAll();
documentViewer.updateView();
}
}
// Retrieve the layers from the document and initialize the UI.
// This function will be called when the document is loaded and will populate
// the layers array with the document's layers and create a checkbox for each layer
// If no layers are found, it will suggest loading the default document
async function initLayers() {
const doc = theInstance.Core.documentViewer.getDocument();
docLayers = await doc.getLayersArray();
// If no layers are found, suggest loading the default document
if(doc.getType() !== 'pdf') {
labelLayers.textContent = `This sample only supports PDFs. Click "${buttonDefault.textContent}" to load a sample with layers.`;
} else if(!docLayers || docLayers.length === 0){
labelLayers.textContent = `This document has no layers. Click "${buttonDefault.textContent}" to load a sample with layers.`;
}
// Clear existing labels and checkboxes if any exist from previous document
layerElements.forEach(element => element.remove());
layerElements = [];
// reset the flatLayers array
flatLayers = [];
if(!docLayers || docLayers.length === 0) {
return; // Exit if no layers are found
}
let currnetLevel = 0; // will be larger than zero for nested layers
// Function to create UI elements for the layers.
// Called recursively in case there are child (nested) layers
function addCheckboxes(layerArray){
layerArray.forEach((layer) => {
flatLayers.push(layer); // Add the layer to the flatLayers array
layer.visible = true; // Set all layers to visible by default
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.value = currnetLevel; // zero if it's not for a child layer
// indent the child checkboxes based on their nesting level
checkbox.style.marginLeft = "" + (currnetLevel * 15) + "px";
checkbox.checked = layer.visible;
// display the layer's name next to its checkbox
const label = document.createElement('label');
label.className = 'label-style';
label.textContent = layer.name;
// The container has 2 lines separated by a line break
// Insert the labels and checkboxes before the line break
controlsContainer.insertBefore(label, lineBreak);
controlsContainer.insertBefore(checkbox, label);
// separate each layer's checkbox and label from the previous layer with a new line break
const br = document.createElement('br');
controlsContainer.insertBefore(br, checkbox);
// handle the change event for the checkboxes
checkbox.onchange = () => {
updateLayersDisplay(checkbox);
};
// Add the line-break, label and checkbox to the layer elements array for later reference
layerElements.push(br);
layerElements.push(label);
layerElements.push(checkbox);
if(layer.children && layer.children.length > 0) {
// Since the layer has children, recursively add checkboxes for these child layers between the brackets
currnetLevel++;
addCheckboxes(layer.children);
currnetLevel--;
}
});
}
// Add checkboxes for each layer in the layers array and create a flat array of layers
addCheckboxes(docLayers);
// Update the label to show the number of layers
labelLayers.textContent = `Layers found: ${flatLayers.length}`;
}
// UI section
// Create a container for all controls (labels, buttons, checkboxes, etc.)
const controlsContainer = document.createElement('div');
const labelUpload = document.createElement('label');
labelUpload.textContent = 'Use the Open File command in the WebViewer UI menu to upload a PDF file';
// Create a button to open default PDF
const buttonDefault = document.createElement('button');
buttonDefault.textContent = 'Default Document';
buttonDefault.onclick = async () => {
// load default PDF with layers
theInstance.UI.loadDocument(defaultLayersDoc);
};
// Label to display layer count or suggest loading default document
const labelLayers = document.createElement('label');
labelLayers.textContent = "";
// Style the container and controls using CSS classes
controlsContainer.className = 'control-container';
buttonDefault.className = 'btn-style';
labelUpload.className = 'label-style';
labelLayers.className = 'label-style';
// Create a break element to separate controls into two lines
const lineBreak = document.createElement('br');
// Append all controls to the container
controlsContainer.appendChild(labelLayers);
controlsContainer.appendChild(lineBreak);
controlsContainer.appendChild(labelUpload);
controlsContainer.appendChild(buttonDefault);
// modify viewer element and its parent div to display controlsContainer with viewer side-by-side
element.parentElement.style.display = "flex";
element.style.display = "inline-block";
element.style.width = "75%";
controlsContainer.style.height = "100%";
controlsContainer.style.display = "inline-block";
controlsContainer.style.width = "25%";
// Add the controls container right before to the viewer
element.parentElement.insertBefore(controlsContainer, element);