forked from sheetjs/docs.sheetjs.com
		
	drash
This commit is contained in:
		
							parent
							
								
									913538b2ed
								
							
						
					
					
						commit
						08b7493c34
					
				| @ -417,7 +417,7 @@ 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 properies 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 | ||||
|  | ||||
| @ -223,9 +223,7 @@ var dataset = Float32Array.from(column); | ||||
| 
 | ||||
| `XLSX.utils.aoa_to_sheet` can generate a worksheet from an array of arrays. | ||||
| ML libraries typically provide APIs to pull an array of arrays, but it will | ||||
| be transponsed | ||||
| a row-major array of arrays.  To export multiple data | ||||
| sets, "transpose" the data: | ||||
| be transposed. To export multiple data sets, manually "transpose" the data: | ||||
| 
 | ||||
| ```js | ||||
| /* assuming data is an array of typed arrays */ | ||||
|  | ||||
| @ -7,7 +7,7 @@ import current from '/version.js'; | ||||
| 
 | ||||
| Over the years, many frameworks have been released. Some were popular years ago | ||||
| but have waned in recent years. There are still many deployments using these | ||||
| frameworks and it is oftentimes esasier to continue maintenance than to rewrite | ||||
| frameworks and it is oftentimes easier to continue maintenance than to rewrite | ||||
| using modern web techniques. | ||||
| 
 | ||||
| SheetJS libraries strive to maintain broad browser and JS engine compatibility. | ||||
|  | ||||
| @ -1370,6 +1370,15 @@ id,content | ||||
| 
 | ||||
| ## Ionic | ||||
| 
 | ||||
| :::note | ||||
| 
 | ||||
| This demo was tested on an Intel Mac on 2022 August 18 with Cordova backend. | ||||
| The file integration uses `@ionic-native/file` version `5.36.0`. | ||||
| 
 | ||||
| The iOS simulator runs iOS 15.5 on an iPod Touch 7th Gen. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| :::warning Telemetry | ||||
| 
 | ||||
| Before starting this demo, manually disable telemetry.  On Linux and macOS: | ||||
| @ -1400,8 +1409,7 @@ npx @capacitor/cli telemetry | ||||
| 
 | ||||
| :::caution | ||||
| 
 | ||||
| The latest version of Ionic uses CapacitorJS. These notes are for older apps | ||||
| using Cordova | ||||
| The latest version of Ionic uses CapacitorJS. These notes are for Cordova apps. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| @ -1432,3 +1440,77 @@ let blob = new Blob([wbout], {type: 'application/octet-stream'}); | ||||
| this.file.writeFile(url, filename, blob, {replace: true}); | ||||
| ``` | ||||
| 
 | ||||
| ### Demo | ||||
| 
 | ||||
| The demo uses Cordova. | ||||
| 
 | ||||
| <details><summary><b>Complete Example</b> (click to show)</summary> | ||||
| 
 | ||||
| 0) Disable telemetry as noted in the warning. | ||||
| 
 | ||||
| Install required global dependencies: | ||||
| 
 | ||||
| ```bash | ||||
| npm i -g cordova-res @angular/cli native-run | ||||
| ``` | ||||
| 
 | ||||
| Follow the [React Native demo](#demo) to ensure iOS and Android sims are ready. | ||||
| 
 | ||||
| 
 | ||||
| 1) Create a new project: | ||||
| 
 | ||||
| ```bash | ||||
| npx @ionic/cli start SheetJSIonic blank --type angular --cordova --quiet --no-git --no-link --confirm | ||||
| ``` | ||||
| 
 | ||||
| If a prompt discusses Cordova and Capacitor, enter `Yes` to continue. | ||||
| 
 | ||||
| If a prompt asks about creating an Ionic account, enter `N` to opt out. | ||||
| 
 | ||||
| 2) Set up Cordova: | ||||
| 
 | ||||
| ```bash | ||||
| npx @ionic/cli cordova platform add ios --confirm | ||||
| npx @ionic/cli cordova plugin add cordova-plugin-file | ||||
| npm install --save @ionic-native/core @ionic-native/file @ionic/cordova-builders | ||||
| ``` | ||||
| 
 | ||||
| 3) Install dependencies: | ||||
| 
 | ||||
| ```bash | ||||
| npm install --save https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz | ||||
| ``` | ||||
| 
 | ||||
