docs.sheetjs.com/docz/docs/03-demos/16-desktop.md

349 lines
9.7 KiB
Markdown
Raw Normal View History

2022-08-03 07:42:37 +00:00
---
sidebar_position: 16
title: Desktop Applications
---
Web technologies like JavaScript and HTML have been adapted to the traditional
app space. Typically these frameworks bundle a JavaScript engine as well as a
windowing framework. SheetJS is compatible with many toolkits.
## NW.js
2022-08-13 22:01:26 +00:00
The [Standalone scripts](../getting-started/installation/standalone) can be referenced in a
2022-08-03 07:42:37 +00:00
`SCRIPT` tag from the entry point HTML page.
This demo was tested against NW.js 0.66.0.
<details><summary><b>Complete Example</b> (click to show)</summary>
1) Create a `package.json` file that specifies the entry point:
```json title="package.json"
{
"name": "sheetjs-nwjs",
"author": "sheetjs",
"version": "0.0.0",
"main": "index.html",
"dependencies": {
"nw": "~0.66.0",
"xlsx": "https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz"
}
}
```
2) Download [`index.html`](pathname:///nwjs/index.html) into the same folder.
:::caution
Right-click the link and select "Save Link As...". Left-clicking the link will
try to load the page in your browser. The goal is to save the file contents.
:::
3) Run `npm install` to install dependencies
4) To verify the app works, run in the test environment:
```
npx nw .
```
The app will show and you should be able to verify reading and writing by using
the file input element to select a spreadsheet and clicking the export button.
5) To build a standalone app, run the builder:
```
npx -p nw-builder nwbuild --mode=build .
```
This will generate the standalone app in the `build\sheetjs-nwjs\` folder.
</details>
### Reading data
The standard HTML5 `FileReader` techniques from the browser apply to NW.js!
NW.js handles the OS minutiae for dragging files into app windows. The
2022-08-13 22:01:26 +00:00
[drag and drop snippet](../solutions/input#example-user-submissions) apply
2022-08-03 07:42:37 +00:00
to DIV elements on the page.
Similarly, 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);
```
### Writing data
File input elements with the attribute `nwsaveas` show UI for saving a file. The
standard trick is to generate a hidden file input DOM element and "click" it.
Since NW.js does not present a `writeFileSync` in the `fs` package, a manual
step is required:
```js
/* pre-build the hidden nwsaveas input element */
var input = document.createElement('input');
input.style.display = 'none';
input.setAttribute('nwsaveas', 'SheetJSNWDemo.xlsx');
input.setAttribute('type', 'file');
document.body.appendChild(input);
/* show a message if the save is canceled */
input.addEventListener('cancel',function(){ alert("Save was canceled!"); });
/* write to a file on the 'change' event */
input.addEventListener('change',function(e){
/* the `value` is the path that the program will write */
var filename = this.value;
/* use XLSX.write with type "buffer" to generate a buffer" */
/* highlight-next-line */
var wbout = XLSX.write(workbook, {type:'buffer', bookType:"xlsx"});
/* highlight-next-line */
fs.writeFile(filename, wbout, function(err) {
if(!err) return alert("Saved to " + filename);
alert("Error: " + (err.message || err));
});
});
input.click();
```
2022-08-04 03:00:20 +00:00
## Electron
2022-08-13 22:01:26 +00:00
The [NodeJS Module](../getting-started/installation/nodejs) can be imported from the main or
2022-08-04 03:00:20 +00:00
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
2022-08-13 22:01:26 +00:00
[`XLSX.writeFile`](../api/write-options) writes workbooks to the filesystem.
2022-08-04 03:00:20 +00:00
`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**
2022-08-13 22:01:26 +00:00
The [drag and drop snippet](../solutions/input#example-user-submissions)
2022-08-04 03:00:20 +00:00
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**
2022-08-13 22:01:26 +00:00
[`XLSX.readFile`](../api/parse-options) reads workbooks from the filesystem.
2022-08-04 03:00:20 +00:00
`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.
:::