Skip to main content

NativeScript

The NodeJS Module can be imported from the main entrypoint or any script in the project.

Binary Data issues

NativeScript will not safely transmit binary or UTF-8 strings. XLSB, NUMBERS, XLSX, XLS, ODS, SYLK, and DBF exports are known to be mangled.

This is a known NativeScript bug.

This demo will focus on ASCII CSV files. Once the bug is resolved, XLSX and other formats will be supported.

Integration Details

The @nativescript/core/file-system package provides classes for file access.

Reading and writing data require a file handle. The following snippet searches typical document folders for a specified filename:

import { File, Folder, knownFolders, path } from '@nativescript/core/file-system';

function get_handle_for_filename(filename: string): File {
const target: Folder = knownFolders.documents() || knownFolders.ios.sharedPublic();
const url: string = path.normalize(target.path + "///" + filename);
return File.fromPath(url);
}

The encoding ISO_8859_1 spiritually resembles the "binary" SheetJS type

Reading data

File#readText(encoding.ISO_8859_1) returns strings compatible with "binary"

/* get binary string */
const bstr: string = await file.readText(encoding.ISO_8859_1);

/* read workbook */
const wb = read(bstr, { type: "binary" });

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:

/* generate binary string */
const bstr: string = write(wb, { bookType: 'csv', type: 'binary' });

/* attempt to save binary string to file */
await file.writeText(bstr, encoding.ISO_8859_1);

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.

Complete Example (click to show)

0) Follow the official Environment Setup instructions (tested with "MacOS + iOS")

1) Create a skeleton NativeScript + Angular app:

ns create SheetJSNS --ng

2) Launch the app in the iOS simulator to verify that the demo built properly:

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:

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

NativeScript Step 4

5) Add the Import and Export buttons to the template:

src/app/item/items.component.html
<ActionBar [title]="version"></ActionBar>

<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>
<ListView [items]="items">
<ng-template let-item="item">
<StackLayout [nsRouterLink]="['/item', item.id]">
<Label [text]="item.name"></Label>
</StackLayout>
</ng-template>
</ListView>
</StackLayout>
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() {
}

/* Export button */
async export() {
}
}

Restart the app process and two buttons should show up at the top:

NativeScript Step 5

6) Implement import and export:

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() {
/* find appropriate path */
const [file, url] = get_handle_for_filename("SheetJSNS.csv");

try {
/* get binary string */
const bstr: string = await file.readText(encoding.ISO_8859_1);

/* read workbook */
const wb = read(bstr, { type: "binary" });

/* grab first sheet */
const wsname: string = wb.SheetNames[0];
const ws = wb.Sheets[wsname];

/* 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); }
}

/* Export button */
async export() {
/* find appropriate path */
const [file, url] = get_handle_for_filename("SheetJSNS.csv");

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");

/* generate binary string */
const wbout: string = write(wb, { bookType: 'csv', type: 'binary' });

/* 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); }
}
}

Restart the app process.

Testing

The app can be tested with the following sequence in the simulator:

  • Hit "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:

id,name,role
1,Ter Stegen,Goalkeeper
3,Piqué,Defender
4,I. Rakitic,Midfielder
...

After the header row, add the line 0,SheetJS,Library:

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. The first item in the list will change:

NativeScript Step 7