This commit is contained in:
SheetJS 2022-08-03 23:00:20 -04:00
parent 5d9b9d9a21
commit f2e6365604
11 changed files with 425 additions and 17 deletions

@ -433,7 +433,7 @@ click the "Click to Export!" button to generate a file.
## RequireJS
[Standalone scripts](../../installation/standalone) comply with AND `define`
[Standalone scripts](../../installation/standalone) comply with AMD `define`
semantics, enabling use in RequireJS out of the box.
To enable use of the alias `xlsx`, the RequireJS config should set an alias in
@ -450,7 +450,7 @@ require.config({
});
// highlight-next-line
require(["xlsx"], function(XLSX) {
/* use XLSX here */
/* use XLSX here */
console.log(XLSX.version);
});
```

@ -126,3 +126,223 @@ input.addEventListener('change',function(e){
input.click();
```
## Electron
The [NodeJS Module](../../installation/nodejs) can be imported from the main or
the renderer thread.
Electron presents a `fs` module. The `require('xlsx')` call loads the CommonJS
module, so `XLSX.readFile` and `XLSX.writeFile` work in the renderer thread.
This demo was tested against Electron 19.0.5 on an Intel Mac (`darwin-x64`).
<details><summary><b>Complete Example</b> (click to show)</summary>
This demo includes a drag-and-drop box as well as a file input box, mirroring
the [SheetJS Data Preview Live Demo](http://oss.sheetjs.com/sheetjs/)
The core data in this demo is an editable HTML table. The readers build up the
table using `sheet_to_html` (with `editable:true` option) and the writers scrape
the table using `table_to_book`.
The demo project is wired for `electron-forge` to build the standalone binary.
1) Download the demo files:
- [`package.json`](pathname:///electron/package.json) : project structure
- [`main.js`](pathname:///electron/main.js) : entrypoint
- [`index.html`](pathname:///electron/index.html) : window page
- [`index.js`](pathname:///electron/index.js) : script loaded in render context
:::caution
Right-click each link and select "Save Link As...". Left-clicking a link will
try to load the page in your browser. The goal is to save the file contents.
:::
2) Run `npm install` to install dependencies.
3) To verify the app works, run in the test environment:
```bash
npx -y electron .
```
The app will show and you should be able to verify reading and writing by using
the relevant buttons to open files and clicking the export button.
4) To build a standalone app, run the builder:
```bash
npm run make
```
This will generate the standalone app in the `out\sheetjs-electron-...` folder.
For a recent Intel Mac, the path will be `out/sheetjs-electron-darwin-x64/`
</details>
### Writing Files
[`XLSX.writeFile`](../../api/write-options) writes workbooks to the filesystem.
`showSaveDialog` shows a Save As dialog and returns the selected file name:
```js
/* from the renderer thread */
const electron = require('@electron/remote');
/* this function will show the save dialog and try to write the workbook */
async function exportFile(workbook) {
/* show Save As dialog */
const result = await electron.dialog.showSaveDialog({
title: 'Save file as',
filters: [{
name: "Spreadsheets",
extensions: ["xlsx", "xls", "xlsb", /* ... other formats ... */]
}]
});
/* write file */
// highlight-next-line
XLSX.writeFile(workbook, result.filePath);
}
```
:::note
In older versions of Electron, `showSaveDialog` returned the path directly:
```js
var dialog = require('electron').remote.dialog;
function exportFile(workbook) {
var result = dialog.showSaveDialog();
XLSX.writeFile(workbook, result);
}
```
:::
### Reading Files
Electron offers 3 different ways to read files, two of which use Web APIs.
**File Input Element**
File input elements automatically map to standard Web APIs.
For example, assuming a file input element on the page:
```html
<input type="file" name="xlfile" id="xlf" />
```
The event handler would process the event as if it were a web event:
```js
async function handleFile(e) {
const file = e.target.files[0];
const data = await file.arrayBuffer();
/* data is an ArrayBuffer */
const workbook = XLSX.read(data);
/* DO SOMETHING WITH workbook HERE */
}
document.getElementById("xlf").addEventListener("change", handleFile, false);
```
**Drag and Drop**
The [drag and drop snippet](../../solutions/input#example-user-submissions)
applies to DIV elements on the page.
For example, assuming a DIV on the page:
```html
<div id="drop">Drop a spreadsheet file here to see sheet data</div>
```
The event handler would process the event as if it were a web event:
```js
async function handleDrop(e) {
e.stopPropagation();
e.preventDefault();
const file = e.dataTransfer.files[0];
const data = await file.arrayBuffer();
/* data is an ArrayBuffer */
const workbook = XLSX.read(data);
/* DO SOMETHING WITH workbook HERE */
}
document.getElementById("drop").addEventListener("drop", handleDrop, false);
```
**Electron API**
[`XLSX.readFile`](../../api/parse-options) reads workbooks from the filesystem.
`showOpenDialog` shows a Save As dialog and returns the selected file name.
Unlike the Web APIs, the `showOpenDialog` flow can be initiated by app code:
```js
/* from the renderer thread */
const electron = require('@electron/remote');
/* this function will show the open dialog and try to parse the workbook */
async function importFile() {
/* show Save As dialog */
const result = await electron.dialog.showOpenDialog({
title: 'Select a file',
filters: [{
name: "Spreadsheets",
extensions: ["xlsx", "xls", "xlsb", /* ... other formats ... */]
}]
});
/* result.filePaths is an array of selected files */
if(result.filePaths.length == 0) throw new Error("No file was selected!");
// highlight-next-line
return XLSX.readFile(result.filePaths[0]);
}
```
:::note
In older versions of Electron, `showOpenDialog` returned the path directly:
```js
var dialog = require('electron').remote.dialog;
function importFile(workbook) {
var result = dialog.showOpenDialog({ properties: ['openFile'] });
return XLSX.readFile(result[0]);
}
```
:::
### Electron Breaking Changes
The first version of this demo used Electron 1.7.5. The current demo includes
the required changes for Electron 19.0.5.
There are no Electron-specific workarounds in the library, but Electron broke
backwards compatibility multiple times. A summary of changes is noted below.
:::caution
Electron 6.x changed the `dialog` API. Methods like `showSaveDialog` originally
returned an array of strings, but now returns a `Promise`. This change was not
documented. [Electron issue](https://github.com/electron/electron/issues/24438)
Electron 9.0.0 and later require the preference `nodeIntegration: true` in order
to `require('xlsx')` in the renderer process.
Electron 12.0.0 and later also require `worldSafeExecuteJavascript: true` and
`contextIsolation: true`.
Electron 14+ must use `@electron/remote` instead of `remote`. An `initialize`
call is required to enable DevTools in the window.
:::

@ -37,7 +37,7 @@ The demo projects include small runnable examples and short explainers.
- [`Command-Line Tools`](./cli)
- [`NodeJS Server-Side Processing`](https://github.com/SheetJS/SheetJS/tree/master/demos/server/)
- [`Electron`](https://github.com/SheetJS/SheetJS/tree/master/demos/electron/)
- [`Electron`](./desktop#electron)
- [`NW.js`](./desktop#nwjs)
- [`Chrome / Chromium Extension`](./chromium)
- [`Google Sheets API`](./gsheet)
@ -63,7 +63,6 @@ The demo projects include small runnable examples and short explainers.
- [`snowpack`](./bundler#snowpack)
- [`swc`](./bundler#swc)
- [`systemjs`](https://github.com/SheetJS/SheetJS/tree/master/demos/systemjs/)
- [`typescript`](https://github.com/SheetJS/SheetJS/tree/master/demos/typescript/)
- [`vite`](./bundler#vite)
- [`webpack 2.x`](https://github.com/SheetJS/SheetJS/tree/master/demos/webpack/)
- [`wmr`](./bundler#wmr)

@ -90,7 +90,7 @@ var XLSX = require("xlsx");
var workbook = XLSX.readFile(path);
```
Electron APIs have changed over time. The [`electron` demo](https://github.com/SheetJS/SheetJS/tree/master/demos/electron/)
Electron APIs have changed over time. The [`electron` demo](../getting-started/demos/desktop#electron)
shows a complete example and details the required version-specific settings.
</TabItem>

@ -235,7 +235,7 @@ var XLSX = require("xlsx");
XLSX.writeFile(workbook, "out.xlsb");
```
Electron APIs have changed over time. The [`electron` demo](https://github.com/SheetJS/SheetJS/tree/master/demos/electron/)
Electron APIs have changed over time. The [`electron` demo](../getting-started/demos/desktop#electron)
shows a complete example and details the required version-specific settings.
</TabItem>

@ -80,8 +80,8 @@ function Table2XLSX(props) {
return (<>
<table id="Table2XLSX"><tbody>
<tr><td colSpan="3">SheetJS Table Export</td></tr>
<tr><td>Author</td><td>ID</td><td>Note</td></tr>
<tr><td>SheetJS</td><td>7262</td><td>Hi!</td></tr>
<tr><td>Author</td><td>ID</td><td>你好!</td></tr>
<tr><td>SheetJS</td><td>7262</td><td>வணக்கம்!</td></tr>
<tr><td colSpan="3">
<a href="//sheetjs.com">Powered by SheetJS</a>
</td></tr>

@ -14,20 +14,20 @@
"write-heading-ids": "docusaurus write-heading-ids"
},
"dependencies": {
"@cmfcmf/docusaurus-search-local": "^0.10.0",
"@docusaurus/core": "2.0.0-beta.20",
"@docusaurus/preset-classic": "2.0.0-beta.20",
"@docusaurus/theme-common": "2.0.0-beta.20",
"@docusaurus/theme-live-codeblock": "2.0.0-beta.20",
"@mdx-js/react": "1.6.22",
"clsx": "1.1.1",
"prism-react-renderer": "1.3.1",
"@cmfcmf/docusaurus-search-local": "0.11.0",
"@docusaurus/core": "2.0.1",
"@docusaurus/preset-classic": "2.0.1",
"@docusaurus/theme-common": "2.0.1",
"@docusaurus/theme-live-codeblock": "2.0.1",
"@mdx-js/react": "2.1.2",
"clsx": "1.2.1",
"prism-react-renderer": "1.3.5",
"react": "17.0.2",
"react-dom": "17.0.2",
"xlsx": "https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "2.0.0-beta.20"
"@docusaurus/module-type-aliases": "2.0.1"
},
"browserslist": {
"production": [

@ -0,0 +1,36 @@
<!DOCTYPE html>
<!-- sheetjs (C) 2013-present SheetJS http://sheetjs.com -->
<!-- vim: set ts=2: -->
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta http-equiv="Content-Security-Policy" content="script-src 'self' https:">
<title>SheetJS Electron Demo</title>
<style>
#drop{
border:2px dashed #bbb;
-moz-border-radius:5px;
-webkit-border-radius:5px;
border-radius:5px;
padding:25px;
text-align:center;
font:20pt bold,"Vollkorn";color:#bbb
}
a { text-decoration: none }
</style>
</head>
<body>
<pre>
<b><a href="https://sheetjs.com">SheetJS Electron Demo</a></b>
<br />
<button id="readBtn">Click here to select a file from your computer</button><br />
<div id="drop">Drop a spreadsheet file here to see sheet data</div>
<input type="file" name="xlfile" id="readIn" /> ... or click here to select a file
</pre>
<p><input type="submit" value="Export Data!" id="exportBtn" disabled="true"></p>
<div id="htmlout"></div>
<br />
<script src="index.js"></script>
</body>
</html>

@ -0,0 +1,73 @@
/* sheetjs (C) 2013-present SheetJS -- https://sheetjs.com */
const XLSX = require('xlsx');
const electron = require('@electron/remote');
/* list of supported extensions */
const EXTENSIONS = "xls|xlsx|xlsm|xlsb|xml|csv|txt|dif|sylk|slk|prn|ods|fods|htm|html|numbers".split("|");
/* write file with Electron API */
async function exportFile() {
const HTMLOUT = document.getElementById('htmlout');
const wb = XLSX.utils.table_to_book(HTMLOUT.getElementsByTagName("TABLE")[0]);
const o = await electron.dialog.showSaveDialog({
title: 'Save file as',
filters: [{
name: "Spreadsheets",
extensions: EXTENSIONS
}]
});
XLSX.writeFile(wb, o.filePath);
electron.dialog.showMessageBox({ message: "Exported data to " + o.filePath, buttons: ["OK"] });
}
document.getElementById('exportBtn').addEventListener('click', exportFile, false);
/* common handler to create HTML tables on the page */
function process_wb(wb) {
const HTMLOUT = document.getElementById('htmlout');
const XPORT = document.getElementById('exportBtn');
XPORT.disabled = false;
HTMLOUT.innerHTML = "";
wb.SheetNames.forEach(function(sheetName) {
const htmlstr = XLSX.utils.sheet_to_html(wb.Sheets[sheetName],{editable:true});
HTMLOUT.innerHTML += htmlstr;
});
}
/* read file with Electron API */
async function handleReadBtn() {
const o = await electron.dialog.showOpenDialog({
title: 'Select a file',
filters: [{
name: "Spreadsheets",
extensions: EXTENSIONS
}],
properties: ['openFile']
});
if(o.filePaths.length == 0) throw new Error("No file was selected!");
process_wb(XLSX.readFile(o.filePaths[0]));
}
document.getElementById('readBtn').addEventListener('click', handleReadBtn, false);
/* read file with Web APIs */
async function readFile(files) {
const f = files[0];
const data = await f.arrayBuffer();
process_wb(XLSX.read(data));
}
// file input element
document.getElementById('readIn').addEventListener('change', (e) => { readFile(e.target.files); }, false);
// drag and drop
const drop = document.getElementById('drop');
drop.addEventListener('drop', (e) => {
e.stopPropagation(); e.preventDefault();
readFile(e.dataTransfer.files);
}, false);
const handleDrag = (e) => {
e.stopPropagation(); e.preventDefault();
e.dataTransfer.dropEffect = 'copy';
};
drop.addEventListener('dragenter', handleDrag, false);
drop.addEventListener('dragover', handleDrag, false);

@ -0,0 +1,29 @@
/* sheetjs (C) 2013-present SheetJS -- http://sheetjs.com */
var electron = require('electron');
var XLSX = require('xlsx');
var app = electron.app;
require('@electron/remote/main').initialize(); // required for Electron 14+
var win = null;
function createWindow() {
if (win) return;
win = new electron.BrowserWindow({
width: 800, height: 600,
webPreferences: {
worldSafeExecuteJavaScript: true, // required for Electron 12+
contextIsolation: false, // required for Electron 12+
nodeIntegration: true,
enableRemoteModule: true
}
});
win.loadURL("file://" + __dirname + "/index.html");
require('@electron/remote/main').enable(win.webContents); // required for Electron 14+
win.webContents.openDevTools();
win.on('closed', function () { win = null; });
}
if (app.setAboutPanelOptions) app.setAboutPanelOptions({ applicationName: 'sheetjs-electron', applicationVersion: "XLSX " + XLSX.version, copyright: "(C) 2017-present SheetJS LLC" });
app.on('open-file', function () { console.log(arguments); });
app.on('ready', createWindow);
app.on('activate', createWindow);
app.on('window-all-closed', function () { if (process.platform !== 'darwin') app.quit(); });

@ -0,0 +1,51 @@
{
"name": "sheetjs-electron",
"author": "sheetjs",
"version": "0.0.0",
"main": "main.js",
"dependencies": {
"@electron/remote": "2.0.8",
"electron-squirrel-startup": "^1.0.0",
"xlsx": "https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz"
},
"scripts": {
"start": "electron-forge start",
"package": "electron-forge package",
"make": "electron-forge make"
},
"devDependencies": {
"@electron-forge/cli": "^6.0.0-beta.64",
"@electron-forge/maker-deb": "^6.0.0-beta.64",
"@electron-forge/maker-rpm": "^6.0.0-beta.64",
"@electron-forge/maker-squirrel": "^6.0.0-beta.64",
"@electron-forge/maker-zip": "^6.0.0-beta.64",
"electron": "19.0.5"
},
"config": {
"forge": {
"packagerConfig": {},
"makers": [
{
"name": "@electron-forge/maker-squirrel",
"config": {
"name": "sheetjs_electron"
}
},
{
"name": "@electron-forge/maker-zip",
"platforms": [
"darwin"
]
},
{
"name": "@electron-forge/maker-deb",
"config": {}
},
{
"name": "@electron-forge/maker-rpm",
"config": {}
}
]
}
}
}