sheetforce
| @ -129,7 +129,7 @@ site.use(sheets({ | ||||
| 
 | ||||
| :::note | ||||
| 
 | ||||
| This was tested against `lume v1.17.5` on 2023 June 25. | ||||
| This demo was last tested against `lume v1.17.5` on 2023 June 25. | ||||
| 
 | ||||
| This example uses the Nunjucks template format. Lume plugins support additional | ||||
| template formats, including Markdown and JSX. | ||||
|  | ||||
| @ -257,9 +257,9 @@ This demo was tested in the following environments: | ||||
| 
 | ||||
| | OS and Version | Arch | Tauri    | Date       | | ||||
| |:---------------|:-----|:---------|:-----------| | ||||
| | macOS 13.4.0   | x64  | `v1.4.0` | 2023-06-25 | | ||||
| | macOS 13.5.1   | x64  | `v1.5.0` | 2023-09-30 | | ||||
| | macOS 13.4.1   | ARM  | `v1.4.0` | 2023-06-29 | | ||||
| | Windows 10     | x64  | `v1.4.1` | 2023-07-30 | | ||||
| | Windows 10     | x64  | `v1.5.0` | 2023-10-01 | | ||||
| | Windows 11     | ARM  | `v1.4.1` | 2023-09-26 | | ||||
| | Linux (HoloOS) | x64  | `v1.4.1` | 2023-07-30 | | ||||
| | Linux (Debian) | ARM  | `v1.4.1` | 2023-09-26 | | ||||
| @ -287,14 +287,14 @@ If required dependencies are installed, the output will show a checkmark next to | ||||
| 
 | ||||
| ``` | ||||
| [✔] Environment | ||||
|     - OS: Mac OS 13.4.0 X64 | ||||
|     - OS: Mac OS 13.5.1 X64 | ||||
|     ✔ Xcode Command Line Tools: installed | ||||
|     ✔ rustc: 1.70.0 (90c541806 2023-05-31) | ||||
|     ✔ Cargo: 1.70.0 (ec8a8a0ca 2023-04-25) | ||||
|     ✔ rustup: 1.26.0 (5af9b9484 2023-04-05) | ||||
|     ✔ rustc: 1.72.1 (d5c2e9c34 2023-09-13) | ||||
|     ✔ Cargo: 1.72.1 (103a7ff2e 2023-08-15) | ||||
|     ✔ rustup: 1.26.0+1046 (d4c684485 2023-08-30) | ||||
|     ✔ Rust toolchain: stable-x86_64-apple-darwin (default) | ||||
|     - node: 18.16.1 | ||||
|     - npm: 9.5.1 | ||||
|     - node: 16.20.2 | ||||
|     - npm: 8.19.4 | ||||
| ``` | ||||
| 
 | ||||
| :::caution pass | ||||
| @ -374,8 +374,7 @@ curl -o src/App.vue https://docs.sheetjs.com/tauri/App.vue | ||||
| npm run tauri build | ||||
| ``` | ||||
| 
 | ||||
| At the end, it will print the path to the generated program. If the program path | ||||
| is not listed, it is typically found in the `src-tauri/target/release` folder. | ||||
| At the end, it will print the path to the generated installer. | ||||
| 
 | ||||
| :::info pass | ||||
| 
 | ||||
| @ -393,9 +392,28 @@ sudo pacman -S openssl | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| 6) Run the program. The following features should be manually verified: | ||||
| 6) Run the program. | ||||
| 
 | ||||
| - When it is opened, the app will download <https://sheetjs.com/pres.numbers> | ||||
| <Tabs groupId="os"> | ||||
|   <TabItem value="unix" label="Linux/MacOS"> | ||||
| 
 | ||||
| ```bash | ||||
| ./src-tauri/target/release/SheetJSTauri | ||||
| ``` | ||||
| 
 | ||||
|   </TabItem> | ||||
|   <TabItem value="win" label="Windows"> | ||||
| 
 | ||||
| ```powershell | ||||
| .\src-tauri\target\release\SheetJSTauri.exe | ||||
| ``` | ||||
| 
 | ||||
|   </TabItem> | ||||
| </Tabs> | ||||
| 
 | ||||
| The following features should be manually verified: | ||||
| 
 | ||||
| - When it is loaded, the app will download <https://sheetjs.com/pres.numbers> | ||||
|   and display the data in a table. | ||||
| - Clicking "Save Data" will show a save dialog. After selecting a path and name, | ||||
|   the app will write a file. That file can be opened in a spreadsheet editor. | ||||
|  | ||||
| @ -5,12 +5,20 @@ 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'; | ||||
| 
 | ||||
| Salesforce apps can use third-party libraries in "Lightning Web Components". | ||||
| [Salesforce](https://www.salesforce.com/) is a suite of cloud-based software | ||||
| systems for Customer Relationship Management (CRM). "Lightning Web Components" | ||||
| (LWC) is a robust JavaScript extension platform available to Salesforce apps[^1]. | ||||
| 
 | ||||
| This demo assumes familiarity with Lightning Web Components.  Salesforce has a | ||||
| [detailed introduction.](https://developer.salesforce.com/docs/component-library/documentation/en/lwc/lwc.get_started_introduction) | ||||
| [SheetJS](https://sheetjs.com) is a JavaScript library for reading and writing | ||||
| data from spreadsheets. | ||||
| 
 | ||||
| This demo explores the LWC scripting features in Salesforce. We'll explore how | ||||
| to install SheetJS scripts in Lightning Web Components and build a sample app | ||||
| for exporting lists to XLSX workbooks. | ||||
| 
 | ||||
| :::caution pass | ||||
| 
 | ||||
| @ -21,191 +29,72 @@ may require some adjustments.  The official documentation should be consulted. | ||||
| 
 | ||||
| :::note | ||||
| 
 | ||||
| This demo was last tested on 2023 April 09 using Lightning API version `57.0`. | ||||
| This demo was last tested on 2023 September 30 using Lightning API version `58.0`. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| ## Getting Started | ||||
| :::warning Telemetry | ||||
| 
 | ||||
| This demo was built on a "Developer Edition" account. At the time of writing, an | ||||
| [account can be created for free.](https://developer.salesforce.com/signup) | ||||
| 
 | ||||
| ### Create Sample Project and Component | ||||
| 
 | ||||
| <!-- spellchecker-disable --> | ||||
| 
 | ||||
| Following the steps in ["Develop in Non-Scratch Orgs"](https://developer.salesforce.com/docs/component-library/documentation/en/lwc/lwc.get_started_sfdx_deploy): | ||||
| 
 | ||||
| <!-- spellchecker-enable --> | ||||
| The Salesforce developer tools embed telemetry. It can be disabled by setting | ||||
| the environment variable `SF_DISABLE_TELEMETRY` to `true` or by running | ||||
| 
 | ||||
| ```bash | ||||
| ## Login | ||||
| sfdx force:auth:web:login -d -a LWC-Hub | ||||
| 
 | ||||
| ## Create Sample Project and Component | ||||
| sfdx force:project:create --projectname SheetForce | ||||
| cd SheetForce | ||||
| sfdx force:lightning:component:create --type lwc -n sheetComponent -d force-app/main/default/lwc | ||||
| npx @salesforce/cli config set disable-telemetry=true --global | ||||
| ``` | ||||
| 
 | ||||
| By default, the component will not be available to app pages.  A few files must | ||||
| be changed: | ||||
| ::: | ||||
| 
 | ||||
| `force-app\main\default\lwc\sheetComponent\sheetComponent.html` add some HTML: | ||||
| ## Integration Details | ||||
| 
 | ||||
| ```html title="force-app\main\default\lwc\sheetComponent\sheetComponent.html" | ||||
| <template> | ||||
|   <!-- highlight-next-line --> | ||||
|   <b>SheetForce demo</b> | ||||
| </template> | ||||
| ``` | ||||
| Lightning Web Components can load scripts stored in static resources. | ||||
| 
 | ||||
| `force-app\main\default\lwc\sheetComponent\sheetComponent.js-meta.xml` change | ||||
| `isExposed` from `false` to `true` and add some metadata: | ||||
| 
 | ||||
| ```xml title="force-app\main\default\lwc\sheetComponent\sheetComponent.js-meta.xml" | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata"> | ||||
|   <apiVersion>57.0</apiVersion> | ||||
|   <!-- highlight-start --> | ||||
|   <isExposed>true</isExposed> | ||||
|   <masterLabel>SheetForce</masterLabel> | ||||
|   <description>SheetJS Demo</description> | ||||
|   <targets> | ||||
|     <target>lightning__AppPage</target> | ||||
|   </targets> | ||||
|   <!-- highlight-end --> | ||||
| </LightningComponentBundle> | ||||
| ``` | ||||
| 
 | ||||
| ### Deploy Sample Project | ||||
| 
 | ||||
| Deploy the project: | ||||
| 
 | ||||
| ```bash | ||||
| sfdx force:source:deploy -p force-app -u SALESFORCE@USER.NAME # replace with actual username | ||||
| ``` | ||||
| 
 | ||||
| The custom component can be found in Custom Code > Lightning Components. | ||||
| 
 | ||||
|  | ||||
| 
 | ||||
| ### Initialize App Page | ||||
| 
 | ||||
| Create an "App Page" in the "Lightning App Builder".  Instructions are included | ||||
| in [Hello World in a Scratch Org](https://developer.salesforce.com/docs/component-library/documentation/en/lwc/lwc.get_started_sfdx_hello_world) | ||||
| 
 | ||||
| The following options should be set: | ||||
| - The "App Page" option should be selected. | ||||
| - The App Label should be set to "SheetJS Demo". | ||||
| - The "One Region" layout should be selected. | ||||
| 
 | ||||
| Under Custom components, you should see "SheetForce".  Click and drag it into | ||||
| the app builder main view to add it to the page. | ||||
| 
 | ||||
| Click "Save" then click "Activate".  The following options should be set: | ||||
| - Click "Change..." next to "Icon" and pick a memorable icon | ||||
| - Under "Lightning Experience" click "LightningBolt" then "Add page to app" | ||||
| 
 | ||||
| Click "Save" to activate the page, then click the left arrow to return to Setup. | ||||
| 
 | ||||
| Click the App Launcher and select "Bolt Solutions" then "SheetJS Demo".  You | ||||
| should see a page like | ||||
| 
 | ||||
|  | ||||
| 
 | ||||
| 
 | ||||
| ## Adding the Standalone Script | ||||
| ### Installation | ||||
| 
 | ||||
| The [SheetJS Standalone scripts](/docs/getting-started/installation/standalone) | ||||
| can be downloaded and added as a static resource. | ||||
| 
 | ||||
| Due to Salesforce name restrictions, the script must be renamed to `sheetjs.js` | ||||
| :::info pass | ||||
| 
 | ||||
| <p>1) Download <a href={`https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js`}>https://cdn.sheetjs.com/xlsx-{current}/package/dist/xlsx.full.min.js</a></p> | ||||
| 
 | ||||
| :::warning pass | ||||
| 
 | ||||
| **DO NOT "COPY AND PASTE"!**  The file should be explicitly downloaded.  Copying | ||||
| and pasting corrupts the source code and the component will fail in subtle ways. | ||||
| 
 | ||||
| The easiest approach is to right-click the link and select "Save Link As..." | ||||
| Due to Salesforce name restrictions, the script must be renamed to `sheetjs.js`. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| 2) Move the file to the `force-app/main/default/staticresources/` folder and | ||||
|    rename the file to `sheetjs.js`. | ||||
| ### Loading SheetJS | ||||
| 
 | ||||
| 3) Create `force-app/main/default/staticresources/sheetjs.resource-meta.xml`: | ||||
| Assuming the script was renamed to `sheetjs.js`, the name of the resource will | ||||
| be `sheetjs`. `async` functions can use `loadScript` to fetch and load the | ||||
| library. The script will define the variable `XLSX`[^2] | ||||
| 
 | ||||
| ```xml title="force-app/main/default/staticresources/sheetjs.resource-meta.xml" | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <StaticResource xmlns="http://soap.sforce.com/2006/04/metadata"> | ||||
|   <cacheControl>Private</cacheControl> | ||||
|   <contentType>application/javascript</contentType> | ||||
| </StaticResource> | ||||
| ``` | ||||
| 
 | ||||
| 4) Deploy the project again: | ||||
| 
 | ||||
| ```bash | ||||
| sfdx force:source:deploy -p force-app -u SALESFORCE@USER.NAME # replace with actual username | ||||
| ``` | ||||
| 
 | ||||
| :::note pass | ||||
| 
 | ||||
| The official documentation recommends adding a static resource with a ZIP file. | ||||
| That approach is not explored in this demo. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| Custom Code > Static Resources should now list `sheetjs`: | ||||
| 
 | ||||
|  | ||||
| 
 | ||||
| ### Test the Static Resource | ||||
| 
 | ||||
| The script can be loaded from component code with: | ||||
| It is recommended to load the library in a callback. For example, the following | ||||
| `@api` method loads the library and exports sample data to a spreadsheet file: | ||||
| 
 | ||||
| ```js | ||||
| import sheetjs from '@salesforce/resourceUrl/sheetjs'; | ||||
| ``` | ||||
| 
 | ||||
| The library includes a version number that can be displayed: | ||||
| 
 | ||||
| 1) Add a reference in `sheetComponent.js` and expose the `version` property: | ||||
| 
 | ||||
| ```js title="force-app/main/default/lwc/sheetComponent/sheetComponent.js" | ||||
| import { LightningElement } from 'lwc'; | ||||
| import { LightningElement, api } from 'lwc'; | ||||
| import { loadScript } from 'lightning/platformResourceLoader'; | ||||
| // highlight-next-line | ||||
| import sheetjs from '@salesforce/resourceUrl/sheetjs'; | ||||
| 
 | ||||
| export default class SheetComponent extends LightningElement { | ||||
|   version = "???"; // start with ??? | ||||
|   async connectedCallback() { | ||||
|     // highlight-next-line | ||||
|   @api async download() { | ||||
|     await loadScript(this, sheetjs); // load the library | ||||
|     // At this point, the library is accessible with the `XLSX` variable | ||||
|     this.version = XLSX.version; | ||||
| 
 | ||||
|     // Create worksheet | ||||
|     var ws = XLSX.utils.aoa_to_sheet([ | ||||
|       [ "S", "h", "e", "e", "t", "J"," S" ], | ||||
|       [  5 ,  4 ,  3 ,  3 ,  7 ,  9 ,  5  ] | ||||
|     ]); | ||||
| 
 | ||||
|     // Create workbook and add worksheet | ||||
|     var wb = XLSX.utils.book_new(); | ||||
|     XLSX.utils.book_append_sheet(wb, ws, "Data"); | ||||
| 
 | ||||
|     // Export Data | ||||
|     XLSX.writeFile(wb, "SheetForceExport.xlsx"); | ||||
|   } | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| 2) Reference the variable in `sheetComponent.html`: | ||||
| 
 | ||||
| ```html title="force-app/main/default/lwc/sheetComponent/sheetComponent.html" | ||||
| <template> | ||||
|   <!-- highlight-next-line --> | ||||
|   <b>SheetForce {version}</b> | ||||
| </template> | ||||
| ``` | ||||
| 
 | ||||
| 3) Deploy the project again and re-load the Bolt Solutions "SheetJS Demo" page: | ||||
| 
 | ||||
|  | ||||
| 
 | ||||
| ## Exporting Data from SF Lists | ||||
| ### Exporting Data from SF List | ||||
| 
 | ||||
| :::note pass | ||||
| 
 | ||||
| @ -214,7 +103,9 @@ There are many different data types and APIs.  This demo uses the deprecated | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| ### Steps | ||||
| Using the LWC Wire Service, components receive data in separate events. Exports | ||||
| are typically generated in a separate event handler. Component state is normally | ||||
| used to handle the timing mismatch. | ||||
| 
 | ||||
| #### Getting Account Data | ||||
| 
 | ||||
| @ -222,6 +113,7 @@ The main method to obtain data is `getListUi` and the key for account data is | ||||
| `ACCOUNT_OBJECT`: | ||||
| 
 | ||||
| ```js | ||||
| import { LightningElement, wire } from 'lwc'; | ||||
| import { getListUi } from 'lightning/uiListApi'; | ||||
| import ACCOUNT_OBJECT from '@salesforce/schema/Account'; | ||||
| 
 | ||||
| @ -240,7 +132,7 @@ export default class SheetComponent extends LightningElement { | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| #### Generating an Array of Arrays | ||||
| #### Array of Arrays | ||||
| 
 | ||||
| SheetJS most reliably translates "arrays of arrays", a nested array which | ||||
| directly maps to individual cell addresses.  For example: | ||||
| @ -301,29 +193,447 @@ A suitable SheetJS array of arrays can be constructed by mapping across records: | ||||
| 
 | ||||
| ```js | ||||
|       var headers = [ "Name", "Phone" ]; | ||||
|       this.aoa = [headers].concat(data.records.records.map(record => [ | ||||
|       var aoa = [headers].concat(data.records.records.map(record => [ | ||||
|         record.fields.Name.value,  // Name field | ||||
|         record.fields.Phone.value, // Phone field | ||||
|       ])); | ||||
| ``` | ||||
| 
 | ||||
| This is readily exported to a spreadsheet in a callback function: | ||||
| #### State | ||||
| 
 | ||||
| This data is available in a wire service callback, but it is common to export | ||||
| the data in a separate API event. This flow is handled with a state variable: | ||||
| 
 | ||||
| ```js | ||||
| export default class SheetComponent extends LightningElement { | ||||
|   // highlight-next-line | ||||
|   aoa; // will hold data for export | ||||
|   @wire(getListUi, { | ||||
|     objectApiName: ACCOUNT_OBJECT.objectApiName, | ||||
|     listViewApiName: 'AllAccounts' | ||||
|   }) listInfo({ error, data }) { | ||||
|     if (data) { | ||||
|       var headers = [ "Name", "Phone" ]; | ||||
|       // create AOA | ||||
|       var _aoa = [headers].concat(data.records.records.map(record => [ | ||||
|         record.fields.Name.value,  // Name field | ||||
|         record.fields.Phone.value, // Phone field | ||||
|       ])); | ||||
| 
 | ||||
|       // assign to state | ||||
|       // highlight-next-line | ||||
|       this.aoa = _aoa; | ||||
|     } else if (error) console.log(error); | ||||
|   }; | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| #### Exporting Data | ||||
| 
 | ||||
| This is readily exported to a spreadsheet in a callback function. Starting from | ||||
| the array of arrays, the SheetJS `aoa_to_sheet` method[^3] generates a SheetJS | ||||
| sheet object[^4]. A workbook object[^5] is created with `book_new`[^6] and the | ||||
| sheet is added with `book_append_sheet`[^7]. Finally, the SheetJS `writeFile` | ||||
| method creates a XLSX file and initiates a download[^8]. | ||||
| 
 | ||||
| ```js | ||||
|   @api async download() { | ||||
|     await loadScript(this, sheetjs); // load the library | ||||
|     // get data from state | ||||
|     // highlight-next-line | ||||
|     var _aoa = this.aoa; | ||||
| 
 | ||||
|     // create workbook | ||||
|     var wb = XLSX.utils.book_new(); | ||||
|     var ws = XLSX.utils.aoa_to_sheet(this.aoa); | ||||
|     var ws = XLSX.utils.aoa_to_sheet(_aoa); | ||||
|     XLSX.utils.book_append_sheet(wb, ws, "Data"); | ||||
| 
 | ||||
|     // export | ||||
|     XLSX.writeFile(wb, "SheetForceExport.xlsx"); | ||||
|   }; | ||||
| ``` | ||||
| 
 | ||||
| ### Complete Example | ||||
| ## Complete Example | ||||
| 
 | ||||
| 1) Add a button to `sheetComponent.html` that will call a `download` callback: | ||||
| :::info pass | ||||
| 
 | ||||
| This demo was built on a "Developer Edition" account. At the time of writing, an | ||||
| [account can be created for free.](https://developer.salesforce.com/signup) | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| 0) Create a "Developer Edition" account. Take note of the unique Username | ||||
| 
 | ||||
| ### Configure Tools | ||||
| 
 | ||||
| 1) Install [NodeJS LTS](https://nodejs.org/en/download). | ||||
| 
 | ||||
| 2) Disable telemetry: | ||||
| 
 | ||||
| ```bash | ||||
| npx @salesforce/cli config set disable-telemetry=true --global | ||||
| ``` | ||||
| 
 | ||||
| 3) Confirm the CLI tool works by checking version information: | ||||
| 
 | ||||
| ```bash | ||||
| npx @salesforce/cli --version | ||||
| ``` | ||||
| 
 | ||||
| :::note pass | ||||
| 
 | ||||
| When the demo was last tested, the command printed | ||||
| 
 | ||||
| ``` | ||||
| @salesforce/cli/2.10.2 darwin-x64 node-v16.20.2 | ||||
| ``` | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| 4) Log into the org from the CLI tool: | ||||
| 
 | ||||
| ```bash | ||||
| npx @salesforce/cli org login web | ||||
| ``` | ||||
| 
 | ||||
| This will open a web browser. Sign in and authorize the application. | ||||
| 
 | ||||
| ### Create Project | ||||
| 
 | ||||
| 5) Create the "SheetForce" sample project with the `project generate` command: | ||||
| 
 | ||||
| ```bash | ||||
| npx @salesforce/cli project generate -n SheetForce | ||||
| ``` | ||||
| 
 | ||||
| Enter the project directory: | ||||
| 
 | ||||
| ```bash | ||||
| cd SheetForce | ||||
| ``` | ||||
| 
 | ||||
| 6) Create a LWC component with the `lightning generate component` command: | ||||
| 
 | ||||
| ```bash | ||||
| npx @salesforce/cli lightning generate component --type lwc -n sheetComponent -d force-app/main/default/lwc | ||||
| ``` | ||||
| 
 | ||||
| :::warning pass | ||||
| 
 | ||||
| At the time of testing, the CLI tool created components with app version 59. | ||||
| This version number is not valid for scratch orgs. | ||||
| 
 | ||||
| **This is a bug in the Salesforce CLI** | ||||
| 
 | ||||
| The workaround is to manually pin version 58 in the JSON and `meta.xml` files. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| 7) Replace `force-app\main\default\lwc\sheetComponent\sheetComponent.html` with | ||||
| the following template code: | ||||
| 
 | ||||
| ```html title="force-app\main\default\lwc\sheetComponent\sheetComponent.html" | ||||
| <template> | ||||
|   <!-- highlight-next-line --> | ||||
|   <b>SheetForce demo</b> | ||||
| </template> | ||||
| ``` | ||||
| 
 | ||||
| 8) Replace `force-app\main\default\lwc\sheetComponent\sheetComponent.js-meta.xml` | ||||
| with the following XML: | ||||
| 
 | ||||
