diff --git a/docz/docs/03-demos/06-data/01-websql.md b/docz/docs/03-demos/06-data/01-websql.md index 250e2eb..d65ee71 100644 --- a/docz/docs/03-demos/06-data/01-websql.md +++ b/docz/docs/03-demos/06-data/01-websql.md @@ -16,15 +16,19 @@ The public demo generates a database from workbook. ## WebSQL Details Importing data from spreadsheets is straightforward using the `generate_sql` -helper function from ["Generating Tables"](/docs/demos/data/sql#generating-tables): +helper function from ["Generating Tables"](/docs/demos/data/sql#generating-tables). + +The Web SQL Database API is callback-based. The following snippet wraps +transactions in Promise objects: ```js const db = openDatabase('sheetql', '1.0', 'SheetJS WebSQL Test', 2097152); const stmts = generate_sql(ws, wsname); + // NOTE: tx.executeSql and db.transaction use callbacks. This wraps in Promises -for(var i = 0; i < stmts.length; ++i) await new Promise((res, rej) => { +for(var stmt of stmts) await new Promise((res, rej) => { db.transaction(tx => - tx.executeSql(stmts[i], [], + tx.executeSql(stmt, [], (tx, data) => res(data), // if the query is successful, return the data (tx, err) => rej(err) // if the query fails, reject with the error )); @@ -33,23 +37,29 @@ for(var i = 0; i < stmts.length; ++i) await new Promise((res, rej) => { The result of a SQL SELECT statement is a `SQLResultSet`. The `rows` property is a `SQLResultSetRowList`. It is an "array-like" structure that has `length` -and properties like `0`, `1`, etc. However, this is not a real Array object. +and properties like `0`, `1`, etc. However, this is not a real Array object! + A real Array can be created using `Array.from`: ```js -const db = openDatabase('sheetql', '1.0', 'SheetJS WebSQL Test', 2097152); db.readTransaction(tx => tx.executeSQL("SELECT * FROM DatabaseTable", [], (tx, data) => { // data.rows is "array-like", so `Array.from` can make it a real array const aoo = Array.from(data.rows); const ws = XLSX.utils.json_to_sheet(aoo); - // ... it is recommended to perform an export here OR wrap in a Promise + // ... perform an export here OR wrap in a Promise }) ); ``` ### Live Demo +:::note + +This demo was last tested on 2023 February 26 + +::: + The following demo generates a database with 5 fixed SQL statements. Queries can be changed in the Live Editor. The WebSQL database can be inspected in the "WebSQL" section of the "Application" Tab of Developer Tools: @@ -67,16 +77,16 @@ function SheetQL() { 'INSERT INTO Presidents (Name, Idx) VALUES ("Joseph Biden", 46)' ]; const xport = React.useCallback(async() => { - // prep database + /* prep database */ const db = openDatabase('sheetql', '1.0', 'SheetJS WebSQL Test', 2097152); - for(var i = 0; i < queries.length; ++i) await new Promise((res, rej) => { + for(var q of queries) await new Promise((res, rej) => { db.transaction((tx) => { - tx.executeSql(queries[i], [], (tx, data) => res(data), (tx, err) => rej(err)); + tx.executeSql(q, [], (tx, data) => res(data), (tx, err) => rej(err)); }); }); - // pull data and generate rows + /* pull data and generate rows */ db.readTransaction(tx => { tx.executeSql("SELECT * FROM Presidents", [], (tx, data) => { const aoo = Array.from(data.rows); @@ -102,16 +112,24 @@ export as XLSX. [The Northwind database is available in SQLite form](https://raw.githubusercontent.com/jpwhite3/northwind-SQLite3/master/dist/northwind.db). +:::note + +This demo was last tested on 2023 February 26 + +::: + ### NodeJS The **`better-sqlite3`** module provides a very simple API for working with SQLite databases. `Statement#all` runs a prepared statement and returns an array of JS objects. +0) [Download `northwind.db`](https://raw.githubusercontent.com/jpwhite3/northwind-SQLite3/master/dist/northwind.db). + 1) Install the dependencies: ```bash -npm i --save https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz better-sqlite3 +npm i --save https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz better-sqlite3@8.1.0 ``` 2) Save the following to `node.mjs`: @@ -157,6 +175,8 @@ XLSX.writeFile(wb, "node.xlsx"); Bun ships with a built-in high-performance module `bun:sqlite`. +0) [Download `northwind.db`](https://raw.githubusercontent.com/jpwhite3/northwind-SQLite3/master/dist/northwind.db). + 1) Install the dependencies: ```bash @@ -206,6 +226,8 @@ XLSX.writeFile(wb, "bun.xlsx"); Deno `sqlite` library returns raw arrays of arrays. +0) [Download `northwind.db`](https://raw.githubusercontent.com/jpwhite3/northwind-SQLite3/master/dist/northwind.db). + 1) Save the following to `deno.ts`: ```ts title="deno.ts" @@ -244,4 +266,4 @@ result.forEach(function(row) { XLSX.writeFile(wb, "deno.xlsx"); ``` -3) Run `deno run --allow-read --allow-write deno.ts` and open `deno.xlsx` +2) Run `deno run --allow-read --allow-write deno.ts` and open `deno.xlsx` diff --git a/docz/docs/03-demos/06-data/03-indexeddb.md b/docz/docs/03-demos/06-data/03-indexeddb.md index 35f7569..c034ec8 100644 --- a/docz/docs/03-demos/06-data/03-indexeddb.md +++ b/docz/docs/03-demos/06-data/03-indexeddb.md @@ -6,26 +6,228 @@ sidebar_custom_props: type: web --- + + + + + :::warning -IndexedDB is a very low-level API. It is strongly recommended to use a wrapper -library or [WebSQL](/docs/demos/data/websql) in production applications. +IndexedDB is a very low-level API. + +Browser vendors recommend using libraries or [WebSQL](/docs/demos/data/websql) +in production applications. + +::: + +## Wrapper Libraries + +A number of popular wrapper libraries seek to simplify IndexedDB operations. + +:::note + +The wrapper libraries in this section have been used by SheetJS users in +production sites. + +::: + +### localForage + +:::note + +This demo was last tested on 2023 February 26 with `localForage` 1.10.0 ::: `localForage` is a IndexedDB wrapper that presents an async Storage interface. -Arrays of objects can be stored using `JSON.stringify` using row index as key: +Arrays of objects can be stored using `setItem` using row index as key: ```js const aoo = XLSX.utils.sheet_to_json(ws); -for(var i = 0; i < aoo.length; ++i) await localForage.setItem(i, JSON.stringify(aoo[i])); +for(var i = 0; i < aoo.length; ++i) await localForage.setItem(i, aoo[i]); ``` -Recovering the array of objects is possible by using `JSON.parse`: +Recovering the array of objects involves an iteration over the storage: ```js const aoo = []; -for(var i = 0; i < localForage.length; ++i) aoo.push(JSON.parse(await localForage.getItem(i))); -const wb = XLSX.utils.json_to_sheet(aoo); +await localforage.iterate((v, k) => { aoa[+k] = v; }); +const ws = XLSX.utils.json_to_sheet(aoo); ``` + +#### Demo + +This demo prepares a small IndexedDB database with some sample data. + +After saving the exported file, the IndexedDB database can be inspected in the +"IndexedDB" section of the "Application" Tab of Developer Tools: + +![IndexedDB view in Developer Tools](pathname:///storageapi/lforage.png) + +```jsx live +function SheetJSLocalForage() { + const data = [ + { Name: "Barack Obama", Index: 44 }, + { Name: "Donald Trump", Index: 45 }, + { Name: "Joseph Biden", Index: 46 } + ]; + const xport = React.useCallback(async() => { + /* force use of IndexedDB and connect to DB */ + localforage.config({ + driver: [ localforage.INDEXEDDB ], + name: "SheetQL", + size: 2097152 + }); + + /* create sample data */ + await localforage.clear(); + for(var i = 0; i < data.length; ++i) await localforage.setItem(i, data[i]); + + /* pull data and generate aoa */ + const aoo = []; + await localforage.iterate((v, k) => { aoo[+k] = v; }); + + /* export */ + const ws = XLSX.utils.json_to_sheet(aoo); + const wb = XLSX.utils.book_new(); + XLSX.utils.book_append_sheet(wb, ws, "Presidents"); + XLSX.writeFile(wb, "SheetJSLocalForage.xlsx"); + }); + return (
); +} +``` + +### DexieJS + +:::note + +This demo was last tested on 2023 February 26 with DexieJS 3.2.3 + +::: + +DexieJS is a minimalistic wrapper for IndexedDB. It provides a convenient +interface for creating multiple logical tables, well-suited for workbooks. + +#### Importing Data + +When configuring tables, DexieJS needs a schema. The schema definition supports +primary keys and other properties, but they are not required: + +```js +/* assuming `wb` is a workbook from XLSX.read */ +var db = new Dexie("SheetJSDexie"); +db.version(1).stores(Object.fromEntries(wb.SheetNames.map(n => ([n, "++"])))); +``` + +After the database is configured, `bulkPut` can insert arrays of objects: + +```js +/* loop over worksheet names */ +for(let i = 0; i <= wb.SheetNames.length; ++i) { + /* get the worksheet for the specified index */ + const wsname = wb.SheetNames[i]; + const ws = wb.Sheets[wsname]; + if(!ws) continue; + /* generate an array of objects */ + const aoo = XLSX.utils.sheet_to_json(ws); + /* push to idb */ + await db[wsname].bulkPut(aoo); +} +``` + +This demo inserts all data from a selected worksheet into a database, then +fetches the data from the first worksheet in reverse: + +```jsx live +/* The live editor requires this function wrapper */ +function SheetJSDexieImport(props) { + const [__html, setHTML] = React.useState("Select a spreadsheet"); + + return (<> + { try { + /* get data as an ArrayBuffer */ + const file = e.target.files[0]; + const data = await file.arrayBuffer(); + + /* parse worksheet */ + const wb = XLSX.read(data); + + /* load into indexeddb */ + await Dexie.delete("SheetJSDexie"); + const db = new Dexie("SheetJSDexie"); + const wsnames = wb.SheetNames.map(n => ([n, "++"])); + db.version(1).stores(Object.fromEntries(wsnames)); + + /* loop over worksheet names */ + for(let i = 0; i <= wb.SheetNames.length; ++i) { + /* get the worksheet for the specified index */ + const wsname = wb.SheetNames[i]; + const ws = wb.Sheets[wsname]; + if(!ws) continue; + /* generate an array of objects */ + const aoo = XLSX.utils.sheet_to_json(ws); + /* push to idb */ + await db[wsname].bulkPut(aoo); + } + + /* fetch the first table in reverse order */ + const rev = await db[wb.SheetNames[0]].reverse().toArray(); + + setHTML(rev.map(r => JSON.stringify(r)).join("\n")); + } catch(e) { setHTML(e && e.message || e); }}}/> +
+  );
+}
+```
+
+#### Exporting Data
+
+`db.tables` is a plain array of table objects. `toArray` fetches data:
+
+```js
+/* create blank workbook */
+const wb = XLSX.utils.book_new();
+/* loop tables */
+for(const table of db.tables) {
+  /* get data */
+  const aoo = await table.toArray();
+  /* create worksheet */
+  const ws = XLSX.utils.json_to_sheet(aoo);
+  /* add to workbook */
+  XLSX.utils.book_append_sheet(wb, ws, table.name);
+}
+```
+
+This demo prepares a small database with some sample data.
+
+```jsx live
+function SheetJSDexieExport() {
+  const data = [
+    { Name: "Barack Obama", Index: 44 },
+    { Name: "Donald Trump", Index: 45 },
+    { Name: "Joseph Biden", Index: 46 }
+  ];
+  const xport = React.useCallback(async() => {
+    /* prepare db */
+    await Dexie.delete("SheetJSDexie");
+    var db = new Dexie("SheetJSDexie");
+    db.version(1).stores({ Presidents: "++" });
+    db.Presidents.bulkPut(data);
+
+    /* pull data and generate workbook */
+    const wb = XLSX.utils.book_new();
+    for(const table of db.tables) {
+      const aoo = await table.toArray();
+      const ws = XLSX.utils.json_to_sheet(aoo);
+      XLSX.utils.book_append_sheet(wb, ws, table.name);
+    }
+    XLSX.writeFile(wb, "SheetJSDexie.xlsx");
+  });
+  return ( 
); +} +``` + +### AlaSQL + +[AlaSQL](/docs/demos/data/alasql) ships with an IndexedDB backend. \ No newline at end of file diff --git a/docz/static/storageapi/lforage.png b/docz/static/storageapi/lforage.png new file mode 100644 index 0000000..80f62cd Binary files /dev/null and b/docz/static/storageapi/lforage.png differ