PDF Layers Showcase Demo Code Sample

Requirements
View Demo

Easily enable or disable layers in PDF construction drawings.

This demo lets you:

  • Upload a PDF file of your construction drawings
  • Toggle individual layers (e.g., architectural, electrical) using Optional Content Groups (OCGs)
  • Edit layout and content directly within the PDF

Implementation steps

To add layer separation capability to a PDF 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.

License Key

1// ES6 Compliant Syntax
2// GitHub Copilot v1, Claude Sonnet 3.5, 2025-08-03
3// File: pdf-layers/index.js
4
5import WebViewer from '@pdftron/webviewer';
6
7const licenseKey = 'YOUR_WEBVIEWER_LICENSE_KEY';
8
9const element = document.getElementById('viewer');
10let theInstance = null;
11const onLoad = async (instance) => {
12 theInstance = instance;
13 instance.Core.documentViewer.addEventListener('documentLoaded', () => {
14 // Initialize layers when the document is loaded
15 initLayers();
16 });
17};
18
19// sample PDF with multiple layers
20const defaultLayersDoc = 'https://apryse.s3.us-west-1.amazonaws.com/public/files/samples/construction_drawing-final.pdf';
21
22WebViewer(
23 {
24 path: '/lib',
25 licenseKey: licenseKey,
26 initialDoc: defaultLayersDoc,
27 enableFilePicker: true, // Enable file picker to open files. In WebViewer -> menu icon -> Open File
28 },
29 element
30).then((instance) => {
31 onLoad(instance);
32});
33
34// Define variable to hold layers information
35let docLayers = null;
36let flatLayers = null;
37
38// Array to hold labels, checkboxes and line breaks for layers
39// This will be used to dynamically create UI elements for each layer and remove them when needed
40let layerElements = [];
41
42// Show or hide layers based on checkbox state.
43// Set the value of the "visible" property for each layer in the layers array
44// and update the document viewer to reflect the changes.
45// The sender parameter is the actual checkbox that was changed.
46// The checkbox's "value" property contains the nesting level of the layer
47function updateLayersDisplay(sender) {
48 // check if layer has children
49 const layerLevel = +sender.value; // get the level as number
50 let startCheck = false;
51 let endCheck = false;
52 layerElements.forEach(el => {
53 if(endCheck)
54 return;
55 if(el.isSameNode(sender)){
56 console.log("sender same as el", sender);
57 startCheck = true; // found our checkbox. Start checking from next iteration
58 return; // from the forEach
59 }
60
61 if(el.type === 'checkbox' && startCheck){
62 if(+el.value <= layerLevel){ // Stop checking when we find first non-child
63 endCheck = true;
64 return;
65 }
66 // el is a child of sender. Set all children like sender
67 el.checked = sender.checked;
68 }
69 });
70 // Following code updates the layer's visibility to match the checkboxes
71 if (flatLayers && flatLayers.length > 0) {
72 let checkboxIndex = 0;
73 // loop to find the checkboxes, which are in the same order as their corresponding flat layers
74 layerElements.forEach(el => {
75 if(el.type === 'checkbox'){
76 flatLayers[checkboxIndex].visible = el.checked;
77 checkboxIndex++;
78 }
79 });
80 // reflect the layers' visibility on the document
81 const documentViewer = theInstance.Core.documentViewer;
82 const doc = documentViewer.getDocument();
83 doc.setLayersArray(flatLayers);
84 documentViewer.refreshAll();
85 documentViewer.updateView();
86 }
87}
88
89// Retrieve the layers from the document and initialize the UI.
90// This function will be called when the document is loaded and will populate
91// the layers array with the document's layers and create a checkbox for each layer
92// If no layers are found, it will suggest loading the default document
93async function initLayers() {
94 const doc = theInstance.Core.documentViewer.getDocument();
95 docLayers = await doc.getLayersArray();
96
97 // If no layers are found, suggest loading the default document
98 if(doc.getType() !== 'pdf') {
99 labelLayers.textContent = `This sample only supports PDFs. Click "${buttonDefault.textContent}" to load a sample with layers.`;
100 } else if(!docLayers || docLayers.length === 0){
101 labelLayers.textContent = `This document has no layers. Click "${buttonDefault.textContent}" to load a sample with layers.`;
102 }
103
104 // Clear existing labels and checkboxes if any exist from previous document
105 layerElements.forEach(element => element.remove());
106 layerElements = [];
107 // reset the flatLayers array
108 flatLayers = [];
109
110 if(!docLayers || docLayers.length === 0) {
111 return; // Exit if no layers are found
112 }
113
114 let currnetLevel = 0; // will be larger than zero for nested layers
115
116 // Function to create UI elements for the layers.
117 // Called recursively in case there are child (nested) layers
118 function addCheckboxes(layerArray){
119 layerArray.forEach((layer) => {
120 flatLayers.push(layer); // Add the layer to the flatLayers array
121 layer.visible = true; // Set all layers to visible by default
122 const checkbox = document.createElement('input');
123 checkbox.type = 'checkbox';
124 checkbox.value = currnetLevel; // zero if it's not for a child layer
125 // indent the child checkboxes based on their nesting level
126 checkbox.style.marginLeft = "" + (currnetLevel * 15) + "px";
127 checkbox.checked = layer.visible;
128 // display the layer's name next to its checkbox
129 const label = document.createElement('label');
130 label.className = 'label-style';
131 label.textContent = layer.name;
132 // The container has 2 lines separated by a line break
133 // Insert the labels and checkboxes before the line break
134 controlsContainer.insertBefore(label, lineBreak);
135 controlsContainer.insertBefore(checkbox, label);
136 // separate each layer's checkbox and label from the previous layer with a new line break
137 const br = document.createElement('br');
138 controlsContainer.insertBefore(br, checkbox);
139 // handle the change event for the checkboxes
140 checkbox.onchange = () => {
141 updateLayersDisplay(checkbox);
142 };
143 // Add the line-break, label and checkbox to the layer elements array for later reference
144 layerElements.push(br);
145 layerElements.push(label);
146 layerElements.push(checkbox);
147 if(layer.children && layer.children.length > 0) {
148 // Since the layer has children, recursively add checkboxes for these child layers between the brackets
149 currnetLevel++;
150 addCheckboxes(layer.children);
151 currnetLevel--;
152 }
153 });
154 }
155 // Add checkboxes for each layer in the layers array and create a flat array of layers
156 addCheckboxes(docLayers);
157
158 // Update the label to show the number of layers
159 labelLayers.textContent = `Layers found: ${flatLayers.length}`;
160}
161
162// UI section
163
164// Create a container for all controls (labels, buttons, checkboxes, etc.)
165const controlsContainer = document.createElement('div');
166
167const labelUpload = document.createElement('label');
168labelUpload.textContent = 'Use the Open File command in the WebViewer UI menu to upload a PDF file';
169
170// Create a button to open default PDF
171const buttonDefault = document.createElement('button');
172buttonDefault.textContent = 'Default Document';
173buttonDefault.onclick = async () => {
174 // load default PDF with layers
175 theInstance.UI.loadDocument(defaultLayersDoc);
176};
177
178// Label to display layer count or suggest loading default document
179const labelLayers = document.createElement('label');
180labelLayers.textContent = "";
181
182// Style the container and controls using CSS classes
183controlsContainer.className = 'control-container';
184
185buttonDefault.className = 'btn-style';
186labelUpload.className = 'label-style';
187labelLayers.className = 'label-style';
188
189// Create a break element to separate controls into two lines
190const lineBreak = document.createElement('br');
191
192// Append all controls to the container
193controlsContainer.appendChild(labelLayers);
194controlsContainer.appendChild(lineBreak);
195controlsContainer.appendChild(labelUpload);
196controlsContainer.appendChild(buttonDefault);
197// modify viewer element and its parent div to display controlsContainer with viewer side-by-side
198element.parentElement.style.display = "flex";
199element.style.display = "inline-block";
200element.style.width = "75%";
201controlsContainer.style.height = "100%";
202controlsContainer.style.display = "inline-block";
203controlsContainer.style.width = "25%";
204// Add the controls container right before to the viewer
205element.parentElement.insertBefore(controlsContainer, element);
206

Did you find this helpful?

Trial setup questions?

Ask experts on Discord

Need other help?

Contact Support

Pricing or product questions?

Contact Sales