| 4) Add `@ionic-native/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'; | ||||
| 
 | ||||
| @NgModule({ | ||||
|   declarations: [AppComponent], | ||||
|   imports: [BrowserModule, IonicModule.forRoot(), AppRoutingModule], | ||||
| 
 | ||||
|   // highlight-next-line | ||||
|   providers: [File, { provide: RouteReuseStrategy, useClass: IonicRouteStrategy }], | ||||
|   bootstrap: [AppComponent], | ||||
| }) | ||||
| export class AppModule {} | ||||
| ``` | ||||
| 
 | ||||
| 5) 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 | ||||
| ``` | ||||
| 
 | ||||
| 6) Test the app: | ||||
| 
 | ||||
| ```bash | ||||
| npx @ionic/cli cordova emulate ios | ||||
| ``` | ||||
| 
 | ||||
| </details> | ||||
| @ -71,15 +71,13 @@ console.log(data); | ||||
| A component will typically map over the data. The following example generates | ||||
| a TABLE with a row for each President: | ||||
| 
 | ||||
| ```tsx title="src/SheetJSReactAoO.tsx" | ||||
| import React, { useEffect, useState } from "react"; | ||||
| import { read, utils } from 'xlsx'; | ||||
| 
 | ||||
| interface President { Name: string; Index: number; } | ||||
| ```jsx title="src/SheetJSReactAoO.js" | ||||
| import React, { useCallback, useEffect, useState } from "react"; | ||||
| import { read, utils, writeFileXLSX } from 'xlsx'; | ||||
| 
 | ||||
| export default function SheetJSReactAoO() { | ||||
|   /* the component state is an array of presidents */ | ||||
|   const [pres, setPres] = useState<President[]>([]); | ||||
|   const [pres, setPres] = useState([]); | ||||
| 
 | ||||
|   /* Fetch and update the state once */ | ||||
|   useEffect(() => { (async() => { | ||||
| @ -87,11 +85,20 @@ export default function SheetJSReactAoO() { | ||||
|     // highlight-start | ||||
|     const wb = read(f); // parse the array buffer | ||||
|     const ws = wb.Sheets[wb.SheetNames[0]]; // get the first worksheet | ||||
|     const data = utils.sheet_to_json<President>(ws); // generate objects | ||||
|     const data = utils.sheet_to_json(ws); // generate objects | ||||
|     setPres(data); // update state | ||||
|     // highlight-end | ||||
|   })(); }, []); | ||||
| 
 | ||||
|   /* get state data and export to XLSX */ | ||||
|   const exportFile = useCallback(() => { | ||||
|     // highlight-next-line | ||||
|     const ws = utils.json_to_sheet(pres); | ||||
|     const wb = utils.book_new(); | ||||
|     utils.book_append_sheet(wb, ws, "Data"); | ||||
|     writeFileXLSX(wb, "SheetJSReactAoO.xlsx"); | ||||
|   }, [pres]); | ||||
| 
 | ||||
|   return (<table><thead><th>Name</th><th>Index</th></thead><tbody> | ||||
|     { /* generate row for each president */ | ||||
| // highlight-start | ||||
| @ -101,7 +108,9 @@ export default function SheetJSReactAoO() { | ||||
|       </tr>)) | ||||
| // highlight-end | ||||
|     } | ||||
|   </tbody></table>); | ||||
|   </tbody><tfoot><td colSpan={2}> | ||||
|     <button onClick={exportFile}>Export XLSX</button> | ||||
|   </td></tfoot></table>); | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| @ -115,13 +124,15 @@ The `sheet_to_html` function generates HTML that is aware of merges and other | ||||
| worksheet features.  React `dangerouslySetInnerHTML` attribute allows code to | ||||
| set the `innerHTML` attribute, effectively inserting the code into the page: | ||||
| 
 | ||||
| ```tsx title="src/SheetJSReactHTML.tsx" | ||||
| import React, { useEffect, useState } from "react"; | ||||
| import { read, utils } from 'xlsx'; | ||||
| ```jsx title="src/SheetJSReactHTML.js" | ||||
| import React, { useCallback, useEffect, useRef, useState } from "react"; | ||||
| import { read, utils, writeFileXLSX } from 'xlsx'; | ||||
| 
 | ||||
| export default function SheetJSReactHTML() { | ||||
|   /* the component state is an HTML string */ | ||||
|   const [html, setHtml] = useState<string>(""); | ||||
|   const [html, setHtml] = useState(""); | ||||
|   /* the ref is used in export */ | ||||
|   const tbl = useRef(null); | ||||
| 
 | ||||
|   /* Fetch and update the state once */ | ||||
|   useEffect(() => { (async() => { | ||||
| @ -134,8 +145,20 @@ export default function SheetJSReactHTML() { | ||||
|     // highlight-end | ||||
|   })(); }, []); | ||||
| 
 | ||||
|   /* get live table and export to XLSX */ | ||||
|   const exportFile = useCallback(() => { | ||||
|     // highlight-start | ||||
|     const elt = tbl.current.getElementsByTagName("TABLE")[0]; | ||||
|     const wb = utils.table_to_book(elt); | ||||
|     // highlight-end | ||||
|     writeFileXLSX(wb, "SheetJSReactHTML.xlsx"); | ||||
|   }, [tbl]); | ||||
| 
 | ||||
|   return ( <> | ||||
|     <button onClick={exportFile}>Export XLSX</button> | ||||
|   // highlight-next-line | ||||
|   return ( <div dangerouslySetInnerHTML={{ __html: html }} />); | ||||
|     <div ref={tbl} dangerouslySetInnerHTML={{ __html: html }} /> | ||||
|   </>); | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
|  | ||||
| @ -74,7 +74,7 @@ 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'; | ||||
| import { read, utils, writeFileXLSX } from 'xlsx'; | ||||
| 
 | ||||
| const rows = ref([]); | ||||
| 
 | ||||
| @ -89,6 +89,14 @@ onMounted(async() => { | ||||
|   /* update data */ | ||||
|   rows.value = utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]]); | ||||
| }); | ||||
| 
 | ||||
| /* get state data and export to XLSX */ | ||||
| function exportFile() { | ||||
|   const ws = utils.json_to_sheet(rows.value); | ||||
|   const wb = utils.book_new(); | ||||
|   utils.book_append_sheet(wb, ws, "Data"); | ||||
|   writeFileXLSX(wb, "SheetJSVueAoO.xlsx"); | ||||
| } | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
| @ -97,7 +105,9 @@ onMounted(async() => { | ||||
|       <td>{{ row.Name }}</td> | ||||
|       <td>{{ row.Index }}</td> | ||||
|     </tr> | ||||
|   </tbody></table> | ||||
|   </tbody><tfoot><td colSpan={2}> | ||||
|     <button @click="exportFile">Export XLSX</button> | ||||
|   </td></tfoot></table> | ||||
| </template> | ||||
| ``` | ||||
| 
 | ||||
