nativescript windows + android test

This commit is contained in:
SheetJS 2024-04-07 23:55:10 -04:00
parent ecca85c9dc
commit 24eaed9f6b
18 changed files with 844 additions and 124 deletions

@ -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: '&gt;=23 &lt;=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>

@ -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>

@ -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>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB