forked from sheetjs/docs.sheetjs.com
		
	docusaurus
This commit is contained in:
		
							parent
							
								
									ea501889ac
								
							
						
					
					
						commit
						9ae03ac35a
					
				| @ -197,6 +197,7 @@ PPI | ||||
| PhantomJS | ||||
| Photoshop | ||||
| PostgreSQL | ||||
| PouchDB | ||||
| PowerShell | ||||
| Preact | ||||
| QuickJS | ||||
|  | ||||
							
								
								
									
										2
									
								
								Makefile
									
									
									
									
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										2
									
								
								Makefile
									
									
									
									
									
								
							| @ -8,7 +8,7 @@ build: | ||||
| 
 | ||||
| .PHONY: init | ||||
| init: | ||||
| 	cd docz; npm i; cd .. | ||||
| 	cd docz; npm i || npm i --legacy-peer-deps; cd .. | ||||
| 
 | ||||
| .PHONY: dev | ||||
| dev: | ||||
|  | ||||
| @ -303,6 +303,35 @@ function ws_to_rdg(ws: WorkSheet): RowCol { | ||||
| 
 | ||||
| In the other direction, a worksheet can be generated with `aoa_to_sheet`: | ||||
| 
 | ||||
| :::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)); | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| ```ts | ||||
| import { WorkSheet, utils } from 'xlsx'; | ||||
| 
 | ||||
| @ -334,125 +363,128 @@ cd sheetjs-cra | ||||
| npm i -S https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz react-data-grid | ||||
| ``` | ||||
| 
 | ||||
| 3) Replace the contents of `src/App.tsx` with the following code.  Note: a copy | ||||
| to clipboard button will show up if you move your mouse over the code.  The | ||||
| notable SheetJS integration code is highlighted below: | ||||
| 
 | ||||
