docs.sheetjs.com/docz/docs/03-demos/05-mobile/02-nativescript.md
2023-05-21 03:03:46 -04:00

11 KiB

title pagination_prev pagination_next sidebar_position sidebar_custom_props
NativeScript demos/static/index demos/desktop/index 2
summary
JS + Native Elements

import current from '/version.js'; import CodeBlock from '@theme/CodeBlock';

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

The "Complete Example" creates an app that looks like the screenshots below:

iOS Android

iOS screenshot

Android screenshot

Integration Details

The discussion covers the NativeScript + Angular integration. Familiarity with Angular and TypeScript is assumed.

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:

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

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

Reading Local Files

getFileAccess().readBufferAsync can read data:

import { getFileAccess } from '@nativescript/core';
import { read } from 'xlsx';

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

Writing Local Files

getFileAccess().writeBufferAsync can write data. iOS supports Uint8Array directly but Android requires a true array of numbers:

import { getFileAccess } from '@nativescript/core';
import { write } from 'xlsx';

/* 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, global.isAndroid ? (Array.from(u8) as any) : u8);

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:

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

Demo

:::note

This demo was tested on an Intel Mac on 2023 May 21. NativeScript version (as verified with ns --version) is 8.5.3.

The iOS simulator runs iOS 16.2 on an iPhone 14 Pro Max.

The Android simulator runs Android 12.0 (S) API 31 on a Pixel 3.

:::

  1. Follow the official Environment Setup instructions

Base Project

  1. Create a skeleton NativeScript + Angular app:
ns create SheetJSNS --ng
  1. 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

  1. From the project folder, install the library:

{\ npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz}

  1. To confirm the library was loaded, change the title to show the version. The differences are highlighted.

src/app/item/items.component.ts should import the version string:

// highlight-next-line
import { version } from 'xlsx';
import { Component, OnInit } from '@angular/core'

// ...

export class ItemsComponent implements OnInit {
  items: Array<Item>
  // highlight-next-line
  version = `SheetJS - ${version}`;

  constructor(private itemService: ItemService) {}
// ...

src/app/item/items.component.html should use the version in the title:

<!-- highlight-next-line -->
<ActionBar [title]="version"></ActionBar>

<GridLayout>
<!-- ... -->

Relaunch the app with ns run ios and the title bar should show the version.

NativeScript Step 4

Local Files

  1. Add the Import and Export buttons to the template:
<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">
    <!-- ... -->
  </ListView>
<!-- highlight-next-line -->
</StackLayout>
// highlight-start
import { version, utils, read, write } from 'xlsx';
import { Dialogs, getFileAccess } from '@nativescript/core';
import { Folder, knownFolders, path } from '@nativescript/core/file-system';
// highlight-end
import { Component, OnInit } from '@angular/core'

import { Item } from './item'
import { ItemService } from './item.service'

// highlight-start
function get_url_for_filename(filename: string): string {
  const target: Folder = knownFolders.documents() || knownFolders.ios.sharedPublic();
  return path.normalize(target.path + "///" + filename);
}
// 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

  1. Implement import and export by adding the highlighted lines:
  /* Import button */
  async import() {
    // highlight-start
    /* find appropriate path */
    const url = get_url_for_filename("SheetJSNS.xls");

    try {
      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(ab);

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

      /* update table */
      this.items = utils.sheet_to_json<Item>(ws);
    } catch(e) { await Dialogs.alert(e.message); }
    // highlight-end
  }

  /* Export button */
  async export() {
    // highlight-start
    /* find appropriate path */
    const url = get_url_for_filename("SheetJSNS.xls");

    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 Uint8Array */
      const u8: Uint8Array = write(wb, { bookType: 'xls', type: 'buffer' });

      /* 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}`);
    } catch(e) { await Dialogs.alert(e.message); }
    // highlight-end
  }

iOS

Relaunch the app with ns run ios

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

  • Tap "Export File". A dialog will print where the file was written

  • Open the file 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
...

Restart the app after saving the file.

  • 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

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:

  • Tap "Export File". A dialog will print where the file was written. Typicaly the URL is /data/user/0/org.nativescript.SheetJSNS/files/SheetJSNS.xls

  • Pull the file from the simulator:

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

Fetching Files

  1. In src/app/item/items.component.ts, make ngOnInit asynchronous:
  async ngOnInit(): Promise<void> {
    this.items = await this.itemService.getItems()
  }
  1. Replace item.service.ts with the following:
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.