--- title: Quasar pagination_prev: demos/static/index pagination_next: demos/desktop/index sidebar_position: 3 sidebar_custom_props: summary: VueJS + Web View --- import current from '/version.js'; import CodeBlock from '@theme/CodeBlock'; The [NodeJS Module](/docs/getting-started/installation/nodejs) can be imported from the main entrypoint or any script in the project. The "Complete Example" creates an app that looks like the screenshots below:
iOS Android
![iOS screenshot](pathname:///quasar/ios.png) ![Android screenshot](pathname:///quasar/and.png)
### Integration Details This demo will use the Quasar ViteJS starter project with VueJS and Cordova. The complete solution uses `cordova-plugin-file` for file operations. It can be installed like any other Cordova plugin: ```bash cd src-cordova cordova plugin add cordova-plugin-file cd .. ``` #### Reading data The `q-file` component presents an API reminiscent of File Input elements: ```html ``` When binding to the `input` element, the callback receives an `Event` object: ```ts import { read } from 'xlsx'; // assuming `todos` is a standard VueJS `ref` async function updateFile(v) { try { // `v.target.files[0]` is the desired file object const files = (v.target as HTMLInputElement).files; if(!files || files.length == 0) return; // read first file const wb = read(await files[0].arrayBuffer()); // get data of first worksheet as an array of objects const data = utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]]); // update state todos.value = data.map(row => ({id: row.Index, content: row.Name})); } catch(e) { console.log(e); } } ``` #### Writing data The API is shaped like the File and Directory Entries API. For clarity, since the code is a "pyramid of doom", the error handlers are omitted: ```ts import { write } from 'xlsx'; // on iOS and android, `XLSX.write` with type "buffer" returns a `Uint8Array` const u8: Uint8Array = write(wb, {bookType: "xlsx", type: "buffer"}); // Request filesystem access for persistent storage window.requestFileSystem(window.PERSISTENT, 0, function(fs) { // Request a handle to "SheetJSQuasar.xlsx", making a new file if necessary fs.root.getFile("SheetJSQuasar.xlsx", {create: true}, entry => { // Request a FileWriter for writing data entry.createWriter(writer => { // The FileWriter API needs an actual Blob const data = new Blob([u8], {type: "application/vnd.ms-excel"}); // This callback is called if the write is successful writer.onwriteend = () => { // TODO: show a dialog }; // writer.onerror will be invoked if there is an error in writing // write the data writer.write(data); }); }); }); ``` ### Demo :::note This demo was tested on an Intel Mac on 2023 April 08. `create-quasar@1.1.2` was installed during app creation. The app used Quasar version `2.11.10`. The iOS simulator runs iOS 16.2 on an iPhone 14 Pro Max. The Android simulator runs Android 12.0 (S) API 31 on a Pixel 3. ::: The demo draws from the ViteJS example. Familiarity with VueJS and TypeScript is assumed. 0) Ensure all of the dependencies are installed. Install the CLI globally: ```bash npm i -g @quasar/cli cordova ``` (you may need to run `sudo npm i -g` if there are permission issues) 1) Create a new app: ```bash npm init quasar ``` When prompted: - "What would you like to build?": `App with Quasar CLI` - "Project folder": `SheetJSQuasar` - "Pick Quasar version": `Quasar v2 (Vue 3 | latest and greatest)` - "Pick script type": `Typescript` - "Pick Quasar App CLI variant": `Quasar App CLI with Vite` - "Package name": (just press enter, it will use the default `sheetjsquasar` - "Project product name": `SheetJSQuasar` - "Project description": `SheetJS + Quasar` - "Author": (just press enter, it will use your git config settings) - "Pick a Vue component style": `Composition API` - "Pick your CSS preprocessor": `None` - "Check the features needed for your project": Deselect everything - "Install project dependencies": `No` 2) Install dependencies: {`\ cd SheetJSQuasar npm i npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`} 3) Set up Cordova: ```bash quasar mode add cordova ``` When prompted, enter the app id `org.sheetjs.quasar`. It will create a new `src-cordova` folder. Continue in that folder: ```bash cd src-cordova cordova platform add ios cordova plugin add cordova-plugin-wkwebview-engine cordova plugin add cordova-plugin-file ``` :::note pass If there is an error `Could not load API for iOS project`, it needs to be reset: ```bash cordova platform rm ios cordova platform add ios cordova plugin add cordova-plugin-file ``` ::: Return to the project directory: ```bash cd .. ``` 4) Start the development server: ```bash quasar dev -m ios ``` :::caution If the app is blank or not refreshing, delete the app and close the simulator, then restart the development process. ::: 5) Add the Dialog plugin to `quasar.config.js`: ```js title="quasar.config.js" framework: { config: {}, // ... // Quasar plugins // highlight-next-line plugins: ['Dialog'] }, ``` 6) In the template section of `src/pages/IndexPage.vue`, replace the example with a Table, Save button and Load file picker component: ```html title="src/pages/IndexPage.vue" ``` This uses two functions that should be added to the component script: ```ts title="src/pages/IndexPage.vue" const meta = ref({ totalCount: 1200 }); // highlight-start function saveFile() { } async function updateFile(v: Event) { } return { todos, meta, saveFile, updateFile }; // highlight-end } }); ``` The app should now show two buttons at the bottom: ![Quasar Step 6](pathname:///mobile/quasar6.png) :::caution If the app is blank or not refreshing, delete the app and close the simulator, then restart the development process. ::: 7) Wire up the `updateFile` function: ```ts title="src/pages/IndexPage.vue" import { defineComponent, ref } from 'vue'; // highlight-start import { read, write, utils } from 'xlsx'; import { useQuasar } from 'quasar'; // highlight-end export default defineComponent({ // ... // highlight-start const $q = useQuasar(); function dialogerr(e: Error) { $q.dialog({title: "Error!", message: e.message || String(e)}); } // highlight-end function saveFile() { } async function updateFile(v: Event) { // highlight-start try { const files = (v.target as HTMLInputElement).files; if(!files || files.length == 0) return; const wb = read(await files[0].arrayBuffer()); const data = utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]]); todos.value = data.map(row => ({id: row.Index, content: row.Name})); } catch(e) { dialogerr(e); } // highlight-end } ``` To test that reading works: - Download - In the simulator, click the Home icon to return to the home screen - Click on the "Files" icon - Click and drag `pres.numbers` from a Finder window into the simulator. ![Quasar Step 7 save file](pathname:///mobile/quasar7a.png) - Make sure "On My iPhone" is highlighted and select "Save" - Click the Home icon again then select the `SheetJSQuasar` app - Click the "Load" button, then select "Choose File" and select `pres`: ![Quasar Step 7 load file](pathname:///mobile/quasar7b.png) Once selected, the screen should refresh with new contents. 8) Wire up the `saveFile` function: ```ts title="src/pages/IndexPage.vue" function saveFile() { // highlight-start /* generate workbook from state */ const ws = utils.json_to_sheet(todos.value); const wb = utils.book_new(); utils.book_append_sheet(wb, ws, "SheetJSQuasar"); const u8: Uint8Array = write(wb, {bookType: "xlsx", type: "buffer"}); const dir: string = $q.cordova.file.documentsDirectory || $q.cordova.file.externalApplicationStorageDirectory; /* save to file */ window.requestFileSystem(window.PERSISTENT, 0, function(fs) { try { fs.root.getFile("SheetJSQuasar.xlsx", {create: true}, entry => { const msg = `File stored at ${dir} ${entry.fullPath}`; entry.createWriter(writer => { try { const data = new Blob([u8], {type: "application/vnd.ms-excel"}); writer.onwriteend = () => { try { $q.dialog({title: "Success!", message: msg}); } catch(e) { dialogerr(e); } }; writer.onerror = dialogerr; writer.write(data); } catch(e) { dialogerr(e); } }, dialogerr); }, dialogerr); } catch(e) { dialogerr(e) } }, dialogerr); // highlight-end } ``` The page should revert to the old contents. To test that writing works: - Click "Save File". You will see a popup with a location: ![Quasar Step 8](pathname:///mobile/quasar8.png) - Find the file and verify the contents are correct. Run in a new terminal: ```bash find ~/Library/Developer/CoreSimulator -name SheetJSQuasar.xlsx | while read x; do echo "$x"; npx xlsx-cli "$x"; done ``` Since the contents reverted, you should see ``` SheetJSQuasar id,content 1,ct1 2,ct2 3,ct3 4,ct4 5,ct5 ``` - Use "Load File" to select `pres.numbers` again. Wait for the app to refresh. - Click "Save File", then re-run the command: ```bash find ~/Library/Developer/CoreSimulator -name SheetJSQuasar.xlsx | while read x; do echo "$x"; npx xlsx-cli "$x"; done ``` The contents from `pres.numbers` should show up now, with a new header row: ``` SheetJSQuasar id,content 42,Bill Clinton 43,GeorgeW Bush 44,Barack Obama 45,Donald Trump 46,Joseph Biden ``` **Android** 9) Create the Android project: ```bash cd src-cordova cordova platform add android cd .. ``` 10) Start the simulator: ```bash quasar dev -m android ``` :::note In local testing, the Quasar build process threw an error: ``` java.lang.IllegalArgumentException: Unsupported class file major version 63 ``` This was resolved by rolling back to Java 1.8 ::: To test that reading works: - Click and drag `pres.numbers` from a Finder window into the simulator. - Tap "Load", tap the `≡` icon, tap "Downloads" and select `pres.numbers`. To test that writing works: - Tap "Save File". You will see a popup with a location. - Pull the file from the simulator and verify the contents: ```bash adb exec-out run-as org.sheetjs.quasar cat files/files/SheetJSQuasar.xlsx > /tmp/SheetJSQuasar.xlsx npx xlsx-cli /tmp/SheetJSQuasar.xlsx ```