docs.sheetjs.com/docz/docs/03-demos/02-grid/17-gdg.md
2023-04-19 16:03:23 -04:00

7.3 KiB

title pagination_prev pagination_next
Glide Datagrid demos/frontend/index 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:

// !! 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:

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:

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:

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:

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];
};

// ...

Importing from Data Store

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:

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:

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:

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:
npm create vite@latest -- sheetjs-gdg --template react-ts
cd sheetjs-gdg
npm i

Install SheetJS and Glide Data Grid required dependencies:

npm i --save https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz
npm i --save @glideapps/glide-data-grid lodash marked

Start dev server:

npm run dev

The terminal window will display a URL (typically http://localhost:5173). Open the URL with a web browser.

  1. Download App.tsx and replace src/App.tsx:
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

  1. 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

Click on the "Export" button to create a file! Open the file and verify.