forked from sheetjs/docs.sheetjs.com
		
	synthetic-dom
This commit is contained in:
		
							parent
							
								
									096b5ddce8
								
							
						
					
					
						commit
						4db5b76501
					
				| @ -484,8 +484,14 @@ The data is in the workbook and can be exported. | ||||
|  | ||||
| 
 | ||||
| There are multiple opportunities for improvement: the headers can be renamed and | ||||
| the column widths can be adjusted.  [SheetJS Pro](https://sheetjs.com/pro) offers | ||||
| additional styling options like cell styling and frozen rows. | ||||
| the column widths can be adjusted. | ||||
| 
 | ||||
| :::tip pass | ||||
| 
 | ||||
| [SheetJS Pro](https://sheetjs.com/pro) offers additional styling options like | ||||
| cell styling and frozen rows. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| <details><summary><b>Changing Header Names</b> (click to show)</summary> | ||||
| 
 | ||||
| @ -957,10 +963,17 @@ of the React Native documentation before testing the demo. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| :::caution pass | ||||
| 
 | ||||
| For Android testing, React Native requires Java 11. It will not work with | ||||
| current Java releases. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| Create a new project by running the following commands in the Terminal: | ||||
| 
 | ||||
| <CodeBlock language="bash">{`\ | ||||
| npx react-native@0.71 init SheetJSPres --version="0.72.0-rc.1" | ||||
| npx -y react-native@0.72.4 init SheetJSPres --version="0.72.4" | ||||
| cd SheetJSPres | ||||
| \n\ | ||||
| npm i -S https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz react-native-blob-util@0.17.1`} | ||||
|  | ||||
| @ -1266,7 +1266,7 @@ Access `http://localhost:8080` in your web browser and click the export button. | ||||
| 
 | ||||
| </details> | ||||
| 
 | ||||
| :::note | ||||
| :::note pass | ||||
| 
 | ||||
| The [Vite section of the Content demo](/docs/demos/static/vitejs) covers asset | ||||
| loaders. They are ideal for static sites pulling data from sheets at build time. | ||||
|  | ||||
| @ -5,10 +5,16 @@ title: Synthetic DOM | ||||
| import current from '/version.js'; | ||||
| import CodeBlock from '@theme/CodeBlock'; | ||||
| 
 | ||||
| `table_to_book` / `table_to_sheet` / `sheet_add_dom` act on HTML DOM elements. | ||||
| Traditionally there is no DOM in server-side environments. | ||||
| SheetJS offers three methods to directly process HTML DOM TABLE elements[^1]: | ||||
| 
 | ||||
| :::note | ||||
| - `table_to_sheet` generates a SheetJS worksheet[^2] from a TABLE element | ||||
| - `table_to_book` generates a SheetJS workbook[^3] from a TABLE element | ||||
| - `sheet_add_dom` adds data from a TABLE element to an existing worksheet | ||||
| 
 | ||||
| These methods work in the web browser. NodeJS and other server-side platforms | ||||
| traditionally lack a DOM implementation, but third-party modules fill the gap. | ||||
| 
 | ||||
| :::tip pass | ||||
| 
 | ||||
| The most robust approach for server-side processing is to automate a headless | ||||
| web browser. ["Browser Automation"](/docs/demos/net/headless) includes demos. | ||||
| @ -17,23 +23,60 @@ web browser. ["Browser Automation"](/docs/demos/net/headless) includes demos. | ||||
| 
 | ||||
| This demo covers synthetic DOM implementations for non-browser platforms. | ||||
| 
 | ||||
| ## Integration Details | ||||
| 
 | ||||
| SheetJS API methods use DOM features that may not be available. | ||||
| 
 | ||||
| ### Table rows | ||||
| 
 | ||||
| The `rows` property of TABLE elements is a list of TR row children. This list | ||||
| automatically updates when rows are added and deleted. | ||||
| 
 | ||||
| SheetJS does not mutate `rows`. Assuming there are no nested tables, the `rows` | ||||
| property can be created using `getElementsByTagName`: | ||||
| 
 | ||||
| ```js | ||||
| tbl.rows = Array.from(tbl.getElementsByTagName("tr")); | ||||
| ``` | ||||
| 
 | ||||
| ### Row cells | ||||
| 
 | ||||
| The `cells` property of TR elements is a list of TD cell children. This list | ||||
| automatically updates when cells are added and deleted. | ||||
| 
 | ||||
| SheetJS does not mutate `cells`. Assuming there are no nested tables, the | ||||
| `cells` property can be created using `getElementsByTagName`: | ||||
| 
 | ||||
| ```js | ||||
| tbl.rows.forEach(row => row.cells = Array.from(row.getElementsByTagName("td"))); | ||||
| ``` | ||||
| 
 | ||||
| ## NodeJS | ||||
| 
 | ||||
| ### JSDOM | ||||
| 
 | ||||
| JSDOM is a DOM implementation for NodeJS. Given an HTML string, a reference to | ||||
| the table element plays nice with the SheetJS DOM methods: | ||||
| JSDOM is a DOM implementation for NodeJS. The synthetic DOM elements are | ||||
| compatible with SheetJS methods. | ||||
| 
 | ||||
| ```js | ||||
| The following example scrapes the first table from the file `SheetJSTable.html` | ||||
| and generates a XLSX workbook: | ||||
| 
 | ||||
| ```js title="SheetJSDOM.js" | ||||
| const XLSX = require("xlsx"); | ||||
| const { readFileSync } = require("fs"); | ||||
| const { JSDOM } = require("jsdom"); | ||||
| 
 | ||||
| /* parse HTML */ | ||||
| const dom = new JSDOM(html_string); | ||||
| /* obtain HTML string.  This example reads from SheetJSTable.html */ | ||||
| const html_str = readFileSync("SheetJSTable.html", "utf8"); | ||||
| 
 | ||||
| // highlight-start | ||||
| /* get first TABLE element */ | ||||
| const tbl = dom.window.document.querySelector("table"); | ||||
| const doc = new JSDOM(html_str).window.document.querySelector("table"); | ||||
| 
 | ||||
| /* generate workbook */ | ||||
| const workbook = XLSX.utils.table_to_book(tbl); | ||||
| const workbook = XLSX.utils.table_to_book(doc); | ||||
| // highlight-end | ||||
| 
 | ||||
| XLSX.writeFile(workbook, "SheetJSDOM.xlsx"); | ||||
| ``` | ||||
| 
 | ||||
| @ -41,7 +84,7 @@ XLSX.writeFile(workbook, "SheetJSDOM.xlsx"); | ||||
| 
 | ||||
| :::note | ||||
| 
 | ||||
| This demo was last tested on 2023 May 18 against JSDOM `22.0.0` | ||||
| This demo was last tested on 2023 September 10 against JSDOM `22.1.0` | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| @ -51,21 +94,7 @@ This demo was last tested on 2023 May 18 against JSDOM `22.0.0` | ||||
| npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz jsdom@22.0.0`} | ||||
| </CodeBlock> | ||||
| 
 | ||||
| 2) Save the following script to `SheetJSDOM.js`: | ||||
| 
 | ||||