| ```xml title="force-app\main\default\lwc\sheetComponent\sheetComponent.js-meta.xml" | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata"> | ||||
|   <!-- highlight-start --> | ||||
|   <apiVersion>58.0</apiVersion> | ||||
|   <isExposed>true</isExposed> | ||||
|   <masterLabel>SheetForce</masterLabel> | ||||
|   <description>SheetJS Demo</description> | ||||
|   <targets> | ||||
|     <target>lightning__AppPage</target> | ||||
|   </targets> | ||||
|   <!-- highlight-end --> | ||||
| </LightningComponentBundle> | ||||
| ``` | ||||
| 
 | ||||
| 9) Edit `sfdx-version.json` and set the `sourceApiVersion` to `58.0`: | ||||
| 
 | ||||
| ```json title="sfdx-version.json" | ||||
|   "name": "SheetForce", | ||||
|   "namespace": "", | ||||
|   "sfdcLoginUrl": "https://login.salesforce.com", | ||||
| // highlight-next-line | ||||
|   "sourceApiVersion": "58.0" | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| ### Deploy Sample Project | ||||
| 
 | ||||
| 10) Deploy the project from the CLI. You will need the Salesforce unique | ||||
| Username. For example, if the Username was `SF@USER.NAME`, the command is: | ||||
| 
 | ||||
| ```bash | ||||
| npx @salesforce/cli project deploy start -d force-app -o SF@USER.NAME | ||||
| ``` | ||||
| 
 | ||||
| 11) Find the new component: | ||||
| 
 | ||||
| <Tabs groupId="sfview"> | ||||
|   <TabItem value="nuevo" label="Lightning Experience"> | ||||
| 
 | ||||
| To find the new component in "Lightning Experience" view: | ||||
| 
 | ||||
| A) In the Salesforce site, click on the gear icon in the top-right corner of the | ||||
| page and select "Setup" (Setup for current app). | ||||
| 
 | ||||
| B) Type "Custom Code" in the left sidebar search box. Expand "Custom Code", | ||||
| expand "Lightning Components" and click "Lightning Components". | ||||
| 
 | ||||
| :::caution pass | ||||
| 
 | ||||
| With certain security settings, Salesforce will show an error: | ||||
| 
 | ||||
| > We can't display this page because your browser blocks cross-domain cookies, but you can view this page in Salesforce Classic. | ||||
| 
 | ||||
| Click the link to open the page in Salesforce Classic. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| 
 | ||||
|   </TabItem> | ||||
|   <TabItem value="classique" label="Classic"> | ||||
| 
 | ||||
| A) Click the "Setup" link in the top-right corner of the page. | ||||
| 
 | ||||
| B) Type "Lightning" in the left sidebar search box. Expand "Develop", expand | ||||
| "Lightning Components" and click "Lightning Components". | ||||
| 
 | ||||
|   </TabItem> | ||||
| </Tabs> | ||||
| 
 | ||||
| The page in Salesforce Classic will look like the screenshot below: | ||||
| 
 | ||||
|  | ||||
| 
 | ||||
| ### Initialize App Page | ||||
| 
 | ||||
| #### Create App Page | ||||
| 
 | ||||
| 12) Create an "App Page" in the "Lightning App Builder": | ||||
| 
 | ||||
| <Tabs groupId="sfview"> | ||||
|   <TabItem value="nuevo" label="Lightning Experience"> | ||||
| 
 | ||||
| A) In the Salesforce site, click on the gear icon in the top-right corner of the | ||||
| page and select "Setup" (Setup for current app). | ||||
| 
 | ||||
| B) Type "App Build" in the left sidebar search box. Expand "User Interface" and | ||||
| click "Lightning App Builder". | ||||
| 
 | ||||
| :::caution pass | ||||
| 
 | ||||
| With certain security settings, Salesforce will show an error: | ||||
| 
 | ||||
| > We can't display this page because your browser blocks cross-domain cookies, but you can view this page in Salesforce Classic. | ||||
| 
 | ||||
| Click the link to open the page in Salesforce Classic. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| C) Click the "New" button. | ||||
| 
 | ||||
|   </TabItem> | ||||
|   <TabItem value="classique" label="Classic"> | ||||
| 
 | ||||
| A) Click the "Setup" link in the top-right corner of the page. | ||||
| 
 | ||||
| B) Type "App Build" in the left search box and select "Lightning App Builder". | ||||
| 
 | ||||
| C) Click the "New" button. | ||||
| 
 | ||||
|   </TabItem> | ||||
| </Tabs> | ||||
| 
 | ||||
| #### App Wizard | ||||
| 
 | ||||
| D) Select "App Page" in the left list and click "Next" | ||||
| 
 | ||||
| E) Type "SheetJS Demo" in the Label textbox and click "Next" | ||||
| 
 | ||||
| F) Select "One Region" in the left list and click "Done" | ||||
| 
 | ||||
| #### App Builder | ||||
| 
 | ||||
| 13) Add the "SheetForce" component to the App Page. | ||||
| 
 | ||||
| In the left "Components" sidebar, under the "Custom" section, there should be a | ||||
| "SheetForce" entry. | ||||
| 
 | ||||
| Click and drag "SheetForce" into the "Add Component(s) Here" frame in the app | ||||
| builder main view to add it to the page. | ||||
| 
 | ||||
| Click "Save". | ||||
| 
 | ||||
| 14) Activate the page. | ||||
| 
 | ||||
| When the "Page Saved" modal is displayed, click "Activate". | ||||
| 
 | ||||
| The following options should be set: | ||||
| - Click "Change..." next to "Icon" and pick a memorable icon | ||||
| - Under "Lightning Experience" click "LightningBolt" then "Add page to app" | ||||
| 
 | ||||
| Click "Save" to activate the page. | ||||
| 
 | ||||
| 15) Open the demo page. | ||||
| 
 | ||||
| Click the left arrow in the top-left corner of the page to return to Setup. | ||||
| 
 | ||||
| If there is a "Switch to Lightning Experience" at the top, click the link. | ||||
| 
 | ||||
| Click the App Launcher (`᎒᎒᎒`) and search for "SheetJS". Under "Items", the new | ||||
| "SheetJS Demo" will be listed, Click "SheetJS Demo". | ||||
| 
 | ||||
| The app will display the "SheetForce demo" text from the component template: | ||||
| 
 | ||||
|  | ||||
| 
 | ||||
| 
 | ||||
| ### Add SheetJS | ||||
| 
 | ||||
| <p>16) Download <a href={`https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js`}>https://cdn.sheetjs.com/xlsx-{current}/package/dist/xlsx.full.min.js</a></p> | ||||
| 
 | ||||
| :::warning pass | ||||
| 
 | ||||
| **DO NOT "COPY AND PASTE"!**  The file should be explicitly downloaded.  Copying | ||||
| and pasting corrupts the source code and the component will fail in subtle ways. | ||||
| 
 | ||||
| The easiest approach is to right-click the link and select "Save Link As..." | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| The following command can be run in PowerShell or `bash`: | ||||
| 
 | ||||
| <CodeBlock language="bash">{`\ | ||||
| curl -o xlsx.full.min.js https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js`} | ||||
| </CodeBlock> | ||||
| 
 | ||||
| 
 | ||||
| 17) Move the file to the `force-app/main/default/staticresources/` folder and | ||||
| rename the file to `sheetjs.js`. | ||||
| 
 | ||||
| If the file was downloaded from the previous command, `mv` can move and rename: | ||||
| 
 | ||||
| ```bash | ||||
| mv xlsx.full.min.js force-app/main/default/staticresources/sheetjs.js | ||||
| ``` | ||||
| 
 | ||||
| 18) Create `force-app/main/default/staticresources/sheetjs.resource-meta.xml` | ||||
| (`sheetjs.resource-meta.xml` in the folder from step 2) with the following XML: | ||||
| 
 | ||||
| ```xml title="force-app/main/default/staticresources/sheetjs.resource-meta.xml" | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <StaticResource xmlns="http://soap.sforce.com/2006/04/metadata"> | ||||
|   <cacheControl>Private</cacheControl> | ||||
|   <contentType>application/javascript</contentType> | ||||
| </StaticResource> | ||||
| ``` | ||||
| 
 | ||||
| 19) Deploy the project again. Replace `SF@USER.NAME` with the unique Username: | ||||
| 
 | ||||
| ```bash | ||||
| npx @salesforce/cli project deploy start -d force-app -o SF@USER.NAME | ||||
| ``` | ||||
| 
 | ||||
| 20) Look for the static resource: | ||||
| 
 | ||||
| <Tabs groupId="sfview"> | ||||
|   <TabItem value="nuevo" label="Lightning Experience"> | ||||
| 
 | ||||
| A) In the Salesforce site, click on the gear icon in the top-right corner of the | ||||
| page and select "Setup" (Setup for current app). | ||||
| 
 | ||||
| B) Type "Static" in the left sidebar search box. Click "Static Resources" | ||||
| 
 | ||||
| :::caution pass | ||||
| 
 | ||||
| With certain security settings, Salesforce will show an error: | ||||
| 
 | ||||
| > We can't display this page because your browser blocks cross-domain cookies, but you can view this page in Salesforce Classic. | ||||
| 
 | ||||
| Click the link to open the page in Salesforce Classic. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
|   </TabItem> | ||||
|   <TabItem value="classique" label="Classic"> | ||||
| 
 | ||||
| A) Click the "Setup" link in the top-right corner of the page. | ||||
| 
 | ||||
| B) Type "Static" in the left sidebar search box. Click "Static Resources" | ||||
| 
 | ||||
|   </TabItem> | ||||
| </Tabs> | ||||
| 
 | ||||
| The page in Salesforce Classic will look like the screenshot below: | ||||
| 
 | ||||
|  | ||||
| 
 | ||||
| ### Test the Static Resource | ||||
| 
 | ||||
| 21) Replace `force-app/main/default/lwc/sheetComponent/sheetComponent.js` with | ||||
| the following script: | ||||
| 
 | ||||
| ```js title="force-app/main/default/lwc/sheetComponent/sheetComponent.js" | ||||
| import { LightningElement } from 'lwc'; | ||||
| import { loadScript } from 'lightning/platformResourceLoader'; | ||||
| import sheetjs from '@salesforce/resourceUrl/sheetjs'; | ||||
| 
 | ||||
| export default class SheetComponent extends LightningElement { | ||||
|   version = "???"; // start with ??? | ||||
|   async connectedCallback() { | ||||
|     await loadScript(this, sheetjs); // load the library | ||||
|     // At this point, the library is accessible with the `XLSX` variable | ||||
|     // highlight-next-line | ||||
|     this.version = XLSX.version; | ||||
|   } | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| This component exposes a `version` property based on the SheetJS version. | ||||
| 
 | ||||
| 22) Replace `force-app/main/default/lwc/sheetComponent/sheetComponent.html` with | ||||
| the following template: | ||||
| 
 | ||||
| ```html title="force-app/main/default/lwc/sheetComponent/sheetComponent.html" | ||||
| <template> | ||||
|   <!-- highlight-next-line --> | ||||
|   <b>SheetForce {version}</b> | ||||
| </template> | ||||
| ``` | ||||
| 
 | ||||
| This template references the `version` property. | ||||
| 
 | ||||
| 23) Deploy the project again. Replace `SF@USER.NAME` with the unique Username: | ||||
| 
 | ||||
| ```bash | ||||
| npx @salesforce/cli project deploy start -d force-app -o SF@USER.NAME | ||||
| ``` | ||||
| 
 | ||||
| 24) Reload the "SheetJS Demo" page. The page should display the SheetJS version: | ||||
| 
 | ||||
|  | ||||
| 
 | ||||
| :::info pass | ||||
| 
 | ||||
| It may take a few minutes for Salesforce to refresh. If the demo still shows the | ||||
| original "SheetForce demo" text after refreshing, close and reopen the demo app. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| ### Export Data from SF Lists | ||||
| 
 | ||||
| 25) Add a button to `sheetComponent.html` that will call a `download` callback: | ||||
| 
 | ||||
| ```html title="force-app/main/default/lwc/sheetComponent/sheetComponent.html" | ||||
| <template> | ||||
| @ -336,7 +646,7 @@ This is readily exported to a spreadsheet in a callback function: | ||||
| </template> | ||||
| ``` | ||||
| 
 | ||||
| 2) Replace `sheetComponent.js` with the following: | ||||
| 26) Replace `sheetComponent.js` with the following: | ||||
| 
 | ||||
| ```js title="force-app/main/default/lwc/sheetComponent/sheetComponent.js" | ||||
| import { LightningElement, wire, api } from 'lwc'; | ||||
| @ -373,13 +683,21 @@ export default class SheetComponent extends LightningElement { | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| 3) Re-deploy and refresh the app page: | ||||
| 27) Deploy the project again. Replace `SF@USER.NAME` with the unique Username: | ||||
| 
 | ||||
|  | ||||
| ```bash | ||||
| npx @salesforce/cli project deploy start -d force-app -o SF@USER.NAME | ||||
| ``` | ||||
| 
 | ||||
| The simple export has all of the data: | ||||
| 28) Reload the "SheetJS Demo" page. The page should include a button for export: | ||||
| 
 | ||||
|  | ||||
|  | ||||
| 
 | ||||
| 29) Click the "Click to Export!" button. The app will attempt to download a file. | ||||
| 
 | ||||
| The simple export includes all of the data: | ||||
| 
 | ||||
|  | ||||
| 
 | ||||
| :::tip pass | ||||
| 
 | ||||
| @ -387,3 +705,12 @@ The simple export has all of the data: | ||||
| cell styling, automatic column width calculations, and frozen rows. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| [^1]: It is strongly recommended to review the [detailed introduction in the Salesforce documentation](https://developer.salesforce.com/docs/platform/lwc/guide/get-started-introduction.html) | ||||
| [^2]: The `XLSX` variable is the main global for the SheetJS library. It exposes methods as described in ["API Reference"](/docs/api/) | ||||
| [^3]: See [`aoa_to_sheet` in "Utilities"](/docs/api/utilities/array#array-of-arrays-input) | ||||
| [^4]: See ["Sheet Objects"](/docs/csf/sheet) | ||||
| [^5]: See ["Workbook Object"](/docs/csf/book) | ||||
| [^6]: See [`book_new` in "Utilities"](/docs/api/utilities/wb) | ||||
| [^7]: See [`book_append_sheet` in "Utilities"](/docs/api/utilities/wb) | ||||
| [^8]: See [`writeFile` in "Writing Files"](/docs/api/write-options) | ||||
| @ -7,8 +7,22 @@ pagination_next: demos/extensions/index | ||||
| import current from '/version.js'; | ||||
| import CodeBlock from '@theme/CodeBlock'; | ||||
| 
 | ||||
| AWS is a Cloud Services platform which includes traditional virtual machine | ||||
| support, "Serverless Functions", cloud storage and much more. | ||||
| [Amazon Web Services](https://aws.amazon.com/) (AWS) 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: | ||||
| 
 | ||||
| - ["Lambda Functions"](#lambda-functions) ("Lambda") explores the serverless | ||||
|   computing offering. The demo creates a JavaScript function that can process | ||||
|   user-submitted files and generate spreadsheets. | ||||
| 
 | ||||
| - ["S3 Storage"](#s3-storage) explores the cloud storage ("S3") offering. The | ||||
|   demo uses the NodeJS connection library to read spreadsheets from S3 and write | ||||
|   spreadsheets to a S3 bucket. | ||||
| 
 | ||||
| :::caution pass | ||||
| 
 | ||||
| @ -17,30 +31,86 @@ will be available in the future. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| This demo focuses on two key offerings: cloud storage ("S3") and the | ||||
| "Serverless Function" platform ("Lambda"). | ||||
| 
 | ||||
| The [NodeJS Module](/docs/getting-started/installation/nodejs) can be shipped in | ||||
| a bundled Lambda function. | ||||
| 
 | ||||
| :::note | ||||
| 
 | ||||
| This was tested on 2023 April 24. | ||||
| This demo was last tested on 2023 October 01. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| ## AWS Lambda Functions | ||||
| ## Lambda Functions | ||||
| 
 | ||||
| AWS offers the NodeJS runtime for JavaScript serverless function.[^1] | ||||
| 
 | ||||
| The [SheetJS NodeJS module](/docs/getting-started/installation/nodejs) can be | ||||
| required in Lambda functions. When deploying, the entire `node_modules` folder | ||||
| can be added to the ZIP package. | ||||
| 
 | ||||
| :::note pass | ||||
| 
 | ||||
| In this demo, the "Function URL" (automatic API Gateway management) features | ||||
| are used.  Older deployments required special "Binary Media Types" to handle | ||||
| formats like XLSX.  At the time of testing, the configuration was not required. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| :::info pass | ||||
| 
 | ||||
| Node.js runtime can use `x86_64` or `arm64` CPU architectures. SheetJS libraries | ||||
| work on both platforms in Linux, Windows, and macOS operating systems. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| ### Reading Data | ||||
| 
 | ||||
| In the Lambda handler method, the `event.body` attribute is a Base64-encoded | ||||
| string.  The `busboy` body parser can accept a decoded body. | ||||
| In the Lambda handler, the `event.body` attribute is a Base64-encoded string | ||||
| representing the HTTP request form data. This body must be parsed. | ||||
| 
 | ||||
| <details open><summary><b>Code Sample</b> (click to hide)</summary> | ||||
| #### Processing Form Bodies | ||||
| 
 | ||||
| The `busboy` body parser[^2] is battle-tested in NodeJS deployments. | ||||
| 
 | ||||
| `busboy` fires a `'file'` event for every file in the form body. The callback | ||||
| receives a NodeJS stream that should be collected into a Buffer: | ||||
| 
 | ||||
| ```js | ||||
| /* accumulate the files manually */ | ||||
| var files = {}; | ||||
| bb.on('file', function(fieldname, file, filename) { | ||||
|   /* concatenate the individual data buffers */ | ||||
|   var buffers = []; | ||||
|   file.on('data', function(data) { buffers.push(data); }); | ||||
|   file.on('end', function() { files[fieldname] = Buffer.concat(buffers); }); | ||||
| }); | ||||
| ``` | ||||
| 
 | ||||
| `busboy` fires a `'finish'` event when the body parsing is finished. Callbacks | ||||
| can assume every file in the form body has been stored in NodeJS Buffer objects. | ||||
| 
 | ||||
| #### Processing NodeJS Buffers | ||||
| 
 | ||||
| The SheetJS `read` method[^3] can read the Buffer objects and generate SheetJS | ||||
| workbook objects[^4] which can be processed with other API functions. | ||||
| 
 | ||||
| For example, a handler can use `sheet_to_csv`[^5] to generate CSV text: | ||||
| 
 | ||||
| ```js | ||||
| /* on the finish event, all of the fields and files are ready */ | ||||
| bb.on('finish', function() { | ||||
|   /* grab the first file */ | ||||
|   var f = files["upload"]; | ||||
|   if(!f) callback(new Error("Must submit a file for processing!")); | ||||
| 
 | ||||
|   /* f[0] is a buffer */ | ||||
|   // highlight-next-line | ||||
|   var wb = XLSX.read(f[0]); | ||||
| 
 | ||||
|   /* grab first worksheet and convert to CSV */ | ||||
|   var ws = wb.Sheets[wb.SheetNames[0]]; | ||||
|   callback(null, { statusCode: 200, body: XLSX.utils.sheet_to_csv(ws) }); | ||||
| }); | ||||
| ``` | ||||
| 
 | ||||
| <details><summary><b>Complete Code Sample</b> (click to show)</summary> | ||||
| 
 | ||||
| This example takes the first uploaded file submitted with the key `upload`, | ||||
| parses the file and returns the CSV content of the first worksheet. | ||||
| @ -92,12 +162,38 @@ exports.handler = function(event, context, callback) { | ||||
| 
 | ||||
| ### Writing Data | ||||
| 
 | ||||
| For safely transmitting binary data, the `base64` type should be used.  Lambda | ||||
| callback response `isBase64Encoded` property forces a binary download. | ||||
| For safely transmitting binary data, Base64 strings should be used. | ||||
| 
 | ||||
| <details open><summary><b>Code Sample</b> (click to hide)</summary> | ||||
| The SheetJS `write` method[^6] with the option `type: "base64"` will generate | ||||
| Base64-encoded strings. | ||||
| 
 | ||||
| This example generates a sample workbook and writes to a XLSX workbook. | ||||
| ```js | ||||
| /* sample SheetJS workbook object */ | ||||
| var wb = XLSX.read("S,h,e,e,t,J,S\n5,4,3,3,7,9,5", {type: "binary"}); | ||||
| /* write to XLSX file in Base64 encoding */ | ||||
| var b64 = XLSX.write(wb, { type: "base64", bookType: "xlsx" }); | ||||
| ``` | ||||
| 
 | ||||
| The Lambda callback response function accepts options. Setting `isBase64Encoded` | ||||
| to `true` will ensure the callback handler decodes the data. To ensure browsers | ||||
| will try to download the response, the `Content-Disposition` header must be set: | ||||
| 
 | ||||
| ```js | ||||
| callback(null, { | ||||
|   statusCode: 200, | ||||
|   /* Base64-encoded file */ | ||||
|   isBase64Encoded: true, | ||||
|   body: b64, | ||||
|   headers: { | ||||
|     /* Browsers will treat the response as the file SheetJSLambda.xlsx */ | ||||
|     "Content-Disposition": 'attachment; filename="SheetJSLambda.xlsx"' | ||||
|   } | ||||
| }); | ||||
| ``` | ||||
| 
 | ||||
| <details><summary><b>Complete Code Sample</b> (click to show)</summary> | ||||
| 
 | ||||
| This example creates a sample workbook object and sends the file in the response: | ||||
| 
 | ||||
| ```js | ||||
| var XLSX = require('xlsx'); | ||||
| @ -123,11 +219,18 @@ exports.handler = function(event, context, callback) { | ||||
| 
 | ||||
| </details> | ||||
| 
 | ||||
| ### Demo | ||||
| ### Lambda Demo | ||||
| 
 | ||||
| <details open><summary><b>Complete Example</b> (click to hide)</summary> | ||||
| :::note pass | ||||
| 
 | ||||
| 0) Review the quick start for JavaScript on AWS | ||||
| At the time of writing, the AWS Free Tier included an allowance of 1 million | ||||
| free requests per month and 400 thousand GB-seconds of compute resources. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| 0) If you do not have an account, create a new AWS free tier account[^7]. | ||||
| 
 | ||||
| #### Create Project ZIP | ||||
| 
 | ||||
| 1) Create a new folder and download [`index.js`](pathname:///aws/index.js): | ||||
| 
 | ||||
| @ -150,28 +253,67 @@ npm i https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz busboy`} | ||||
| yes | zip -c ../SheetJSLambda.zip -r . | ||||
| ``` | ||||
| 
 | ||||
| 4) In the web interface for AWS Lambda, create a new Function with the options: | ||||
| #### Lambda Setup | ||||
| 
 | ||||
| - Select "Author from scratch" (default choice when last verified) | ||||
| - "Function Name": SheetJSLambda | ||||
| - "Runtime": "Node.js" (select the version in the "Latest supported" block) | ||||
| - Advanced Settings: | ||||
|   + check "Enable function URL" | ||||
|   + Auth type: NONE | ||||
| 4) Sign into the [AWS Management Console](https://aws.amazon.com/console/) with | ||||
| a root user account. | ||||
| 
 | ||||
| 5) Type "Lambda" in the top search box and click Lambda (under Services). | ||||
| 
 | ||||
| 6) Open "Functions" in the left sidebar. | ||||
| 
 | ||||
| If the left sidebar is not open, click the `≡` icon in the left edge of the page. | ||||
| 
 | ||||
| 7) Click the "Create function" button in the main panel. | ||||
| 
 | ||||
| 8) Select the following options: | ||||
| 
 | ||||
| - In the top list, select "Author from scratch" (default choice) | ||||
| 
 | ||||
| - Type a memorable "Function Name" ("SheetJSLambda" when last tested) | ||||
| 
 | ||||
| - In the "Runtime" dropdown, look for the "Latest supported" section and select | ||||
|   "Node.js" ("Node.js 18.x" when last tested) | ||||
| 
 | ||||
| - Expand "Advanced Settings" and check "Enable function URL". This will display | ||||
|   a few sub-options: | ||||
|   + "Auth type" select "NONE" (disable IAM authentication) | ||||
|   + Check "Configure cross-origin resource sharing (CORS)" | ||||
| 
 | ||||
| 5) In the Interface, click "Upload from" and select ".zip file".  Click the | ||||
| "Upload" button in the modal, select SheetJSLambda.zip, and click "Save". | ||||
| 9) Click "Create function" to create the function. | ||||
| 
 | ||||
| #### Upload Code | ||||
| 
 | ||||
| 10) In the Interface, scroll down and select the "Code" tab. | ||||
| 
 | ||||
| 11) Click the "Upload from" dropdown and select ".zip file". | ||||
| 
 | ||||
| 12) Click the "Upload" button in the modal. With the file picker, select the | ||||
| `SheetJSLambda.zip` file created in step 3. Click "Save". | ||||
| 
 | ||||
| :::note pass | ||||
| When the demo was last tested, the ZIP was small enough that the Lambda code | ||||
| editor will load the package. | ||||
| 
 | ||||
| 6) Enable external access to the function. | ||||
| ::: | ||||
| 
 | ||||
| Under Configuration > Function URL, click "Edit" and ensure that Auth type is | ||||
| set to NONE.  If it is not, select NONE and click Save. | ||||
| 13) In the code editor, double-click `index.js` and confirm the code editor | ||||
| displays JavaScript code. | ||||
| 
 | ||||
| #### External Access | ||||
| 
 | ||||
| 14) Click "Configuration" in the tab list. | ||||
| 
 | ||||
| 15) In the sidebar below the tab list, select "Function URL" and click "Edit". | ||||
| 
 | ||||
| 16) Set the "Auth type" to "NONE" and click Save. The page will redirect to the | ||||
| 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  | ||||
| `FunctionURLAllowPublicAccess` is listed. | ||||
| 
 | ||||
| Under Configuration > Permissions, scroll down to "Resource-based policy". | ||||
| If no policy statements are defined, select "Add Permission" with the options: | ||||
| 
 | ||||
| - Select "Function URL" at the top | ||||
| @ -182,110 +324,260 @@ If no policy statements are defined, select "Add Permission" with the options: | ||||
| 
 | ||||
| Click "Save" and a new Policy statement should be created. | ||||
| 
 | ||||
| 7) Find the Function URL (It is in the "Function Overview" section). | ||||
| #### Lambda Testing | ||||
| 
 | ||||
