gdg
This commit is contained in:
parent
15401fc1ed
commit
a93a691ee0
@ -16,6 +16,7 @@ Other demos cover general React deployments, including:
|
||||
- [iOS and Android applications powered by React Native](/docs/demos/mobile/reactnative)
|
||||
- [Desktop application powered by React Native Windows + macOS](/docs/demos/desktop/reactnative)
|
||||
- [React Data Grid UI component](/docs/demos/grid#react-data-grid)
|
||||
- [Glide Data Grid UI component](/docs/demos/grid#glide-data-grid)
|
||||
|
||||
|
||||
## Installation
|
||||
|
@ -331,6 +331,16 @@ function ws_to_rdg(ws: WorkSheet): RowCol {
|
||||
|
||||
In the other direction, a worksheet can be generated with `aoa_to_sheet`:
|
||||
|
||||
```ts
|
||||
import { WorkSheet, utils } from 'xlsx';
|
||||
|
||||
type Row = any[];
|
||||
|
||||
function rdg_to_ws(rows: Row[]): WorkSheet {
|
||||
return utils.aoa_to_sheet(rows);
|
||||
}
|
||||
```
|
||||
|
||||
:::caution
|
||||
|
||||
When the demo was last refreshed, row array objects were preserved. This was
|
||||
@ -338,8 +348,6 @@ 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';
|
||||
|
||||
@ -360,14 +368,311 @@ function rdg_to_ws(rows: Row[]): WorkSheet {
|
||||
}
|
||||
```
|
||||
|
||||
```ts
|
||||
import { WorkSheet, utils } from 'xlsx';
|
||||
:::
|
||||
|
||||
type Row = any[];
|
||||
|
||||
function rdg_to_ws(rows: Row[]): WorkSheet {
|
||||
return utils.aoa_to_sheet(rows);
|
||||
### Glide Data Grid
|
||||
|
||||
:::note
|
||||
|
||||
This demo was last tested on 2023 February 07 with the ViteJS+React+TypeScript
|
||||
starter (Vite `4.1.1`, React `18.2.0`) and `@glideapps/glide-data-grid@5.2.1`.
|
||||
|
||||
:::
|
||||
|
||||
#### GDG Demo
|
||||
|
||||
<details><summary><b>Complete Example</b> (click to show)</summary>
|
||||
|
||||
1) Create a new project:
|
||||
|
||||
```bash
|
||||
npm create vite@latest -- sheetjs-gdg --template react-ts
|
||||
cd sheetjs-gdg
|
||||
npm i
|
||||
```
|
||||
|
||||
Install SheetJS and Glide Data Grid required dependencies:
|
||||
|
||||
```bash
|
||||
npm i --save https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz
|
||||
npm i --save @glideapps/glide-data-grid lodash marked
|
||||
```
|
||||
|
||||
Start dev server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
The terminal window will display a URL (typically `http://localhost:5173`).
|
||||
Open the URL with a web browser.
|
||||
|
||||
2) Download [`App.tsx`](pathname:///gdg/App.tsx) and replace `src/App.tsx`:
|
||||
|
||||
```bash
|
||||
curl -L -o src/App.tsx https://docs.sheetjs.com/gdg/App.tsx
|
||||
```
|
||||
|
||||
Refresh the browser window and a grid should be displayed:
|
||||
|
||||
![glide-data-grid initial view](pathname:///gdg/pre.png)
|
||||
|
||||
3) To test the export functionality, make some changes to the grid data.
|
||||
|
||||
Suppose you believe that President Grover Cleveland should be counted once.
|
||||
That would imply President Clinton should be index 41 and the indices of the
|
||||
other presidents should be decremented. By double-clicking on each cell in the
|
||||
Index column, a cell editor should appear. Decrement each index:
|
||||
|
||||
![glide-data-grid after edits](pathname:///gdg/post.png)
|
||||
|
||||
Click on the "Export" button to create a file! Open the file and verify.
|
||||
|
||||
</details>
|
||||
|
||||
#### Backing Store
|
||||
|
||||
Under the hood, the `DataEditor` component is designed to call methods and
|
||||
request data to display in the grid. It is typical to store data *outside* of
|
||||
component state. A `getCellContent` callback will pull data from the external
|
||||
backing store, while SheetJS operations will directly act on the store:
|
||||
|
||||
```js
|
||||
// !! THESE OBJECTS ARE DEFINED OUTSIDE OF THE COMPONENT FUNCTION !!
|
||||
|
||||
// this will store the raw data objects
|
||||
let data: any[] = [];
|
||||
// this will store the header names
|
||||
let header: string[] = [];
|
||||
```
|
||||
|
||||
#### GDG Props
|
||||
|
||||
:::note
|
||||
|
||||
This is a high-level overview. The official documentation should be consulted.
|
||||
|
||||
:::
|
||||
|
||||
_Columns_
|
||||
|
||||
`DataEditor` expects column metadata to be passed through a `columns` prop. This
|
||||
should be managed in the component state:
|
||||
|
||||
```js
|
||||
import { useState } from 'react';
|
||||
import { DataEditor, GridColumn } from '@glideapps/glide-data-grid';
|
||||
|
||||
function App() {
|
||||
// highlight-next-line
|
||||
const [cols, setCols] = useState<GridColumn[]>([]); // gdg column objects
|
||||
// ...
|
||||
return ( <>
|
||||
// ...
|
||||
<DataEditor
|
||||
// ... props
|
||||
// highlight-next-line
|
||||
columns={cols}
|
||||
/>
|
||||
// ...
|
||||
</> );
|
||||
}
|
||||
export default App;
|
||||
```
|
||||
|
||||
Each `GridColumn` object expects a `title` representing the display name and an
|
||||
`id` representing the key to index within the data object.
|
||||
|
||||
_Data_
|
||||
|
||||
The `DataEditor` component expects a `getCellContent` callback for supplying
|
||||
data. The callback accepts column and row indices. The column index should be
|
||||
used to find the header key:
|
||||
|
||||
```js
|
||||
import { useCallback } from 'react';
|
||||
import { DataEditor, GridCellKind, GridCell, Item } from '@glideapps/glide-data-grid';
|
||||
|
||||
// ...
|
||||
|
||||
function App() {
|
||||
// ...
|
||||
// backing data store -> gdg
|
||||
// highlight-start
|
||||
const getContent = useCallback((cell: Item): GridCell => {
|
||||
const [col, row] = cell;
|
||||
return {
|
||||
kind: GridCellKind.Text,
|
||||
// header[col] is the name of the field
|
||||
displayData: String(data[row]?.[header[col]]??""),
|
||||
data: data[row]?.[header[col]],
|
||||
};
|
||||
}, []);
|
||||
// highlight-end
|
||||
// ...
|
||||
return ( <>
|
||||
// ...
|
||||
<DataEditor
|
||||
// ... props
|
||||
// highlight-next-line
|
||||
getCellContent={getContent}
|
||||
/>
|
||||
// ...
|
||||
</> );
|
||||
}
|
||||
```
|
||||
|
||||
_Row Count_
|
||||
|
||||
`DataEditor` also accepts a `rows` property indicating the number of rows. This
|
||||
is best managed in state:
|
||||
|
||||
```js
|
||||
import { useState } from 'react';
|
||||
import { DataEditor } from '@glideapps/glide-data-grid';
|
||||
|
||||
function App() {
|
||||
// highlight-next-line
|
||||
const [rows, setRows] = useState<number>(0); // number of rows
|
||||
// ...
|
||||
return ( <>
|
||||
// ...
|
||||
<DataEditor
|
||||
// ... props
|
||||
// highlight-next-line
|
||||
rows={rows}
|
||||
/>
|
||||
// ...
|
||||
</> );
|
||||
}
|
||||
export default App;
|
||||
```
|
||||
|
||||
_Editing Data_
|
||||
|
||||
The demo uses the `onCellEdited` callback to write back to the data store.
|
||||
|
||||
#### Parsing Data
|
||||
|
||||
_SheetJS to Data Store_
|
||||
|
||||
The raw data objects are readily generated with `sheet_to_json`. The headers
|
||||
can be pulled by extracting the first row of the worksheet:
|
||||
|
||||
```js
|
||||
import { utils, WorkBook } from 'xlsx';
|
||||
|
||||
// ...
|
||||
|
||||
const update_backing_store = (wb: WorkBook) => {
|
||||
// get first worksheet
|
||||
const sheet = wb.Sheets[wb.SheetNames[0]];
|
||||
|
||||
// set data
|
||||
// highlight-next-line
|
||||
data = utils.sheet_to_json<any>(sheet);
|
||||
|
||||
// create a range consisting of the first row
|
||||
const range = utils.decode_range(sheet["!ref"]??"A1"); // original range
|
||||
range.e.r = range.s.r; // set ending row to starting row (select first row)
|
||||
|
||||
// pull headers
|
||||
// highlight-next-line
|
||||
header = utils.sheet_to_json<string[]>(sheet, {header: 1, range})[0];
|
||||
};
|
||||
|
||||
// ...
|
||||
```
|
||||
|
||||
_Data Store to GDG_
|
||||
|
||||
Scheduling a refresh for the `DataEditor` involves updating the grid column
|
||||
metadata and row count through the standard state. It also requires a special
|
||||
`updateCells` call to instruct the grid to mark the cached data as stale:
|
||||
|
||||
```js
|
||||
import { useRef } from 'react'
|
||||
import { WorkBook } from 'xlsx'
|
||||
import { DataEditor, GridColumn, Item, DataEditorRef } from '@glideapps/glide-data-grid'
|
||||
|
||||
function App() {
|
||||
const ref = useRef<DataEditorRef>(null); // gdg ref
|
||||
// ...
|
||||
const parse_wb = (wb: WorkBook) => {
|
||||
update_backing_store(wb);
|
||||
|
||||
// highlight-start
|
||||
// update column metadata by pulling from external header keys
|
||||
setCols(header.map(h => ({title: h, id: h} as GridColumn)));
|
||||
|
||||
// update number of rows
|
||||
setRows(data.length);
|
||||
|
||||
if(data.length > 0) {
|
||||
// create an array of the cells that must be updated
|
||||
let cells = data.map(
|
||||
(_,R) => Array.from({length:header.length}, (_,C) => ({cell: ([C,R] as Item)}))
|
||||
).flat();
|
||||
// initiate update using the `ref` attached to the DataEditor
|
||||
ref.current?.updateCells(cells)
|
||||
}
|
||||
// highlight-end
|
||||
};
|
||||
// ...
|
||||
return ( <>
|
||||
// ...
|
||||
<DataEditor
|
||||
// ... props
|
||||
// highlight-next-line
|
||||
ref={ref}
|
||||
/>
|
||||
// ...
|
||||
</> );
|
||||
}
|
||||
export default App;
|
||||
```
|
||||
|
||||
#### Writing Data
|
||||
|
||||
`json_to_sheet` works directly on the `data` array:
|
||||
|
||||
```js
|
||||
const ws = utils.json_to_sheet(data); // easy :)
|
||||
```
|
||||
|
||||
Since the editor can change the header titles, it is strongly recommended to
|
||||
pull column data from the state and rewrite the header row:
|
||||
|
||||
```js
|
||||
import { utils, writeFileXLSX } from 'xlsx';
|
||||
|
||||
function App() {
|
||||
// ...
|
||||
const exportXLSX = useCallback(() => {
|
||||
// highlight-start
|
||||
// generate worksheet using data with the order specified in the columns array
|
||||
const ws = utils.json_to_sheet(data, {header: cols.map(c => c.id ?? c.title)});
|
||||
|
||||
// rewrite header row with titles
|
||||
utils.sheet_add_aoa(ws, [cols.map(c => c.title ?? c.id)], {origin: "A1"});
|
||||
// highlight-end
|
||||
|
||||
// create workbook
|
||||
const wb = utils.book_new();
|
||||
utils.book_append_sheet(wb, ws, "Export"); // replace with sheet name
|
||||
// download file
|
||||
writeFileXLSX(wb, "sheetjs-gdg.xlsx");
|
||||
}, []);
|
||||
// ...
|
||||
return ( <>
|
||||
// ...
|
||||
// highlight-next-line
|
||||
<button onClick={exportXLSX}><b>Export XLSX!</b></button>
|
||||
// ...
|
||||
</> );
|
||||
}
|
||||
export default App;
|
||||
```
|
||||
|
||||
### Material UI Data Grid
|
||||
|
@ -34,6 +34,7 @@ run in the web browser, demos will include interactive examples.
|
||||
- [`canvas-datagrid`](/docs/demos/grid#canvas-datagrid)
|
||||
- [`x-spreadsheet`](/docs/demos/grid#x-spreadsheet)
|
||||
- [`react-data-grid`](/docs/demos/grid#react-data-grid)
|
||||
- [`glide-data-grid`](/docs/demos/grid#glide-data-grid)
|
||||
- [`vue3-table-lite`](/docs/demos/grid#vue3-table-lite)
|
||||
- [`angular-ui-grid`](/docs/demos/grid#angular-ui-grid)
|
||||
- [`material ui`](/docs/demos/grid#material-ui-table)
|
||||
|
82
docz/static/gdg/App.tsx
Normal file
82
docz/static/gdg/App.tsx
Normal file
@ -0,0 +1,82 @@
|
||||
import { useState, useCallback, useEffect, useRef, ChangeEvent } from 'react';
|
||||
import { utils, read, writeFileXLSX, WorkBook } from 'xlsx';
|
||||
import { DataEditor, GridCellKind, GridCell, GridColumn, Item, DataEditorRef, EditableGridCell } from '@glideapps/glide-data-grid';
|
||||
import "@glideapps/glide-data-grid/dist/index.css";
|
||||
|
||||
// this will store the raw data objects
|
||||
let data: any[] = [];
|
||||
// this will store the header names
|
||||
let header: string[] = [];
|
||||
|
||||
function App() {
|
||||
const [cols, setCols] = useState<GridColumn[]>([]); // gdg column objects
|
||||
const [rows, setRows] = useState<number>(0); // number of rows
|
||||
const ref = useRef<DataEditorRef>(null); // gdg ref
|
||||
|
||||
// read/write between gdg and the backing data store
|
||||
const getContent = useCallback((cell: Item): GridCell => {
|
||||
const [col, row] = cell;
|
||||
return {
|
||||
kind: GridCellKind.Text,
|
||||
allowOverlay: true,
|
||||
readonly: false,
|
||||
displayData: String(data[row]?.[header[col]]??""),
|
||||
data: data[row]?.[header[col]],
|
||||
};
|
||||
}, []);
|
||||
const onCellEdited = useCallback((cell: Item, newValue: EditableGridCell) => {
|
||||
const [ col, row ] = cell;
|
||||
data[row][header[col]] = newValue.data;
|
||||
}, []);
|
||||
|
||||
// update the data store from a workbook object
|
||||
const parse_wb = (wb: WorkBook) => {
|
||||
const sheet = wb.Sheets[wb.SheetNames[0]];
|
||||
data = utils.sheet_to_json<any>(sheet);
|
||||
const range = utils.decode_range(sheet["!ref"]??"A1"); range.e.r = range.s.r;
|
||||
header = utils.sheet_to_json<string[]>(sheet, {header: 1, range})[0];
|
||||
setCols(header.map(h => ({title: h, id: h} as GridColumn)));
|
||||
setRows(data.length);
|
||||
if(data.length > 0) {
|
||||
let cells = data.map(
|
||||
(_,R) => Array.from({length:header.length}, (_,C) => ({cell: ([C,R] as Item)}))
|
||||
).flat();
|
||||
ref.current?.updateCells(cells);
|
||||
}
|
||||
};
|
||||
// file input element onchange event handler
|
||||
const onChange = useCallback(async (e: ChangeEvent<HTMLInputElement>) => {
|
||||
if(!e.target?.files) return;
|
||||
parse_wb(read(await e.target.files[0].arrayBuffer()));
|
||||
}, []);
|
||||
// when the component loads, fetch and display a sample workbook
|
||||
useEffect(() => {
|
||||
(async() => {
|
||||
parse_wb(read(await (await fetch("https://sheetjs.com/pres.numbers")).arrayBuffer()));
|
||||
})();
|
||||
}, []);
|
||||
|
||||
// export data
|
||||
const exportXLSX = useCallback(() => {
|
||||
// generate worksheet using data with the order specified in the columns array
|
||||
const ws = utils.json_to_sheet(data, {header: cols.map(c => c.id ?? c.title)});
|
||||
// rewrite header row with titles
|
||||
utils.sheet_add_aoa(ws, [cols.map(c => c.title ?? c.id)], {origin: "A1"});
|
||||
// create workbook
|
||||
const wb = utils.book_new();
|
||||
utils.book_append_sheet(wb, ws, "Export"); // replace with sheet name
|
||||
// download file
|
||||
writeFileXLSX(wb, "sheetjs-gdg.xlsx");
|
||||
}, []);
|
||||
|
||||
return ( <>
|
||||
<input type="file" onChange={onChange} />
|
||||
<button onClick={exportXLSX}><b>Export XLSX!</b></button>
|
||||
<div className="App">
|
||||
<DataEditor getCellContent={getContent} columns={cols} rows={rows} onCellEdited={onCellEdited} ref={ref}/>
|
||||
</div>
|
||||
<div id="portal"></div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
export default App;
|
BIN
docz/static/gdg/post.png
Normal file
BIN
docz/static/gdg/post.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 66 KiB |
BIN
docz/static/gdg/pre.png
Normal file
BIN
docz/static/gdg/pre.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 66 KiB |
Loading…
Reference in New Issue
Block a user