forked from sheetjs/docs.sheetjs.com
		
	rnm
This commit is contained in:
		
							parent
							
								
									be15eb3620
								
							
						
					
					
						commit
						4dd53501ff
					
				| @ -36,8 +36,8 @@ new versions are released! | ||||
| 
 | ||||
| ## NetSuite | ||||
| 
 | ||||
| After downloading the script, it can be referenced directly in `define` calls | ||||
| in SuiteScripts: | ||||
| After uploading the script to the File Cabinet, it can be referenced directly in | ||||
| `define` calls in SuiteScripts: | ||||
| 
 | ||||
| ```js | ||||
| define(['N/file', './xlsx.full.min'], function(file, XLSX) { | ||||
| @ -45,8 +45,8 @@ define(['N/file', './xlsx.full.min'], function(file, XLSX) { | ||||
| }) | ||||
| ``` | ||||
| 
 | ||||
| As explained in the [NetSuite demo](/docs/demos/cloud/netsuite), module | ||||
| aliases are created in config files referenced via `@NAmdConfig` comments. | ||||
| As explained in the [NetSuite demo](/docs/demos/cloud/netsuite), module aliases | ||||
| can be created in config files referenced via `@NAmdConfig` comments. | ||||
| 
 | ||||
| ## SAP UI5 | ||||
| 
 | ||||
|  | ||||
| @ -54,13 +54,13 @@ module.exports = { | ||||
| NextJS collects telemetry by default. The `telemetry` subcommand can disable it: | ||||
| 
 | ||||
| ```js | ||||
| npx next@13.4.4 telemetry disable | ||||
| npx next@13.4.12 telemetry disable | ||||
| ``` | ||||
| 
 | ||||
| The setting can be verified by running | ||||
| 
 | ||||
| ```js | ||||
| npx next@13.4.4 telemetry status | ||||
| npx next@13.4.12 telemetry status | ||||
| ``` | ||||
| 
 | ||||
| ::: | ||||
| @ -69,10 +69,11 @@ npx next@13.4.4 telemetry status | ||||
| 
 | ||||
| The following deployments were tested: | ||||
| 
 | ||||
| | NextJS | Date       | | ||||
| |:-------|:-----------| | ||||
| | 13.1.1 | 2023-01-14 | | ||||
| | 13.4.4 | 2023-05-26 | | ||||
| | NextJS  | NodeJS    | Date       | | ||||
| |:--------|:----------|:-----------| | ||||
| | 11.1.4  | `16.20.1` | 2023-07-23 | | ||||
| | 12.3.4  | `18.17.0` | 2023-07-23 | | ||||
| | 13.4.12 | `18.17.0` | 2023-07-23 | | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| @ -503,6 +504,9 @@ This demo showcases the following SheetJS + NextJS flows: | ||||
| | `/sheets/[id]`        | asset module | `getStaticPaths`     | `sheet_to_html` | | ||||
| | `/getServerSideProps` | lifecycle    | `getServerSideProps` | `sheet_to_html` | | ||||
| 
 | ||||
| The commands in this demo use `next@13.4.12`. Other versions were tested by | ||||
| replacing the version number in the relevant commands. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| ### Initial Setup | ||||
| @ -510,13 +514,13 @@ This demo showcases the following SheetJS + NextJS flows: | ||||
| 0) Disable NextJS telemetry: | ||||
| 
 | ||||
| ```js | ||||
| npx next@13.4.4 telemetry disable | ||||
| npx next@13.4.12 telemetry disable | ||||
| ``` | ||||
| 
 | ||||
| Confirm it is disabled by running | ||||
| 
 | ||||
| ```js | ||||
| npx next@13.4.4 telemetry status | ||||
| npx next@13.4.12 telemetry status | ||||
| ``` | ||||
| 
 | ||||
| 1) Set up folder structure.  At the end, a `pages` folder with a `sheets` | ||||
| @ -538,7 +542,7 @@ curl -LO https://docs.sheetjs.com/next/sheetjs.xlsx | ||||
| 3) Install dependencies: | ||||
| 
 | ||||
| <CodeBlock language="bash">{`\ | ||||
| npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz next@13.4.4`} | ||||
| npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz next@13.4.12`} | ||||
| </CodeBlock> | ||||
| 
 | ||||
| 4) Download NextJS config scripts and place in the root folder: | ||||
| @ -596,7 +600,7 @@ cd ../.. | ||||
| 6) Test the deployment: | ||||
| 
 | ||||
| ```bash | ||||
| npx next@13.4.4 | ||||
| npx next@13.4.12 | ||||
| ``` | ||||
| 
 | ||||
| Open a web browser and access: | ||||
| @ -622,20 +626,20 @@ After saving the file, the website should refresh with the new row. | ||||
| 8) Stop the server and run a production build: | ||||
| 
 | ||||
| ```bash | ||||
| npx next@13.4.4 build | ||||
| npx next@13.4.12 build | ||||
| ``` | ||||
| 
 | ||||
| The final output will show a list of the routes and types: | ||||
| 
 | ||||
| ``` | ||||
| Route (pages)                              Size     First Load JS | ||||
| ┌ ○ /                                      563 B          74.4 kB | ||||
| ├   /_app                                  0 B            73.9 kB | ||||
| ├ ○ /404                                   182 B          74.1 kB | ||||
| ├ λ /getServerSideProps                    522 B          74.4 kB | ||||
| ├ ● /getStaticPaths                        2.89 kB        76.8 kB | ||||
| ├ ● /getStaticProps                        586 B          74.5 kB | ||||
| └ ● /sheets/[id]                           522 B          74.4 kB | ||||
| ┌ ○ /                                      563 B          75.3 kB | ||||
| ├   /_app                                  0 B            74.8 kB | ||||
| ├ ○ /404                                   182 B            75 kB | ||||
| ├ λ /getServerSideProps                    522 B          75.3 kB | ||||
| ├ ● /getStaticPaths                        2.91 kB        77.7 kB | ||||
| ├ ● /getStaticProps                        586 B          75.4 kB | ||||
| └ ● /sheets/[id] (303 ms)                  522 B          75.3 kB | ||||
|     ├ /sheets/0 | ||||
|     └ /sheets/1 | ||||
| ``` | ||||
| @ -647,7 +651,7 @@ worksheets in the file.  `/getServerSideProps` is server-rendered. | ||||
| 9) Try to build a static site: | ||||
| 
 | ||||
| ```bash | ||||
| npx next@13.4.4 export | ||||
| npx next@13.4.12 export | ||||
| ``` | ||||
| 
 | ||||
| :::note The static export will fail! | ||||
| @ -663,19 +667,19 @@ is still server-rendered. | ||||
| 
 | ||||
| ```bash | ||||
| rm -f pages/getServerSideProps.js | ||||
| npx next@13.4.4 build | ||||
| npx next@13.4.12 build | ||||
| ``` | ||||
| 
 | ||||
| Inspecting the output, there should be no lines with the `λ` symbol: | ||||
| 
 | ||||
| ``` | ||||
| Route (pages)                              Size     First Load JS | ||||
| ┌ ○ /                                      563 B          74.4 kB | ||||
| ├   /_app                                  0 B            73.9 kB | ||||
| ├ ○ /404                                   182 B          74.1 kB | ||||
| ├ ● /getStaticPaths                        2.89 kB        76.8 kB | ||||
| ├ ● /getStaticProps                        586 B          74.5 kB | ||||
| └ ● /sheets/[id]                           522 B          74.4 kB | ||||
| ┌ ○ /                                      563 B          75.3 kB | ||||
| ├   /_app                                  0 B            74.8 kB | ||||
| ├ ○ /404                                   182 B            75 kB | ||||
| ├ ● /getStaticPaths                        2.91 kB        77.7 kB | ||||
| ├ ● /getStaticProps                        586 B          75.4 kB | ||||
| └ ● /sheets/[id]                           522 B          75.3 kB | ||||
|     ├ /sheets/0 | ||||
|     └ /sheets/1 | ||||
| ``` | ||||
| @ -683,7 +687,7 @@ Route (pages)                              Size     First Load JS | ||||
| 11) Generate the static site: | ||||
| 
 | ||||
| ```bash | ||||
| npx next@13.4.4 export | ||||
| npx next@13.4.12 export | ||||
| ``` | ||||
| 
 | ||||
| The static site will be written to the `out` subfolder | ||||
| @ -694,5 +698,6 @@ The static site will be written to the `out` subfolder | ||||
| npx http-server out | ||||
| ``` | ||||
| 
 | ||||
| The command will start a local HTTP server for testing the generated site. Note | ||||
| that `/getServerSideProps` will 404 since the page was removed. | ||||
| The command will start a local HTTP server at `http://localhost:8080/` for | ||||
| testing the generated site. Note that `/getServerSideProps` will 404 since the | ||||
| page was removed. | ||||
|  | ||||
| @ -20,7 +20,6 @@ The following deployments were tested: | ||||
| | Nuxt Content | Nuxt     | Date       | | ||||
| |:-------------|:---------|:-----------| | ||||
| | `1.15.1`     | `2.16.3` | 2023-06-01 | | ||||
| | `2.3.0`      | `3.0.0`  | 2023-01-19 | | ||||
| | `2.6.0`      | `3.5.2`  | 2023-06-01 | | ||||
| 
 | ||||
| ::: | ||||
|  | ||||
| @ -1,5 +1,7 @@ | ||||
| --- | ||||
| title: React Native | ||||
| sidebar_label: React Native | ||||
| description: Build data-intensive desktop apps with React Native. Seamlessly integrate spreadsheets into your app using SheetJS. Securely process and generate Excel files at the desk. | ||||
| pagination_prev: demos/mobile/index | ||||
| pagination_next: demos/data/index | ||||
| sidebar_position: 6 | ||||
| @ -7,26 +9,25 @@ sidebar_custom_props: | ||||
|   summary: Native Components with React | ||||
| --- | ||||
| 
 | ||||
| # Sheets on the Desktop with React Native | ||||
| 
 | ||||
| import current from '/version.js'; | ||||
| import Tabs from '@theme/Tabs'; | ||||
| import TabItem from '@theme/TabItem'; | ||||
| import CodeBlock from '@theme/CodeBlock'; | ||||
| 
 | ||||
| :::note | ||||
| 
 | ||||
