docusaurus

This commit is contained in:
SheetJS 2022-09-03 06:02:45 -04:00
parent ea501889ac
commit 9ae03ac35a
11 changed files with 679 additions and 122 deletions

@ -197,6 +197,7 @@ PPI
PhantomJS
Photoshop
PostgreSQL
PouchDB
PowerShell
Preact
QuickJS

@ -8,7 +8,7 @@ build:
.PHONY: init
init:
cd docz; npm i; cd ..
cd docz; npm i || npm i --legacy-peer-deps; cd ..
.PHONY: dev
dev:

@ -303,6 +303,35 @@ function ws_to_rdg(ws: WorkSheet): RowCol {
In the other direction, a worksheet can be generated with `aoa_to_sheet`:
:::caution
When the demo was last refreshed, row array objects were preserved. This was
not the case in a later release. The row arrays must be re-created.
The snippet defines a `arrayify` function that creates arrays if necessary.
:::
```ts
import { WorkSheet, utils } from 'xlsx';
type Row = any[];
// highlight-start
function arrayify(rows: any[]): Row[] {
return rows.map(row => {
var length = Object.keys(row).length;
for(; length > 0; --length) if(row[length-1] != null) break;
return Array.from({length, ...row});
});
}
// highlight-end
function rdg_to_ws(rows: Row[]): WorkSheet {
return utils.aoa_to_sheet(arrayify(rows));
}
```
```ts
import { WorkSheet, utils } from 'xlsx';
@ -334,125 +363,128 @@ cd sheetjs-cra
npm i -S https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz react-data-grid
```
3) Replace the contents of `src/App.tsx` with the following code. Note: a copy
to clipboard button will show up if you move your mouse over the code. The
notable SheetJS integration code is highlighted below:
```tsx title="src/App.tsx"
import React, { useEffect, useState, ChangeEvent } from "react";
import DataGrid, { textEditor, Column } from "react-data-grid";
import { read, utils, WorkSheet, writeFile } from "xlsx";
import './App.css';
type DataSet = { [index: string]: WorkSheet; };
type Row = any[];
type AOAColumn = Column<Row>;
type RowCol = { rows: Row[]; columns: AOAColumn[]; };
/* this method returns `rows` and `columns` data for sheet change */
const getRowsCols = ( data: DataSet, sheetName: string ): RowCol => ({
rows: utils.sheet_to_json<Row>(data[sheetName], {header:1}),
columns: Array.from({
length: utils.decode_range(data[sheetName]["!ref"]||"A1").e.c + 1
}, (_, i) => ({ key: String(i), name: utils.encode_col(i), editor: textEditor }))
});
export default function App() {
const [rows, setRows] = useState<Row[]>([]); // data rows
const [columns, setColumns] = useState<AOAColumn[]>([]); // columns
const [workBook, setWorkBook] = useState<DataSet>({} as DataSet); // workbook
const [sheets, setSheets] = useState<string[]>([]); // list of sheet names
const [current, setCurrent] = useState<string>(""); // selected sheet
/* called when sheet dropdown is changed */
function selectSheet(name: string) {
// highlight-start
/* update workbook cache in case the current worksheet was changed */
workBook[current] = utils.aoa_to_sheet(rows);
// highlight-end
/* get data for desired sheet and update state */
const { rows: new_rows, columns: new_columns } = getRowsCols(workBook, name);
setRows(new_rows);
setColumns(new_columns);
setCurrent(name);
}
/* this method handles refreshing the state with new workbook data */
async function handleAB(file: ArrayBuffer): Promise<void> {
// highlight-start
/* read file data */
const data = read(file);
// highlight-end
/* update workbook state */
setWorkBook(data.Sheets);
setSheets(data.SheetNames);
/* select the first worksheet */
const name = data.SheetNames[0];
const { rows: new_rows, columns: new_columns } = getRowsCols(data.Sheets, name);
setRows(new_rows);
setColumns(new_columns);
setCurrent(name);
}
/* called when file input element is used to select a new file */
async function handleFile(ev: ChangeEvent<HTMLInputElement>): Promise<void> {
const file = await ev.target.files?.[0]?.arrayBuffer();
if(file) await handleAB(file);
}
/* when page is loaded, fetch and processs worksheet */
useEffect(() => { (async () => {
const f = await fetch("https://sheetjs.com/pres.numbers");
await handleAB(await f.arrayBuffer());
})(); }, []);
/* method is called when one of the save buttons is clicked */
function saveFile(ext: string): void {
/* update current worksheet in case changes were made */
workBook[current] = utils.aoa_to_sheet(rows);
// highlight-start
/* construct workbook and loop through worksheets */
const wb = utils.book_new();
sheets.forEach((n) => { utils.book_append_sheet(wb, workBook[n], n); });
// highlight-end
/* generate a file and download it */
writeFile(wb, "sheet." + ext);
}
return (
<>
<h3>SheetJS × React-Data-Grid Demo</h3>
<input type="file" onChange={handleFile} />
{sheets.length > 0 && ( <>
<p>Use the dropdown to switch to a worksheet:&nbsp;
<select onChange={async (e) => selectSheet(sheets[+(e.target.value)])}>
{sheets.map((sheet, idx) => (<option key={sheet} value={idx}>{sheet}</option>))}
</select>
</p>
<div className="flex-cont"><b>Current Sheet: {current}</b></div>
<DataGrid columns={columns} rows={rows} onRowsChange={setRows} />
<p>Click one of the buttons to create a new file with the modified data</p>
<div className="flex-cont">{["xlsx", "xlsb", "xls"].map((ext) => (
<button key={ext} onClick={() => saveFile(ext)}>export [.{ext}]</button>
))}</div>
</>)}
</>
);
}
```
3) Download [`App.tsx`](pathname:///rdg/App.tsx) and replace `src/App.tsx`.
4) run `npm start`. When you load the page in the browser, it will attempt to
fetch <https://sheetjs.com/pres.numbers> and load the data.
The following screenshot was taken from the demo:
![react-data-grid screenshot](pathname:///react/rdg1.png)
![react-data-grid screenshot](pathname:///rdg/rdg1.png)
</details>
### Material UI Data Grid
Material UI Data Grid and React Data Grid share many state patterns and idioms.
Differences from ["React Data Grid"](#react-data-grid) will be highlighted.
[A complete example is included below.](#muidg-demo)
:::warning
Despite presenting an editable UI, Material UI Data Grid version `5.17.0` does
not update the state when values are changed. The demo uses the React Data Grid
editable structure in the hopes that a future version does support state.
Until the issues are resolved, "React Data Grid" is an excellent choice.
:::
**Rows and Columns State**
The analogue of `Column` is `GridColDef`. The simple structure looks like:
```js
// highlight-next-line
import { DataGrid, GridColDef } from "@mui/x-data-grid";
export default function App() {
const [rows, setRows] = useState([]);
const [columns, setColumns] = useState([]);
return ( <DataGrid columns={columns} rows={rows} onRowsChange={setRows} /> );
}
```
The most generic data representation is an array of arrays. To sate the grid,
columns must be objects whose `field` property is the index converted to string:
```ts
import { WorkSheet, utils } from 'xlsx';
// highlight-next-line
import { GridColDef } from "@mui/x-data-grid";
type Row = any[];
type RowCol = { rows: Row[]; columns: GridColDef[]; };
function ws_to_muidg(ws: WorkSheet): RowCol {
/* create an array of arrays */
const rows = utils.sheet_to_json(ws, { header: 1 });
/* create column array */
const range = utils.decode_range(ws["!ref"]||"A1");
const columns = Array.from({ length: range.e.c + 1 }, (_, i) => ({
// highlight-start
field: String(i), // MUIDG will access row["0"], row["1"], etc
headerName: utils.encode_col(i), // the column labels will be A, B, etc
editable: true // enable cell editing
// highlight-end
}));
return { rows, columns }; // these can be fed to setRows / setColumns
}
```
In the other direction, a worksheet can be generated with `aoa_to_sheet`:
:::caution
`x-data-grid` does not properly preserve row array objects, so the row arrays
must be re-created. The snippet defines a `arrayify` function.
:::
```ts
import { WorkSheet, utils } from 'xlsx';
type Row = any[];
// highlight-start
function arrayify(rows: any[]): Row[] {
return rows.map(row => {
var length = Object.keys(row).length;
for(; length > 0; --length) if(row[length-1] != null) break;
return Array.from({length, ...row});
});
}
// highlight-end
function muidg_to_ws(rows: Row[]): WorkSheet {
return utils.aoa_to_sheet(arrayify(rows));
}
```
<!-- spellchecker-disable -->
#### MUIDG Demo
<!-- spellchecker-enable -->
<details><summary><b>Complete Example</b> (click to show)</summary>
0) [Follow the React Data Grid demo](#rdg-demo) and generate the sample app.
1) Install dependencies:
```bash
npm i -S https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz @mui/x-data-grid @emotion/react @emotion/styled
```
2) Download [`App.tsx`](pathname:///muidg/App.tsx) and replace `src/App.tsx`.
3) run `npm start`. When you load the page in the browser, it will attempt to
fetch <https://sheetjs.com/pres.numbers> and load the data.
</details>
@ -585,3 +617,205 @@ cd ..
fetch <https://sheetjs.com/pres.numbers> and load the data.
</details>
## Standard HTML Tables
Many UI components present styled HTML tables. Data can be extracted from the
tables given a reference to the underlying TABLE element:
```js
function export_html_table(table) {
const wb = XLSX.utils.table_to_book(table);
XLSX.writeFile(wb, "HTMLTable.xlsx");
} // yes, it's that easy!
```
:::info
SheetJS CE is focused on data preservation and will extract values from tables.
[SheetJS Pro](https://sheetjs.com/pro) offers styling support when reading from
TABLE elements and when writing to XLSX and other spreadsheet formats.
:::
### Fixed Tables
When the page has a raw HTML table, the easiest solution is to attach an `id`:
```html
<table id="xport"><tr><td>SheetJS</td></tr></table>
<script src="https://cdn.sheetjs.com/xlsx-latest/package/dist/shim.min.js"></script>
<script src="https://cdn.sheetjs.com/xlsx-latest/package/dist/xlsx.full.min.js"></script>
<script>
/* as long as this script appears after the table, it will be visible */
var tbl = document.getElementById("xport");
const wb = XLSX.utils.table_to_book(tbl);
XLSX.writeFile(wb, "HTMLTable.xlsx");
</script>
```
When programmatically constructing the table in the browser, retain a reference:
```js
var tbl = document.createElement("TABLE");
tbl.insertRow(0).insertCell(0).innerHTML = "SheetJS";
document.body.appendChild(tbl);
const wb = XLSX.utils.table_to_book(tbl);
XLSX.writeFile(wb, "HTMLFlicker.xlsx");
document.body.removeChild(tbl);
```
### React
The typical solution is to attach a Ref to the table element. The `current`
property will be a live reference which plays nice with `table_to_book`:
```jsx
// highlight-next-line
import { useRef } from "react";
export default function ReactTable() {
// highlight-next-line
const tbl = useRef(null);
return ( <>
<button onClick={() => {
// highlight-next-line
const wb = XLSX.utils.table_to_book(tbl.current);
XLSX.writeFile(wb, "ReactTable.xlsx");
}}>Export</button>
// highlight-next-line
<table ref={tbl}>
{/* ... TR and TD/TH elements ... */}
</table>
</>);
}
```
### Material UI Table
The `Table` component abstracts the `<table>` element in HTML.
```tsx
import TableContainer from '@mui/material/TableContainer';
import Table from '@mui/material/Table';
// ...
// highlight-next-line
import { useRef } from "react";
// ...
export default function BasicTable() {
// highlight-next-line
const tbl = useRef<HTMLTableElement>(null);
return (<>
<button onClick={() => {
const wb = utils.table_to_book(tbl.current);
writeFileXLSX(wb, "SheetJSMaterialUI.xlsx");
}}>Export</button>
<TableContainer {...}>
// highlight-next-line
<Table {...} ref={tbl}>
{/* ... material ui table machinations ... */}
</Table>
</TableContainer>
<>);
}
```
<details><summary><b>Complete Example</b> (click to show)</summary>
1) Create a new TypeScript `create-react-app` app:
```bash
npx create-react-app sheetjs-mui --template typescript
cd sheetjs-mui
```
2) Install dependencies:
```bash
npm i -S https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz @mui/material
```
3) Replace `src/App.tsx` with the following code. This is based on the official
Material UI Table example. Differences are highlighted.
```tsx title="src/App.tsx"
// highlight-start
import React, { useEffect, useState, useRef, ChangeEvent } from "react";
import { utils, writeFileXLSX } from 'xlsx';
// highlight-end
import Table from '@mui/material/Table';
import TableBody from '@mui/material/TableBody';
import TableCell from '@mui/material/TableCell';
import TableContainer from '@mui/material/TableContainer';
import TableHead from '@mui/material/TableHead';
import TableRow from '@mui/material/TableRow';
import Paper from '@mui/material/Paper';
function createData(
name: string,
calories: number,
fat: number,
carbs: number,
protein: number,
) {
return { name, calories, fat, carbs, protein };
}
const rows = [
createData('Frozen yoghurt', 159, 6.0, 24, 4.0),
createData('Ice cream sandwich', 237, 9.0, 37, 4.3),
createData('Eclair', 262, 16.0, 24, 6.0),
createData('Cupcake', 305, 3.7, 67, 4.3),
createData('Gingerbread', 356, 16.0, 49, 3.9),
];
export default function BasicTable() {
// highlight-start
const tbl = useRef<HTMLTableElement>(null);
return ( <>
<button onClick={() => {
const wb = utils.table_to_book(tbl.current);
writeFileXLSX(wb, "SheetJSMaterialUI.xlsx");
}}>Export</button>
// highlight-end
<TableContainer component={Paper}>
// highlight-next-line
<Table sx={{ minWidth: 650 }} aria-label="simple table" ref={tbl}>
<TableHead>
<TableRow>
<TableCell>Dessert (100g serving)</TableCell>
<TableCell align="right">Calories</TableCell>
<TableCell align="right">Fat&nbsp;(g)</TableCell>
<TableCell align="right">Carbs&nbsp;(g)</TableCell>
<TableCell align="right">Protein&nbsp;(g)</TableCell>
</TableRow>
</TableHead>
<TableBody>
{rows.map((row) => (
<TableRow
key={row.name}
sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
>
<TableCell component="th" scope="row">
{row.name}
</TableCell>
<TableCell align="right">{row.calories}</TableCell>
<TableCell align="right">{row.fat}</TableCell>
<TableCell align="right">{row.carbs}</TableCell>
<TableCell align="right">{row.protein}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
// highlight-next-line
</> );
}
```
4) run `npm start`. Click the "Export" button and inspect the generated file.
</details>

