From e1584a41a32f20646c6c7f65f15390f809c30bc5 Mon Sep 17 00:00:00 2001 From: SheetJS Date: Thu, 11 May 2023 02:17:10 -0400 Subject: [PATCH] mui --- docz/docs/03-demos/01-frontend/01-react.md | 6 +- .../03-demos/02-grid/{11-rdg.md => 16-rdg.md} | 0 docz/docs/03-demos/02-grid/18-mui.md | 229 +++++++++++++++ docz/docs/03-demos/02-grid/index.md | 262 +----------------- docz/docs/03-demos/07-data/26-redis.md | 36 ++- docz/docs/07-csf/07-features/02-hyperlinks.md | 31 ++- docz/static/{muidg => mui/dg}/App.tsx | 12 +- docz/static/mui/table/App.tsx | 66 +++++ 8 files changed, 363 insertions(+), 279 deletions(-) rename docz/docs/03-demos/02-grid/{11-rdg.md => 16-rdg.md} (100%) create mode 100644 docz/docs/03-demos/02-grid/18-mui.md rename docz/static/{muidg => mui/dg}/App.tsx (88%) create mode 100644 docz/static/mui/table/App.tsx diff --git a/docz/docs/03-demos/01-frontend/01-react.md b/docz/docs/03-demos/01-frontend/01-react.md index 8025419..bfeaff3 100644 --- a/docz/docs/03-demos/01-frontend/01-react.md +++ b/docz/docs/03-demos/01-frontend/01-react.md @@ -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"; diff --git a/docz/docs/03-demos/02-grid/11-rdg.md b/docz/docs/03-demos/02-grid/16-rdg.md similarity index 100% rename from docz/docs/03-demos/02-grid/11-rdg.md rename to docz/docs/03-demos/02-grid/16-rdg.md diff --git a/docz/docs/03-demos/02-grid/18-mui.md b/docz/docs/03-demos/02-grid/18-mui.md new file mode 100644 index 0000000..37e159f --- /dev/null +++ b/docz/docs/03-demos/02-grid/18-mui.md @@ -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 `` 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(null); + return ( <> + + +// highlight-next-line +
+ {/* ... material ui table machinations ... */} +
+ + <>); +} +``` + +#### MUI Table Demo + +
Complete Example (click to hide) + +:::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: + +{`\ +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`} + + +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. + +
+ +## 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 ( ); +} +``` + +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 ( ); +} +``` + + + +#### MUIDG Demo + + + +
Complete Example (click to hide) + +:::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: + +{`\ +npm i -S https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz @mui/x-data-grid @emotion/react @emotion/styled`} + + +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 + +
diff --git a/docz/docs/03-demos/02-grid/index.md b/docz/docs/03-demos/02-grid/index.md index a48a820..3a65e86 100644 --- a/docz/docs/03-demos/02-grid/index.md +++ b/docz/docs/03-demos/02-grid/index.md @@ -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 ( ); -} -``` - -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)); -} -``` - - - -#### MUIDG Demo - - - -
Complete Example (click to show) - -0) [Follow the React Data Grid demo](#rdg-demo) and generate the sample app. - -1) Install dependencies: - -{`\ -npm i -S https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.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 and load the data. - -
+**[The exposition has been moved to a separate page.](/docs/demos/grid/mui#material-ui-data-grid)** @@ -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 ( <> - - // highlight-next-line - - {/* ... TR and TD/TH elements ... */} -
- ); -} -``` +**[The exposition has been moved to a separate page.](/docs/demos/frontend/react#html)** ### Material UI Table -The `Table` component abstracts the `` 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(null); - return ( <> - - -// highlight-next-line -
- {/* ... material ui table machinations ... */} -
- - <>); -} -``` - -
Complete Example (click to show) - -1) Create a new TypeScript `create-react-app` app: - -```bash -npx create-react-app sheetjs-mui --template typescript -cd sheetjs-mui -``` - -2) Install dependencies: - -{`\ -npm i -S https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.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(null); - return ( <> - - // highlight-end - - // highlight-next-line - - - - Dessert (100g serving) - Calories - Fat (g) - Carbs (g) - Protein (g) - - - - {rows.map((row) => ( - - - {row.name} - - {row.calories} - {row.fat} - {row.carbs} - {row.protein} - - ))} - -
-
- // highlight-next-line - ); -} -``` - -4) run `npm start`. Click the "Export" button and inspect the generated file. - -
+**[The exposition has been moved to a separate page.](/docs/demos/grid/mui#material-ui-table)** diff --git a/docz/docs/03-demos/07-data/26-redis.md b/docz/docs/03-demos/07-data/26-redis.md index 71a49d1..d78de7a 100644 --- a/docz/docs/03-demos/07-data/26-redis.md +++ b/docz/docs/03-demos/07-data/26-redis.md @@ -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: {`\ -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`} +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 diff --git a/docz/docs/07-csf/07-features/02-hyperlinks.md b/docz/docs/07-csf/07-features/02-hyperlinks.md index 14874d9..1045fe3 100644 --- a/docz/docs/07-csf/07-features/02-hyperlinks.md +++ b/docz/docs/07-csf/07-features/02-hyperlinks.md @@ -5,11 +5,22 @@ sidebar_position: 2 # Hyperlinks
- Format Support (click to show) + File Format Support (click to show) -**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.
@@ -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. SheetJS Pro Basic -extends this export with support for hyperlink styling. +the default cell style. + +SheetJS Pro Basic includes support for +general hyperlink styling. ::: diff --git a/docz/static/muidg/App.tsx b/docz/static/mui/dg/App.tsx similarity index 88% rename from docz/static/muidg/App.tsx rename to docz/static/mui/dg/App.tsx index 0cefa98..f5c424b 100644 --- a/docz/static/muidg/App.tsx +++ b/docz/static/mui/dg/App.tsx @@ -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 ( <>

SheetJS × MUI Data Grid Demo

@@ -99,7 +105,7 @@ export default function App() {

Current Sheet: {current}
- +

Click one of the buttons to create a new file with the modified data

{["xlsx", "xlsb", "xls"].map((ext) => ( diff --git a/docz/static/mui/table/App.tsx b/docz/static/mui/table/App.tsx new file mode 100644 index 0000000..3fdd824 --- /dev/null +++ b/docz/static/mui/table/App.tsx @@ -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(null); + return ( <> + + + + + + Dessert (100g serving) + Calories + Fat (g) + Carbs (g) + Protein (g) + + + + {rows.map((row) => ( + + + {row.name} + + {row.calories} + {row.fat} + {row.carbs} + {row.protein} + + ))} + +
+
+ ); +} \ No newline at end of file