| This section covers React Native for desktop applications.  For iOS and Android | ||||
| applications, [check the mobile demo](/docs/demos/mobile/reactnative) | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| React Native for Windows + macOS is a backend for React Native that supports | ||||
| React Native for Windows + macOS[^1] is a backend for React Native that supports | ||||
| native apps.  The Windows backend builds apps for use on Windows 10 / 11, Xbox, | ||||
| and other supported platforms.  The macOS backend supports macOS 10.14 SDK | ||||
| 
 | ||||
| The [NodeJS Module](/docs/getting-started/installation/nodejs) can be imported | ||||
| from the main app script.  File operations must be written in native code. | ||||
| [SheetJS](https://sheetjs.com) is a JavaScript library for reading and writing | ||||
| data from spreadsheets. | ||||
| 
 | ||||
| The "Complete Example" creates an app that looks like the screenshots below: | ||||
| This demo uses React Native for Windows + macOS and SheetJS to process | ||||
| spreadsheets. We'll explore how to load SheetJS in a React Native deskktop app | ||||
| and create native modules for selecting and reading files from the computer. | ||||
| 
 | ||||
| The Windows and macOS demos create apps that look like the screenshots below: | ||||
| 
 | ||||
| <table><thead><tr> | ||||
|   <th><a href="#windows-demo">Win10</a></th> | ||||
| @ -41,8 +42,6 @@ The "Complete Example" creates an app that looks like the screenshots below: | ||||
| 
 | ||||
| </td></tr></tbody></table> | ||||
| 
 | ||||
| <details><summary><b>Tested Environments</b> (click to show)</summary> | ||||
| 
 | ||||
| :::note | ||||
| 
 | ||||
| This demo was tested in the following environments: | ||||
| @ -51,13 +50,169 @@ This demo was tested in the following environments: | ||||
| |:---------------|:-----|:------------|:-----------| | ||||
| | Windows 10     | x64  | `v0.70.10`  | 2023-01-04 | | ||||
| | Windows 11     | x64  | `v0.71.11`  | 2023-05-11 | | ||||
| | MacOS 12.4     | x64  | `v0.64.30`  | 2023-01-04 | | ||||
| | MacOS 12.6     | x64  | `v0.71.26`  | 2023-07-23 | | ||||
| | MacOS 13.4     | arm  | `v0.71.18`  | 2023-07-06 | | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| :::info pass | ||||
| 
 | ||||
| This section covers React Native for desktop applications.  For iOS and Android | ||||
| applications, [check the mobile demo](/docs/demos/mobile/reactnative) | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| ## Integration Details | ||||
| 
 | ||||
| The [SheetJS NodeJS Module](/docs/getting-started/installation/nodejs) can be | ||||
| imported from the main `App.js` entrypoint or any script in the project. | ||||
| 
 | ||||
| ### 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 within the structure corresponds to one row. | ||||
| 
 | ||||
| The state is initialized with the following snippet: | ||||
| 
 | ||||
| ```js | ||||
| const [ aoa, setAoA ] = useState(["SheetJS".split(""), "5433795".split("")]); | ||||
| ``` | ||||
| 
 | ||||
| #### 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 */ | ||||
|   setAoA(data); | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| ### Displaying Data | ||||
| 
 | ||||
| The demos use native `View` elements from `react-native` to display data. | ||||
| 
 | ||||
| <details><summary><b>Explanation</b> (click to show)</summary> | ||||
| 
 | ||||
| Since some spreadsheets may have empty cells between cells containing data, | ||||
| looping over the rows may skip values! | ||||
| 
 | ||||
| This example explicitly loops over the row and column indices. | ||||
| 
 | ||||
| **Determining the Row Indices** | ||||
| 
 | ||||
| The first row index is `0` and the last row index is `aoa.length - 1`. This | ||||
| corresponds to the `for` loop: | ||||
| 
 | ||||
| ```js | ||||
| for(var R = 0; R < aoa.length; ++R) {/* ... */} | ||||
| ``` | ||||
| 
 | ||||
| **Determining the Column Indices** | ||||
| 
 | ||||
| The first column index is `0` and the last column index must be calculated from | ||||
| the maximum column index across every row. | ||||
| 
 | ||||
| Traditionally this would be implemented in a `for` loop: | ||||
| 
 | ||||
| ```js | ||||
| var max_col_index = 0; | ||||
| for(var R = 0; R < aoa.length; ++R) { | ||||
|   if(!aoa[R]) continue; | ||||
|   max_col_index = Math.max(max_col_index, aoa[R].length - 1); | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| `Array#reduce` simplifies this calculation: | ||||
| 
 | ||||
| ```js | ||||
| const max_col_index = aoa.reduce((C,row) => Math.max(C,row.length), 1) - 1; | ||||
| ``` | ||||
| 
 | ||||
| **Looping from 0 to N-1** | ||||
| 
 | ||||
| Traditionally a `for` loop would be used: | ||||
| 
 | ||||
| ```js | ||||
| var data = []; | ||||
| for(var R = 0; R < max_row; ++R) data[R] = func(R); | ||||
| ``` | ||||
| 
 | ||||
| For creating an array of React Native components, `Array.from` should be used: | ||||
| 
 | ||||
| ```jsx | ||||
| var children = Array.from({length: max_row}, (_,R) => ( <Row key={R} /> )); | ||||
| ``` | ||||
| 
 | ||||
| </details> | ||||
| 
 | ||||
| The relevant parts for rendering data are shown below: | ||||
| 
 | ||||
| ```jsx | ||||
| import React, { useState, type FC } from 'react'; | ||||
| import { SafeAreaView, ScrollView, Text, View } from 'react-native'; | ||||
| 
 | ||||
| const App: FC = () => { | ||||
|   const [ aoa, setAoA ] = useState(["SheetJS".split(""), "5433795".split("")]); | ||||
|   const max_cols = aoa.reduce((acc,row) => Math.max(acc,row.length),1); | ||||
| 
 | ||||
|   return ( | ||||
|     <SafeAreaView> | ||||
|       <ScrollView contentInsetAdjustmentBehavior="automatic"> | ||||
|         {/* Table Container */} | ||||
|         <View>{ | ||||
|           /* Loop over the row indices */ | ||||
|           // highlight-next-line | ||||
|           Array.from({length: aoa.length}, (_, R) => ( | ||||
|             /* Table Row */ | ||||
|             <View key={R}>{ | ||||
|               /* Loop over the column indices */ | ||||
|               // highlight-next-line | ||||
|               Array.from({length: max_cols}, (_, C) => ( | ||||
|                 /* Table Cell */ | ||||
|                 <View key={C}> | ||||
|                    // highlight-next-line | ||||
|                   <Text>{String(aoa?.[R]?.[C]??"")}</Text> | ||||
|                 </View> | ||||
|               )) | ||||
|             }</View> | ||||
|           )) | ||||
|         }</View> | ||||
|       </ScrollView> | ||||
|     </SafeAreaView> | ||||
|   ); | ||||
| }; | ||||
| export default App; | ||||
| ``` | ||||
| 
 | ||||
| ## Native Modules | ||||
| 
 | ||||
| :::caution | ||||
| @ -70,8 +225,7 @@ assumes some familiarity with Objective-C. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| React Native for Windows + macOS use [Turbo Modules](https://reactnative.dev/docs/the-new-architecture/pillars-turbomodules) | ||||
| for effortless integration with native libraries and code. | ||||
| React Native for Windows + macOS use Turbo Modules[^4] for native integrations. | ||||
| 
 | ||||
| The demos define a native module named `DocumentPicker`. | ||||
| 
 | ||||
| @ -357,20 +511,20 @@ curl -Lo windows/SheetJSWin/DocumentPicker.h https://docs.sheetjs.com/reactnativ | ||||
| 
 | ||||
| Now the native module will be added to the app. | ||||
| 
 | ||||
| 4) Remove `App.js` (if it exists) and save the following to `App.tsx`: | ||||
| 4) Remove `App.js` (if it exists) and download [`App.tsx`](https://docs.sheetjs.com/reactnative/rnw/App.tsx): | ||||
| 
 | ||||
| <Tabs groupId="shell"> | ||||
|   <TabItem value="pwsh" label="PowerShell"> | ||||
| 
 | ||||
| ```bash | ||||
| iwr -Uri https://docs.sheetjs.com/reactnative/desktop/App.tsx -OutFile App.tsx | ||||
| iwr -Uri https://docs.sheetjs.com/reactnative/rnw/App.tsx -OutFile App.tsx | ||||
| ``` | ||||
| 
 | ||||
|   </TabItem> | ||||
|   <TabItem value="bash" label="WSL Bash"> | ||||
| 
 | ||||
| ```bash | ||||
| curl -LO https://docs.sheetjs.com/reactnative/desktop/App.tsx | ||||
| curl -LO https://docs.sheetjs.com/reactnative/rnw/App.tsx | ||||
| ``` | ||||
| 
 | ||||
|   </TabItem> | ||||
| @ -389,29 +543,31 @@ file picker to select the `pres.xlsx` file and the app will show the data. | ||||
| 
 | ||||
| ## macOS Demo | ||||
| 
 | ||||
| 0) Follow the [React Native](https://reactnative.dev/docs/environment-setup) | ||||
|    guide for React Native CLI on MacOS. | ||||
| 0) Follow the "Setting up the development environment"[^5] guide in the React | ||||
|    Native documentation for "React Native CLI Quickstart" + "macOS" + "iOS". | ||||
| 
 | ||||