| @ -114,9 +124,10 @@ 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'; | ||||
| import { read, utils, writeFileXLSX } from 'xlsx'; | ||||
| 
 | ||||
| const html = ref(""); | ||||
| const tableau = ref(); | ||||
| 
 | ||||
| onMounted(async() => { | ||||
|   /* Download from https://sheetjs.com/pres.numbers */ | ||||
| @ -129,10 +140,17 @@ onMounted(async() => { | ||||
|   /* update data */ | ||||
|   html.value = utils.sheet_to_html(wb.Sheets[wb.SheetNames[0]]); | ||||
| }); | ||||
| 
 | ||||
| /* get live table and export to XLSX */ | ||||
| function exportFile() { | ||||
|   const wb = utils.table_to_book(tableau.value.getElementsByTagName("TABLE")[0]) | ||||
|   writeFileXLSX(wb, "SheetJSVueHTML.xlsx"); | ||||
| } | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <div v-html="html"></div> | ||||
|   <button @click="exportFile">Export XLSX</button> | ||||
| </template> | ||||
| ``` | ||||
| 
 | ||||
|  | ||||
| @ -16,7 +16,7 @@ and TypeScript familiarity is assumed. | ||||
| Other demos cover general Angular deployments, including: | ||||
| 
 | ||||
| - [iOS and Android applications powered by NativeScript](./mobile#nativescript) | ||||
| - [iOS and Android applications powered by ionic](./mobile#nativescript) | ||||
| - [iOS and Android applications powered by ionic](./mobile#ionic) | ||||
| 
 | ||||
| :::warning | ||||
| 
 | ||||
| @ -80,12 +80,12 @@ console.log(data); | ||||
| ] | ||||
| ``` | ||||
| 
 | ||||
| A component will typically loop over the data uaing `*ngFor`. The following | ||||
| A component will typically loop over the data using `*ngFor`. The following | ||||
| example generates a TABLE with a row for each President: | ||||
| 
 | ||||
| ```ts title="src/app/app.component.ts" | ||||
| import { Component } from '@angular/core'; | ||||
| import { read, utils } from 'xlsx'; | ||||
| import { read, utils, writeFileXLSX } from 'xlsx'; | ||||
| 
 | ||||
| interface President { Name: string; Index: number }; | ||||
| 
 | ||||
| @ -101,7 +101,9 @@ interface President { Name: string; Index: number }; | ||||
|       <td>{{row.Index}}</td> | ||||
|     </tr> | ||||
| // highlight-end | ||||
|   </tbody> | ||||
|   </tbody><tfoot> | ||||
|     <button (click)="onSave()">Export XLSX</button> | ||||
|   </tfoot> | ||||
| </table></div> | ||||
| ` | ||||
| }) | ||||
| @ -122,6 +124,14 @@ export class AppComponent { | ||||
|     this.rows = utils.sheet_to_json<President>(wb.Sheets[wb.SheetNames[0]]); | ||||
| 
 | ||||
|   })(); } | ||||
|   /* get state data and export to XLSX */ | ||||
|   onSave(): void { | ||||
|     // highlight-next-line | ||||
|     const ws = utils.json_to_sheet(this.rows); | ||||
|     const wb = utils.book_new(); | ||||
|     utils.book_append_sheet(wb, ws, "Data"); | ||||
|     writeFileXLSX(wb, "SheetJSAngularAoO.xlsx"); | ||||
|   } | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| @ -137,20 +147,22 @@ and should therefore be safe to pass to an `innerHTML`-bound variable, but the | ||||
| `DomSanitizer` approach is strongly recommended: | ||||
| 
 | ||||
