> For the complete documentation index, see [llms.txt](https://docs.apryse.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.apryse.com/web/get-started/samples/showcase-demo-spreadsheet-editor.md).

# Spreadsheet Editor Showcase Demo Sample Code

Enable loading, viewing, and editing XLSX files directly in your browser.

{% hint style="info" %}
**Requirements**

*These packages are required to use these features in production. Trial keys have unlimited access to all features*

<a href="/web/get-started/readme.md" class="button primary">Web SDK</a><a href="https://apryse.com/capabilities#SpreadsheetEditor" class="button primary">Package: Spreadsheet Editor</a><a href="/web/ui-customization/web-component-vs-iframe.md#using-an-iframe" class="button primary">iFrame - WebViewer</a><a href="https://showcase.apryse.com/spreadsheet-editor" class="button primary">Live demo</a>
{% endhint %}

Easily enable loading, viewing, and editing XLSX files directly in your browser, without server-side dependencies or MS Office installations.

This demo lets you:

* Upload XLSX files directly to your browser
* View and edit XSLX files
* Process XLSX files within a custom app

Learn more about [Web SDK ](/web/get-started/readme.md)and [Spreadsheet Editor.](/web/spreadsheet-editor/spreadsheet-editor.md)

### **Implementation steps**

To add XLSX file viewing and editing capability with WebViewer:

Step 1: Follow [get started in your preferred web stack for WebViewer](/web/get-started/readme.md) Step 2: Implement [WebViewer using iFrame](/web/ui-customization/web-component-vs-iframe.md#using-an-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-license-key/apryse-license-key platform="WEB\_VIEWER" variant="compact" %}

{% tabs %}
{% tab title="index.js" %}

<pre class="language-js" data-line-numbers><code class="lang-js">// ES6 Compliant Syntax
// GitHub Copilot v1, Claude Sonnet 4 (Preview), 2025-09-15
// File: showcase-demos/spreadsheet-editor/index.js

import WebViewer from '@pdftron/webviewer';

const licenseKey = '<code class="expression">visitor.claims.wvKey || "YOUR_WEBVIEWER_LICENSE_KEY"</code>';

// Custom File class to hold file metadata (not to be confused with browser's File API)
class FileMetadata {
  constructor(options) {
    this.name = options.name;
    this.displayName = options.displayName;
    this.path = options.path;
    this.extension = options.extension;
    this.displayExtension = options.displayExtension;
    this.id = options.id;
  }
}

const files = {
  ANNUAL_FINANCIAL_REPORT: new FileMetadata({
    name: 'annual_financial_report.xlsx',
    displayName: 'Annual Financial Report (xlsx)',
    path: 'https://apryse.s3.us-west-1.amazonaws.com/public/files/samples/annual_financial_report.xlsx',
    extension: 'xlsx',
    id: 82,
  }),
  INVOICE_TEMPLATE: new FileMetadata({
    name: 'invoice_template.xlsx',
    displayName: 'Invoice Template (xlsx)',
    path: 'https://apryse.s3.us-west-1.amazonaws.com/public/files/samples/invoice_template.xlsx',
    extension: 'xlsx',
    displayExtension: 'xlsx',
    id: 48,
  }),
  XLSX_5000_ROWS: new FileMetadata({
    name: 'file_5000_rows.xlsx',
    displayName: 'File with 5000 rows (xlsx)',
    path: 'https://apryse.s3.us-west-1.amazonaws.com/public/files/samples/file_5000_rows.xlsx',
    extension: 'xlsx',
    id: 83,
  }),
}

const sampleDocuments = [
  files.ANNUAL_FINANCIAL_REPORT,
  files.INVOICE_TEMPLATE,
  files.XLSX_5000_ROWS,
];

const defaultFile = sampleDocuments[0].path;

// Function to initialize and load the Spreadsheet Editor

function loadSpreadsheetEditor() {
  const element = document.getElementById('viewer');
  if (!element) {
    console.error('Viewer div not found.');
    return;
  }

  WebViewer.Iframe({
    path: '/lib',
    licenseKey: licenseKey,
    initialDoc: defaultFile,
    enableFilePicker: true, // Enable file picker to open files. In WebViewer -> menu icon -> Open File
    initialMode: WebViewer.Modes.SPREADSHEET_EDITOR,
  }, element).then(instance => {

    const { documentViewer, SpreadsheetEditor } = instance.Core;
    const spreadsheetEditorManager = documentViewer.getSpreadsheetEditorManager();
    const SpreadsheetEditorEvents = SpreadsheetEditor.SpreadsheetEditorManager.Events;

    // Ensure the Spreadsheet Editor Manager is ready and set to editing mode      
    spreadsheetEditorManager.addEventListener(
      SpreadsheetEditorEvents.SPREADSHEET_EDITOR_READY,
      () => {
        spreadsheetEditorManager.setEditMode(
          SpreadsheetEditor.SpreadsheetEditorEditMode.EDITING
        );
      },
      { once: true }
    );

  });

  // UI section
  //
  // Create a container for all controls (label, dropdown, and buttons)
  const controlsContainer = document.createElement('div');
  controlsContainer.className = 'control-container';
  controlsContainer.id = 'gallery-container';
  element.insertBefore(controlsContainer, element.firstChild);

  const filesArray = [
    { name: sampleDocuments[0].displayName, thumbnail: '/showcase-demos/spreadsheet-editor/annual_financial_report.png', url: sampleDocuments[0].path },
    { name: sampleDocuments[1].displayName, thumbnail: '/showcase-demos/spreadsheet-editor/invoice_template.png', url: sampleDocuments[1].path },
    { name: sampleDocuments[2].displayName, thumbnail: '/showcase-demos/spreadsheet-editor/file_5000_rows.png', url: sampleDocuments[2].path }
  ]

  //
  console.log('gallery-picker.js loaded, GalleryPicker:', window.GalleryPicker);
  // Dynamically load gallery-picker.js if not already loaded
  if (!window.GalleryPicker) {
    const script = document.createElement('script');
    script.src = '/showcase-demos/spreadsheet-editor/gallery-picker.js';
    script.onload = function () {
      console.log('Safe to use GalleryPicker here');
      GalleryPicker.init('gallery-container', filesArray, (file) => {
        console.log('Selected file:', file);
        WebViewer.getInstance().UI.loadDocument(file.url, { filename: file.name, extension: file.extension });
      });
      console.log('GalleryPicker.init', GalleryPicker.init);

    };
    document.head.appendChild(script);
  }

  if (window.GalleryPicker) {
    element.insertBefore(controlsContainer, element.firstChild);
  }
}

loadSpreadsheetEditor();



</code></pre>

{% endtab %}

{% tab title="gallery-picker.js" %}
{% code title="gallery-picker.js" lineNumbers="true" %}

```js
// ES6 Compliant Syntax
// GitHub Copilot v1, Claude Sonnet 4 (Preview), 2025-09-15
// File: showcase-demos/spreadsheet-editor/gallery-picker.js
// GalleryPicker.js
// Static class for displaying a scrollable grid of thumbnails and handling file selection

class GalleryPicker {
  static files = [];
  static gridContainer = null;
  static externalCallback = null;

  // Initialize the gallery grid
  static async init(containerId, filesArray, onFileClick) {

    this.files = filesArray;
    this.externalCallback = onFileClick;
    this.gridContainer = document.getElementById(containerId);
    if (!this.gridContainer) {
      throw new Error(`Container with id '${containerId}' not found.`);
    }
    this.gridContainer.innerHTML = '';
    this.gridContainer.classList.add('gallery-grid');
    await this.renderGrid();
  }

  // Render the grid of thumbnails
  static async renderGrid() {
    for (const file of this.files) {
      const thumbDiv = document.createElement('div');
      thumbDiv.className = 'gallery-thumb';
      thumbDiv.title = file.name;
      thumbDiv.tabIndex = 0;
      thumbDiv.setAttribute('role', 'button');
      thumbDiv.setAttribute('aria-label', file.name);
      thumbDiv.onclick = () => this.handleClick(file);
      thumbDiv.onkeydown = (e) => {
        if (e.key === 'Enter' || e.key === ' ') {
          this.handleClick(file);
        }
      };

      const img = document.createElement('img');
      img.src = file.thumbnail;
      img.alt = file.name;
      img.className = 'gallery-thumb-img';
      thumbDiv.appendChild(img);

      const label = document.createElement('div');
      label.className = 'gallery-thumb-label';
      label.textContent = file.name;
      thumbDiv.appendChild(label);

      this.gridContainer.appendChild(thumbDiv);
    }
  }

  // Handle click event and pass info to external class
  static handleClick(file) {
    if (typeof this.externalCallback === 'function') {
      this.externalCallback(file);
    }
  }

  // Add a new file to the gallery
  static async addFile(fileObj) {
    this.files.push(fileObj);
    await this.renderGrid();
  }

  // Remove a file by name
  static async removeFile(fileName) {
    this.files = this.files.filter(f => f.name !== fileName);
    await this.renderGrid();
  }


  //Get images to local drive
  static async downloadThumbnails() {
    const images = [
      {
        url: 'https://apryse.s3.us-west-1.amazonaws.com/public/files/samples/thumbs/annual_financial_report.png',
        filename: 'annual_financial_report.png'
      },
      {
        url: 'https://apryse.s3.us-west-1.amazonaws.com/public/files/samples/thumbs/invoice_template.png',
        filename: 'invoice_template.png'
      },
      {
        url: 'https://apryse.s3.us-west-1.amazonaws.com/public/files/samples/thumbs/file_5000_rows.png',
        filename: 'file_5000_rows.png'
      }
    ];

    for (const img of images) {
      try {
        const response = await fetch(img.url);
        if (!response.ok) throw new Error(`Failed to fetch ${img.url}`);
        const blob = await response.blob();
        const link = document.createElement('a');
        link.href = URL.createObjectURL(blob);
        link.download = img.filename;
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
        URL.revokeObjectURL(link.href);
      } catch (err) {
        console.error('Error downloading', img.url, err);
      }
    }
  }
}

window.GalleryPicker = GalleryPicker;

// Usage sample:
//  const filesArray = [
//    { name: sampleDocuments[0].displayName, thumbnail: '/showcase-demos/spreadsheet-editor/annual_financial_report.png', url: sampleDocuments[0].path },
//    { name: sampleDocuments[1].displayName, thumbnail: '/showcase-demos/spreadsheet-editor/invoice_template.png', url: sampleDocuments[1].path },
//    { name: sampleDocuments[2].displayName, thumbnail: '/showcase-demos/spreadsheet-editor/file_5000_rows.png', url: sampleDocuments[2].path }
//  ] 
// GalleryPicker.init('gallery-container', filesArray, (file) => {
//    console.log('Selected file:', file);
//    WebViewer.getInstance().UI.loadDocument(file.url, { filename: file.name, extension: file.extension });



```

{% endcode %}
{% endtab %}

{% tab title="index.css" %}
{% code title="index.css" lineNumbers="true" %}

```css
/* Layout for viewer and its children */
#viewer {
    display: flex;
    margin: 0 0 0 0;
    width: 100%;
}

#playground-viewer{
    height: 90% !important;
}

/* Controls Container on the left */
.control-container {
    width: 150px;
    overflow-x: hidden;
    margin: auto;
    padding-bottom: 50px;
    box-sizing: border-box;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 1px;
    margin: 0;
    padding: 10px 5px 5px 10px;
    border-right: 1px solid #e0e0e0;
    background-color: rgba(112, 198, 255, 0.2);
    /* Light blue background for contrast */
}

.bottom-headers-wrapper {
    bottom: -11px !important;
}

#editorWrapper {
    width: 100% !important;
    height: 85% !important;
    padding-bottom: 50px !important;
    margin-bottom: 250px !important;
}

/* WebViewer container on the right */
#webviewer-1 {
    margin: 0;
    padding-bottom: 10px !important;


}

/* Responsive Design */
@media (max-width: 768px) {
    #viewer {
        flex-direction: column;
    }

    .control-container {
        width: 150px;
        height: auto;
        border-right: none;
        border-bottom: 1px solid #e0e0e0;
        align-items: center;
    }


}


#GenericFileTab{
    margin-bottom: 50px !important;
}

/* Gallery Picker Styles */
.gallery-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
    gap: 16px;
    overflow-y: auto;
    max-height: 100vh;
    padding: 10px;
}

.gallery-thumb {
    cursor: pointer;
    border: 1px solid #ccc;
    border-radius: 6px;
    background: #fff;
    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: 8px;
    transition: box-shadow 0.2s;
}

.gallery-thumb:hover,
.gallery-thumb:focus {
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
    border-color: #70c6ff;
}

.gallery-thumb-img {
    width: 100px;
    height: 100px;
    object-fit: cover;
    border-radius: 4px;
    margin-bottom: 6px;
}

.gallery-thumb-label {
    font-size: 13px;
    color: #333;
    text-align: center;
    margin-top: 2px;
    word-break: break-word;
}
```

{% endcode %}
{% endtab %}
{% endtabs %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.apryse.com/web/get-started/samples/showcase-demo-spreadsheet-editor.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