| 1) Create a new project using React Native `0.71`: | ||||
| ### Project Setup | ||||
| 
 | ||||
| 1) Create a new React Native project using React Native `0.71`: | ||||
| 
 | ||||
| ```bash | ||||
| npx react-native init SheetJSmacOS --template react-native@^0.71.0 | ||||
| npx -y react-native init SheetJSmacOS --template react-native@^0.71.0 | ||||
| cd SheetJSmacOS | ||||
| ``` | ||||
| 
 | ||||
| Create the MacOS part of the application: | ||||
| 2) Create the MacOS part of the application: | ||||
| 
 | ||||
| ```bash | ||||
| npx react-native-macos-init --no-telemetry | ||||
| npx -y react-native-macos-init --no-telemetry | ||||
| ``` | ||||
| 
 | ||||
| Install the SheetJS library: | ||||
| 3) Install the SheetJS library: | ||||
| 
 | ||||
| <CodeBlock language="bash">{`\ | ||||
| npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`} | ||||
| </CodeBlock> | ||||
| 
 | ||||
| To ensure that the app works, launch the app: | ||||
| 4) To ensure that the app works, launch the app: | ||||
| 
 | ||||
| ```bash | ||||
| npx react-native run-macos | ||||
| @ -419,7 +575,10 @@ npx react-native run-macos | ||||
| 
 | ||||
| Close the running app from the dock and close the Metro terminal window. | ||||
| 
 | ||||
| 2) Create the file `macos/SheetJSmacOS-macOS/RCTDocumentPicker.h`: | ||||
| ### Native Module | ||||
| 
 | ||||
| 5) Create the file `macos/SheetJSmacOS-macOS/RCTDocumentPicker.h` with the | ||||
|    following contents: | ||||
| 
 | ||||
| ```objc title="macos/SheetJSmacOS-macOS/RCTDocumentPicker.h" | ||||
| #import <React/RCTBridgeModule.h> | ||||
| @ -427,7 +586,8 @@ Close the running app from the dock and close the Metro terminal window. | ||||
| @end | ||||
| ``` | ||||
| 
 | ||||
| Create the file `macos/SheetJSmacOS-macOS/RCTDocumentPicker.m`: | ||||
| 6) Create the file `macos/SheetJSmacOS-macOS/RCTDocumentPicker.m` with the | ||||
|     following contents: | ||||
| 
 | ||||
| ```objc title="macos/SheetJSmacOS-macOS/RCTDocumentPicker.m" | ||||
| #import <Foundation/Foundation.h> | ||||
| @ -462,20 +622,26 @@ RCT_EXPORT_METHOD(PickAndRead:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromi | ||||
| @end | ||||
| ``` | ||||
| 
 | ||||
| 3) Edit the project file `macos/SheetJSmacOS.xcodeproj/project.pbxproj`. | ||||
| 7) Edit the project file `macos/SheetJSmacOS.xcodeproj/project.pbxproj`. | ||||
| 
 | ||||
| There are four places where lines must be added: | ||||
| 
 | ||||
| A) Immediately after `/* Begin PBXBuildFile section */` | ||||
| :::note pass | ||||
| 
 | ||||
| A) Copy the highlighted line and paste under `/* Begin PBXBuildFile section */`: | ||||
| 
 | ||||
| ```plist | ||||
| /* Begin PBXBuildFile section */ | ||||
| // highlight-next-line | ||||
|     4717DC6A28CC499A00A9BE56 /* RCTDocumentPicker.m in Sources */ = {isa = PBXBuildFile; fileRef = 4717DC6928CC499A00A9BE56 /* RCTDocumentPicker.m */; }; | ||||
|     13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; }; | ||||
|     5142014D2437B4B30078DB4F /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5142014C2437B4B30078DB4F /* AppDelegate.mm */; }; | ||||
| ``` | ||||
| 
 | ||||
| B) Immediately after `/* Begin PBXFileReference section */` | ||||
| ::: | ||||
| 
 | ||||
| :::note pass | ||||
| 
 | ||||
| B) Copy the highlighted lines and paste under `/* Begin PBXFileReference section */`: | ||||
| 
 | ||||
| ```plist | ||||
| /* Begin PBXFileReference section */ | ||||
| @ -486,6 +652,10 @@ B) Immediately after `/* Begin PBXFileReference section */` | ||||
|     008F07F21AC5B25A0029DE68 /* main.jsbundle */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = main.jsbundle; sourceTree = "<group>"; }; | ||||
| ``` | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| :::note pass | ||||
| 
 | ||||
| C) The goal is to add a reference to the `PBXSourcesBuildPhase` block for the | ||||
| `macOS` target.  To determine this, look in the `PBXNativeTarget section` for | ||||
| a block with the comment `SheetJSmacOS-macOS`: | ||||
| @ -505,6 +675,9 @@ a block with the comment `SheetJSmacOS-macOS`: | ||||
| Within the block, look for `buildPhases` and find the hex string for `Sources`: | ||||
| 
 | ||||
| ```plist | ||||
|     514201482437B4B30078DB4F /* SheetJSmacOS-macOS */ = { | ||||
|       isa = PBXNativeTarget; | ||||
|       buildConfigurationList = 5142015A2437B4B40078DB4F /* Build configuration list for PBXNativeTarget "SheetJSmacOS-macOS" */; | ||||
|       buildPhases = ( | ||||
|         1A938104A937498D81B3BD3B /* [CP] Check Pods Manifest.lock */, | ||||
|         381D8A6F24576A6C00465D17 /* Start Packager */, | ||||
| @ -528,14 +701,17 @@ add the highlighted line: | ||||
|       files = ( | ||||
| // highlight-next-line | ||||
|         4717DC6A28CC499A00A9BE56 /* RCTDocumentPicker.m in Sources */, | ||||
|         514201502437B4B30078DB4F /* ViewController.m in Sources */, | ||||
|         514201582437B4B40078DB4F /* main.m in Sources */, | ||||
|         5142014D2437B4B30078DB4F /* AppDelegate.m in Sources */, | ||||
|         5142014D2437B4B30078DB4F /* AppDelegate.mm in Sources */, | ||||
|       ); | ||||
|       runOnlyForDeploymentPostprocessing = 0; | ||||
|     }; | ||||
| ``` | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| :::note pass | ||||
| 
 | ||||
| D) The goal is to add file references to the "main group".  Search for | ||||
| `/* Begin PBXProject section */` and there should be one Project object. | ||||
| Within the project object, look for `mainGroup`: | ||||
| @ -570,63 +746,57 @@ highlighted lines: | ||||
|         13B07FAE1A68108700A75B9A /* SheetJSmacOS-iOS */, | ||||
| ``` | ||||
| 
 | ||||
| 4) Replace `App.tsx` with the following: | ||||
| ::: | ||||
| 
 | ||||