| ```tsx title="src/App.tsx" | ||||
| import React, { useEffect, useState, ChangeEvent } from "react"; | ||||
| import DataGrid, { textEditor, Column } from "react-data-grid"; | ||||
| import { read, utils, WorkSheet, writeFile } from "xlsx"; | ||||
| 
 | ||||
| import './App.css'; | ||||
| 
 | ||||
| type DataSet = { [index: string]: WorkSheet; }; | ||||
| type Row = any[]; | ||||
| type AOAColumn = Column<Row>; | ||||
| type RowCol = { rows: Row[]; columns: AOAColumn[]; }; | ||||
| 
 | ||||
| /* this method returns `rows` and `columns` data for sheet change */ | ||||
| const getRowsCols = ( data: DataSet, sheetName: string ): RowCol => ({ | ||||
|   rows: utils.sheet_to_json<Row>(data[sheetName], {header:1}), | ||||
|   columns: Array.from({ | ||||
|     length: utils.decode_range(data[sheetName]["!ref"]||"A1").e.c + 1 | ||||
|   }, (_, i) => ({ key: String(i), name: utils.encode_col(i), editor: textEditor })) | ||||
| }); | ||||
| 
 | ||||
| export default function App() { | ||||
|   const [rows, setRows] = useState<Row[]>([]); // data rows | ||||
|   const [columns, setColumns] = useState<AOAColumn[]>([]); // columns | ||||
|   const [workBook, setWorkBook] = useState<DataSet>({} as DataSet); // workbook | ||||
|   const [sheets, setSheets] = useState<string[]>([]); // list of sheet names | ||||
|   const [current, setCurrent] = useState<string>(""); // selected sheet | ||||
| 
 | ||||
|   /* called when sheet dropdown is changed */ | ||||
|   function selectSheet(name: string) { | ||||
|     // highlight-start | ||||
|     /* update workbook cache in case the current worksheet was changed */ | ||||
|     workBook[current] = utils.aoa_to_sheet(rows); | ||||
|     // highlight-end | ||||
| 
 | ||||
|     /* get data for desired sheet and update state */ | ||||
|     const { rows: new_rows, columns: new_columns } = getRowsCols(workBook, name); | ||||
|     setRows(new_rows); | ||||
|     setColumns(new_columns); | ||||
|     setCurrent(name); | ||||
|   } | ||||
| 
 | ||||
|   /* this method handles refreshing the state with new workbook data */ | ||||
|   async function handleAB(file: ArrayBuffer): Promise<void> { | ||||
|     // highlight-start | ||||
|     /* read file data */ | ||||
|     const data = read(file); | ||||
|     // highlight-end | ||||
| 
 | ||||
|     /* update workbook state */ | ||||
|     setWorkBook(data.Sheets); | ||||
|     setSheets(data.SheetNames); | ||||
| 
 | ||||
|     /* select the first worksheet */ | ||||
|     const name = data.SheetNames[0]; | ||||
|     const { rows: new_rows, columns: new_columns } = getRowsCols(data.Sheets, name); | ||||
|     setRows(new_rows); | ||||
|     setColumns(new_columns); | ||||
|     setCurrent(name); | ||||
|   } | ||||
| 
 | ||||
|   /* called when file input element is used to select a new file */ | ||||
|   async function handleFile(ev: ChangeEvent<HTMLInputElement>): Promise<void> { | ||||
|     const file = await ev.target.files?.[0]?.arrayBuffer(); | ||||
|     if(file) await handleAB(file); | ||||
|   } | ||||
| 
 | ||||
|   /* when page is loaded, fetch and processs worksheet */ | ||||
|   useEffect(() => { (async () => { | ||||
|       const f = await fetch("https://sheetjs.com/pres.numbers"); | ||||
|       await handleAB(await f.arrayBuffer()); | ||||
|   })(); }, []); | ||||
| 
 | ||||
|   /* method is called when one of the save buttons is clicked */ | ||||
|   function saveFile(ext: string): void { | ||||
|     /* update current worksheet in case changes were made */ | ||||
|     workBook[current] = utils.aoa_to_sheet(rows); | ||||
| 
 | ||||
|     // highlight-start | ||||
|     /* construct workbook and loop through worksheets */ | ||||
|     const wb = utils.book_new(); | ||||
|     sheets.forEach((n) => { utils.book_append_sheet(wb, workBook[n], n); }); | ||||
|     // highlight-end | ||||
| 
 | ||||
|     /* generate a file and download it */ | ||||
|     writeFile(wb, "sheet." + ext); | ||||
|   } | ||||
| 
 | ||||
|   return ( | ||||
|     <> | ||||
|       <h3>SheetJS × React-Data-Grid Demo</h3> | ||||
|       <input type="file" onChange={handleFile} /> | ||||
|       {sheets.length > 0 && ( <> | ||||
|         <p>Use the dropdown to switch to a worksheet:  | ||||
|           <select onChange={async (e) => selectSheet(sheets[+(e.target.value)])}> | ||||
|             {sheets.map((sheet, idx) => (<option key={sheet} value={idx}>{sheet}</option>))} | ||||
|           </select> | ||||
|         </p> | ||||
|         <div className="flex-cont"><b>Current Sheet: {current}</b></div> | ||||
|         <DataGrid columns={columns} rows={rows} onRowsChange={setRows} /> | ||||
|         <p>Click one of the buttons to create a new file with the modified data</p> | ||||
|         <div className="flex-cont">{["xlsx", "xlsb", "xls"].map((ext) => ( | ||||
|           <button key={ext} onClick={() => saveFile(ext)}>export [.{ext}]</button> | ||||
|         ))}</div> | ||||
|       </>)} | ||||
|     </> | ||||
|   ); | ||||
| } | ||||
| ``` | ||||
| 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> | ||||
| 
 | ||||
| ### 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"; | ||||
| 
 | ||||
| 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, | ||||
| 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[]; }; | ||||
| 
 | ||||
| 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) => ({ | ||||
|     // highlight-start | ||||
|     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 | ||||
|     // highlight-end | ||||
|   })); | ||||
| 
 | ||||
|   return { rows, columns }; // these can be fed to setRows / setColumns | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| In the other direction, a worksheet can be generated with `aoa_to_sheet`: | ||||
| 
 | ||||
| :::caution | ||||
| 
 | ||||
