Easily enable loading, viewing, and editing XLSX files directly in your browser, without server-side dependencies or MS Office installations.
This demo lets you:
Implementation steps
To add XLSX file viewing and editing capability with 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 4 (Preview), 2025-09-15
3// File: showcase-demos/spreadsheet-editor/index.js
4
5import WebViewer from '@pdftron/webviewer';
6
7// Custom File class to hold file metadata (not to be confused with browser's File API)
8class FileMetadata {
9 constructor(options) {
10 this.name = options.name;
11 this.displayName = options.displayName;
12 this.path = options.path;
13 this.extension = options.extension;
14 this.displayExtension = options.displayExtension;
15 this.id = options.id;
16 }
17}
18
19const files = {
20 ANNUAL_FINANCIAL_REPORT: new FileMetadata({
21 name: 'annual_financial_report.xlsx',
22 displayName: 'Annual Financial Report (xlsx)',
23 path: 'https://apryse.s3.us-west-1.amazonaws.com/public/files/samples/annual_financial_report.xlsx',
24 extension: 'xlsx',
25 id: 82,
26 }),
27 INVOICE_TEMPLATE: new FileMetadata({
28 name: 'invoice_template.xlsx',
29 displayName: 'Invoice Template (xlsx)',
30 path: 'https://apryse.s3.us-west-1.amazonaws.com/public/files/samples/invoice_template.xlsx',
31 extension: 'xlsx',
32 displayExtension: 'xlsx',
33 id: 48,
34 }),
35 XLSX_5000_ROWS: new FileMetadata({
36 name: 'file_5000_rows.xlsx',
37 displayName: 'File with 5000 rows (xlsx)',
38 path: 'https://apryse.s3.us-west-1.amazonaws.com/public/files/samples/file_5000_rows.xlsx',
39 extension: 'xlsx',
40 id: 83,
41 }),
42}
43
44const sampleDocuments = [
45 files.ANNUAL_FINANCIAL_REPORT,
46 files.INVOICE_TEMPLATE,
47 files.XLSX_5000_ROWS,
48];
49
50const defaultFile = sampleDocuments[0].path;
51
52// Function to initialize and load the Spreadsheet Editor
53
54function loadSpreadsheetEditor() {
55 const element = document.getElementById('viewer');
56 if (!element) {
57 console.error('Viewer div not found.');
58 return;
59 }
60
61 WebViewer.Iframe({
62 path: '/lib',
63 licenseKey: 'ADD_YOUR_LICENSE_KEY',
64 initialDoc: defaultFile,
65 enableFilePicker: true, // Enable file picker to open files. In WebViewer -> menu icon -> Open File
66 initialMode: WebViewer.Modes.SPREADSHEET_EDITOR,
67 }, element).then(instance => {
68
69 const { documentViewer, SpreadsheetEditor } = instance.Core;
70 const spreadsheetEditorManager = documentViewer.getSpreadsheetEditorManager();
71 const SpreadsheetEditorEvents = SpreadsheetEditor.SpreadsheetEditorManager.Events;
72
73 // Ensure the Spreadsheet Editor Manager is ready and set to editing mode
74 spreadsheetEditorManager.addEventListener(
75 SpreadsheetEditorEvents.SPREADSHEET_EDITOR_READY,
76 () => {
77 spreadsheetEditorManager.setEditMode(
78 SpreadsheetEditor.SpreadsheetEditorEditMode.EDITING
79 );
80 },
81 { once: true }
82 );
83
84 });
85
86 // UI section
87 //
88 // Create a container for all controls (label, dropdown, and buttons)
89 const controlsContainer = document.createElement('div');
90 controlsContainer.className = 'control-container';
91 controlsContainer.id = 'gallery-container';
92 element.insertBefore(controlsContainer, element.firstChild);
93
94 const filesArray = [
95 { name: sampleDocuments[0].displayName, thumbnail: '/showcase-demos/spreadsheet-editor/annual_financial_report.png', url: sampleDocuments[0].path },
96 { name: sampleDocuments[1].displayName, thumbnail: '/showcase-demos/spreadsheet-editor/invoice_template.png', url: sampleDocuments[1].path },
97 { name: sampleDocuments[2].displayName, thumbnail: '/showcase-demos/spreadsheet-editor/file_5000_rows.png', url: sampleDocuments[2].path }
98 ]
99
100 //
101 console.log('gallery-picker.js loaded, GalleryPicker:', window.GalleryPicker);
102 // Dynamically load gallery-picker.js if not already loaded
103 if (!window.GalleryPicker) {
104 const script = document.createElement('script');
105 script.src = '/showcase-demos/spreadsheet-editor/gallery-picker.js';
106 script.onload = function () {
107 console.log('Safe to use GalleryPicker here');
108 GalleryPicker.init('gallery-container', filesArray, (file) => {
109 console.log('Selected file:', file);
110 WebViewer.getInstance().UI.loadDocument(file.url, { filename: file.name, extension: file.extension });
111 });
112 console.log('GalleryPicker.init', GalleryPicker.init);
113
114 };
115 document.head.appendChild(script);
116 }
117
118 if (window.GalleryPicker) {
119 element.insertBefore(controlsContainer, element.firstChild);
120 }
121}
122
123loadSpreadsheetEditor();
124
125
126
1// ES6 Compliant Syntax
2// GitHub Copilot v1, Claude Sonnet 4 (Preview), 2025-09-15
3// File: showcase-demos/spreadsheet-editor/gallery-picker.js
4// GalleryPicker.js
5// Static class for displaying a scrollable grid of thumbnails and handling file selection
6
7class GalleryPicker {
8 static files = [];
9 static gridContainer = null;
10 static externalCallback = null;
11
12 // Initialize the gallery grid
13 static async init(containerId, filesArray, onFileClick) {
14
15 this.files = filesArray;
16 this.externalCallback = onFileClick;
17 this.gridContainer = document.getElementById(containerId);
18 if (!this.gridContainer) {
19 throw new Error(`Container with id '${containerId}' not found.`);
20 }
21 this.gridContainer.innerHTML = '';
22 this.gridContainer.classList.add('gallery-grid');
23 await this.renderGrid();
24 }
25
26 // Render the grid of thumbnails
27 static async renderGrid() {
28 for (const file of this.files) {
29 const thumbDiv = document.createElement('div');
30 thumbDiv.className = 'gallery-thumb';
31 thumbDiv.title = file.name;
32 thumbDiv.tabIndex = 0;
33 thumbDiv.setAttribute('role', 'button');
34 thumbDiv.setAttribute('aria-label', file.name);
35 thumbDiv.onclick = () => this.handleClick(file);
36 thumbDiv.onkeydown = (e) => {
37 if (e.key === 'Enter' || e.key === ' ') {
38 this.handleClick(file);
39 }
40 };
41
42 const img = document.createElement('img');
43 img.src = file.thumbnail;
44 img.alt = file.name;
45 img.className = 'gallery-thumb-img';
46 thumbDiv.appendChild(img);
47
48 const label = document.createElement('div');
49 label.className = 'gallery-thumb-label';
50 label.textContent = file.name;
51 thumbDiv.appendChild(label);
52
53 this.gridContainer.appendChild(thumbDiv);
54 }
55 }
56
57 // Handle click event and pass info to external class
58 static handleClick(file) {
59 if (typeof this.externalCallback === 'function') {
60 this.externalCallback(file);
61 }
62 }
63
64 // Add a new file to the gallery
65 static async addFile(fileObj) {
66 this.files.push(fileObj);
67 await this.renderGrid();
68 }
69
70 // Remove a file by name
71 static async removeFile(fileName) {
72 this.files = this.files.filter(f => f.name !== fileName);
73 await this.renderGrid();
74 }
75
76
77 //Get images to local drive
78 static async downloadThumbnails() {
79 const images = [
80 {
81 url: 'https://apryse.s3.us-west-1.amazonaws.com/public/files/samples/thumbs/annual_financial_report.png',
82 filename: 'annual_financial_report.png'
83 },
84 {
85 url: 'https://apryse.s3.us-west-1.amazonaws.com/public/files/samples/thumbs/invoice_template.png',
86 filename: 'invoice_template.png'
87 },
88 {
89 url: 'https://apryse.s3.us-west-1.amazonaws.com/public/files/samples/thumbs/file_5000_rows.png',
90 filename: 'file_5000_rows.png'
91 }
92 ];
93
94 for (const img of images) {
95 try {
96 const response = await fetch(img.url);
97 if (!response.ok) throw new Error(`Failed to fetch ${img.url}`);
98 const blob = await response.blob();
99 const link = document.createElement('a');
100 link.href = URL.createObjectURL(blob);
101 link.download = img.filename;
102 document.body.appendChild(link);
103 link.click();
104 document.body.removeChild(link);
105 URL.revokeObjectURL(link.href);
106 } catch (err) {
107 console.error('Error downloading', img.url, err);
108 }
109 }
110 }
111}
112
113window.GalleryPicker = GalleryPicker;
114
115// Usage sample:
116// const filesArray = [
117// { name: sampleDocuments[0].displayName, thumbnail: '/showcase-demos/spreadsheet-editor/annual_financial_report.png', url: sampleDocuments[0].path },
118// { name: sampleDocuments[1].displayName, thumbnail: '/showcase-demos/spreadsheet-editor/invoice_template.png', url: sampleDocuments[1].path },
119// { name: sampleDocuments[2].displayName, thumbnail: '/showcase-demos/spreadsheet-editor/file_5000_rows.png', url: sampleDocuments[2].path }
120// ]
121// GalleryPicker.init('gallery-container', filesArray, (file) => {
122// console.log('Selected file:', file);
123// WebViewer.getInstance().UI.loadDocument(file.url, { filename: file.name, extension: file.extension });
124
125
126
1/* Layout for viewer and its children */
2#viewer {
3 display: flex;
4 margin: 0 0 0 0;
5 width: 100%;
6}
7
8#playground-viewer{
9 height: 90% !important;
10}
11
12/* Controls Container on the left */
13.control-container {
14 width: 150px;
15 overflow-x: hidden;
16 margin: auto;
17 padding-bottom: 50px;
18 box-sizing: border-box;
19 display: flex;
20 flex-direction: column;
21 align-items: flex-start;
22 gap: 1px;
23 margin: 0;
24 padding: 10px 5px 5px 10px;
25 border-right: 1px solid #e0e0e0;
26 background-color: rgba(112, 198, 255, 0.2);
27 /* Light blue background for contrast */
28}
29
30.bottom-headers-wrapper {
31 bottom: -11px !important;
32}
33
34#editorWrapper {
35 width: 100% !important;
36 height: 85% !important;
37 padding-bottom: 50px !important;
38 margin-bottom: 250px !important;
39}
40
41/* WebViewer container on the right */
42#webviewer-1 {
43 margin: 0;
44 padding-bottom: 10px !important;
45
46
47}
48
49/* Responsive Design */
50@media (max-width: 768px) {
51 #viewer {
52 flex-direction: column;
53 }
54
55 .control-container {
56 width: 150px;
57 height: auto;
58 border-right: none;
59 border-bottom: 1px solid #e0e0e0;
60 align-items: center;
61 }
62
63
64}
65
66
67#GenericFileTab{
68 margin-bottom: 50px !important;
69}
70
71/* Gallery Picker Styles */
72.gallery-grid {
73 display: grid;
74 grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
75 gap: 16px;
76 overflow-y: auto;
77 max-height: 100vh;
78 padding: 10px;
79}
80
81.gallery-thumb {
82 cursor: pointer;
83 border: 1px solid #ccc;
84 border-radius: 6px;
85 background: #fff;
86 box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
87 display: flex;
88 flex-direction: column;
89 align-items: center;
90 padding: 8px;
91 transition: box-shadow 0.2s;
92}
93
94.gallery-thumb:hover,
95.gallery-thumb:focus {
96 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
97 border-color: #70c6ff;
98}
99
100.gallery-thumb-img {
101 width: 100px;
102 height: 100px;
103 object-fit: cover;
104 border-radius: 4px;
105 margin-bottom: 6px;
106}
107
108.gallery-thumb-label {
109 font-size: 13px;
110 color: #333;
111 text-align: center;
112 margin-top: 2px;
113 word-break: break-word;
114}
Did you find this helpful?
Trial setup questions?
Ask experts on DiscordNeed other help?
Contact SupportPricing or product questions?
Contact Sales