| ```tsx title="App.tsx" | ||||
| import React, { useState, type Node } from 'react'; | ||||
| import { SafeAreaView, ScrollView, StyleSheet, Text, TouchableHighlight, View } from 'react-native'; | ||||
| import { read, utils, version } from 'xlsx'; | ||||
| import { getEnforcing } from 'react-native/Libraries/TurboModule/TurboModuleRegistry'; | ||||
| const DocumentPicker = getEnforcing('DocumentPicker'); | ||||
| 
 | ||||
| const App: () => Node = () => { | ||||
| 
 | ||||
|   const [ aoa, setAoA ] = useState(["SheetJS".split(""), "5433795".split("")]); | ||||
| 
 | ||||
|   return ( | ||||
|     <SafeAreaView style={styles.outer}> | ||||
|       <Text style={styles.title}>SheetJS × React Native MacOS {version}</Text> | ||||
|       <TouchableHighlight onPress={async() => { | ||||
|         try { | ||||
|           const b64 = await DocumentPicker.PickAndRead(); | ||||
|           const wb = read(b64); | ||||
|           setAoA(utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]], { header: 1 } )); | ||||
|         } catch(err) { alert(`Error: ${err.message}`); } | ||||
|       }}><Text style={styles.button}>Click here to Open File!</Text></TouchableHighlight> | ||||
|       <ScrollView contentInsetAdjustmentBehavior="automatic"> | ||||
|         <View style={styles.table}>{aoa.map((row,R) => ( | ||||
|           <View style={styles.row} key={R}>{row.map((cell,C) => ( | ||||
|             <View style={styles.cell} key={C}><Text>{cell}</Text></View> | ||||
|           ))}</View> | ||||
|         ))}</View> | ||||
|       </ScrollView> | ||||
|     </SafeAreaView> | ||||
|   ); | ||||
| }; | ||||
| 
 | ||||
| const styles = StyleSheet.create({ | ||||
|   cell: { flex: 4 }, | ||||
|   row: { flexDirection: 'row', justifyContent: 'space-evenly', padding: 10, backgroundColor: 'white', }, | ||||
|   table: { display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', }, | ||||
|   outer: { marginTop: 32, paddingHorizontal: 24, }, | ||||
|   title: { fontSize: 24, fontWeight: '600', }, | ||||
|   button: { marginTop: 8, fontSize: 18, fontWeight: '400', }, | ||||
| }); | ||||
| 
 | ||||
| export default App; | ||||
| ``` | ||||
| 
 | ||||