| ```ts title="src/app/app.component.ts" | ||||
| import { Component } from '@angular/core'; | ||||
| import { Component, ElementRef, ViewChild } from '@angular/core'; | ||||
| // highlight-next-line | ||||
| import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; | ||||
| import { read, utils } from 'xlsx'; | ||||
| import { read, utils, writeFileXLSX } from 'xlsx'; | ||||
| 
 | ||||
| @Component({ | ||||
|   selector: 'app-root', | ||||
| // highlight-next-line | ||||
|   template: `<div class="content" role="main" [innerHTML]="html"></div>` | ||||
|   template: `<div class="content" role="main" [innerHTML]="html" #tableau></div> | ||||
|     <button (click)="onSave()">Export XLSX</button>` | ||||
| }) | ||||
| export class AppComponent { | ||||
|   // highlight-start | ||||
|   constructor(private sanitizer: DomSanitizer) {} | ||||
|   html: SafeHtml = ""; | ||||
|   @ViewChild('tableau') tabeller!: ElementRef<HTMLDivElement>; | ||||
|   // highlight-end | ||||
|   ngOnInit(): void { (async() => { | ||||
|     /* Download from https://sheetjs.com/pres.numbers */ | ||||
| @ -166,6 +178,14 @@ export class AppComponent { | ||||
|     this.html = this.sanitizer.bypassSecurityTrustHtml(h); | ||||
|     // highlight-end | ||||
|   })(); } | ||||
|   /* get live table and export to XLSX */ | ||||
|   onSave(): void { | ||||
|     // highlight-start | ||||
|     const elt = this.tabeller.nativeElement.getElementsByTagName("TABLE")[0]; | ||||
|     const wb = utils.table_to_book(elt); | ||||
|     // highlight-end | ||||
|     writeFileXLSX(wb, "SheetJSAngularHTML.xlsx"); | ||||
|   } | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										225
									
								
								docz/docs/03-demos/24-server.md
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										225
									
								
								docz/docs/03-demos/24-server.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,225 @@ | ||||
| --- | ||||
| sidebar_position: 23 | ||||
| title: HTTP Server Processing | ||||
| --- | ||||
| 
 | ||||
| Server-Side JS platforms like NodeJS and Deno have built-in APIs for listening | ||||
| on network interfaces.  They provide wrappers for requests and responses. | ||||
| 
 | ||||
| ## Overview | ||||
| 
 | ||||
| #### Reading Data | ||||
| 
 | ||||
| Typically servers receive form data with content type `multipart/form-data` or | ||||
| `application/x-www-form-urlencoded`. The platforms themselves typically do not | ||||
| provide "body parsing" functions, instead leaning on the community to supply | ||||
| modules to take the encoded data and split into form fields and files. | ||||
| 
 | ||||
| NodeJS servers typically use a parser like `formidable`. In the example below, | ||||
| `formidable` will write to file and `XLSX.readFile` will read the file: | ||||
| 
 | ||||
| ```js | ||||
| var XLSX = require("xlsx"); // This is using the CommonJS build | ||||
| var formidable = require("formidable"); | ||||
| 
 | ||||
