This commit is contained in:
SheetJS 2023-05-11 02:17:10 -04:00
parent e6fc787cd2
commit e1584a41a3
8 changed files with 363 additions and 279 deletions

@ -161,7 +161,11 @@ However, this does not handle merge cells well!
The `sheet_to_html` function generates HTML that is aware of merges and other
worksheet features. React `dangerouslySetInnerHTML` attribute allows code to
set the `innerHTML` attribute, effectively inserting the code into the page:
set the `innerHTML` attribute, effectively inserting the code into the page.
In this example, the component attaches a `ref` to the `DIV` container. During
export, the first `TABLE` child element can be parsed with `table_to_book` to
generate a workbook object.
```jsx title="src/SheetJSReactHTML.js"
import React, { useCallback, useEffect, useRef, useState } from "react";

@ -0,0 +1,229 @@
---
title: Material UI
pagination_prev: demos/frontend/index
pagination_next: demos/net/index
---
import current from '/version.js';
import CodeBlock from '@theme/CodeBlock';
This demo covers the traditional Material UI Table as well as the MUI Data Grid.
## Material UI Table
The `Table` component abstracts the `<table>` element in HTML. `table_to_book`
can process a `ref` attached to the `Table` element:
```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={() => {
// highlight-next-line
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>
<>);
}
```
#### MUI Table Demo
<details open><summary><b>Complete Example</b> (click to hide)</summary>
:::note
This demo was last run on 2023 May 11 against Material UI 5.13.0 paired with
Emotion 11.11.0
:::
1) Create a new TypeScript `create-react-app` app:
```bash
npx create-react-app sheetjs-mui --template typescript
cd sheetjs-mui
```
2) Install dependencies:
<CodeBlock language="bash">{`\
npm i -S https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz @mui/material@5.13.0 @emotion/react@11.11.0 @emotion/styled@11.11.0`}
</CodeBlock>
3) Download [`App.tsx`](pathname:///mui/table/App.tsx) and replace `src/App.tsx`.
```bash
curl -L -o src/App.tsx https://docs.sheetjs.com/mui/table/App.tsx
```
4) Start the development server:
```bash
npm start
```
The script should open the live demo in a web browser. Click the "Export" button
to save the file. Open the generated file in a spreadsheet editor.
</details>
## Material UI Data Grid
[A complete example is included below.](#muidg-demo)
**Rows and Columns State**
MUI Data Grid state consists of an Array of column metadata and an Array of row
objects. Typically both are defined in state:
```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} /> );
}
```
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';
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) => ({
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
}));
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));
}
```
**Editing Cells**
The `processRowUpdate` prop receives the new row data, allowing for easy
state mutation:
```tsx
import { GridRowModel } from "@mui/x-data-grid";
export default function App() {
// ...
const processRowUpdate = useCallback((rowNew: GridRowModel, rowOld: GridRowModel) => {
/* scan each column and manually set state entries */
for(var j = 0; j < columns.length; ++j) if(rowNew[j] != null) {
rows[rowNew.id][j] = isNaN(+rowNew[j]) ? rowNew[j] : +rowNew[j];
}
/* force a state update */
setRows(rows);
/* commit the new row */
return rowNew;
}, [columns, rows]);
return ( <DataGrid columns={columns} rows={rows} processRowUpdate={processRowUpdate} /> );
}
```
<!-- spellchecker-disable -->
#### MUIDG Demo
<!-- spellchecker-enable -->
<details open><summary><b>Complete Example</b> (click to hide)</summary>
:::note
This demo was last run on 2023 May 11 against MUI data grid 6.3.1 paired with
Emotion 11.11.0
:::
1) Create a new TypeScript `create-react-app` app:
```bash
npx create-react-app sheetjs-muidg --template typescript
cd sheetjs-muidg
```
2) Install dependencies:
<CodeBlock language="bash">{`\
npm i -S https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz @mui/x-data-grid @emotion/react @emotion/styled`}
</CodeBlock>
3) Download [`App.tsx`](pathname:///mui/dg/App.tsx) and replace `src/App.tsx`.
```bash
curl -L -o src/App.tsx https://docs.sheetjs.com/mui/dg/App.tsx
```
4) Start the development server:
```bash
npm start
```
When the page loads, it will fetch and process <https://sheetjs.com/pres.numbers>
</details>