| 5) Test the app: | ||||
| 8) To ensure that the app still works, launch the app again: | ||||
| 
 | ||||
| ```bash | ||||
| npx react-native run-macos | ||||
| ``` | ||||
| 
 | ||||
| Download <https://sheetjs.com/pres.xlsx>, then click on "open file". Use the | ||||
| file picker to select the `pres.xlsx` file and the app will show the data. | ||||
| Close the running app from the dock and close the Metro terminal window. | ||||
| 
 | ||||
| 6) Make a release build: | ||||
| ### Application | ||||
| 
 | ||||
| 9) Download [`App.tsx`](https://docs.sheetjs.com/reactnative/rnm/App.tsx) and | ||||
|    replace the file in the project: | ||||
| 
 | ||||
| ```bash | ||||
| curl -LO https://docs.sheetjs.com/reactnative/rnm/App.tsx | ||||
| ``` | ||||
| 
 | ||||
| 10) Test the app: | ||||
| 
 | ||||
| ```bash | ||||
| npx react-native run-macos | ||||
| ``` | ||||
| 
 | ||||
| Download <https://sheetjs.com/pres.xlsx>. | ||||
| 
 | ||||
| Click "Click here to Open File!" and use the file picker to select `pres.xlsx` . | ||||
| The app will refresh and display the data from the file. | ||||
| 
 | ||||
| 11) Make a release build: | ||||
| 
 | ||||
| ```bash | ||||
| xcodebuild -workspace macos/SheetJSmacOS.xcworkspace -scheme SheetJSmacOS-macOS -config Release | ||||
| ``` | ||||
| 
 | ||||
| The last line of the output will include the path to the app. If it is not | ||||
| displayed, the app path can be found in the `DerivedData` folder: | ||||
| 
 | ||||
| ```bash | ||||
| find ~/Library/Developer/Xcode/DerivedData -name SheetJSmacOS.app | grep Release | ||||
| ``` | ||||
| 
 | ||||
| 12) Run the release app: | ||||
| 
 | ||||
