This commit is contained in:
SheetJS 2022-08-18 04:41:34 -04:00
parent d4a38231dd
commit 2d5f8ebce4
17 changed files with 1163 additions and 153 deletions

@ -27,6 +27,86 @@ suitable for a number of libraries. When more advanced shapes are needed,
it is easier to munge the output of an array of arrays.
### 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>
### Canvas DataGrid
After extensive testing, [`canvas-datagrid`](https://canvas-datagrid.js.org/demo.html)
@ -120,7 +200,6 @@ many additional features including massive data streaming, sorting and styling.
</details>
### Angular UI Grid
:::warning
@ -223,7 +302,7 @@ import { WorkSheet, utils } from 'xlsx';
type Row = any[];
function ws_to_rdg(rows: Row[]): WorkSheet {
function rdg_to_ws(rows: Row[]): WorkSheet {
return utils.aoa_to_sheet(rows);
}
```
@ -361,8 +440,135 @@ export default function App() {
4) run `npm start`. When you load the dev page in the browser, it will attempt
to fetch <https://sheetjs.com/pres.numbers> and load the data.
</details>
The following screenshot was taken from the demo:
![react-data-grid screenshot](pathname:///react/rdg1.png)
</details>
### vue3-table-lite
:::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
:::
[`vue3-table-lite`](https://vue3-lite-table.vercel.app/) is a data grid built
for Vue
[A complete example is included below.](#vte-demo)
#### Rows and Columns Bindings
`vue3-table-lite` presents two bindable attributes: an array of column metadata
(`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>
```
These can be mutated through the `value` property in Vue lifecycle methods:
```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,
the columns must be objects whose `field` property is the stringified number:
```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);
}
```
#### VTE Demo
<details><summary><b>Complete Example</b> (click to show)</summary>
1) Create a new ViteJS App using the Vue + TypeScript template:
```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 ..
```
4) run `npm run dev`. When you load the dev page in the browser, it will try
to fetch <https://sheetjs.com/pres.numbers> and load the data.
</details>

@ -434,7 +434,7 @@ ready, it will read the hardcoded test file and print the contents as CSV.
5) Run the script using the Hermes standalone binary:
```
```bash
hermes xlsx.hermes.js
```
@ -540,6 +540,93 @@ to `SheetJSwift.xlsx`. That file can be verified by opening in Excel / Numbers.
</details>
## JerryScript
JerryScript is a lightweight JavaScript engine designed for use in low-memory
environments like microcontrollers. As part of the build suite, the project
generates a C library and a standalone CLI tool.
The simplest way to interact with the engine is to pass Base64 strings.
:::note
While applications should link against the official libraries, the standalone tool
is useful for verifying functionality.
:::
:::caution
This demo requires a much larger heap size than is normally used in JerryScript
deployments! In local testing, the following sizes were needed:
- 8192 (8M) for <https://sheetjs.com/pres.xlsx>
- 65536 (64M) for <https://sheetjs.com/pres.numbers>
This works on a Raspberry Pi.
:::
<details><summary><b>Complete Example</b> (click to show)</summary>
Due to limitations of the standalone binary, this demo will encode a test file
as a Base64 string and directly add it to an amalgamated script.
0) Build the library and command line tool with required options:
```bash
git clone --depth=1 https://github.com/jerryscript-project/jerryscript.git
cd jerryscript
python tools/build.py --error-messages=ON --logging=ON --mem-heap=8192 --cpointer-32bit=ON
```
1) Download the standalone script, shim, and test file:
<ul>
<li><a href={`https://cdn.sheetjs.com/xlsx-latest/package/dist/xlsx.full.min.js`}>xlsx.full.min.js</a></li>
<li><a href={`https://cdn.sheetjs.com/xlsx-latest/package/dist/shim.min.js`}>shim.min.js</a></li>
<li><a href="https://sheetjs.com/pres.xlsx">pres.xlsx</a></li>
</ul>
2) Bundle the test file and create `payload.js`:
```bash
node -e "fs.writeFileSync('payload.js', 'var payload = \"' + fs.readFileSync('pres.xlsx').toString('base64') + '\";')"
```
3) Create support scripts:
- `global.js` creates a `global` variable and defines a fake `console`:
```js title="global.js"
var global = (function(){ return this; }).call(null);
var console = { log: function(x) { print(x); } };
```
- `jerry.js` will call `XLSX.read` and `XLSX.utils.sheet_to_csv`:
```js title="jerry.js"
/* sheetjs (C) 2013-present SheetJS -- http://sheetjs.com */
var wb = XLSX.read(payload, {type:'base64'});
console.log(XLSX.utils.sheet_to_csv(wb.Sheets[wb.SheetNames[0]]));
```
4) Create the amalgamation `xlsx.jerry.js`:
```bash
cat global.js xlsx.full.min.js payload.js jerry.js > xlsx.jerry.js
```
The final script defines `global` before loading the standalone library. Once
ready, it will read the hardcoded test file and print the contents as CSV.
5) Run the script using the `jerry` standalone binary:
```bash
build/bin/jerry xlsx.jerry.js; echo $?
```
</details>
## QuickJS

