forked from sheetjs/docs.sheetjs.com
388 lines
11 KiB
Markdown
388 lines
11 KiB
Markdown
|
---
|
||
|
title: Sheets at Native Speed with Lynx
|
||
|
sidebar_label: Lynx
|
||
|
description: Build data-intensive mobile apps with Lynx. Seamlessly integrate spreadsheets into your app using SheetJS. Securely process and generate Excel files in the field.
|
||
|
pagination_prev: demos/static/index
|
||
|
pagination_next: demos/desktop/index
|
||
|
sidebar_position: 7
|
||
|
sidebar_custom_props:
|
||
|
summary: Lynx + Native Rendering
|
||
|
---
|
||
|
|
||
|
import current from '/version.js';
|
||
|
import Tabs from '@theme/Tabs';
|
||
|
import TabItem from '@theme/TabItem';
|
||
|
import CodeBlock from '@theme/CodeBlock';
|
||
|
|
||
|
export const r = {style: {color:"red"}};
|
||
|
export const g = {style: {color:"green"}};
|
||
|
export const y = {style: {color:"gold"}};
|
||
|
export const gr = {style: {color:"gray"}};
|
||
|
|
||
|
[Lynx](https://lynxjs.org/) is a modern cross-platform framework. It builds iOS, Android
|
||
|
and Web apps that use JavaScript for describing layouts and events.
|
||
|
|
||
|
[SheetJS](https://sheetjs.com) is a JavaScript library for reading and writing
|
||
|
data from spreadsheets.
|
||
|
|
||
|
:::caution Lynx support is considered experimental.
|
||
|
|
||
|
Lynx is a great, fast, open-source alternative to React Native. Any issues should be reported to the Lynx project for
|
||
|
further diagnosis.
|
||
|
|
||
|
:::
|
||
|
|
||
|
This demo uses [ReactLynx](https://lynxjs.org/react) and SheetJS to process and generate
|
||
|
spreadsheets. We'll explore how to load SheetJS in ReactLynx app in a few ways:
|
||
|
|
||
|
- ["Fetching Remote Data"](#fetching-remote-data) uses the built-in `fetch` to download
|
||
|
and parse remote workbook files.
|
||
|
|
||
|
The "Fetching Remote Data" example creates an app that looks like the screenshots below:
|
||
|
|
||
|
<table><thead><tr>
|
||
|
<th><a href="#demo">iOS</a></th>
|
||
|
<th><a href="#demo">Android</a></th>
|
||
|
</tr></thead><tbody><tr><td>
|
||
|
|
||
|

|
||
|
|
||
|
</td><td>
|
||
|
|
||
|

|
||
|
|
||
|
</td></tr></tbody></table>
|
||
|
|
||
|
:::caution pass
|
||
|
**Before testing this demo, follow the official React Lynx Guide!**[^1]
|
||
|
|
||
|
Follow the instructions for iOS (requires macOS) and for Android. They will
|
||
|
cover installation and system configuration. You should be able to build and run
|
||
|
a sample app in the Android and the iOS (if applicable) simulators.
|
||
|
|
||
|
:::
|
||
|
|
||
|
|
||
|
## Integration Detail
|
||
|
|
||
|
The [SheetJS NodeJS Module](/docs/getting-started/installation/nodejs) can be
|
||
|
imported from any component or script in the app.
|
||
|
|
||
|
### Internal State
|
||
|
|
||
|
For simplicity, this demo uses an "Array of Arrays"[^2] as the internal state.
|
||
|
|
||
|
|
||
|
<table>
|
||
|
<thead><tr><th>Spreadsheet</th><th>Array of Arrays</th></tr></thead>
|
||
|
<tbody><tr><td>
|
||
|
|
||
|

|
||
|
|
||
|
</td><td>
|
||
|
|
||
|
```js
|
||
|
[
|
||
|
["Name", "Index"],
|
||
|
["Bill Clinton", 42],
|
||
|
["GeorgeW Bush", 43],
|
||
|
["Barack Obama", 44],
|
||
|
["Donald Trump", 45],
|
||
|
["Joseph Biden", 46]
|
||
|
]
|
||
|
```
|
||
|
|
||
|
</td></tr></tbody></table>
|
||
|
|
||
|
Each array represents a row in the table.
|
||
|
|
||
|
This demo also keeps track of the column widths as a single array of numbers.
|
||
|
The widths are used by the display component.
|
||
|
|
||
|
```tsx
|
||
|
const [data, setData] = useState<any[]>([
|
||
|
"SheetJS".split(""),
|
||
|
[5, 4, 3, 3, 7, 9, 5],
|
||
|
[8, 6, 7, 5, 3, 0, 9]
|
||
|
]);
|
||
|
const [widths, setWidths] = useState<number[]>(Array.from({ length: 7 }, () => 20));
|
||
|
```
|
||
|
|
||
|
#### Updating State
|
||
|
|
||
|
Starting from a SheetJS worksheet object, `sheet_to_json`[^3] with the `header`
|
||
|
option can generate an array of arrays:
|
||
|
|
||
|
```js
|
||
|
/* assuming `wb` is a SheetJS workbook */
|
||
|
function update_state(wb) {
|
||
|
/* convert first worksheet to AOA */
|
||
|
const wsname = wb.SheetNames[0];
|
||
|
const ws = wb.Sheets[wsname];
|
||
|
const data = utils.sheet_to_json(ws, {header:1});
|
||
|
|
||
|
/* update state */
|
||
|
setData(data);
|
||
|
|
||
|
/* update column widths */
|
||
|
setWidths(make_width(data));
|
||
|
}
|
||
|
```
|
||
|
|
||
|
_Calculating Column Widths_
|
||
|
|
||
|
Column widths can be calculated by walking each column and calculating the max
|
||
|
data width. Using the array of arrays:
|
||
|
|
||
|
```js
|
||
|
/* this function takes an array of arrays and generates widths */
|
||
|
function make_width(aoa) {
|
||
|
/* walk each row */
|
||
|
aoa.forEach((r) => {
|
||
|
/* walk each column */
|
||
|
r.forEach((c, C) => {
|
||
|
/* update column width based on the length of the cell contents */
|
||
|
res[C] = Math.max(res[C]||60, String(c).length * 10);
|
||
|
});
|
||
|
});
|
||
|
/* use a default value for columns with no data */
|
||
|
for(let C = 0; C < res.length; ++C) if(!res[C]) res[C] = 60;
|
||
|
return res;
|
||
|
}
|
||
|
```
|
||
|
|
||
|
### Displaying Data
|
||
|
|
||
|
The demo uses Lynx builtin element `<view/>` and `<text/>` to display the first worksheet.
|
||
|
|
||
|
The demo uses components similar to the example below:
|
||
|
|
||
|
```tsx
|
||
|
{/* Table container */}
|
||
|
<view className='Table'>
|
||
|
{/* Map through each row in the data array */}
|
||
|
{data.map((row, rowIndex) => (
|
||
|
<view key={`row-${rowIndex}`} className="Row">
|
||
|
{/* Map through each cell in the current row */}
|
||
|
{Array.isArray(row) && row.map((cell, cellIndex) => (
|
||
|
{/* Cell with dynamic width based on content */}
|
||
|
<view
|
||
|
key={`cell-${rowIndex}-${cellIndex}`} className="Cell"
|
||
|
style={{ width: `${widths[cellIndex]}px` }}>
|
||
|
{/* Display cell content as text */}
|
||
|
<text>{String(cell)}</text>
|
||
|
</view>
|
||
|
))}
|
||
|
</view>
|
||
|
))}
|
||
|
</view>
|
||
|
```
|
||
|
|
||
|
## Fetching Remote Data
|
||
|
|
||
|
This snippet downloads and parses https://docs.sheetjs.com/pres.xlsx:
|
||
|
|
||
|
```tsx
|
||
|
/* fetch data into an ArrayBuffer */
|
||
|
const ab = await (await fetch("https://docs.sheetjs.com/pres.xlsx")).arrayBuffer();
|
||
|
/* parse data */
|
||
|
const wb = XLSX.read(ab);
|
||
|
```
|
||
|
|
||
|
The `data.map()` approach allows direct rendering of the worksheet data in a tabular format, with each cell
|
||
|
width dynamically calculated based on content.
|
||
|
|
||
|
### Fetch Demo
|
||
|
|
||
|
:::note Tested Deployments
|
||
|
|
||
|
This demo was tested in the following environments:
|
||
|
|
||
|
**Simulators**
|
||
|
|
||
|
| OS | Device | Lynx | LynxExplorer | Dev Platform | Date |
|
||
|
|:-----------|:--------------------|:---------|:-------------|:-------------|:-----------|
|
||
|
| Android 35 | Pixel 3a | `0.8.3` | `3.2.0-rc.0` | `darwin-arm` | 2025-03-11 |
|
||
|
| iOS 18.3 | iPhone 16 Pro | `0.8.3` | `3.2.0-rc.0` | `darwin-arm` | 2025-03-11 |
|
||
|
|
||
|
:::
|
||
|
|
||
|
:::danger Real Devices
|
||
|
|
||
|
As of `2025-03-11`, there is no simple, standalone guide on how to build your Lynx app for real devices.
|
||
|
|
||
|
:::
|
||
|
|
||
|
:::caution
|
||
|
|
||
|
First install Lynx by following the Guide![^1]. Make sure you can run a basic test app on your
|
||
|
simulator before continuing!
|
||
|
|
||
|
:::
|
||
|
|
||
|
0) Install Lynx dependencies
|
||
|
|
||
|
1) Create project:
|
||
|
|
||
|
```bash
|
||
|
pnpm create rspeedy@0.8.3 -d SheetJSLynxFetch -t react-ts --tools biome
|
||
|
```
|
||
|
|
||
|
2) Install shared dependencies:
|
||
|
|
||
|
<CodeBlock language="bash">{`\
|
||
|
cd SheetJSLynxFetch
|
||
|
curl -o ./src/assets/SheetJS-logo.png https://docs.sheetjs.com/logo.png
|
||
|
pnpm install
|
||
|
pnpm i -S https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
|
||
|
</CodeBlock>
|
||
|
|
||
|
3) Download [`App.tsx`](pathname:///lynx/App.tsx) and replace:
|
||
|
|
||
|
```bash
|
||
|
curl -o ./src/App.tsx https://docs.sheetjs.com/lynx/App.tsx
|
||
|
```
|
||
|
|
||
|
4) Download [`App.css`](pathname://lynx/App.css) and replace:
|
||
|
|
||
|
```bash
|
||
|
curl -o ./src/App.css https://docs.sheetjs.com/lynx/App.css
|
||
|
```
|
||
|
<a id="step5"></a>
|
||
|
5) Start the development server, run:
|
||
|
|
||
|
```bash
|
||
|
pnpm run dev
|
||
|
```
|
||
|
|
||
|
6) Start the Android emulator:
|
||
|
|
||
|
<details open>
|
||
|
<summary><b>Details</b> (click to hide)</summary>
|
||
|
|
||
|
**Android Studio**
|
||
|
|
||
|
In Android Studio, click "More actions" > "Virtual Device Manager". Look for the
|
||
|
emulated device in the list and click the ▶ button to play.
|
||
|
|
||
|
**Command Line**
|
||
|
List the available emulators with `emulator -list-avds`
|
||
|
|
||
|
```
|
||
|
shjs@sheetjs SheetJSLynxFetch % emulator -list-avds
|
||
|
Medium_Phone_API_35
|
||
|
^^^^^^^^^^^^^^^^^^^--- emulator name
|
||
|
```
|
||
|
|
||
|
Emulator name should be passed to `emulator -avd`. In a previous test, the name was
|
||
|
`Medium_Phone_API_35` and the launch command was:
|
||
|
|
||
|
```bash
|
||
|
emulator -avd Medium_Phone_API_35
|
||
|
```
|
||
|
|
||
|
:::note pass
|
||
|
|
||
|
On macOS, `~/Library/Android/sdk/emulator/` is the typical location
|
||
|
for the `emulator` binary. If it cannot be found, add the folder to `PATH`:
|
||
|
|
||
|
```bash
|
||
|
export PATH="$PATH":~/Library/Android/sdk/emulator
|
||
|
emulator -avd Medium_Phone_API_35
|
||
|
```
|
||
|
|
||
|
:::
|
||
|
|
||
|
</details>
|
||
|
|
||
|
7) While the Android emulator is open, download LynxExplorer[^4] - (Is a sandbox for trying out Lynx quickly)
|
||
|
|
||
|
<Tabs groupId="lang">
|
||
|
<TabItem name="Android" value="Android">
|
||
|
1. Download the pre-build app from the [GitHub Release](https://github.com/lynx-family/lynx/releases/tag/3.2.0-rc.0) and
|
||
|
select the APK `LynxExplorer-noasan-release.apk`.
|
||
|
2. Drag and drop the APK `LynxExplorer-noasan-release.apk` in to your Android simulator.
|
||
|
</TabItem>
|
||
|
</Tabs>
|
||
|
|
||
|
---
|
||
|
8) From [step 5](#step5), you will see a QR code appear in the terminal with a hyperlink like this. Copy the HTTP link.
|
||
|
|
||
|

|
||
|
|
||
|
9) In the simulator, open the _LynxExplorer_ app. In the input field labeled **Enter Card URL**, paste the link and
|
||
|
click **Go**.
|
||
|
|
||
|
10) When opened, the app should look like the "Before" screenshot below. After tapping "Import data from a spreadsheet",
|
||
|
verify that the app shows new data:
|
||
|
|
||
|
<table><thead><tr>
|
||
|
<th>Before</th>
|
||
|
<th>After</th>
|
||
|
</tr></thead><tbody><tr><td>
|
||
|
|
||
|

|
||
|
|
||
|
</td><td>
|
||
|
|
||
|

|
||
|
|
||
|
</td></tr></tbody></table>
|
||
|
|
||
|
|
||
|
**iOS Testing**
|
||
|
|
||
|
:::danger pass
|
||
|
|
||
|
**iOS testing can only be performed on Apple hardware running macOS!**
|
||
|
|
||
|
Xcode and iOS simulators are not available on Windows or Linux.
|
||
|
|
||
|
:::
|
||
|
|
||
|
<Tabs groupId="lang">
|
||
|
<TabItem name="iOS Simulator" value="iOS Simulator">
|
||
|
1. Install Xcode open up the Mac App Store, search for [Xcode](https://apps.apple.com/us/app/xcode/id497799835), and
|
||
|
click Install (or Update if you have it already).
|
||
|
2. Download [`LynxExplorer-arm64.app.tar.gz`](https://github.com/lynx-family/lynx/releases/latest/download/LynxExplorer-arm64.app.tar.gz).
|
||
|
3. Then, extract the downloaded archive:
|
||
|
|
||
|
```bash
|
||
|
mkdir -p LynxExplorer-arm64.app/
|
||
|
tar -zxf LynxExplorer-arm64.app.tar.gz -C LynxExplorer-arm64.app/
|
||
|
```
|
||
|
|
||
|
4. Install LynxExplorer[^4] on simulator open Xcode, and choose Open Developer Tool from the Xcode menu. Click the
|
||
|
Simulator to launch one. Drag "LynxExplorer-arm64.app" into it.
|
||
|
|
||
|
</TabItem>
|
||
|
</Tabs>
|
||
|
|
||
|
11) From [step 5](#step5), you will see a QR code appear in the terminal with a hyperlink like this. Copy the HTTP link.
|
||
|
|
||
|

|
||
|
|
||
|
12) In the simulator, open the _LynxExplorer_ app. In the input field labeled **Enter Card URL**, paste the link and
|
||
|
click **Go**.
|
||
|
|
||
|
13) When opened, the app should look like the "Before" screenshot below. After tapping "Import data from a spreadsheet",
|
||
|
verify that the app shows new data:
|
||
|
|
||
|
<table><thead><tr>
|
||
|
<th>Before</th>
|
||
|
<th>After</th>
|
||
|
</tr></thead><tbody><tr><td>
|
||
|
|
||
|

|
||
|
|
||
|
</td><td>
|
||
|
|
||
|

|
||
|
|
||
|
</td></tr></tbody></table>
|
||
|
|
||
|
[^1]: Follow the ["Quick Start guide](https://lynxjs.org/guide/start/quick-start.html) and select the appropriate
|
||
|
"Lynx Explorer sandbox"
|
||
|
[^2]: See ["Array of Arrays" in the API reference](/docs/api/utilities/array#array-of-arrays)
|
||
|
[^3]: See ["Array Output" in "Utility Functions"](/docs/api/utilities/array#array-output)
|
||
|
[^4]: See ["LynxExplorer sandbox"](https://github.com/lynx-family/lynx/releases/tag/3.2.0-rc.0/)
|