2023-04-07 08:30:20 +00:00
|
|
|
---
|
|
|
|
title: Glide Datagrid
|
|
|
|
pagination_prev: demos/frontend/index
|
|
|
|
pagination_next: demos/net/index
|
|
|
|
---
|
|
|
|
|
2023-04-27 09:12:19 +00:00
|
|
|
import current from '/version.js';
|
2023-05-01 01:27:02 +00:00
|
|
|
import CodeBlock from '@theme/CodeBlock';
|
2023-04-27 09:12:19 +00:00
|
|
|
|
2023-04-07 08:30:20 +00:00
|
|
|
:::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`.
|
|
|
|
|
|
|
|
:::
|
|
|
|
|
|
|
|
## Integration 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[] = [];
|
|
|
|
```
|
|
|
|
|
|
|
|
#### 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];
|
|
|
|
};
|
|
|
|
|
|
|
|
// ...
|
|
|
|
```
|
|
|
|
|
2023-04-19 20:03:23 +00:00
|
|
|
_Importing from Data Store_
|
2023-04-07 08:30:20 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
```
|
|
|
|
|
|
|
|
## Demo
|
|
|
|
|
|
|
|
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:
|
|
|
|
|
2023-05-01 01:27:02 +00:00
|
|
|
<CodeBlock language="bash">{`\
|
|
|
|
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz @glideapps/glide-data-grid@5.2.1`}
|
|
|
|
</CodeBlock>
|
2023-04-07 08:30:20 +00:00
|
|
|
|
|
|
|
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.
|