| 
									
										
										
										
											2023-04-07 08:30:20 +00:00
										 |  |  | --- | 
					
						
							|  |  |  | 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]; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // ... | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ```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: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |  | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 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: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |  | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Click on the "Export" button to create a file!  Open the file and verify. |