2023-01-05 03:57:48 +00:00
---
2023-10-19 05:23:55 +00:00
title: Data Munging in NeutralinoJS
2023-06-29 21:07:52 +00:00
sidebar_label: NeutralinoJS
description: Build data-intensive desktop apps using NeutralinoJS. Seamlessly integrate spreadsheets into your app using SheetJS. Quickly modernize Excel-powered business processes.
2023-01-05 23:33:49 +00:00
pagination_prev: demos/mobile/index
2024-03-18 08:24:41 +00:00
pagination_next: demos/cli/index
2023-01-05 03:57:48 +00:00
sidebar_position: 5
sidebar_custom_props:
summary: Webview + Lightweight Extensions
---
2023-04-29 11:21:37 +00:00
import current from '/version.js';
2023-01-05 03:57:48 +00:00
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
2023-04-29 11:21:37 +00:00
import CodeBlock from '@theme/CodeBlock';
2023-01-05 03:57:48 +00:00
2023-06-29 21:07:52 +00:00
[NeutralinoJS ](https://neutralino.js.org/ ) is a modern desktop app framework.
NeutralinoJS apps pair platform-native browser tools with a static web server.
[SheetJS ](https://sheetjs.com ) is a JavaScript library for reading and writing
data from spreadsheets.
This demo uses NeutralinoJS and SheetJS to pull data from a spreadsheet and
display the data in the app. We'll explore how to load SheetJS in a NeutralinoJS
app and use native features to read and write files.
2023-01-05 03:57:48 +00:00
2023-06-29 21:07:52 +00:00
The ["Complete Example" ](#complete-example ) section covers a complete desktop
app to read and write workbooks. The app will look like the screenshots below:
2023-02-12 08:15:17 +00:00
< table > < thead > < tr >
2023-12-02 08:39:35 +00:00
< th > < a href = "#complete-example" > Windows< / a > < / th >
2023-02-12 08:15:17 +00:00
< th > < a href = "#complete-example" > macOS< / a > < / th >
< th > < a href = "#complete-example" > Linux< / a > < / th >
< / tr > < / thead > < tbody > < tr > < td >
2023-12-02 08:39:35 +00:00
![Windows screenshot ](pathname:///neu/win10.png )
2023-03-19 06:02:55 +00:00
< / td > < td >
2023-02-12 08:15:17 +00:00
![macOS screenshot ](pathname:///neu/macos.png )
< / td > < td >
![Linux screenshot ](pathname:///neu/linux.png )
< / td > < / tr > < / tbody > < / table >
## Integration Details
2023-01-05 03:57:48 +00:00
2023-09-22 06:32:55 +00:00
The [SheetJS Standalone scripts ](/docs/getting-started/installation/standalone )
can be added to the `index.html` entry point.
2023-01-05 03:57:48 +00:00
2023-06-29 21:07:52 +00:00
For code running in the window, native methods must be explicitly enabled in the
NeutralinoJS `neutralino.conf.json` settings file[^1].
2023-01-05 03:57:48 +00:00
2023-06-29 21:07:52 +00:00
- `os.*` enables the open and save dialog methods.
- `filesystem.*` enables reading and writing file data.
2023-01-05 03:57:48 +00:00
2023-06-29 21:07:52 +00:00
The starter app enables `os.*` so typically one line must be added:
2023-01-05 03:57:48 +00:00
```json title="neutralino.config.json"
"nativeAllowList": [
"app.*",
"os.*",
// highlight-next-line
"filesystem.*",
"debug.log"
],
```
2023-06-29 21:07:52 +00:00
### Reading Files
There are three steps to reading files:
2023-01-05 03:57:48 +00:00
2023-06-29 21:07:52 +00:00
1) Show an open file dialog with `Neutralino.os.showOpenDialog` [^2]. This method
resolves to the selected path.
2023-01-05 03:57:48 +00:00
2023-06-29 21:07:52 +00:00
2) Read raw data from the file with `Neutralino.filesystem.readBinaryFile` [^3].
This method resolves to a standard `ArrayBuffer` .
2023-01-05 03:57:48 +00:00
2023-06-29 21:07:52 +00:00
3) Parse the data with the SheetJS `read` method[^4]. This method returns a
SheetJS workbook object.
2023-02-12 08:15:17 +00:00
2023-06-29 21:07:52 +00:00
The following code example defines a single function `openFile` that performs
all three steps and returns a SheetJS workbook object:
2023-02-12 08:15:17 +00:00
```js
const filters = [
2023-06-29 21:07:52 +00:00
{name: "Excel Binary Workbook", extensions: ["xls", "xlsb"]},
{name: "Excel Workbook", extensions: ["xls", "xlsx"]},
2023-02-12 08:15:17 +00:00
]
async function openFile() {
/* show open file dialog */
const [filename] = await Neutralino.os.showOpenDialog(
'Open a spreadsheet',
2023-06-29 21:07:52 +00:00
{ filters, multiSelections: false }
2023-02-12 08:15:17 +00:00
);
/* read data into an ArrayBuffer */
const ab = await Neutralino.filesystem.readBinaryFile(filename);
/* parse with SheetJS */
const wb = XLSX.read(ab);
return wb;
}
```
2023-06-29 21:07:52 +00:00
At this point, standard SheetJS utility functions[^5] can extract data from the
workbook object. The demo includes a button that calls `sheet_to_html` [^6] to
generate an HTML TABLE and add to the DOM:
```js
const open_button_callback = async() => {
const wb = await openFile();
/* get the first worksheet */
// highlight-start
const ws = wb.Sheets[wb.SheetNames[0]];
// highlight-end
/* get data from the first worksheet */
// highlight-start
const html = XLSX.utils.sheet_to_html(ws);
// highlight-end
/* display table */
document.getElementById('info').innerHTML = html;
};
```
2023-02-12 08:15:17 +00:00
### Writing Files
2023-06-29 21:07:52 +00:00
There are three steps to reading files:
1) Show a file dialog with `Neutralino.os.showSaveDialog` [^7]. This method
resolves to the selected path.
2) Write the data with the SheetJS `write` method[^8]. The output book type can
be inferred from the selected file path. Using the `buffer` output type[^9],
the method returns a `Uint8Array` object that plays nice with NeutralinoJS.
2) Write to file with `Neutralino.filesystem.writeBinaryFile` [^10].
The following code example defines a single function `saveFile` that performs
all three steps starting from a SheetJS workbook object:
2023-02-12 08:15:17 +00:00
```js
const filters = [
2023-06-29 21:07:52 +00:00
{name: "Excel Binary Workbook", extensions: ["xls", "xlsb"]},
{name: "Excel Workbook", extensions: ["xls", "xlsx"]},
2023-02-12 08:15:17 +00:00
]
async function saveFile(wb) {
/* show save file dialog */
const filename = await Neutralino.os.showSaveDialog(
'Save to file',
2023-06-29 21:07:52 +00:00
{ filters }
2023-02-12 08:15:17 +00:00
);
/* Generate workbook */
const bookType = filename.slice(filename.lastIndexOf(".") + 1);
const data = XLSX.write(wb, { bookType, type: "buffer" });
/* save data to file */
await Neutralino.filesystem.writeBinaryFile(filename, data);
}
```
2023-06-29 21:07:52 +00:00
The demo includes a button that calls `table_to_book` [^11] to generate a
workbook object from the HTML table:
```js
const save_button_callback = async() => {
/* get the table */
const tbl = document.getElementById('info').querySelector('table');
/* generate workbook from the table */
// highlight-start
const wb = XLSX.utils.table_to_book(tbl);
// highlight-end
await saveFile(wb);
}
```
2023-02-12 08:15:17 +00:00
## Complete Example
2023-12-02 08:39:35 +00:00
:::note Tested Deployments
2023-06-29 21:07:52 +00:00
This demo was tested in the following environments:
2023-02-12 08:15:17 +00:00
2024-03-16 16:04:18 +00:00
| OS and Version | Architecture | Server | Client | Date |
|:---------------|:-------------|:---------|:---------|:-----------|
| macOS 14.4 | `darwin-x64` | `5.0.0` | `5.0.1` | 2024-03-15 |
2024-05-26 07:50:55 +00:00
| macOS 14.5 | `darwin-arm` | `5.1.0` | `5.1.0` | 2024-05-25 |
2024-03-25 04:13:01 +00:00
| Windows 10 | `win10-x64` | `5.1.0` | `5.1.0` | 2024-03-24 |
2024-05-28 05:20:05 +00:00
| Windows 11 | `win11-arm` | `5.1.0` | `5.1.1` | 2024-05-28 |
2024-03-22 04:45:40 +00:00
| Linux (HoloOS) | `linux-x64` | `5.0.0` | `5.0.1` | 2024-03-21 |
2024-05-28 05:20:05 +00:00
| Linux (Debian) | `linux-arm` | `5.1.0` | `5.1.1` | 2024-05-28 |
On `win11-arm` , the Electron runner is a proper ARM64 binary but the binaries
generated by Electron Forge are x64. The x64 binaries run in Windows on ARM.
2023-02-12 08:15:17 +00:00
:::
2023-01-05 03:57:48 +00:00
The app core state will be the HTML table. Reading files will add the table to
the window. Writing files will parse the table into a spreadsheet.
2024-04-08 04:47:04 +00:00
< details >
< summary > < b > Installation Notes< / b > (click to show)< / summary >
2023-09-25 07:30:54 +00:00
NeutralinoJS uses `portable-file-dialogs` [^12] to show open and save dialogs. On
Linux, Zenity or KDialog are require.
The last Debian test was run on a system using LXDE. KDialog is supported but
must be explicitly installed:
```bash
sudo apt-get install kdialog
```
2024-03-22 04:45:40 +00:00
NeutralinoJS requires `libwekit2gtk` . On Arch Linux-based platforms including
the Steam Deck, `webkit2gtk` can be installed through the package manager:
```bash
sudo pacman -Syu webkit2gtk
```
2023-09-25 07:30:54 +00:00
< / details >
2023-01-05 03:57:48 +00:00
1) Create a new NeutralinoJS app:
```bash
npx @neutralinojs/neu create sheetjs-neu
cd sheetjs-neu
```
2023-09-22 06:32:55 +00:00
2) Download the SheetJS Standalone script and move to the `resources/js/`
subdirectory in the `sheetjs-neu` folder:
< ul >
< li > < a href = {`https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js`} > xlsx.full.min.js< / a > < / li >
< / ul >
2023-01-05 03:57:48 +00:00
2023-04-29 11:21:37 +00:00
< CodeBlock language = "bash" > {`\
curl -L -o resources/js/xlsx.full.min.js https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js`}
< / CodeBlock >
2023-01-05 03:57:48 +00:00
2023-06-29 21:07:52 +00:00
3) Add the highlighted line to `neutralino.config.json` in `nativeAllowList` :
2023-01-05 03:57:48 +00:00
2024-01-29 03:29:45 +00:00
```json title="neutralino.config.json (add highlighted line)"
2023-01-05 03:57:48 +00:00
"nativeAllowList": [
"app.*",
"os.*",
2023-02-13 04:07:25 +00:00
// highlight-start
2023-01-05 03:57:48 +00:00
"filesystem.*",
// highlight-end
"debug.log"
],
```
2023-08-28 22:40:53 +00:00
:::note pass
There may be multiple `nativeAllowList` blocks in the configuration file. The
line must be added to the first block.
:::
2023-09-22 06:32:55 +00:00
4) Replace the contents of `resources/index.html` with the following code:
2023-01-05 03:57:48 +00:00
```html title="resources/index.html"
2023-06-29 21:07:52 +00:00
<!DOCTYPE html>
< html >
< head >
< meta charset = "UTF-8" >
< title > SheetJS + NeutralinoJS< / title >
< link rel = "stylesheet" href = "styles.css" >
< / head >
2023-01-05 03:57:48 +00:00
< body >
< div id = "neutralinoapp" >
< h1 > SheetJS × NeutralinoJS< / h1 >
< button onclick = "importData()" > Import Data< / button >
< button onclick = "exportData()" > Export Data< / button >
< div id = "info" > < / div >
< / div >
< script src = "js/neutralino.js" > < / script >
<!-- Load the browser build and make XLSX available to main.js -->
< script src = "js/xlsx.full.min.js" > < / script >
< script src = "js/main.js" > < / script >
< / body >
2023-06-29 21:07:52 +00:00
< / html >
2023-01-05 03:57:48 +00:00
```
2023-09-22 06:32:55 +00:00
5) Append the following code to `resources/styles.css` to center the table:
2023-01-05 03:57:48 +00:00
2023-09-22 06:32:55 +00:00
```css title="resources/styles.css (add to end)"
2023-01-05 03:57:48 +00:00
#info {
width:100%;
text-align: unset;
}
table {
margin: 0 auto;
}
```
2023-09-22 06:32:55 +00:00
6) Print the version number in the `showInfo` method of `resources/js/main.js` :
2023-01-05 03:57:48 +00:00
2024-05-28 05:20:05 +00:00
```js title="resources/js/main.js (add highlighted lines)"
2023-06-29 21:07:52 +00:00
function showInfo() {
document.getElementById('info').innerHTML = `
2023-01-05 03:57:48 +00:00
${NL_APPID} is running on port ${NL_PORT} inside ${NL_OS}
< br / > < br / >
< span > server: v${NL_VERSION} . client: v${NL_CVERSION}< / span >
// highlight-start
< br / > < br / >
< span > SheetJS version ${XLSX.version}< / span >
// highlight-end
`;
2023-06-29 21:07:52 +00:00
}
2023-01-05 03:57:48 +00:00
```
2023-09-22 06:32:55 +00:00
7) Run the app:
2023-01-05 03:57:48 +00:00
```bash
npx @neutralinojs/neu run
```
2023-06-29 21:07:52 +00:00
< p > The app should print < code > SheetJS Version {current}< / code > < / p >
2023-01-05 03:57:48 +00:00
2023-09-22 06:32:55 +00:00
8) Add the following code to the bottom of `resources/js/main.js` :
2023-01-05 03:57:48 +00:00
2023-09-22 06:32:55 +00:00
```js title="resources/js/main.js (add to end)"
2023-01-05 03:57:48 +00:00
(async() => {
2024-04-26 04:16:13 +00:00
const ab = await (await fetch("https://docs.sheetjs.com/pres.numbers")).arrayBuffer();
2023-01-05 03:57:48 +00:00
const wb = XLSX.read(ab);
const ws = wb.Sheets[wb.SheetNames[0]];
document.getElementById('info').innerHTML = XLSX.utils.sheet_to_html(ws);
})();
```
2024-05-26 07:50:55 +00:00
9) Close the app. Run the app again:
```bash
npx @neutralinojs/neu run
```
2023-01-05 03:57:48 +00:00
When the app loads, a table should show in the main screen.
2023-09-22 06:32:55 +00:00
10) Add `importFile` and `exportFile` to the bottom of `resources/js/main.js` :
2023-01-05 03:57:48 +00:00
2023-09-22 06:32:55 +00:00
```js title="resources/js/main.js (add to end)"
2023-01-05 03:57:48 +00:00
async function importData() {
/* show open dialog */
const [filename] = await Neutralino.os.showOpenDialog('Open a spreadsheet');
/* read data */
const ab = await Neutralino.filesystem.readBinaryFile(filename);
const wb = XLSX.read(ab);
/* make table */
const ws = wb.Sheets[wb.SheetNames[0]];
document.getElementById('info').innerHTML = XLSX.utils.sheet_to_html(ws);
}
async function exportData() {
2023-06-29 21:07:52 +00:00
/* show save dialog */
2023-01-05 03:57:48 +00:00
const filename = await Neutralino.os.showSaveDialog('Save to file');
/* make workbook */
const tbl = document.getElementById('info').querySelector("table");
const wb = XLSX.utils.table_to_book(tbl);
/* make file */
const bookType = filename.slice(filename.lastIndexOf(".") + 1);
const data = XLSX.write(wb, { bookType, type: "buffer" });
await Neutralino.filesystem.writeBinaryFile(filename, data);
}
```
2024-05-26 07:50:55 +00:00
11) Close the app. Run the app again:
```bash
npx @neutralinojs/neu run
```
2023-01-05 03:57:48 +00:00
When the app loads, click the "Import File" button and select a spreadsheet to
2023-06-29 21:07:52 +00:00
see the contents.
2023-12-02 08:39:35 +00:00
:::info pass
If no dialog is displayed, see the ["Installation Notes" ](#complete-example ) for
more details. On Linux ARM64, KDialog or Zenity must be installed.
:::
2023-06-29 21:07:52 +00:00
Click "Export File" and enter `SheetJSNeu.xlsx` to write a new file.
:::caution pass
When saving the file, the actual file extension must be included. Attempting to
save as `SheetJSNeu` will not automatically add the `.xlsx` extension!
:::
2023-01-05 03:57:48 +00:00
2023-09-22 06:32:55 +00:00
12) Build production apps:
2023-01-05 03:57:48 +00:00
```bash
2023-06-29 21:07:52 +00:00
npx @neutralinojs/neu build
2023-01-05 03:57:48 +00:00
```
2024-05-26 07:50:55 +00:00
Platform-specific programs will be created in the `dist` folder:
2024-05-28 05:20:05 +00:00
| Platform | Path to binary |
|:-------------|:---------------------------------------------|
| `darwin-arm` | `./dist/sheetjs-neu/sheetjs-neu-mac_arm64` |
| `win11-arm` | `.\dist\sheetjs-neu\sheetjs-neu-win_x64.exe` |
| `linux-arm` | `.\dist\sheetjs-neu\sheetjs-neu-linux_arm64` |
2024-05-26 07:50:55 +00:00
Run the generated app and confirm that Presidential data is displayed.
2023-06-29 21:07:52 +00:00
[^1]: See [`nativeAllowList` ](https://neutralino.js.org/docs/configuration/neutralino.config.json#nativeallowlist-string ) in the NeutralinoJS documentation
[^2]: See [`os.showOpenDialog` ](https://neutralino.js.org/docs/api/os#osshowopendialogtitle-options ) in the NeutralinoJS documentation
[^3]: See [`filesystem.readBinaryFile` ](https://neutralino.js.org/docs/api/filesystem/#filesystemreadbinaryfilefilename ) in the NeutralinoJS documentation
[^4]: See [`read` in "Reading Files" ](/docs/api/parse-options )
[^5]: See ["Utility Functions" ](/docs/api/utilities/ )
[^6]: See ["HTML Table Output" in "Utility Functions" ](/docs/api/utilities/html#html-table-output )
[^7]: See [`os.showSaveDialog` ](https://neutralino.js.org/docs/api/os#osshowsavedialogtitle-options ) in the NeutralinoJS documentation
[^8]: See [`write` in "Writing Files" ](/docs/api/write-options )
[^9]: See ["Supported Output Formats" ](/docs/api/write-options#supported-output-formats )
[^10]: See [`filesystem.writeBinaryFile` ](https://neutralino.js.org/docs/api/filesystem/#filesystemwritebinaryfilefilename-data ) in the NeutralinoJS documentation
[^11]: See ["HTML Table Input" in "Utility Functions" ](/docs/api/utilities/html#html-table-input )
2023-09-25 07:30:54 +00:00
[^12]: See [the list of supported `portable-file-dialogs` ]
(https://github.com/samhocevar/portable-file-dialogs#status)