| `x-data-grid` does not properly preserve row array objects, so the row arrays | ||||
| must be re-created.  The snippet defines a `arrayify` function. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| ```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 muidg_to_ws(rows: Row[]): WorkSheet { | ||||
|   return utils.aoa_to_sheet(arrayify(rows)); | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| <!-- spellchecker-disable --> | ||||
| 
 | ||||
| #### 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`. | ||||
| 
 | ||||
| 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. | ||||
| 
 | ||||
| </details> | ||||
| 
 | ||||
| @ -585,3 +617,205 @@ cd .. | ||||
|    fetch <https://sheetjs.com/pres.numbers> and load the data. | ||||
| 
 | ||||
| </details> | ||||
| 
 | ||||
| ## 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> | ||||
|   </>); | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| ### 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); | ||||
|   return (<> | ||||
|     <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> | ||||
|  | ||||
| @ -15,7 +15,7 @@ and TypeScript familiarity is assumed. | ||||
| Other demos cover general Angular deployments, including: | ||||
| 
 | ||||
| - [iOS and Android applications powered by NativeScript](./mobile#nativescript) | ||||
| - [iOS and Android applications powered by ionic](./mobile#ionic) | ||||
| - [iOS and Android applications powered by Ionic](./mobile#ionic) | ||||
| 
 | ||||
| :::warning | ||||
| 
 | ||||
|  | ||||
| @ -205,3 +205,104 @@ Inspect the output and compare with the data in `SheetJSRedisTest.mjs`. | ||||
| Open `SheetJSRedis.xlsx` and verify the columns have the correct data | ||||
| 
 | ||||
| </details> | ||||
| 
 | ||||
| 
 | ||||
| ### PouchDB | ||||
| 
 | ||||
| `Database#allDocs` is the standard approach for bulk data export. The generated | ||||
| row objects have an additional `_id` and `_rev` keys that should be removed. | ||||
| 
 | ||||
| Nested objects must be flattened. The ["Tutorial"](../getting-started/example) | ||||
| includes an example of constructing a simple array. | ||||
| 
 | ||||
| ```js | ||||
| function export_pouchdb_to_xlsx(db) { | ||||
|   /* fetch all rows, including the underlying data */ | ||||
|   db.allDocs({include_docs: true}, function(err, doc) { | ||||
|      | ||||
|     /* pull the individual data rows */ | ||||
|     const aoo = doc.rows.map(r => { | ||||
|       /* `rest` will include every field from `r` except for _id and _rev */ | ||||
|       const { _id, _rev, ...rest } = r; | ||||
|       return rest; | ||||
|     }); | ||||
| 
 | ||||
|     /* generate worksheet */ | ||||
|     const ws = XLSX.utils.json_to_sheet(aoo); | ||||
| 
 | ||||
|     /* generate workbook and export */ | ||||
|     const wb = XLSX.utils.book_new(); | ||||
|     XLSX.utils.book_append_sheet(wb, ws, "Sheet1"); | ||||
|     XLSX.writeFile(wb, "SheetJSPouch.xlsx"); | ||||
|   }); | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| <details><summary><b>Complete Example</b> (click to show)</summary> | ||||
| 
 | ||||
| 0) Download the "Working Version" from the Getting Started guide. | ||||
| 
 | ||||
| [ZIP](https://github.com/nickcolley/getting-started-todo/archive/master.zip) | ||||
| 
 | ||||
| The ZIP file should have `MD5` checksum `ac4da7cb0cade1be293ba222462f109c`: | ||||
| 
 | ||||
| ```bash | ||||
| curl -LO https://github.com/nickcolley/getting-started-todo/archive/master.zip | ||||
| md5sum master.zip || md5 master.zip | ||||
| ### the checksum will be printed | ||||
| ``` | ||||
| 
 | ||||
| If the download is unavailable, a mirror is available at | ||||
| <https://docs.sheetjs.com/pouchdb/master.zip> | ||||
| 
 | ||||
| 1) Unzip the `master.zip` file and enter the folder: | ||||
| 
 | ||||
| ```bash | ||||
| unzip master.zip | ||||
| cd getting-started-todo-master | ||||
| ``` | ||||
| 
 | ||||
| 2) Edit `index.html` to reference the SheetJS library and add a button: | ||||
| 
 | ||||
| ```html title="index.html" | ||||
|   <body> | ||||
| <!-- highlight-start --> | ||||
|     <script src="https://cdn.sheetjs.com/xlsx-latest/package/dist/xlsx.full.min.js"></script> | ||||
|     <button id="xport">Export!</button> | ||||
| <!-- highlight-end --> | ||||
|     <section id="todoapp"> | ||||
| ``` | ||||
| 
 | ||||
| 3) Just before the end of `app.js`, add a `click` event listener: | ||||
| 
 | ||||
| ```js title="app.js" | ||||
|   if (remoteCouch) { | ||||
|     sync(); | ||||
|   } | ||||
| 
 | ||||
|   // highlight-start | ||||
|   document.getElementById("xport").addEventListener("click", function() { | ||||
|     db.allDocs({include_docs: true}, function(err, doc) { | ||||
|       const aoo = doc.rows.map(r => { | ||||
|         const { _id, _rev, ... rest } = r.doc; | ||||
|         return rest; | ||||
|       }); | ||||
|       const ws = XLSX.utils.json_to_sheet(aoo); | ||||
|       const wb = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(wb, ws, "Sheet1"); | ||||
|       XLSX.writeFile(wb, "SheetJSPouch.xlsx"); | ||||
|     }); | ||||
|   }); | ||||
|   // highlight-end | ||||
| })(); | ||||
| ``` | ||||
| 
 | ||||
| 4) Start a local web server: | ||||
| 
 | ||||
| ```bash | ||||
| npx http-server . | ||||
| ``` | ||||
| 
 | ||||
| Access `http://localhost:8080` from your browser.  Add a few items and click | ||||
| the "Export!" button to generate a new file. | ||||
| 
 | ||||
| </details> | ||||
|  | ||||
| @ -35,7 +35,7 @@ run in the web browser, demos will include interactive examples. | ||||
| - [`react-data-grid`](./grid#react-data-grid) | ||||
| - [`vue3-table-lite`](./grid#vue3-table-lite) | ||||
| - [`angular-ui-grid`](./grid#angular-ui-grid) | ||||
| 
 | ||||
| - [`material ui`](./grid#material-ui-table) | ||||
| ### Platforms and Integrations | ||||
| 
 | ||||
| - [`Command-Line Tools`](./cli) | ||||
|  | ||||
| @ -15,11 +15,11 @@ | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "@cmfcmf/docusaurus-search-local": "0.11.0", | ||||
|     "@docusaurus/core": "2.0.1", | ||||
|     "@docusaurus/plugin-client-redirects": "2.0.1", | ||||
|     "@docusaurus/preset-classic": "2.0.1", | ||||
|     "@docusaurus/theme-common": "2.0.1", | ||||
|     "@docusaurus/theme-live-codeblock": "2.0.1", | ||||
|     "@docusaurus/core": "2.1.0", | ||||
|     "@docusaurus/plugin-client-redirects": "2.1.0", | ||||
|     "@docusaurus/preset-classic": "2.1.0", | ||||
|     "@docusaurus/theme-common": "2.1.0", | ||||
|     "@docusaurus/theme-live-codeblock": "2.1.0", | ||||
|     "@mdx-js/react": "1.6.22", | ||||
|     "clsx": "1.2.1", | ||||
|     "prism-react-renderer": "1.3.5", | ||||
| @ -28,7 +28,7 @@ | ||||
|     "xlsx": "https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@docusaurus/module-type-aliases": "2.0.1" | ||||
|     "@docusaurus/module-type-aliases": "2.1.0" | ||||
|   }, | ||||
|   "browserslist": { | ||||
|     "production": [ | ||||
|  | ||||
							
								
								
									
										111
									
								
								docz/static/muidg/App.tsx
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										111
									
								
								docz/static/muidg/App.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,111 @@ | ||||
| import React, { useEffect, useState, ChangeEvent } from "react"; | ||||
| import { DataGrid, GridColDef } from "@mui/x-data-grid"; | ||||
| import { read, utils, WorkSheet, writeFile } from "xlsx"; | ||||
| 
 | ||||
| import './App.css'; | ||||
| 
 | ||||
| type DataSet = { [index: string]: WorkSheet; }; | ||||
| type Row = any[]; | ||||
| type RowCol = { rows: Row[]; columns: GridColDef[]; }; | ||||
| 
 | ||||
| function arrayify(rows: any[]): Row[] { | ||||
|   return rows.map(row => { | ||||
|     if(Array.isArray(row)) return row; | ||||
|     var length = Object.keys(row).length; | ||||
|     for(; length > 0; --length) if(row[length-1] != null) break; | ||||
|     return Array.from({length, ...row}); | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| /* this method returns `rows` and `columns` data for sheet change */ | ||||
| const getRowsCols = ( data: DataSet, sheetName: string ): RowCol => ({ | ||||
|   rows: utils.sheet_to_json<Row>(data[sheetName], {header:1}).map((r,id) => ({...r, id})), | ||||
|   columns: Array.from({ | ||||
|     length: utils.decode_range(data[sheetName]["!ref"]||"A1").e.c + 1 | ||||
|   }, (_, i) => ({ field: String(i), headerName: utils.encode_col(i), editable: true })) | ||||
| }); | ||||
| 
 | ||||
| export default function App() { | ||||
|   const [rows, setRows] = useState<Row[]>([]); // data rows
 | ||||
|   const [columns, setColumns] = useState<GridColDef[]>([]); // columns
 | ||||
|   const [workBook, setWorkBook] = useState<DataSet>({} as DataSet); // workbook
 | ||||
|   const [sheets, setSheets] = useState<string[]>([]); // list of sheet names
 | ||||
|   const [current, setCurrent] = useState<string>(""); // selected sheet
 | ||||
| 
 | ||||
|   /* called when sheet dropdown is changed */ | ||||
|   function selectSheet(name: string) { | ||||
|     /* update workbook cache in case the current worksheet was changed */ | ||||
|     workBook[current] = utils.aoa_to_sheet(arrayify(rows)); | ||||
| 
 | ||||
|     /* get data for desired sheet and update state */ | ||||
|     const { rows: new_rows, columns: new_columns } = getRowsCols(workBook, name); | ||||
|     setRows(new_rows); | ||||
|     setColumns(new_columns); | ||||
|     setCurrent(name); | ||||
|   } | ||||
| 
 | ||||
|   /* this method handles refreshing the state with new workbook data */ | ||||
|   async function handleAB(file: ArrayBuffer): Promise<void> { | ||||
|     /* read file data */ | ||||
|     const data = read(file); | ||||
| 
 | ||||
|     /* update workbook state */ | ||||
|     setWorkBook(data.Sheets); | ||||
|     setSheets(data.SheetNames); | ||||
| 
 | ||||
|     /* select the first worksheet */ | ||||
|     const name = data.SheetNames[0]; | ||||
|     const { rows: new_rows, columns: new_columns } = getRowsCols(data.Sheets, name); | ||||
|     setRows(new_rows); | ||||
|     setColumns(new_columns); | ||||
|     setCurrent(name); | ||||
|   } | ||||
| 
 | ||||
|   /* called when file input element is used to select a new file */ | ||||
|   async function handleFile(ev: ChangeEvent<HTMLInputElement>): Promise<void> { | ||||
|     const file = await ev.target.files?.[0]?.arrayBuffer(); | ||||
|     if(file) await handleAB(file); | ||||
|   } | ||||
| 
 | ||||
|   /* when page is loaded, fetch and processs worksheet */ | ||||
|   useEffect(() => { (async () => { | ||||
|       const f = await fetch("https://sheetjs.com/pres.numbers"); | ||||
|       await handleAB(await f.arrayBuffer()); | ||||
|   })(); }, []); | ||||
| 
 | ||||
|   /* method is called when one of the save buttons is clicked */ | ||||
|   function saveFile(ext: string): void { | ||||
|     console.log(rows); | ||||
|     /* update current worksheet in case changes were made */ | ||||
|     workBook[current] = utils.aoa_to_sheet(arrayify(rows)); | ||||
| 
 | ||||
|     /* construct workbook and loop through worksheets */ | ||||
|     const wb = utils.book_new(); | ||||
|     sheets.forEach((n) => { utils.book_append_sheet(wb, workBook[n], n); }); | ||||
| 
 | ||||
|     /* generate a file and download it */ | ||||
|     writeFile(wb, "SheetJSMUIDG." + ext); | ||||
|   } | ||||
| 
 | ||||
|   return ( | ||||
|     <> | ||||
|       <h3>SheetJS × MUI Data Grid Demo</h3> | ||||
|       <input type="file" onChange={handleFile} /> | ||||
|       {sheets.length > 0 && ( <> | ||||
|         <p>Use the dropdown to switch to a worksheet:  | ||||
|           <select onChange={async (e) => selectSheet(sheets[+(e.target.value)])}> | ||||
|             {sheets.map((sheet, idx) => (<option key={sheet} value={idx}>{sheet}</option>))} | ||||
|           </select> | ||||
|         </p> | ||||
|         <div className="flex-cont"><b>Current Sheet: {current}</b></div> | ||||
|         <div style={{width:"100%", height:400}}> | ||||
|           <DataGrid columns={columns} rows={rows} experimentalFeatures={{ newEditingApi: true }} /> | ||||
|         </div> | ||||
|         <p>Click one of the buttons to create a new file with the modified data</p> | ||||
|         <div className="flex-cont">{["xlsx", "xlsb", "xls"].map((ext) => ( | ||||
|           <button key={ext} onClick={() => saveFile(ext)}>export [.{ext}]</button> | ||||
|         ))}</div> | ||||
|       </>)} | ||||
|     </> | ||||
|   ); | ||||
| } | ||||
							
								
								
									
										
											BIN
										
									
								
								docz/static/pouchdb/master.zip
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										
											BIN
										
									
								
								docz/static/pouchdb/master.zip
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										110
									
								
								docz/static/rdg/App.tsx
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										110
									
								
								docz/static/rdg/App.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,110 @@ | ||||
| import React, { useEffect, useState, ChangeEvent } from "react"; | ||||
| import DataGrid, { textEditor, Column } from "react-data-grid"; | ||||
| import { read, utils, WorkSheet, writeFile } from "xlsx"; | ||||
| 
 | ||||
| import './App.css'; | ||||
| 
 | ||||
| type DataSet = { [index: string]: WorkSheet; }; | ||||
| type Row = any[]; | ||||
| type AOAColumn = Column<Row>; | ||||
| type RowCol = { rows: Row[]; columns: AOAColumn[]; }; | ||||
| 
 | ||||
| function arrayify(rows: any[]): Row[] { | ||||
|   return rows.map(row => { | ||||
|     if(Array.isArray(row)) return row; | ||||
|     var length = Object.keys(row).length; | ||||
|     for(; length > 0; --length) if(row[length-1] != null) break; | ||||
|     return Array.from({length, ...row}); | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| /* this method returns `rows` and `columns` data for sheet change */ | ||||
| const getRowsCols = ( data: DataSet, sheetName: string ): RowCol => ({ | ||||
|   rows: utils.sheet_to_json<Row>(data[sheetName], {header:1}), | ||||
|   columns: Array.from({ | ||||
|     length: utils.decode_range(data[sheetName]["!ref"]||"A1").e.c + 1 | ||||
|   }, (_, i) => ({ key: String(i), name: utils.encode_col(i), editor: textEditor })) | ||||
| }); | ||||
| 
 | ||||
| export default function App() { | ||||
|   const [rows, setRows] = useState<Row[]>([]); // data rows
 | ||||
|   const [columns, setColumns] = useState<AOAColumn[]>([]); // columns
 | ||||
|   const [workBook, setWorkBook] = useState<DataSet>({} as DataSet); // workbook
 | ||||
|   const [sheets, setSheets] = useState<string[]>([]); // list of sheet names
 | ||||
|   const [current, setCurrent] = useState<string>(""); // selected sheet
 | ||||
| 
 | ||||
|   /* called when sheet dropdown is changed */ | ||||
|   function selectSheet(name: string) { | ||||
|     /* update workbook cache in case the current worksheet was changed */ | ||||
|     workBook[current] = utils.aoa_to_sheet(arrayify(rows)); | ||||
| 
 | ||||
|     /* get data for desired sheet and update state */ | ||||
|     const { rows: new_rows, columns: new_columns } = getRowsCols(workBook, name); | ||||
|     setRows(new_rows); | ||||
|     setColumns(new_columns); | ||||
|     setCurrent(name); | ||||
|   } | ||||
| 
 | ||||
|   /* this method handles refreshing the state with new workbook data */ | ||||
|   async function handleAB(file: ArrayBuffer): Promise<void> { | ||||
|     /* read file data */ | ||||
|     const data = read(file); | ||||
| 
 | ||||
|     /* update workbook state */ | ||||
|     setWorkBook(data.Sheets); | ||||
|     setSheets(data.SheetNames); | ||||
| 
 | ||||
|     /* select the first worksheet */ | ||||
|     const name = data.SheetNames[0]; | ||||
|     const { rows: new_rows, columns: new_columns } = getRowsCols(data.Sheets, name); | ||||
|     setRows(new_rows); | ||||
|     setColumns(new_columns); | ||||
|     setCurrent(name); | ||||
|   } | ||||
| 
 | ||||
|   /* called when file input element is used to select a new file */ | ||||
|   async function handleFile(ev: ChangeEvent<HTMLInputElement>): Promise<void> { | ||||
|     const file = await ev.target.files?.[0]?.arrayBuffer(); | ||||
|     if(file) await handleAB(file); | ||||
|   } | ||||
| 
 | ||||
|   /* when page is loaded, fetch and processs worksheet */ | ||||
|   useEffect(() => { (async () => { | ||||
|       const f = await fetch("https://sheetjs.com/pres.numbers"); | ||||
|       await handleAB(await f.arrayBuffer()); | ||||
|   })(); }, []); | ||||
| 
 | ||||
|   /* method is called when one of the save buttons is clicked */ | ||||
|   function saveFile(ext: string): void { | ||||
|     console.log(rows); | ||||
|     /* update current worksheet in case changes were made */ | ||||
|     workBook[current] = utils.aoa_to_sheet(arrayify(rows)); | ||||
| 
 | ||||
|     /* construct workbook and loop through worksheets */ | ||||
|     const wb = utils.book_new(); | ||||
|     sheets.forEach((n) => { utils.book_append_sheet(wb, workBook[n], n); }); | ||||
| 
 | ||||
|     /* generate a file and download it */ | ||||
|     writeFile(wb, "SheetJSRDG." + ext); | ||||
|   } | ||||
| 
 | ||||
|   return ( | ||||
|     <> | ||||
|       <h3>SheetJS × React-Data-Grid Demo</h3> | ||||
|       <input type="file" onChange={handleFile} /> | ||||
|       {sheets.length > 0 && ( <> | ||||
|         <p>Use the dropdown to switch to a worksheet:  | ||||
|           <select onChange={async (e) => selectSheet(sheets[+(e.target.value)])}> | ||||
|             {sheets.map((sheet, idx) => (<option key={sheet} value={idx}>{sheet}</option>))} | ||||
|           </select> | ||||
|         </p> | ||||
|         <div className="flex-cont"><b>Current Sheet: {current}</b></div> | ||||
|         <DataGrid columns={columns} rows={rows} onRowsChange={setRows} /> | ||||
|         <p>Click one of the buttons to create a new file with the modified data</p> | ||||
|         <div className="flex-cont">{["xlsx", "xlsb", "xls"].map((ext) => ( | ||||
|           <button key={ext} onClick={() => saveFile(ext)}>export [.{ext}]</button> | ||||
|         ))}</div> | ||||
|       </>)} | ||||
|     </> | ||||
|   ); | ||||
| } | ||||
| Before Width: | Height: | Size: 107 KiB After Width: | Height: | Size: 107 KiB | 
		Loading…
	
		Reference in New Issue
	
	Block a user