diff --git a/docz/docs/02-getting-started/02-examples/02-export.mdx b/docz/docs/02-getting-started/02-examples/02-export.mdx index 3eb6bf9..67b4fc6 100644 --- a/docz/docs/02-getting-started/02-examples/02-export.mdx +++ b/docz/docs/02-getting-started/02-examples/02-export.mdx @@ -938,6 +938,18 @@ current Java releases. ::: +:::warning pass + +There are a number of potential pitfalls. + +The [React Native demo](/docs/demos/mobile/reactnative) lists some issues +encountered in previous test runs and potential resolutions. + +**Please reach out to [the SheetJS chat](https://sheetjs.com/chat) if there are +any issues not mentioned in the demo page.** + +::: + Create a new project by running the following commands in the Terminal: {`\ @@ -1043,7 +1055,7 @@ i - run on iOS a - run on Android ``` -Press `a` to run on android. +Press `a` to run on Android. The app will launch in the emulator. After clicking "Press to Export", the app will show an alert with the location to the generated file (`/data/user/0/com.sheetjspres/files/Presidents.xlsx`) @@ -1060,11 +1072,32 @@ This command generates `Presidents.xlsx` which can be opened. :::info Device Testing ["Running on Device"](https://reactnative.dev/docs/running-on-device) in the -React Native docs covers device configuration. +React Native docs covers device configuration. To summarize: + +1) Enable USB debugging on the Android device. + +2) Connect the Android device to the computer with a USB cable. + +3) Close any running Android and iOS emulators. + +4) Run `npx react-native run-android` `Presidents.xlsx` will be copied to the `Downloads` folder. The file is visible in the Files app and can be opened with the Google Sheets app. +::: + +:::caution pass + +**This demo worked on multiple local Android devices in local tests.** It is not +guaranteed to run on every Android device or Android version. + +The [React Native demo](/docs/demos/mobile/reactnative) lists some issues +encountered in previous test runs and potential resolutions. + +Please reach out to [the SheetJS chat](https://sheetjs.com/chat) if there are +any issues not mentioned in the demo page. + ::: diff --git a/docz/docs/03-demos/01-math/11-tensorflow.md b/docz/docs/03-demos/01-math/11-tensorflow.md index da7ffad..bdfbd39 100644 --- a/docz/docs/03-demos/01-math/11-tensorflow.md +++ b/docz/docs/03-demos/01-math/11-tensorflow.md @@ -41,7 +41,7 @@ Each browser demo was tested in the following environments: | Browser | TF.js version | Date | |:------------|:--------------|:-----------| -| Chrome 119 | `4.14.0` | 2023-12-09 | +| Chrome 122 | `4.14.0` | 2024-04-07 | | Safari 17.4 | `4.14.0` | 2024-03-23 | ::: diff --git a/docz/docs/03-demos/02-frontend/09-legacy.md b/docz/docs/03-demos/02-frontend/09-legacy.md index 3696ab5..f81f4ae 100644 --- a/docz/docs/03-demos/02-frontend/09-legacy.md +++ b/docz/docs/03-demos/02-frontend/09-legacy.md @@ -198,14 +198,19 @@ included in the page and the relevant features are enabled on the target system. ### KnockoutJS -KnockoutJS was a popular MVVM framework. +[KnockoutJS](https://knockoutjs.com/) was a popular MVVM framework. -The [Live demo](pathname:///knockout/knockout.html) shows a view model that is +The [Live demo](pathname:///knockout/knockout3.html) shows a view model that is updated with file data and exported to spreadsheets. :::note Tested Deployments -This demo was last run on 2023 December 04 using KnockoutJS `3.4.2` +This demo was tested in the following environments: + +| KnockoutJS | Date | Live Demo | +|:-----------|:-----------|:-----------------------------------------------| +| `3.5.0` | 2024-04-07 | [**KO3**](pathname:///knockout/knockout3.html) | +| `2.3.0` | 2024-04-07 | [**KO2**](pathname:///knockout/knockout2.html) | ::: diff --git a/docz/docs/03-demos/03-net/02-upload/index.mdx b/docz/docs/03-demos/03-net/02-upload/index.mdx index 4c64a45..86f9103 100644 --- a/docz/docs/03-demos/03-net/02-upload/index.mdx +++ b/docz/docs/03-demos/03-net/02-upload/index.mdx @@ -17,7 +17,7 @@ cloud storage solutions. Spreadsheets can be written using SheetJS and uploaded. This demo explores file uploads using a number of browser APIs and wrapper libraries. The upload process will generate a sample XLSX workbook, upload the -file to [a test server](#test-server), and display the response. +file to [a test server](https://s2c.sheetjs.com), and display the response. :::info pass @@ -60,41 +60,69 @@ flowchart LR form --> |POST\nrequest| server ``` -```js +### Generating Files + +In a typical scenario, a process generates arrays of simple objects. + +The SheetJS `json_to_sheet` method[^2] generates a SheetJS worksheet object[^3]. +The `book_new` method[^4] creates a workbook object that includes the worksheet. + +The `write` method[^5] generates the file in memory. + +The following snippet creates a sample dataset and generates an `ArrayBuffer` +object representing the workbook bytes: + +```js title="Generating an XLSX file in memory" /* create sample SheetJS workbook object */ var aoa = [ ["S", "h", "e", "e", "t", "J", "S"], [ 5, 4, 3, 3, 7, 9, 5] ]; var ws = XLSX.utils.aoa_to_sheet(aoa); -var wb = XLSX.utils.book_new(); +var wb = XLSX.utils.book_new(ws, "Sheet1"); XLSX.utils.book_append_sheet(wb, ws, "Sheet1"); /* export SheetJS workbook object to XLSX file bytes */ var data = XLSX.write(wb, {bookType: 'xlsx', type: 'array'}); +``` + +### Creating Form Data + +`File` objects represent files. The `File` constructor accepts an array of data +fragments and a filename. + +Browser APIs typically represent form body data using `FormData` objects. The +`append` method adds fields to the `FormData` object. Adding `File` objects +effectively "attaches" a file in the upload. + +The following snippet constructs a new `FormData` object. The `file` field in +the form will be set to the data from the previous snippet: + +```js title="Creating Form Data and attaching the generated file" +/* create File */ +var file = new File([data], 'sheetjs.xlsx') +// generated XLSX ^^^^ ^^^^^^^^^^^^ file name /* build FormData with the generated file */ var fdata = new FormData(); -fdata.append('file', new File([data], 'sheetjs.xlsx')); -// field name ^^^^ file name ^^^^^^^^^^^^ +fdata.append('file', file); +// ^^^^ field name in the form body +``` +### POST Request + +This demo explores a number of APIs and libraries for making POST requests. Each +approach will upload data stored in `FormData` objects. + +This snippet uses `XMLHttpRequest` to upload data to : + +```js title="Uploading Form Data with XMLHttpRequest" /* send data using XMLHttpRequest */ var req = new XMLHttpRequest(); req.open("POST", "https://s2c.sheetjs.com", true); req.send(fdata); ``` -## Test Server - -The service is currently hosted on Deno Deploy. The -["Deno Deploy" demo](/docs/demos/cloud/deno#demo) covers the exact steps for -deploying the service. - -The CORS-enabled service handles POST requests by looking for uploaded files in -the `"file"` key. If a file is found, the file will be parsed using the SheetJS -`read` method[^2] and the first worksheet will be converted to HTML using the -`sheet_to_html` method[^3]. - ## Browser Demos When the upload button is clicked, the browser will build up a new workbook, @@ -112,9 +140,55 @@ Each browser demo was tested in the following environments: ::: +#### Test Server + +The service is currently hosted on Deno Deploy. The +["Deno Deploy" demo](/docs/demos/cloud/deno#demo) covers the exact steps for +deploying the service. + +The CORS-enabled service handles POST requests by looking for uploaded files in +the `"file"` key. If a file is found, the file will be parsed using the SheetJS +`read` method[^6] and the first worksheet will be converted to HTML using the +`sheet_to_html` method[^7]. + ### XMLHttpRequest -This demo uses [the code snippet from the intro](#uploading-binary-data). +Using the `XMLHttpRequest` API, the `send` method can accept `FormData` objects: + +```js title="Uploading Form Data with XMLHttpRequest" +/* send data using XMLHttpRequest */ +var req = new XMLHttpRequest(); +req.open("POST", "https://s2c.sheetjs.com", true); +req.send(fdata); +``` + +
Complete Code Snippet (click to show) + +```js title="SheetJS + XMLHttpRequest example" +/* create sample SheetJS workbook object */ +var aoa = [ + ["S", "h", "e", "e", "t", "J", "S"], + [ 5, 4, 3, 3, 7, 9, 5] +]; +const ws = XLSX.utils.aoa_to_sheet(aoa); +const wb = XLSX.utils.book_new(); +XLSX.utils.book_append_sheet(wb, ws, "Sheet1"); + +/* export SheetJS workbook object to XLSX file bytes */ +var data = XLSX.write(wb, {bookType: 'xlsx', type: 'array'}); + +/* build FormData with the generated file */ +var fdata = new FormData(); +fdata.append('file', new File([data], 'sheetjs.xlsx')); +// field name ^^^^ file name ^^^^^^^^^^^^ + +/* send data using XMLHttpRequest */ +var req = new XMLHttpRequest(); +req.open("POST", "https://s2c.sheetjs.com", true); +req.send(fdata); +``` + +
Live demo (click to show) @@ -184,7 +258,14 @@ function SheetJSXHRUL() { `fetch` takes a second parameter which allows for setting POST request body: -```js +```js title="Uploading Form Data with fetch" +/* send data using fetch */ +fetch("https://s2c.sheetjs.com", { method: "POST", body: fdata }); +``` + +
Complete Code Snippet (click to show) + +```js title="SheetJS + fetch example" /* create sample SheetJS workbook object */ var aoa = [ ["S", "h", "e", "e", "t", "J", "S"], @@ -206,6 +287,8 @@ fdata.append('file', new File([data], 'sheetjs.xlsx')); fetch("https://s2c.sheetjs.com", { method: "POST", body: fdata }); ``` +
+
Live demo (click to show) This demo uses `fetch` to upload data to . It will parse @@ -276,7 +359,14 @@ are still relevant. Uploading form data is nearly identical to the `fetch` example: -```js +```js title="Uploading Form Data with axios" +/* send data using axios */ +axios("https://s2c.sheetjs.com", { method: "POST", body: fdata }); +``` + +
Complete Code Snippet (click to show) + +```js title="SheetJS + axios example" /* create sample SheetJS workbook object */ var aoa = [ ["S", "h", "e", "e", "t", "J", "S"], @@ -298,6 +388,8 @@ fdata.append('file', new File([data], 'sheetjs.xlsx')); axios("https://s2c.sheetjs.com", { method: "POST", data: fdata }); ``` +
+
Live demo (click to show) This demo uses `axios` to upload data to . It will parse @@ -375,7 +467,14 @@ with a "Fluent Interface". The `send` method accepts a `FormData` object as the first argument: -```js +```js title="Uploading Form Data with superagent" +/* send data using superagent */ +superagent.post("https://s2c.sheetjs.com").send(fd); +``` + +
Complete Code Snippet (click to show) + +```js title="SheetJS + superagent example" /* create sample SheetJS workbook object */ var aoa = [ ["S", "h", "e", "e", "t", "J", "S"], @@ -397,6 +496,8 @@ fdata.append('file', new File([data], 'sheetjs.xlsx')); superagent.post("https://s2c.sheetjs.com").send(fd); ``` +
+
Live demo (click to show) This demo uses `superagent` to upload data to . It will @@ -473,11 +574,16 @@ These examples show how to upload data in NodeJS. ### fetch -The `fetch` implementation mirrors the [browser `fetch`](#fetch). +NodeJS `fetch`, available in version 20, mirrors the [browser `fetch`](#fetch). :::note Tested Deployments -This demo was last tested on 2023 November 19 against NodeJS `20.9.0` +This demo was tested in the following environments: + +| NodeJS | Date | +|:-----------|:-----------| +| `20.12.1` | 2024-04-07 | +| `21.7.2` | 2024-04-07 | ::: @@ -533,6 +639,155 @@ It will print CSV contents of the test file.
+### request + +The deprecated [`request`](https://github.com/request/request) library is useful +in legacy NodeJS deployments where `fetch` may not be available. + +The SheetJS `write` method will generate NodeJS Buffer objects when the `type` +option is set to `"buffer"`: + +```js +/* export SheetJS workbook object to XLSX file bytes */ +const data = XLSX.write(wb, {bookType: 'xlsx', type: 'buffer'}); +``` + +A `request` file object can be built using the Buffer. The file object must +include an `options` object that specifies the file name and content type: + +```js +/* create a file object for the `request` form data */ +const request_file = { + /* `value` can be a Buffer object */ + value: data, + options: { + /* `options.filename` is the filename that the server will see */ + filename: "sheetjs.xlsx", + /* `options.contentType` must be set */ + contentType: "application/octet-stream" + } +}; +``` + + +The `request` and `request.post` methods accept an options argument. The +`formData` property specifies the body to be uploaded. Property names correspond +to the uploaded form names and values describe the uploaded content. + +The `request` file object should be added to the `formData` object: + +```js +request({ + // ... other options ... + formData: { + // ... other form fields ... + + /* the server will see the uploaded file in the `file` body property */ + /* highlight-next-line */ + file: request_file + } +}, function(err, res) { /* handle response ... */ }); +``` + +:::note Tested Deployments + +This demo was tested in the following environments: + +| NodeJS | `request` | Date | +|:-----------|:----------|:-----------| +| `0.10.48` | `2.88.2` | 2024-04-07 | +| `0.12.18` | `2.88.2` | 2024-04-07 | +| `4.9.1` | `2.88.2` | 2024-04-07 | +| `6.17.1` | `2.88.2` | 2024-04-07 | +| `8.17.0` | `2.88.2` | 2024-04-07 | +| `10.24.1` | `2.88.2` | 2024-04-07 | +| `12.22.12` | `2.88.2` | 2024-04-07 | +| `14.21.3` | `2.88.2` | 2024-04-07 | +| `16.20.2` | `2.88.2` | 2024-04-07 | +| `18.20.1` | `2.88.2` | 2024-04-07 | +| `20.12.1` | `2.88.2` | 2024-04-07 | + +::: + +
Complete Example (click to show) + +This demo uses `request` to upload data to . It will +parse the workbook and return data in CSV rows. + +1) Install the [SheetJS NodeJS module](/docs/getting-started/installation/nodejs) +and `request` module: + +{`\ +npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz request`} + + +2) Save the following to `SheetJSRequest.js`: + +```js title="SheetJSRequest.js" +const XLSX = require("xlsx"); +const request = require("request"); + +/* create sample SheetJS workbook object */ +var aoa = [ + ["S", "h", "e", "e", "t", "J", "S"], + [ 5, 4, 3, 3, 7, 9, 5] +]; +const ws = XLSX.utils.aoa_to_sheet(aoa); +const wb = XLSX.utils.book_new(); +XLSX.utils.book_append_sheet(wb, ws, "Sheet1"); + +/* export SheetJS workbook object to XLSX file bytes */ +var data = XLSX.write(wb, {bookType: 'xlsx', type: 'buffer'}); + +request({ + method: "POST", + url: "https://s2c.sheetjs.com", + headers: { + Accept: "text/html" + }, + formData: { + type: "csv", + file: { + value: data, + options: { + filename: "sheetjs.xlsx", + contentType: "application/octet-stream" + } + } + } +}, function(err, res, body) { + if(err) return console.error(err); + console.log(body); +}); +``` + +3) Run the script: + +```bash +node SheetJSRequest.js +``` + +It will print CSV contents of the test file. + +:::caution pass + +For legacy versions of NodeJS, the process may fail with a certificate error: + +``` +{ [Error: certificate not trusted] code: 'CERT_UNTRUSTED' } +``` + +The environment variable `NODE_TLS_REJECT_UNAUTHORIZED` can be set to `0`: + +```bash +env NODE_TLS_REJECT_UNAUTHORIZED="0" node SheetJSRequest.js +``` + +**It is strongly recommended to upgrade to a newer version of NodeJS!** + +::: + +
## Troubleshooting @@ -571,5 +826,9 @@ const res = await fetch(url, {method:"POST", body: fdata}); If the generated file is valid, then the issue is in the server infrastructure. [^1]: See [`write` in "Writing Files"](/docs/api/write-options) -[^2]: See [`read` in "Reading Files"](/docs/api/parse-options) -[^3]: See [`sheet_to_html` in "Utilities"](/docs/api/utilities/html#html-table-output) +[^2]: See [`json_to_sheet` in "Utilities"](/docs/api/utilities/array#array-of-objects-input) +[^3]: See ["Worksheet Object" in "SheetJS Data Model"](/docs/csf/sheet) for more details. +[^4]: See [`book_new` in "Utilities"](/docs/api/utilities/wb) +[^5]: See [`write` in "Writing Files"](/docs/api/write-options) +[^6]: See [`read` in "Reading Files"](/docs/api/parse-options) +[^7]: See [`sheet_to_html` in "Utilities"](/docs/api/utilities/html#html-table-output) diff --git a/docz/docs/03-demos/03-net/09-dom.md b/docz/docs/03-demos/03-net/09-dom.md index f84a1f3..39c1599 100644 --- a/docz/docs/03-demos/03-net/09-dom.md +++ b/docz/docs/03-demos/03-net/09-dom.md @@ -335,7 +335,7 @@ The script will create a file `SheetJSDenoDOM.xlsx` that can be opened.
[^1]: See [`table_to_sheet` in "HTML" Utilities](/docs/api/utilities/html#create-new-sheet) -[^2]: See ["Worksheet Object" in "SheetJS Data Model"](/docs/csf/book) for more details. +[^2]: See ["Worksheet Object" in "SheetJS Data Model"](/docs/csf/sheet) for more details. [^3]: See [`table_to_book` in "HTML" Utilities](/docs/api/utilities/html#create-new-sheet) [^4]: See ["Workbook Object" in "SheetJS Data Model"](/docs/csf/book) for more details. [^5]: See [`sheet_add_dom` in "HTML" Utilities](/docs/api/utilities/html#add-to-sheet) \ No newline at end of file diff --git a/docz/docs/03-demos/12-static/04-esbuild.md b/docz/docs/03-demos/12-static/04-esbuild.md index 3bfd4da..f8b60f0 100644 --- a/docz/docs/03-demos/12-static/04-esbuild.md +++ b/docz/docs/03-demos/12-static/04-esbuild.md @@ -32,8 +32,8 @@ importing the SheetJS library in a browser script. ## ESBuild Loader -ESBuild supports custom loader plugins. The loader receives an absolute path to -the spreadsheet on the filesystem. +ESBuild releases starting from `0.9.1` support custom loader plugins. The loader +receives an absolute path to the spreadsheet on the filesystem. The [SheetJS NodeJS module](/docs/getting-started/installation/nodejs) can be imported from ESBuild loader plugins. @@ -200,7 +200,23 @@ document.body.appendChild(elt); :::note Tested Deployments -This demo was last tested on 2023 December 04 against ESBuild 0.19.8 +This demo was tested in the following environments: + +| `esbuild` | Date | +|:----------|:-----------| +| `0.20.2` | 2024-04-07 | +| `0.19.12` | 2024-04-07 | +| `0.18.20` | 2024-04-07 | +| `0.17.19` | 2024-04-07 | +| `0.16.17` | 2024-04-07 | +| `0.15.18` | 2024-04-07 | +| `0.14.54` | 2024-04-07 | +| `0.13.15` | 2024-04-07 | +| `0.12.29` | 2024-04-07 | +| `0.11.23` | 2024-04-07 | +| `0.10.2` | 2024-04-07 | +| `0.9.7` | 2024-04-07 | +| `0.9.1` | 2024-04-07 | ::: @@ -212,7 +228,7 @@ This demo was last tested on 2023 December 04 against ESBuild 0.19.8 mkdir sheetjs-esb cd sheetjs-esb npm init -y -npm i --save esbuild@0.19.8 +npm i --save esbuild@0.20.2 ``` 1) Install the SheetJS NodeJS module: diff --git a/docz/docs/03-demos/12-static/06-webpack.md b/docz/docs/03-demos/12-static/06-webpack.md index 4d2a189..cc8de8f 100644 --- a/docz/docs/03-demos/12-static/06-webpack.md +++ b/docz/docs/03-demos/12-static/06-webpack.md @@ -57,9 +57,23 @@ flowchart LR ### Webpack Config -A special rule should be added to `module.rules`: +The Webpack configuration is normally saved to `webpack.config.js`. -```js title="webpack.config.js" +#### Required Settings + +`module.rules` is an array of rule objects that controls module synthesis.[^2] +For the SheetJS Webpack integration, the following properties are required: + +- `test` describes whether the rule is relevant. If the property is a regular +expression, Webpack will test the filename against the `test` property. + +- `use` lists the loaders that will process files matching the `test`. The +loaders are specified using the `loader` property of the loader object. + +The following example instructs Webpack to use the `sheetjs-loader.js` script +when the file name ends in `.numbers` or `.xls` or `.xlsx` or `.xlsb`: + +```js title="webpack.config.js (define loader)" // ... module.exports = { // ... @@ -68,7 +82,7 @@ module.exports = { // highlight-start { /* `test` matches file extensions */ - test: /\.(numbers|xls|xlsx|xlsb)/, + test: /\.(numbers|xls|xlsx|xlsb)$/, /* use the loader script */ use: [ { loader: './sheetjs-loader' } ] } @@ -78,24 +92,21 @@ module.exports = { }; ``` -Hot Module Replacement enables reloading when files are updated: +#### Recommended Settings -```js title="webpack.config.js" -// ... -module.exports = { - // ... - // highlight-start - devServer: { - static: './dist', - hot: true, - } - // highlight-end -}; -``` +It is strongly recommended to enable other Webpack features: -It is strongly recommended to add an alias to simplify imports: +- `resolve.alias` defines path aliases. If data files are stored in one folder, +an alias ensures that each page can reference the files using the same name[^3]. -```js title="webpack.config.js" +- `devServer.hot` enables "hot module replacement"[^4], ensuring that pages will +refresh in development mode when spreadsheets are saved. + +The following example instructs Webpack to treat `~` as the root of the project +(so `~/data/pres.xlsx` refers to `pres.xlsx` in the data folder) and to enable +live reloading: + +```js title="webpack.config.js (other recommended settings)" // ... module.exports = { // ... @@ -107,21 +118,32 @@ module.exports = { } }, // highlight-end + // ... + // highlight-start + /* enable live reloading in development mode */ + devServer: { static: './dist', hot: true } + // highlight-end }; ``` ### SheetJS Loader -The SheetJS loader script must export a `raw` property that is set to `true`. +The SheetJS loader script must be saved to the script referenced in the Webpack +configuration (`sheetjs-loader.js`). + +As with [ViteJS](/docs/demos/static/vitejs), Webpack will interpret data as +UTF-8 strings. This corrupts binary formats including XLSX and XLS. To suppress +this behavior and instruct Webpack to pass a NodeJS `Buffer` object, the loader +script must export a `raw` property that is set to `true`[^5]. The base export is expected to be the loader function. The loader receives the -file bytes as a Buffer, which can be parsed with the SheetJS `read` method[^2]. -`read` returns a SheetJS workbook object[^3]. +file bytes as a Buffer, which can be parsed with the SheetJS `read` method[^6]. +`read` returns a SheetJS workbook object[^7]. The loader in this demo will parse the workbook, pull the first worksheet, and -generate an array of row objects using the `sheet_to_json` method[^4]: +generate an array of row objects using the `sheet_to_json` method[^8]: -```js title="sheetjs-loader.js" +```js title="sheetjs-loader.js (Webpack loader)" const XLSX = require("xlsx"); function loader(content) { @@ -131,8 +153,11 @@ function loader(content) { var data = XLSX.utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]]); return `export default JSON.parse('${JSON.stringify(data)}')`; } + /* ensure the function receives a Buffer */ loader.raw = true; + +/* export the loader */ module.exports = loader; ``` @@ -141,7 +166,7 @@ module.exports = loader; Spreadsheets can be imported using the plugin. Assuming `pres.xlsx` is stored in the `data` subfolder, `~/data/pres.xlsx` can be imported from any script: -```js title="src/index.js" +```js title="src/index.js (main script)" import data from '~/data/pres.xlsx'; /* `data` is an array of objects from data/pres.xlsx */ @@ -159,7 +184,7 @@ document.body.appendChild(elt); :::note Tested Deployments -This demo was last tested on 2023 December 04 against Webpack 5.89.0 +This demo was last tested on 2024 April 06 against Webpack 5.91.0 ::: @@ -171,7 +196,7 @@ This demo was last tested on 2023 December 04 against Webpack 5.89.0 mkdir sheetjs-wp5 cd sheetjs-wp5 npm init -y -npm install webpack@5.89.0 webpack-cli@5.1.4 webpack-dev-server@4.15.1 --save +npm install webpack@5.91.0 webpack-cli@5.1.4 webpack-dev-server@5.0.4 --save mkdir -p dist mkdir -p src mkdir -p data @@ -235,7 +260,7 @@ module.exports = { module: { rules: [ { - test: /\.(numbers|xls|xlsx|xlsb)/, + test: /\.(numbers|xls|xlsx|xlsb)$/, use: [ { loader: './sheetjs-loader' } ] } ] @@ -287,9 +312,10 @@ The terminal will print URLs for the development server: It should display a table of Presidents with "Name" and "Index" columns -10) Add a new row to the spreadsheet and save the file. +10) Add a new row to the spreadsheet (set `A7` to "SheetJS Dev" and `B7` to 47) +and save the file. -Upon saving, the page should refresh with the new data. +After saving the file, the page should automatically refresh with the new data. ### Static Site Test @@ -320,6 +346,10 @@ To verify that the data was added to the page, append `main.js` to the URL president names. It will not include SheetJS library references! [^1]: See ["Plugins"](https://webpack.js.org/concepts/plugins/) in the Webpack documentation. -[^2]: See [`read` in "Reading Files"](/docs/api/parse-options) -[^3]: See ["Workbook Object"](/docs/csf/book) -[^4]: See [`sheet_to_json` in "Utilities"](/docs/api/utilities/array#array-output) +[^2]: See [`module.rules`](https://webpack.js.org/configuration/module/#modulerules) in the Webpack documentation. +[^3]: See [`resolve.alias`](https://webpack.js.org/configuration/resolve/#resolvealias) in the Webpack documentation. +[^4]: See ["Hot Module Replacement"](https://webpack.js.org/concepts/hot-module-replacement/) in the Webpack documentation. +[^5]: See ["Raw" Loader](https://webpack.js.org/api/loaders/#raw-loader) in the Webpack documentation. +[^6]: See [`read` in "Reading Files"](/docs/api/parse-options) +[^7]: See ["Workbook Object"](/docs/csf/book) +[^8]: See [`sheet_to_json` in "Utilities"](/docs/api/utilities/array#array-output) diff --git a/docz/docs/03-demos/17-mobile/02-nativescript.md b/docz/docs/03-demos/17-mobile/02-nativescript.md index 594f46b..12f3d1a 100644 --- a/docz/docs/03-demos/17-mobile/02-nativescript.md +++ b/docz/docs/03-demos/17-mobile/02-nativescript.md @@ -13,7 +13,7 @@ import CodeBlock from '@theme/CodeBlock'; export const g = {style: {color:"green"}}; export const r = {style: {color:"red"}}; -export const y = {style: {color:"yellow"}}; +export const y = {style: {color:"gold"}}; [NativeScript](https://nativescript.org/) is a mobile app framework. It builds iOS and Android apps that use JavaScript for describing layouts and events. @@ -25,7 +25,8 @@ This demo uses NativeScript and SheetJS to process and generate spreadsheets. We'll explore how to load SheetJS in a NativeScript app; parse and generate spreadsheets stored on the device; and fetch and parse remote files. -The "Complete Example" creates an app that looks like the screenshots below: +The ["Complete Example"](#complete-example) creates an app that looks like the +screenshots below: @@ -51,12 +52,20 @@ Angular and TypeScript is assumed. This demo was tested in the following environments: -| OS | Type | Device | NS | Date | -|:-----------|:-----|:--------------------|:---------|:-----------| -| Android 34 | Sim | Pixel 3a | `8.6.1` | 2023-12-04 | -| iOS 17.0.1 | Sim | iPhone SE (3rd gen) | `8.6.1` | 2023-12-04 | -| Android 29 | Real | NVIDIA Shield | `8.6.1` | 2023-12-04 | -| iOS 15.1 | Real | iPad Pro | `8.6.1` | 2023-12-04 | +**Simulators** + +| OS | Device | NS | Dev Platform | Date | +|:-----------|:--------------------|:---------|:-------------|:-----------| +| Android 34 | Pixel 3a | `8.6.1` | `darwin-x64` | 2023-12-04 | +| iOS 17.0.1 | iPhone SE (3rd gen) | `8.6.1` | `darwin-x64` | 2023-12-04 | +| Android 34 | Pixel 3a | `8.6.5` | `win10-x64` | 2024-04-07 | + +**Real Devices** + +| OS | Device | NS | Date | +|:-----------|:--------------------|:---------|:-----------| +| Android 30 | NVIDIA Shield | `8.6.5` | 2024-04-07 | +| iOS 15.1 | iPad Pro | `8.6.1` | 2023-12-04 | ::: @@ -88,9 +97,9 @@ imported from any component or script in the app. The `@nativescript/core/file-system` package provides classes for file access. The `File` class does not support binary data, but the file access singleton -from `@nativescript/core` does support reading and writing `ArrayBuffer`. +from `@nativescript/core` does support reading and writing `ArrayBuffer` data. -Reading and writing data require a URL. The following snippet searches typical +Reading and writing data require a URL. The following snippet searches typical document folders for a specified filename: ```ts @@ -102,6 +111,68 @@ function get_url_for_filename(filename: string): string { } ``` +### App Configuration + +Due to privacy concerns, apps must request file access. There are special APIs +for accessing data and are subject to change in future platform versions. + +
Technical Details (click to show) + +**Android** + +Android security has evolved over the years. In newer Android versions, the +following workarounds were required: + +- `READ_EXTERNAL_STORAGE` and `WRITE_EXTERNAL_STORAGE` allow apps to access +files outside of the app scope. These are required for scoped storage access. + +When the demo was last tested, this option was enabled by default. + +- `android:requestLegacyExternalStorage="true"` enabled legacy behavior in some +older releases. + +The manifest is saved to `App_Resources/Android/src/main/AndroidManifest.xml`: + +```xml title="App_Resources/Android/src/main/AndroidManifest.xml (add highlighted lines)" + + android:requestLegacyExternalStorage="true" + android:name="com.tns.NativeScriptApplication" + android:allowBackup="true" + android:icon="@mipmap/ic_launcher" + android:label="@string/app_name" + android:theme="@style/AppTheme" + android:hardwareAccelerated="true"> +``` + +- Permissions must be explicitly requested. + +`@nativescript-community/perms` is a community module for managing permissions: + +```ts title="App script or component" +import { request } from '@nativescript-community/perms'; +import { File } from '@nativescript/core/file-system'; +``` + +Storage access must be requested before writing data: + +```ts title="App script or component (before writing file)" + /* request permissions */ + const res = await request('storage'); +``` + +The external paths can be resolved using the low-level APIs: + +```ts title="App script or component (writing to downloads folder)" + /* find Downloads folder */ + const dl_dir = android.os.Environment.DIRECTORY_DOWNLOADS; + const dl = android.os.Environment.getExternalStoragePublicDirectory(dl_dir).getAbsolutePath(); + /* write to file */ + File.fromPath(dl + "/SheetJSNS.xls").writeSync(data); +``` + +
+ ### Reading Local Files `getFileAccess().readBufferAsync` can read data into an `ArrayBuffer` object. @@ -204,10 +275,9 @@ When the demo was last tested, the latest version of the Android API was 34. NativeScript did not support that API level. The exact error message from `npx -p nativescript ns doctor ios` clearly stated supported versions: -(x is red, body text is yellow) -``` -✖ No compatible version of the Android SDK Build-tools are installed on your system. You can install any version in the following range: '>=23 <=33'. -``` +
+ No compatible version of the Android SDK Build-tools are installed on your system. You can install any version in the following range: '>=23 <=33'.
+
The SDK Platform `Android 13.0 ("Tiramisu")` was compatible with NativeScript. Until NativeScript properly supports API level 34, "Tiramisu" must be used. @@ -257,7 +327,7 @@ In the last macOS test, the following output was displayed:
  Getting environment information{'\n'}
 {'\n'}
-No issues were detected.{'\n'}
+No issues were detected.{'\n'}
  Xcode is installed and is configured properly.{'\n'}
  xcodeproj is installed and is configured properly.{'\n'}
  CocoaPods are installed.{'\n'}
@@ -291,7 +361,8 @@ npx -p nativescript ns run android
 (this may take a while)
 
 Once the simulator launches and the test app is displayed, end the script by
-selecting the terminal and entering the key sequence `CTRL + C`
+selecting the terminal and entering the key sequence `CTRL + C`. On Windows,
+if prompted to `Terminate batch job`, type `y` and press Enter.
 
 :::note pass
 
@@ -303,12 +374,6 @@ Emulator start failed with: No emulator image available for device identifier 'u
 
 :::
 
-6) From the project folder, install the library:
-
-{`\
-npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
-
-
 ### Add SheetJS
 
 :::note pass
@@ -317,10 +382,16 @@ The goal of this section is to display the SheetJS library version number.
 
 :::
 
+6) From the project folder, install the SheetJS NodeJS module:
+
+{`\
+npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
+
+
 7) Edit `src/app/item/items.component.ts` so that the component imports the
 SheetJS version string and adds it to a `version` variable in the component:
 
-```ts title="src/app/item/items.component.ts"
+```ts title="src/app/item/items.component.ts (add highlighted lines)"
 // highlight-next-line
 import { version } from 'xlsx';
 import { Component, OnInit } from '@angular/core'
@@ -339,7 +410,7 @@ export class ItemsComponent implements OnInit {
 8) Edit the template `src/app/item/items.component.html` to reference `version`
 in the title of the action bar:
 
-```xml title="src/app/item/items.component.html"
+```xml title="src/app/item/items.component.html (edit highlighted line)"
 
 
 
@@ -347,7 +418,7 @@ in the title of the action bar:
 
 ```
 
-9) Relaunch the app in the Android simulator:
+9) End the script and relaunch the app in the Android simulator:
 
 ```bash
 npx -p nativescript ns run android
@@ -361,7 +432,7 @@ The title bar should show the version.
 
 10) Add the Import and Export buttons to the template:
 
-```xml title="src/app/item/items.component.html"
+```xml title="src/app/item/items.component.html (add highlighted lines)"
 
 
 
@@ -424,13 +495,19 @@ export class ItemsComponent implements OnInit {
 }
 ```
 
-12) Restart the app process. Two buttons should show up at the top:
+12) End the script and relaunch the app in the Android simulator:
+
+```bash
+npx -p nativescript ns run android
+```
+
+Two buttons should appear just below the header:
 
 ![NativeScript Step 5](pathname:///nativescript/step5.png)
 
 13) Implement import and export by adding the highlighted lines:
 
-```ts title="src/app/item/items.component.ts"
+```ts title="src/app/item/items.component.ts (add highlighted lines)"
   /* Import button */
   async import() {
     // highlight-start
@@ -486,21 +563,22 @@ export class ItemsComponent implements OnInit {
 
 ```bash
 npx -p nativescript ns run android
-````
+```
 
 If the app does not automatically launch, manually open the `SheetJSNS` app.
 
 15) Tap "Export File". A dialog will print where the file was written. Typically
 the URL is `/data/user/0/org.nativescript.SheetJSNS/files/SheetJSNS.xls`
 
-16) Pull the file from the simulator:
+16) Pull the file from the simulator. The following commands should be run in a
+new terminal or PowerShell window:
 
 ```bash
 adb root
 adb pull /data/user/0/org.nativescript.SheetJSNS/files/SheetJSNS.xls SheetJSNS.xls
 ```
 
-If the emulator cannot be rooted:
+If the emulator cannot be rooted, the following command works in macOS:
 
 ```bash
 adb shell "run-as org.nativescript.SheetJSNS cat /data/user/0/org.nativescript.SheetJSNS/files/SheetJSNS.xls" > SheetJSNS.xls
@@ -510,8 +588,9 @@ adb shell "run-as org.nativescript.SheetJSNS cat /data/user/0/org.nativescript.S
 
 After the header row, insert a row with cell A2 = 0, B2 = SheetJS, C2 = Library:
 
-```
+```text
 id | name       | role
+# highlight-next-line
  0 | SheetJS    | Library
  1 | Ter Stegen | Goalkeeper
  3 | Piqué      | Defender
@@ -524,7 +603,7 @@ id | name       | role
 adb push SheetJSNS.xls /data/user/0/org.nativescript.SheetJSNS/files/SheetJSNS.xls
 ```
 
-If the emulator cannot be rooted:
+If the emulator cannot be rooted, the following command works in macOS:
 
 ```bash
 dd if=SheetJSNS.xls | adb shell "run-as org.nativescript.SheetJSNS dd of=/data/user/0/org.nativescript.SheetJSNS/files/SheetJSNS.xls"
@@ -533,8 +612,20 @@ dd if=SheetJSNS.xls | adb shell "run-as org.nativescript.SheetJSNS dd of=/data/u
 19) Tap "Import File".  A dialog will print the path of the file that was read.
 The first item in the list will change.
 
+![NativeScript Step 6](pathname:///nativescript/step6.png)
+
 ### iOS
 
+:::warning pass
+
+**iOS testing can only be performed on Apple hardware running macOS!**
+
+Xcode and iOS simulators are not available on Windows or Linux.
+
+Scroll down to ["Fetching Files"](#android-device) for Android device testing.
+
+:::
+
 20) Launch the app in the iOS Simulator:
 
 ```bash
@@ -547,8 +638,9 @@ npx -p nativescript ns run ios
 
 After the header row, insert a row with cell A2 = 0, B2 = SheetJS, C2 = Library:
 
-```
+```text
 id | name       | role
+# highlight-next-line
  0 | SheetJS    | Library
  1 | Ter Stegen | Goalkeeper
  3 | Piqué      | Defender
@@ -566,7 +658,7 @@ The first item in the list will change:
 
 25) In `src/app/item/items.component.ts`, make `ngOnInit` asynchronous:
 
-```ts title="src/app/item/items.component.ts"
+```ts title="src/app/item/items.component.ts (replace existing function)"
   async ngOnInit(): Promise {
     this.items = await this.itemService.getItems()
   }
@@ -605,11 +697,11 @@ export class ItemService {
 }
 ```
 
-27) Relaunch the app in the Android simulator:
+27) End the script and relaunch the app in the Android simulator:
 
 ```bash
 npx -p nativescript ns run android
-````
+```
 
 The app should show Presidential data.
 
@@ -621,7 +713,61 @@ If the device asks to allow USB debugging, tap "Allow".
 
 29) Close any Android / iOS emulators.
 
-30) Build APK and run on device:
+30) Enable "Legacy External Storage" in the Android app. The manifest is stored
+at `App_Resources/Android/src/main/AndroidManifest.xml`:
+
+```xml title="App_Resources/Android/src/main/AndroidManifest.xml (add highlighted line)"
+  
+    android:requestLegacyExternalStorage="true"
+    android:name="com.tns.NativeScriptApplication"
+    android:allowBackup="true"
+    android:icon="@mipmap/ic_launcher"
+    android:label="@string/app_name"
+    android:theme="@style/AppTheme"
+    android:hardwareAccelerated="true">
+```
+
+31) Install the `@nativescript-community/perms` dependency:
+
+```bash
+npm i --save @nativescript-community/perms
+```
+
+32) Add the highlighted lines to `items.component.ts`:
+
+- Import `File` from NativeScript core and `request` from the new dependency:
+
+```ts title="items.component.ts (add highlighted lines)"
+import { Dialogs, getFileAccess, Utils } from '@nativescript/core';
+// highlight-start
+import { request } from '@nativescript-community/perms';
+import { Folder, knownFolders, path, File } from '@nativescript/core/file-system';
+// highlight-end
+import { Component, OnInit } from '@angular/core'
+// ...
+```
+
+- Add a new write operation to the `export` method:
+
+```ts title="items.component.ts (add highlighted lines)"
+      /* attempt to save Uint8Array to file */
+      await getFileAccess().writeBufferAsync(url, global.isAndroid ? (Array.from(u8) as any) : u8);
+      await Dialogs.alert(`Wrote to SheetJSNS.xls at ${url}`);
+
+      /* highlight-start */
+      if(global.isAndroid) {
+        /* request permissions */
+        const res = await request('storage');
+        /* write to Downloads folder */
+        const dl = android.os.Environment.getExternalStoragePublicDirectory(android.os.Environment.DIRECTORY_DOWNLOADS).getAbsolutePath();
+        File.fromPath(dl + "/SheetJSNS.xls").writeSync(Array.from(u8));
+      }
+      /* highlight-end */
+    } catch(e) { await Dialogs.alert(e.message); }
+```
+
+33) Build APK and run on device:
 
 ```bash
 npx -p nativescript ns run android
@@ -630,15 +776,27 @@ npx -p nativescript ns run android
 If the Android emulators are closed and an Android device is connected, the last
 command will build an APK and install on the device.
 
+
Android Device Testing (click to hide) + +When the app launches, if the SheetJS library is loaded and if the device is +connected to the Internet, a list of Presidents should be displayed. + +Tap "Export File". The app will show an alert. Tap "OK". + +Switch to the "Files" app and open the "Downloads" folder. There should be a new +file named `SheetJSNS.xls`. + +
+ ### iOS Device -31) Connect an iOS device using a USB cable +34) Connect an iOS device using a USB cable -32) Close any Android / iOS emulators. +35) Close any Android / iOS emulators. -33) Enable developer code signing certificates[^9] +36) Enable developer code signing certificates[^9] -34) Run on device: +37) Run on device: ```bash npx -p nativescript ns run ios diff --git a/docz/docs/03-demos/19-desktop/02-nwjs.md b/docz/docs/03-demos/19-desktop/02-nwjs.md index ee37f3e..9747137 100644 --- a/docz/docs/03-demos/19-desktop/02-nwjs.md +++ b/docz/docs/03-demos/19-desktop/02-nwjs.md @@ -111,16 +111,14 @@ input.click(); This demo was tested in the following environments: -| OS and Version | Architecture | NW.js | Date | -|:---------------|:-------------|:---------|:-----------| -| macOS 14.3.1 | `darwin-x64` | `0.85.0` | 2024-03-12 | -| macOS 14.1.2 | `darwin-arm` | `0.82.0` | 2023-12-01 | -| Windows 10 | `win10-x64` | `0.83.0` | 2024-03-04 | -| Windows 11 | `win11-arm` | `0.82.0` | 2023-12-01 | -| Linux (HoloOS) | `linux-x64` | `0.85.0` | 2024-03-12 | - -There is no official Linux ARM64 release. The community release[^1] was tested -and verified on 2023-09-27. +| OS and Version | Architecture | NW.js | Date | Notes | +|:---------------|:-------------|:---------|:-----------|:---------------------| +| macOS 14.3.1 | `darwin-x64` | `0.85.0` | 2024-03-12 | | +| macOS 14.1.2 | `darwin-arm` | `0.82.0` | 2023-12-01 | | +| Windows 10 | `win10-x64` | `0.83.0` | 2024-03-04 | | +| Windows 11 | `win11-arm` | `0.82.0` | 2023-12-01 | | +| Linux (HoloOS) | `linux-x64` | `0.85.0` | 2024-03-12 | | +| Linux (Ubuntu) | `linux-arm` | `0.60.0` | 2023-09-27 | Unofficial build[^1] | ::: diff --git a/docz/docs/03-demos/27-local/01-file.md b/docz/docs/03-demos/27-local/01-file.md index d5739d7..48b35fc 100644 --- a/docz/docs/03-demos/27-local/01-file.md +++ b/docz/docs/03-demos/27-local/01-file.md @@ -447,14 +447,14 @@ This browser demo was tested in the following environments: | Browser | Date | |:------------|:-----------| -| Chrome 119 | 2023-11-30 | +| Chrome 122 | 2024-04-07 | Some lesser-used browsers do not support File System Access API: | Browser | Date | |:------------|:-----------| -| Safari 17.4 | 2024-03-23 | -| Firefox 119 | 2023-11-04 | +| Safari 17.4 | 2024-04-07 | +| Firefox 124 | 2024-04-07 | ::: diff --git a/docz/docs/09-miscellany/05-contributing.md b/docz/docs/09-miscellany/05-contributing.md index aa555d1..1688120 100644 --- a/docz/docs/09-miscellany/05-contributing.md +++ b/docz/docs/09-miscellany/05-contributing.md @@ -41,9 +41,9 @@ These instructions were tested on the following platforms: |:------------------------------|:-------------|:-----------| | Linux (Steam Deck Holo x64) | `linux-x64` | 2024-04-01 | | Linux (Ubuntu 18 AArch64) | `linux-arm` | 2023-12-01 | -| MacOS 14.4 (x64) | `darwin-x64` | 2024-03-15 | +| MacOS 14.4 (x64) | `darwin-x64` | 2024-04-04 | | MacOS 14.1.2 (ARM64) | `darwin-arm` | 2023-12-01 | -| Windows 10 (x64) + WSL Ubuntu | `win10-x64` | 2024-03-04 | +| Windows 10 (x64) + WSL Ubuntu | `win10-x64` | 2024-04-04 | | Windows 11 (x64) + WSL Ubuntu | `win11-x64` | 2023-10-14 | | Windows 11 (ARM) + WSL Ubuntu | `win11-arm` | 2023-09-18 | diff --git a/docz/static/knockout/knockout.html b/docz/static/knockout/knockout.html index 2eafde5..c8444bd 100644 --- a/docz/static/knockout/knockout.html +++ b/docz/static/knockout/knockout.html @@ -10,6 +10,7 @@ +
diff --git a/docz/static/knockout/knockout2.html b/docz/static/knockout/knockout2.html
new file mode 100644
index 0000000..45d4d6e
--- /dev/null
+++ b/docz/static/knockout/knockout2.html
@@ -0,0 +1,110 @@
+
+
+
+
+
+  SheetJS + KnockoutJS
+  
+  
+
+  
+  
+  
+  
+
+
+
+SheetJS + KnockoutJS demo
+
+The core library can be used as-is in KnockoutJS applications.
+The Community Edition README details some common use cases.
+We also have some more public demos
+
+This demo shows:
+- view model backed by an array of arrays
+- file import that refreshes the model
+- table of editable `input` elements that are bound to the model
+- file export based on the model
+
+Sample Spreadsheet
+
+ + +
iOS
+ + + +
+ + + + + + + + + diff --git a/docz/static/knockout/knockout3.html b/docz/static/knockout/knockout3.html new file mode 100644 index 0000000..7055cb5 --- /dev/null +++ b/docz/static/knockout/knockout3.html @@ -0,0 +1,110 @@ + + + + + + SheetJS + KnockoutJS + + + + + + + + + +
+SheetJS + KnockoutJS demo
+
+The core library can be used as-is in KnockoutJS applications.
+The Community Edition README details some common use cases.
+We also have some more public demos
+
+This demo shows:
+- view model backed by an array of arrays
+- file import that refreshes the model
+- table of editable `input` elements that are bound to the model
+- file export based on the model
+
+Sample Spreadsheet
+
+ + + + + + +
+ + + + + + + + + diff --git a/docz/static/nativescript/and.png b/docz/static/nativescript/and.png index b37ef13..df699ab 100644 Binary files a/docz/static/nativescript/and.png and b/docz/static/nativescript/and.png differ diff --git a/docz/static/nativescript/step4.png b/docz/static/nativescript/step4.png index 217da3b..0b52c65 100644 Binary files a/docz/static/nativescript/step4.png and b/docz/static/nativescript/step4.png differ diff --git a/docz/static/nativescript/step5.png b/docz/static/nativescript/step5.png index 1a6375a..8dc35e1 100644 Binary files a/docz/static/nativescript/step5.png and b/docz/static/nativescript/step5.png differ diff --git a/docz/static/nativescript/step6.png b/docz/static/nativescript/step6.png new file mode 100644 index 0000000..2e49970 Binary files /dev/null and b/docz/static/nativescript/step6.png differ