827 lines
24 KiB
Plaintext
827 lines
24 KiB
Plaintext
---
|
|
pagination_prev: getting-started/index
|
|
sidebar_position: 2
|
|
---
|
|
|
|
import current from '/version.js';
|
|
import Tabs from '@theme/Tabs';
|
|
import TabItem from '@theme/TabItem';
|
|
import CodeBlock from '@theme/CodeBlock';
|
|
|
|
# Tutorial
|
|
|
|
SheetJS presents a simple JS interface that works with "Array of Arrays" and
|
|
"Array of JS Objects". The API functions are building blocks that should be
|
|
combined with other JS APIs to solve problems.
|
|
|
|
The discussion focuses on the problem solving mindset. API details are covered
|
|
in other parts of the documentation.
|
|
|
|
The goal of this example is to generate a XLSX workbook of US President names
|
|
and birthdays. [Click here](#live-demo) to jump to the live demo. The sequence
|
|
diagram below shows the process:
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
actor U as User
|
|
participant P as Page
|
|
participant A as API
|
|
U->>P: click button
|
|
P->>A: fetch data
|
|
A->>P: raw data
|
|
Note over P: process data
|
|
Note over P: make workbook
|
|
Note over P: setup download
|
|
P->>U: download workbook
|
|
```
|
|
|
|
|
|
## Acquire Data
|
|
|
|
### Raw Data
|
|
|
|
[The raw data is available in JSON form](https://theunitedstates.io/congress-legislators/executive.json).
|
|
For convenience, it has been [mirrored here](https://sheetjs.com/data/executive.json)
|
|
|
|
Acquiring the data is straightforward with `fetch`:
|
|
|
|
```js
|
|
const url = "https://sheetjs.com/data/executive.json";
|
|
const raw_data = await (await fetch(url)).json();
|
|
```
|
|
|
|
The raw data is an Array of objects. This is the data for John Adams:
|
|
|
|
```js
|
|
{
|
|
"id": { /* (data omitted) */ },
|
|
"name": {
|
|
"first": "John", // <-- first name
|
|
"last": "Adams" // <-- last name
|
|
},
|
|
"bio": {
|
|
"birthday": "1735-10-19", // <-- birthday
|
|
"gender": "M"
|
|
},
|
|
"terms": [
|
|
{ "type": "viceprez", /* (other fields omitted) */ },
|
|
{ "type": "viceprez", /* (other fields omitted) */ },
|
|
{ "type": "prez", /* (other fields omitted) */ }
|
|
]
|
|
}
|
|
```
|
|
|
|
### Filtering for Presidents
|
|
|
|
The dataset includes Aaron Burr, a Vice President who was never President!
|
|
|
|
`Array#filter` creates a new array with the desired rows. A President served
|
|
at least one term with `type` set to `"prez"`. To test if a particular row has
|
|
at least one `"prez"` term, `Array#some` is another native JS function. The
|
|
complete filter would be:
|
|
|
|
```js
|
|
const prez = raw_data.filter(row => row.terms.some(term => term.type === "prez"));
|
|
```
|
|
|
|
### Sorting by First Term
|
|
|
|
The dataset is sorted in chronological order by the first presidential or vice
|
|
presidential term. The Vice President and President in a given term are sorted
|
|
alphabetically. Joe Biden and Barack Obama were Vice President and President
|
|
respectively in 2009. Since "Biden" is lexicographically before "Obama", Biden's
|
|
data point appears first. The goal is to sort the presidents in order of their
|
|
presidential term.
|
|
|
|
The first step is adding the first presidential term start date to the dataset.
|
|
`Array#find` will find the first value in an array that matches a criterion.
|
|
The following code looks at each president and creates a `"start"` property that
|
|
represents the start of the first presidential term.
|
|
|
|
```js
|
|
prez.forEach(prez => prez.start = prez.terms.find(term => term.type === "prez").start);
|
|
```
|
|
|
|
`Array#sort` will sort the array. Since the `start` properties are strings, the
|
|
recommended approach is to use `String#localeCompare` to compare strings:
|
|
|
|
```js
|
|
prez.sort((l,r) => l.start.localeCompare(r.start));
|
|
```
|
|
|
|
### Reshaping the Array
|
|
|
|
For this example, the name will be the first name combined with the last name
|
|
(`row.name.first + " " + row.name.last`) and the birthday will be available at
|
|
`row.bio.birthday`. Using `Array#map`, the dataset can be massaged in one call:
|
|
|
|
```js
|
|
const rows = prez.map(row => ({
|
|
name: row.name.first + " " + row.name.last,
|
|
birthday: row.bio.birthday
|
|
}));
|
|
```
|
|
|
|
The result is an array of "simple" objects with no nesting:
|
|
|
|
```js
|
|
[
|
|
{ name: "George Washington", birthday: "1732-02-22" },
|
|
{ name: "John Adams", birthday: "1735-10-19" },
|
|
// ... one row per President
|
|
]
|
|
```
|
|
|
|
## Create a Workbook
|
|
|
|
With the cleaned dataset, `XLSX.utils.json_to_sheet` generates a worksheet:
|
|
|
|
```js
|
|
const worksheet = XLSX.utils.json_to_sheet(rows);
|
|
```
|
|
|
|
`XLSX.utils.book_new` creates a new workbook and `XLSX.utils.book_append_sheet`
|
|
appends a worksheet to the workbook. The new worksheet will be called "Dates":
|
|
|
|
```js
|
|
const workbook = XLSX.utils.book_new();
|
|
XLSX.utils.book_append_sheet(workbook, worksheet, "Dates");
|
|
```
|
|
|
|
## Clean up Workbook
|
|
|
|
The data is in the workbook and can be exported.
|
|
|
|
![Rough export](pathname:///example/rough.png)
|
|
|
|
There are multiple opportunities for improvement: the headers can be renamed and
|
|
the column widths can be adjusted. [SheetJS Pro](https://sheetjs.com/pro) offers
|
|
additional styling options like cell styling and frozen rows.
|
|
|
|
<details><summary><b>Changing Header Names</b> (click to show)</summary>
|
|
|
|
By default, `json_to_sheet` creates a worksheet with a header row. In this case,
|
|
the headers come from the JS object keys: "name" and "birthday".
|
|
|
|
The headers are in cells `A1` and `B1`. `XLSX.utils.sheet_add_aoa` can write
|
|
text values to the existing worksheet starting at cell `A1`:
|
|
|
|
```js
|
|
XLSX.utils.sheet_add_aoa(worksheet, [["Name", "Birthday"]], { origin: "A1" });
|
|
```
|
|
|
|
</details>
|
|
|
|
<details><summary><b>Changing Column Widths</b> (click to show)</summary>
|
|
|
|
Some of the names are longer than the default column width. Column widths are
|
|
set by setting the `"!cols"` worksheet property.
|
|
|
|
The following line sets the width of column A to approximately 10 characters:
|
|
|
|
```js
|
|
worksheet["!cols"] = [ { wch: 10 } ]; // set column A width to 10 characters
|
|
```
|
|
|
|
One `Array#reduce` call over `rows` can calculate the maximum width:
|
|
|
|
```js
|
|
const max_width = rows.reduce((w, r) => Math.max(w, r.name.length), 10);
|
|
worksheet["!cols"] = [ { wch: max_width } ];
|
|
```
|
|
|
|
</details>
|
|
|
|
After cleanup, the generated workbook looks like the screenshot below:
|
|
|
|
![Final export](pathname:///example/final.png)
|
|
|
|
## Export a File
|
|
|
|
`XLSX.writeFile` creates a spreadsheet file and tries to write it to the system.
|
|
In the browser, it will try to prompt the user to download the file. In NodeJS,
|
|
it will write to the local directory.
|
|
|
|
```js
|
|
XLSX.writeFile(workbook, "Presidents.xlsx", { compression: true });
|
|
```
|
|
|
|
## Live Demo
|
|
|
|
This demo runs in the web browser! Click "Click to Generate File!" and the
|
|
browser should try to create `Presidents.xlsx`
|
|
|
|
```jsx live
|
|
function Presidents() { return ( <button onClick={async () => {
|
|
/* fetch JSON data and parse */
|
|
const url = "https://sheetjs.com/data/executive.json";
|
|
const raw_data = await (await fetch(url)).json();
|
|
|
|
/* filter for the Presidents */
|
|
const prez = raw_data.filter(row => row.terms.some(term => term.type === "prez"));
|
|
|
|
/* sort by first presidential term */
|
|
prez.forEach(prez => prez.start = prez.terms.find(term => term.type === "prez").start);
|
|
prez.sort((l,r) => l.start.localeCompare(r.start));
|
|
|
|
/* flatten objects */
|
|
const rows = prez.map(row => ({
|
|
name: row.name.first + " " + row.name.last,
|
|
birthday: row.bio.birthday
|
|
}));
|
|
|
|
/* generate worksheet and workbook */
|
|
const worksheet = XLSX.utils.json_to_sheet(rows);
|
|
const workbook = XLSX.utils.book_new();
|
|
XLSX.utils.book_append_sheet(workbook, worksheet, "Dates");
|
|
|
|
/* fix headers */
|
|
XLSX.utils.sheet_add_aoa(worksheet, [["Name", "Birthday"]], { origin: "A1" });
|
|
|
|
/* calculate column width */
|
|
const max_width = rows.reduce((w, r) => Math.max(w, r.name.length), 10);
|
|
worksheet["!cols"] = [ { wch: max_width } ];
|
|
|
|
/* create an XLSX file and try to save to Presidents.xlsx */
|
|
XLSX.writeFile(workbook, "Presidents.xlsx", { compression: true });
|
|
}}><b>Click to Generate file!</b></button> ); }
|
|
```
|
|
|
|
<https://sheetjs.com/pres.html> is a hosted version of this demo.
|
|
|
|
## Run the Demo Locally
|
|
|
|
<Tabs>
|
|
<TabItem value="browser" label="Web Browser">
|
|
|
|
Save the following script to `SheetJSStandaloneDemo.html`:
|
|
|
|
<CodeBlock language="html" title="SheetJSStandaloneDemo.html">{`\
|
|
<body>
|
|
<script src="https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js"></script>
|
|
<script>
|
|
(async() => {
|
|
/* fetch JSON data and parse */
|
|
const url = "https://sheetjs.com/data/executive.json";
|
|
const raw_data = await (await fetch(url)).json();
|
|
\n\
|
|
/* filter for the Presidents */
|
|
const prez = raw_data.filter(row => row.terms.some(term => term.type === "prez"));
|
|
\n\
|
|
/* sort by first presidential term */
|
|
prez.forEach(prez => prez.start = prez.terms.find(term => term.type === "prez").start);
|
|
prez.sort((l,r) => l.start.localeCompare(r.start));
|
|
\n\
|
|
/* flatten objects */
|
|
const rows = prez.map(row => ({
|
|
name: row.name.first + " " + row.name.last,
|
|
birthday: row.bio.birthday
|
|
}));
|
|
\n\
|
|
/* generate worksheet and workbook */
|
|
const worksheet = XLSX.utils.json_to_sheet(rows);
|
|
const workbook = XLSX.utils.book_new();
|
|
XLSX.utils.book_append_sheet(workbook, worksheet, "Dates");
|
|
\n\
|
|
/* fix headers */
|
|
XLSX.utils.sheet_add_aoa(worksheet, [["Name", "Birthday"]], { origin: "A1" });
|
|
\n\
|
|
/* calculate column width */
|
|
const max_width = rows.reduce((w, r) => Math.max(w, r.name.length), 10);
|
|
worksheet["!cols"] = [ { wch: max_width } ];
|
|
\n\
|
|
/* create an XLSX file and try to save to Presidents.xlsx */
|
|
XLSX.writeFile(workbook, "Presidents.xlsx", { compression: true });
|
|
})();
|
|
</script>
|
|
</body>`}
|
|
</CodeBlock>
|
|
|
|
After saving the file, run a local web server in the folder with the HTML file.
|
|
For example, if NodeJS is installed:
|
|
|
|
```bash
|
|
npx http-server .
|
|
```
|
|
|
|
The server process will display a URL (typically `http://127.0.0.1:8080`). Open
|
|
`http://127.0.0.1:8080/SheetJSStandaloneDemo.html` in your browser.
|
|
|
|
</TabItem>
|
|
<TabItem value="nodejs" label="Command-Line (NodeJS)">
|
|
|
|
Install the dependencies:
|
|
|
|
<CodeBlock language="bash">{`\
|
|
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
|
|
</CodeBlock>
|
|
|
|
Save the following script to `SheetJSNodeJS.js`:
|
|
|
|
```js title="SheetJSNodeJS.js"
|
|
const XLSX = require("xlsx");
|
|
|
|
(async() => {
|
|
/* fetch JSON data and parse */
|
|
const url = "https://sheetjs.com/data/executive.json";
|
|
const raw_data = await (await fetch(url)).json();
|
|
|
|
/* filter for the Presidents */
|
|
const prez = raw_data.filter(row => row.terms.some(term => term.type === "prez"));
|
|
|
|
/* sort by first presidential term */
|
|
prez.forEach(prez => prez.start = prez.terms.find(term => term.type === "prez").start);
|
|
prez.sort((l,r) => l.start.localeCompare(r.start));
|
|
|
|
/* flatten objects */
|
|
const rows = prez.map(row => ({
|
|
name: row.name.first + " " + row.name.last,
|
|
birthday: row.bio.birthday
|
|
}));
|
|
|
|
/* generate worksheet and workbook */
|
|
const worksheet = XLSX.utils.json_to_sheet(rows);
|
|
const workbook = XLSX.utils.book_new();
|
|
XLSX.utils.book_append_sheet(workbook, worksheet, "Dates");
|
|
|
|
/* fix headers */
|
|
XLSX.utils.sheet_add_aoa(worksheet, [["Name", "Birthday"]], { origin: "A1" });
|
|
|
|
/* calculate column width */
|
|
const max_width = rows.reduce((w, r) => Math.max(w, r.name.length), 10);
|
|
worksheet["!cols"] = [ { wch: max_width } ];
|
|
|
|
/* create an XLSX file and try to save to Presidents.xlsx */
|
|
XLSX.writeFile(workbook, "Presidents.xlsx", { compression: true });
|
|
})();
|
|
```
|
|
|
|
After saving the script, run the script:
|
|
|
|
```bash
|
|
node SheetJSNodeJS.js
|
|
```
|
|
|
|
This script will write a new file `Presidents.xlsx` in the same folder.
|
|
|
|
:::caution
|
|
|
|
Native `fetch` support was added in NodeJS 18. For older versions of NodeJS,
|
|
the script will throw an error `fetch is not defined`. A third-party library
|
|
like `axios` presents a similar API for fetching data:
|
|
|
|
<details><summary><b>Example using axios</b> (click to show)</summary>
|
|
|
|
Install the dependencies:
|
|
|
|
<CodeBlock language="bash">{`\
|
|
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz axios`}
|
|
</CodeBlock>
|
|
|
|
Save the following script to `SheetJSAxios.js` (differences are highlighted):
|
|
|
|
```js title="SheetJSAxios.js"
|
|
const XLSX = require("xlsx");
|
|
// highlight-next-line
|
|
const axios = require("axios");
|
|
|
|
(async() => {
|
|
/* fetch JSON data and parse */
|
|
const url = "https://sheetjs.com/data/executive.json";
|
|
// highlight-next-line
|
|
const raw_data = (await axios(url, {responseType: "json"})).data;
|
|
|
|
/* filter for the Presidents */
|
|
const prez = raw_data.filter(row => row.terms.some(term => term.type === "prez"));
|
|
|
|
/* sort by first presidential term */
|
|
prez.forEach(prez => prez.start = prez.terms.find(term => term.type === "prez").start);
|
|
prez.sort((l,r) => l.start.localeCompare(r.start));
|
|
|
|
/* flatten objects */
|
|
const rows = prez.map(row => ({
|
|
name: row.name.first + " " + row.name.last,
|
|
birthday: row.bio.birthday
|
|
}));
|
|
|
|
/* generate worksheet and workbook */
|
|
const worksheet = XLSX.utils.json_to_sheet(rows);
|
|
const workbook = XLSX.utils.book_new();
|
|
XLSX.utils.book_append_sheet(workbook, worksheet, "Dates");
|
|
|
|
/* fix headers */
|
|
XLSX.utils.sheet_add_aoa(worksheet, [["Name", "Birthday"]], { origin: "A1" });
|
|
|
|
/* calculate column width */
|
|
const max_width = rows.reduce((w, r) => Math.max(w, r.name.length), 10);
|
|
worksheet["!cols"] = [ { wch: max_width } ];
|
|
|
|
/* create an XLSX file and try to save to Presidents.xlsx */
|
|
XLSX.writeFile(workbook, "Presidents.xlsx", { compression: true });
|
|
})();
|
|
```
|
|
|
|
After saving the script, run the script:
|
|
|
|
```bash
|
|
node SheetJSAxios.js
|
|
```
|
|
|
|
This script will write a new file `Presidents.xlsx` in the same folder.
|
|
|
|
</details>
|
|
|
|
:::
|
|
|
|
<details><summary><b>Other Server-Side Platforms</b> (click to show)</summary>
|
|
|
|
<Tabs>
|
|
<TabItem value="deno" label="Deno">
|
|
|
|
Save the following script to `SheetJSDeno.ts`:
|
|
|
|
<CodeBlock language="ts" title="SheetJSDeno.ts">{`\
|
|
// @deno-types="https://cdn.sheetjs.com/xlsx-${current}/package/types/index.d.ts"
|
|
import * as XLSX from 'https://cdn.sheetjs.com/xlsx-${current}/package/xlsx.mjs';
|
|
\n\
|
|
/* fetch JSON data and parse */
|
|
const url = "https://sheetjs.com/data/executive.json";
|
|
const raw_data = await (await fetch(url)).json();
|
|
\n\
|
|
/* filter for the Presidents */
|
|
const prez = raw_data.filter((row: any) => row.terms.some((term: any) => term.type === "prez"));
|
|
\n\
|
|
/* sort by first presidential term */
|
|
prez.forEach(prez => prez.start = prez.terms.find(term => term.type === "prez").start);
|
|
prez.sort((l,r) => l.start.localeCompare(r.start));
|
|
\n\
|
|
/* flatten objects */
|
|
const rows = prez.map((row: any) => ({
|
|
name: row.name.first + " " + row.name.last,
|
|
birthday: row.bio.birthday
|
|
}));
|
|
\n\
|
|
/* generate worksheet and workbook */
|
|
const worksheet = XLSX.utils.json_to_sheet(rows);
|
|
const workbook = XLSX.utils.book_new();
|
|
XLSX.utils.book_append_sheet(workbook, worksheet, "Dates");
|
|
\n\
|
|
/* fix headers */
|
|
XLSX.utils.sheet_add_aoa(worksheet, [["Name", "Birthday"]], { origin: "A1" });
|
|
\n\
|
|
/* calculate column width */
|
|
const max_width = rows.reduce((w: number, r: any) => Math.max(w, r.name.length), 10);
|
|
worksheet["!cols"] = [ { wch: max_width } ];
|
|
\n\
|
|
/* create an XLSX file and try to save to Presidents.xlsx */
|
|
XLSX.writeFile(workbook, "Presidents.xlsx", { compression: true });`}
|
|
</CodeBlock>
|
|
|
|
After saving the script, run the script:
|
|
|
|
```bash
|
|
deno run -A SheetJSDeno.ts
|
|
```
|
|
|
|
This script will write a new file `Presidents.xlsx` in the same folder.
|
|
|
|
</TabItem>
|
|
<TabItem value="bun" label="Bun">
|
|
|
|
<p>Download <a href={`https://cdn.sheetjs.com/xlsx-${current}/package/xlsx.mjs`}>https://cdn.sheetjs.com/xlsx-{current}/package/xlsx.mjs</a> to <code>xlsx.mjs</code>:</p>
|
|
|
|
<CodeBlock language="bash">{`\
|
|
curl -LO https://cdn.sheetjs.com/xlsx-${current}/package/xlsx.mjs`}
|
|
</CodeBlock>
|
|
|
|
Save the following script to `SheetJSBun.js`:
|
|
|
|
```js title="SheetJSBun.js"
|
|
import * as XLSX from './xlsx.mjs';
|
|
import * as fs from 'fs';
|
|
XLSX.set_fs(fs);
|
|
|
|
/* fetch JSON data and parse */
|
|
const url = "https://sheetjs.com/data/executive.json";
|
|
const raw_data = await (await fetch(url)).json();
|
|
|
|
/* filter for the Presidents */
|
|
const prez = raw_data.filter((row) => row.terms.some((term) => term.type === "prez"));
|
|
|
|
/* sort by first presidential term */
|
|
prez.forEach(prez => prez.start = prez.terms.find(term => term.type === "prez").start);
|
|
prez.sort((l,r) => l.start.localeCompare(r.start));
|
|
|
|
/* flatten objects */
|
|
const rows = prez.map((row) => ({
|
|
name: row.name.first + " " + row.name.last,
|
|
birthday: row.bio.birthday
|
|
}));
|
|
|
|
/* generate worksheet and workbook */
|
|
const worksheet = XLSX.utils.json_to_sheet(rows);
|
|
const workbook = XLSX.utils.book_new();
|
|
XLSX.utils.book_append_sheet(workbook, worksheet, "Dates");
|
|
|
|
/* fix headers */
|
|
XLSX.utils.sheet_add_aoa(worksheet, [["Name", "Birthday"]], { origin: "A1" });
|
|
|
|
/* calculate column width */
|
|
const max_width = rows.reduce((w, r) => Math.max(w, r.name.length), 10);
|
|
worksheet["!cols"] = [ { wch: max_width } ];
|
|
|
|
/* create an XLSX file and try to save to Presidents.xlsx */
|
|
XLSX.writeFile(workbook, "Presidents.xlsx", { compression: true });
|
|
```
|
|
|
|
After saving the script, run the script:
|
|
|
|
```bash
|
|
bun SheetJSBun.js
|
|
```
|
|
|
|
This script will write a new file `Presidents.xlsx` in the same folder.
|
|
|
|
</TabItem>
|
|
</Tabs>
|
|
|
|
</details>
|
|
|
|
</TabItem>
|
|
|
|
<TabItem value="desktop" label="Desktop App">
|
|
|
|
Save the following script to `SheetJSNW.html`:
|
|
|
|
<CodeBlock language="html" title="SheetJSNW.html">{`\
|
|
<body>
|
|
<script src="https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js"></script>
|
|
<script>
|
|
(async() => {
|
|
/* fetch JSON data and parse */
|
|
const url = "https://sheetjs.com/data/executive.json";
|
|
const raw_data = await (await fetch(url)).json();
|
|
\n\
|
|
/* filter for the Presidents */
|
|
const prez = raw_data.filter(row => row.terms.some(term => term.type === "prez"));
|
|
\n\
|
|
/* sort by first presidential term */
|
|
prez.forEach(prez => prez.start = prez.terms.find(term => term.type === "prez").start);
|
|
prez.sort((l,r) => l.start.localeCompare(r.start));
|
|
\n\
|
|
/* flatten objects */
|
|
const rows = prez.map(row => ({
|
|
name: row.name.first + " " + row.name.last,
|
|
birthday: row.bio.birthday
|
|
}));
|
|
\n\
|
|
/* generate worksheet and workbook */
|
|
const worksheet = XLSX.utils.json_to_sheet(rows);
|
|
const workbook = XLSX.utils.book_new();
|
|
XLSX.utils.book_append_sheet(workbook, worksheet, "Dates");
|
|
\n\
|
|
/* fix headers */
|
|
XLSX.utils.sheet_add_aoa(worksheet, [["Name", "Birthday"]], { origin: "A1" });
|
|
\n\
|
|
/* calculate column width */
|
|
const max_width = rows.reduce((w, r) => Math.max(w, r.name.length), 10);
|
|
worksheet["!cols"] = [ { wch: max_width } ];
|
|
\n\
|
|
/* create an XLSX file and try to save to Presidents.xlsx */
|
|
XLSX.writeFile(workbook, "Presidents.xlsx", { compression: true });
|
|
})();
|
|
</script>
|
|
</body>`}
|
|
</CodeBlock>
|
|
|
|
Save the following to `package.json`:
|
|
|
|
<CodeBlock language="json" title="package.json">{`\
|
|
{
|
|
"name": "sheetjs-nwjs",
|
|
"author": "sheetjs",
|
|
"version": "0.0.0",
|
|
"main": "SheetJSNW.html",
|
|
"dependencies": {
|
|
"nw": "~0.66.0",
|
|
"xlsx": "https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz"
|
|
}
|
|
}`}
|
|
</CodeBlock>
|
|
|
|
Install dependencies and run:
|
|
|
|
```bash
|
|
npm i
|
|
npx nw .
|
|
```
|
|
|
|
The app will show a save dialog. After selecting a path, it will write the file.
|
|
|
|
</TabItem>
|
|
<TabItem value="mobile" label="Mobile App">
|
|
|
|
:::note Initial Setup
|
|
|
|
Follow the [Environment Setup](https://reactnative.dev/docs/environment-setup)
|
|
of the React Native documentation before testing the demo.
|
|
|
|
:::
|
|
|
|
Create a new project by running the following commands in the Terminal:
|
|
|
|
<CodeBlock language="bash">{`\
|
|
npx react-native@0.71 init SheetJSPres --version="0.72.0-rc.1"
|
|
cd SheetJSPres
|
|
\n\
|
|
npm i -S https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz react-native-blob-util@0.17.1`}
|
|
</CodeBlock>
|
|
|
|
Save the following to `App.tsx` in the project:
|
|
|
|
```js title="App.tsx"
|
|
import React from 'react';
|
|
import { Alert, Button, SafeAreaView, Text, View } from 'react-native';
|
|
import { utils, version, write } from 'xlsx';
|
|
import RNBU from 'react-native-blob-util';
|
|
|
|
const make_workbook = async() => {
|
|
/* fetch JSON data and parse */
|
|
const url = "https://sheetjs.com/data/executive.json";
|
|
const raw_data = await (await fetch(url)).json();
|
|
|
|
/* filter for the Presidents */
|
|
const prez = raw_data.filter(row => row.terms.some(term => term.type === "prez"));
|
|
|
|
/* sort by first presidential term */
|
|
prez.forEach(prez => prez.start = prez.terms.find(term => term.type === "prez").start);
|
|
prez.sort((l,r) => l.start.localeCompare(r.start));
|
|
|
|
/* flatten objects */
|
|
const rows = prez.map(row => ({
|
|
name: row.name.first + " " + row.name.last,
|
|
birthday: row.bio.birthday
|
|
}));
|
|
|
|
/* generate worksheet and workbook */
|
|
const worksheet = utils.json_to_sheet(rows);
|
|
const workbook = utils.book_new();
|
|
utils.book_append_sheet(workbook, worksheet, "Dates");
|
|
|
|
/* fix headers */
|
|
utils.sheet_add_aoa(worksheet, [["Name", "Birthday"]], { origin: "A1" });
|
|
|
|
/* calculate column width */
|
|
const max_width = rows.reduce((w, r) => Math.max(w, r.name.length), 10);
|
|
worksheet["!cols"] = [ { wch: max_width } ];
|
|
|
|
/* React Native does not support `writeFile`. This is a low-level write ! */
|
|
|
|
/* write workbook to buffer */
|
|
const buf = write(workbook, {type:'buffer', bookType:"xlsx"});
|
|
|
|
/* write buffer to file */
|
|
const filename = RNBU.fs.dirs.DocumentDir + "/Presidents.xlsx";
|
|
await RNBU.fs.writeFile(filename, Array.from(buf), 'ascii');
|
|
|
|
/* Copy to downloads directory (android) */
|
|
try { await RNBU.MediaCollection.copyToMediaStore({
|
|
parentFolder: "",
|
|
mimeType: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
name: "Presidents.xlsx"
|
|
}, "Download", filename); } catch(e) {}
|
|
|
|
return filename;
|
|
};
|
|
|
|
const App = () => ( <SafeAreaView><View style={{ marginTop: 32, padding: 24 }}>
|
|
<Text style={{ fontSize: 24, fontWeight: 'bold' }}>SheetJS {version} Export Demo</Text>
|
|
<Button title='Press to Export' onPress={async() => {
|
|
try {
|
|
const filename = await make_workbook();
|
|
Alert.alert("Export Finished", `Exported to ${filename}`);
|
|
} catch(err) {
|
|
Alert.alert("Export Error", `Error ${err.message||err}`);
|
|
}
|
|
}}/>
|
|
</View></SafeAreaView> );
|
|
|
|
export default App;
|
|
```
|
|
|
|
<Tabs>
|
|
<TabItem value="asim" label="Android">
|
|
|
|
:::note
|
|
|
|
The Android demo has been tested in Windows 10 and in macOS.
|
|
|
|
:::
|
|
|
|
Test the app in the Android simulator:
|
|
|
|
```bash
|
|
npm run android
|
|
```
|
|
|
|
After clicking "Press to Export", the app will show an alert with the location
|
|
to the generated file.
|
|
|
|
In the Android simulator, pulling the file requires additional steps. This
|
|
command will pull a Base64-encoded string from the simulator:
|
|
|
|
```bash
|
|
adb exec-out run-as com.sheetjspres base64 files/Presidents.xlsx > pres.b64
|
|
```
|
|
|
|
Decoding the file requires an OS-specific command:
|
|
|
|
<Tabs>
|
|
<TabItem value="windows" label="Windows">
|
|
|
|
```powershell
|
|
certutil -decode .\pres.b64 .\Presidents.xlsx
|
|
```
|
|
|
|
</TabItem>
|
|
<TabItem value="macos" label="macOS">
|
|
|
|
```bash
|
|
base64 -D pres.b64 > Presidents.xlsx
|
|
```
|
|
|
|
</TabItem>
|
|
</Tabs>
|
|
|
|
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.
|
|
|
|
`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.
|
|
|
|
:::
|
|
|
|
</TabItem>
|
|
<TabItem value="ios" label="iOS">
|
|
|
|
:::caution
|
|
|
|
This demo runs in iOS and requires a Macintosh computer with Xcode installed.
|
|
|
|
:::
|
|
|
|
The native component must be linked:
|
|
|
|
```bash
|
|
cd ios; pod install; cd ..
|
|
```
|
|
|
|
Test the app in the iOS simulator:
|
|
|
|
```bash
|
|
npm run ios
|
|
```
|
|
|
|
After clicking "Press to Export", the app will show an alert with the location
|
|
to the generated file.
|
|
|
|
:::info Device Testing
|
|
|
|
["Running on Device"](https://reactnative.dev/docs/running-on-device) in the
|
|
React Native docs covers device configuration.
|
|
|
|
The `UIFileSharingEnabled` and `LSSupportsOpeningDocumentsInPlace` entitlements
|
|
are required for iOS to show the generated files in the "Files" app.
|
|
|
|
The highlighted lines should be added to the iOS project `Info.plist` just
|
|
before the last `</dict>` tag:
|
|
|
|
```xml title="ios/SheetJSPres/Info.plist"
|
|
<key>UIViewControllerBasedStatusBarAppearance</key>
|
|
<false/>
|
|
<!-- highlight-start -->
|
|
<key>UIFileSharingEnabled</key>
|
|
<true/>
|
|
<key>LSSupportsOpeningDocumentsInPlace</key>
|
|
<true/>
|
|
<!-- highlight-end -->
|
|
</dict>
|
|
</plist>
|
|
```
|
|
|
|
After adding the settings and rebuilding the app, the file will be visible in
|
|
the "Files" app. Under "On My iPhone", there will be a folder `SheetJSPres`.
|
|
Within the folder there will be a file named `Presidents`. Touch the file to
|
|
see a preview of the data. The Numbers app can open the file.
|
|
|
|
:::
|
|
|
|
</TabItem>
|
|
</Tabs>
|
|
|
|
</TabItem>
|
|
</Tabs> |