--- pagination_prev: getting-started/index sidebar_position: 2 --- import current from '/version.js'; import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import CodeBlock from '@theme/CodeBlock'; # Tutorial SheetJS presents a simple JS interface that works with "Array of Arrays" and "Array of JS Objects". The API functions are building blocks that should be combined with other JS APIs to solve problems. The discussion focuses on the problem solving mindset. API details are covered in other parts of the documentation. The goal of this example is to generate a XLSX workbook of US President names and birthdays. [Click here](#live-demo) to jump to the live demo. The sequence diagram below shows the process: ```mermaid sequenceDiagram actor U as User participant P as Page participant A as API U->>P: click button P->>A: fetch data A->>P: raw data Note over P: process data Note over P: make workbook Note over P: setup download P->>U: download workbook ``` ## Acquire Data ### Raw Data [The raw data is available in JSON form](https://theunitedstates.io/congress-legislators/executive.json). For convenience, it has been [mirrored here](https://sheetjs.com/data/executive.json) Acquiring the data is straightforward with `fetch`: ```js const url = "https://sheetjs.com/data/executive.json"; const raw_data = await (await fetch(url)).json(); ``` The raw data is an Array of objects. This is the data for John Adams: ```js { "id": { /* (data omitted) */ }, "name": { "first": "John", // <-- first name "last": "Adams" // <-- last name }, "bio": { "birthday": "1735-10-19", // <-- birthday "gender": "M" }, "terms": [ { "type": "viceprez", /* (other fields omitted) */ }, { "type": "viceprez", /* (other fields omitted) */ }, { "type": "prez", /* (other fields omitted) */ } ] } ``` ### Filtering for Presidents The dataset includes Aaron Burr, a Vice President who was never President! `Array#filter` creates a new array with the desired rows. A President served at least one term with `type` set to `"prez"`. To test if a particular row has at least one `"prez"` term, `Array#some` is another native JS function. The complete filter would be: ```js const prez = raw_data.filter(row => row.terms.some(term => term.type === "prez")); ``` ### Sorting by First Term The dataset is sorted in chronological order by the first presidential or vice presidential term. The Vice President and President in a given term are sorted alphabetically. Joe Biden and Barack Obama were Vice President and President respectively in 2009. Since "Biden" is lexicographically before "Obama", Biden's data point appears first. The goal is to sort the presidents in order of their presidential term. The first step is adding the first presidential term start date to the dataset. `Array#find` will find the first value in an array that matches a criterion. The following code looks at each president and creates a `"start"` property that represents the start of the first presidential term. ```js prez.forEach(prez => prez.start = prez.terms.find(term => term.type === "prez").start); ``` `Array#sort` will sort the array. Since the `start` properties are strings, the recommended approach is to use `String#localeCompare` to compare strings: ```js prez.sort((l,r) => l.start.localeCompare(r.start)); ``` ### Reshaping the Array For this example, the name will be the first name combined with the last name (`row.name.first + " " + row.name.last`) and the birthday will be available at `row.bio.birthday`. Using `Array#map`, the dataset can be massaged in one call: ```js const rows = prez.map(row => ({ name: row.name.first + " " + row.name.last, birthday: row.bio.birthday })); ``` The result is an array of "simple" objects with no nesting: ```js [ { name: "George Washington", birthday: "1732-02-22" }, { name: "John Adams", birthday: "1735-10-19" }, // ... one row per President ] ``` ## Create a Workbook With the cleaned dataset, `XLSX.utils.json_to_sheet` generates a worksheet: ```js const worksheet = XLSX.utils.json_to_sheet(rows); ``` `XLSX.utils.book_new` creates a new workbook and `XLSX.utils.book_append_sheet` appends a worksheet to the workbook. The new worksheet will be called "Dates": ```js const workbook = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(workbook, worksheet, "Dates"); ``` ## Clean up Workbook The data is in the workbook and can be exported.  There are multiple opportunities for improvement: the headers can be renamed and the column widths can be adjusted. [SheetJS Pro](https://sheetjs.com/pro) offers additional styling options like cell styling and frozen rows. <details><summary><b>Changing Header Names</b> (click to show)</summary> By default, `json_to_sheet` creates a worksheet with a header row. In this case, the headers come from the JS object keys: "name" and "birthday". The headers are in cells `A1` and `B1`. `XLSX.utils.sheet_add_aoa` can write text values to the existing worksheet starting at cell `A1`: ```js XLSX.utils.sheet_add_aoa(worksheet, [["Name", "Birthday"]], { origin: "A1" }); ``` </details> <details><summary><b>Changing Column Widths</b> (click to show)</summary> Some of the names are longer than the default column width. Column widths are set by setting the `"!cols"` worksheet property. The following line sets the width of column A to approximately 10 characters: ```js worksheet["!cols"] = [ { wch: 10 } ]; // set column A width to 10 characters ``` One `Array#reduce` call over `rows` can calculate the maximum width: ```js const max_width = rows.reduce((w, r) => Math.max(w, r.name.length), 10); worksheet["!cols"] = [ { wch: max_width } ]; ``` </details> After cleanup, the generated workbook looks like the screenshot below:  ## Export a File `XLSX.writeFile` creates a spreadsheet file and tries to write it to the system. In the browser, it will try to prompt the user to download the file. In NodeJS, it will write to the local directory. ```js XLSX.writeFile(workbook, "Presidents.xlsx", { compression: true }); ``` ## Live Demo This demo runs in the web browser! Click "Click to Generate File!" and the browser should try to create `Presidents.xlsx` ```jsx live function Presidents() { return ( <button onClick={async () => { /* fetch JSON data and parse */ const url = "https://sheetjs.com/data/executive.json"; const raw_data = await (await fetch(url)).json(); /* filter for the Presidents */ const prez = raw_data.filter(row => row.terms.some(term => term.type === "prez")); /* sort by first presidential term */ prez.forEach(prez => prez.start = prez.terms.find(term => term.type === "prez").start); prez.sort((l,r) => l.start.localeCompare(r.start)); /* flatten objects */ const rows = prez.map(row => ({ name: row.name.first + " " + row.name.last, birthday: row.bio.birthday })); /* generate worksheet and workbook */ const worksheet = XLSX.utils.json_to_sheet(rows); const workbook = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(workbook, worksheet, "Dates"); /* fix headers */ XLSX.utils.sheet_add_aoa(worksheet, [["Name", "Birthday"]], { origin: "A1" }); /* calculate column width */ const max_width = rows.reduce((w, r) => Math.max(w, r.name.length), 10); worksheet["!cols"] = [ { wch: max_width } ]; /* create an XLSX file and try to save to Presidents.xlsx */ XLSX.writeFile(workbook, "Presidents.xlsx", { compression: true }); }}><b>Click to Generate file!</b></button> ); } ``` <https://sheetjs.com/pres.html> is a hosted version of this demo. ## Run the Demo Locally <Tabs> <TabItem value="browser" label="Web Browser"> Save the following script to `SheetJSStandaloneDemo.html`: <CodeBlock language="html" title="SheetJSStandaloneDemo.html">{`\ <body> <script src="https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js"></script> <script> (async() => { /* fetch JSON data and parse */ const url = "https://sheetjs.com/data/executive.json"; const raw_data = await (await fetch(url)).json(); \n\ /* filter for the Presidents */ const prez = raw_data.filter(row => row.terms.some(term => term.type === "prez")); \n\ /* sort by first presidential term */ prez.forEach(prez => prez.start = prez.terms.find(term => term.type === "prez").start); prez.sort((l,r) => l.start.localeCompare(r.start)); \n\ /* flatten objects */ const rows = prez.map(row => ({ name: row.name.first + " " + row.name.last, birthday: row.bio.birthday })); \n\ /* generate worksheet and workbook */ const worksheet = XLSX.utils.json_to_sheet(rows); const workbook = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(workbook, worksheet, "Dates"); \n\ /* fix headers */ XLSX.utils.sheet_add_aoa(worksheet, [["Name", "Birthday"]], { origin: "A1" }); \n\ /* calculate column width */ const max_width = rows.reduce((w, r) => Math.max(w, r.name.length), 10); worksheet["!cols"] = [ { wch: max_width } ]; \n\ /* create an XLSX file and try to save to Presidents.xlsx */ XLSX.writeFile(workbook, "Presidents.xlsx", { compression: true }); })(); </script> </body>`} </CodeBlock> After saving the file, run a local web server in the folder with the HTML file. For example, if NodeJS is installed: ```bash npx http-server . ``` The server process will display a URL (typically `http://127.0.0.1:8080`). Open `http://127.0.0.1:8080/SheetJSStandaloneDemo.html` in your browser. </TabItem> <TabItem value="nodejs" label="Command-Line (NodeJS)"> Install the dependencies: <CodeBlock language="bash">{`\ npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`} </CodeBlock> Save the following script to `SheetJSNodeJS.js`: ```js title="SheetJSNodeJS.js" const XLSX = require("xlsx"); (async() => { /* fetch JSON data and parse */ const url = "https://sheetjs.com/data/executive.json"; const raw_data = await (await fetch(url)).json(); /* filter for the Presidents */ const prez = raw_data.filter(row => row.terms.some(term => term.type === "prez")); /* sort by first presidential term */ prez.forEach(prez => prez.start = prez.terms.find(term => term.type === "prez").start); prez.sort((l,r) => l.start.localeCompare(r.start)); /* flatten objects */ const rows = prez.map(row => ({ name: row.name.first + " " + row.name.last, birthday: row.bio.birthday })); /* generate worksheet and workbook */ const worksheet = XLSX.utils.json_to_sheet(rows); const workbook = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(workbook, worksheet, "Dates"); /* fix headers */ XLSX.utils.sheet_add_aoa(worksheet, [["Name", "Birthday"]], { origin: "A1" }); /* calculate column width */ const max_width = rows.reduce((w, r) => Math.max(w, r.name.length), 10); worksheet["!cols"] = [ { wch: max_width } ]; /* create an XLSX file and try to save to Presidents.xlsx */ XLSX.writeFile(workbook, "Presidents.xlsx", { compression: true }); })(); ``` After saving the script, run the script: ```bash node SheetJSNodeJS.js ``` This script will write a new file `Presidents.xlsx` in the same folder. :::caution Native `fetch` support was added in NodeJS 18. For older versions of NodeJS, the script will throw an error `fetch is not defined`. A third-party library like `axios` presents a similar API for fetching data: <details><summary><b>Example using axios</b> (click to show)</summary> Install the dependencies: <CodeBlock language="bash">{`\ npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz axios`} </CodeBlock> Save the following script to `SheetJSAxios.js` (differences are highlighted): ```js title="SheetJSAxios.js" const XLSX = require("xlsx"); // highlight-next-line const axios = require("axios"); (async() => { /* fetch JSON data and parse */ const url = "https://sheetjs.com/data/executive.json"; // highlight-next-line const raw_data = (await axios(url, {responseType: "json"})).data; /* filter for the Presidents */ const prez = raw_data.filter(row => row.terms.some(term => term.type === "prez")); /* sort by first presidential term */ prez.forEach(prez => prez.start = prez.terms.find(term => term.type === "prez").start); prez.sort((l,r) => l.start.localeCompare(r.start)); /* flatten objects */ const rows = prez.map(row => ({ name: row.name.first + " " + row.name.last, birthday: row.bio.birthday })); /* generate worksheet and workbook */ const worksheet = XLSX.utils.json_to_sheet(rows); const workbook = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(workbook, worksheet, "Dates"); /* fix headers */ XLSX.utils.sheet_add_aoa(worksheet, [["Name", "Birthday"]], { origin: "A1" }); /* calculate column width */ const max_width = rows.reduce((w, r) => Math.max(w, r.name.length), 10); worksheet["!cols"] = [ { wch: max_width } ]; /* create an XLSX file and try to save to Presidents.xlsx */ XLSX.writeFile(workbook, "Presidents.xlsx", { compression: true }); })(); ``` After saving the script, run the script: ```bash node SheetJSAxios.js ``` This script will write a new file `Presidents.xlsx` in the same folder. </details> ::: <details><summary><b>Other Server-Side Platforms</b> (click to show)</summary> <Tabs> <TabItem value="deno" label="Deno"> Save the following script to `SheetJSDeno.ts`: <CodeBlock language="ts" title="SheetJSDeno.ts">{`\ // @deno-types="https://cdn.sheetjs.com/xlsx-${current}/package/types/index.d.ts" import * as XLSX from 'https://cdn.sheetjs.com/xlsx-${current}/package/xlsx.mjs'; \n\ /* fetch JSON data and parse */ const url = "https://sheetjs.com/data/executive.json"; const raw_data = await (await fetch(url)).json(); \n\ /* filter for the Presidents */ const prez = raw_data.filter((row: any) => row.terms.some((term: any) => term.type === "prez")); \n\ /* sort by first presidential term */ prez.forEach(prez => prez.start = prez.terms.find(term => term.type === "prez").start); prez.sort((l,r) => l.start.localeCompare(r.start)); \n\ /* flatten objects */ const rows = prez.map((row: any) => ({ name: row.name.first + " " + row.name.last, birthday: row.bio.birthday })); \n\ /* generate worksheet and workbook */ const worksheet = XLSX.utils.json_to_sheet(rows); const workbook = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(workbook, worksheet, "Dates"); \n\ /* fix headers */ XLSX.utils.sheet_add_aoa(worksheet, [["Name", "Birthday"]], { origin: "A1" }); \n\ /* calculate column width */ const max_width = rows.reduce((w: number, r: any) => Math.max(w, r.name.length), 10); worksheet["!cols"] = [ { wch: max_width } ]; \n\ /* create an XLSX file and try to save to Presidents.xlsx */ XLSX.writeFile(workbook, "Presidents.xlsx", { compression: true });`} </CodeBlock> After saving the script, run the script: ```bash deno run -A SheetJSDeno.ts ``` This script will write a new file `Presidents.xlsx` in the same folder. </TabItem> <TabItem value="bun" label="Bun"> <p>Download <a href={`https://cdn.sheetjs.com/xlsx-${current}/package/xlsx.mjs`}>https://cdn.sheetjs.com/xlsx-{current}/package/xlsx.mjs</a> to <code>xlsx.mjs</code>:</p> <CodeBlock language="bash">{`\ curl -LO https://cdn.sheetjs.com/xlsx-${current}/package/xlsx.mjs`} </CodeBlock> Save the following script to `SheetJSBun.js`: ```js title="SheetJSBun.js" import * as XLSX from './xlsx.mjs'; import * as fs from 'fs'; XLSX.set_fs(fs); /* fetch JSON data and parse */ const url = "https://sheetjs.com/data/executive.json"; const raw_data = await (await fetch(url)).json(); /* filter for the Presidents */ const prez = raw_data.filter((row) => row.terms.some((term) => term.type === "prez")); /* sort by first presidential term */ prez.forEach(prez => prez.start = prez.terms.find(term => term.type === "prez").start); prez.sort((l,r) => l.start.localeCompare(r.start)); /* flatten objects */ const rows = prez.map((row) => ({ name: row.name.first + " " + row.name.last, birthday: row.bio.birthday })); /* generate worksheet and workbook */ const worksheet = XLSX.utils.json_to_sheet(rows); const workbook = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(workbook, worksheet, "Dates"); /* fix headers */ XLSX.utils.sheet_add_aoa(worksheet, [["Name", "Birthday"]], { origin: "A1" }); /* calculate column width */ const max_width = rows.reduce((w, r) => Math.max(w, r.name.length), 10); worksheet["!cols"] = [ { wch: max_width } ]; /* create an XLSX file and try to save to Presidents.xlsx */ XLSX.writeFile(workbook, "Presidents.xlsx", { compression: true }); ``` After saving the script, run the script: ```bash bun SheetJSBun.js ``` This script will write a new file `Presidents.xlsx` in the same folder. </TabItem> </Tabs> </details> </TabItem> <TabItem value="desktop" label="Desktop App"> Save the following script to `SheetJSNW.html`: <CodeBlock language="html" title="SheetJSNW.html">{`\ <body> <script src="https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js"></script> <script> (async() => { /* fetch JSON data and parse */ const url = "https://sheetjs.com/data/executive.json"; const raw_data = await (await fetch(url)).json(); \n\ /* filter for the Presidents */ const prez = raw_data.filter(row => row.terms.some(term => term.type === "prez")); \n\ /* sort by first presidential term */ prez.forEach(prez => prez.start = prez.terms.find(term => term.type === "prez").start); prez.sort((l,r) => l.start.localeCompare(r.start)); \n\ /* flatten objects */ const rows = prez.map(row => ({ name: row.name.first + " " + row.name.last, birthday: row.bio.birthday })); \n\ /* generate worksheet and workbook */ const worksheet = XLSX.utils.json_to_sheet(rows); const workbook = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(workbook, worksheet, "Dates"); \n\ /* fix headers */ XLSX.utils.sheet_add_aoa(worksheet, [["Name", "Birthday"]], { origin: "A1" }); \n\ /* calculate column width */ const max_width = rows.reduce((w, r) => Math.max(w, r.name.length), 10); worksheet["!cols"] = [ { wch: max_width } ]; \n\ /* create an XLSX file and try to save to Presidents.xlsx */ XLSX.writeFile(workbook, "Presidents.xlsx", { compression: true }); })(); </script> </body>`} </CodeBlock> Save the following to `package.json`: <CodeBlock language="json" title="package.json">{`\ { "name": "sheetjs-nwjs", "author": "sheetjs", "version": "0.0.0", "main": "SheetJSNW.html", "dependencies": { "nw": "~0.66.0", "xlsx": "https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz" } }`} </CodeBlock> Install dependencies and run: ```bash npm i npx nw . ``` The app will show a save dialog. After selecting a path, it will write the file. </TabItem> <TabItem value="mobile" label="Mobile App"> :::note Initial Setup Follow the [Environment Setup](https://reactnative.dev/docs/environment-setup) of the React Native documentation before testing the demo. ::: Create a new project by running the following commands in the Terminal: <CodeBlock language="bash">{`\ npx react-native@0.71 init SheetJSPres --version="0.72.0-rc.1" cd SheetJSPres \n\ npm i -S https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz react-native-blob-util@0.17.1`} </CodeBlock> Save the following to `App.tsx` in the project: ```js title="App.tsx" import React from 'react'; import { Alert, Button, SafeAreaView, Text, View } from 'react-native'; import { utils, version, write } from 'xlsx'; import RNBU from 'react-native-blob-util'; const make_workbook = async() => { /* fetch JSON data and parse */ const url = "https://sheetjs.com/data/executive.json"; const raw_data = await (await fetch(url)).json(); /* filter for the Presidents */ const prez = raw_data.filter(row => row.terms.some(term => term.type === "prez")); /* sort by first presidential term */ prez.forEach(prez => prez.start = prez.terms.find(term => term.type === "prez").start); prez.sort((l,r) => l.start.localeCompare(r.start)); /* flatten objects */ const rows = prez.map(row => ({ name: row.name.first + " " + row.name.last, birthday: row.bio.birthday })); /* generate worksheet and workbook */ const worksheet = utils.json_to_sheet(rows); const workbook = utils.book_new(); utils.book_append_sheet(workbook, worksheet, "Dates"); /* fix headers */ utils.sheet_add_aoa(worksheet, [["Name", "Birthday"]], { origin: "A1" }); /* calculate column width */ const max_width = rows.reduce((w, r) => Math.max(w, r.name.length), 10); worksheet["!cols"] = [ { wch: max_width } ]; /* React Native does not support `writeFile`. This is a low-level write ! */ /* write workbook to buffer */ const buf = write(workbook, {type:'buffer', bookType:"xlsx"}); /* write buffer to file */ const filename = RNBU.fs.dirs.DocumentDir + "/Presidents.xlsx"; await RNBU.fs.writeFile(filename, Array.from(buf), 'ascii'); /* Copy to downloads directory (android) */ try { await RNBU.MediaCollection.copyToMediaStore({ parentFolder: "", mimeType: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", name: "Presidents.xlsx" }, "Download", filename); } catch(e) {} return filename; }; const App = () => ( <SafeAreaView><View style={{ marginTop: 32, padding: 24 }}> <Text style={{ fontSize: 24, fontWeight: 'bold' }}>SheetJS {version} Export Demo</Text> <Button title='Press to Export' onPress={async() => { try { const filename = await make_workbook(); Alert.alert("Export Finished", `Exported to ${filename}`); } catch(err) { Alert.alert("Export Error", `Error ${err.message||err}`); } }}/> </View></SafeAreaView> ); export default App; ``` <Tabs> <TabItem value="asim" label="Android"> :::note The Android demo has been tested in Windows 10 and in macOS. ::: Test the app in the Android simulator: ```bash npx react-native start ``` Once Metro is ready, it will display the commands: ``` r - reload the app d - open developer menu i - run on iOS a - run on Android ``` Press `a` to run on android. After clicking "Press to Export", the app will show an alert with the location to the generated file (`/data/user/0/com.sheetjspres/files/Presidents.xlsx`) In the Android simulator, pulling the file requires additional steps: ```bash adb root adb pull /data/user/0/com.sheetjspres/files/Presidents.xlsx Presidents.xlsx ``` This command generates `Presidents.xlsx` which can be opened. :::info Device Testing ["Running on Device"](https://reactnative.dev/docs/running-on-device) in the React Native docs covers device configuration. `Presidents.xlsx` will be copied to the `Downloads` folder. The file is visible in the Files app and can be opened with the Google Sheets app. ::: </TabItem> <TabItem value="ios" label="iOS"> :::caution This demo runs in iOS and requires a Macintosh computer with Xcode installed. ::: The native component must be linked: ```bash cd ios; pod install; cd .. ``` Test the app in the iOS simulator: ```bash npm run ios ``` After clicking "Press to Export", the app will show an alert with the location to the generated file. :::info Device Testing ["Running on Device"](https://reactnative.dev/docs/running-on-device) in the React Native docs covers device configuration. The `UIFileSharingEnabled` and `LSSupportsOpeningDocumentsInPlace` entitlements are required for iOS to show the generated files in the "Files" app. The highlighted lines should be added to the iOS project `Info.plist` just before the last `</dict>` tag: ```xml title="ios/SheetJSPres/Info.plist" <key>UIViewControllerBasedStatusBarAppearance</key> <false/> <!-- highlight-start --> <key>UIFileSharingEnabled</key> <true/> <key>LSSupportsOpeningDocumentsInPlace</key> <true/> <!-- highlight-end --> </dict> </plist> ``` After adding the settings and rebuilding the app, the file will be visible in the "Files" app. Under "On My iPhone", there will be a folder `SheetJSPres`. Within the folder there will be a file named `Presidents`. Touch the file to see a preview of the data. The Numbers app can open the file. ::: </TabItem> </Tabs> </TabItem> </Tabs>