docs.sheetjs.com/docz/docs/03-demos/04-grid.md

588 lines
16 KiB
Markdown
Raw Normal View History

2022-08-01 05:34:23 +00:00
---
title: Data Grids and UI
---
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
With a familiar UI, [`x-spreadsheet`](https://myliang.github.io/x-spreadsheet/)
is an excellent choice for developers looking for a modern editor.
[Click here for a live integration demo.](pathname:///xspreadsheet/)
<details><summary><b>Full Exposition</b> (click to show)</summary>
**Obtaining the Library**
The `x-data-spreadsheet` NodeJS packages include a minified script that can be
directly inserted as a script tag. The unpkg CDN also serves this script:
```html
<!-- x-spreadsheet stylesheet -->
<link rel="stylesheet" href="https://unpkg.com/x-data-spreadsheet/dist/xspreadsheet.css"/>
<!-- x-spreadsheet library -->
<script src="https://unpkg.com/x-data-spreadsheet/dist/xspreadsheet.js"></script>
```
**Previewing and Editing Data**
The HTML document needs a container element:
```html
<div id="gridctr"></div>
```
Grid initialization is a one-liner:
```js
var grid = x_spreadsheet(document.getElementById("gridctr"));
```
`x-spreadsheet` handles the entire edit cycle. No intervention is necessary.
**SheetJS and x-spreadsheet**
The integration library can be downloaded from the SheetJS CDN:
[Development Use](https://cdn.sheetjs.com/xspreadsheet/xlsxspread.js)
[Production Use](https://cdn.sheetjs.com/xspreadsheet/xlsxspread.min.js)
When used in a browser tag, it exposes two functions: `xtos` and `stox`.
- `stox(worksheet)` returns a data structure suitable for `grid.loadData`
- `xtos(data)` accepts the result of `grid.getData` and generates a workbook
_Reading Data_
The following snippet fetches a spreadsheet and loads the grid:
```js
(async() => {
const ab = await (await fetch("https://sheetjs.com/pres.numbers")).arrayBuffer();
grid.loadData(stox(XLSX.read(ab)));
})();
```
The same pattern can be used in file input elements and other data sources.
_Writing Data_
The following snippet exports the grid data to a file:
```js
/* build workbook from the grid data */
XLSX.writeFile(xtos(grid.getData()), "SheetJS.xlsx");
```
**Additional Features**
This demo barely scratches the surface. The underlying grid component includes
many additional features that work with [SheetJS Pro](https://sheetjs.com/pro).
</details>
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
[Tabulator](http://tabulator.info/docs/5.3/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.
The [AngularJS demo](./legacy#angularjs) covers more general strategies.
:::
[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.
:::
[`react-data-grid`](https://github.com/adazzle/react-data-grid) is a data grid
built for React. `react-data-grid` powers <https://sheet.js.org/>
[A complete example is included below.](#rdg-demo)
#### 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`:
```ts
import { WorkSheet, utils } from 'xlsx';
type Row = any[];
2022-08-18 08:41:34 +00:00
function rdg_to_ws(rows: Row[]): WorkSheet {
2022-08-17 07:10:01 +00:00
return utils.aoa_to_sheet(rows);
}
```
2022-08-25 08:22:28 +00:00
<!-- spellchecker-disable -->
2022-08-17 07:10:01 +00:00
#### RDG Demo
2022-08-25 08:22:28 +00:00
<!-- spellchecker-enable -->
2022-08-17 07:10:01 +00:00
<details><summary><b>Complete Example</b> (click to show)</summary>
2022-08-25 08:22:28 +00:00
1) Create a new TypeScript `create-react-app` app:
2022-08-17 07:10:01 +00:00
```bash
npx create-react-app sheetjs-cra --template typescript
cd sheetjs-cra
```
2) Install dependencies:
```bash
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
2022-08-25 08:22:28 +00:00
notable SheetJS integration code is highlighted below:
2022-08-17 07:10:01 +00:00
```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:&nbsp;
<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>
</>)}
</>
);
}
```
2022-08-25 08:22:28 +00:00
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.
2022-08-17 07:10:01 +00:00
The following screenshot was taken from the demo:
![react-data-grid screenshot](pathname:///react/rdg1.png)
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-08-25 08:22:28 +00:00
[`vue3-table-lite`](https://vue3-lite-table.vercel.app/) is a VueJS data grid.
2022-08-18 08:41:34 +00:00
2022-08-25 08:22:28 +00:00
[A complete example is included below.](#vuejs-demo)
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-08-25 08:22:28 +00:00
#### VueJS Demo
2022-08-18 08:41:34 +00:00
<details><summary><b>Complete Example</b> (click to show)</summary>
2022-08-25 08:22:28 +00:00
1) Create a new ViteJS App using the VueJS + TypeScript template:
2022-08-18 08:41:34 +00:00
```bash
npm create vite@latest sheetjs-vue -- --template vue-ts
cd sheetjs-vue
```
2) Install dependencies:
```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
cd src
rm -f App.vue
curl -LO https://docs.sheetjs.com/vtl/App.vue
cd ..
```
2022-08-25 08:22:28 +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.
2022-08-18 08:41:34 +00:00
</details>