| ```js title="SheetJSDOM.js" | ||||
| const XLSX = require("xlsx"); | ||||
| const { readFileSync } = require("fs"); | ||||
| const { JSDOM } = require("jsdom"); | ||||
| 
 | ||||
| /* obtain HTML string.  This example reads from SheetJSTable.html */ | ||||
| const html_str = readFileSync("SheetJSTable.html", "utf8"); | ||||
| /* get first TABLE element */ | ||||
| const doc = new JSDOM(html_str).window.document.querySelector("table"); | ||||
| /* generate workbook */ | ||||
| const workbook = XLSX.utils.table_to_book(doc); | ||||
| XLSX.writeFile(workbook, "SheetJSDOM.xlsx"); | ||||
| ``` | ||||
| 2) Save the previous codeblock to `SheetJSDOM.js`. | ||||
| 
 | ||||
| 3) Download [the sample `SheetJSTable.html`](pathname:///dom/SheetJSTable.html): | ||||
| 
 | ||||
| @ -83,48 +112,79 @@ The script will create a file `SheetJSDOM.xlsx` that can be opened. | ||||
| 
 | ||||
| </details> | ||||
| 
 | ||||
| ### XMLDOM | ||||
| ### HappyDOM | ||||
| 
 | ||||
| XMLDOM provides a DOM framework for NodeJS. Given an HTML string, a reference to | ||||
| the table element works with the SheetJS DOM methods after patching the object. | ||||
| HappyDOM provides a DOM framework for NodeJS. For the tested version (`11.0.2`), | ||||
| the following patches were needed: | ||||
| 
 | ||||
| - TABLE `rows` property (explained above) | ||||
| - TR `cells` property (explained above) | ||||
| 
 | ||||
| <details><summary><b>Complete Demo</b> (click to show)</summary> | ||||
| 
 | ||||
| :::note | ||||
| 
 | ||||
| This demo was last tested on 2023 May 18 against XMLDOM `0.8.7` | ||||
| This demo was last tested on 2023 September 10 against HappyDOM `11.0.2` | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| 1) Install SheetJS and HappyDOM libraries: | ||||
| 
 | ||||
| <CodeBlock language="bash">{`\ | ||||
| npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz happy-dom@11.0.2`} | ||||
| </CodeBlock> | ||||
| 
 | ||||
| 2) Download [the sample script `SheetJSHappyDOM.js`](pathname:///dom/SheetJSHappyDOM.js): | ||||
| 
 | ||||
| ```bash | ||||
| curl -LO https://docs.sheetjs.com/dom/SheetJSHappyDOM.js | ||||
| ``` | ||||
| 
 | ||||
| 3) Run the script: | ||||
| 
 | ||||
| ```bash | ||||
| node SheetJSHappyDOM.js | ||||
| ``` | ||||
| 
 | ||||
| The script will create a file `SheetJSHappyDOM.xlsx` that can be opened. | ||||
| 
 | ||||
| </details> | ||||
| 
 | ||||
| ### XMLDOM | ||||
| 
 | ||||
| XMLDOM provides a DOM framework for NodeJS. For the tested version (`0.8.10`), | ||||
| the following patches were needed: | ||||
| 
 | ||||
| - TABLE `rows` property (explained above) | ||||
| - TR `cells` property (explained above) | ||||
| - Element `innerHTML` property: | ||||
| 
 | ||||