| require("http").createServer(function(req, res) { | ||||
|   if(req.method !== "POST") return res.end(""); | ||||
| 
 | ||||
|   /* parse body and implement logic in callback */ | ||||
|   // highlight-next-line | ||||
|   (new formidable.IncomingForm()).parse(req, function(err, fields, files) { | ||||
|     /* if successful, files is an object whose keys are param names */ | ||||
|     // highlight-next-line | ||||
|     var file = files["upload"]; // <input type="file" id="upload" name="upload"> | ||||
|     /* file.path is a location in the filesystem, usually in a temp folder */ | ||||
|     // highlight-next-line | ||||
|     var wb = XLSX.readFile(file.filepath); | ||||
|     // print the first worksheet back as a CSV | ||||
|     res.end(XLSX.utils.sheet_to_csv(wb.Sheets[wb.SheetNames[0]])); | ||||
|   }); | ||||
| }).listen(process.env.PORT || 3000); | ||||
| ``` | ||||
| 
 | ||||
| `XLSX.read` will accept NodeJS buffers as well as `Uint8Array`, Base64 strings, | ||||
| binary strings, and plain Arrays of bytes.  This covers the interface types of | ||||
| a wide variety of frameworks. | ||||
| 
 | ||||
| #### Writing Data | ||||
| 
 | ||||
| Typically server libraries use a response API that accepts `Uint8Array` data. | ||||
| `XLSX.write` with the option `type: "buffer"` will generate data.  To force the | ||||
| response to be treated as an attachment, set the `Content-Disposition` header: | ||||
| 
 | ||||
| ```js | ||||
| var XLSX = require("xlsx"); // This is using the CommonJS build | ||||
| 
 | ||||
| require("http").createServer(function(req, res) { | ||||
|   if(req.method !== "GET") return res.end(""); | ||||
|   var wb = XLSX.read("S,h,e,e,t,J,S\n5,4,3,3,7,9,5", {type: "binary"}); | ||||
|   // highlight-start | ||||
|   res.setHeader('Content-Disposition', 'attachment; filename="SheetJS.xlsx"'); | ||||
|   res.end(XLSX.write(wb, {type:"buffer", bookType: "xlsx"})); | ||||
|   // highlight-end | ||||
| }).listen(process.env.PORT || 3000); | ||||
| ``` | ||||
| 
 | ||||
| ## Deno | ||||
| 
 | ||||
| :::warning | ||||
| 
 | ||||
| Many hosted services like Deno Deploy do not offer filesystem access. | ||||
| 
 | ||||
| This breaks web frameworks that use the filesystem in body parsing. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| Deno provides the basic elements to implement a server.  It does not provide a | ||||
| body parser out of the box. | ||||
| 
 | ||||
| ### Drash | ||||
| 
 | ||||
| In testing, [Drash](https://drash.land/drash/) had an in-memory body parser | ||||
| which could handle file uploads on hosted services like Deno Deploy. | ||||
| 
 | ||||
| The service <https://s2c.sheetjs.com> is hosted on Deno Deploy using Drash! | ||||
| 
 | ||||
| _Reading Data_ | ||||
| 
 | ||||
| `Request#bodyParam` reads body parameters.  For uploaded files, the `content` | ||||
| property is a `Uint8Array`: | ||||
| 
 | ||||
| ```ts | ||||
| // @deno-types="https://cdn.sheetjs.com/xlsx-latest/package/types/index.d.ts" | ||||
| import { read, utils } from 'https://cdn.sheetjs.com/xlsx-latest/package/xlsx.mjs'; | ||||
| 
 | ||||
| import * as Drash from "https://deno.land/x/drash@v2.5.4/mod.ts"; | ||||
| 
 | ||||
| class ParseResource extends Drash.Resource { | ||||
|   public paths = ["/"]; | ||||
| 
 | ||||
|   public POST(request: Drash.Request, response: Drash.Response) { | ||||
|     // assume a form upload like <input type="file" id="upload" name="upload"> | ||||
|     // highlight-next-line | ||||
|     const file = request.bodyParam<Drash.Types.BodyFile>("upload"); | ||||
|     if (!file) throw new Error("File is required!"); | ||||
|     // highlight-next-line | ||||
|     var wb = read(file.content, {type: "buffer"}); | ||||
|     return response.html( utils.sheet_to_html(wb.Sheets[wb.SheetNames[0]])); | ||||
|   } | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| _Writing Data_ | ||||
| 
 | ||||
| Headers are manually set with `Response#headers.set` while the raw body is set | ||||
| with `Response#send`: | ||||
| 
 | ||||
