| 
									
										
										
										
											2022-08-01 05:34:23 +00:00
										 |  |  | --- | 
					
						
							| 
									
										
										
										
											2022-08-26 19:21:53 +00:00
										 |  |  | title: Data Grids and Tables | 
					
						
							| 
									
										
										
										
											2023-02-28 11:40:44 +00:00
										 |  |  | pagination_prev: demos/frontend/index | 
					
						
							|  |  |  | pagination_next: demos/net/index | 
					
						
							| 
									
										
										
										
											2022-08-01 05:34:23 +00:00
										 |  |  | --- | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Various JavaScript UI components provide a more interactive editing experience. | 
					
						
							|  |  |  | Most are able to interchange with arrays of arrays or arrays of data objects. | 
					
						
							|  |  |  | This demo focuses on a few open source data grids. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | :::note | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | [SheetJS Pro](https://sheetjs.com/pro) offers additional features like styling | 
					
						
							|  |  |  | and images. The UI tools typically support many of these advanced features. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | To eliminate any confusion, the live examples linked from this page demonstrate | 
					
						
							|  |  |  | SheetJS Community Edition data interchange. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ::: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ## Managed Lifecycle
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Many UI components tend to manage the entire lifecycle, providing methods to | 
					
						
							|  |  |  | import and export data. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | The `sheet_to_json` utility function generates arrays of objects, which is | 
					
						
							|  |  |  | suitable for a number of libraries.  When more advanced shapes are needed, | 
					
						
							| 
									
										
										
										
											2022-08-25 08:22:28 +00:00
										 |  |  | it is easier to process an array of arrays. | 
					
						
							| 
									
										
										
										
											2022-08-01 05:34:23 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-18 08:41:34 +00:00
										 |  |  | ### x-spreadsheet
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-28 11:40:44 +00:00
										 |  |  | With a familiar UI, `x-spreadsheet` is an excellent choice for a modern editor. | 
					
						
							| 
									
										
										
										
											2022-08-18 08:41:34 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | [Click here for a live integration demo.](pathname:///xspreadsheet/) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-28 11:40:44 +00:00
										 |  |  | [The exposition has been moved to a separate page.](/docs/demos/grid/xs) | 
					
						
							| 
									
										
										
										
											2022-08-18 08:41:34 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-01 05:34:23 +00:00
										 |  |  | ### Canvas DataGrid
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | After extensive testing, [`canvas-datagrid`](https://canvas-datagrid.js.org/demo.html) | 
					
						
							|  |  |  | stood out as a very high-performance grid with an incredibly simple API. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | [Click here for a live integration demo.](pathname:///cdg/index.html) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | <details><summary><b>Full Exposition</b> (click to show)</summary> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | **Obtaining the Library** | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | The `canvas-datagrid` NodeJS packages include a minified script that can be | 
					
						
							|  |  |  | directly inserted as a script tag.  The unpkg CDN also serves this script: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ```html | 
					
						
							|  |  |  | <script src="https://unpkg.com/canvas-datagrid/dist/canvas-datagrid.js"></script> | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | **Previewing Data** | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | The HTML document needs a container element: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ```html | 
					
						
							|  |  |  | <div id="gridctr"></div> | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Grid initialization is a one-liner: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ```js | 
					
						
							|  |  |  | var grid = canvasDatagrid({ | 
					
						
							|  |  |  |   parentNode: document.getElementById('gridctr'), | 
					
						
							|  |  |  |   data: [] | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | For large data sets, it's necessary to constrain the size of the grid. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ```js | 
					
						
							|  |  |  | grid.style.height = '100%'; | 
					
						
							|  |  |  | grid.style.width = '100%'; | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Once the workbook is read and the worksheet is selected, assigning the data | 
					
						
							|  |  |  | variable automatically updates the view: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ```js | 
					
						
							|  |  |  | grid.data = XLSX.utils.sheet_to_json(ws, {header:1}); | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | This demo previews the first worksheet. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | **Editing** | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | `canvas-datagrid` handles the entire edit cycle.  No intervention is necessary. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | **Saving Data** | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | `grid.data` is immediately readable and can be converted back to a worksheet. | 
					
						
							|  |  |  | Some versions return an array-like object without the length, so a little bit of | 
					
						
							|  |  |  | preparation may be needed: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ```js | 
					
						
							|  |  |  | /* converts an array of array-like objects into an array of arrays */ | 
					
						
							|  |  |  | function prep(arr) { | 
					
						
							|  |  |  |   var out = []; | 
					
						
							|  |  |  |   for(var i = 0; i < arr.length; ++i) { | 
					
						
							|  |  |  |     if(!arr[i]) continue; | 
					
						
							|  |  |  |     if(Array.isArray(arr[i])) { out[i] = arr[i]; continue }; | 
					
						
							|  |  |  |     var o = new Array(); | 
					
						
							|  |  |  |     Object.keys(arr[i]).forEach(function(k) { o[+k] = arr[i][k] }); | 
					
						
							|  |  |  |     out[i] = o; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   return out; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* build worksheet from the grid data */ | 
					
						
							|  |  |  | var ws = XLSX.utils.aoa_to_sheet(prep(grid.data)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* build up workbook */ | 
					
						
							|  |  |  | var wb = XLSX.utils.book_new(); | 
					
						
							|  |  |  | XLSX.utils.book_append_sheet(wb, ws, 'SheetJS'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* generate download */ | 
					
						
							|  |  |  | XLSX.writeFile(wb, "SheetJS.xlsx"); | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | **Additional Features** | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | This demo barely scratches the surface.  The underlying grid component includes | 
					
						
							|  |  |  | many additional features including massive data streaming, sorting and styling. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | </details> | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-24 23:48:22 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | ### Tabulator
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-19 21:12:12 +00:00
										 |  |  | [Tabulator](https://tabulator.info/docs/5.4/download#xlsx) includes deep support | 
					
						
							| 
									
										
										
										
											2022-08-25 08:22:28 +00:00
										 |  |  | through a special Export button.  It handles the SheetJS operations internally. | 
					
						
							| 
									
										
										
										
											2022-08-24 23:48:22 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-01 05:34:23 +00:00
										 |  |  | ### Angular UI Grid
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | :::warning | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | This UI Grid is for AngularJS, not the modern Angular.  New projects should not | 
					
						
							|  |  |  | use AngularJS.  This demo is included for legacy applications. | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-09 17:25:32 +00:00
										 |  |  | The [AngularJS demo](/docs/demos/frontend/legacy#angularjs) covers more general strategies. | 
					
						
							| 
									
										
										
										
											2022-08-01 05:34:23 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | ::: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | [Click here for a live integration demo.](pathname:///angularjs/ui-grid.html) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | <details><summary><b>Notes</b> (click to show)</summary> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | The library does not provide any way to modify the import button, so the demo | 
					
						
							| 
									
										
										
										
											2022-08-25 08:22:28 +00:00
										 |  |  | includes a simple directive for a File Input HTML element.  It also includes a | 
					
						
							| 
									
										
										
										
											2022-08-01 05:34:23 +00:00
										 |  |  | sample service for export which adds an item to the export menu. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | The demo `SheetJSImportDirective` follows the prescription from the README for | 
					
						
							|  |  |  | File input controls using `readAsArrayBuffer`, converting to a suitable | 
					
						
							|  |  |  | representation and updating the scope. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | `SheetJSExportService` exposes export functions for `XLSB` and `XLSX`.  Other | 
					
						
							|  |  |  | file formats can be exported by changing the `bookType` variable.  It grabs | 
					
						
							|  |  |  | values from the grid, builds an array of arrays, generates a workbook and forces | 
					
						
							|  |  |  | a download.  By setting the `filename` and `sheetname` options in the `ui-grid` | 
					
						
							|  |  |  | options, the output can be controlled. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | </details> | 
					
						
							| 
									
										
										
										
											2022-08-17 07:10:01 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | ## Framework Lifecycle
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | For modern frameworks like React, data grids tend to follow the framework state | 
					
						
							|  |  |  | and idioms.  The same `sheet_to_json` and `json_to_sheet` / `aoa_to_sheet` | 
					
						
							|  |  |  | methods are used, but they pull from a shared state object that can be mutated | 
					
						
							|  |  |  | with other buttons and components on the page. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ### React Data Grid
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | :::note | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | This demo was tested against `react-data-grid 7.0.0-beta.15`, React 18.2.0, | 
					
						
							|  |  |  | and `create-react-app` 5.0.1 on 2022 August 16. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ::: | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-08 05:43:21 +00:00
										 |  |  | <!-- spellchecker-disable --> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #### RDG Demo
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | <!-- spellchecker-enable --> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | <details><summary><b>Complete Example</b> (click to show)</summary> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 1) Create a new TypeScript `create-react-app` app: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ```bash | 
					
						
							|  |  |  | npx create-react-app sheetjs-cra --template typescript | 
					
						
							|  |  |  | cd sheetjs-cra | 
					
						
							|  |  |  | ``` | 
					
						
							| 
									
										
										
										
											2022-08-17 07:10:01 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-08 05:43:21 +00:00
										 |  |  | 2) Install dependencies: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ```bash | 
					
						
							|  |  |  | npm i -S https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz react-data-grid | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 3) Download [`App.tsx`](pathname:///rdg/App.tsx) and replace `src/App.tsx`. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 4) run `npm start`.  When you load the page in the browser, it will attempt to | 
					
						
							|  |  |  |    fetch <https://sheetjs.com/pres.numbers> and load the data. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | The following screenshot was taken from the demo: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |  | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | </details> | 
					
						
							| 
									
										
										
										
											2022-08-17 07:10:01 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | #### Rows and Columns state
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | `react-data-grid` state consists of an Array of column metadata and an Array of | 
					
						
							|  |  |  | row objects. Typically both are defined in state: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ```jsx | 
					
						
							|  |  |  | import DataGrid, { Column } from "react-data-grid"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export default function App() { | 
					
						
							|  |  |  |   const [rows, setRows] = useState([]); | 
					
						
							|  |  |  |   const [columns, setColumns] = useState([]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return ( <DataGrid columns={columns} rows={rows} onRowsChange={setRows} /> ); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | The most generic data representation is an array of arrays. To sate the grid, | 
					
						
							| 
									
										
										
										
											2022-08-25 08:22:28 +00:00
										 |  |  | columns must be objects whose `key` property is the index converted to string: | 
					
						
							| 
									
										
										
										
											2022-08-17 07:10:01 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | ```ts | 
					
						
							|  |  |  | import { WorkSheet, utils } from 'xlsx'; | 
					
						
							|  |  |  | import { textEditor, Column } from "react-data-grid"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type Row = any[]; | 
					
						
							|  |  |  | type AOAColumn = Column<Row>; | 
					
						
							|  |  |  | type RowCol = { rows: Row[]; columns: AOAColumn[]; }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function ws_to_rdg(ws: WorkSheet): RowCol { | 
					
						
							|  |  |  |   /* create an array of arrays */ | 
					
						
							|  |  |  |   const rows = utils.sheet_to_json(ws, { header: 1 }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /* create column array */ | 
					
						
							|  |  |  |   const range = utils.decode_range(ws["!ref"]||"A1"); | 
					
						
							|  |  |  |   const columns = Array.from({ length: range.e.c + 1 }, (_, i) => ({ | 
					
						
							|  |  |  |     key: String(i), // RDG will access row["0"], row["1"], etc | 
					
						
							|  |  |  |     name: utils.encode_col(i), // the column labels will be A, B, etc | 
					
						
							|  |  |  |     editor: textEditor // enable cell editing | 
					
						
							|  |  |  |   })); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return { rows, columns }; // these can be fed to setRows / setColumns | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | In the other direction, a worksheet can be generated with `aoa_to_sheet`: | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-07 09:24:49 +00:00
										 |  |  | ```ts | 
					
						
							|  |  |  | import { WorkSheet, utils } from 'xlsx'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type Row = any[]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function rdg_to_ws(rows: Row[]): WorkSheet { | 
					
						
							|  |  |  |   return utils.aoa_to_sheet(rows); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-03 10:02:45 +00:00
										 |  |  | :::caution | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | When the demo was last refreshed, row array objects were preserved.  This was | 
					
						
							|  |  |  | 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'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type Row = any[]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // highlight-start | 
					
						
							|  |  |  | function arrayify(rows: any[]): Row[] { | 
					
						
							|  |  |  |   return rows.map(row => { | 
					
						
							|  |  |  |     var length = Object.keys(row).length; | 
					
						
							|  |  |  |     for(; length > 0; --length) if(row[length-1] != null) break; | 
					
						
							|  |  |  |     return Array.from({length, ...row}); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | // highlight-end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function rdg_to_ws(rows: Row[]): WorkSheet { | 
					
						
							|  |  |  |   return utils.aoa_to_sheet(arrayify(rows)); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-07 09:24:49 +00:00
										 |  |  | ::: | 
					
						
							| 
									
										
										
										
											2022-08-17 07:10:01 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-07 09:24:49 +00:00
										 |  |  | ### 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> | 
					
						
							|  |  |  |     // ... | 
					
						
							|  |  |  |   </> ); | 
					
						
							| 
									
										
										
										
											2022-08-17 07:10:01 +00:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2023-02-07 09:24:49 +00:00
										 |  |  | export default App; | 
					
						
							| 
									
										
										
										
											2022-08-17 07:10:01 +00:00
										 |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-03 10:02:45 +00:00
										 |  |  | ### Material UI Data Grid
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Material UI Data Grid and React Data Grid share many state patterns and idioms. | 
					
						
							|  |  |  | Differences from ["React Data Grid"](#react-data-grid) will be highlighted. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | [A complete example is included below.](#muidg-demo) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | :::warning | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Despite presenting an editable UI, Material UI Data Grid version `5.17.0` does | 
					
						
							|  |  |  | not update the state when values are changed. The demo uses the React Data Grid | 
					
						
							|  |  |  | editable structure in the hopes that a future version does support state. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Until the issues are resolved, "React Data Grid" is an excellent choice. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ::: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | **Rows and Columns State** | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | The analogue of `Column` is `GridColDef`.  The simple structure looks like: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ```js | 
					
						
							|  |  |  | // highlight-next-line | 
					
						
							|  |  |  | import { DataGrid, GridColDef } from "@mui/x-data-grid"; | 
					
						
							| 
									
										
										
										
											2022-08-17 07:10:01 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | export default function App() { | 
					
						
							| 
									
										
										
										
											2022-09-03 10:02:45 +00:00
										 |  |  |   const [rows, setRows] = useState([]); | 
					
						
							|  |  |  |   const [columns, setColumns] = useState([]); | 
					
						
							| 
									
										
										
										
											2022-08-17 07:10:01 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-03 10:02:45 +00:00
										 |  |  |   return ( <DataGrid columns={columns} rows={rows} onRowsChange={setRows} /> ); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | The most generic data representation is an array of arrays. To sate the grid, | 
					
						
							|  |  |  | columns must be objects whose `field` property is the index converted to string: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ```ts | 
					
						
							|  |  |  | import { WorkSheet, utils } from 'xlsx'; | 
					
						
							|  |  |  | // highlight-next-line | 
					
						
							|  |  |  | import { GridColDef } from "@mui/x-data-grid"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type Row = any[]; | 
					
						
							|  |  |  | type RowCol = { rows: Row[]; columns: GridColDef[]; }; | 
					
						
							| 
									
										
										
										
											2022-08-17 07:10:01 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-03 10:02:45 +00:00
										 |  |  | function ws_to_muidg(ws: WorkSheet): RowCol { | 
					
						
							|  |  |  |   /* create an array of arrays */ | 
					
						
							|  |  |  |   const rows = utils.sheet_to_json(ws, { header: 1 }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /* create column array */ | 
					
						
							|  |  |  |   const range = utils.decode_range(ws["!ref"]||"A1"); | 
					
						
							|  |  |  |   const columns = Array.from({ length: range.e.c + 1 }, (_, i) => ({ | 
					
						
							| 
									
										
										
										
											2022-08-17 07:10:01 +00:00
										 |  |  |     // highlight-start | 
					
						
							| 
									
										
										
										
											2022-09-03 10:02:45 +00:00
										 |  |  |     field: String(i), // MUIDG will access row["0"], row["1"], etc | 
					
						
							|  |  |  |     headerName: utils.encode_col(i), // the column labels will be A, B, etc | 
					
						
							|  |  |  |     editable: true // enable cell editing | 
					
						
							| 
									
										
										
										
											2022-08-17 07:10:01 +00:00
										 |  |  |     // highlight-end | 
					
						
							| 
									
										
										
										
											2022-09-03 10:02:45 +00:00
										 |  |  |   })); | 
					
						
							| 
									
										
										
										
											2022-08-17 07:10:01 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-03 10:02:45 +00:00
										 |  |  |   return { rows, columns }; // these can be fed to setRows / setColumns | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | ``` | 
					
						
							| 
									
										
										
										
											2022-08-17 07:10:01 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-03 10:02:45 +00:00
										 |  |  | In the other direction, a worksheet can be generated with `aoa_to_sheet`: | 
					
						
							| 
									
										
										
										
											2022-08-17 07:10:01 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-03 10:02:45 +00:00
										 |  |  | :::caution | 
					
						
							| 
									
										
										
										
											2022-08-17 07:10:01 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-03 10:02:45 +00:00
										 |  |  | `x-data-grid` does not properly preserve row array objects, so the row arrays | 
					
						
							|  |  |  | must be re-created.  The snippet defines a `arrayify` function. | 
					
						
							| 
									
										
										
										
											2022-08-17 07:10:01 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-03 10:02:45 +00:00
										 |  |  | ::: | 
					
						
							| 
									
										
										
										
											2022-08-17 07:10:01 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-03 10:02:45 +00:00
										 |  |  | ```ts | 
					
						
							|  |  |  | import { WorkSheet, utils } from 'xlsx'; | 
					
						
							| 
									
										
										
										
											2022-08-17 07:10:01 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-03 10:02:45 +00:00
										 |  |  | type Row = any[]; | 
					
						
							| 
									
										
										
										
											2022-08-17 07:10:01 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-03 10:02:45 +00:00
										 |  |  | // highlight-start | 
					
						
							|  |  |  | function arrayify(rows: any[]): Row[] { | 
					
						
							|  |  |  |   return rows.map(row => { | 
					
						
							|  |  |  |     var length = Object.keys(row).length; | 
					
						
							|  |  |  |     for(; length > 0; --length) if(row[length-1] != null) break; | 
					
						
							|  |  |  |     return Array.from({length, ...row}); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | // highlight-end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function muidg_to_ws(rows: Row[]): WorkSheet { | 
					
						
							|  |  |  |   return utils.aoa_to_sheet(arrayify(rows)); | 
					
						
							| 
									
										
										
										
											2022-08-17 07:10:01 +00:00
										 |  |  | } | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-03 10:02:45 +00:00
										 |  |  | <!-- spellchecker-disable --> | 
					
						
							| 
									
										
										
										
											2022-08-17 07:10:01 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-03 10:02:45 +00:00
										 |  |  | #### MUIDG Demo
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | <!-- spellchecker-enable --> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | <details><summary><b>Complete Example</b> (click to show)</summary> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 0) [Follow the React Data Grid demo](#rdg-demo) and generate the sample app. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 1) Install dependencies: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ```bash | 
					
						
							|  |  |  | npm i -S https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz @mui/x-data-grid @emotion/react @emotion/styled | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 2) Download [`App.tsx`](pathname:///muidg/App.tsx) and replace `src/App.tsx`. | 
					
						
							| 
									
										
										
										
											2022-08-17 07:10:01 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-03 10:02:45 +00:00
										 |  |  | 3) run `npm start`.  When you load the page in the browser, it will attempt to | 
					
						
							|  |  |  |    fetch <https://sheetjs.com/pres.numbers> and load the data. | 
					
						
							| 
									
										
										
										
											2022-08-18 08:41:34 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | </details> | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-25 08:22:28 +00:00
										 |  |  | <!-- spellchecker-disable --> | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-18 08:41:34 +00:00
										 |  |  | ### vue3-table-lite
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-25 08:22:28 +00:00
										 |  |  | <!-- spellchecker-enable --> | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-18 08:41:34 +00:00
										 |  |  | :::note | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | This demo was tested against `vue3-table-lite 1.2.4`, VueJS `3.2.37`, ViteJS | 
					
						
							|  |  |  | 3.0.7, and `@vitejs/plugin-vue` 3.0.3 on 2022 August 18 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ::: | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-08 05:43:21 +00:00
										 |  |  | #### VueJS Demo
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | <details><summary><b>Complete Example</b> (click to show)</summary> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 1) Create a new ViteJS App using the VueJS + TypeScript template: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ```bash | 
					
						
							|  |  |  | npm create vite@latest sheetjs-vue -- --template vue-ts | 
					
						
							|  |  |  | cd sheetjs-vue | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 2) Install dependencies: | 
					
						
							| 
									
										
										
										
											2022-08-18 08:41:34 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-08 05:43:21 +00:00
										 |  |  | ```bash | 
					
						
							|  |  |  | npm i | 
					
						
							|  |  |  | npm i -S https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz vue3-table-lite | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 3) Download [`src/App.vue`](pathname:///vtl/App.vue) and replace the contents: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ```bash | 
					
						
							| 
									
										
										
										
											2023-01-10 00:31:37 +00:00
										 |  |  | curl -L -o src/App.vue https://docs.sheetjs.com/vtl/App.vue | 
					
						
							| 
									
										
										
										
											2022-11-08 05:43:21 +00:00
										 |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 4) run `npm run dev`.  When you load the page in the browser, it will try to | 
					
						
							|  |  |  |    fetch <https://sheetjs.com/pres.numbers> and load the data. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | </details> | 
					
						
							| 
									
										
										
										
											2022-08-18 08:41:34 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | #### Rows and Columns Bindings
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-25 08:22:28 +00:00
										 |  |  | `vue3-table-lite` presents two attribute bindings: an array of column metadata | 
					
						
							| 
									
										
										
										
											2022-08-18 08:41:34 +00:00
										 |  |  | (`columns`) and an array of objects representing the displayed data (`rows`). | 
					
						
							|  |  |  | Typically both are `ref` objects: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ```html | 
					
						
							|  |  |  | <script setup lang="ts"> | 
					
						
							|  |  |  | import { ref } from "vue"; | 
					
						
							|  |  |  | import VueTableLite from "vue3-table-lite/ts"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* rows */ | 
					
						
							|  |  |  | type Row = any[]; | 
					
						
							|  |  |  | const rows = ref<Row[]>([]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* columns */ | 
					
						
							|  |  |  | type Column = { field: string; label: string; }; | 
					
						
							|  |  |  | const columns = ref<Column[]>([]); | 
					
						
							|  |  |  | </script> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | <template> | 
					
						
							|  |  |  |   <vue-table-lite :columns="columns" :rows="rows"></vue-table-lite> | 
					
						
							|  |  |  | </template> | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-25 08:22:28 +00:00
										 |  |  | These can be mutated through the `value` property in VueJS lifecycle methods: | 
					
						
							| 
									
										
										
										
											2022-08-18 08:41:34 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | ```ts | 
					
						
							|  |  |  | import { onMounted } from "vue"; | 
					
						
							|  |  |  | onMounted(() => { | 
					
						
							|  |  |  |   columns.value = [ { field: "name", label: "Names" }]; | 
					
						
							|  |  |  |   rows.value = [ { name: "SheetJS" }, { name: "VueJS" } ]; | 
					
						
							|  |  |  | }) | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | The most generic data representation is an array of arrays. To sate the grid, | 
					
						
							| 
									
										
										
										
											2022-08-25 08:22:28 +00:00
										 |  |  | columns must be objects whose `field` property is the index converted to string: | 
					
						
							| 
									
										
										
										
											2022-08-18 08:41:34 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | ```js | 
					
						
							|  |  |  | import { ref } from "vue"; | 
					
						
							|  |  |  | import { utils } from 'xlsx'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* generate row and column data */ | 
					
						
							|  |  |  | function ws_to_vte(ws) { | 
					
						
							|  |  |  |   /* create an array of arrays */ | 
					
						
							|  |  |  |   const rows = utils.sheet_to_json(ws, { header: 1 }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /* create column array */ | 
					
						
							|  |  |  |   const range = utils.decode_range(ws["!ref"]||"A1"); | 
					
						
							|  |  |  |   const columns = Array.from({ length: range.e.c + 1 }, (_, i) => ({ | 
					
						
							|  |  |  |     field: String(i), // VTE will access row["0"], row["1"], etc | 
					
						
							|  |  |  |     label: utils.encode_col(i), // the column labels will be A, B, etc | 
					
						
							|  |  |  |   })); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return { rows, columns }; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const rows = ref([]); | 
					
						
							|  |  |  | const columns = ref([]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* update refs */ | 
					
						
							|  |  |  | function update_refs(ws) { | 
					
						
							|  |  |  |   const data = ws_to_vte(ws); | 
					
						
							|  |  |  |   rows.value = data.rows; | 
					
						
							|  |  |  |   columns.value = data.columns; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | In the other direction, a worksheet can be generated with `aoa_to_sheet`: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ```js | 
					
						
							|  |  |  | import { utils } from 'xlsx'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const rows = ref([]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function vte_to_ws(rows) { | 
					
						
							|  |  |  |   return utils.aoa_to_sheet(rows.value); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-03 10:02:45 +00:00
										 |  |  | ## Standard HTML Tables
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Many UI components present styled HTML tables.  Data can be extracted from the | 
					
						
							|  |  |  | tables given a reference to the underlying TABLE element: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ```js | 
					
						
							|  |  |  | function export_html_table(table) { | 
					
						
							|  |  |  |   const wb = XLSX.utils.table_to_book(table); | 
					
						
							|  |  |  |   XLSX.writeFile(wb, "HTMLTable.xlsx"); | 
					
						
							|  |  |  | } // yes, it's that easy! | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | :::info | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | SheetJS CE is focused on data preservation and will extract values from tables. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | [SheetJS Pro](https://sheetjs.com/pro) offers styling support when reading from | 
					
						
							|  |  |  | TABLE elements and when writing to XLSX and other spreadsheet formats. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ::: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ### Fixed Tables
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | When the page has a raw HTML table, the easiest solution is to attach an `id`: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ```html | 
					
						
							|  |  |  | <table id="xport"><tr><td>SheetJS</td></tr></table> | 
					
						
							|  |  |  | <script src="https://cdn.sheetjs.com/xlsx-latest/package/dist/shim.min.js"></script> | 
					
						
							|  |  |  | <script src="https://cdn.sheetjs.com/xlsx-latest/package/dist/xlsx.full.min.js"></script> | 
					
						
							|  |  |  | <script> | 
					
						
							|  |  |  | /* as long as this script appears after the table, it will be visible */ | 
					
						
							|  |  |  | var tbl = document.getElementById("xport"); | 
					
						
							|  |  |  | const wb = XLSX.utils.table_to_book(tbl); | 
					
						
							|  |  |  | XLSX.writeFile(wb, "HTMLTable.xlsx"); | 
					
						
							|  |  |  | </script> | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | When programmatically constructing the table in the browser, retain a reference: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ```js | 
					
						
							|  |  |  | var tbl = document.createElement("TABLE"); | 
					
						
							|  |  |  | tbl.insertRow(0).insertCell(0).innerHTML = "SheetJS"; | 
					
						
							|  |  |  | document.body.appendChild(tbl); | 
					
						
							|  |  |  | const wb = XLSX.utils.table_to_book(tbl); | 
					
						
							|  |  |  | XLSX.writeFile(wb, "HTMLFlicker.xlsx"); | 
					
						
							|  |  |  | document.body.removeChild(tbl); | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ### React
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | The typical solution is to attach a Ref to the table element.  The `current` | 
					
						
							|  |  |  | property will be a live reference which plays nice with `table_to_book`: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ```jsx | 
					
						
							|  |  |  | // highlight-next-line | 
					
						
							|  |  |  | import { useRef } from "react"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export default function ReactTable() { | 
					
						
							|  |  |  | // highlight-next-line | 
					
						
							|  |  |  |   const tbl = useRef(null); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return ( <> | 
					
						
							|  |  |  |     <button onClick={() => { | 
					
						
							|  |  |  |       // highlight-next-line | 
					
						
							|  |  |  |       const wb = XLSX.utils.table_to_book(tbl.current); | 
					
						
							|  |  |  |       XLSX.writeFile(wb, "ReactTable.xlsx"); | 
					
						
							|  |  |  |     }}>Export</button> | 
					
						
							|  |  |  |     // highlight-next-line | 
					
						
							|  |  |  |     <table ref={tbl}> | 
					
						
							|  |  |  |     {/* ... TR and TD/TH elements ... */} | 
					
						
							|  |  |  |     </table> | 
					
						
							| 
									
										
										
										
											2023-02-28 11:40:44 +00:00
										 |  |  |   </> ); | 
					
						
							| 
									
										
										
										
											2022-09-03 10:02:45 +00:00
										 |  |  | } | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ### Material UI Table
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | The `Table` component abstracts the `<table>` element in HTML. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ```tsx | 
					
						
							|  |  |  | import TableContainer from '@mui/material/TableContainer'; | 
					
						
							|  |  |  | import Table from '@mui/material/Table'; | 
					
						
							|  |  |  | // ... | 
					
						
							|  |  |  | // highlight-next-line | 
					
						
							|  |  |  | import { useRef } from "react"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // ... | 
					
						
							|  |  |  | export default function BasicTable() { | 
					
						
							|  |  |  | // highlight-next-line | 
					
						
							|  |  |  |   const tbl = useRef<HTMLTableElement>(null); | 
					
						
							| 
									
										
										
										
											2023-02-28 11:40:44 +00:00
										 |  |  |   return ( <> | 
					
						
							| 
									
										
										
										
											2022-09-03 10:02:45 +00:00
										 |  |  |     <button onClick={() => { | 
					
						
							|  |  |  |       const wb = utils.table_to_book(tbl.current); | 
					
						
							|  |  |  |       writeFileXLSX(wb, "SheetJSMaterialUI.xlsx"); | 
					
						
							|  |  |  |     }}>Export</button> | 
					
						
							|  |  |  |     <TableContainer {...}> | 
					
						
							|  |  |  | // highlight-next-line | 
					
						
							|  |  |  |       <Table {...} ref={tbl}> | 
					
						
							|  |  |  |       {/* ... material ui table machinations ... */} | 
					
						
							|  |  |  |       </Table> | 
					
						
							|  |  |  |     </TableContainer> | 
					
						
							|  |  |  |   <>); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | <details><summary><b>Complete Example</b> (click to show)</summary> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 1) Create a new TypeScript `create-react-app` app: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ```bash | 
					
						
							|  |  |  | npx create-react-app sheetjs-mui --template typescript | 
					
						
							|  |  |  | cd sheetjs-mui | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 2) Install dependencies: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ```bash | 
					
						
							|  |  |  | npm i -S https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz @mui/material | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 3) Replace `src/App.tsx` with the following code. This is based on the official | 
					
						
							|  |  |  | Material UI Table example.  Differences are highlighted. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ```tsx title="src/App.tsx" | 
					
						
							|  |  |  | // highlight-start | 
					
						
							|  |  |  | import React, { useEffect, useState, useRef, ChangeEvent } from "react"; | 
					
						
							|  |  |  | import { utils, writeFileXLSX } from 'xlsx'; | 
					
						
							|  |  |  | // highlight-end | 
					
						
							|  |  |  | import Table from '@mui/material/Table'; | 
					
						
							|  |  |  | import TableBody from '@mui/material/TableBody'; | 
					
						
							|  |  |  | import TableCell from '@mui/material/TableCell'; | 
					
						
							|  |  |  | import TableContainer from '@mui/material/TableContainer'; | 
					
						
							|  |  |  | import TableHead from '@mui/material/TableHead'; | 
					
						
							|  |  |  | import TableRow from '@mui/material/TableRow'; | 
					
						
							|  |  |  | import Paper from '@mui/material/Paper'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function createData( | 
					
						
							|  |  |  |   name: string, | 
					
						
							|  |  |  |   calories: number, | 
					
						
							|  |  |  |   fat: number, | 
					
						
							|  |  |  |   carbs: number, | 
					
						
							|  |  |  |   protein: number, | 
					
						
							|  |  |  | ) { | 
					
						
							|  |  |  |   return { name, calories, fat, carbs, protein }; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const rows = [ | 
					
						
							|  |  |  |   createData('Frozen yoghurt', 159, 6.0, 24, 4.0), | 
					
						
							|  |  |  |   createData('Ice cream sandwich', 237, 9.0, 37, 4.3), | 
					
						
							|  |  |  |   createData('Eclair', 262, 16.0, 24, 6.0), | 
					
						
							|  |  |  |   createData('Cupcake', 305, 3.7, 67, 4.3), | 
					
						
							|  |  |  |   createData('Gingerbread', 356, 16.0, 49, 3.9), | 
					
						
							|  |  |  | ]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export default function BasicTable() { | 
					
						
							|  |  |  |   // highlight-start | 
					
						
							|  |  |  |   const tbl = useRef<HTMLTableElement>(null); | 
					
						
							|  |  |  |   return ( <> | 
					
						
							|  |  |  |     <button onClick={() => { | 
					
						
							|  |  |  |       const wb = utils.table_to_book(tbl.current); | 
					
						
							|  |  |  |       writeFileXLSX(wb, "SheetJSMaterialUI.xlsx"); | 
					
						
							|  |  |  |     }}>Export</button> | 
					
						
							|  |  |  |     // highlight-end | 
					
						
							|  |  |  |     <TableContainer component={Paper}> | 
					
						
							|  |  |  |       // highlight-next-line | 
					
						
							|  |  |  |       <Table sx={{ minWidth: 650 }} aria-label="simple table" ref={tbl}> | 
					
						
							|  |  |  |         <TableHead> | 
					
						
							|  |  |  |           <TableRow> | 
					
						
							|  |  |  |             <TableCell>Dessert (100g serving)</TableCell> | 
					
						
							|  |  |  |             <TableCell align="right">Calories</TableCell> | 
					
						
							|  |  |  |             <TableCell align="right">Fat (g)</TableCell> | 
					
						
							|  |  |  |             <TableCell align="right">Carbs (g)</TableCell> | 
					
						
							|  |  |  |             <TableCell align="right">Protein (g)</TableCell> | 
					
						
							|  |  |  |           </TableRow> | 
					
						
							|  |  |  |         </TableHead> | 
					
						
							|  |  |  |         <TableBody> | 
					
						
							|  |  |  |           {rows.map((row) => ( | 
					
						
							|  |  |  |             <TableRow | 
					
						
							|  |  |  |               key={row.name} | 
					
						
							|  |  |  |               sx={{ '&:last-child td, &:last-child th': { border: 0 } }} | 
					
						
							|  |  |  |             > | 
					
						
							|  |  |  |               <TableCell component="th" scope="row"> | 
					
						
							|  |  |  |                 {row.name} | 
					
						
							|  |  |  |               </TableCell> | 
					
						
							|  |  |  |               <TableCell align="right">{row.calories}</TableCell> | 
					
						
							|  |  |  |               <TableCell align="right">{row.fat}</TableCell> | 
					
						
							|  |  |  |               <TableCell align="right">{row.carbs}</TableCell> | 
					
						
							|  |  |  |               <TableCell align="right">{row.protein}</TableCell> | 
					
						
							|  |  |  |             </TableRow> | 
					
						
							|  |  |  |           ))} | 
					
						
							|  |  |  |         </TableBody> | 
					
						
							|  |  |  |       </Table> | 
					
						
							|  |  |  |     </TableContainer> | 
					
						
							|  |  |  |     // highlight-next-line | 
					
						
							|  |  |  |   </> ); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 4) run `npm start`.  Click the "Export" button and inspect the generated file. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | </details> |