| ```js | ||||
| Object.defineProperty(tbl.__proto__, "innerHTML", { get: function() { | ||||
| 	var outerHTML = new XMLSerializer().serializeToString(this); | ||||
| 	if(outerHTML.match(/</g).length == 1) return ""; | ||||
| 	return outerHTML.slice(0, outerHTML.lastIndexOf("</")).replace(/<[^"'>]*(("[^"]*"|'[^']*')[^"'>]*)*>/, ""); | ||||
| }}); | ||||
| ``` | ||||
| 
 | ||||
| <details><summary><b>Complete Demo</b> (click to show)</summary> | ||||
| 
 | ||||
| :::note | ||||
| 
 | ||||
| This demo was last tested on 2023 September 10 against XMLDOM `0.8.10` | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| 1) Install SheetJS and XMLDOM libraries: | ||||
| 
 | ||||
| <CodeBlock language="bash">{`\ | ||||
| npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz @xmldom/xmldom@0.8.7`} | ||||
| npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz @xmldom/xmldom@0.8.10`} | ||||
| </CodeBlock> | ||||
| 
 | ||||
| 2) Save the following codeblock to `SheetJSXMLDOM.js`: | ||||
| 2) Download [the sample script `SheetJSXMLDOM.js`](pathname:///dom/SheetJSXMLDOM.js): | ||||
| 
 | ||||
| ```js title="SheetJSXMLDOM.js" | ||||
| const XLSX = require("xlsx"); | ||||
| const { DOMParser, XMLSerializer } = require("@xmldom/xmldom"); | ||||
| 
 | ||||
| (async() => { | ||||
| const text = await (await fetch('https://docs.sheetjs.com/dom/SheetJSTable.html')).text(); | ||||
| const doc = new DOMParser().parseFromString( text, "text/html"); | ||||
| const tbl = doc.getElementsByTagName("table")[0]; | ||||
| 
 | ||||
| /* patch XMLDOM */ | ||||
| tbl.rows = Array.from(tbl.getElementsByTagName("tr")); | ||||
| tbl.rows.forEach(row => row.cells = Array.from(row.getElementsByTagName("td"))) | ||||
| Object.defineProperty(tbl.__proto__, "innerHTML", { get: function() { | ||||
| 	var outerHTML = new XMLSerializer().serializeToString(this); | ||||
| 	if(outerHTML.match(/</g).length == 1) return ""; | ||||
| 	return outerHTML.slice(0, outerHTML.lastIndexOf("</")).replace(/<[^"'>]*(("[^"]*"|'[^']*')[^"'>]*)*>/, ""); | ||||
| }}); | ||||
| 
 | ||||
| const workbook = XLSX.utils.table_to_book(tbl); | ||||
| XLSX.writeFile(workbook, "SheetJSXMLDOM.xlsx"); | ||||
| })(); | ||||
| ```bash | ||||
| curl -LO https://docs.sheetjs.com/dom/SheetJSXMLDOM.js | ||||
| ``` | ||||
| 
 | ||||
| 3) Run the script: | ||||
| @ -135,28 +195,26 @@ node SheetJSXMLDOM.js | ||||
| 
 | ||||
| The script will create a file `SheetJSXMLDOM.xlsx` that can be opened. | ||||
| 
 | ||||
| 
 | ||||
| </details> | ||||
| 
 | ||||
| ### CheerioJS | ||||
| 
 | ||||
| :::caution | ||||
| :::caution pass | ||||
| 
 | ||||
