From 059a1a3783a3411d4c3b8cbba75a5b40bbba9275 Mon Sep 17 00:00:00 2001 From: SheetJS Date: Wed, 19 Apr 2023 04:50:07 -0400 Subject: [PATCH] knex --- docz/docs/03-demos/01-frontend/01-react.md | 2 +- docz/docs/03-demos/02-grid/11-rdg.md | 127 +++++++++++++++ docz/docs/03-demos/02-grid/index.md | 126 +-------------- docz/docs/03-demos/04-static/01-lume.md | 14 +- docz/docs/03-demos/07-data/10-sql.md | 61 +------- docz/docs/03-demos/07-data/14-knex.md | 170 +++++++++++++++++++++ docz/docs/03-demos/09-cloud/09-azure.md | 2 +- docz/docs/03-demos/10-extensions/05-osa.md | 4 +- docz/docs/03-demos/index.md | 9 +- docz/docs/06-solutions/05-output.md | 4 +- docz/static/rdg/App.tsx | 1 + 11 files changed, 326 insertions(+), 194 deletions(-) create mode 100644 docz/docs/03-demos/02-grid/11-rdg.md create mode 100644 docz/docs/03-demos/07-data/14-knex.md diff --git a/docz/docs/03-demos/01-frontend/01-react.md b/docz/docs/03-demos/01-frontend/01-react.md index cbb9a0f..2f89ab3 100644 --- a/docz/docs/03-demos/01-frontend/01-react.md +++ b/docz/docs/03-demos/01-frontend/01-react.md @@ -15,7 +15,7 @@ Other demos cover general React deployments, including: - [Static Site Generation powered by NextJS](/docs/demos/static/nextjs) - [iOS and Android applications powered by React Native](/docs/demos/mobile/reactnative) - [Desktop application powered by React Native Windows + macOS](/docs/demos/desktop/reactnative) -- [React Data Grid UI component](/docs/demos/grid#react-data-grid) +- [React Data Grid UI component](/docs/demos/grid/rdg) - [Glide Data Grid UI component](/docs/demos/grid/gdg) diff --git a/docz/docs/03-demos/02-grid/11-rdg.md b/docz/docs/03-demos/02-grid/11-rdg.md new file mode 100644 index 0000000..b237260 --- /dev/null +++ b/docz/docs/03-demos/02-grid/11-rdg.md @@ -0,0 +1,127 @@ +--- +title: React Datagrid +pagination_prev: demos/frontend/index +pagination_next: demos/net/index +--- + +:::note + +This demo was last tested on 2023 April 18 with `react-data-grid 7.0.0-beta.28`, +`create-react-app` 5.0.1 and React 18.2.0. + +::: + + +The demo creates a site that looks like the screenshot below: + +![react-data-grid screenshot](pathname:///rdg/rdg1.png) + +## Integration Details + +#### 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 ( ); +} +``` + +The most generic data representation is an array of arrays. To sate the grid, +columns must be objects whose `key` property is the index converted to string: + +```ts +import { WorkSheet, utils } from 'xlsx'; +import { textEditor, Column } from "react-data-grid"; + +type Row = any[]; +type AOAColumn = Column; +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[]; + +function rdg_to_ws(rows: Row[]): WorkSheet { + return utils.aoa_to_sheet(rows); +} +``` + +:::caution + +When the demo was last refreshed, row array objects were preserved. This was +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)); +} +``` + +::: + +## Demo + +1) Create a new TypeScript `create-react-app` app: + +```bash +npx create-react-app sheetjs-rdg --template typescript +cd sheetjs-rdg +``` + +2) Install dependencies: + +```bash +npm i -S https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz react-data-grid +``` + +3) Download [`App.tsx`](pathname:///rdg/App.tsx) and replace `src/App.tsx`. + +```bash +curl -L -o src/App.tsx https://docs.sheetjs.com/rdg/App.tsx +``` + +4) run `npm start`. When you load the page in the browser, it will attempt to + fetch and load the data. diff --git a/docz/docs/03-demos/02-grid/index.md b/docz/docs/03-demos/02-grid/index.md index 329cce6..124ab4c 100644 --- a/docz/docs/03-demos/02-grid/index.md +++ b/docz/docs/03-demos/02-grid/index.md @@ -91,129 +91,7 @@ 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. - -::: - - - -#### RDG Demo - - - -
Complete Example (click to show) - -1) Create a new TypeScript `create-react-app` app: - -```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) 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 and load the data. - -The following screenshot was taken from the demo: - -![react-data-grid screenshot](pathname:///rdg/rdg1.png) - -
- -#### 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 ( ); -} -``` - -The most generic data representation is an array of arrays. To sate the grid, -columns must be objects whose `key` property is the index converted to string: - -```ts -import { WorkSheet, utils } from 'xlsx'; -import { textEditor, Column } from "react-data-grid"; - -type Row = any[]; -type AOAColumn = Column; -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[]; - -function rdg_to_ws(rows: Row[]): WorkSheet { - return utils.aoa_to_sheet(rows); -} -``` - -:::caution - -When the demo was last refreshed, row array objects were preserved. This was -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)); -} -``` - -::: - +**[The exposition has been moved to a separate page.](/docs/demos/grid/rdg)** ### Glide Data Grid @@ -222,7 +100,7 @@ function rdg_to_ws(rows: Row[]): WorkSheet { ### 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. +Differences from ["React Data Grid"](/docs/demos/grid/rdg) will be highlighted. [A complete example is included below.](#muidg-demo) diff --git a/docz/docs/03-demos/04-static/01-lume.md b/docz/docs/03-demos/04-static/01-lume.md index de96587..5ff7837 100644 --- a/docz/docs/03-demos/04-static/01-lume.md +++ b/docz/docs/03-demos/04-static/01-lume.md @@ -103,7 +103,7 @@ from the `"Presidents"` sheet: :::note -This was tested against `lume v1.15.3` on 2023 March 14. +This was tested against `lume v1.16.2` on 2023 April 18. This example uses the Nunjucks template format. Lume plugins support additional template formats, including Markdown and JSX. @@ -156,8 +156,16 @@ curl -L -o _data/pres.numbers https://sheetjs.com/pres.numbers deno task lume --serve ``` -To verify it works, access http://localhost:3000 from your web browser. -Adding a new row and saving `pres.numbers` should refresh the data +To verify it works, access http://localhost:3000 from your web browser. Open +`_data/pres.numbers` and add a new row to the bottom of the sheet. The page will +refresh and show the new contents. + +:::caution + +There is a known bug with Deno hot reloading. If the page does not refresh +automatically, upgrade with `deno upgrade` and restart the development server. + +::: 5) Stop the server (press `CTRL+C` in the terminal window) and run diff --git a/docz/docs/03-demos/07-data/10-sql.md b/docz/docs/03-demos/07-data/10-sql.md index 3200c23..69bcaef 100644 --- a/docz/docs/03-demos/07-data/10-sql.md +++ b/docz/docs/03-demos/07-data/10-sql.md @@ -143,66 +143,7 @@ types and other database minutiae. **Knex** -The result of a `SELECT` statement is an array of objects: - -```js -const aoo = await connection.select("*").from("DataTable"); -const worksheet = XLSX.utils.json_to_sheet(aoa); -``` - -Knex wraps primitive types when creating a table. `generate_sql` takes a `knex` -connection object and uses the API: - -
Generating a Table (click to show) - -```js -// define mapping between determined types and Knex types -const PG = { "n": "float", "s": "text", "b": "boolean" }; - -async function generate_sql(knex, ws, wsname) { - - // generate an array of objects from the data - const aoo = XLSX.utils.sheet_to_json(ws); - - // types will map column headers to types, while hdr holds headers in order - const types = {}, hdr = []; - - // loop across each row object - aoo.forEach(row => - // Object.entries returns a row of [key, value] pairs. Loop across those - Object.entries(row).forEach(([k,v]) => { - - // If this is first time seeing key, mark unknown and append header array - if(!types[k]) { types[k] = "?"; hdr.push(k); } - - // skip null and undefined - if(v == null) return; - - // check and resolve type - switch(typeof v) { - case "string": // strings are the broadest type - types[k] = "s"; break; - case "number": // if column is not string, number is the broadest type - if(types[k] != "s") types[k] = "n"; break; - case "boolean": // only mark boolean if column is unknown or boolean - if("?b".includes(types[k])) types[k] = "b"; break; - default: types[k] = "s"; break; // default to string type - } - }) - ); - - await knex.schema.dropTableIfExists(wsname); - await knex.schema.createTable(wsname, (table) => { hdr.forEach(h => { table[PG[types[h]] || "text"](h); }); }); - for(let i = 0; i < aoo.length; ++i) { - if(!aoo[i] || !Object.keys(aoo[i]).length) continue; - try { await knex.insert(aoo[i]).into(wsname); } catch(e) {} - } - return knex; -} -``` - -
- +**[The exposition has been moved to a separate page.](/docs/demos/data/knex)** ### Other SQL Databases diff --git a/docz/docs/03-demos/07-data/14-knex.md b/docz/docs/03-demos/07-data/14-knex.md new file mode 100644 index 0000000..3a7cd60 --- /dev/null +++ b/docz/docs/03-demos/07-data/14-knex.md @@ -0,0 +1,170 @@ +--- +title: Knex SQL Builder +pagination_prev: demos/desktop/index +pagination_next: demos/local/index +sidebar_custom_props: + sql: true +--- + +:::note + +This demo was last tested on 2023 April 19 with Knex 2.4.2 and `better-sqlite`. + +::: + +## Integration Details + +#### Importing Data + +`sheet_to_json` generates an array of objects. An `INSERT` statement can be +generated from each row object using `knex.insert`: + +```js +const aoo = XLSX.utils.sheet_to_json(ws); +for(let i = 0; i < aoo.length; ++i) await knex.insert(aoo[i]).into(table_name); +``` + +#### Exporting Data + +The result of a `SELECT` statement is an array of objects: + +```js +const aoo = await knex.select("*").from(table_name); +const worksheet = XLSX.utils.json_to_sheet(aoo); +``` + +## Complete Example + +1) Install dependencies: + +```bash +npm i --save https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz knex better-sqlite3 +``` + +2) Download the [test file](https://sheetjs.com/pres.numbers) + +```bash +curl -LO https://sheetjs.com/pres.numbers +``` + +3) Save the following utility script to `sheetjsknex.js`: + +```js title="sheetjsknex.js" +const XLSX = require("xlsx"); + +// define mapping between determined types and Knex types +const PG = { "n": "float", "s": "text", "b": "boolean" }; + +async function ws_to_knex(knex, ws, table_name) { + + // generate an array of objects from the data + const aoo = XLSX.utils.sheet_to_json(ws); + + // types will map column headers to types, while hdr holds headers in order + const types = {}, hdr = []; + + // loop across each row object + aoo.forEach(row => + // Object.entries returns a row of [key, value] pairs. Loop across those + Object.entries(row).forEach(([k,v]) => { + + // If this is first time seeing key, mark unknown and append header array + if(!types[k]) { types[k] = "?"; hdr.push(k); } + + // skip null and undefined + if(v == null) return; + + // check and resolve type + switch(typeof v) { + case "string": // strings are the broadest type + types[k] = "s"; break; + case "number": // if column is not string, number is the broadest type + if(types[k] != "s") types[k] = "n"; break; + case "boolean": // only mark boolean if column is unknown or boolean + if("?b".includes(types[k])) types[k] = "b"; break; + default: types[k] = "s"; break; // default to string type + } + }) + ); + + await knex.schema.dropTableIfExists(table_name); + + // use column type info to create table + await knex.schema.createTable(table_name, (table) => { hdr.forEach(h => { table[PG[types[h]] || "text"](h); }); }); + + // insert each non-empty row object + for(let i = 0; i < aoo.length; ++i) { + if(!aoo[i] || !Object.keys(aoo[i]).length) continue; + try { await knex.insert(aoo[i]).into(table_name); } catch(e) {} + } + return knex; +} + +async function knex_to_ws(knex, table_name) { + const aoo = await knex.select("*").from(table_name); + return XLSX.utils.json_to_sheet(aoo); +} + +module.exports = { ws_to_knex, knex_to_ws }; +``` + +4) Save the following to `SheetJSKnexTest.js`: + +```js title="SheetJSKnexTest.js" +const { ws_to_knex, knex_to_ws } = require("./sheetjsknex"); +const Knex = require('knex'); + +/* read file and get first worksheet */ +const XLSX = require("xlsx"); +const oldwb = XLSX.readFile("pres.numbers"); +const wsname = oldwb.SheetNames[0]; +const oldws = oldwb.Sheets[wsname]; + +(async() => { + /* open connection to SheetJSKnex.db */ + let knex = Knex({ client: 'better-sqlite3', connection: { filename: "SheetJSKnex.db" } }); + try { + /* load data into database and close connection */ + await ws_to_knex(knex, oldws, "Test_Table"); + } finally { knex.destroy(); } + + /* reconnect to SheetJSKnex.db */ + knex = Knex({ client: 'better-sqlite3', connection: { filename: "SheetJSKnex.db" } }); + try { + /* get data from db */ + const aoo = await knex.select("*").from("Test_Table"); + + /* export to file */ + const newws = await knex_to_ws(knex, "Test_Table"); + const newwb = XLSX.utils.book_new(); + XLSX.utils.book_append_sheet(newwb, newws, "Export"); + XLSX.writeFile(newwb, "SheetJSKnex.xlsx"); + } finally { knex.destroy(); } +})(); +``` + +This script will read `pres.numbers` and load data into a table `Test_Table`. +The SQLite database will be saved to `SheetJSKnex.db`. + +After closing the connection, the script will make a new connection and export +data to `SheetJSKnex.xlsx`. + +5) Run the script: + +```bash +node SheetJSKnexTest.js +``` + +The script will generate two artifacts: + +`SheetJSKnex.xlsx` can be opened in a spreadsheet app or tested in the terminal: + +```bash +npx xlsx-cli SheetJSKnex.xlsx +``` + +`SheetJSKnex.db` can be verified with the `sqlite3` command line tool: + +```bash +sqlite3 SheetJSKnex.db 'select * from Test_Table' +``` diff --git a/docz/docs/03-demos/09-cloud/09-azure.md b/docz/docs/03-demos/09-cloud/09-azure.md index 17151b9..236f441 100644 --- a/docz/docs/03-demos/09-cloud/09-azure.md +++ b/docz/docs/03-demos/09-cloud/09-azure.md @@ -258,7 +258,7 @@ console.log(utils.sheet_to_csv(wb.Sheets[wb.SheetNames[0]])); ```js title="SheetJSWriteToAzure.mjs" import { BlobServiceClient } from "@azure/storage-blob"; -import { read, utils } from "xlsx"; +import { write, utils } from "xlsx"; /* replace these constants */ const connStr = ""; diff --git a/docz/docs/03-demos/10-extensions/05-osa.md b/docz/docs/03-demos/10-extensions/05-osa.md index c708e1e..00ebc48 100644 --- a/docz/docs/03-demos/10-extensions/05-osa.md +++ b/docz/docs/03-demos/10-extensions/05-osa.md @@ -15,7 +15,7 @@ available as a global. A JS stub can expose methods from AppleScript scripts. :::note -This demo was last tested on 2022 April 18 in macOS Monterey. +This demo was last tested on 2023 April 18 in macOS Monterey. ::: @@ -218,7 +218,7 @@ extractResult(res) on getContext() set UnixPath to POSIX path of ((path to me as text) & "::") - set libpath to POSIX path of (UnixPath & "xlsx.shim.js") + set libpath to POSIX path of (UnixPath & "xlsx.stub.js") set {src, err} to current application's NSString's stringWithContentsOfFile:libpath encoding:(current application's NSISOLatin1StringEncoding) |error|:(reference) set lang to current application's OSALanguage's languageForName:"JavaScript" diff --git a/docz/docs/03-demos/index.md b/docz/docs/03-demos/index.md index 4f7af16..08219ca 100644 --- a/docz/docs/03-demos/index.md +++ b/docz/docs/03-demos/index.md @@ -34,7 +34,7 @@ run in the web browser, demos will include interactive examples. - [`canvas-datagrid`](/docs/demos/grid/cdg) - [`x-spreadsheet`](/docs/demos/grid/xs) -- [`react-data-grid`](/docs/demos/grid#react-data-grid) +- [`react-data-grid`](/docs/demos/grid/rdg) - [`glide-data-grid`](/docs/demos/grid/gdg) - [`vue3-table-lite`](/docs/demos/grid#vue3-table-lite) - [`angular-ui-grid`](/docs/demos/grid#angular-ui-grid) @@ -70,6 +70,8 @@ run in the web browser, demos will include interactive examples. - [`Excel JavaScript API`](/docs/demos/extensions/excelapi) - [`ExtendScript for Adobe Apps`](/docs/demos/extensions/extendscript) - [`Chrome and Chromium Extensions`](/docs/demos/extensions/chromium) +- [`Google Sheets + Apps Script`](/docs/demos/extensions/gsheet) +- [`AppleScript and OSA`](/docs/demos/extensions/osa) ### Cloud Platforms @@ -116,10 +118,13 @@ run in the web browser, demos will include interactive examples. ### Other Programming Languages - [`JavaScript Engines`](/docs/demos/engines) -- [`Duktape (C)`](/docs/demos/engines/duktape) +- [`Duktape (C / Perl)`](/docs/demos/engines/duktape) - [`JavaScriptCore (Swift)`](/docs/demos/engines/jsc) - [`Rhino (Java)`](/docs/demos/engines/rhino) +- [`Nashorn (Java)`](/docs/demos/engines/nashorn) - [`Goja (Go)`](/docs/demos/engines/goja) +- [`ChakraCore (C++)`](/docs/demos/engines/chakra) +- [`QuickJS (C)`](/docs/demos/engines/quickjs) - [`ExecJS (Ruby)`](/docs/demos/engines/rb) - [`JavaScript::Engine (Perl)`](/docs/demos/engines/perl) diff --git a/docz/docs/06-solutions/05-output.md b/docz/docs/06-solutions/05-output.md index e0eac3d..60c1824 100644 --- a/docz/docs/06-solutions/05-output.md +++ b/docz/docs/06-solutions/05-output.md @@ -605,7 +605,7 @@ previewing and modifying structured data in the web browser. -[`react-data-grid`](/docs/demos/grid#react-data-grid) is a data grid built for +[`react-data-grid`](/docs/demos/grid/rdg) is a data grid built for React. It uses two properties: `rows` of data objects and `columns` which describe the columns. The grid API can play nice with an array of arrays. @@ -616,6 +616,8 @@ import { useEffect, useState } from "react"; import DataGrid from "react-data-grid"; import { read, utils } from "xlsx"; +import 'react-data-grid/lib/styles.css'; + const url = "https://oss.sheetjs.com/test_files/RkNumber.xls"; export default function App() { diff --git a/docz/static/rdg/App.tsx b/docz/static/rdg/App.tsx index 1c87782..2ec3fe3 100644 --- a/docz/static/rdg/App.tsx +++ b/docz/static/rdg/App.tsx @@ -2,6 +2,7 @@ import React, { useEffect, useState, ChangeEvent } from "react"; import DataGrid, { textEditor, Column } from "react-data-grid"; import { read, utils, WorkSheet, writeFile } from "xlsx"; +import 'react-data-grid/lib/styles.css'; import './App.css'; type DataSet = { [index: string]: WorkSheet; };