| ```bash | ||||
| open -a "$(find ~/Library/Developer/Xcode/DerivedData -name SheetJSmacOS.app | grep Release | head -n 1)" | ||||
| ``` | ||||
| 
 | ||||
| [^1]: The [official website](https://microsoft.github.io/react-native-windows/) covers both platforms, but there are separate repositories for [Windows](https://github.com/microsoft/react-native-windows) and [macOS](https://github.com/microsoft/react-native-macos) | ||||
| [^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 ["Turbo Native Modules"](https://reactnative.dev/docs/the-new-architecture/pillars-turbomodules) in the React Native documentation. | ||||
| [^5]: See ["Setting up the development environment"](https://reactnative.dev/docs/environment-setup) in the React Native documentation. Select the "React Native CLI Quickstart" tab and choose the Development OS "macOS" and the Target OS "iOS". | ||||
| @ -15,7 +15,7 @@ changes.  Git can also store and track binary data artifacts. | ||||
| 
 | ||||
| GitHub is a popular host for Git repositories.  GitHub's "Flat Data" project | ||||
| explores storing and comparing versions of structured CSV and JSON data. The | ||||
| official "Excel to CSV" example uses SheetJS to generate CSV data from files: | ||||
| official "Excel to CSV"[^1] example uses SheetJS to generate CSV data from files: | ||||
| 
 | ||||
| ```mermaid | ||||
| sequenceDiagram | ||||
| @ -273,3 +273,5 @@ jobs: | ||||
| 
 | ||||
|    The update process will run once an hour.  If you return in a few hours and | ||||
|    refresh the page, there should be more commits in the selection list. | ||||
| 
 | ||||
| [^1]: See ["Excel to CSV"](https://githubnext.com/projects/flat-data#:~:text=View%20code-,Excel,-to%20CSV) in the "Flat Data" writeup | ||||
| @ -28,6 +28,8 @@ form data.  This can be disabled with cloud-specific configuration: | ||||
| 
 | ||||
| - [AWS Lambda Functions](/docs/demos/cloud/aws#aws-lambda-functions) | ||||
| - [Azure Functions](/docs/demos/cloud/azure#azure-functions) | ||||
| - [GitHub Actions](/docs/demos/cloud/github) | ||||
| - [Deno Deploy](/docs/demos/cloud/deno) | ||||
| 
 | ||||
| ## Cloud Storage | ||||
| 
 | ||||
| @ -44,7 +46,6 @@ File hosting services provide simple solutions for storing data, synchronizing | ||||
| files across devices, and sharing with specific users or customers. Demos: | ||||
| 
 | ||||
| - [Dropbox](/docs/demos/cloud/dropbox) | ||||
| - [GitHub](/docs/demos/cloud/github) | ||||
| 
 | ||||
| ## Cloud Data | ||||
| 
 | ||||
|  | ||||
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 108 KiB | 
							
								
								
									
										42
									
								
								docz/static/reactnative/rnm/App.tsx
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										42
									
								
								docz/static/reactnative/rnm/App.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,42 @@ | ||||
| import React, { useState, type FC } from 'react'; | ||||
| import { SafeAreaView, ScrollView, StyleSheet, Text, TouchableHighlight, View, Alert } from 'react-native'; | ||||
| import { read, utils, version } from 'xlsx'; | ||||
| import { getEnforcing } from 'react-native/Libraries/TurboModule/TurboModuleRegistry'; | ||||
| const DocumentPicker = getEnforcing('DocumentPicker'); | ||||
| 
 | ||||
| const App: FC = () => { | ||||
| 
 | ||||
|   const [ aoa, setAoA ] = useState(["SheetJS".split(""), "5433795".split("")]); | ||||
|   const max_cols = aoa.reduce((acc,row) => Math.max(acc,row.length),1); | ||||
| 
 | ||||
|   return ( | ||||
|     <SafeAreaView style={styles.outer}> | ||||
|       <Text style={styles.title}>SheetJS × React Native MacOS {version}</Text> | ||||
|       <TouchableHighlight onPress={async() => { | ||||
|         try { | ||||
|           const b64 = await DocumentPicker.PickAndRead(); | ||||
|           const wb = read(b64); | ||||
|           setAoA(utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]], { header: 1 } )); | ||||
|         } catch(err) { Alert.alert(`Read Error`, `${err instanceof Error ? err.message : err}`); } | ||||
|       }}><Text style={styles.button}>Click here to Open File!</Text></TouchableHighlight> | ||||
|       <ScrollView contentInsetAdjustmentBehavior="automatic"> | ||||
|         <View style={styles.table}>{Array.from({length: aoa.length}, (_, R) => ( | ||||
|           <View style={styles.row} key={R}>{Array.from({length: max_cols}, (_, C) => ( | ||||
|             <View style={styles.cell} key={C}><Text>{String(aoa?.[R]?.[C]??"")}</Text></View> | ||||
|           ))}</View> | ||||
|         ))}</View> | ||||
|       </ScrollView> | ||||
|     </SafeAreaView> | ||||
|   ); | ||||
| }; | ||||
| 
 | ||||
| const styles = StyleSheet.create({ | ||||
|   cell: { flex: 4 }, | ||||
|   row: { flexDirection: 'row', justifyContent: 'space-evenly', padding: 10, backgroundColor: 'white', }, | ||||
|   table: { display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', }, | ||||
|   outer: { marginTop: 32, paddingHorizontal: 24, }, | ||||
|   title: { fontSize: 24, fontWeight: '600', }, | ||||
|   button: { marginTop: 8, fontSize: 18, fontWeight: '400', }, | ||||
| }); | ||||
| 
 | ||||
| export default App; | ||||
							
								
								
									
										3
									
								
								docz/static/reactnative/rnm/RCTDocumentPicker.h
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										3
									
								
								docz/static/reactnative/rnm/RCTDocumentPicker.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | ||||
| #import <React/RCTBridgeModule.h> | ||||
| @interface RCTDocumentPicker : NSObject <RCTBridgeModule> | ||||
| @end | ||||
							
								
								
									
										30
									
								
								docz/static/reactnative/rnm/RCTDocumentPicker.m
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										30
									
								
								docz/static/reactnative/rnm/RCTDocumentPicker.m
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,30 @@ | ||||
| #import <Foundation/Foundation.h> | ||||
| #import <React/RCTUtils.h> | ||||
| 
 | ||||
| #import "RCTDocumentPicker.h" | ||||
| 
 | ||||
| @implementation RCTDocumentPicker | ||||
| 
 | ||||
| RCT_EXPORT_MODULE(); | ||||
| 
 | ||||
| RCT_EXPORT_METHOD(PickAndRead:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) | ||||
| { | ||||
|   RCTExecuteOnMainQueue(^{ | ||||
|     NSOpenPanel *panel = [NSOpenPanel openPanel]; | ||||
|     [panel setCanChooseDirectories:NO]; | ||||
|     [panel setAllowsMultipleSelection:NO]; | ||||
|     [panel setMessage:@"Select a spreadsheet to read"]; | ||||
| 
 | ||||
|     [panel beginWithCompletionHandler:^(NSInteger result){ | ||||
|       if (result == NSModalResponseOK) { | ||||
|         NSURL *selected = [[panel URLs] objectAtIndex:0]; | ||||
|         NSFileHandle *hFile = [NSFileHandle fileHandleForReadingFromURL:selected error:nil]; | ||||
|         if(hFile) { | ||||
|           NSData *data = [hFile readDataToEndOfFile]; | ||||
|           resolve([data base64EncodedStringWithOptions:0]); | ||||
|         } else reject(@"read_failure", @"Could not read selected file!", nil); | ||||
|       } else reject(@"select_failure", @"No file selected!", nil); | ||||
|     }]; | ||||
|   }); | ||||
| } | ||||
| @end | ||||
| @ -1,12 +1,13 @@ | ||||
| import React, { useState, type Node } from 'react'; | ||||
| import { SafeAreaView, ScrollView, StyleSheet, Text, TouchableHighlight, View } from 'react-native'; | ||||
| import React, { useState, type FC } from 'react'; | ||||
| import { SafeAreaView, ScrollView, StyleSheet, Text, TouchableHighlight, View, Alert } from 'react-native'; | ||||
| import { read, utils, version } from 'xlsx'; | ||||
| import { getEnforcing } from 'react-native/Libraries/TurboModule/TurboModuleRegistry'; | ||||
| const DocumentPicker = getEnforcing('DocumentPicker'); | ||||
| 
 | ||||
| const App: () => Node = () => { | ||||
| const App: FC = () => { | ||||
| 
 | ||||
|   const [ aoa, setAoA ] = useState(["SheetJS".split(""), "5433795".split("")]); | ||||
|   const max_cols = aoa.reduce((acc,row) => Math.max(acc,row.length),1); | ||||
| 
 | ||||
|   return ( | ||||
|     <SafeAreaView style={styles.outer}> | ||||
| @ -16,12 +17,12 @@ const App: () => Node = () => { | ||||
|           const b64 = await DocumentPicker.PickAndRead(); | ||||
|           const wb = read(b64); | ||||
|           setAoA(utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]], { header: 1 } )); | ||||
|         } catch(err) { alert(`Error: ${err.message}`); } | ||||
|         } catch(err) { Alert.alert(`Read Error`, `${err instanceof Error ? err.message : err}`); } | ||||
|       }}><Text style={styles.button}>Click here to Open File!</Text></TouchableHighlight> | ||||
|       <ScrollView contentInsetAdjustmentBehavior="automatic"> | ||||
|         <View style={styles.table}>{aoa.map((row,R) => ( | ||||
|           <View style={styles.row} key={R}>{row.map((cell,C) => ( | ||||
|             <View style={styles.cell} key={C}><Text>{cell}</Text></View> | ||||
|         <View style={styles.table}>{Array.from({length: aoa.length}, (_, R) => ( | ||||
|           <View style={styles.row} key={R}>{Array.from({length: max_cols}, (_, C) => ( | ||||
|             <View style={styles.cell} key={C}><Text>{String(aoa?.[R]?.[C]??"")}</Text></View> | ||||
|           ))}</View> | ||||
|         ))}</View> | ||||
|       </ScrollView> | ||||
| @ -38,4 +39,4 @@ const styles = StyleSheet.create({ | ||||
|   button: { marginTop: 8, fontSize: 18, fontWeight: '400', }, | ||||
| }); | ||||
| 
 | ||||
| export default App; | ||||
| export default App; | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user