| ```ts | ||||
| // @deno-types="https://cdn.sheetjs.com/xlsx-latest/package/types/index.d.ts" | ||||
| import { read, utils } from 'https://cdn.sheetjs.com/xlsx-latest/package/xlsx.mjs'; | ||||
| 
 | ||||
| import * as Drash from "https://deno.land/x/drash@v2.5.4/mod.ts"; | ||||
| 
 | ||||
| class WriteResource extends Drash.Resource { | ||||
|   public paths = ["/export"]; | ||||
| 
 | ||||
|   public GET(request: Drash.Request, response: Drash.Response): void { | ||||
|     // create some fixed workbook | ||||
|     const data = ["SheetJS".split(""), [5,4,3,3,7,9,5]]; | ||||
|     const ws = utils.aoa_to_sheet(data); | ||||
|     const wb = utils.book_new(); utils.book_append_sheet(wb, ws, "data"); | ||||
|     // write the workbook to XLSX as a Uint8Array | ||||
|     // highlight-next-line | ||||
|     const file = write(wb, { bookType: "xlsx", type: "buffer"}); | ||||
|     // set headers | ||||
|     response.headers.set("Content-Disposition", 'attachment; filename="SheetJSDrash.xlsx"'); | ||||
|     // send data | ||||
|     // highlight-next-line | ||||
|     return response.send("application/vnd.ms-excel", file); | ||||
|   } | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| <details><summary><b>Complete Example</b> (click to show)</summary> | ||||
| 
 | ||||
| 1) Save the following script to `SheetJSDrash.ts`: | ||||
| 
 | ||||
| ```ts title="SheetJSDrash.ts" | ||||
| /*! sheetjs (C) 2013-present SheetJS -- http://sheetjs.com */ | ||||
| // @deno-types="https://cdn.sheetjs.com/xlsx-latest/package/types/index.d.ts" | ||||
| import { read, utils, set_cptable } from 'https://cdn.sheetjs.com/xlsx-latest/package/xlsx.mjs'; | ||||
| import * as cptable from 'https://cdn.sheetjs.com/xlsx-latest/package/dist/cpexcel.full.mjs'; | ||||
| set_cptable(cptable); | ||||
| 
 | ||||
| import * as Drash from "https://deno.land/x/drash@v2.5.4/mod.ts"; | ||||
| 
 | ||||