@ -102,117 +102,7 @@ with other buttons and components on the page.
### Material UI Data Grid
Material UI Data Grid and React Data Grid share many state patterns and idioms.
Differences from ["React Data Grid"](/docs/demos/grid/rdg) 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:
<CodeBlock language="bash">{`\
npm i -S https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz @mui/x-data-grid @emotion/react @emotion/styled`}
</CodeBlock>
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>
**[The exposition has been moved to a separate page.](/docs/demos/grid/mui#material-ui-data-grid)**
<!-- spellchecker-disable -->
@ -282,154 +172,8 @@ 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>
</> );
}
```
**[The exposition has been moved to a separate page.](/docs/demos/frontend/react#html)**
### 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:
<CodeBlock language="bash">{`\
npm i -S https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz @mui/material`}
</CodeBlock>
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>
**[The exposition has been moved to a separate page.](/docs/demos/grid/mui#material-ui-table)**

@ -170,8 +170,8 @@ const aoa = [ ["Hash"], [key] ].concat(Object.entries(values));
:::note
This demo was last tested on 2023 February 23 with Redis 7.0.8, Redis connector
module 4.6.4 and NodeJS 18.14.2.
This demo was last tested on 2023 May 11 with Redis 7.0.11, Redis connector
module 4.6.6 and NodeJS 20.1.0.
:::
@ -179,19 +179,28 @@ module 4.6.4 and NodeJS 18.14.2.
The most recent version of the `redis` node module does not work with most
versions of NodeJS. It is "ESM-only", requiring NodeJS 18 or later. As a result,
this demo also requires NodeJS version 18.
Questions regarding the `redis` library and the decision to drop traditional
NodeJS "CommonJS" module support should be directed to the Redis team.
this demo also requires NodeJS version 18 or later.
:::
0) Set up and start a local Redis server. On Intel macOS:
0) Set up and start a local Redis server.
:::note
This demo was last tested on Intel macOS. Redis was installed with:
```bash
brew install redis@7.0.8
brew install redis@7.0.11
```
The following command started the server process:
```bash
/usr/local/opt/redis/bin/redis-server /usr/local/etc/redis.conf
```
:::
1) Download the following scripts:
- [`SheetJSRedis.mjs`](pathname:///nosql/SheetJSRedis.mjs)
@ -202,13 +211,18 @@ curl -LO https://docs.sheetjs.com/nosql/SheetJSRedis.mjs
curl -LO https://docs.sheetjs.com/nosql/SheetJSRedisTest.mjs
```
2) Install dependencies and run:
2) Install dependencies:
<CodeBlock language="bash">{`\
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz redis@4.6.4
node SheetJSRedisTest.mjs`}
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz redis@4.6.6`}
</CodeBlock>
3) Run the test script:
```bash
node SheetJSRedisTest.mjs
```
Inspect the output and compare with the data in `SheetJSRedisTest.mjs`.
Open `SheetJSRedis.xlsx` and verify the columns have the correct data

@ -5,11 +5,22 @@ sidebar_position: 2
# Hyperlinks
<details>
<summary><b>Format Support</b> (click to show)</summary>
<summary><b>File Format Support</b> (click to show)</summary>
**Cell Hyperlinks**: XLSX/M, XLSB, BIFF8 XLS, XLML, ODS, HTML
| Formats | Link | Tooltip | Storage Representation |
|:------------------|:-----:|:-------:|:-----------------------|
| XLSX / XLSM | ✔ | ✔ | Cell Link + Tooltip |
| XLSB | ✔ | ✔ | Cell Link + Tooltip |
| XLS (BIFF8) | ✔ | ✔ | Cell Link + Tooltip |
| XLML | ✔ | ✔ | Cell Link + Tooltip |
| ODS / FODS / UOS | ✔ | | Span Link + Tooltip |
| HTML | ✔ | * | Span Link |
| NUMBERS | | * | Span Link ** |
**Tooltips**: XLSX/M, XLSB, BIFF8 XLS, XLML
Asterisks (*) mark features that are not supported by the file formats.
For "Span Link" formats, parsers apply the first hyperlink to the entire cell
and writers apply the hyperlink to the entire cell text.
</details>
@ -26,9 +37,19 @@ ws["A1"].l = { Target: "https://sheetjs.com", Tooltip: "Find us @ SheetJS.com!"
:::note
Following traditional software, hyperlinks are applied to entire cell objects.
Some formats (including HTML) attach links to text spans. The parsers apply the
first link to the entire cell. Writers apply links to the entire cell text.
:::
:::caution
Excel does not automatically style hyperlinks. They will be displayed using
the default cell style. <a href="https://sheetjs.com/pro">SheetJS Pro Basic</a>
extends this export with support for hyperlink styling.
the default cell style.
<a href="https://sheetjs.com/pro">SheetJS Pro Basic</a> includes support for
general hyperlink styling.
:::

@ -1,5 +1,5 @@
import React, { useEffect, useState, ChangeEvent } from "react";
import { DataGrid, GridColDef } from "@mui/x-data-grid";
import React, { useCallback, useEffect, useState, ChangeEvent } from "react";
import { DataGrid, GridColDef, GridRowModel } from "@mui/x-data-grid";
import { read, utils, WorkSheet, writeFile } from "xlsx";
import './App.css';
@ -87,6 +87,12 @@ export default function App() {
writeFile(wb, "SheetJSMUIDG." + ext);
}
const processRowUpdate = useCallback((rowNew: GridRowModel, rowOld: GridRowModel) => {
for(var j = 0; j < columns.length; ++j) if(rowNew[j] != null) rows[rowNew.id][j] = isNaN(+rowNew[j]) ? rowNew[j] : +rowNew[j];
setRows(rows);
return rowNew;
}, [columns, rows]);
return (
<>
<h3>SheetJS × MUI Data Grid Demo</h3>
@ -99,7 +105,7 @@ export default function App() {
</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 }} />
<DataGrid columns={columns} rows={rows} processRowUpdate={processRowUpdate} />
</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) => (

@ -0,0 +1,66 @@
import React, { useRef } from "react";
import { utils, writeFileXLSX } from 'xlsx';
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() {
const tbl = useRef<HTMLTableElement>(null);
return ( <>
<button onClick={() => {
const wb = utils.table_to_book(tbl.current);
writeFileXLSX(wb, "SheetJSMaterialUI.xlsx");
}}>Export</button>
<TableContainer component={Paper}>
<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>
</> );
}