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/) |