From 990f42934bb6bc2b5f222577cb8efea2fc526f4b Mon Sep 17 00:00:00 2001 From: SheetJS Date: Fri, 6 Oct 2023 16:47:17 -0400 Subject: [PATCH] azure --- docz/docs/03-demos/09-cloud/02-netsuite.md | 2 +- docz/docs/03-demos/09-cloud/11-aws.md | 6 +- docz/docs/03-demos/09-cloud/12-azure.md | 571 +++++++++++++++++---- docz/docs/index.md | 120 ++++- docz/docusaurus.config.js | 1 + docz/static/azure/bloblobber.png | Bin 0 -> 25987 bytes docz/static/azure/index.js | 67 +-- docz/static/sqlite/chinook.sql | 4 +- 8 files changed, 632 insertions(+), 139 deletions(-) create mode 100644 docz/static/azure/bloblobber.png diff --git a/docz/docs/03-demos/09-cloud/02-netsuite.md b/docz/docs/03-demos/09-cloud/02-netsuite.md index 86317d5a..f7c26792 100644 --- a/docz/docs/03-demos/09-cloud/02-netsuite.md +++ b/docz/docs/03-demos/09-cloud/02-netsuite.md @@ -25,7 +25,7 @@ This demo was verified by NetSuite consultants in the following deployments: | `@NScriptType` | `@NApiVersion` | Date | |:----------------|:---------------|:-----------| | ScheduledScript | 2.1 | 2023-08-18 | -| Restlet | 2.1 | 2023-04-20 | +| Restlet | 2.1 | 2023-10-05 | | Suitelet | 2.1 | 2023-07-21 | | MapReduceScript | 2.1 | 2023-07-31 | diff --git a/docz/docs/03-demos/09-cloud/11-aws.md b/docz/docs/03-demos/09-cloud/11-aws.md index 647b2b8d..83158fb7 100644 --- a/docz/docs/03-demos/09-cloud/11-aws.md +++ b/docz/docs/03-demos/09-cloud/11-aws.md @@ -311,7 +311,7 @@ Function properties. 17) Select the "Configuration" tab and select "Permissions" in the left sidebar. -18) Scroll down to "Resource-based policy statements" and ensure that +18) Scroll down to "Resource-based policy statements" and ensure that `FunctionURLAllowPublicAccess` is listed. If no policy statements are defined, select "Add Permission" with the options: @@ -373,7 +373,7 @@ var s3 = new AWS.S3({ ``` -### Reading Data +### Downloading Data #### Fetching Files from S3 @@ -425,7 +425,7 @@ stream.on('end', function() { }); ``` -### Writing Data +### Uploading Data The SheetJS `write` method[^13] with the option `type: "buffer"` will generate NodeJS Buffers. `S3#upload` directly accepts these Buffer objects. diff --git a/docz/docs/03-demos/09-cloud/12-azure.md b/docz/docs/03-demos/09-cloud/12-azure.md index bf71e7b1..5cca6b2d 100644 --- a/docz/docs/03-demos/09-cloud/12-azure.md +++ b/docz/docs/03-demos/09-cloud/12-azure.md @@ -5,10 +5,26 @@ pagination_next: demos/extensions/index --- import current from '/version.js'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; import CodeBlock from '@theme/CodeBlock'; -Azure is a Cloud Services platform which includes traditional virtual machine -support, "Serverless Functions", cloud storage and much more. +[Azure Cloud Services](https://azure.microsoft.com/) is a Cloud Services +platform which includes traditional virtual machine support, "Serverless +Functions" and cloud storage. + +[SheetJS](https://sheetjs.com) is a JavaScript library for reading and writing +data from spreadsheets. + +This demo explores two key AWS offerings: + +- ["Azure Functions"](#azure-functions) ("Lambda") explores the serverless + computing offering. The demo creates a JavaScript function that can process + user-submitted files and generate spreadsheets. + +- ["Blob Storage"](#blob-storage) explores the cloud storage offering. The demo + uses the NodeJS connection library to read spreadsheets from storage and write + spreadsheets back to cloud storage. :::caution pass @@ -17,22 +33,77 @@ will be available in the future. ::: -This demo focuses on two key offerings: cloud storage ("Azure Blob Storage") -and the "Serverless Function" platform ("Azure Functions"). - :::note -This demo was last tested on 2023 April 29. +This demo was last tested on 2023 October 06. ::: +## Telemetry + +:::warning Telemetry + +**Each command-line tool related to Azure embeds telemetry.** + +Azure tools embed telemetry without proper disclaimer. + +::: + +It is strongly recommended to disable telemetry before working with Azure. + +#### Azure Functions Core Tools + +Azure Functions Core Tools (`func`) telemetry is controlled through the +`FUNCTIONS_CORE_TOOLS_TELEMETRY_OPTOUT` environment variable. + + + + +Add the following line to `.profile`, `.bashrc` and `.zshrc`: + +```bash +export FUNCTIONS_CORE_TOOLS_TELEMETRY_OPTOUT=1 +``` + +Close and restart the Terminal to load the changes. + + + + +Type `env` in the search bar and select "Edit the system environment variables". + +In the new window, click the "Environment Variables..." button. + +In the new window, look for the "System variables" section and click "New..." + +Set the "Variable name" to `FUNCTIONS_CORE_TOOLS_TELEMETRY_OPTOUT` and the value +to `1`. + +Click "OK" in each window (3 windows) and restart your computer. + + + + +#### Azure CLI + +Azure CLI (`az`) telemetry can be disabled using a subcommand (after installing +the CLI tool)[^1]: + +```bash +az configure -d collect_telemetry=false +``` + ## Azure Functions +The [SheetJS NodeJS module](/docs/getting-started/installation/nodejs) can be +required in Azure Functions that use the NodeJS runtime. + This discussion focuses on the "HTTP Trigger" function type. -:::info pass +:::note pass -To enable binary data processing, a setting must be changed in `function.json`: +In earlier tests, to enable binary data processing, `function.json` required a +`dataType` option: ```json title="function.json" { @@ -45,150 +116,295 @@ To enable binary data processing, a setting must be changed in `function.json`: "name": "req", ``` +In the most recent test, the template did not create a `function.json` and the +option was not required. + ::: ### Reading Data -`formidable` expects a stream and Azure does not present one. It can be made: +Using `@azure/functions`, the handler callback receives a `Request` object. With +standard JS operations, the file can be pulled into an `ArrayBuffer` object. + +The SheetJS `read` method[^2] can read the `ArrayBuffer` objects and generate +SheetJS workbook objects[^3] which can be processed with other API functions. + +For example, a handler can use `sheet_to_csv`[^4] to generate CSV text: ```js +const { Blob } = require('buffer'); +const { app } = require('@azure/functions'); const XLSX = require('xlsx'); -const formidable = require('formidable'); -const Readable = require('stream').Readable; -/* formidable expects the request object to be a stream */ -const streamify = (req) => { - if(typeof req.on !== 'undefined') return req; - const s = new Readable(); - s._read = ()=>{}; - s.push(Buffer.from(req.body)); - s.push(null); - Object.assign(s, req); - return s; -}; +app.http('SheetJSAzure', { + methods: ['POST'], + handler: async (req, context) => { + /* grab the file at form key `upload` */ + const formData = await req.formData(); + const f = formData.get("upload"); -module.exports = (context, req) => { - const form = new formidable.IncomingForm(); - form.parse(streamify(req), (err, fields, files) => { - /* grab the first file */ - var f = files["upload"]; - if(!f) { - context.res = { status: 400, body: "Must submit a file for processing!" }; - } else { - /* file is stored in a temp directory, so we can point to that and read it */ - const wb = XLSX.read(f.filepath, {type:"file"}); + if(!(f instanceof Blob)) return { status: 400, body: "Must submit a file" }; - /* generate CSV from first sheet */ - const csv = XLSX.utils.sheet_to_csv(wb.Sheets[wb.SheetNames[0]]); - context.res = { status: 200, body: csv }; - } - context.done(); - }); -} + /* parse file */ + const ab = await f.arrayBuffer(); + const wb = XLSX.read(ab); + + /* generate CSV from first sheet */ + const ws = wb.Sheets[wb.SheetNames[0]]; + const csv = XLSX.utils.sheet_to_csv(ws); + return { status: 200, body: csv }; + } +}); ``` ### Writing Data -The `body` property can be a Buffer, like those generated by `XLSX.write`: +The SheetJS `write` method[^5] with the option `type: "buffer"` will generate +NodeJS buffers which can be sent in the callback handler response. + +The following example generates a sample worksheet using the `aoa_to_sheet`[^6] +method, generates a sample workbook using worksheet helper methods[^7], writes +the workbook to XLSX format in a Buffer, and sends the Buffer in the response: ```js +const { app } = require('@azure/functions'); const XLSX = require('xlsx'); -module.exports = (context, req) => { - // generate XLSX file in a Buffer - var ws = XLSX.utils.aoa_to_sheet(["SheetJS".split(""), [5,4,3,3,7,9,5]]); - var wb = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(wb, ws, "Data"); - // highlight-next-line - var buf = XLSX.write(wb, {type: "buffer", bookType: "xlsx"}); - // Set the body and Content-Disposition header - // highlight-start - context.res = { - status: 200, - headers: { "Content-Disposition": `attachment; filename="SheetJSAzure.xlsx";` }, - body: buf - }; - // highlight-end - context.done(); -}; +app.http('SheetJSAzure', { + methods: ['GET'], + handler: async (req, context) => { + /* generate sample worksheet */ + var ws = XLSX.utils.aoa_to_sheet(["SheetJS".split(""), [5, 4, 3, 3, 7, 9, 5]]); + /* generate workbook */ + var wb = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(wb, ws, "Data"); + /* write to XLSX, returning a NodeJS Buffer */ + var buf = XLSX.write(wb, { type: "buffer", bookType: "xlsx" }); + /* send Buffer to client */ + return { + status: 200, + /* Content-Disposition header */ + headers: { "Content-Disposition": `attachment; filename="SheetJSAzure.xlsx";` }, + /* data */ + body: buf + }; + } +}); ``` -### Demo +### Functions Demo -
Complete Example (click to show) +:::note pass -0) Review the quick start for JavaScript on Azure Functions. This involves -installing the Azure Functions Core Tools and other dependencies. +At the time of writing, the Azure Free Tier included an allowance of 1 million +free requests per month. -1) Create a new project and install dependencies: +::: + +0) If you do not have an account, create a new Azure free tier account[^8]. + +#### Local Setup + +1) [Disable Azure Functions Core Tools Telemetry](#azure-functions-core-tools). + +2) Install the CLI tool using npm: + +```bash +npm i -g azure-functions-core-tools@4 --unsafe-perm true +``` + +:::note pass + +On macOS and Linux, `sudo` may be required: + +```bash +sudo npm i -g azure-functions-core-tools@4 --unsafe-perm true +``` + +::: + +3) Install [Azure CLI](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli) + +4) Disable Azure CLI telemetry: + +```bash +az configure -d collect_telemetry=false +``` + +#### Start Project + +5) Create a new JavaScript HTTP Trigger project: + +```bash +mkdir SheetJSAzure +cd SheetJSAzure +func new --template httpTrigger --language JavaScript --name SheetJSAzure +``` + +:::warning pass + +When the demo was last tested, the stock TypeScript template did not work. + +**This is a bug in the Azure Functions Core Tools** + +Until the bugs are resolved, JavaScript should be preferred over TypeScript. + +::: + +6) Start the local server: + +```bash +npm start +``` + +7) While the server is running, open a new terminal window and make a request: + +```bash +curl -L http://localhost:7071/api/SheetJSAzure +``` + +The terminal should display `Hello, world!` + +#### Add SheetJS + +8) Install the SheetJS NodeJS module: {`\ -func init sheetjs-azure --worker-runtime node --language javascript -cd sheetjs-azure -npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz formidable`} +npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`} -2) Create a new "HTTP Trigger" function: +9) Download [the sample script](pathname:///azure/index.js): ```bash -func new --template "Http Trigger" --name SheetJSAzure +curl -L -o src/functions/SheetJSAzure.js https://docs.sheetjs.com/azure/index.js ``` -3) Edit `SheetJSAzure/function.json` to add the `dataType: "binary"` property: +#### Local Test -```js title="SheetJSAzure/function.json" - "direction": "in", -// highlight-next-line - "dataType": "binary", - "name": "req", -``` - -4) Download [`SheetJSAzure/index.js`](pathname:///aws/index.js): +10) Stop and restart the dev server: ```bash -curl -L -o SheetJSAzure/index.js https://docs.sheetjs.com/azure/index.js +npm start ``` -5) Test locally with `npm start` - -To test uploads, download and run: +11) In a separate terminal window, download +and make a POST request to the dev server: ```bash curl -LO https://sheetjs.com/pres.numbers curl -X POST -F "upload=@pres.numbers" http://localhost:7071/api/SheetJSAzure ``` -To test downloads, access `http://localhost:7071/api/SheetJSAzure` and download -the generated file. Confirm it is a valid file. +If the test succeeded, the terminal will print CSV rows from the test file data. -6) Deploy to Azure. Replace `NAME_OF_FUNCTION_APP` with the name: +12) Open a web browser and access `http://localhost:7071/api/SheetJSAzure` . -```bash -func azure functionapp publish NAME_OF_FUNCTION_APP +If the test succeeded, the browser will attempt to download `SheetJSAzure.xlsx`. +Open in Excel or another spreadsheet editor to confirm the file is valid. + +#### Create Remote Function + +13) Sign into the [Azure Portal](https://portal.azure.com/#home) + +14) Type "Function App" in the top search box and click "Function App" + +15) Click "+ Create" + +16) Select the following options: + +- Type a memorable "Function Name" ("sheetjsazure" when last tested) + +- "Do you want to deploy code or container image?": select "Code" + +- "Runtime stack": select NodeJS + +- "Hosting options and plans": "Consumption (Serverless)" + +17) Click "Review + create", then click "Create" to create the function. + +The page will display a status message + +> ... Deployment is in progress + +When the resources are configured, the status will change to + +> Your deployment is complete + +18) Click "Go to Resource". + +19) Take note of the URL from the table + +#### Deploy to Azure + +20) Sign into Azure: + +``` +az login ``` -Get the function URL and test using the same sequence as in step 5. +The login flow resumes in the browser. -
+21) Deploy to Azure. Replace `FUNCTION_NAME` with the name from Step 16: -## Azure Blob Storage +```bash +func azure functionapp publish FUNCTION_NAME +``` + +After publishing, the process will print the "Invoke url": + +``` +Functions in sheetjsazure: + SheetJSAzure - [httpTrigger] +// highlight-next-line + Invoke url: https://sheetjsazure.azurewebsites.net/api/sheetjsazure +``` + +Take note of that URL. + +#### Remote Test + + +22) In a separate terminal window, download +and make a POST request to the production server. Replace `FUNCTION_URL` with +the Invoke URL from Step 21: + +```bash +curl -LO https://sheetjs.com/pres.numbers +curl -X POST -F "upload=@pres.numbers" FUNCTION_URL +``` + +If the test succeeded, the terminal will print CSV rows from the test file data. + +23) Open a web browser and access the Invoke URL from Step 21. + +If the test succeeded, the browser will attempt to download `SheetJSAzure.xlsx`. +Open in Excel or another spreadsheet editor to confirm the file is valid. + +## Blob Storage The main module for Azure Blob Storage is `@azure/storage-blob`. This example was tested using the "Connection String" authentication method. The strings are found in the Azure Portal under "Access Keys" for the storage account. -### Reading Data +### Downloading Data The `BlobClient#download` method returns a Stream. After collecting into a -Buffer, `XLSX.read` can parse the data: +Buffer, the SheetJS `read` method[^9] can parse the data into a workbook[^10]. + +The following demo uses the `sheet_to_csv`[^11] utility function to display the +contents of a file in Azure Blob Storage: ```js title="SheetJSReadFromAzure.mjs" import { BlobServiceClient } from "@azure/storage-blob"; import { read, utils } from "xlsx"; /* replace these constants */ +// highlight-start const connStr = ""; const containerName = ""; -const blobName = ""; +// highlight-end + +/* Blob name */ +const blobName = "SheetJSBloblobber.xlsx"; /* get a readable stream*/ const blobServiceClient = BlobServiceClient.fromConnectionString(connStr); @@ -207,18 +423,27 @@ const wb = read(downloaded); console.log(utils.sheet_to_csv(wb.Sheets[wb.SheetNames[0]])); ``` -### Writing Data +### Uploading Data -`BlockBlobClient#upload` directly accepts a Buffer: +The SheetJS `write` method[^12] with the option `type: "buffer"` will generate +NodeJS buffers which can be uploaded with `BlockBlobClient#upload`. + +The following example generates a sample worksheet using the `aoa_to_sheet`[^13] +method, generates a sample workbook using worksheet helper methods[^14], writes +the workbook to XLSX format in a Buffer, and sends the Buffer in the response: ```js title="SheetJSWriteToAzure.mjs" import { BlobServiceClient } from "@azure/storage-blob"; import { write, utils } from "xlsx"; /* replace these constants */ +// highlight-start const connStr = ""; const containerName = ""; -const blobName = ""; +// highlight-end + +/* Blob name */ +const blobName = "SheetJSBloblobber.xlsx"; /* Create a simple workbook and write XLSX to buffer */ const ws = utils.aoa_to_sheet(["SheetJS".split(""), [5,4,3,3,7,9,5]]); @@ -231,3 +456,165 @@ const containerClient = blobServiceClient.getContainerClient(containerName); const blockBlobClient = containerClient.getBlockBlobClient(blobName); const uploadBlobResponse = await blockBlobClient.upload(buf, buf.length); ``` + +### Blob Demo + +:::note pass + +At the time of writing, new Azure accounts were granted a 12-month trial of Blob +Storage. The trial includes 5GB of "Locally-redundant storage" with 20,000 read +requests and 2000 write requests per month. + +::: + +0) If you do not have an account, create a new Azure free tier account[^8]. + +#### Storage Account Setup + +1) Sign into the [Azure Portal](https://portal.azure.com/#home) + +2) Type "Storage" in the top search box and click "Storage accounts" + +3) Click "+ Create" + +4) Select the following options: + +- Type a memorable "Storage account name" ("sheetjstorage" when last tested) + +- "Redundancy": select LRS (Locally-redundant storage) + +- "Hosting options and plans": "Consumption (Serverless)" + +5) Click "Review", then click "Create" to create the storage. + +The page will display a status message + +> ... Deployment is in progress + +When the resources are configured, the status will change to + +> Your deployment is complete + +6) Click "Go to Resource". + +#### Access Keys + +7) Click "Access keys" in the left sidebar (under "Security + networking") + +8) Look for the "Connection string" title under "key1". In the row below the +title, click "Show" to reveal the key. Click the copy icon or manually copy the +key, storing it in a safe place. + +#### Container Setup + +9) Click "Containers" in the left sidebar. + +10) Click "+ Container" + +11) Select the following options: + +- Type a memorable "Name" ("sheetjs-container" when last tested) + +12) Click "Create" to create the container. + +#### Project Setup + +13) Create a new project folder: + +```bash +mkdir SheetJSBlob +cd SheetJSBlob +npm init -y +``` + +14) Install dependencies: + +{`\ +npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz @azure/storage-blob`} + + +14) Copy the [`SheetJSReadFromAzure.mjs` code block](#downloading-data) and save +to `SheetJSReadFromAzure.mjs`. + +15) Copy the [`SheetJSWriteToAzure.mjs` code block](#uploading-data) and save +to `SheetJSWriteToAzure.mjs`. + +16) Edit both `SheetJSReadFromAzure.mjs` and `SheetJSWriteToAzure.mjs`: + +- Replace the `connStr` value with the connection string from Step 8 +- Replace the `containerName` value with the container name from Step 11 + +#### Test + +:::note pass + +The write demo creates a simple workbook, generates a NodeJS buffer, and uploads +the buffer to a file named `SheetJSBloblobber.xlsx` on Azure Blob Storage. + +The read demo fetches `SheetJSBloblobber.xlsx` and displays the data. + +``` + | A | B | C | D | E | F | G | +---+---|---|---|---|---|---|---| + 1 | S | h | e | e | t | J | S | + 2 | 5 | 4 | 3 | 3 | 7 | 9 | 5 | +``` + +::: + +17) Run the write test: + +```bash +node SheetJSWriteToAzure.mjs +``` + +This will write the file `SheetJSBloblobber.xlsx` to the container. + +18) Run the read test: + +```bash +node SheetJSReadFromAzure.mjs +``` + +It will fetch the file created in the previous step and display CSV rows. + +``` +S,h,e,e,t,J,S +5,4,3,3,7,9,5 +``` + +19) Sign into the [Azure Portal](https://portal.azure.com/#home) + +20) Type "Storage" in the top search box and click "Storage accounts" + +21) Click on the name of the storage + +22) In the middle column, click "Containers". It will be under "Data storage". + +23) Click on the name of the container in the table + +24) Verify that the table shows `SheetJSBloblobber.xlsx`: + +![SheetJSBloblobber.xlsx in the container](pathname:///azure/bloblobber.png) + +25) Click on the name `SheetJSBloblobber.xlsx`. + +26) In the right pane, click "Download". + +The downloaded file is the raw file stored in Azure Blob Storage. To confirm it +is valid, open the file in Excel or another spreadsheet editor. + +[^1]: The platform-specific installers are available at +[^2]: See [`read` in "Reading Files"](/docs/api/parse-options) +[^3]: See ["Workbook Object" in "SheetJS Data Model"](/docs/csf/book) for more details. +[^4]: See [`sheet_to_csv` in "CSV and Text"](/docs/api/utilities/csv#delimiter-separated-output) +[^5]: See [`write` in "Writing Files"](/docs/api/write-options) +[^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]: Registering for a free account [on the Azure Free Tier](https://azure.microsoft.com/en-us/free) requires a valid phone number and a valid credit card. +[^9]: See [`read` in "Reading Files"](/docs/api/parse-options) +[^10]: See ["Workbook Object" in "SheetJS Data Model"](/docs/csf/book) for more details. +[^11]: See [`sheet_to_csv` in "CSV and Text"](/docs/api/utilities/csv#delimiter-separated-output) +[^12]: See [`write` in "Writing Files"](/docs/api/write-options) +[^13]: See [`aoa_to_sheet` in "Utilities"](/docs/api/utilities/array#array-of-arrays-input) +[^14]: See ["Workbook Helpers" in "Utilities"](/docs/api/utilities/wb) for details on `book_new` and `book_append_sheet`. diff --git a/docz/docs/index.md b/docz/docs/index.md index 3ce90e49..a13eaf71 100644 --- a/docz/docs/index.md +++ b/docz/docs/index.md @@ -5,6 +5,8 @@ title: Overview --- import current from '/version.js'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; import CodeBlock from '@theme/CodeBlock'; # SheetJS CE @@ -27,6 +29,9 @@ run entirely in the web browser.
How to add to your site (click to show) + + + 1) Make sure your table has an ID: ```html @@ -45,7 +50,7 @@ run entirely in the web browser. ``` -4) Add an event handler for the `click` event to create a workbook and download: +4) Add an event handler for the `click` event to export table data to XLSX: ```html ``` + + + +:::note pass + +This example assumes you have an existing project with an HTML TABLE element: + +```jsx title="Sample Component" +function App() { + return ( <> +

SheetJS Table

+ + + + + +
SheetJS Table Export
AuthorID你好!
SheetJS7262வணக்கம்!
+ Powered by SheetJS +
+ ) +} +export default App; +``` + +If you are starting from scratch, create a new ViteJS + ReactJS project: + +```bash +npm create vite@latest -- sheetjs-react --template react --default +cd sheetjs-react +npm install +npm run dev +``` + +Replace `src/App.jsx` with the sample component. + +::: + +1) Install the SheetJS library using a package manager: + + + +{`\ +npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`} + + + +{`\ +pnpm install https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`} + + + +{`\ +yarn add https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`} + + + + +2) Ensure that your component script imports `useRef` from the `react` library: + +```js +import { useRef } from "react"; +``` + +3) Add the following line at the top of your component script: + +```js +import { utils, writeFileXLSX } from "xlsx"; +``` + +4) Create a ref in the body of your function component: + +```jsx +function App() { +// highlight-next-line + const tbl = useRef(null); + // ... +``` + +5) Attach the ref to the table element: + +```jsx +function App() { + // ... + return ( + {/*...*/} +// highlight-next-line + + {/*...*/} +``` + +6) Add a button with a click handler that will export table data to XLSX: + +```jsx +function App() { + // ... + return ( + {/*...*/} +// highlight-start + +// highlight-end + {/*...*/} +``` + + + +
How to automate with NodeJS (click to show) @@ -74,7 +190,7 @@ using the `puppeteer` and `playwright` browser automation frameworks. function Table2XLSX(props) { /* Callback invoked when the button is clicked */ - const xport = React.useCallback(async () => { + const xport = React.useCallback(() => { /* Create worksheet from HTML DOM TABLE */ const table = document.getElementById("Table2XLSX"); const wb = XLSX.utils.table_to_book(table); diff --git a/docz/docusaurus.config.js b/docz/docusaurus.config.js index c283b59f..bb7c94c8 100644 --- a/docz/docusaurus.config.js +++ b/docz/docusaurus.config.js @@ -202,6 +202,7 @@ const config = { { from: '/docs/demos/hosting/dropbox', to: '/docs/demos/cloud/dropbox/' }, { from: '/docs/demos/hosting/github', to: '/docs/demos/cloud/github/' }, /* data */ + { from: '/docs/getting-started/demos/database', to: '/docs/demos/data/' }, { from: '/docs/demos/database', to: '/docs/demos/data/' }, { from: '/docs/demos/nosql', to: '/docs/demos/data/' }, { from: '/docs/getting-started/demos/nosql', to: '/docs/demos/data/' }, diff --git a/docz/static/azure/bloblobber.png b/docz/static/azure/bloblobber.png new file mode 100644 index 0000000000000000000000000000000000000000..182ef1d79a36a60374d3b193fc43b7132175ce6e GIT binary patch literal 25987 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 { - if(typeof req.on !== 'undefined') return req; - const s = new Readable(); - s._read = ()=>{}; - s.push(Buffer.from(req.body)); - s.push(null); - Object.assign(s, req); - return s; -}; +app.http('SheetJSAzure', { + methods: ['GET', 'POST'], + authLevel: 'anonymous', + handler: async (req, context) => { + if (req.method == "POST") { + /* grab the file at form key `upload` */ + const formData = await req.formData(); + const f = formData.get("upload"); -module.exports = (context, req) => { - if(req.method == "POST") { - const form = new formidable.IncomingForm(); - form.parse(streamify(req), (err, fields, files) => { - /* grab the first file */ - var f = files["upload"]; - if(!f) { - context.res = { status: 400, body: "Must submit a file for processing!" }; + if (!f || !(f instanceof Blob)) { + return { status: 400, body: "Must submit a file for processing!" }; } else { - /* file is stored in a temp directory, so we can point to that and read it */ - const wb = XLSX.read(f.filepath, {type:"file"}); + /* parse file */ + const ab = await f.arrayBuffer(); + const wb = XLSX.read(ab); /* generate CSV from first sheet */ const csv = XLSX.utils.sheet_to_csv(wb.Sheets[wb.SheetNames[0]]); - context.res = { status: 200, body: csv }; + return { status: 200, body: csv }; } - context.done(); - }); - } else if(req.method == "GET") { - var ws = XLSX.utils.aoa_to_sheet(["SheetJS".split(""), [5,4,3,3,7,9,5]]); - var wb = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(wb, ws, "Data"); - var buf = XLSX.write(wb, {type: "buffer", bookType: "xlsx"}); - context.res = { - status: 200, - headers: { "Content-Disposition": `attachment; filename="SheetJSAzure.xlsx";` }, - body: buf - }; - context.done(); - } else { - context.res = { status: 500, body: `Unsupported method ${req.method}` }; - context.done(); + + } else if (req.method == "GET") { + var ws = XLSX.utils.aoa_to_sheet(["SheetJS".split(""), [5, 4, 3, 3, 7, 9, 5]]); + var wb = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(wb, ws, "Data"); + var buf = XLSX.write(wb, { type: "buffer", bookType: "xlsx" }); + return { + status: 200, + headers: { "Content-Disposition": `attachment; filename="SheetJSAzure.xlsx";` }, + body: buf + }; + } else return { status: 500, body: `Unsupported method ${req.method}` }; } -}; \ No newline at end of file +}); \ No newline at end of file diff --git a/docz/static/sqlite/chinook.sql b/docz/static/sqlite/chinook.sql index 4d357707..2680dc60 100644 --- a/docz/static/sqlite/chinook.sql +++ b/docz/static/sqlite/chinook.sql @@ -1,11 +1,11 @@ /* Licensing Note: - At the time this extract was made (2023 May 28), the linked license page + At the time this snapshot was taken, the linked license page http://www.codeplex.com/ChinookDatabase/license - was unavailable (Microsoft shuttered CodePlex). + was unavailable (Microsoft shuttered CodePlex in 2021) archive.org has a snapshot of the project license page: