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
7const licenseKey = 'YOUR_WEBVIEWER_LICENSE_KEY';
8
9// Custom File class to hold file metadata (not to be confused with browser's File API)
10class FileMetadata {
11 constructor(options) {
12 this.name = options.name;
13 this.displayName = options.displayName;
14 this.path = options.path;
15 this.extension = options.extension;
16 this.displayExtension = options.displayExtension;
17 this.id = options.id;
18 }
19}
20
21const files = {
22 ANNUAL_FINANCIAL_REPORT: new FileMetadata({
23 name: 'annual_financial_report.xlsx',
24 displayName: 'Annual Financial Report (xlsx)',
25 path: 'https://apryse.s3.us-west-1.amazonaws.com/public/files/samples/annual_financial_report.xlsx',
26 extension: 'xlsx',
27 id: 82,
28 }),
29 INVOICE_TEMPLATE: new FileMetadata({
30 name: 'invoice_template.xlsx',
31 displayName: 'Invoice Template (xlsx)',
32 path: 'https://apryse.s3.us-west-1.amazonaws.com/public/files/samples/invoice_template.xlsx',
33 extension: 'xlsx',
34 displayExtension: 'xlsx',
35 id: 48,
36 }),
37 XLSX_5000_ROWS: new FileMetadata({
38 name: 'file_5000_rows.xlsx',
39 displayName: 'File with 5000 rows (xlsx)',
40 path: 'https://apryse.s3.us-west-1.amazonaws.com/public/files/samples/file_5000_rows.xlsx',
41 extension: 'xlsx',
42 id: 83,
43 }),
44}
45
46const sampleDocuments = [
47 files.ANNUAL_FINANCIAL_REPORT,
48 files.INVOICE_TEMPLATE,
49 files.XLSX_5000_ROWS,
50];
51
52const defaultFile = sampleDocuments[0].path;
53
54// Function to initialize and load the Spreadsheet Editor
55
56function loadSpreadsheetEditor() {
57 const element = document.getElementById('viewer');
58 if (!element) {
59 console.error('Viewer div not found.');
60 return;
61 }
62
63 WebViewer.Iframe({
64 path: '/lib',
65 licenseKey: licenseKey,
66 initialDoc: defaultFile,
67 enableFilePicker: true, // Enable file picker to open files. In WebViewer -> menu icon -> Open File
68 initialMode: WebViewer.Modes.SPREADSHEET_EDITOR,
69 }, element).then(instance => {
70
71 const { documentViewer, SpreadsheetEditor } = instance.Core;
72 const spreadsheetEditorManager = documentViewer.getSpreadsheetEditorManager();
73 const SpreadsheetEditorEvents = SpreadsheetEditor.SpreadsheetEditorManager.Events;
74
75 // Ensure the Spreadsheet Editor Manager is ready and set to editing mode
76 spreadsheetEditorManager.addEventListener(
77 SpreadsheetEditorEvents.SPREADSHEET_EDITOR_READY,
78 () => {
79 spreadsheetEditorManager.setEditMode(
80 SpreadsheetEditor.SpreadsheetEditorEditMode.EDITING
81 );
82 },
83 { once: true }
84 );
85
86 });
87
88 // UI section
89 //
90 // Create a container for all controls (label, dropdown, and buttons)
91 const controlsContainer = document.createElement('div');
92 controlsContainer.className = 'control-container';
93 controlsContainer.id = 'gallery-container';
94 element.insertBefore(controlsContainer, element.firstChild);
95
96 const filesArray = [
97 { name: sampleDocuments[0].displayName, thumbnail: '/showcase-demos/spreadsheet-editor/annual_financial_report.png', url: sampleDocuments[0].path },
98 { name: sampleDocuments[1].displayName, thumbnail: '/showcase-demos/spreadsheet-editor/invoice_template.png', url: sampleDocuments[1].path },
99 { name: sampleDocuments[2].displayName, thumbnail: '/showcase-demos/spreadsheet-editor/file_5000_rows.png', url: sampleDocuments[2].path }
100 ]
101
102 //
103 console.log('gallery-picker.js loaded, GalleryPicker:', window.GalleryPicker);
104 // Dynamically load gallery-picker.js if not already loaded
105 if (!window.GalleryPicker) {
106 const script = document.createElement('script');
107 script.src = '/showcase-demos/spreadsheet-editor/gallery-picker.js';
108 script.onload = function () {
109 console.log('Safe to use GalleryPicker here');
110 GalleryPicker.init('gallery-container', filesArray, (file) => {
111 console.log('Selected file:', file);
112 WebViewer.getInstance().UI.loadDocument(file.url, { filename: file.name, extension: file.extension });
113 });
114 console.log('GalleryPicker.init', GalleryPicker.init);
115
116 };
117 document.head.appendChild(script);
118 }
119
120 if (window.GalleryPicker) {
121 element.insertBefore(controlsContainer, element.firstChild);
122 }
123}
124
125loadSpreadsheetEditor();
126
127
128
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