Easily enable loading, viewing, and editing XLSX files directly in your browser, without server-side dependencies or MS Office installations.
This demo lets you:
Learn more about Web SDK and Spreadsheet Editor.
To add XLSX file viewing and editing capability with WebViewer:
Step 1: Follow get started in your preferred web stack for WebViewer
Step 2: Implement WebViewer using iFrame
Step 3: 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.
Apryse collects some data regarding your usage of the SDK for product improvement.
The data that Apryse collects include:
For clarity, no other data is collected by the SDK and Apryse has no access to the contents of your documents.
If you wish to continue without data collection, contact us and we will email you a no-tracking trial key for you to get started.
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