| class ParseResource extends Drash.Resource { | ||||
|   public paths = ["/"]; | ||||
| 
 | ||||
|   public POST(request: Drash.Request, response: Drash.Response) { | ||||
|     const file = request.bodyParam<Drash.Types.BodyFile>("file"); | ||||
|     if (!file) throw new Error("File is required!"); | ||||
|     var wb = read(file.content, {type: "buffer"}); | ||||
|     return response.html( utils.sheet_to_html(wb.Sheets[wb.SheetNames[0]])); | ||||
|   } | ||||
| 
 | ||||
|   public GET(request: Drash.Request, response: Drash.Response): void { | ||||
|     return response.html(`\ | ||||
| <!DOCTYPE html> | ||||
| <html> | ||||
|   <head> | ||||
|     <title>SheetJS Spreadsheet to HTML Conversion Service</title> | ||||
|     <meta charset="utf-8" /> | ||||
|   </head> | ||||
|   <body> | ||||
| <pre><h3><a href="//sheetjs.com/">SheetJS</a> Spreadsheet Conversion Service</h3> | ||||
| <b>API</b> | ||||
| 
 | ||||
| Send a POST request to https://s2c.sheetjs.com/ with the file in the "file" body parameter: | ||||
| 
 | ||||
| $ curl -X POST -F"file=@test.xlsx" https://s2c.sheetjs.com/ | ||||
| 
 | ||||
| The response will be an HTML TABLE generated from the first worksheet. | ||||
| 
 | ||||
| <b>Try it out!</b><form action="/" method="post" enctype="multipart/form-data"> | ||||
| 
 | ||||
| <input type="file" name="file" /> | ||||
| 
 | ||||
| Use the file input element to select a file, then click "Submit" | ||||
| 
 | ||||
| <button type="submit">Submit</button> | ||||
| </form> | ||||
| </pre> | ||||
|   </body> | ||||
| </html>`, | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| const server = new Drash.Server({ | ||||
|   hostname: "", | ||||
|   port: 3000, | ||||
|   protocol: "http", | ||||
|   resources: [ ParseResource ], | ||||
| }); | ||||
| 
 | ||||
| server.run(); | ||||
| 
 | ||||
| console.log(`Server running at ${server.address}.`); | ||||
| ``` | ||||
| 
 | ||||
| 2) Run the server: | ||||
| 
 | ||||
| ```bash | ||||
| deno run --allow-net SheetJSDrash.ts | ||||
| ``` | ||||
| 
 | ||||
| 3) Download the test file <https://sheetjs.com/pres.numbers> | ||||
| 
 | ||||
| 4) Open http://localhost:3000/ in your browser. | ||||
| 
 | ||||
| Click "Choose File" and select `pres.numbers`.  Then click "Submit" | ||||
| 
 | ||||
| The page should show the contents of the file as an HTML table. | ||||
| 
 | ||||
| </details> | ||||
| @ -37,6 +37,7 @@ The demo projects include small runnable examples and short explainers. | ||||
| - [`Command-Line Tools`](./cli) | ||||
| - [`iOS and Android Mobile Applications`](./mobile) | ||||
| - [`NodeJS Server-Side Processing`](https://github.com/SheetJS/SheetJS/tree/master/demos/server/) | ||||
| - [`Deno Server-Side Processing`](./server#deno) | ||||
| - [`Content Management and Static Sites`](./content) | ||||
| - [`Electron`](./desktop#electron) | ||||
| - [`NW.js`](./desktop#nwjs) | ||||
|  | ||||
| @ -48,7 +48,7 @@ then `AAA`.  Some sample values, along with SheetJS column indices, are listed: | ||||
| | Second  | `B`     |     `1` | | ||||
| | 26th    | `Z`     |    `25` | | ||||
| | 27th    | `AA`    |    `26` | | ||||
| | 702st   | `ZZ`    |   `701` | | ||||
| | 702nd   | `ZZ`    |   `701` | | ||||
| | 703rd   | `AAA`   |   `702` | | ||||
| | 16384th | `XFD`   | `16383` | | ||||
| 
 | ||||
|  | ||||
| @ -98,8 +98,8 @@ pixels.  When the pixel and character counts do not align, Excel rounds values. | ||||
| XLSX internally stores column widths in a nebulous "Max Digit Width" form.  The | ||||
| Max Digit Width is the width of the largest digit when rendered (generally the | ||||
| "0" character is the widest).  The internal width must be an integer multiple of | ||||
| the the width divided by 256.  ECMA-376 describes a formula for converting | ||||
| between pixels and the internal width.  This represents a hybrid approach. | ||||
| the width divided by 256.  ECMA-376 describes a formula for converting between | ||||
| pixels and the internal width.  This represents a hybrid approach. | ||||
| 
 | ||||
| Read functions attempt to populate all three properties.  Write functions will | ||||
| try to cycle specified values to the desired type.  In order to avoid potential | ||||
|  | ||||
| @ -269,7 +269,7 @@ function SheetJSHeaderOrder() { | ||||
| 
 | ||||
| ### HTML Table Input | ||||
| 
 | ||||
| **Create a worksheet or workbook from an HTML DOM TABLE** | ||||
| **Create a worksheet or workbook from a HTML DOM TABLE** | ||||
| 
 | ||||
| ```js | ||||
| var ws = XLSX.utils.table_to_sheet(elt, opts); | ||||
|  | ||||
							
								
								
									
										125
									
								
								docz/static/ionic/home.page.ts
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										125
									
								
								docz/static/ionic/home.page.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,125 @@ | ||||
| /* sheetjs (C) 2013-present SheetJS -- http://sheetjs.com */ | ||||
| /* vim: set ts=2: */ | ||||
| import { Component } from '@angular/core'; | ||||
| import { File } from '@ionic-native/file/ngx'; | ||||
| import * as XLSX from 'xlsx'; | ||||
| 
 | ||||
| type AOA = any[][]; | ||||
| 
 | ||||
| @Component({ | ||||
|   selector: 'app-home', | ||||
|   //templateUrl: 'home.page.html',
 | ||||
|   styleUrls: ['home.page.scss'], | ||||
|   template: ` | ||||
| <ion-header> | ||||
|   <ion-toolbar> | ||||
|     <ion-title>SheetJS Ionic Demo</ion-title> | ||||
|   </ion-toolbar> | ||||
| </ion-header> | ||||
| 
 | ||||
| <ion-content [fullscreen]="true"> | ||||
|   <ion-header collapse="condense"> | ||||
|     <ion-toolbar> | ||||
|       <ion-title>SheetJS Demo</ion-title> | ||||
|     </ion-toolbar> | ||||
|   </ion-header> | ||||
| 
 | ||||
|   <ion-grid> | ||||
|     <ion-row *ngFor="let row of data"> | ||||
|       <ion-col *ngFor="let val of row"> | ||||
|         {{val}} | ||||
|       </ion-col> | ||||
|     </ion-row> | ||||
|   </ion-grid> | ||||
| </ion-content> | ||||
| 
 | ||||
| <ion-footer padding> | ||||
|   <input type="file" (change)="onFileChange($event)" multiple="false" /> | ||||
|   <button ion-button color="secondary" (click)="import()">Import Data</button> | ||||
|   <button ion-button color="secondary" (click)="export()">Export Data</button> | ||||
| </ion-footer> | ||||
| ` | ||||
| }) | ||||
| 
 | ||||
| export class HomePage { | ||||
|   data: any[][] = [[1,2,3],[4,5,6]]; | ||||
|   constructor(public file: File) {} | ||||
| 
 | ||||
|   read(ab: ArrayBuffer) { | ||||
|     /* read workbook */ | ||||
|     const wb: XLSX.WorkBook = XLSX.read(new Uint8Array(ab), {type: 'array'}); | ||||
| 
 | ||||
|     /* grab first sheet */ | ||||
|     const wsname: string = wb.SheetNames[0]; | ||||
|     const ws: XLSX.WorkSheet = wb.Sheets[wsname]; | ||||
| 
 | ||||
|     /* save data */ | ||||
|     this.data = (XLSX.utils.sheet_to_json(ws, {header: 1}) as AOA); | ||||
|   }; | ||||
| 
 | ||||
|   write(): XLSX.WorkBook { | ||||
|     /* 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'); | ||||
| 
 | ||||
|     return wb; | ||||
|   }; | ||||
| 
 | ||||
|   /* File Input element for browser */ | ||||
|   onFileChange(evt: any) { | ||||
|     /* wire up file reader */ | ||||
|     const target: DataTransfer = (evt.target as DataTransfer); | ||||
|     if (target.files.length !== 1) { throw new Error('Cannot use multiple files'); } | ||||
|     const reader: FileReader = new FileReader(); | ||||
|     reader.onload = (e: any) => { | ||||
|       const ab: ArrayBuffer = e.target.result; | ||||
|       this.read(ab); | ||||
|     }; | ||||
|     reader.readAsArrayBuffer(target.files[0]); | ||||
|   }; | ||||
| 
 | ||||
|   /* Import button for mobile */ | ||||
|   async import() { | ||||
|     try { | ||||
|       const target: string = this.file.documentsDirectory || this.file.externalDataDirectory || this.file.dataDirectory || ''; | ||||
|       const dentry = await this.file.resolveDirectoryUrl(target); | ||||
|       const url: string = dentry.nativeURL || ''; | ||||
|       alert(`Attempting to read SheetJSIonic.xlsx from ${url}`); | ||||
|       const ab: ArrayBuffer = await this.file.readAsArrayBuffer(url, 'SheetJSIonic.xlsx'); | ||||
|       this.read(ab); | ||||
|     } catch(e) { | ||||
|       const m: string = e.message; | ||||
|       alert(m.match(/It was determined/) ? 'Use File Input control' : `Error: ${m}`); | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   /* Export button */ | ||||
|   async export() { | ||||
|     const wb: XLSX.WorkBook = this.write(); | ||||
|     const filename = 'SheetJSIonic.xlsx'; | ||||
|     try { | ||||
|       /* generate Blob */ | ||||
|       const wbout: ArrayBuffer = XLSX.write(wb, { bookType: 'xlsx', type: 'array' }); | ||||
| 
 | ||||
|       /* find appropriate path for mobile */ | ||||
|       const target: string = this.file.documentsDirectory || this.file.externalDataDirectory || this.file.dataDirectory || ''; | ||||
|       const dentry = await this.file.resolveDirectoryUrl(target); | ||||
|       const url: string = dentry.nativeURL || ''; | ||||
| 
 | ||||
|       /* attempt to save blob to file */ | ||||
|       await this.file.writeFile(url, filename, wbout, {replace: true}); | ||||
|       alert(`Wrote to SheetJSIonic.xlsx in ${url}`); | ||||
|     } catch(e) { | ||||
|       if(e.message.match(/It was determined/)) { | ||||
|         /* in the browser, use writeFile */ | ||||
|         XLSX.writeFile(wb, filename); | ||||
|       } else { | ||||
|         alert(`Error: ${e.message}`); | ||||
|       } | ||||
|     } | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user