@ -15,7 +15,7 @@ and TypeScript familiarity is assumed.
Other demos cover general Angular deployments, including:
- [iOS and Android applications powered by NativeScript](./mobile#nativescript)
- [iOS and Android applications powered by ionic](./mobile#ionic)
- [iOS and Android applications powered by Ionic](./mobile#ionic)
:::warning

@ -205,3 +205,104 @@ Inspect the output and compare with the data in `SheetJSRedisTest.mjs`.
Open `SheetJSRedis.xlsx` and verify the columns have the correct data
</details>
### PouchDB
`Database#allDocs` is the standard approach for bulk data export. The generated
row objects have an additional `_id` and `_rev` keys that should be removed.
Nested objects must be flattened. The ["Tutorial"](../getting-started/example)
includes an example of constructing a simple array.
```js
function export_pouchdb_to_xlsx(db) {
/* fetch all rows, including the underlying data */
db.allDocs({include_docs: true}, function(err, doc) {
/* pull the individual data rows */
const aoo = doc.rows.map(r => {
/* `rest` will include every field from `r` except for _id and _rev */
const { _id, _rev, ...rest } = r;
return rest;
});
/* generate worksheet */
const ws = XLSX.utils.json_to_sheet(aoo);
/* generate workbook and export */
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, "Sheet1");
XLSX.writeFile(wb, "SheetJSPouch.xlsx");
});
}
```
<details><summary><b>Complete Example</b> (click to show)</summary>
0) Download the "Working Version" from the Getting Started guide.
[ZIP](https://github.com/nickcolley/getting-started-todo/archive/master.zip)
The ZIP file should have `MD5` checksum `ac4da7cb0cade1be293ba222462f109c`:
```bash
curl -LO https://github.com/nickcolley/getting-started-todo/archive/master.zip
md5sum master.zip || md5 master.zip
### the checksum will be printed
```
If the download is unavailable, a mirror is available at
<https://docs.sheetjs.com/pouchdb/master.zip>
1) Unzip the `master.zip` file and enter the folder:
```bash
unzip master.zip
cd getting-started-todo-master
```
2) Edit `index.html` to reference the SheetJS library and add a button:
```html title="index.html"
<body>
<!-- highlight-start -->
<script src="https://cdn.sheetjs.com/xlsx-latest/package/dist/xlsx.full.min.js"></script>
<button id="xport">Export!</button>
<!-- highlight-end -->
<section id="todoapp">
```
3) Just before the end of `app.js`, add a `click` event listener:
```js title="app.js"
if (remoteCouch) {
sync();
}
// highlight-start
document.getElementById("xport").addEventListener("click", function() {
db.allDocs({include_docs: true}, function(err, doc) {
const aoo = doc.rows.map(r => {
const { _id, _rev, ... rest } = r.doc;
return rest;
});
const ws = XLSX.utils.json_to_sheet(aoo);
const wb = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(wb, ws, "Sheet1");
XLSX.writeFile(wb, "SheetJSPouch.xlsx");
});
});
// highlight-end
})();
```
4) Start a local web server:
```bash
npx http-server .
```
Access `http://localhost:8080` from your browser. Add a few items and click
the "Export!" button to generate a new file.
</details>

@ -35,7 +35,7 @@ run in the web browser, demos will include interactive examples.
- [`react-data-grid`](./grid#react-data-grid)
- [`vue3-table-lite`](./grid#vue3-table-lite)
- [`angular-ui-grid`](./grid#angular-ui-grid)
- [`material ui`](./grid#material-ui-table)
### Platforms and Integrations
- [`Command-Line Tools`](./cli)

@ -15,11 +15,11 @@
},
"dependencies": {
"@cmfcmf/docusaurus-search-local": "0.11.0",
"@docusaurus/core": "2.0.1",
"@docusaurus/plugin-client-redirects": "2.0.1",
"@docusaurus/preset-classic": "2.0.1",
"@docusaurus/theme-common": "2.0.1",
"@docusaurus/theme-live-codeblock": "2.0.1",
"@docusaurus/core": "2.1.0",
"@docusaurus/plugin-client-redirects": "2.1.0",
"@docusaurus/preset-classic": "2.1.0",
"@docusaurus/theme-common": "2.1.0",
"@docusaurus/theme-live-codeblock": "2.1.0",
"@mdx-js/react": "1.6.22",
"clsx": "1.2.1",
"prism-react-renderer": "1.3.5",
@ -28,7 +28,7 @@
"xlsx": "https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "2.0.1"
"@docusaurus/module-type-aliases": "2.1.0"
},
"browserslist": {
"production": [

111
docz/static/muidg/App.tsx Normal file

@ -0,0 +1,111 @@
import React, { useEffect, useState, ChangeEvent } from "react";
import { DataGrid, GridColDef } from "@mui/x-data-grid";
import { read, utils, WorkSheet, writeFile } from "xlsx";
import './App.css';
type DataSet = { [index: string]: WorkSheet; };
type Row = any[];
type RowCol = { rows: Row[]; columns: GridColDef[]; };
function arrayify(rows: any[]): Row[] {
return rows.map(row => {
if(Array.isArray(row)) return row;
var length = Object.keys(row).length;
for(; length > 0; --length) if(row[length-1] != null) break;
return Array.from({length, ...row});
});
}
/* this method returns `rows` and `columns` data for sheet change */
const getRowsCols = ( data: DataSet, sheetName: string ): RowCol => ({
rows: utils.sheet_to_json<Row>(data[sheetName], {header:1}).map((r,id) => ({...r, id})),
columns: Array.from({
length: utils.decode_range(data[sheetName]["!ref"]||"A1").e.c + 1
}, (_, i) => ({ field: String(i), headerName: utils.encode_col(i), editable: true }))
});
export default function App() {
const [rows, setRows] = useState<Row[]>([]); // data rows
const [columns, setColumns] = useState<GridColDef[]>([]); // columns
const [workBook, setWorkBook] = useState<DataSet>({} as DataSet); // workbook
const [sheets, setSheets] = useState<string[]>([]); // list of sheet names
const [current, setCurrent] = useState<string>(""); // selected sheet
/* called when sheet dropdown is changed */
function selectSheet(name: string) {
/* update workbook cache in case the current worksheet was changed */
workBook[current] = utils.aoa_to_sheet(arrayify(rows));
/* get data for desired sheet and update state */
const { rows: new_rows, columns: new_columns } = getRowsCols(workBook, name);
setRows(new_rows);
setColumns(new_columns);
setCurrent(name);
}
/* this method handles refreshing the state with new workbook data */
async function handleAB(file: ArrayBuffer): Promise<void> {
/* read file data */
const data = read(file);
/* update workbook state */
setWorkBook(data.Sheets);
setSheets(data.SheetNames);
/* select the first worksheet */
const name = data.SheetNames[0];
const { rows: new_rows, columns: new_columns } = getRowsCols(data.Sheets, name);
setRows(new_rows);
setColumns(new_columns);
setCurrent(name);
}
/* called when file input element is used to select a new file */
async function handleFile(ev: ChangeEvent<HTMLInputElement>): Promise<void> {
const file = await ev.target.files?.[0]?.arrayBuffer();
if(file) await handleAB(file);
}
/* when page is loaded, fetch and processs worksheet */
useEffect(() => { (async () => {
const f = await fetch("https://sheetjs.com/pres.numbers");
await handleAB(await f.arrayBuffer());
})(); }, []);
/* method is called when one of the save buttons is clicked */
function saveFile(ext: string): void {
console.log(rows);
/* update current worksheet in case changes were made */
workBook[current] = utils.aoa_to_sheet(arrayify(rows));
/* construct workbook and loop through worksheets */
const wb = utils.book_new();
sheets.forEach((n) => { utils.book_append_sheet(wb, workBook[n], n); });
/* generate a file and download it */
writeFile(wb, "SheetJSMUIDG." + ext);
}
return (
<>
<h3>SheetJS × MUI Data Grid Demo</h3>
<input type="file" onChange={handleFile} />
{sheets.length > 0 && ( <>
<p>Use the dropdown to switch to a worksheet:&nbsp;
<select onChange={async (e) => selectSheet(sheets[+(e.target.value)])}>
{sheets.map((sheet, idx) => (<option key={sheet} value={idx}>{sheet}</option>))}
</select>
</p>
<div className="flex-cont"><b>Current Sheet: {current}</b></div>
<div style={{width:"100%", height:400}}>
<DataGrid columns={columns} rows={rows} experimentalFeatures={{ newEditingApi: true }} />
</div>
<p>Click one of the buttons to create a new file with the modified data</p>
<div className="flex-cont">{["xlsx", "xlsb", "xls"].map((ext) => (
<button key={ext} onClick={() => saveFile(ext)}>export [.{ext}]</button>
))}</div>
</>)}
</>
);
}

Binary file not shown.

110
docz/static/rdg/App.tsx Normal file

@ -0,0 +1,110 @@
import React, { useEffect, useState, ChangeEvent } from "react";
import DataGrid, { textEditor, Column } from "react-data-grid";
import { read, utils, WorkSheet, writeFile } from "xlsx";
import './App.css';
type DataSet = { [index: string]: WorkSheet; };
type Row = any[];
type AOAColumn = Column<Row>;
type RowCol = { rows: Row[]; columns: AOAColumn[]; };
function arrayify(rows: any[]): Row[] {
return rows.map(row => {
if(Array.isArray(row)) return row;
var length = Object.keys(row).length;
for(; length > 0; --length) if(row[length-1] != null) break;
return Array.from({length, ...row});
});
}
/* this method returns `rows` and `columns` data for sheet change */
const getRowsCols = ( data: DataSet, sheetName: string ): RowCol => ({
rows: utils.sheet_to_json<Row>(data[sheetName], {header:1}),
columns: Array.from({
length: utils.decode_range(data[sheetName]["!ref"]||"A1").e.c + 1
}, (_, i) => ({ key: String(i), name: utils.encode_col(i), editor: textEditor }))
});
export default function App() {
const [rows, setRows] = useState<Row[]>([]); // data rows
const [columns, setColumns] = useState<AOAColumn[]>([]); // columns
const [workBook, setWorkBook] = useState<DataSet>({} as DataSet); // workbook
const [sheets, setSheets] = useState<string[]>([]); // list of sheet names
const [current, setCurrent] = useState<string>(""); // selected sheet
/* called when sheet dropdown is changed */
function selectSheet(name: string) {
/* update workbook cache in case the current worksheet was changed */
workBook[current] = utils.aoa_to_sheet(arrayify(rows));
/* get data for desired sheet and update state */
const { rows: new_rows, columns: new_columns } = getRowsCols(workBook, name);
setRows(new_rows);
setColumns(new_columns);
setCurrent(name);
}
/* this method handles refreshing the state with new workbook data */
async function handleAB(file: ArrayBuffer): Promise<void> {
/* read file data */
const data = read(file);
/* update workbook state */
setWorkBook(data.Sheets);
setSheets(data.SheetNames);
/* select the first worksheet */
const name = data.SheetNames[0];
const { rows: new_rows, columns: new_columns } = getRowsCols(data.Sheets, name);
setRows(new_rows);
setColumns(new_columns);
setCurrent(name);
}
/* called when file input element is used to select a new file */
async function handleFile(ev: ChangeEvent<HTMLInputElement>): Promise<void> {
const file = await ev.target.files?.[0]?.arrayBuffer();
if(file) await handleAB(file);
}
/* when page is loaded, fetch and processs worksheet */
useEffect(() => { (async () => {
const f = await fetch("https://sheetjs.com/pres.numbers");
await handleAB(await f.arrayBuffer());
})(); }, []);
/* method is called when one of the save buttons is clicked */
function saveFile(ext: string): void {
console.log(rows);
/* update current worksheet in case changes were made */
workBook[current] = utils.aoa_to_sheet(arrayify(rows));
/* construct workbook and loop through worksheets */
const wb = utils.book_new();
sheets.forEach((n) => { utils.book_append_sheet(wb, workBook[n], n); });
/* generate a file and download it */
writeFile(wb, "SheetJSRDG." + ext);
}
return (
<>
<h3>SheetJS × React-Data-Grid Demo</h3>
<input type="file" onChange={handleFile} />
{sheets.length > 0 && ( <>
<p>Use the dropdown to switch to a worksheet:&nbsp;
<select onChange={async (e) => selectSheet(sheets[+(e.target.value)])}>
{sheets.map((sheet, idx) => (<option key={sheet} value={idx}>{sheet}</option>))}
</select>
</p>
<div className="flex-cont"><b>Current Sheet: {current}</b></div>
<DataGrid columns={columns} rows={rows} onRowsChange={setRows} />
<p>Click one of the buttons to create a new file with the modified data</p>
<div className="flex-cont">{["xlsx", "xlsb", "xls"].map((ext) => (
<button key={ext} onClick={() => saveFile(ext)}>export [.{ext}]</button>
))}</div>
</>)}
</>
);
}

Before

(image error) Size: 107 KiB

After

(image error) Size: 107 KiB