| Cheerio does not support a number of fundamental properties out of the box. They | ||||
| can be shimmed, but it is strongly recommended to use a more compliant library. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| CheerioJS provides a DOM-like framework for NodeJS. Given an HTML string, a | ||||
| reference to the table element works with the SheetJS DOM methods with some | ||||
| prototype fixes. [`SheetJSCheerio.js`](pathname:///dom/SheetJSCheerio.js) is a | ||||
| complete script. | ||||
| CheerioJS provides a DOM-like framework for NodeJS. Many features were missing. | ||||
| [`SheetJSCheerio.js`](pathname:///dom/SheetJSCheerio.js) implements the missing | ||||
| features to ensure that SheetJS DOM methods can process TABLE elements. | ||||
| 
 | ||||
| <details><summary><b>Complete Demo</b> (click to show)</summary> | ||||
| 
 | ||||
| :::note | ||||
| 
 | ||||
| This demo was last tested on 2023 May 18 against Cheerio `1.0.0-rc.12` | ||||
| This demo was last tested on 2023 September 10 against Cheerio `1.0.0-rc.12` | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| @ -192,8 +250,11 @@ The script will create a file `SheetJSCheerio.xlsx` that can be opened. | ||||
| 
 | ||||
| ### DenoDOM | ||||
| 
 | ||||
| DenoDOM provides a DOM framework for Deno. Given an HTML string, a reference to | ||||
| the table element works with the SheetJS DOM methods after patching the object. | ||||
| DenoDOM provides a DOM framework for Deno. For the tested version (`0.1.38`), | ||||
| the following patches were needed: | ||||
| 
 | ||||
| - TABLE `rows` property (explained above) | ||||
| - TR `cells` property (explained above) | ||||
| 
 | ||||
| This example fetches [a sample table](pathname:///dom/SheetJSTable.html): | ||||
| 
 | ||||
| @ -220,11 +281,11 @@ const workbook = XLSX.utils.table_to_book(tbl); | ||||
| XLSX.writeFile(workbook, "SheetJSDenoDOM.xlsx");`} | ||||
| </CodeBlock> | ||||
| 
 | ||||
| <details open><summary><b>Complete Demo</b> (click to hide)</summary> | ||||
| <details><summary><b>Complete Demo</b> (click to show)</summary> | ||||
| 
 | ||||
| :::note | ||||
| 
 | ||||
| This demo was last tested on 2023 May 18 against DenoDOM `0.1.38` | ||||
| This demo was last tested on 2023 September 10 against DenoDOM `0.1.38` | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| @ -238,4 +299,8 @@ deno run --allow-net --allow-write SheetJSDenoDOM.ts | ||||
| 
 | ||||
| The script will create a file `SheetJSDenoDOM.xlsx` that can be opened. | ||||
| 
 | ||||
| </details> | ||||
| </details> | ||||
| 
 | ||||
| [^1]: See ["HTML Table Input" in "Utilities"](/docs/api/utilities/html#html-table-input) | ||||
| [^2]: See ["Worksheet Object" in "SheetJS Data Model"](/docs/csf/book) for more details. | ||||
| [^3]: See ["Workbook Object" in "SheetJS Data Model"](/docs/csf/book) for more details. | ||||
|  | ||||
| @ -1,5 +1,7 @@ | ||||
| --- | ||||
| title: Ionic | ||||
| title: Data Conduction in Ionic Apps | ||||
| sidebar_label: Ionic | ||||
| description: Build data-intensive mobile apps with Ionic and Cordova. Seamlessly integrate spreadsheets into your app using SheetJS. Let data in your Excel spreadsheets shine. | ||||
| pagination_prev: demos/static/index | ||||
| pagination_next: demos/desktop/index | ||||
| sidebar_position: 4 | ||||
| @ -10,10 +12,17 @@ sidebar_custom_props: | ||||
| import current from '/version.js'; | ||||
| import CodeBlock from '@theme/CodeBlock'; | ||||
| 
 | ||||
| The [NodeJS Module](/docs/getting-started/installation/nodejs) can be imported | ||||
| from the main entrypoint or any script in the project. | ||||
| [Ionic](https://ionicframework.com/) is a mobile app framework for building iOS | ||||
| and Android apps with the Cordova platform. | ||||
| 
 | ||||
| The "Complete Example" creates an app that looks like the screenshots below: | ||||
| [SheetJS](https://sheetjs.com) is a JavaScript library for reading and writing | ||||
| data from spreadsheets. | ||||
| 
 | ||||
| This demo uses Ionic and SheetJS to process data and generate spreadsheets. | ||||
| We'll explore how to load SheetJS in an Ionic app and use Ionic APIs and plugins | ||||
| to extract data from, and write data to, spreadsheet files on the device. | ||||
| 
 | ||||
| The ["Demo"](#demo) creates an app that looks like the screenshots below: | ||||
| 
 | ||||
| <table><thead><tr> | ||||
|   <th><a href="#demo">iOS</a></th> | ||||
| @ -28,6 +37,13 @@ The "Complete Example" creates an app that looks like the screenshots below: | ||||
| 
 | ||||
| </td></tr></tbody></table> | ||||
| 
 | ||||
| :::info pass | ||||
| 
 | ||||
| This demo covers Ionic apps using the Cordova platform. | ||||
| 
 | ||||
| The [CapacitorJS demo](/docs/demos/mobile/capacitor) covers CapacitorJS apps. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| :::warning Telemetry | ||||
| 
 | ||||
| @ -57,18 +73,36 @@ npx @capacitor/cli telemetry | ||||
| 
 | ||||
| ## Integration Details | ||||
| 
 | ||||
| :::caution pass | ||||
| The [SheetJS NodeJS Module](/docs/getting-started/installation/nodejs) can be | ||||
| imported from the main entrypoint or any script in the project. | ||||
| 
 | ||||
| The latest version of Ionic uses CapacitorJS. These notes are for Cordova apps. | ||||
| The [CapacitorJS demo](/docs/demos/mobile/capacitor) covers CapacitorJS apps. | ||||
| ### Internal State | ||||
| 
 | ||||
| ::: | ||||
| The ["Angular" demo](/docs/demos/frontend/angular) discusses a number of state | ||||
| representations and preview strategies. | ||||
| 
 | ||||
| ### Angular | ||||
| For this demo, the internal state is an "array of arrays"[^1] (`any[][]`): | ||||
| 
 | ||||
| `Array<Array<any>>` neatly maps to a table with `ngFor`: | ||||
| ```ts title="Array of Arrays state" | ||||
| import { Component } from '@angular/core'; | ||||
| type AOA = any[][]; | ||||
| 
 | ||||
| ```html | ||||
| @Component({...}) | ||||
| export class SheetJSTablePage { | ||||
|   data: AOA = [ | ||||
|     ["S", "h", "e", "e", "t", "J", "S"], | ||||
|     [  5,   4,   3,   3,   7,   9,   5] | ||||
|   ]; | ||||
|   // ... | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| ### Displaying Data | ||||
| 
 | ||||
| `ion-grid`[^2] is a display grid component. The Angular `ngFor` directive[^3] | ||||
| simplifies iteration over the array of arrays: | ||||
| 
 | ||||
| ```html title="Template for displaying an array of arrays" | ||||
| <ion-grid> | ||||
|   <ion-row *ngFor="let row of data"> | ||||
|     <ion-col *ngFor="let val of row"> | ||||
| @ -78,29 +112,62 @@ The [CapacitorJS demo](/docs/demos/mobile/capacitor) covers CapacitorJS apps. | ||||
| </ion-grid> | ||||
| ``` | ||||
| 
 | ||||
| ### Cordova | ||||
| ### File Operations | ||||
| 
 | ||||
| `@ionic-native/file` reads and writes files on devices. | ||||
| `@awesome-cordova-plugins/file` reads and writes files on devices. | ||||
| 
 | ||||
| _Reading Files_ | ||||
| :::info pass | ||||
| 
 | ||||
| `readAsArrayBuffer` returns `ArrayBuffer` objects suitable for `array` type: | ||||
| The plugins in the `@ionic-native` scope have been deprecated. The community | ||||
| modules in the `@awesome-cordova-plugins` scope should be used. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| #### Reading Files | ||||
| 
 | ||||
| `this.file.readAsArrayBuffer` reads file data from a specified URL and resolves | ||||
| to `ArrayBuffer` objects. | ||||
| 
 | ||||
| These objects can be parsed with the SheetJS `read` method[^4]. The SheetJS | ||||
| `sheet_to_json` method[^5] with the option `header: 1` generates an array of | ||||
| arrays which can be assigned to the page state: | ||||
| 
 | ||||
| ```ts | ||||
| /* read a workbook */ | ||||
| /* read a workbook file */ | ||||
| const ab: ArrayBuffer = await this.file.readAsArrayBuffer(url, filename); | ||||
| /* parse */ | ||||
| const wb: XLSX.WorkBook = XLSX.read(ab, {type: 'array'}); | ||||
| /* generate an array of arrays from the first worksheet */ | ||||
| const ws: XLSX.WorkSheet = wb.SheetNames[wb.Sheets[0]]; | ||||
| const aoa: AOA = XLSX.utils.sheet_to_json(ws, {header: 1}); | ||||
| /* update state */ | ||||
| this.data = aoa; | ||||
| ``` | ||||
| 
 | ||||
| _Writing Files_ | ||||
| #### Writing Files | ||||
| 
 | ||||
| `array` type can be converted to blobs that can be exported with `writeFile`: | ||||
| `this.file.writeFile` writes file data stored in `Blob` objects to the device. | ||||
| 
 | ||||
| From the array of arrays, the SheetJS `aoa_to_sheet` method[^6] generates a | ||||
| worksheet object. The `book_new` and `book_append_sheet` helpers[^7] generate a | ||||
| workbook object. The SheetJS `write` method[^8] with the option `type: "array"` | ||||
| will generate an `ArrayBuffer`, from which a `Blob` can be created: | ||||
| 
 | ||||
| ```ts | ||||
| /* write a workbook */ | ||||
| const wbout: ArrayBuffer = XLSX.write(wb, { bookType: 'xlsx', type: 'array' }); | ||||
| let blob = new Blob([wbout], {type: 'application/octet-stream'}); | ||||
| /* generate worksheet */ | ||||
| const ws: XLSX.WorkSheet = XLSX.utils.aoa_to_sheet(this.data); | ||||
| 
 | ||||
| /* generate workbook and add the worksheet */ | ||||
| const wb: XLSX.WorkBook = XLSX.utils.book_new(); | ||||
| XLSX.utils.book_append_sheet(wb, ws, 'SheetJS'); | ||||
| 
 | ||||
| /* write XLSX to ArrayBuffer */ | ||||
| const ab: ArrayBuffer = XLSX.write(wb, { bookType: 'xlsx', type: 'array' }); | ||||
| 
 | ||||
| /* generate Blob */ | ||||
| let blob = new Blob([ab], {type: 'application/octet-stream'}); | ||||
| 
 | ||||
| /* write Blob to device */ | ||||
| this.file.writeFile(url, filename, blob, {replace: true}); | ||||
| ``` | ||||
| 
 | ||||
| @ -108,47 +175,72 @@ this.file.writeFile(url, filename, blob, {replace: true}); | ||||
| 
 | ||||
| :::note | ||||
| 
 | ||||
| This demo was tested on an Intel Mac on 2023 March 28 with Cordova. | ||||
| The file integration uses `@ionic-native/file` version `5.36.0`. | ||||
| The project was last tested in 2023 September 10. `ionic info` showed: | ||||
| 
 | ||||
| The iOS simulator runs iOS 15.5 on an iPhone SE (3rd Generation). | ||||
| - Ionic: `@ionic/angular 7.3.3`, `@ionic/angular-toolkit 9.0.0` | ||||
| - Cordova: `cordova-lib@12.0.1`, `android 12.0.1, ios 7.0.1` | ||||
| 
 | ||||
| The Android simulator runs Android 12.0 (S) API 31 on a Pixel 3. | ||||
| The file integration uses `@awesome-cordova-plugins/file` version `6.4.0`. | ||||
| 
 | ||||
| The iOS demo was last tested on 2023 September 10 on an emulated iPhone SE | ||||
| (3rd generation) + iOS 16.4 | ||||
| 
 | ||||
| The Android demo was last tested on 2023 September 10 on an emulated Pixel 3 + | ||||
| Android 13 ("Tiramisu") API 33. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| The app in this demo will display data in a table. | ||||
| 
 | ||||
| On load, a [test file](https://sheetjs.com/pres.numbers) will be processed. | ||||
| 
 | ||||
| When a document is selected with the file picker, it will be processed and the | ||||
| table will refresh to show the contents. | ||||
| 
 | ||||
| "Import Data" will attempt to read `SheetJSIonic.xlsx` from a known location. An | ||||
| alert will display the expected location. | ||||
| 
 | ||||
| "Export Data" will attempt to export the table data to `SheetJSIonic.xlsx` in a | ||||
| known location. After writing, an alert will display the location of the file. | ||||
| 
 | ||||
| 
 | ||||
| ### Platform Setup | ||||
| 
 | ||||
| 0) Disable telemetry as noted in the warning. | ||||
| 
 | ||||
| Install required global dependencies: | ||||
| 1) Follow the official instructions for iOS and Android development[^9]. | ||||
| 
 | ||||
| 2) Install required global dependencies: | ||||
| 
 | ||||
| ```bash | ||||
| npm i -g cordova-res @angular/cli native-run @ionic/cli | ||||
| ``` | ||||
| 
 | ||||
| Follow the [React Native demo](/docs/demos/mobile/reactnative) to ensure iOS and Android sims are ready. | ||||
| ### Base Project | ||||
| 
 | ||||
| 
 | ||||
| 1) Create a new project: | ||||
| 3) Create a new project: | ||||
| 
 | ||||
| ```bash | ||||
| ionic start SheetJSIonic blank --type angular --cordova --quiet --no-git --no-link --confirm | ||||
| ``` | ||||
| 
 | ||||
| When asked to select `NgModules` or `Standalone Components`, select `NgModules` | ||||
| 
 | ||||
| If a prompt asks to confirm Cordova use, enter `Yes` to continue. | ||||
| 
 | ||||
| If a prompt asks about creating an Ionic account, enter `N` to opt out. | ||||
| 
 | ||||
| 2) Set up Cordova: | ||||
| 4) Set up Cordova: | ||||
| 
 | ||||
| ```bash | ||||
| cd SheetJSIonic | ||||
| ionic cordova plugin add cordova-plugin-file | ||||
| ionic cordova platform add ios --confirm | ||||
| ionic cordova platform add android --confirm | ||||
| npm i --save @ionic-native/core @ionic-native/file @ionic/cordova-builders | ||||
| npm i --save @awesome-cordova-plugins/core @awesome-cordova-plugins/file @ionic/cordova-builders | ||||
| ``` | ||||
| 
 | ||||
| :::note | ||||
| :::note pass | ||||
| 
 | ||||
| If `cordova-plugin-file` is added before the platforms, installation may fail: | ||||
| 
 | ||||
| @ -165,9 +257,9 @@ ionic cordova platform add ios --confirm | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| :::caution | ||||
| :::caution pass | ||||
| 
 | ||||
| If the `npm i` fails due to `rxjs` resolution, add the highlighted lines | ||||
| If the `npm i` step fails due to `rxjs` resolution, add the highlighted lines | ||||
| to `package.json` to force a resolution: | ||||
| 
 | ||||
| ```js title="package.json" | ||||
| @ -180,24 +272,26 @@ to `package.json` to force a resolution: | ||||
|   "dependencies": { | ||||
| ``` | ||||
| 
 | ||||
| Note that the required `rxjs` version will be displayed in the error log. | ||||
| 
 | ||||
| After adding the lines, the `npm i` command will succeed. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| 3) Install dependencies: | ||||
| 5) Install dependencies: | ||||
| 
 | ||||
| <CodeBlock language="bash">{`\ | ||||
| npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`} | ||||
| </CodeBlock> | ||||
| 
 | ||||
| 4) Add `@ionic-native/file` to the module.  Differences highlighted below: | ||||
| 6) Add `@awesome-cordova-plugins/file` to the module.  Differences highlighted below: | ||||
| 
 | ||||
| ```ts title="src/app/app.module.ts" | ||||
| import { AppComponent } from './app.component'; | ||||
| import { AppRoutingModule } from './app-routing.module'; | ||||
| 
 | ||||
| // highlight-next-line | ||||
| import { File } from '@ionic-native/file/ngx'; | ||||
| import { File } from '@awesome-cordova-plugins/file/ngx'; | ||||
| 
 | ||||
| @NgModule({ | ||||
|   declarations: [AppComponent], | ||||
| @ -210,19 +304,41 @@ import { File } from '@ionic-native/file/ngx'; | ||||
| export class AppModule {} | ||||
| ``` | ||||
| 
 | ||||
| 5) Download [`home.page.ts`](pathname:///ionic/home.page.ts) and replace: | ||||
| 7) Download [`home.page.ts`](pathname:///ionic/home.page.ts) and replace: | ||||
| 
 | ||||
| ```bash | ||||
| curl -o src/app/home/home.page.ts -L https://docs.sheetjs.com/ionic/home.page.ts | ||||
| ``` | ||||
| 
 | ||||
| **iOS Testing** | ||||
| ### iOS | ||||
| 
 | ||||
| 8) Enable file sharing and make the documents folder visible in the iOS app. | ||||
| Add the following lines to `platforms/ios/SheetJSIonic/SheetJSIonic-Info.plist`: | ||||
| 
 | ||||
| ```xml title="platforms/ios/SheetJSIonic/SheetJSIonic-Info.plist (add to file)" | ||||
| <plist version="1.0"> | ||||
| <dict> | ||||
| <!-- highlight-start --> | ||||
|   <key>UIFileSharingEnabled</key> | ||||
|   <true/> | ||||
|   <key>LSSupportsOpeningDocumentsInPlace</key> | ||||
|   <true/> | ||||
| <!-- highlight-end --> | ||||
|   <key>CFBundleDevelopmentRegion</key> | ||||
| ``` | ||||
| 
 | ||||
| (The root element of the document is `plist` and it contains one `dict` child) | ||||
| 
 | ||||
| 9) Build the app and start the simulator | ||||
| 
 | ||||
| ```bash | ||||
| ionic cordova emulate ios | ||||
| ``` | ||||
| 
 | ||||
| :::caution | ||||
| When the app is loaded, a list of Presidents should be displayed. This list is | ||||
| dynamically generated by fetching and parsing a test file. | ||||
| 
 | ||||
| :::caution pass | ||||
| 
 | ||||
| In some test runs, the `cordova build ios --emulator` step failed with error: | ||||
| 
 | ||||
| @ -239,13 +355,54 @@ npm i --save cordova-ios | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| **Android Testing** | ||||
| :::info pass | ||||
| 
 | ||||
| In the most recent test, the `native-run ios` command failed with | ||||
| 
 | ||||
| ``` | ||||
| [native-run] ERR_UNKNOWN: Path 'platforms/ios/build/emulator/SheetJSIonic.app' not found | ||||
| ``` | ||||
| 
 | ||||
| Inspecting `platforms/ios/build/`, the actual folder name was: | ||||
| 
 | ||||
| ```bash | ||||
| % ls platforms/ios/build | ||||
| #highlight-next-line | ||||
| Debug-iphonesimulator | ||||
| ``` | ||||
| 
 | ||||
| The iOS simulator can be launched manually: | ||||
| 
 | ||||
| ```bash | ||||
| native-run ios --app platforms/ios/build/Debug-iphonesimulator/SheetJSIonic.app --virtual | ||||
| ``` | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| ### Android | ||||
| 
 | ||||
| 10) Enable file reading and writing in the Android app. | ||||
| 
 | ||||
| Edit `platforms/android/app/src/main/AndroidManifest.xml` and add the following | ||||
| two lines before the `application` tag: | ||||
| 
 | ||||
| ```xml title="platforms/android/app/src/main/AndroidManifest.xml (add to file)" | ||||
|     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> | ||||
|     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> | ||||
| ``` | ||||
| 
 | ||||
| In the `application` tag, add the attribute `android:requestLegacyExternalStorage="true"`. | ||||
| 
 | ||||
| 11) Build the app and start the emulator | ||||
| 
 | ||||
| ```bash | ||||
| ionic cordova emulate android | ||||
| ``` | ||||
| 
 | ||||
| :::caution | ||||
| When the app is loaded, a list of Presidents should be displayed. This list is | ||||
| dynamically generated by fetching and parsing a test file. | ||||
| 
 | ||||
| :::caution pass | ||||
| 
 | ||||
| In some test runs, `cordova build android --emulator` step failed with error: | ||||
| 
 | ||||
| @ -260,3 +417,40 @@ npm i --save cordova-android | ||||
| ``` | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| :::caution pass | ||||
| 
 | ||||
| In some tests, the build failed with a Gradle error: | ||||
| 
 | ||||
| ``` | ||||
| Could not find an installed version of Gradle either in Android Studio, | ||||
| or on your system to install the gradle wrapper. Please include gradle | ||||
| in your path or install Android Studio | ||||
| ``` | ||||
| 
 | ||||
| On macOS, this issue was resolved by installing gradle with Homebrew manager: | ||||
| 
 | ||||
| ```bash | ||||
| brew install gradle | ||||
| ``` | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| :::warning pass | ||||
| 
 | ||||
| When the demo was last tested on Android, reading files worked as expected. | ||||
| However, the generated files were not externally visible from the Files app. | ||||
| 
 | ||||
| **This is a known bug with Android SDK 33 and the underlying file plugins!** | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| [^1]: See ["Array of Arrays" in the API reference](/docs/api/utilities/array#array-of-arrays) | ||||
| [^2]: See [`ion-grid`](https://ionicframework.com/docs/api/grid) in the Ionic documentation. | ||||
| [^3]: See [`ngFor`](https://angular.io/api/common/NgFor) in the Angular documentation. | ||||
| [^4]: See [`read` in "Reading Files"](/docs/api/parse-options) | ||||
| [^5]: See ["Array Output" in "Utility Functions"](/docs/api/utilities/array#array-output) | ||||
| [^6]: See [`aoa_to_sheet` in "Utilities"](/docs/api/utilities/array#array-of-arrays-input) | ||||
| [^7]: See ["Workbook Helpers" in "Utilities"](/docs/api/utilities/wb) for details on `book_new` and `book_append_sheet`. | ||||
| [^8]: See [`write` in "Writing Files"](/docs/api/write-options) | ||||
| [^9]: See ["Developing for iOS"](https://ionicframework.com/docs/v6/developing/ios) and ["Developing for Android"](https://ionicframework.com/docs/v6/developing/android) in the v6 Ionic framework documentation. | ||||
| @ -200,7 +200,7 @@ after the `Permissions` comment: | ||||
| 
 | ||||
| <!-- highlight-start --> | ||||
|     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> | ||||
|     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> | ||||
|     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> | ||||
| <!-- highlight-end --> | ||||
| 
 | ||||
|     <uses-permission android:name="android.permission.INTERNET" /> | ||||
|  | ||||
							
								
								
									
										11
									
								
								docz/static/dom/SheetJSDOM.js
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										11
									
								
								docz/static/dom/SheetJSDOM.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,11 @@ | ||||
| const XLSX = require("xlsx"); | ||||
| const { readFileSync } = require("fs"); | ||||
| const { JSDOM } = require("jsdom"); | ||||
| 
 | ||||
| /* obtain HTML string.  This example reads from SheetJSTable.html */ | ||||
| const html_str = readFileSync("SheetJSTable.html", "utf8"); | ||||
| /* get first TABLE element */ | ||||
| const doc = new JSDOM(html_str).window.document.querySelector("table"); | ||||
| /* generate workbook */ | ||||
| const workbook = XLSX.utils.table_to_book(doc); | ||||
| XLSX.writeFile(workbook, "SheetJSDOM.xlsx"); | ||||
							
								
								
									
										23
									
								
								docz/static/dom/SheetJSHappyDOM.js
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										23
									
								
								docz/static/dom/SheetJSHappyDOM.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,23 @@ | ||||
| const XLSX = require("xlsx"); | ||||
| const { readFileSync } = require("fs"); | ||||
| const { Window } = require("happy-dom"); | ||||
| 
 | ||||
| /* obtain HTML string.  This example reads from SheetJSTable.html */ | ||||
| const html_str = readFileSync("SheetJSTable.html", "utf8"); | ||||
| 
 | ||||
| /* get table element */ | ||||
| const window = new Window({ | ||||
|     url: "https://localhost:8080", | ||||
|     width: 1024, | ||||
|     height: 768 | ||||
| }); | ||||
| window.document.body.innerHTML = html_str; | ||||
| const tbl = window.document.body.getElementsByTagName("table")[0]; | ||||
| 
 | ||||
| /* add `rows` and `cells` properties */ | ||||
| tbl.rows = Array.from(tbl.getElementsByTagName("tr")); | ||||
| tbl.rows.forEach(row => row.cells = Array.from(row.getElementsByTagName("td"))) | ||||
| 
 | ||||
| /* generate workbook */ | ||||
| const workbook = XLSX.utils.table_to_book(tbl); | ||||
| XLSX.writeFile(workbook, "SheetJSHappyDOM.xlsx"); | ||||
							
								
								
									
										20
									
								
								docz/static/dom/SheetJSXMLDOM.js
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										20
									
								
								docz/static/dom/SheetJSXMLDOM.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,20 @@ | ||||
| const XLSX = require("xlsx"); | ||||
| const { DOMParser, XMLSerializer } = require("@xmldom/xmldom"); | ||||
| 
 | ||||
| (async() => { | ||||
| const text = await (await fetch('https://docs.sheetjs.com/dom/SheetJSTable.html')).text(); | ||||
| const doc = new DOMParser().parseFromString( text, "text/html"); | ||||
| const tbl = doc.getElementsByTagName("table")[0]; | ||||
| 
 | ||||
| /* patch XMLDOM */ | ||||
| tbl.rows = Array.from(tbl.getElementsByTagName("tr")); | ||||
| tbl.rows.forEach(row => row.cells = Array.from(row.getElementsByTagName("td"))); | ||||
| Object.defineProperty(tbl.__proto__, "innerHTML", { get: function() { | ||||
|     var outerHTML = new XMLSerializer().serializeToString(this); | ||||
|     if(outerHTML.match(/</g).length == 1) return ""; | ||||
|     return outerHTML.slice(0, outerHTML.lastIndexOf("</")).replace(/<[^"'>]*(("[^"]*"|'[^']*')[^"'>]*)*>/, ""); | ||||
| }}); | ||||
| 
 | ||||
| const workbook = XLSX.utils.table_to_book(tbl); | ||||
| XLSX.writeFile(workbook, "SheetJSXMLDOM.xlsx"); | ||||
| })(); | ||||
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 46 KiB | 
| @ -1,7 +1,7 @@ | ||||
| /* sheetjs (C) 2013-present SheetJS -- https://sheetjs.com */ | ||||
| /* vim: set ts=2: */ | ||||
| import { Component, OnInit } from '@angular/core'; | ||||
| import { File } from '@ionic-native/file/ngx'; | ||||
| import { File } from '@awesome-cordova-plugins/file/ngx'; | ||||
| import * as XLSX from 'xlsx'; | ||||
| 
 | ||||
| type AOA = any[][]; | ||||
| @ -42,7 +42,7 @@ type AOA = any[][]; | ||||
| }) | ||||
| 
 | ||||
| export class HomePage implements OnInit { | ||||
|   data: any[][] = [[1,2,3],[4,5,6]]; | ||||
|   data: AOA = [[1,2,3],[4,5,6]]; | ||||
|   constructor(public file: File) {} | ||||
| 
 | ||||
|   async ngOnInit(): Promise<void> { | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user