2023-01-05 23:33:49 +00:00
|
|
|
---
|
|
|
|
title: NativeScript
|
2023-02-28 11:40:44 +00:00
|
|
|
pagination_prev: demos/static/index
|
2023-01-05 23:33:49 +00:00
|
|
|
pagination_next: demos/desktop/index
|
|
|
|
sidebar_position: 2
|
|
|
|
sidebar_custom_props:
|
|
|
|
summary: JS + Native Elements
|
|
|
|
---
|
|
|
|
|
2023-04-27 09:12:19 +00:00
|
|
|
import current from '/version.js';
|
2023-05-07 13:58:36 +00:00
|
|
|
import CodeBlock from '@theme/CodeBlock';
|
2023-04-27 09:12:19 +00:00
|
|
|
|
2023-09-22 06:32:55 +00:00
|
|
|
The [SheetJS NodeJS Module](/docs/getting-started/installation/nodejs) can be
|
|
|
|
imported from any component or script in the app.
|
2023-01-05 23:33:49 +00:00
|
|
|
|
2023-04-03 09:09:59 +00:00
|
|
|
The "Complete Example" creates an app that looks like the screenshots below:
|
2023-01-05 23:33:49 +00:00
|
|
|
|
2023-04-03 09:09:59 +00:00
|
|
|
<table><thead><tr>
|
|
|
|
<th><a href="#demo">iOS</a></th>
|
2023-05-07 17:43:21 +00:00
|
|
|
<th><a href="#demo">Android</a></th>
|
2023-04-03 09:09:59 +00:00
|
|
|
</tr></thead><tbody><tr><td>
|
2023-01-05 23:33:49 +00:00
|
|
|
|
2023-05-21 07:03:46 +00:00
|
|
|
![iOS screenshot](pathname:///nativescript/ios.png)
|
2023-01-05 23:33:49 +00:00
|
|
|
|
2023-05-07 17:43:21 +00:00
|
|
|
</td><td>
|
|
|
|
|
2023-05-21 07:03:46 +00:00
|
|
|
![Android screenshot](pathname:///nativescript/and.png)
|
2023-05-07 17:43:21 +00:00
|
|
|
|
2023-04-03 09:09:59 +00:00
|
|
|
</td></tr></tbody></table>
|
2023-01-05 23:33:49 +00:00
|
|
|
|
|
|
|
## Integration Details
|
|
|
|
|
2023-04-03 09:09:59 +00:00
|
|
|
The discussion covers the NativeScript + Angular integration. Familiarity with
|
|
|
|
Angular and TypeScript is assumed.
|
|
|
|
|
2023-01-05 23:33:49 +00:00
|
|
|
The `@nativescript/core/file-system` package provides classes for file access.
|
2023-04-03 09:09:59 +00:00
|
|
|
The `File` class does not support binary data, but the file access singleton
|
|
|
|
from `@nativescript/core` does support reading and writing `ArrayBuffer`.
|
2023-01-05 23:33:49 +00:00
|
|
|
|
2023-04-03 09:09:59 +00:00
|
|
|
Reading and writing data require a URL. The following snippet searches typical
|
|
|
|
document folders for a specified filename:
|
2023-01-05 23:33:49 +00:00
|
|
|
|
|
|
|
```ts
|
2023-04-03 09:09:59 +00:00
|
|
|
import { Folder, knownFolders, path } from '@nativescript/core/file-system';
|
2023-01-05 23:33:49 +00:00
|
|
|
|
2023-04-03 09:09:59 +00:00
|
|
|
function get_url_for_filename(filename: string): string {
|
2023-01-05 23:33:49 +00:00
|
|
|
const target: Folder = knownFolders.documents() || knownFolders.ios.sharedPublic();
|
2023-04-03 09:09:59 +00:00
|
|
|
return path.normalize(target.path + "///" + filename);
|
2023-01-05 23:33:49 +00:00
|
|
|
}
|
|
|
|
```
|
|
|
|
|
2023-05-21 07:03:46 +00:00
|
|
|
### Reading Local Files
|
2023-01-05 23:33:49 +00:00
|
|
|
|
2023-04-03 09:09:59 +00:00
|
|
|
`getFileAccess().readBufferAsync` can read data:
|
2023-01-05 23:33:49 +00:00
|
|
|
|
|
|
|
```ts
|
2023-04-03 09:09:59 +00:00
|
|
|
import { getFileAccess } from '@nativescript/core';
|
2023-05-21 07:03:46 +00:00
|
|
|
import { read } from 'xlsx';
|
2023-04-03 09:09:59 +00:00
|
|
|
|
|
|
|
/* find appropriate path */
|
|
|
|
const url = get_url_for_filename("SheetJSNS.xls");
|
|
|
|
|
|
|
|
/* get data */
|
|
|
|
const ab: ArrayBuffer = await getFileAccess().readBufferAsync(url);
|
2023-01-05 23:33:49 +00:00
|
|
|
|
|
|
|
/* read workbook */
|
2023-04-03 09:09:59 +00:00
|
|
|
const wb = read(ab);
|
2023-01-05 23:33:49 +00:00
|
|
|
```
|
|
|
|
|
2023-05-21 07:03:46 +00:00
|
|
|
### Writing Local Files
|
2023-01-05 23:33:49 +00:00
|
|
|
|
2023-05-07 17:43:21 +00:00
|
|
|
`getFileAccess().writeBufferAsync` can write data. iOS supports `Uint8Array`
|
|
|
|
directly but Android requires a true array of numbers:
|
2023-01-05 23:33:49 +00:00
|
|
|
|
|
|
|
```ts
|
2023-04-03 09:09:59 +00:00
|
|
|
import { getFileAccess } from '@nativescript/core';
|
2023-05-21 07:03:46 +00:00
|
|
|
import { write } from 'xlsx';
|
2023-01-05 23:33:49 +00:00
|
|
|
|
2023-04-03 09:09:59 +00:00
|
|
|
/* find appropriate path */
|
|
|
|
const url = get_url_for_filename("SheetJSNS.xls");
|
|
|
|
|
|
|
|
/* generate Uint8Array */
|
|
|
|
const u8: Uint8Array = write(wb, { bookType: 'xls', type: 'binary' });
|
|
|
|
|
|
|
|
/* attempt to save Uint8Array to file */
|
2023-05-07 17:43:21 +00:00
|
|
|
await getFileAccess().writeBufferAsync(url, global.isAndroid ? (Array.from(u8) as any) : u8);
|
2023-01-05 23:33:49 +00:00
|
|
|
```
|
|
|
|
|
2023-05-21 07:03:46 +00:00
|
|
|
### Fetching Remote Files
|
|
|
|
|
|
|
|
`getFile` from `@nativescript/core/http` can download files. After storing the
|
|
|
|
file in a temporary folder, `getFileAccess().readBufferAsync` can read the data:
|
|
|
|
|
|
|
|
```ts
|
|
|
|
import { knownFolders, path, getFileAccess } from '@nativescript/core'
|
|
|
|
import { getFile } from '@nativescript/core/http';
|
|
|
|
import { read } from 'xlsx';
|
|
|
|
|
|
|
|
/* generate temporary path for the new file */
|
|
|
|
const temp: string = path.join(knownFolders.temp().path, "pres.xlsx");
|
|
|
|
|
|
|
|
/* download file */
|
|
|
|
const file = await getFile("https://sheetjs.com/pres.xlsx", temp)
|
|
|
|
|
|
|
|
/* get data */
|
|
|
|
const ab: ArrayBuffer = await getFileAccess().readBufferAsync(file.path);
|
|
|
|
|
|
|
|
/* read workbook */
|
|
|
|
const wb = read(ab);
|
|
|
|
```
|
|
|
|
|
2023-01-05 23:33:49 +00:00
|
|
|
## Demo
|
|
|
|
|
|
|
|
:::note
|
|
|
|
|
2023-05-21 07:03:46 +00:00
|
|
|
This demo was tested on an Intel Mac on 2023 May 21. NativeScript version
|
2023-05-07 17:43:21 +00:00
|
|
|
(as verified with `ns --version`) is `8.5.3`.
|
2023-01-05 23:33:49 +00:00
|
|
|
|
2023-04-03 09:09:59 +00:00
|
|
|
The iOS simulator runs iOS 16.2 on an iPhone 14 Pro Max.
|
2023-01-05 23:33:49 +00:00
|
|
|
|
2023-05-21 07:03:46 +00:00
|
|
|
The Android simulator runs Android 12.0 (S) API 31 on a Pixel 3.
|
|
|
|
|
2023-04-03 09:09:59 +00:00
|
|
|
:::
|
2023-01-05 23:33:49 +00:00
|
|
|
|
2023-04-03 09:09:59 +00:00
|
|
|
0) Follow the official Environment Setup instructions
|
2023-01-05 23:33:49 +00:00
|
|
|
|
2023-05-07 17:43:21 +00:00
|
|
|
### Base Project
|
|
|
|
|
2023-01-05 23:33:49 +00:00
|
|
|
1) Create a skeleton NativeScript + Angular app:
|
|
|
|
|
|
|
|
```bash
|
|
|
|
ns create SheetJSNS --ng
|
|
|
|
```
|
|
|
|
|
|
|
|
2) Launch the app in the iOS simulator to verify that the demo built properly:
|
|
|
|
|
|
|
|
```bash
|
|
|
|
cd SheetJSNS
|
|
|
|
ns run ios
|
|
|
|
```
|
|
|
|
|
|
|
|
(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`
|
|
|
|
|
|
|
|
3) From the project folder, install the library:
|
|
|
|
|
2023-05-07 13:58:36 +00:00
|
|
|
<CodeBlock language="bash">{`\
|
2023-04-27 09:12:19 +00:00
|
|
|
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
|
2023-05-07 13:58:36 +00:00
|
|
|
</CodeBlock>
|
2023-01-05 23:33:49 +00:00
|
|
|
|
|
|
|
4) To confirm the library was loaded, change the title to show the version. The
|
|
|
|
differences are highlighted.
|
|
|
|
|
2023-04-03 09:09:59 +00:00
|
|
|
`src/app/item/items.component.ts` should import the version string:
|
2023-01-05 23:33:49 +00:00
|
|
|
|
|
|
|
```ts title="src/app/item/items.component.ts"
|
|
|
|
// highlight-next-line
|
|
|
|
import { version } from 'xlsx';
|
|
|
|
import { Component, OnInit } from '@angular/core'
|
|
|
|
|
2023-04-03 09:09:59 +00:00
|
|
|
// ...
|
2023-01-05 23:33:49 +00:00
|
|
|
|
|
|
|
export class ItemsComponent implements OnInit {
|
|
|
|
items: Array<Item>
|
|
|
|
// highlight-next-line
|
|
|
|
version = `SheetJS - ${version}`;
|
|
|
|
|
|
|
|
constructor(private itemService: ItemService) {}
|
2023-04-03 09:09:59 +00:00
|
|
|
// ...
|
2023-01-05 23:33:49 +00:00
|
|
|
```
|
|
|
|
|
2023-04-03 09:09:59 +00:00
|
|
|
`src/app/item/items.component.html` should use the version in the title:
|
2023-01-05 23:33:49 +00:00
|
|
|
|
|
|
|
```xml title="src/app/item/items.component.html"
|
|
|
|
<!-- highlight-next-line -->
|
|
|
|
<ActionBar [title]="version"></ActionBar>
|
|
|
|
|
|
|
|
<GridLayout>
|
2023-04-03 09:09:59 +00:00
|
|
|
<!-- ... -->
|
2023-01-05 23:33:49 +00:00
|
|
|
```
|
|
|
|
|
|
|
|
Relaunch the app with `ns run ios` and the title bar should show the version.
|
|
|
|
|
|
|
|
![NativeScript Step 4](pathname:///mobile/nativescript4.png)
|
|
|
|
|
2023-05-21 07:03:46 +00:00
|
|
|
### Local Files
|
|
|
|
|
2023-01-05 23:33:49 +00:00
|
|
|
5) Add the Import and Export buttons to the template:
|
|
|
|
|
|
|
|
```xml title="src/app/item/items.component.html"
|
|
|
|
<ActionBar [title]="version"></ActionBar>
|
|
|
|
|
|
|
|
<!-- highlight-start -->
|
|
|
|
<StackLayout>
|
|
|
|
<StackLayout orientation="horizontal">
|
|
|
|
<Button text="Import File" (tap)="import()" style="padding: 10px"></Button>
|
|
|
|
<Button text="Export File" (tap)="export()" style="padding: 10px"></Button>
|
|
|
|
</StackLayout>
|
|
|
|
<!-- highlight-end -->
|
|
|
|
<ListView [items]="items">
|
2023-04-03 09:09:59 +00:00
|
|
|
<!-- ... -->
|
2023-01-05 23:33:49 +00:00
|
|
|
</ListView>
|
|
|
|
<!-- highlight-next-line -->
|
|
|
|
</StackLayout>
|
|
|
|
```
|
|
|
|
|
|
|
|
```ts title="src/app/item/items.component.ts"
|
|
|
|
// highlight-start
|
|
|
|
import { version, utils, read, write } from 'xlsx';
|
2023-04-03 09:09:59 +00:00
|
|
|
import { Dialogs, getFileAccess } from '@nativescript/core';
|
|
|
|
import { Folder, knownFolders, path } from '@nativescript/core/file-system';
|
2023-01-05 23:33:49 +00:00
|
|
|
// highlight-end
|
|
|
|
import { Component, OnInit } from '@angular/core'
|
|
|
|
|
|
|
|
import { Item } from './item'
|
|
|
|
import { ItemService } from './item.service'
|
|
|
|
|
|
|
|
// highlight-start
|
2023-04-03 09:09:59 +00:00
|
|
|
function get_url_for_filename(filename: string): string {
|
2023-01-05 23:33:49 +00:00
|
|
|
const target: Folder = knownFolders.documents() || knownFolders.ios.sharedPublic();
|
2023-04-03 09:09:59 +00:00
|
|
|
return path.normalize(target.path + "///" + filename);
|
2023-01-05 23:33:49 +00:00
|
|
|
}
|
|
|
|
// highlight-end
|
|
|
|
|
|
|
|
@Component({
|
|
|
|
selector: 'ns-items',
|
|
|
|
templateUrl: './items.component.html',
|
|
|
|
})
|
|
|
|
export class ItemsComponent implements OnInit {
|
|
|
|
items: Array<Item>
|
|
|
|
version: string = `SheetJS - ${version}`;
|
|
|
|
|
|
|
|
constructor(private itemService: ItemService) {}
|
|
|
|
|
|
|
|
ngOnInit(): void {
|
|
|
|
this.items = this.itemService.getItems()
|
|
|
|
}
|
|
|
|
|
|
|
|
// highlight-start
|
|
|
|
/* Import button */
|
|
|
|
async import() {
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Export button */
|
|
|
|
async export() {
|
|
|
|
}
|
|
|
|
// highlight-end
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
Restart the app process and two buttons should show up at the top:
|
|
|
|
|
|
|
|
![NativeScript Step 5](pathname:///mobile/nativescript5.png)
|
|
|
|
|
2023-04-03 09:09:59 +00:00
|
|
|
6) Implement import and export by adding the highlighted lines:
|
2023-01-05 23:33:49 +00:00
|
|
|
|
|
|
|
```ts title="src/app/item/items.component.ts"
|
|
|
|
/* Import button */
|
|
|
|
async import() {
|
|
|
|
// highlight-start
|
|
|
|
/* find appropriate path */
|
2023-04-03 09:09:59 +00:00
|
|
|
const url = get_url_for_filename("SheetJSNS.xls");
|
2023-01-05 23:33:49 +00:00
|
|
|
|
|
|
|
try {
|
2023-04-03 09:09:59 +00:00
|
|
|
await Dialogs.alert(`Attempting to read from SheetJSNS.xls at ${url}`);
|
|
|
|
/* get data */
|
|
|
|
const ab: ArrayBuffer = await getFileAccess().readBufferAsync(url);
|
2023-01-05 23:33:49 +00:00
|
|
|
|
|
|
|
/* read workbook */
|
2023-04-03 09:09:59 +00:00
|
|
|
const wb = read(ab);
|
2023-01-05 23:33:49 +00:00
|
|
|
|
|
|
|
/* grab first sheet */
|
|
|
|
const wsname: string = wb.SheetNames[0];
|
|
|
|
const ws = wb.Sheets[wsname];
|
|
|
|
|
|
|
|
/* update table */
|
|
|
|
this.items = utils.sheet_to_json<Item>(ws);
|
2023-04-03 09:09:59 +00:00
|
|
|
} catch(e) { await Dialogs.alert(e.message); }
|
2023-01-05 23:33:49 +00:00
|
|
|
// highlight-end
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Export button */
|
|
|
|
async export() {
|
|
|
|
// highlight-start
|
|
|
|
/* find appropriate path */
|
2023-04-03 09:09:59 +00:00
|
|
|
const url = get_url_for_filename("SheetJSNS.xls");
|
2023-01-05 23:33:49 +00:00
|
|
|
|
|
|
|
try {
|
|
|
|
/* create worksheet from data */
|
|
|
|
const ws = utils.json_to_sheet(this.items);
|
|
|
|
|
|
|
|
/* create workbook from worksheet */
|
|
|
|
const wb = utils.book_new();
|
|
|
|
utils.book_append_sheet(wb, ws, "Sheet1");
|
|
|
|
|
2023-04-03 09:09:59 +00:00
|
|
|
/* generate Uint8Array */
|
|
|
|
const u8: Uint8Array = write(wb, { bookType: 'xls', type: 'buffer' });
|
2023-01-05 23:33:49 +00:00
|
|
|
|
2023-04-03 09:09:59 +00:00
|
|
|
/* attempt to save Uint8Array to file */
|
2023-05-07 17:43:21 +00:00
|
|
|
await getFileAccess().writeBufferAsync(url, global.isAndroid ? (Array.from(u8) as any) : u8);
|
2023-04-03 09:09:59 +00:00
|
|
|
await Dialogs.alert(`Wrote to SheetJSNS.xls at ${url}`);
|
|
|
|
} catch(e) { await Dialogs.alert(e.message); }
|
2023-01-05 23:33:49 +00:00
|
|
|
// highlight-end
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
2023-04-03 09:09:59 +00:00
|
|
|
### iOS
|
2023-01-05 23:33:49 +00:00
|
|
|
|
2023-04-03 09:09:59 +00:00
|
|
|
Relaunch the app with `ns run ios`
|
2023-01-05 23:33:49 +00:00
|
|
|
|
|
|
|
The app can be tested with the following sequence in the simulator:
|
|
|
|
|
2023-04-03 09:09:59 +00:00
|
|
|
- Tap "Export File". A dialog will print where the file was written
|
2023-01-05 23:33:49 +00:00
|
|
|
|
2023-04-03 09:09:59 +00:00
|
|
|
- Open the file with a spreadsheet editor.
|
2023-01-05 23:33:49 +00:00
|
|
|
|
2023-04-03 09:09:59 +00:00
|
|
|
After the header row, insert a row with cell A2 = 0, B2 = SheetJS, C2 = Library:
|
2023-01-05 23:33:49 +00:00
|
|
|
|
2023-04-03 09:09:59 +00:00
|
|
|
```
|
|
|
|
id | name | role
|
|
|
|
0 | SheetJS | Library
|
|
|
|
1 | Ter Stegen | Goalkeeper
|
|
|
|
3 | Piqué | Defender
|
2023-01-05 23:33:49 +00:00
|
|
|
...
|
|
|
|
```
|
|
|
|
|
2023-04-03 09:09:59 +00:00
|
|
|
Restart the app after saving the file.
|
|
|
|
|
|
|
|
- Tap "Import File". A dialog will print the path of the file that was read.
|
2023-01-05 23:33:49 +00:00
|
|
|
The first item in the list will change:
|
|
|
|
|
|
|
|
![NativeScript Step 7](pathname:///mobile/nativescript7.png)
|
|
|
|
|
2023-05-07 17:43:21 +00:00
|
|
|
### Android
|
|
|
|
|
|
|
|
Launch the app with `ns run android`. If the app does not automatically launch,
|
|
|
|
manually open the `SheetJSNS` app.
|
|
|
|
|
|
|
|
The app can be tested with the following sequence in the simulator:
|
|
|
|
|
2023-08-30 03:44:38 +00:00
|
|
|
- Tap "Export File". A dialog will print where the file was written. Typically
|
2023-05-07 17:43:21 +00:00
|
|
|
the URL is `/data/user/0/org.nativescript.SheetJSNS/files/SheetJSNS.xls`
|
|
|
|
|
|
|
|
- Pull the file from the simulator:
|
|
|
|
|
|
|
|
```bash
|
|
|
|
adb root
|
|
|
|
adb pull /data/user/0/org.nativescript.SheetJSNS/files/SheetJSNS.xls SheetJSNS.xls
|
|
|
|
```
|
|
|
|
|
|
|
|
- Open `SheetJSNS.xls` with a spreadsheet editor.
|
|
|
|
|
|
|
|
After the header row, insert a row with cell A2 = 0, B2 = SheetJS, C2 = Library:
|
|
|
|
|
|
|
|
```
|
|
|
|
id | name | role
|
|
|
|
0 | SheetJS | Library
|
|
|
|
1 | Ter Stegen | Goalkeeper
|
|
|
|
3 | Piqué | Defender
|
|
|
|
...
|
|
|
|
```
|
|
|
|
|
|
|
|
- Push the file back to the simulator:
|
|
|
|
|
|
|
|
```bash
|
|
|
|
adb push SheetJSNS.xls /data/user/0/org.nativescript.SheetJSNS/files/SheetJSNS.xls
|
|
|
|
```
|
|
|
|
|
|
|
|
- Tap "Import File". A dialog will print the path of the file that was read.
|
2023-05-21 07:03:46 +00:00
|
|
|
The first item in the list will change.
|
|
|
|
|
|
|
|
### Fetching Files
|
|
|
|
|
|
|
|
7) In `src/app/item/items.component.ts`, make `ngOnInit` asynchronous:
|
|
|
|
|
|
|
|
```ts title="src/app/item/items.component.ts"
|
|
|
|
async ngOnInit(): Promise<void> {
|
|
|
|
this.items = await this.itemService.getItems()
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
8) Replace `item.service.ts` with the following:
|
|
|
|
|
|
|
|
```ts title="src/app/item/item.service.ts"
|
|
|
|
import { Injectable } from '@angular/core'
|
|
|
|
|
|
|
|
import { knownFolders, path, getFileAccess } from '@nativescript/core'
|
|
|
|
import { getFile } from '@nativescript/core/http';
|
|
|
|
import { read, utils } from 'xlsx';
|
|
|
|
|
|
|
|
import { Item } from './item'
|
|
|
|
interface IPresident { Name: string; Index: number };
|
|
|
|
|
|
|
|
@Injectable({ providedIn: 'root' })
|
|
|
|
export class ItemService {
|
|
|
|
private items: Array<Item>;
|
|
|
|
|
|
|
|
async getItems(): Promise<Array<Item>> {
|
|
|
|
/* fetch https://sheetjs.com/pres.xlsx */
|
|
|
|
const temp: string = path.join(knownFolders.temp().path, "pres.xlsx");
|
|
|
|
const ab = await getFile("https://sheetjs.com/pres.xlsx", temp)
|
|
|
|
/* read the temporary file */
|
|
|
|
const wb = read(await getFileAccess().readBufferAsync(ab.path));
|
|
|
|
/* translate the first worksheet to the required Item type */
|
|
|
|
const data = utils.sheet_to_json<IPresident>(wb.Sheets[wb.SheetNames[0]]);
|
|
|
|
return this.items = data.map((pres, id) => ({id, name: pres.Name, role: ""+pres.Index} as Item));
|
|
|
|
}
|
|
|
|
|
|
|
|
getItem(id: number): Item {
|
|
|
|
return this.items.filter((item) => item.id === id)[0]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
Relaunching the app in iOS or Android simulator should show Presidential data.
|