forked from sheetjs/docs.sheetjs.com
		
	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: | ||||
| 
 | ||||
|  | ||||
| 
 | ||||
| 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. | ||||
| 
 | ||||
| </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