ns-binary
@ -10,78 +10,83 @@ sidebar_custom_props:
|
||||
The [NodeJS Module](/docs/getting-started/installation/nodejs) can be imported
|
||||
from the main entrypoint or any script in the project.
|
||||
|
||||
:::warning Binary Data issues
|
||||
The "Complete Example" creates an app that looks like the screenshots below:
|
||||
|
||||
NativeScript will not safely transmit binary or UTF-8 strings. XLSB, NUMBERS,
|
||||
XLSX, XLS, ODS, SYLK, and DBF exports are known to be mangled.
|
||||
<table><thead><tr>
|
||||
<th><a href="#demo">iOS</a></th>
|
||||
</tr></thead><tbody><tr><td>
|
||||
|
||||
This is a known NativeScript bug.
|
||||
![iOS screenshot](pathname:///mobile/nsios.png)
|
||||
|
||||
This demo will focus on ASCII CSV files. Once the bug is resolved, XLSX and
|
||||
other formats will be supported.
|
||||
|
||||
:::
|
||||
</td></tr></tbody></table>
|
||||
|
||||
## Integration Details
|
||||
|
||||
The `@nativescript/core/file-system` package provides classes for file access.
|
||||
The discussion covers the NativeScript + Angular integration. Familiarity with
|
||||
Angular and TypeScript is assumed.
|
||||
|
||||
Reading and writing data require a file handle. The following snippet searches
|
||||
typical document folders for a specified filename:
|
||||
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`.
|
||||
|
||||
Reading and writing data require a URL. The following snippet searches typical
|
||||
document folders for a specified filename:
|
||||
|
||||
```ts
|
||||
import { File, Folder, knownFolders, path } from '@nativescript/core/file-system';
|
||||
import { Folder, knownFolders, path } from '@nativescript/core/file-system';
|
||||
|
||||
function get_handle_for_filename(filename: string): File {
|
||||
function get_url_for_filename(filename: string): string {
|
||||
const target: Folder = knownFolders.documents() || knownFolders.ios.sharedPublic();
|
||||
const url: string = path.normalize(target.path + "///" + filename);
|
||||
return File.fromPath(url);
|
||||
return path.normalize(target.path + "///" + filename);
|
||||
}
|
||||
```
|
||||
|
||||
The encoding `ISO_8859_1` spiritually resembles the `"binary"` SheetJS type
|
||||
#### Reading data
|
||||
|
||||
**Reading data**
|
||||
|
||||
`File#readText(encoding.ISO_8859_1)` returns strings compatible with `"binary"`
|
||||
`getFileAccess().readBufferAsync` can read data:
|
||||
|
||||
```ts
|
||||
/* get binary string */
|
||||
const bstr: string = await file.readText(encoding.ISO_8859_1);
|
||||
import { getFileAccess } from '@nativescript/core';
|
||||
|
||||
/* find appropriate path */
|
||||
const url = get_url_for_filename("SheetJSNS.xls");
|
||||
|
||||
/* get data */
|
||||
const ab: ArrayBuffer = await getFileAccess().readBufferAsync(url);
|
||||
|
||||
/* read workbook */
|
||||
const wb = read(bstr, { type: "binary" });
|
||||
const wb = read(ab);
|
||||
```
|
||||
|
||||
**Writing data**
|
||||
#### Writing data
|
||||
|
||||
`File#writeText` with the `ISO_8859_1` encoding accepts `"binary"` strings with
|
||||
the caveat listed in the warning at the top of this section:
|
||||
`getFileAccess().writeBufferAsync` can write data:
|
||||
|
||||
```ts
|
||||
/* generate binary string */
|
||||
const bstr: string = write(wb, { bookType: 'csv', type: 'binary' });
|
||||
import { getFileAccess } from '@nativescript/core';
|
||||
|
||||
/* attempt to save binary string to file */
|
||||
await file.writeText(bstr, encoding.ISO_8859_1);
|
||||
/* 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 */
|
||||
await getFileAccess().writeBufferAsync(url, u8);
|
||||
```
|
||||
|
||||
## Demo
|
||||
|
||||
The demo builds off of the NativeScript + Angular example. Familiarity with
|
||||
Angular and TypeScript is assumed.
|
||||
|
||||
:::note
|
||||
|
||||
This demo was tested on an Intel Mac on 2022 August 10. NativeScript version
|
||||
(as verified with `ns --version`) is `8.3.2`. The iOS simulator runs iOS 15.5
|
||||
on an iPhone SE 3rd generation.
|
||||
This demo was tested on an Intel Mac on 2023 April 03. NativeScript version
|
||||
(as verified with `ns --version`) is `8.5.1`.
|
||||
|
||||
The iOS simulator runs iOS 16.2 on an iPhone 14 Pro Max.
|
||||
|
||||
:::
|
||||
|
||||
<details><summary><b>Complete Example</b> (click to show)</summary>
|
||||
|
||||
0) Follow the official Environment Setup instructions (tested with "MacOS + iOS")
|
||||
0) Follow the official Environment Setup instructions
|
||||
|
||||
1) Create a skeleton NativeScript + Angular app:
|
||||
|
||||
@ -110,48 +115,32 @@ npm i --save https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz
|
||||
4) To confirm the library was loaded, change the title to show the version. The
|
||||
differences are highlighted.
|
||||
|
||||
`src/app/item/items.component.ts` imports the version string to the component:
|
||||
`src/app/item/items.component.ts` should import the version string:
|
||||
|
||||
```ts title="src/app/item/items.component.ts"
|
||||
// highlight-next-line
|
||||
import { version } from 'xlsx';
|
||||
import { Component, OnInit } from '@angular/core'
|
||||
|
||||
import { Item } from './item'
|
||||
import { ItemService } from './item.service'
|
||||
// ...
|
||||
|
||||
@Component({
|
||||
selector: 'ns-items',
|
||||
templateUrl: './items.component.html',
|
||||
})
|
||||
export class ItemsComponent implements OnInit {
|
||||
items: Array<Item>
|
||||
// highlight-next-line
|
||||
version = `SheetJS - ${version}`;
|
||||
|
||||
constructor(private itemService: ItemService) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.items = this.itemService.getItems()
|
||||
}
|
||||
}
|
||||
// ...
|
||||
```
|
||||
|
||||
`src/app/item/items.component.html` references the version in the title:
|
||||
`src/app/item/items.component.html` should use the version in the title:
|
||||
|
||||
```xml title="src/app/item/items.component.html"
|
||||
<!-- highlight-next-line -->
|
||||
<ActionBar [title]="version"></ActionBar>
|
||||
|
||||
<GridLayout>
|
||||
<ListView [items]="items">
|
||||
<ng-template let-item="item">
|
||||
<StackLayout [nsRouterLink]="['/item', item.id]">
|
||||
<Label [text]="item.name"></Label>
|
||||
</StackLayout>
|
||||
</ng-template>
|
||||
</ListView>
|
||||
</GridLayout>
|
||||
<!-- ... -->
|
||||
```
|
||||
|
||||
Relaunch the app with `ns run ios` and the title bar should show the version.
|
||||
@ -171,11 +160,7 @@ Relaunch the app with `ns run ios` and the title bar should show the version.
|
||||
</StackLayout>
|
||||
<!-- highlight-end -->
|
||||
<ListView [items]="items">
|
||||
<ng-template let-item="item">
|
||||
<StackLayout [nsRouterLink]="['/item', item.id]">
|
||||
<Label [text]="item.name"></Label>
|
||||
</StackLayout>
|
||||
</ng-template>
|
||||
<!-- ... -->
|
||||
</ListView>
|
||||
<!-- highlight-next-line -->
|
||||
</StackLayout>
|
||||
@ -184,9 +169,8 @@ Relaunch the app with `ns run ios` and the title bar should show the version.
|
||||
```ts title="src/app/item/items.component.ts"
|
||||
// highlight-start
|
||||
import { version, utils, read, write } from 'xlsx';
|
||||
import { Dialogs } from '@nativescript/core';
|
||||
import { encoding } from '@nativescript/core/text';
|
||||
import { File, Folder, knownFolders, path } from '@nativescript/core/file-system';
|
||||
import { Dialogs, getFileAccess } from '@nativescript/core';
|
||||
import { Folder, knownFolders, path } from '@nativescript/core/file-system';
|
||||
// highlight-end
|
||||
import { Component, OnInit } from '@angular/core'
|
||||
|
||||
@ -194,10 +178,9 @@ import { Item } from './item'
|
||||
import { ItemService } from './item.service'
|
||||
|
||||
// highlight-start
|
||||
function get_handle_for_filename(filename: string): [File, string] {
|
||||
function get_url_for_filename(filename: string): string {
|
||||
const target: Folder = knownFolders.documents() || knownFolders.ios.sharedPublic();
|
||||
const url: string = path.normalize(target.path + "///" + filename);
|
||||
return [File.fromPath(url), url];
|
||||
return path.normalize(target.path + "///" + filename);
|
||||
}
|
||||
// highlight-end
|
||||
|
||||
@ -231,50 +214,22 @@ Restart the app process and two buttons should show up at the top:
|
||||
|
||||
![NativeScript Step 5](pathname:///mobile/nativescript5.png)
|
||||
|
||||
6) Implement import and export:
|
||||
6) Implement import and export by adding the highlighted lines:
|
||||
|
||||
```ts title="src/app/item/items.component.ts"
|
||||
import { version, utils, read, write } from 'xlsx';
|
||||
import { Dialogs } from '@nativescript/core';
|
||||
import { encoding } from '@nativescript/core/text';
|
||||
import { File, Folder, knownFolders, path } from '@nativescript/core/file-system';
|
||||
import { Component, OnInit } from '@angular/core'
|
||||
|
||||
import { Item } from './item'
|
||||
import { ItemService } from './item.service'
|
||||
|
||||
function get_handle_for_filename(filename: string): [File, string] {
|
||||
const target: Folder = knownFolders.documents() || knownFolders.ios.sharedPublic();
|
||||
const url: string = path.normalize(target.path + "///" + filename);
|
||||
return [File.fromPath(url), url];
|
||||
}
|
||||
|
||||
@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()
|
||||
}
|
||||
|
||||
/* Import button */
|
||||
async import() {
|
||||
// highlight-start
|
||||
/* find appropriate path */
|
||||
const [file, url] = get_handle_for_filename("SheetJSNS.csv");
|
||||
const url = get_url_for_filename("SheetJSNS.xls");
|
||||
|
||||
try {
|
||||
/* get binary string */
|
||||
const bstr: string = await file.readText(encoding.ISO_8859_1);
|
||||
await Dialogs.alert(`Attempting to read from SheetJSNS.xls at ${url}`);
|
||||
/* get data */
|
||||
const ab: ArrayBuffer = await getFileAccess().readBufferAsync(url);
|
||||
|
||||
/* read workbook */
|
||||
const wb = read(bstr, { type: "binary" });
|
||||
const wb = read(ab);
|
||||
|
||||
/* grab first sheet */
|
||||
const wsname: string = wb.SheetNames[0];
|
||||
@ -282,8 +237,7 @@ export class ItemsComponent implements OnInit {
|
||||
|
||||
/* update table */
|
||||
this.items = utils.sheet_to_json<Item>(ws);
|
||||
Dialogs.alert(`Attempting to read to ${filename} in ${url}`);
|
||||
} catch(e) { Dialogs.alert(e.message); }
|
||||
} catch(e) { await Dialogs.alert(e.message); }
|
||||
// highlight-end
|
||||
}
|
||||
|
||||
@ -291,7 +245,7 @@ export class ItemsComponent implements OnInit {
|
||||
async export() {
|
||||
// highlight-start
|
||||
/* find appropriate path */
|
||||
const [file, url] = get_handle_for_filename("SheetJSNS.csv");
|
||||
const url = get_url_for_filename("SheetJSNS.xls");
|
||||
|
||||
try {
|
||||
/* create worksheet from data */
|
||||
@ -301,50 +255,41 @@ export class ItemsComponent implements OnInit {
|
||||
const wb = utils.book_new();
|
||||
utils.book_append_sheet(wb, ws, "Sheet1");
|
||||
|
||||
/* generate binary string */
|
||||
const wbout: string = write(wb, { bookType: 'csv', type: 'binary' });
|
||||
/* generate Uint8Array */
|
||||
const u8: Uint8Array = write(wb, { bookType: 'xls', type: 'buffer' });
|
||||
|
||||
/* attempt to save binary string to file */
|
||||
await file.writeText(wbout, encoding.ISO_8859_1);
|
||||
Dialogs.alert(`Wrote to ${filename} in ${url}`);
|
||||
} catch(e) { Dialogs.alert(e.message); }
|
||||
/* attempt to save Uint8Array to file */
|
||||
await getFileAccess().writeBufferAsync(url, u8);
|
||||
await Dialogs.alert(`Wrote to SheetJSNS.xls at ${url}`);
|
||||
} catch(e) { await Dialogs.alert(e.message); }
|
||||
// highlight-end
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Restart the app process.
|
||||
### iOS
|
||||
|
||||
**Testing**
|
||||
Relaunch the app with `ns run ios`
|
||||
|
||||
The app can be tested with the following sequence in the simulator:
|
||||
|
||||
- Hit "Export File". A dialog will print where the file was written
|
||||
- Tap "Export File". A dialog will print where the file was written
|
||||
|
||||
- Open that file with a text editor. It will be a 3-column CSV:
|
||||
- Open the file with a spreadsheet editor.
|
||||
|
||||
```csv
|
||||
id,name,role
|
||||
1,Ter Stegen,Goalkeeper
|
||||
3,Piqué,Defender
|
||||
4,I. Rakitic,Midfielder
|
||||
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
|
||||
...
|
||||
```
|
||||
|
||||
After the header row, add the line `0,SheetJS,Library`:
|
||||
Restart the app after saving the file.
|
||||
|
||||
```csv
|
||||
id,name,role
|
||||
0,SheetJS,Library
|
||||
1,Ter Stegen,Goalkeeper
|
||||
3,Piqué,Defender
|
||||
...
|
||||
```
|
||||
|
||||
- Hit "Import File". A dialog will print the path of the file that was read.
|
||||
- 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 7](pathname:///mobile/nativescript7.png)
|
||||
|
||||
</details>
|
||||
|
||||
|
@ -56,7 +56,4 @@ should support and investigate community modules. If there are popular modules
|
||||
for features that must be included, or for teams that are comfortable with
|
||||
native app development, React Native is the obvious choice.
|
||||
|
||||
NativeScript is not recommended at this time. There are known bugs related to
|
||||
binary data processing. The demo only supports plaintext file formats like CSV.
|
||||
|
||||
:::
|
||||
|
@ -521,7 +521,7 @@ sequenceDiagram
|
||||
deactivate Page
|
||||
end
|
||||
Worker->>User: finish download
|
||||
Worker->>Page: send competion message
|
||||
Worker->>Page: send completion message
|
||||
deactivate Worker
|
||||
activate Page
|
||||
Page->>User: download complete
|
||||
|
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 42 KiB |
BIN
docz/static/mobile/nsios.png
Normal file
After Width: | Height: | Size: 74 KiB |