| Try to access that URL in a web browser and the site will try to download | ||||
| `SheetJSLambda.xlsx`.  Save and open the file to confirm it is valid. | ||||
| 19) Find the Function URL (It is in the "Function Overview" section). | ||||
| 
 | ||||
| To test parsing, download <https://sheetjs.com/pres.numbers> and make a POST | ||||
| request to the public function URL (change `FUNCTION_URL` in the command): | ||||
| 20) Try to access the function URL in a web browser. | ||||
| 
 | ||||
| The site will attempt to download `SheetJSLambda.xlsx`.  Save and open the file | ||||
| to confirm it is valid. | ||||
| 
 | ||||
| 21) Download <https://sheetjs.com/pres.numbers> and make a POST request to the | ||||
| public function URL. | ||||
| 
 | ||||
| This can be tested on the command line. Change `FUNCTION_URL` in the commands: | ||||
| 
 | ||||
| ```bash | ||||
| curl -LO https://sheetjs.com/pres.numbers | ||||
| curl -X POST -F "upload=@pres.numbers" FUNCTION_URL | ||||
| ``` | ||||
| 
 | ||||
| The result should be a CSV output of the first sheet. | ||||
| 
 | ||||
| </details> | ||||
| The terminal will display CSV output of the first sheet. | ||||
| 
 | ||||
| ## S3 Storage | ||||
| 
 | ||||
| The main module for S3 and all AWS services is `aws-sdk`. | ||||
| The main NodeJS module for S3 and all AWS services is `aws-sdk`[^8]. | ||||
| 
 | ||||
| The [SheetJS NodeJS module](/docs/getting-started/installation/nodejs) can be | ||||
| required in NodeJS scripts. | ||||
| 
 | ||||
| ### Connecting to S3 | ||||
| 
 | ||||
| The `AWS` module includes a function `S3` that performs the connection. Access | ||||
| keys for an IAM user[^9] must be used: | ||||
| 
 | ||||
| ```js | ||||
| /* credentials */ | ||||
| var accessKeyId = "...", secretAccessKey = "...""; | ||||
| 
 | ||||
| /* file location */ | ||||
| var Bucket = "...", Key = "pres.numbers"; | ||||
| 
 | ||||
| /* connect to s3 account */ | ||||
| var AWS = require('aws-sdk'); | ||||
| var s3 = new AWS.S3({ | ||||
|   apiVersion: '2006-03-01', | ||||
|   credentials: { accessKeyId, secretAccessKey } | ||||
| }); | ||||
| 
 | ||||
| ``` | ||||
| 
 | ||||
| ### Reading Data | ||||
| 
 | ||||
| #### Fetching Files from S3 | ||||
| 
 | ||||
| The `s3#getObject` method returns an object with a `createReadStream` method. | ||||
| Buffers can be concatenated and passed to `XLSX.read`. | ||||
| `createReadStream` returns a NodeJS stream: | ||||
| 
 | ||||
| <details open><summary><b>Demo</b> (click to hide)</summary> | ||||
| ```js | ||||
| /* open stream to the file */ | ||||
| var stream = s3.getObject({ Bucket: Bucket, Key: Key }).createReadStream(); | ||||
| ``` | ||||
| 
 | ||||
| This sample fetches a buffer from S3 and parses the workbook. | ||||
| #### Concatenating NodeJS Streams | ||||
| 
 | ||||
| 1) Save the following script to `SheetJSReadFromS3.js`: | ||||
| Buffers can be concatenated from the stream into one unified Buffer object: | ||||
| 
 | ||||
| ```js title="SheetJSReadFromS3.js" | ||||
| var XLSX = require("xlsx"); | ||||
| var AWS = require('aws-sdk'); | ||||
| 
 | ||||
| /* replace these constants */ | ||||
| var accessKeyId = "<REPLACE WITH ACCESS KEY ID>"; | ||||
| var secretAccessKey = "<REPLACE WITH SECRET ACCESS KEY>"; | ||||
| var Bucket = "<REPLACE WITH BUCKET NAME>"; | ||||
| 
 | ||||
| var Key = "pres.numbers"; | ||||
| 
 | ||||
| /* Get stream */ | ||||
| var s3 = new AWS.S3({ | ||||
|   apiVersion: '2006-03-01', | ||||
|   credentials: { | ||||
|     accessKeyId: accessKeyId, | ||||
|     secretAccessKey: secretAccessKey | ||||
|   } | ||||
| }); | ||||
| var f = s3.getObject({ Bucket: Bucket, Key: Key }).createReadStream(); | ||||
| 
 | ||||
| /* collect data */ | ||||
| ```js | ||||
| /* array of buffers */ | ||||
| var bufs = []; | ||||
| f.on('data', function(data) { bufs.push(data); }); | ||||
| f.on('end', function() { | ||||
|   /* concatenate and parse */ | ||||
|   var wb = XLSX.read(Buffer.concat(bufs)); | ||||
|   console.log(XLSX.utils.sheet_to_csv(wb.Sheets[wb.SheetNames[0]])); | ||||
| /* add each data chunk to the array */ | ||||
| stream.on('data', function(data) { bufs.push(data); }); | ||||
| /* the callback will be called after all of the data is collected */ | ||||
| stream.on('end', function() { | ||||
|   /* concatenate */ | ||||
|   var buf = Buffer.concat(bufs); | ||||
| 
 | ||||
|   /* AT THIS POINT, `buf` is a NodeJS Buffer */ | ||||
| }); | ||||
| ``` | ||||
| 
 | ||||
| 2) Create a new bucket (or get the name of an existing bucket). | ||||
| #### Parsing NodeJS Buffers | ||||
| 
 | ||||
| 3) Download <https://sheetjs.com/pres.numbers> | ||||
| The SheetJS `read` method[^10] can read the final object and generate SheetJS | ||||
| workbook objects[^11] which can be processed with other API functions. | ||||
| 
 | ||||
| In the S3 site, open the bucket and click "Upload". In the Upload page, click | ||||
| and drag the `pres.numbers` file into the browser window and click "Upload". | ||||
| For example, a callback can use `sheet_to_csv`[^12] to generate CSV text: | ||||
| 
 | ||||
| 4) Edit `SheetJSReadFromS3.js` and replace the marked constants: | ||||
| ```js | ||||
| stream.on('end', function() { | ||||
|   /* concatenate */ | ||||
|   var buf = Buffer.concat(bufs); | ||||
| 
 | ||||
| - `accessKeyId`: access key for the AWS account | ||||
| - `secretAccessKey`: secret access key for the AWS account | ||||
| - `Bucket`: name of the bucket | ||||
|   /* parse */ | ||||
|   var wb = XLSX.read(Buffer.concat(bufs)); | ||||
| 
 | ||||
| 5) Run the script: | ||||
| 
 | ||||
| ```bash | ||||
| node SheetJSReadFromS3.js | ||||
|   /* generate CSV from first worksheet */ | ||||
|   var first_ws = wb.Sheets[wb.SheetNames[0]]; | ||||
|   var csv = XLSX.utils.sheet_to_csv(first_ws); | ||||
|   console.log(csv); | ||||
| }); | ||||
| ``` | ||||
| 
 | ||||
| The program will display the data in CSV format. | ||||
| 
 | ||||
| </details> | ||||
| 
 | ||||
| ### Writing Data | ||||
| 
 | ||||
| `S3#upload` directly accepts a Buffer. | ||||
| The SheetJS `write` method[^13] with the option `type: "buffer"` will generate | ||||
| NodeJS Buffers. `S3#upload` directly accepts these Buffer objects. | ||||
| 
 | ||||
| <details open><summary><b>Demo</b> (click to hide)</summary> | ||||
| This example creates a sample workbook object, generates XLSX file data in a | ||||
| NodeJS Buffer, and uploads the data to S3: | ||||
| 
 | ||||
| ```js | ||||
| /* generate sample workbook */ | ||||
| var wb = XLSX.read("S,h,e,e,t,J,S\n5,4,3,3,7,9,5", {type: "binary"}); | ||||
| 
 | ||||
| /* write to XLSX file in a NodeJS Buffer */ | ||||
| var Body = XLSX.write(wb, {type: "buffer", bookType: "xlsx"}); | ||||
| 
 | ||||
| /* upload buffer */ | ||||
| s3.upload({ Bucket, Key, Body }, function(err, data) { | ||||
|   if(err) throw err; | ||||
|   console.log("Uploaded to " + data.Location); | ||||
| }); | ||||
| ``` | ||||
| 
 | ||||
| ### S3 Demo | ||||
| 
 | ||||
| :::note pass | ||||
| 
 | ||||
| At the time of writing, the AWS Free Tier included 5GB of S3 storage with 20,000 | ||||
| Get requests and 2000 Put requests per month. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| This sample fetches a buffer from S3 and parses the workbook. | ||||
| 
 | ||||
| 0) If you do not have an account, create a new AWS free tier account[^14]. | ||||
| 
 | ||||
| #### Create S3 Bucket | ||||
| 
 | ||||
| 1) Sign into the [AWS Management Console](https://aws.amazon.com/console/) with | ||||
| a root user account. | ||||
| 
 | ||||
| 2) Type "S3" in the top search box and click S3 (under Services). | ||||
| 
 | ||||
| 3) Open "Buckets" in the left sidebar. | ||||
| 
 | ||||
| If the left sidebar is not open, click the `≡` icon in the left edge of the page. | ||||
| 
 | ||||
| 4) Click the "Create bucket" button in the main panel. | ||||
| 
 | ||||
| 5) Select the following options: | ||||
| 
 | ||||
| - Type a memorable "Bucket Name" ("sheetjsbouquet" when last tested) | ||||
| 
 | ||||
| - In the "Object Ownership" section, select "ACLs disabled" | ||||
| 
 | ||||
| - Check "Block *all* public access" | ||||
| 
 | ||||
| - Look for the "Bucket Versioning" section and select "Disable" | ||||
| 
 | ||||
| 6) Click "Create bucket" to create the bucket. | ||||
| 
 | ||||
| #### Create IAM User | ||||
| 
 | ||||
| 7) Type "IAM" in the top search box and click IAM (under Services). | ||||
| 
 | ||||
| 8) Open "Users" in the left sidebar. | ||||
| 
 | ||||
| If the left sidebar is not open, click the `≡` icon in the left edge of the page. | ||||
| 
 | ||||
| 9) Click the "Create user" button in the main panel. | ||||
| 
 | ||||
| 10) In step 1, type a memorable "Bucket Name" ("sheetjs-user" when last tested). | ||||
| Click "Next". | ||||
| 
 | ||||
| 11) In step 2, click "Next" | ||||
| 
 | ||||
| 12) In step 3, click "Create user" to create the user. | ||||
| 
 | ||||
| #### Add Permissions | ||||
| 
 | ||||
| 13) Click the new user name in the Users table. | ||||
| 
 | ||||
| 14) Select the "Permissions" tab | ||||
| 
 | ||||
| 15) Click the "Add permissions" dropdown and select "Add permissions". | ||||
| 
 | ||||
| 16) Select "Attach policies directly". | ||||
| 
 | ||||
| 17) In the "Permissions policies" section, search for "AmazonS3FullAccess". | ||||
| There should be one entry. | ||||
| 
 | ||||
| 18) Check the checkbox next to "AmazonS3FullAccess" and click the "Next" button. | ||||
| 
 | ||||
| 19) In the "Review" screen, click "Add permissions" | ||||
| 
 | ||||
| #### Generate Keys | ||||
| 
 | ||||
| 20) Click "Security credentials", then click "Create access key". | ||||
| 
 | ||||
| 21) Select the "Local code" option. Check "I understand the above recommendation | ||||
| and want to proceed to create an access key." and click "Next" | ||||
| 
 | ||||
| 22) Click "Create Access Key" and click "Download .csv file" in the next screen. | ||||
| 
 | ||||
| In the generated CSV: | ||||
| 
 | ||||
| - Cell A2 is the "Access key ID" (`accessKeyId` in the AWS API) | ||||
| - Cell B2 is the "Secret access key" (`secretAccessKey` in the AWS API) | ||||
| 
 | ||||
| #### Set up Project | ||||
| 
 | ||||
| 23) Create a new NodeJS project: | ||||
| 
 | ||||
| ```bash | ||||
| mkdir SheetJSS3 | ||||
| cd SheetJSS3 | ||||
| npm init -y | ||||
| ``` | ||||
| 
 | ||||
| 24) Install dependencies: | ||||
| 
 | ||||
| <CodeBlock language="bash">{`\ | ||||
| mkdir -p node_modules | ||||
| npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz aws-sdk@2.1467.0`} | ||||
| </CodeBlock> | ||||
| 
 | ||||
| #### Write Test | ||||
| 
 | ||||
| :::note pass | ||||
| 
 | ||||
| This sample creates a simple workbook, generates a NodeJS buffer, and uploads | ||||
| the buffer to S3. | ||||
| 
 | ||||
| 1) Save the following script to `SheetJSWriteToS3.js`: | ||||
| ``` | ||||
|    | A | B | C | D | E | F | G | | ||||
| ---+---|---|---|---|---|---|---| | ||||
|  1 | S | h | e | e | t | J | S | | ||||
|  2 | 5 | 4 | 3 | 3 | 7 | 9 | 5 | | ||||
| ``` | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| 25) Save the following script to `SheetJSWriteToS3.js`: | ||||
| 
 | ||||
| ```js title="SheetJSWriteToS3.js" | ||||
| var XLSX = require("xlsx"); | ||||
| var AWS = require('aws-sdk'); | ||||
| 
 | ||||
| /* replace these constants */ | ||||
| // highlight-start | ||||
| var accessKeyId = "<REPLACE WITH ACCESS KEY ID>"; | ||||
| var secretAccessKey = "<REPLACE WITH SECRET ACCESS KEY>"; | ||||
| var Bucket = "<REPLACE WITH BUCKET NAME>"; | ||||
| // highlight-end | ||||
| 
 | ||||
| var Key = "test.xlsx"; | ||||
| 
 | ||||
| @ -308,21 +600,94 @@ s3.upload({ Bucket: Bucket, Key: Key, Body: Body }, function(err, data) { | ||||
| }); | ||||
| ``` | ||||
| 
 | ||||
| 2) Create a new bucket (or get the name of an existing bucket). | ||||
| 
 | ||||
| 3) Edit `SheetJSWriteToS3.js` and replace the marked constants: | ||||
| 26) Edit `SheetJSWriteToS3.js` and replace the highlighted lines: | ||||
| 
 | ||||
| - `accessKeyId`: access key for the AWS account | ||||
| - `secretAccessKey`: secret access key for the AWS account | ||||
| - `Bucket`: name of the bucket | ||||
| 
 | ||||
| 4) Run the script: | ||||
| The keys are found in the CSV from step 22. The Bucket is the name from step 5. | ||||
| 
 | ||||
| 27) Run the script: | ||||
| 
 | ||||
| ```bash | ||||
| node SheetJSWriteToS3.js | ||||
| ``` | ||||
| 
 | ||||
| 5) In the S3 site, select the bucket and download the object named `test.xlsx`. | ||||
| Open the file in a spreadsheet editor. | ||||
| This file will be stored with the object name `test.xlsx`. It can be manually | ||||
| downloaded from the S3 web interface. | ||||
| 
 | ||||
| </details> | ||||
| #### Read Test | ||||
| 
 | ||||
| This sample will download and process the test file from "Write Test". | ||||
| 
 | ||||
| 28) Save the following script to `SheetJSReadFromS3.js`: | ||||
| 
 | ||||
| ```js title="SheetJSReadFromS3.js" | ||||
| var XLSX = require("xlsx"); | ||||
| var AWS = require('aws-sdk'); | ||||
| 
 | ||||
| /* replace these constants */ | ||||
| // highlight-start | ||||
| var accessKeyId = "<REPLACE WITH ACCESS KEY ID>"; | ||||
| var secretAccessKey = "<REPLACE WITH SECRET ACCESS KEY>"; | ||||
| var Bucket = "<REPLACE WITH BUCKET NAME>"; | ||||
| // highlight-end | ||||
| 
 | ||||
| var Key = "test.xlsx"; | ||||
| 
 | ||||
| /* Get stream */ | ||||
| var s3 = new AWS.S3({ | ||||
|   apiVersion: '2006-03-01', | ||||
|   credentials: { | ||||
|     accessKeyId: accessKeyId, | ||||
|     secretAccessKey: secretAccessKey | ||||
|   } | ||||
| }); | ||||
| var f = s3.getObject({ Bucket: Bucket, Key: Key }).createReadStream(); | ||||
| 
 | ||||
| /* collect data */ | ||||
| var bufs = []; | ||||
| f.on('data', function(data) { bufs.push(data); }); | ||||
| f.on('end', function() { | ||||
|   /* concatenate and parse */ | ||||
|   var wb = XLSX.read(Buffer.concat(bufs)); | ||||
|   console.log(XLSX.utils.sheet_to_csv(wb.Sheets[wb.SheetNames[0]])); | ||||
| }); | ||||
| ``` | ||||
| 
 | ||||
| 29) Edit `SheetJSReadFromS3.js` and replace the highlighted lines: | ||||
| 
 | ||||
| - `accessKeyId`: access key for the AWS account | ||||
| - `secretAccessKey`: secret access key for the AWS account | ||||
| - `Bucket`: name of the bucket | ||||
| 
 | ||||
| The keys are found in the CSV from Step 22. The Bucket is the name from Step 5. | ||||
| 
 | ||||
| 30) Run the script: | ||||
| 
 | ||||
| ```bash | ||||
| node SheetJSReadFromS3.js | ||||
| ``` | ||||
| 
 | ||||
| The program will display the data in CSV format. | ||||
| 
 | ||||
| ``` | ||||
| S,h,e,e,t,J,S | ||||
| 5,4,3,3,7,9,5 | ||||
| ``` | ||||
| 
 | ||||
| [^1]: See ["Building Lambda functions with Node.js"](https://docs.aws.amazon.com/lambda/latest/dg/lambda-nodejs.html) in the AWS documentation | ||||
| [^2]: The `busboy` module is distributed [on the public NPM registry](https://npm.im/busboy) | ||||
| [^3]: See [`read` in "Reading Files"](/docs/api/parse-options) | ||||
| [^4]: See ["Workbook Object" in "SheetJS Data Model"](/docs/csf/book) for more details. | ||||
| [^5]: See [`sheet_to_csv` in "CSV and Text"](/docs/api/utilities/csv#delimiter-separated-output) | ||||
| [^6]: See [`write` in "Writing Files"](/docs/api/write-options) | ||||
| [^7]: Registering for a free account [on the AWS Free Tier](https://aws.amazon.com/free/) requires a valid phone number and a valid credit card. | ||||
| [^8]: The `aws-sdk` module is distributed [on the public NPM registry](https://npm.im/aws-sdk) | ||||
| [^9]: See ["Managing access keys for IAM users"](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html) in the AWS documentation | ||||
| [^10]: See [`read` in "Reading Files"](/docs/api/parse-options) | ||||
| [^11]: See ["Workbook Object" in "SheetJS Data Model"](/docs/csf/book) for more details. | ||||
| [^12]: See [`sheet_to_csv` in "CSV and Text"](/docs/api/utilities/csv#delimiter-separated-output) | ||||
| [^13]: See [`write` in "Writing Files"](/docs/api/write-options) | ||||
| [^14]: Registering for a free account [on the AWS Free Tier](https://aws.amazon.com/free/) requires a valid phone number and a valid credit card. | ||||
| @ -22,7 +22,7 @@ and the "Serverless Function" platform ("Azure Functions"). | ||||
| 
 | ||||
| :::note | ||||
| 
 | ||||
| This was tested on 2023 April 29. | ||||
| This demo was last tested on 2023 April 29. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
|  | ||||
| @ -41,8 +41,8 @@ These instructions were tested on the following platforms: | ||||
| |:------------------------------|:-----------| | ||||
| | Linux (Steam Deck Holo x64)   | 2023-09-22 | | ||||
| | Linux (Ubuntu 18 AArch64)     | 2023-09-07 | | ||||
| | MacOS 10.13 (x64)             | 2023-04-04 | | ||||
| | MacOS 13.0 (ARM64)            | 2023-04-13 | | ||||
| | MacOS 10.13.6 (x64)           | 2023-09-30 | | ||||
| | MacOS 13.6 (ARM64)            | 2023-09-30 | | ||||
| | Windows 10 (x64) + WSL Ubuntu | 2023-07-23 | | ||||
| | Windows 11 (x64) + WSL Ubuntu | 2023-08-31 | | ||||
| | Windows 11 (ARM) + WSL Ubuntu | 2023-09-18 | | ||||
| @ -59,7 +59,7 @@ tests will pass in Windows XP with NodeJS 5.10.0. | ||||
| import Tabs from '@theme/Tabs'; | ||||
| import TabItem from '@theme/TabItem'; | ||||
| 
 | ||||
| <Tabs> | ||||
| <Tabs groupId="os"> | ||||
|   <TabItem value="wsl" label="Windows WSL"> | ||||
| 
 | ||||
| A) Ensure WSL ("WSL 2" in Windows 10) and the Ubuntu distribution are installed. | ||||
| @ -97,7 +97,7 @@ C) Install NodeJS | ||||
| 
 | ||||
| :::info pass | ||||
| 
 | ||||
| When this was last tested, the script showed a deprecation notice. | ||||
| In the most recent test, the script showed a deprecation notice. | ||||
| 
 | ||||
| **The script worked as expected.** | ||||
| 
 | ||||
| @ -164,10 +164,12 @@ sudo apt-get install -y unzip | ||||
|   </TabItem> | ||||
|   <TabItem value="osx" label="MacOS"> | ||||
| 
 | ||||
| A) Run `git`. If Xcode or the command-line tools are not installed, you will be | ||||
| asked to install.  Click "Install" and run through the steps. | ||||
| A) Open a terminal window and run `git`. | ||||
| 
 | ||||
| B) Open a terminal window and install Homebrew: | ||||
| If Xcode or the command-line tools are not installed, you will be asked to | ||||
| install. Click "Install" and run through the steps. | ||||
| 
 | ||||
| B) Open a terminal window and install the Homebrew package manager: | ||||
| 
 | ||||
| ```bash | ||||
| /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" | ||||
| @ -185,7 +187,7 @@ To confirm analytics are disabled, run | ||||
| brew analytics state | ||||
| ``` | ||||
| 
 | ||||
| It should print `Analytics are disabled.` | ||||
| The message should state that analytics are disabled or destroyed. | ||||
| 
 | ||||
| D) Install Mercurial and Subversion: | ||||
| 
 | ||||
| @ -193,15 +195,21 @@ D) Install Mercurial and Subversion: | ||||
| brew install mercurial subversion | ||||
| ``` | ||||
| 
 | ||||
| E) Install NodeJS | ||||
| E) Install NodeJS. | ||||
| 
 | ||||
| :::note pass | ||||
| 
 | ||||
| [The official NodeJS site](https://nodejs.org/en/download/) provides installers | ||||
| for "LTS" and "Current" releases.  The "LTS" version should be installed. | ||||
| 
 | ||||
| Older versions of macOS are not compatible with newer versions of NodeJS. In | ||||
| local testing, macOS 10.13 required NodeJS version `12.22.12` | ||||
| **Older versions of macOS are not compatible with newer versions of NodeJS.** | ||||
| 
 | ||||
| In local testing, macOS 10.13 required NodeJS version `12.22.12`: | ||||
| 
 | ||||
| ```bash | ||||
| curl -LO https://nodejs.org/download/release/v12.22.12/node-v12.22.12.pkg  | ||||
| open node-v12.22.12.pkg | ||||
| ``` | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| @ -289,6 +297,28 @@ git clone https://git.sheetjs.com/sheetjs/sheetjs | ||||
| cd sheetjs | ||||
| ``` | ||||
| 
 | ||||
| :::note pass | ||||
| 
 | ||||
| On older platforms, the clone may fail due to SSL certificate problems: | ||||
| 
 | ||||
| ```bash | ||||
| fatal: unable to access 'https://git.sheetjs.com/sheetjs/sheetjs/': SSL certificate problem: certificate has expired | ||||
| ``` | ||||
| 
 | ||||
| The simplest workaround is to disable SSL verification: | ||||
| 
 | ||||
| ```bash | ||||
| git config --global http.sslVerify false | ||||
| ``` | ||||
| 
 | ||||
| **It is strongly recommended to re-enable SSL verification after cloning**: | ||||
| 
 | ||||
| ```bash | ||||
| git config --global http.sslVerify true | ||||
| ``` | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| 1) Install NodeJS modules for building the scripts: | ||||
| 
 | ||||
| ```bash | ||||
| @ -334,6 +364,27 @@ make test_misc | ||||
| git checkout -- . | ||||
| ``` | ||||
| 
 | ||||
| :::info pass | ||||
| 
 | ||||
| In some tests on older releases of macOS, the build failed with an error: | ||||
| 
 | ||||
| ```bash | ||||
| ReferenceError: n is not defined | ||||
| ``` | ||||
| 
 | ||||
| The first error in the call stack points to `dist/xlsx.zahl.js`. | ||||
| 
 | ||||
| Older versions of macOS `sed` are known to misinterpret newline characters. The | ||||
| workaround is to upgrade to a newer version of `sed`. On macOS: | ||||
| 
 | ||||
| ```bash | ||||
| brew install gnu-sed | ||||
| echo 'export PATH="/usr/local/opt/gnu-sed/libexec/gnubin:$PATH"' >> ~/.profile | ||||
| . ~/.profile | ||||
| ``` | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| ### Reproduce official builds | ||||
| 
 | ||||
| 5) Run `git log` and search for the commit that matches a particular release | ||||
| @ -374,21 +425,71 @@ make dist | ||||
| 
 | ||||
| The local checksum for the browser script can be computed with: | ||||
| 
 | ||||
| <Tabs groupId="os"> | ||||
|   <TabItem value="wsl" label="Windows WSL"> | ||||
| 
 | ||||
| ```bash | ||||
| $ md5sum dist/xlsx.full.min.js | ||||
| 0b2f539797f92d35c6394274818f2c22  dist/xlsx.full.min.js | ||||
| md5sum dist/xlsx.full.min.js | ||||
| ``` | ||||
| 
 | ||||
|   </TabItem> | ||||
|   <TabItem value="osx" label="MacOS"> | ||||
| 
 | ||||
| ```bash | ||||
| md5 dist/xlsx.full.min.js | ||||
| ``` | ||||
| 
 | ||||
|   </TabItem> | ||||
|   <TabItem value="l" label="Linux"> | ||||
| 
 | ||||
| ```bash | ||||
| md5sum dist/xlsx.full.min.js | ||||
| ``` | ||||
| 
 | ||||
|   </TabItem> | ||||
| </Tabs> | ||||
| 
 | ||||
| 
 | ||||
| The checksum for the CDN version can be computed with: | ||||
| 
 | ||||
| <Tabs groupId="os"> | ||||
|   <TabItem value="wsl" label="Windows WSL"> | ||||
| 
 | ||||
| ```bash | ||||
| $ curl -L https://cdn.sheetjs.com/xlsx-0.20.0/package/dist/xlsx.full.min.js | md5sum - | ||||
| 0b2f539797f92d35c6394274818f2c22  - | ||||
| curl -L https://cdn.sheetjs.com/xlsx-0.20.0/package/dist/xlsx.full.min.js | md5sum - | ||||
| ``` | ||||
| 
 | ||||
|   </TabItem> | ||||
|   <TabItem value="osx" label="MacOS"> | ||||
| 
 | ||||
| ```bash | ||||
| curl -k -L https://cdn.sheetjs.com/xlsx-0.20.0/package/dist/xlsx.full.min.js | md5 | ||||
| ``` | ||||
| 
 | ||||
|   </TabItem> | ||||
|   <TabItem value="l" label="Linux"> | ||||
| 
 | ||||
| ```bash | ||||
| curl -L https://cdn.sheetjs.com/xlsx-0.20.0/package/dist/xlsx.full.min.js | md5sum - | ||||
| ``` | ||||
| 
 | ||||
|   </TabItem> | ||||
| </Tabs> | ||||
| 
 | ||||
| When the demo was last tested on macOS, against version `0.20.0`: | ||||
| 
 | ||||
| ```bash | ||||
| $ md5 dist/xlsx.full.min.js | ||||
| # highlight-next-line | ||||
| MD5 (dist/xlsx.full.min.js) = 0b2f539797f92d35c6394274818f2c22 | ||||
| $ curl -k -L https://cdn.sheetjs.com/xlsx-0.20.0/package/dist/xlsx.full.min.js | md5 | ||||
| # highlight-next-line | ||||
| 0b2f539797f92d35c6394274818f2c22 | ||||
| ``` | ||||
| 
 | ||||
| The two hashes should match. | ||||
| 
 | ||||
| 9) To return to the HEAD commit, run | ||||
| 9) Return to the `HEAD` commit: | ||||
| 
 | ||||
| ```bash | ||||
| git checkout master | ||||
|  | ||||
| Before Width: | Height: | Size: 81 KiB | 
| Before Width: | Height: | Size: 72 KiB | 
| Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 81 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docz/static/salesforce/export.png
									
									
									
									
									
										Normal file
									
								
							
							
								
									
								
								
								
								
								
									
									
								
							
						
						| After Width: | Height: | Size: 68 KiB | 
| Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 98 KiB | 
| Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docz/static/salesforce/version.png
									
									
									
									
									
										Normal file
									
								
							
							
								
									
								
								
								
								
								
									
									
								
							
						
						| After Width: | Height: | Size: 76 KiB | 
| Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 82 KiB |