forked from sheetjs/docs.sheetjs.com
		
	azure
This commit is contained in:
		
							parent
							
								
									62641b5da6
								
							
						
					
					
						commit
						990f42934b
					
				| @ -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 | | ||||
| 
 | ||||
|  | ||||
| @ -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. | ||||
|  | ||||
| @ -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. | ||||
| 
 | ||||
| <Tabs groupId="os"> | ||||
|   <TabItem value="unix" label="Linux/MacOS"> | ||||
| 
 | ||||
| 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. | ||||
| 
 | ||||
|   </TabItem> | ||||
|   <TabItem value="win" label="Windows"> | ||||
| 
 | ||||
| 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. | ||||
| 
 | ||||
|   </TabItem> | ||||
| </Tabs> | ||||
| 
 | ||||
| #### 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 | ||||
| 
 | ||||
| <details open><summary><b>Complete Example</b> (click to show)</summary> | ||||
| :::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: | ||||
| 
 | ||||
| <CodeBlock language="bash">{`\ | ||||
| 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`} | ||||
| </CodeBlock> | ||||
| 
 | ||||
| 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 <https://sheetjs.com/pres.numbers> and run: | ||||
| 11) In a separate terminal window, download <https://sheetjs.com/pres.numbers> | ||||
| 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. | ||||
| 
 | ||||
| </details> | ||||
| 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 <https://sheetjs.com/pres.numbers> | ||||
| 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 = "<REPLACE WITH CONNECTION STRING>"; | ||||
| const containerName = "<REPLACE WITH CONTAINER NAME>"; | ||||
| const blobName = "<REPLACE WITH BLOB NAME>"; | ||||
| // 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 = "<REPLACE WITH CONNECTION STRING>"; | ||||
| const containerName = "<REPLACE WITH CONTAINER NAME>"; | ||||
| const blobName = "<REPLACE WITH BLOB NAME>"; | ||||
| // 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: | ||||
| 
 | ||||
| <CodeBlock language="bash">{`\ | ||||
| npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz @azure/storage-blob`} | ||||
| </CodeBlock> | ||||
| 
 | ||||
| 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`: | ||||
| 
 | ||||
|  | ||||
| 
 | ||||
| 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 <https://learn.microsoft.com/en-us/cli/azure/install-azure-cli> | ||||
| [^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`. | ||||
|  | ||||
| @ -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. | ||||
| 
 | ||||
| <details><summary><b>How to add to your site</b> (click to show)</summary> | ||||
| 
 | ||||
| <Tabs groupId="deployment"> | ||||
|   <TabItem value="vanilla" label="HTML"> | ||||
| 
 | ||||
| 1) Make sure your table has an ID: | ||||
| 
 | ||||
| ```html | ||||
| @ -45,7 +50,7 @@ run entirely in the web browser. | ||||
| <button id="sheetjsexport"><b>Export as XLSX</b></button> | ||||
| ``` | ||||
| 
 | ||||
| 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 | ||||
| <script> | ||||
| @ -58,6 +63,117 @@ document.getElementById("sheetjsexport").addEventListener('click', function() { | ||||
| </script> | ||||
| ``` | ||||
| 
 | ||||
|   </TabItem> | ||||
|   <TabItem value="react" label="React"> | ||||
| 
 | ||||
| :::note pass | ||||
| 
 | ||||
| This example assumes you have an existing project with an HTML TABLE element: | ||||
| 
 | ||||
| ```jsx title="Sample Component" | ||||
| function App() { | ||||
|   return ( <> | ||||
|       <h3>SheetJS Table</h3> | ||||
|       <table> | ||||
|         <tr><td colSpan="3">SheetJS Table Export</td></tr> | ||||
|         <tr><td>Author</td><td>ID</td><td>你好!</td></tr> | ||||
|         <tr><td>SheetJS</td><td>7262</td><td>வணக்கம்!</td></tr> | ||||
|         <tr><td colSpan="3"> | ||||
|           <a href="//sheetjs.com">Powered by SheetJS</a> | ||||
|         </td></tr> | ||||
|       </table> | ||||
|   </> ) | ||||
| } | ||||
| 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: | ||||
| 
 | ||||
| <Tabs groupId="pm"> | ||||
|   <TabItem value="npm" label="npm"> | ||||
| <CodeBlock language="bash">{`\ | ||||
| npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`} | ||||
| </CodeBlock> | ||||
|   </TabItem> | ||||
|   <TabItem value="pnpm" label="pnpm"> | ||||
| <CodeBlock language="bash">{`\ | ||||
| pnpm install https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`} | ||||
| </CodeBlock> | ||||
|   </TabItem> | ||||
|   <TabItem value="yarn" label="Yarn" default> | ||||
| <CodeBlock language="bash">{`\ | ||||
| yarn add https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`} | ||||
| </CodeBlock> | ||||
|   </TabItem> | ||||
| </Tabs> | ||||
| 
 | ||||
| 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 | ||||
|     <table ref={tbl}> | ||||
|       {/*...*/} | ||||
| ``` | ||||
| 
 | ||||
| 6) Add a button with a click handler that will export table data to XLSX: | ||||
| 
 | ||||
| ```jsx | ||||
| function App() { | ||||
|   // ... | ||||
|   return ( | ||||
|     {/*...*/} | ||||
| // highlight-start | ||||
|     <button onClick={() => { | ||||
|       // generate workbook from table element | ||||
|       const wb = utils.table_to_book(tbl.current); | ||||
|       // write to XLSX | ||||
|       writeFileXLSX(wb, "SheetJSReactExport.xlsx"); | ||||
|     }}>Export XLSX</button> | ||||
| // highlight-end | ||||
|     {/*...*/} | ||||
| ``` | ||||
| 
 | ||||
|   </TabItem> | ||||
| </Tabs> | ||||
| 
 | ||||
| </details> | ||||
| 
 | ||||
| <details><summary><b>How to automate with NodeJS</b> (click to show)</summary> | ||||
| @ -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); | ||||
|  | ||||
| @ -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/' }, | ||||
|  | ||||
							
								
								
									
										
											BIN
										
									
								
								docz/static/azure/bloblobber.png
									
									
									
									
									
										Normal file
									
								
							
							
								
									
								
								
								
								
								
									
									
								
							
						
						
									
										
											BIN
										
									
								
								docz/static/azure/bloblobber.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 25 KiB | 
| @ -1,49 +1,38 @@ | ||||
| /* sheetjs (C) SheetJS -- https://sheetjs.com */ | ||||
| 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: ['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}` }; | ||||
|   } | ||||
| }; | ||||
| }); | ||||
| @ -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: | ||||
| 
 | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user