nativescript windows + android test
@ -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:
|
||||
|
||||
<CodeBlock language="bash">{`\
|
||||
@ -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.
|
||||
|
||||
:::
|
||||
|
||||
</TabItem>
|
||||
|
@ -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 |
|
||||
|
||||
:::
|
||||
|
@ -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) |
|
||||
|
||||
:::
|
||||
|
||||
|
@ -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 <https://s2c.sheetjs.com>:
|
||||
|
||||
```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 <https://s2c.sheetjs.com> 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 <https://s2c.sheetjs.com> 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);
|
||||
```
|
||||
|
||||
<details><summary><b>Complete Code Snippet</b> (click to show)</summary>
|
||||
|
||||
```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);
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details><summary><b>Live demo</b> (click to show)</summary>
|
||||
|
||||
@ -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 });
|
||||
```
|
||||
|
||||
<details><summary><b>Complete Code Snippet</b> (click to show)</summary>
|
||||
|
||||
```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 });
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details><summary><b>Live demo</b> (click to show)</summary>
|
||||
|
||||
This demo uses `fetch` to upload data to <https://s2c.sheetjs.com>. 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 });
|
||||
```
|
||||
|
||||
<details><summary><b>Complete Code Snippet</b> (click to show)</summary>
|
||||
|
||||
```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 });
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details><summary><b>Live demo</b> (click to show)</summary>
|
||||
|
||||
This demo uses `axios` to upload data to <https://s2c.sheetjs.com>. 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);
|
||||
```
|
||||
|
||||
<details><summary><b>Complete Code Snippet</b> (click to show)</summary>
|
||||
|
||||
```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);
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details><summary><b>Live demo</b> (click to show)</summary>
|
||||
|
||||
This demo uses `superagent` to upload data to <https://s2c.sheetjs.com>. 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.
|
||||
|
||||
</details>
|
||||
|
||||
### 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 |
|
||||
|
||||
:::
|
||||
|
||||
<details><summary><b>Complete Example</b> (click to show)</summary>
|
||||
|
||||
This demo uses `request` to upload data to <https://s2c.sheetjs.com>. 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:
|
||||
|
||||
<CodeBlock language="bash">{`\
|
||||
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz request`}
|
||||
</CodeBlock>
|
||||
|
||||
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!**
|
||||
|
||||
:::
|
||||
|
||||
</details>
|
||||
|
||||
## 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)
|
||||
|
@ -335,7 +335,7 @@ The script will create a file `SheetJSDenoDOM.xlsx` that can be opened.
|
||||
</details>
|
||||
|
||||
[^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)
|
@ -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:
|
||||
|
@ -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)
|
||||
|
@ -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:
|
||||
|
||||
<table><thead><tr>
|
||||
<th><a href="#complete-example">iOS</a></th>
|
||||
@ -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.
|
||||
|
||||
<details><summary><b>Technical Details</b> (click to show)</summary>
|
||||
|
||||
**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)"
|
||||
<application
|
||||
<!-- highlight-next-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">
|
||||
```
|
||||
|
||||
- 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);
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### 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'.
|
||||
```
|
||||
<pre>
|
||||
<span {...r}>✖</span> 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'.
|
||||
</pre>
|
||||
|
||||
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:
|
||||
<pre>
|
||||
<span {...g}>✔</span> Getting environment information{'\n'}
|
||||
{'\n'}
|
||||
No issues were detected.{'\n'}
|
||||
<b>No issues were detected.</b>{'\n'}
|
||||
<span {...g}>✔</span> Xcode is installed and is configured properly.{'\n'}
|
||||
<span {...g}>✔</span> xcodeproj is installed and is configured properly.{'\n'}
|
||||
<span {...g}>✔</span> 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:
|
||||
|
||||
<CodeBlock language="bash">{`\
|
||||
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
|
||||
</CodeBlock>
|
||||
|
||||
### 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:
|
||||
|
||||
<CodeBlock language="bash">{`\
|
||||
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
|
||||
</CodeBlock>
|
||||
|
||||
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)"
|
||||
<!-- highlight-next-line -->
|
||||
<ActionBar [title]="version"></ActionBar>
|
||||
|
||||
@ -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)"
|
||||
<ActionBar [title]="version"></ActionBar>
|
||||
|
||||
<!-- highlight-start -->
|
||||
@ -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<void> {
|
||||
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)"
|
||||
<application
|
||||
<!-- highlight-next-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.
|
||||
|
||||
<details open><summary><b>Android Device Testing</b> (click to hide)</summary>
|
||||
|
||||
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`.
|
||||
|
||||
</details>
|
||||
|
||||
### 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
|
||||
|
@ -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] |
|
||||
|
||||
:::
|
||||
|
||||
|
@ -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 |
|
||||
|
||||
:::
|
||||
|
||||
|
@ -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 |
|
||||
|
||||
|
@ -10,6 +10,7 @@
|
||||
<!-- SheetJS js-xlsx library -->
|
||||
<script src="https://cdn.sheetjs.com/xlsx-latest/package/dist/shim.min.js"></script>
|
||||
<script src="https://cdn.sheetjs.com/xlsx-latest/package/dist/xlsx.full.min.js"></script>
|
||||
<meta name="robots" content="noindex">
|
||||
</head>
|
||||
<body>
|
||||
<pre>
|
||||
|
110
docz/static/knockout/knockout2.html
Normal file
@ -0,0 +1,110 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- sheetjs (C) 2013-present SheetJS https://sheetjs.com -->
|
||||
<!-- vim: set ts=2: -->
|
||||
<html ng-app="sjs">
|
||||
<head>
|
||||
<title>SheetJS + KnockoutJS</title>
|
||||
<!-- KnockoutJS -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/2.3.0/knockout-min.js"></script>
|
||||
|
||||
<!-- SheetJS js-xlsx library -->
|
||||
<script src="https://cdn.sheetjs.com/xlsx-latest/package/dist/shim.min.js"></script>
|
||||
<script src="https://cdn.sheetjs.com/xlsx-latest/package/dist/xlsx.full.min.js"></script>
|
||||
<meta name="robots" content="noindex">
|
||||
</head>
|
||||
<body>
|
||||
<pre>
|
||||
<b><a href="https://sheetjs.com">SheetJS + KnockoutJS demo</a></b>
|
||||
|
||||
The core library can be used as-is in KnockoutJS applications.
|
||||
The <a href="https://docs.sheetjs.com">Community Edition README</a> details some common use cases.
|
||||
We also have some <a href="https://docs.sheetjs.com/docs/demos/">more public demos</a>
|
||||
|
||||
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
|
||||
|
||||
<a href="https://sheetjs.com/pres.xlsx">Sample Spreadsheet</a>
|
||||
</pre>
|
||||
|
||||
<input name="xlfile" id="xlf" class="left" style="width: 200px;" type="file">
|
||||
<table data-bind="foreach: aoa">
|
||||
<tr data-bind="foreach: $data">
|
||||
<td><input data-bind="value: $parent[$index()]"/></td>
|
||||
</tr>
|
||||
</table>
|
||||
<script id='aoa' type="text/html"></script>
|
||||
<button id="export">Export Sheet to XLSX</button>
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
/* knockout setup */
|
||||
function ViewModel() {
|
||||
/* use an array of arrays */
|
||||
this.aoa = ko.observableArray([
|
||||
[1,2],
|
||||
[3,4]
|
||||
]);
|
||||
}
|
||||
|
||||
var model = new ViewModel();
|
||||
ko.applyBindings(model);
|
||||
/* do an update to confirm KO was loaded properly */
|
||||
model.aoa([[1,2,3],[4,5,6]]);
|
||||
model.aoa.push([7,8,9]);
|
||||
|
||||
/* set up file input handler */
|
||||
(function() {
|
||||
var input_dom_element = document.getElementById('xlf');
|
||||
function handleFile(e) {
|
||||
var files = e.target.files, f = files[0];
|
||||
var reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
var data = e.target.result;
|
||||
data = new Uint8Array(data);
|
||||
var workbook = XLSX.read(data, {type: 'array'});
|
||||
process_wb(workbook);
|
||||
};
|
||||
reader.readAsArrayBuffer(f);
|
||||
}
|
||||
input_dom_element.addEventListener('change', handleFile, false);
|
||||
})();
|
||||
|
||||
/* update model */
|
||||
function process_wb(wb) {
|
||||
/* pull first worksheet */
|
||||
var ws = wb.Sheets[wb.SheetNames[0]];
|
||||
/* convert to AOA */
|
||||
var aoa = XLSX.utils.sheet_to_json(ws, {header:1});
|
||||
/* update model */
|
||||
model.aoa(aoa);
|
||||
}
|
||||
|
||||
document.getElementById("export").onclick = function() {
|
||||
/* get array of arrays */
|
||||
var data = model.aoa();
|
||||
/* convert to worksheet */
|
||||
var ws = XLSX.utils.aoa_to_sheet(data);
|
||||
/* build new workbook */
|
||||
var wb = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(wb, ws, "Sheet1");
|
||||
/* write file */
|
||||
XLSX.writeFile(wb, "SheetJSKnockoutDemo.xlsx")
|
||||
};
|
||||
</script>
|
||||
<script type="text/javascript">
|
||||
/* eslint no-use-before-define:0 */
|
||||
var _gaq = _gaq || [];
|
||||
_gaq.push(['_setAccount', 'UA-36810333-1']);
|
||||
_gaq.push(['_trackPageview']);
|
||||
|
||||
(function() {
|
||||
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
|
||||
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
|
||||
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
110
docz/static/knockout/knockout3.html
Normal file
@ -0,0 +1,110 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- sheetjs (C) 2013-present SheetJS https://sheetjs.com -->
|
||||
<!-- vim: set ts=2: -->
|
||||
<html ng-app="sjs">
|
||||
<head>
|
||||
<title>SheetJS + KnockoutJS</title>
|
||||
<!-- KnockoutJS -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.5.0/knockout-min.js"></script>
|
||||
|
||||
<!-- SheetJS js-xlsx library -->
|
||||
<script src="https://cdn.sheetjs.com/xlsx-latest/package/dist/shim.min.js"></script>
|
||||
<script src="https://cdn.sheetjs.com/xlsx-latest/package/dist/xlsx.full.min.js"></script>
|
||||
<meta name="robots" content="noindex">
|
||||
</head>
|
||||
<body>
|
||||
<pre>
|
||||
<b><a href="https://sheetjs.com">SheetJS + KnockoutJS demo</a></b>
|
||||
|
||||
The core library can be used as-is in KnockoutJS applications.
|
||||
The <a href="https://docs.sheetjs.com">Community Edition README</a> details some common use cases.
|
||||
We also have some <a href="https://docs.sheetjs.com/docs/demos/">more public demos</a>
|
||||
|
||||
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
|
||||
|
||||
<a href="https://sheetjs.com/pres.xlsx">Sample Spreadsheet</a>
|
||||
</pre>
|
||||
|
||||
<input name="xlfile" id="xlf" class="left" style="width: 200px;" type="file">
|
||||
<table data-bind="foreach: aoa">
|
||||
<tr data-bind="foreach: $data">
|
||||
<td><input data-bind="value: $parent[$index()]"/></td>
|
||||
</tr>
|
||||
</table>
|
||||
<script id='aoa' type="text/html"></script>
|
||||
<button id="export">Export Sheet to XLSX</button>
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
/* knockout setup */
|
||||
function ViewModel() {
|
||||
/* use an array of arrays */
|
||||
this.aoa = ko.observableArray([
|
||||
[1,2],
|
||||
[3,4]
|
||||
]);
|
||||
}
|
||||
|
||||
var model = new ViewModel();
|
||||
ko.applyBindings(model);
|
||||
/* do an update to confirm KO was loaded properly */
|
||||
model.aoa([[1,2,3],[4,5,6]]);
|
||||
model.aoa.push([7,8,9]);
|
||||
|
||||
/* set up file input handler */
|
||||
(function() {
|
||||
var input_dom_element = document.getElementById('xlf');
|
||||
function handleFile(e) {
|
||||
var files = e.target.files, f = files[0];
|
||||
var reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
var data = e.target.result;
|
||||
data = new Uint8Array(data);
|
||||
var workbook = XLSX.read(data, {type: 'array'});
|
||||
process_wb(workbook);
|
||||
};
|
||||
reader.readAsArrayBuffer(f);
|
||||
}
|
||||
input_dom_element.addEventListener('change', handleFile, false);
|
||||
})();
|
||||
|
||||
/* update model */
|
||||
function process_wb(wb) {
|
||||
/* pull first worksheet */
|
||||
var ws = wb.Sheets[wb.SheetNames[0]];
|
||||
/* convert to AOA */
|
||||
var aoa = XLSX.utils.sheet_to_json(ws, {header:1});
|
||||
/* update model */
|
||||
model.aoa(aoa);
|
||||
}
|
||||
|
||||
document.getElementById("export").onclick = function() {
|
||||
/* get array of arrays */
|
||||
var data = model.aoa();
|
||||
/* convert to worksheet */
|
||||
var ws = XLSX.utils.aoa_to_sheet(data);
|
||||
/* build new workbook */
|
||||
var wb = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(wb, ws, "Sheet1");
|
||||
/* write file */
|
||||
XLSX.writeFile(wb, "SheetJSKnockoutDemo.xlsx")
|
||||
};
|
||||
</script>
|
||||
<script type="text/javascript">
|
||||
/* eslint no-use-before-define:0 */
|
||||
var _gaq = _gaq || [];
|
||||
_gaq.push(['_setAccount', 'UA-36810333-1']);
|
||||
_gaq.push(['_trackPageview']);
|
||||
|
||||
(function() {
|
||||
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
|
||||
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
|
||||
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 65 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 28 KiB |
BIN
docz/static/nativescript/step6.png
Normal file
After Width: | Height: | Size: 33 KiB |