This commit is contained in:
SheetJS 2023-04-07 04:30:20 -04:00
parent 04b4124414
commit ef06aba826
9 changed files with 346 additions and 327 deletions

@ -16,7 +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)
- [Glide Data Grid UI component](/docs/demos/grid/gdg)
## Installation

@ -118,8 +118,8 @@ function exportFile() {
:::note
This demo was last run on 2022 November 11 using `vue@3.2.41`. When running
`npm init`, the package `create-vue@3.4.0` was installed.
This demo was last run on 2023 April 06 using `vue@3.2.47`. When running
`npm init`, the package `create-vue@3.6.1` was installed.
:::
@ -194,8 +194,8 @@ function exportFile() {
:::note
This demo was last run on 2022 November 11 using `vue@3.2.41`. When running
`npm init`, the package `create-vue@3.4.0` was installed.
This demo was last run on 2023 April 06 using `vue@3.2.47`. When running
`npm init`, the package `create-vue@3.6.1` was installed.
:::

@ -0,0 +1,305 @@
---
title: Glide Datagrid
pagination_prev: demos/frontend/index
pagination_next: demos/net/index
---
:::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];
};
// ...
```
_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;
```
## 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:
```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.

@ -217,307 +217,7 @@ function rdg_to_ws(rows: Row[]): WorkSheet {
### 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;
```
**[The exposition has been moved to a separate page.](/docs/demos/grid/gdg)**
### Material UI Data Grid

@ -425,7 +425,7 @@ It should prompt to download `SheetJSNest.xlsx`
:::note
This demo was verified on 2022 August 24 using `fastify@4.5.2`
This demo was verified on 2023 April 06 using `fastify@4.15.0`
:::
@ -498,7 +498,7 @@ fastify.get('/', (req, reply) => {
0) Save the following snippet to `SheetJSFastify.js`:
```js
```js title="SheetJSFastify.js"
/* load SheetJS Library */
const XLSX = require("xlsx");
/* load fastify and enable body parsing */

@ -81,8 +81,8 @@ The following query pulls the `Name` and `Index` fields from each row:
:::note
This demo was tested on 2022 November 11 against `create-gatsby@3.0.0`. The
generated project used `gatsby@5.0.0` and `react@18.2.0`.
This demo was tested on 2023 April 06 against `create-gatsby@3.8.0`. The
generated project used `gatsby@5.8.1` and `react@18.2.0`.
:::
@ -121,9 +121,23 @@ npm i --save gatsby-transformer-excel gatsby-source-filesystem
`}
</code></pre>
5) Edit `gatsby-config.js` and add the following lines to the `plugins` array:
5) Make a `src/data` directory, download <https://sheetjs.com/pres.xlsx>, and
move the downloaded file into the new folder:
```js
```bash
mkdir -p src/data
curl -L -o src/data/pres.xlsx https://sheetjs.com/pres.xlsx
```
6) Edit `gatsby-config.js` and add the following lines to the `plugins` array:
```js title="gatsby-config.js"
module.exports = {
siteMetadata: {
title: `sheetjs-gatsby`,
siteUrl: `https://www.yourdomain.tld`,
},
// highlight-start
plugins: [
{
resolve: `gatsby-source-filesystem`,
@ -134,18 +148,12 @@ npm i --save gatsby-transformer-excel gatsby-source-filesystem
},
`gatsby-transformer-excel`,
],
// highlight-end
}
```
Stop and restart the development server process (`npm run develop`).
6) Make a `src/data` directory, download <https://sheetjs.com/pres.xlsx>, and
move the downloaded file into the new folder:
```bash
mkdir -p src/data
curl -L -o src/data/pres.xlsx https://sheetjs.com/pres.xlsx
```
### GraphiQL test
7) Open the GraphiQL editor at `http://localhost:8000/___graphql`.

@ -11,7 +11,7 @@ and on-demand server rendering powered by spreadsheets.
:::note
This demo was tested on 2022 November 18 against Nuxt Content `v1.15.1`.
This demo was tested on 2023 April 06 against Nuxt Content `v1.15.1`.
:::
@ -87,7 +87,7 @@ neatly with nested `v-for`:
:::note
The project was generated using `create-nuxt-app v4.0.0`. The generated project
used Nuxt `v2.15.8` and Nuxt Content `v1.15.1`.
used Nuxt `v2.16.3` and Nuxt Content `v1.15.1`.
:::

@ -153,7 +153,7 @@ Deno.writeFileSync(out_file, new TextEncoder().encode(csv));
:::note
This was tested on 2023 February 11 using the GitHub UI.
This was last tested on 2023 April 06 using the GitHub UI.
:::
@ -240,7 +240,13 @@ jobs:
6) Click the `☰` icon and click "Go to Repository" to return to the repo page.
7) Click "Actions" to see the workflows. In the left column, click `flatsheet`.
7) Click "Settings" to see the repository settings. In the left column, click
"Actions" to expand the submenu and click "General".
Scroll down to "Workflow permissions" and select "Read and write permissions"
if it is not selected. Click "Save".
8) Click "Actions" to see the workflows. In the left column, click `flatsheet`.
This is the page for the action. Every time the action is run, a new entry
will be added to the list.
@ -249,12 +255,12 @@ jobs:
This will start a new run. After about 30 seconds, a new row should show up
in the main area. The icon should be a white `✓` in a green circle.
8) Click "Code" to return to the main view. It should have a file listing that
9) Click "Code" to return to the main view. It should have a file listing that
includes `data.xlsx` (downloaded file) and `data.csv` (generated data)
Now repeat step 7 to run the action a second time. Click "Code" again.
9) Go to the URL bar and change "github.com" to "flatgithub.com". For example,
10) Go to the URL bar and change "github.com" to "flatgithub.com". For example,
if the URL was originally `https://github.com/SheetJS/flat-sheet` , the new
URL should be `https://flatgithub.com/SheetJS/flat-sheet` . Press Enter.

@ -35,7 +35,7 @@ run in the web browser, demos will include interactive examples.
- [`canvas-datagrid`](/docs/demos/grid/cdg)
- [`x-spreadsheet`](/docs/demos/grid/xs)
- [`react-data-grid`](/docs/demos/grid#react-data-grid)
- [`glide-data-grid`](/docs/demos/grid#glide-data-grid)
- [`glide-data-grid`](/docs/demos/grid/gdg)
- [`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)