@ -1,5 +1,5 @@
---
sidebar_position: 20
sidebar_position: 21
title: ReactJS
---
@ -38,7 +38,7 @@ Typically, some users will create a spreadsheet with source data that should be
loaded into the site. This sheet will have known columns. For example, our
[presidents sheet](https://sheetjs.com/pres.xlsx) has "Name" / "Index" columns:
![`pres.xlsx` data](pathname:///react/pres.png)
![`pres.xlsx` data](pathname:///pres.png)
This naturally maps to an array of typed objects, as in the TS example below:

@ -0,0 +1,184 @@
---
sidebar_position: 22
title: VueJS
---
[VueJS](https://vuejs.org/) is a JS library for building user interfaces.
This demo tries to cover common Vue data flow ideas and strategies. Single-File
Components (SFC) and VueJS familiarity is assumed.
Other demos cover general VueJS deployments, including:
- [Static Site Generation powered by NuxtJS](./content#nuxtjs)
- [iOS and Android applications powered by Quasar](./mobile#quasar)
- [`vue3-table-lite` UI component](./grid#vue3-table-lite)
## Installation
[The "Frameworks" section](../getting-started/installation/frameworks) covers
installation with Yarn and other package managers.
The library can be imported directly from JS or JSX code with:
```js
import { read, utils, writeFile } from 'xlsx';
```
## Internal State
The various SheetJS APIs work with various data shapes. The preferred state
depends on the application.
### Array of Objects
Typically, some users will create a spreadsheet with source data that should be
loaded into the site. This sheet will have known columns. For example, our
[presidents sheet](https://sheetjs.com/pres.xlsx) has "Name" / "Index" columns:
![`pres.xlsx` data](pathname:///pres.png)
This naturally maps to an array of typed objects, as in the TS example below:
```ts
import { read, utils } from 'xlsx';
interface President {
Name: string;
Index: number;
}
const f = await (await fetch("https://sheetjs.com/pres.xlsx")).arrayBuffer();
const wb = read(f);
const data = utils.sheet_to_json<President>(wb.Sheets[wb.SheetNames[0]]);
console.log(data);
```
`data` will be an array of objects:
```js
[
{ Name: "Bill Clinton", Index: 42 },
{ Name: "GeorgeW Bush", Index: 43 },
{ Name: "Barack Obama", Index: 44 },
{ Name: "Donald Trump", Index: 45 },
{ Name: "Joseph Biden", Index: 46 }
]
```
A component will typically map over the data. The following example generates
a TABLE with a row for each President:
```html title="src/SheetJSVueAoO.vue"
<script setup>
import { ref, onMounted } from "vue";
import { read, utils } from 'xlsx';
const rows = ref([]);
onMounted(async() => {
/* Download from https://sheetjs.com/pres.numbers */
const f = await fetch("https://sheetjs.com/pres.numbers");
const ab = await f.arrayBuffer();
/* parse workbook */
const wb = read(ab);
/* update data */
rows.value = utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]]);
});
</script>
<template>
<table><thead><th>Name</th><th>Index</th></thead><tbody>
<tr v-for="(row, idx) in rows" :key="idx">
<td>{{ row.Name }}</td>
<td>{{ row.Index }}</td>
</tr>
</tbody></table>
</template>
```
### HTML
The main disadvantage of the Array of Objects approach is the specific nature
of the columns. For more general use, passing around an Array of Arrays works.
However, this does not handle merge cells well!
The `sheet_to_html` function generates HTML that is aware of merges and other
worksheet features. VueJS `v-html` attribute allows assignment of `innerHTML`
attribute, effectively inserting the code into the page:
```html title="src/SheetJSVueHTML.vue"
<script setup>
import { ref, onMounted } from "vue";
import { read, utils } from 'xlsx';
const html = ref("");
onMounted(async() => {
/* Download from https://sheetjs.com/pres.numbers */
const f = await fetch("https://sheetjs.com/pres.numbers");
const ab = await f.arrayBuffer();
/* parse workbook */
const wb = read(ab);
/* update data */
html.value = utils.sheet_to_html(wb.Sheets[wb.SheetNames[0]]);
});
</script>
<template>
<div v-html="html"></div>
</template>
```
### Rows and Columns
Some data grids and UI components split worksheet state in two parts: an array
of column attribute objects and an array of row objects. The former is used to
generate column headings and for indexing into the row objects.
The safest approach is to use an array of arrays for state and to generate
column objects that map to A1-style column headers.
The [Vue Table Lite demo](./grid#rows-and-columns-bindings) uses this approach
with the following column and row structure:
```js
/* rows are generated with a simple array of arrays */
rows.value = utils.sheet_to_json(worksheet, { header: 1 });
/* column objects are generated based on the worksheet range */
const range = utils.decode_range(ws["!ref"]||"A1");
columns.value = Array.from({ length: range.e.c + 1 }, (_, i) => ({
/* for an array of arrays, the keys are "0", "1", "2", ... */
field: String(i),
/* column labels: encode_col translates 0 -> "A", 1 -> "B", 2 -> "C", ... */
label: XLSX.utils.encode_col(i)
}));
```
## Legacy Deployments
[The Standalone Scripts](../getting-started/installation/standalone) play nice
with legacy deployments that do not use a bundler.
The legacy demos show a simple VueJS component. It is written in ES5 syntax.
The pages are not minified and "View Source" should be used to inspect.
- [VueJS version 2](pathname:///vue/index2.html)
- [VueJS version 3](pathname:///vue/index3.html)
There is a shared component [`SheetJS-vue.js`](pathname:///vue/SheetJS-vue.js)
:::caution
The entire demo is designed to run in Internet Explorer and does not reflect
modern design patterns.
:::

@ -18,18 +18,18 @@ The demo projects include small runnable examples and short explainers.
### Frameworks
- [`Angular.JS`](./legacy#angularjs)
- [`Angular 2+ and Ionic`](https://github.com/SheetJS/SheetJS/tree/master/demos/angular2/)
- [`Knockout`](./legacy#knockout)
- [`React`](./react)
- [`VueJS`](https://github.com/SheetJS/SheetJS/tree/master/demos/vue/)
- [`VueJS`](./vue)
- [`Angular.JS`](./legacy#angularjs)
- [`Knockout`](./legacy#knockout)
### Front-End UI Components
- [`canvas-datagrid`](./grid#canvas-datagrid)
- [`x-spreadsheet`](https://github.com/SheetJS/SheetJS/tree/master/demos/xspreadsheet/)
- [`react-data-grid`](https://github.com/SheetJS/SheetJS/tree/master/demos/react/modify/)
- [`vue3-table-light`](https://github.com/SheetJS/SheetJS/tree/master/demos/vue/modify/)
- [`x-spreadsheet`](./grid#x-spreadsheet)
- [`react-data-grid`](./grid#react-data-grid)
- [`vue3-table-lite`](./grid#vue3-table-lite)
- [`angular-ui-grid`](./grid#angular-ui-grid)
### Platforms and Integrations

@ -1,4 +1,4 @@
/* xlsx.js (C) 2013-present SheetJS -- http://sheetjs.com */
/* sheetjs (C) 2013-present SheetJS -- http://sheetjs.com */
var XLSX = require('xlsx');
/*jshint browser:true */
/*global require */

@ -1,5 +1,5 @@
<!DOCTYPE html>
<!-- xlsx.js (C) 2013-present SheetJS http://sheetjs.com -->
<!-- sheetjs (C) 2013-present SheetJS http://sheetjs.com -->
<!-- vim: set ts=2: -->
<html>
<head>

@ -1,4 +1,4 @@
/* xlsx.js (C) 2013-present SheetJS -- http://sheetjs.com */
/* sheetjs (C) 2013-present SheetJS -- http://sheetjs.com */
var XLSX = require('xlsx');
postMessage({t:"ready"});

@ -1,5 +1,5 @@
<!DOCTYPE html>
<!-- xlsx.js (C) 2013-present SheetJS http://sheetjs.com -->
<!-- sheetjs (C) 2013-present SheetJS http://sheetjs.com -->
<!-- vim: set ts=2: -->
<html>
<head>

@ -1,5 +1,5 @@
<!DOCTYPE html>
<!-- xlsx.js (C) 2013-present SheetJS http://sheetjs.com -->
<!-- sheetjs (C) 2013-present SheetJS http://sheetjs.com -->
<!-- vim: set ts=2: -->
<html ng-app="sjs">
<head>

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

@ -3,156 +3,156 @@
<!-- vim: set ts=2: -->
<html lang="en" style="height: 100%">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>SheetJS React Demo</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" />
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script src="https://unpkg.com/react/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom.production.min.js"></script>
<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>
<style>body, #app { height: 100%; }</style>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>SheetJS React Demo</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" />
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script src="https://unpkg.com/react/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom.production.min.js"></script>
<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>
<style>body, #app { height: 100%; }</style>
</head>
<body>
<div class="container-fluid"><h1><a href="http://sheetjs.com">SheetJS × React Demo</a></h1><br /></div>
<div id="app" class="container-fluid"></div>
<script type="text/babel">
/* sheetjs (C) 2013-present SheetJS -- http://sheetjs.com */
/* Notes:
- usage: `ReactDOM.render( <SheetJSApp />, document.getElementById('app') );`
- xlsx.full.min.js is loaded in the head of the HTML page
- this script should be referenced with type="text/babel"
- babel.js in-browser transpiler should be loaded before this script
*/
const { read, writeFile } = XLSX;
const { decode_range, encode_col, sheet_to_json, aoa_to_sheet, book_new, book_append_sheet } = XLSX.utils;
<div class="container-fluid"><h1><a href="http://sheetjs.com">SheetJS × React Demo</a></h1><br /></div>
<div id="app" class="container-fluid"></div>
<script type="text/babel">
/* sheetjs (C) 2013-present SheetJS -- http://sheetjs.com */
/* Notes:
- usage: `ReactDOM.render( <SheetJSApp />, document.getElementById('app') );`
- xlsx.full.min.js is loaded in the head of the HTML page
- this script should be referenced with type="text/babel"
- babel.js in-browser transpiler should be loaded before this script
*/
const { read, writeFile } = XLSX;
const { decode_range, encode_col, sheet_to_json, aoa_to_sheet, book_new, book_append_sheet } = XLSX.utils;
/* generate an array of column objects */
const make_cols = refstr => Array.from({length: decode_range(refstr).e.c + 1}, (_, i) => ({ name: encode_col(i), key: i}));
/* generate an array of column objects */
const make_cols = refstr => Array.from({length: decode_range(refstr).e.c + 1}, (_, i) => ({ name: encode_col(i), key: i}));
/* main component */
function SheetJSApp() {
const [data, setData] = React.useState([]);
const [cols, setCols] = React.useState([]);
/* main component */
function SheetJSApp() {
const [data, setData] = React.useState([]);
const [cols, setCols] = React.useState([]);
const handleFile = (file) => {
const reader = new FileReader();
reader.onload = (e) => {
/* Parse data */
const ab = e.target.result;
const wb = read(ab, { type: 'array' });
/* Get first worksheet */
const wsname = wb.SheetNames[0];
const ws = wb.Sheets[wsname];
/* Convert array of arrays */
const data = sheet_to_json(ws, { header: 1 });
/* Update state */
setData(data);
setCols(make_cols(ws['!ref']))
};
reader.readAsArrayBuffer(file);
}
const handleFile = (file) => {
const reader = new FileReader();
reader.onload = (e) => {
/* Parse data */
const ab = e.target.result;
const wb = read(ab, { type: 'array' });
/* Get first worksheet */
const wsname = wb.SheetNames[0];
const ws = wb.Sheets[wsname];
/* Convert array of arrays */
const data = sheet_to_json(ws, { header: 1 });
/* Update state */
setData(data);
setCols(make_cols(ws['!ref']))
};
reader.readAsArrayBuffer(file);
}
const exportFile = () => {
/* convert state to workbook */
const ws = aoa_to_sheet(data);
const wb = book_new();
book_append_sheet(wb, ws, "SheetJS");
/* generate XLSX file and send to client */
writeFile(wb, "sheetjs.xlsx")
};
const exportFile = () => {
/* convert state to workbook */
const ws = aoa_to_sheet(data);
const wb = book_new();
book_append_sheet(wb, ws, "SheetJS");
/* generate XLSX file and send to client */
writeFile(wb, "sheetjs.xlsx")
};
return (
<DragDropFile handleFile={handleFile}>
<div className="row"><div className="col-xs-12">
<DataInput handleFile={handleFile} />
</div></div>
<div className="row"><div className="col-xs-12">
{data.length ? <button className="btn btn-success" onClick={exportFile}>Export</button> : ""}
</div></div>
<div className="row"><div className="col-xs-12">
<OutTable data={data} cols={cols} />
</div></div>
</DragDropFile>
);
}
return (
<DragDropFile handleFile={handleFile}>
<div className="row"><div className="col-xs-12">
<DataInput handleFile={handleFile} />
</div></div>
<div className="row"><div className="col-xs-12">
{data.length ? <button className="btn btn-success" onClick={exportFile}>Export</button> : ""}
</div></div>
<div className="row"><div className="col-xs-12">
<OutTable data={data} cols={cols} />
</div></div>
</DragDropFile>
);
}
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
/*
Simple HTML5 file drag-and-drop wrapper
usage: <DragDropFile handleFile={handleFile}>...</DragDropFile>
handleFile(file:File):void;
*/
/*
Simple HTML5 file drag-and-drop wrapper
usage: <DragDropFile handleFile={handleFile}>...</DragDropFile>
handleFile(file:File):void;
*/
function DragDropFile({ handleFile, children }) {
const suppress = (e) => { e.stopPropagation(); e.preventDefault(); };
const handleDrop = (e) => {
e.stopPropagation(); e.preventDefault();
const files = e.dataTransfer.files;
if (files && files[0]) handleFile(files[0]);
};
function DragDropFile({ handleFile, children }) {
const suppress = (e) => { e.stopPropagation(); e.preventDefault(); };
const handleDrop = (e) => {
e.stopPropagation(); e.preventDefault();
const files = e.dataTransfer.files;
if (files && files[0]) handleFile(files[0]);
};
return ( <div onDrop={handleDrop} onDragEnter={suppress} onDragOver={suppress}>{children}</div> );
}
return ( <div onDrop={handleDrop} onDragEnter={suppress} onDragOver={suppress}>{children}</div> );
}
/*
Simple HTML5 file input wrapper
usage: <DataInput handleFile={callback} />
handleFile(file:File):void;
*/
/*
Simple HTML5 file input wrapper
usage: <DataInput handleFile={callback} />
handleFile(file:File):void;
*/
function DataInput({ handleFile }) {
const handleChange = (e) => {
const files = e.target.files;
if (files && files[0]) handleFile(files[0]);
};
function DataInput({ handleFile }) {
const handleChange = (e) => {
const files = e.target.files;
if (files && files[0]) handleFile(files[0]);
};
return (
<form className="form-inline">
<div className="form-group">
<label htmlFor="file">Drag or choose a spreadsheet file</label><br />
<input type="file" className="form-control" id="file" accept={SheetJSFT} onChange={handleChange} />
</div>
</form>
)
}
/* list of supported file types */
const SheetJSFT = [
"xlsx", "xlsb", "xlsm", "xls", "xml", "csv", "txt", "ods", "fods", "uos", "sylk", "dif", "dbf", "prn", "qpw", "123", "wb*", "wq*", "html", "htm"
].map(x => `.${x}`).join(",");
return (
<form className="form-inline">
<div className="form-group">
<label htmlFor="file">Drag or choose a spreadsheet file</label><br />
<input type="file" className="form-control" id="file" accept={SheetJSFT} onChange={handleChange} />
</div>
</form>
)
}
/* list of supported file types */
const SheetJSFT = [
"xlsx", "xlsb", "xlsm", "xls", "xml", "csv", "txt", "ods", "fods", "uos", "sylk", "dif", "dbf", "prn", "qpw", "123", "wb*", "wq*", "html", "htm"
].map(x => `.${x}`).join(",");
/*
Simple HTML Table
usage: <OutTable data={data} cols={cols} />
data:Array<Array<any> >;
cols:Array<{name:string, key:number|string}>;
*/
function OutTable({ data, cols }) {
return (
<div className="table-responsive">
<table className="table table-striped">
<thead>
<tr>{cols.map((c) => <th key={c.key}>{c.name}</th>)}</tr>
</thead>
<tbody>
{data.map((r, i) => <tr key={i}>
{cols.map(c => <td key={c.key}>{r[c.key]}</td>)}
</tr>)}
</tbody>
</table>
</div>
);
}
/*
Simple HTML Table
usage: <OutTable data={data} cols={cols} />
data:Array<Array<any> >;
cols:Array<{name:string, key:number|string}>;
*/
function OutTable({ data, cols }) {
return (
<div className="table-responsive">
<table className="table table-striped">
<thead>
<tr>{cols.map((c) => <th key={c.key}>{c.name}</th>)}</tr>
</thead>
<tbody>
{data.map((r, i) => <tr key={i}>
{cols.map(c => <td key={c.key}>{r[c.key]}</td>)}
</tr>)}
</tbody>
</table>
</div>
);
}
/* React 18 uses ReactDOM.createRoot; < 18 should use ReactDOM.render */
const root_elt = document.getElementById('app');
if(typeof ReactDOM.createRoot !== "undefined") {
const root = ReactDOM.createRoot(root_elt);
root.render(<SheetJSApp/>);
} else {
ReactDOM.render(<SheetJSApp />, root_elt);
}
</script>
/* React 18 uses ReactDOM.createRoot; < 18 should use ReactDOM.render */
const root_elt = document.getElementById('app');
if(typeof ReactDOM.createRoot !== "undefined") {
const root = ReactDOM.createRoot(root_elt);
root.render(<SheetJSApp/>);
} else {
ReactDOM.render(<SheetJSApp />, root_elt);
}
</script>
</body>
</html>

219
docz/static/vtl/App.vue Normal file

@ -0,0 +1,219 @@
<script setup lang="ts">
/*! sheetjs (C) SheetJS -- http://sheetjs.com */
import { ref, onMounted } from "vue";
import VueTableLite from "vue3-table-lite/ts";
import { read, utils, WorkSheet, writeFile } from "xlsx";
type DataSet = { [index: string]: WorkSheet; };
type Row = any[];
type RowCB = (row: Row) => string;
type Column = { field: string; label: string; display: RowCB; };
type RowCol = { rows: Row[]; cols: Column[]; };
const currFileName = ref<string>("");
const currSheet = ref<string>("");
const sheets = ref<string[]>([]);
const workBook = ref<DataSet>({} as DataSet);
const rows = ref<Row[]>([]);
const columns = ref<Column[]>([]);
const exportTypes: string[] = ["xlsx", "xlsb", "csv", "html"];
let cell = 0;
function resetCell() {
cell = 0;
}
const getRowsCols = ( data: DataSet, sheetName: string ): RowCol => ({
rows: utils.sheet_to_json<Row>(data[sheetName], {header:1}),
cols: Array.from({
length: utils.decode_range(data[sheetName]["!ref"]||"A1").e.c + 1
}, (_, i) => (<Column>{ field: String(i), label: utils.encode_col(i), display: makeDisplay(i) }))
});
const makeDisplay = (col: number): RowCB => (row: Row) => `<span
style="user-select: none; display: block"
onblur="endEdit(event)" ondblclick="startEdit(event)"
position="${Math.floor(cell++ / columns.value.length)}.${col}"
onkeydown="endEdit(event)">${row?.[col] ?? "&nbsp;"}</span>`;
(window as any).startEdit = function (ev: MouseEvent) {
(ev?.target as HTMLSpanElement).contentEditable = "true";
(ev?.target as HTMLSpanElement).focus();
};
(window as any).endEdit = function (ev: FocusEvent | KeyboardEvent) {
if (typeof (ev as KeyboardEvent).key == "undefined" || (ev as KeyboardEvent).key === "Enter") {
const pos = (ev.target as HTMLSpanElement)?.getAttribute("position")?.split(".");
if(!pos) return;
(ev?.target as HTMLSpanElement).contentEditable = "true";
rows.value[+pos[0]][+pos[1]] = (ev.target as HTMLSpanElement).innerText;
workBook.value[currSheet.value] = utils.json_to_sheet(rows.value, {
header: columns.value.map((col: Column) => col.field),
skipHeader: true,
});
}
};
async function importAB(ab: ArrayBuffer, name: string): Promise<void> {
const data = read(ab);
currFileName.value = name;
currSheet.value = data.SheetNames?.[0];
sheets.value = data.SheetNames;
workBook.value = data.Sheets;
selectSheet(currSheet.value);
}
async function importFile(ev: Event): Promise<void> {
const file = (ev.target as HTMLInputElement)?.files?.[0];
if(!file) return;
await importAB(await file.arrayBuffer(), file.name);
}
function exportFile(type: string): void {
const wb = utils.book_new();
sheets.value.forEach((sheet) => {
utils.book_append_sheet(wb, workBook.value[sheet], sheet);
});
writeFile(wb, `sheet.${type}`);
}
function selectSheet(sheet: string): void {
const { rows: newRows, cols: newCols } = getRowsCols(workBook.value, sheet);
resetCell();
rows.value = newRows;
columns.value = newCols;
currSheet.value = sheet;
}
/* Download from https://sheetjs.com/pres.numbers */
onMounted(async() => {
const response = await fetch("https://sheetjs.com/pres.numbers");
await importAB(await response.arrayBuffer(), "pres.numbers");
});
</script>
<template>
<header class="imp-exp">
<div class="import">
<input type="file" id="import" @change="importFile" />
<label for="import">import</label>
</div>
<span v-if="currFileName">{{ currFileName }}</span>
<div class="export" v-if="currFileName">
<span>export</span>
<ul>
<li v-for="(type, idx) in exportTypes" :key="idx" @click="exportFile(type)">
{{ `.${type}` }}
</li>
</ul>
</div>
</header>
<div class="sheets">
<span
v-for="(sheet, idx) in sheets"
:key="idx"
@click="selectSheet(sheet)"
:class="[currSheet === sheet ? 'selected' : '']"
>
{{ sheet }}
</span>
</div>
<vue-table-lite :is-static-mode="true" :page-size="50" :columns="columns" :rows="rows"></vue-table-lite>
</template>
<style>
.imp-exp {
display: flex;
justify-content: space-between;
padding: 0.5rem;
font-family: mono;
color: #212529;
}
.import {
font-size: medium;
}
.import input {
position: absolute;
opacity: 0;
cursor: pointer;
}
.import label {
background-color: white;
border: 1px solid;
padding: 0.3rem;
}
.export:hover {
border-bottom: none;
}
.export:hover ul {
display: block;
}
.export span {
padding: 0.3rem;
border: 1px solid;
cursor: pointer;
}
.export ul {
display: none;
position: absolute;
z-index: 5;
background-color: white;
list-style: none;
padding: 0.3rem;
border: 1px solid;
margin-top: 0.3rem;
border-top: none;
}
.export ul li {
padding: 0.3rem;
text-align: center;
}
.export ul li:hover {
background-color: lightgray;
cursor: pointer;
}
.sheets {
display: flex;
justify-content: center;
margin: 0.3rem;
color: #212529;
}
.sheets span {
border: 1px solid;
padding: 0.5rem;
margin: 0.3rem;
}
.sheets span:hover:not(.selected) {
background-color: lightgray;
cursor: pointer;
}
.selected {
background-color: #343a40;
color: white;
}
</style>

@ -0,0 +1,60 @@
/* sheetjs (C) 2013-present SheetJS -- http://sheetjs.com */
var SheetJSFT = [
"xlsx", "xlsb", "xlsm", "xls", "xml", "csv", "txt", "ods", "fods", "uos", "sylk", "dif", "dbf", "prn", "qpw", "123", "wb*", "wq*", "html", "htm", "numbers"
].map(function(x) { return "." + x; }).join(",");
var SJSTemplate = [
'<div>',
'<input type="file" multiple="false" id="sheetjs-input" accept="' + SheetJSFT + '" @change="onchange" />',
'<br/>',
'<button type="button" id="export-table" style="visibility:hidden" @click="onexport">Export to XLSX</button>',
'<br/>',
'<div id="out-table"></div>',
'</div>'
].join("");
var component_struct = {
template: SJSTemplate,
methods: {
onchange: function(evt) {
var files = evt.target.files;
if (!files || files.length == 0) return;
var file = files[0];
var reader = new FileReader();
reader.onload = function (e) {
// get data
var bytes = new Uint8Array(e.target.result);
/* read workbook */
var wb = XLSX.read(bytes);
/* grab first sheet */
var wsname = wb.SheetNames[0];
var ws = wb.Sheets[wsname];
/* generate HTML */
var HTML = XLSX.utils.sheet_to_html(ws);
/* update table */
document.getElementById('out-table').innerHTML = HTML;
/* show export button */
document.getElementById('export-table').style.visibility = "visible";
};
reader.readAsArrayBuffer(file);
},
onexport: function(evt) {
/* generate workbook object from table */
var wb = XLSX.utils.table_to_book(document.getElementById('out-table').getElementsByTagName("TABLE")[0]);
/* generate file and force a download*/
XLSX.writeFile(wb, "sheetjs.xlsx");
}
}
};
if(Vue.component) {
Vue.component('html-preview', component_struct);
} else {
var app = Vue.createApp({});
app.component('html-preview', component_struct);
}

@ -0,0 +1,56 @@
<!DOCTYPE html>
<!-- sheetjs (C) 2013-present SheetJS http://sheetjs.com -->
<!-- vim: set ts=2: -->
<html>
<head>
<title>SheetJS + VueJS2</title>
<!-- Vue 2 -->
<script src="https://unpkg.com/vue@2.x"></script>
<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>
<!-- SheetJS Vue components -->
<script src="SheetJS-vue.js"></script>
<style>
.grid1 {
width: 500px;
height: 400px;
};
</style>
</head>
<body>
<pre>
<b><a href="http://sheetjs.com">SheetJS + VueJS2 demo</a></b>
This demo shows a sample Vue component "html-preview" that:
- displays a file input that accepts a spreadsheet file
- draws the first worksheet of a submitted file as HTML
- presents an export button to generate XLSX files
<a href="https://obamawhitehouse.archives.gov/sites/default/files/omb/budget/fy2014/assets/receipts.xls">Sample Spreadsheet</a>
</pre>
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-36810333-1']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
<div id="app">
<html-preview></html-preview>
</div>
<script lang="javascript">
if(Vue.component) var app = new Vue({ el: '#app' });
else app.mount('#app');
</script>
</body>
</html>

@ -0,0 +1,56 @@
<!DOCTYPE html>
<!-- sheetjs (C) 2013-present SheetJS http://sheetjs.com -->
<!-- vim: set ts=2: -->
<html>
<head>
<title>SheetJS + VueJS3</title>
<!-- Vue 2 -->
<script src="https://unpkg.com/vue@3.x"></script>
<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>
<!-- SheetJS Vue components -->
<script src="SheetJS-vue.js"></script>
<style>
.grid1 {
width: 500px;
height: 400px;
};
</style>
</head>
<body>
<pre>
<b><a href="http://sheetjs.com">SheetJS + VueJS3 demo</a></b>
This demo shows a sample Vue component "html-preview" that:
- displays a file input that accepts a spreadsheet file
- draws the first worksheet of a submitted file as HTML
- presents an export button to generate XLSX files
<a href="https://obamawhitehouse.archives.gov/sites/default/files/omb/budget/fy2014/assets/receipts.xls">Sample Spreadsheet</a>
</pre>
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-36810333-1']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
<div id="app">
<html-preview></html-preview>
</div>
<script lang="javascript">
if(Vue.component) var app = new Vue({ el: '#app' });
else app.mount('#app');
</script>
</body>
</html>

@ -0,0 +1,142 @@
<!DOCTYPE html>
<!-- sheetjs (C) SheetJS http://sheetjs.com -->
<!-- vim: set ts=2: -->
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>SheetJS + x-spreadsheet Live Demo</title>
<style>
#drop{
border:2px dashed #bbb;
-moz-border-radius:5px;
-webkit-border-radius:5px;
border-radius:5px;
padding:25px;
text-align:center;
font:20pt bold,"Vollkorn";color:#bbb
}
#b64data{
width:100%;
}
a { text-decoration: none }
</style>
<!-- x-spreadsheet stylesheet -->
<link rel="stylesheet" href="https://unpkg.com/x-data-spreadsheet/dist/xspreadsheet.css"/>
</head>
<body>
<pre>
<b><a href="http://sheetjs.com">SheetJS Data Preview Live Demo</a></b>
<a href="https://github.com/myliang/x-spreadsheet">x-spreadsheet component library</a>
<a href="https://github.com/SheetJS/sheetjs">Source Code Repo</a>
<a href="https://github.com/SheetJS/sheetjs/issues">Issues? Something look weird? Click here and report an issue</a>
<div id="drop">Drop a spreadsheet file here to see sheet data</div>
<input type="file" name="xlfile" id="xlf" /> ... or click here to select a file
<textarea id="b64data">... or paste a base64-encoding here</textarea>
</pre>
<p><input type="submit" value="Export to XLSX!" id="xport" onclick="export_xlsx();"></p>
<div id="htmlout"></div>
<br />
<script src="https://unpkg.com/x-data-spreadsheet/dist/xspreadsheet.js"></script>
<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 src="https://cdn.sheetjs.com/xspreadsheet/xlsxspread.min.js"></script>
<script>
/*jshint browser:true */
/* eslint-env browser */
/* eslint no-use-before-define:0 */
/*global Uint8Array, Uint16Array, ArrayBuffer */
/*global XLSX */
var HTMLOUT = document.getElementById('htmlout');
var xspr = x_spreadsheet(HTMLOUT);
HTMLOUT.style.height = (window.innerHeight - 400) + "px";
HTMLOUT.style.width = (window.innerWidth - 50) + "px";
var process_wb = (function() {
var XPORT = document.getElementById('xport');
return function process_wb(wb) {
/* convert to x-spreadsheet form */
var data = stox(wb);
/* update x-spreadsheet */
xspr.loadData(data);
XPORT.disabled = false;
if(typeof console !== 'undefined') console.log("output", new Date());
};
})();
var do_file = (function() {
return function do_file(files) {
var f = files[0];
var reader = new FileReader();
reader.onload = function(e) {
if(typeof console !== 'undefined') console.log("onload", new Date());
var data = e.target.result;
process_wb(XLSX.read(data));
};
reader.readAsArrayBuffer(f);
};
})();
(function() {
var drop = document.getElementById('drop');
if(!drop.addEventListener) return;
function handleDrop(e) {
e.stopPropagation();
e.preventDefault();
do_file(e.dataTransfer.files);
}
function handleDragover(e) {
e.stopPropagation();
e.preventDefault();
e.dataTransfer.dropEffect = 'copy';
}
drop.addEventListener('dragenter', handleDragover, false);
drop.addEventListener('dragover', handleDragover, false);
drop.addEventListener('drop', handleDrop, false);
})();
(function() {
var xlf = document.getElementById('xlf');
if(!xlf.addEventListener) return;
function handleFile(e) { do_file(e.target.files); }
xlf.addEventListener('change', handleFile, false);
})();
(function() {
try {
fetch("https://sheetjs.com/pres.numbers")
.then(function(res) { return res.arrayBuffer(); })
.then(function(ab) { process_wb(XLSX.read(ab)); });
} catch(e) {}
})();
function export_xlsx() {
var new_wb = xtos(xspr.getData());
/* write file and trigger a download */
XLSX.writeFile(new_wb, 'sheetjs.xlsx', {});
}
</script>
<script type="text/javascript">
/* eslint no-use-before-define:0 */
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-36810333-1']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
</body>
</html>