docs.sheetjs.com/docz/docs/03-demos/17-mobile/03-quasar.md

499 lines
14 KiB
Markdown
Raw Permalink Normal View History

2023-01-05 23:33:49 +00:00
---
2023-09-18 06:44:33 +00:00
title: Data in Quasar Apps
sidebar_label: Quasar
description: Build data-intensive mobile apps with Quasar and Cordova. Seamlessly integrate spreadsheets into your app using SheetJS. Let data in your Excel spreadsheets shine.
2023-02-28 11:40:44 +00:00
pagination_prev: demos/static/index
2023-01-05 23:33:49 +00:00
pagination_next: demos/desktop/index
sidebar_position: 3
sidebar_custom_props:
summary: VueJS + Web View
---
2023-04-27 09:12:19 +00:00
import current from '/version.js';
2023-05-07 13:58:36 +00:00
import CodeBlock from '@theme/CodeBlock';
2023-04-27 09:12:19 +00:00
2023-09-18 06:44:33 +00:00
[Quasar](https://quasar.dev/) is a VueJS framework for building iOS and Android
apps with the Cordova platform.
[SheetJS](https://sheetjs.com) is a JavaScript library for reading and writing
data from spreadsheets.
2023-01-05 23:33:49 +00:00
2023-09-18 06:44:33 +00:00
This demo uses Quasar and SheetJS to process data and generate spreadsheets.
We'll explore how to load SheetJS in an Quasar app and use Quasar and Cordova
features to extract data from, and write data to, spreadsheets on the device.
The ["Demo"](#demo) creates an app that looks like the screenshots below:
2023-04-08 08:42:15 +00:00
<table><thead><tr>
<th><a href="#demo">iOS</a></th>
<th><a href="#demo">Android</a></th>
</tr></thead><tbody><tr><td>
![iOS screenshot](pathname:///quasar/ios.png)
</td><td>
![Android screenshot](pathname:///quasar/and.png)
</td></tr></tbody></table>
2023-01-05 23:33:49 +00:00
2023-12-05 03:46:54 +00:00
:::note Tested Deployments
This demo was tested in the following environments:
2024-04-30 04:29:40 +00:00
**Simulators**
| OS | Device | Quasar | Dev Platform | Date |
|:-----------|:--------------------|:---------|:-------------|:-----------|
| Android 34 | Pixel 3a | `2.14.1` | `darwin-x64` | 2023-12-04 |
| iOS 17.0.1 | iPhone SE (3rd gen) | `2.14.1` | `darwin-x64` | 2023-12-04 |
2023-12-05 03:46:54 +00:00
:::
2023-09-20 21:53:18 +00:00
## Integration Details
2023-01-05 23:33:49 +00:00
2023-09-22 06:32:55 +00:00
The [SheetJS NodeJS Module](/docs/getting-started/installation/nodejs) can be
imported from any component or script in the app.
2023-09-18 06:44:33 +00:00
2023-04-08 08:42:15 +00:00
This demo will use the Quasar ViteJS starter project with VueJS and Cordova.
2023-09-18 06:44:33 +00:00
The starter places the backing Cordova project in the `src-cordova` folder.
2023-04-08 08:42:15 +00:00
2023-01-05 23:33:49 +00:00
The complete solution uses `cordova-plugin-file` for file operations. It can
2023-09-18 06:44:33 +00:00
be installed from the Cordova folder:
2023-01-05 23:33:49 +00:00
```bash
cd src-cordova
cordova plugin add cordova-plugin-file
cd ..
```
2023-09-18 06:44:33 +00:00
### Reading data
2023-01-05 23:33:49 +00:00
2023-09-18 06:44:33 +00:00
The QFile[^1] component presents an API reminiscent of File Input elements:
2023-01-05 23:33:49 +00:00
```html
<q-file label="Load File" filled label-color="orange" @input="updateFile"/>
```
2023-09-18 06:44:33 +00:00
When binding to the `input` element, the callback receives an `Event` object.
Using standard DOM operations, the file data can be pulled into an `ArrayBuffer`
and parsed using the SheetJS `read` method[^2]. `read` returns a workbook[^3]
object that holds data and metadata for each worksheet.
This snippet reads a workbook, pulls the first worksheet, generates an array of
objects using the SheetJS `sheet_to_json`[^4] method, and updates state:
2023-01-05 23:33:49 +00:00
```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); } }
```
2023-09-20 21:53:18 +00:00
### Writing data
2023-01-05 23:33:49 +00:00
2023-09-18 06:44:33 +00:00
Starting from an array of objects, the SheetJS `json_to_sheet` method[^5]
generates a SheetJS worksheet object. The `book_append_sheet` and `book_new`
helper functions[^6] create a SheetJS workbook object that can be exported:
2023-01-05 23:33:49 +00:00
2023-09-18 06:44:33 +00:00
```js
import { utils } from 'xlsx';
// assuming `todos` is a VueJS ref whose value is an array of objects
const ws = utils.json_to_sheet(todos.value);
const wb = utils.book_new();
utils.book_append_sheet(wb, ws, "SheetJSQuasar");
```
The SheetJS `write` function[^7] with the option `type: "buffer"` will generate
`Uint8Array` objects that can be converted to blobs and exported:
```js
2023-01-05 23:33:49 +00:00
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"});
2023-09-18 06:44:33 +00:00
```
The `cordova-plugin-file` API writes the data to the filesystem. The code uses
the File and Directory Entries API[^8]:
```ts
2023-01-05 23:33:49 +00:00
// 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);
});
});
});
```
2023-09-20 21:53:18 +00:00
## Demo
2023-01-05 23:33:49 +00:00
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
```
<!-- spellchecker-disable -->
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:
<!-- spellchecker-enable -->
2023-05-07 13:58:36 +00:00
<CodeBlock language="bash">{`\
2023-01-05 23:33:49 +00:00
cd SheetJSQuasar
npm i
2023-04-27 09:12:19 +00:00
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
2023-05-07 13:58:36 +00:00
</CodeBlock>
2023-01-05 23:33:49 +00:00
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
```
2023-09-05 18:04:23 +00:00
:::note pass
2023-01-05 23:33:49 +00:00
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 ..
```
2023-09-18 06:44:33 +00:00
11) Enable file sharing and make the documents folder visible in the iOS app.
The following lines must be added to `src-cordova/platforms/ios/SheetJSQuasar/SheetJSQuasar-Info.plist`:
```xml title="src-cordova/platforms/ios/SheetJSQuasar/SheetJSQuasar-Info.plist (add to file)"
<plist version="1.0">
<dict>
<!-- highlight-start -->
<key>UIFileSharingEnabled</key>
<true/>
<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>
<!-- highlight-end -->
<key>CFBundleDevelopmentRegion</key>
```
(The root element of the document is `plist` and it contains one `dict` child)
5) Start the development server:
2023-01-05 23:33:49 +00:00
```bash
quasar dev -m ios
```
2023-09-18 06:44:33 +00:00
:::caution pass
2023-01-05 23:33:49 +00:00
If the app is blank or not refreshing, delete the app and close the simulator,
then restart the development process.
:::
2023-09-18 06:44:33 +00:00
6) Add the Dialog plugin to `quasar.config.js`:
2023-01-05 23:33:49 +00:00
```js title="quasar.config.js"
2023-04-08 08:42:15 +00:00
framework: {
config: {},
// ...
2023-01-05 23:33:49 +00:00
// Quasar plugins
// highlight-next-line
plugins: ['Dialog']
2023-04-08 08:42:15 +00:00
},
2023-01-05 23:33:49 +00:00
```
2023-09-18 06:44:33 +00:00
7) In the template section of `src/pages/IndexPage.vue`, replace the example
2023-04-08 08:42:15 +00:00
with a Table, Save button and Load file picker component:
2023-01-05 23:33:49 +00:00
```html title="src/pages/IndexPage.vue"
2023-04-08 08:42:15 +00:00
<template>
<q-page class="row items-center justify-evenly">
2023-01-05 23:33:49 +00:00
<!-- highlight-start -->
2023-04-08 08:42:15 +00:00
<q-table :rows="todos" />
2023-01-05 23:33:49 +00:00
<q-btn-group>
<q-file label="Load File" filled label-color="orange" @input="updateFile"/>
<q-btn label="Save File" @click="saveFile" />
</q-btn-group>
2023-04-14 08:13:40 +00:00
<!-- highlight-end -->
</q-page>
2023-01-05 23:33:49 +00:00
</template>
```
This uses two functions that should be added to the component script:
```ts title="src/pages/IndexPage.vue"
const meta = ref<Meta>({
totalCount: 1200
});
// highlight-start
function saveFile() {
}
2023-04-08 08:42:15 +00:00
async function updateFile(v: Event) {
2023-01-05 23:33:49 +00:00
}
return { todos, meta, saveFile, updateFile };
// highlight-end
}
});
```
The app should now show two buttons at the bottom:
![Quasar Step 6](pathname:///mobile/quasar6.png)
2023-09-24 03:59:48 +00:00
:::caution pass
2023-01-05 23:33:49 +00:00
If the app is blank or not refreshing, delete the app and close the simulator,
then restart the development process.
:::
2023-09-18 06:44:33 +00:00
8) Wire up the `updateFile` function:
2023-01-05 23:33:49 +00:00
```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();
2023-04-08 08:42:15 +00:00
function dialogerr(e: Error) { $q.dialog({title: "Error!", message: e.message || String(e)}); }
2023-01-05 23:33:49 +00:00
// highlight-end
function saveFile() {
}
2023-04-08 08:42:15 +00:00
async function updateFile(v: Event) {
2023-01-05 23:33:49 +00:00
// highlight-start
try {
const files = (v.target as HTMLInputElement).files;
if(!files || files.length == 0) return;
const wb = read(await files[0].arrayBuffer());
2023-04-08 08:42:15 +00:00
const data = utils.sheet_to_json<any>(wb.Sheets[wb.SheetNames[0]]);
2023-01-05 23:33:49 +00:00
todos.value = data.map(row => ({id: row.Index, content: row.Name}));
} catch(e) { dialogerr(e); }
// highlight-end
}
```
To test that reading works:
2024-04-26 04:16:13 +00:00
- Download https://docs.sheetjs.com/pres.numbers
2023-01-05 23:33:49 +00:00
- 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)
2023-04-08 08:42:15 +00:00
Once selected, the screen should refresh with new contents.
2023-01-05 23:33:49 +00:00
2023-09-18 06:44:33 +00:00
9) Wire up the `saveFile` function:
2023-01-05 23:33:49 +00:00
2023-04-08 08:42:15 +00:00
```ts title="src/pages/IndexPage.vue"
2023-01-05 23:33:49 +00:00
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"});
2023-04-08 08:42:15 +00:00
const dir: string = $q.cordova.file.documentsDirectory || $q.cordova.file.externalApplicationStorageDirectory;
2023-01-05 23:33:49 +00:00
/* save to file */
window.requestFileSystem(window.PERSISTENT, 0, function(fs) {
try {
fs.root.getFile("SheetJSQuasar.xlsx", {create: true}, entry => {
2023-04-08 08:42:15 +00:00
const msg = `File stored at ${dir} ${entry.fullPath}`;
2023-01-05 23:33:49 +00:00
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
```
2023-04-08 08:42:15 +00:00
**Android**
2023-09-18 06:44:33 +00:00
10) Create the Android project:
2023-04-08 08:42:15 +00:00
```bash
cd src-cordova
cordova platform add android
cd ..
```
2023-09-18 06:44:33 +00:00
11) Start the simulator:
2023-01-05 23:33:49 +00:00
2023-04-08 08:42:15 +00:00
```bash
quasar dev -m android
```
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
```
2023-09-18 06:44:33 +00:00
[^1]: See ["File Picker"](https://quasar.dev/vue-components/file-picker) in the Quasar documentation.
[^2]: See [`read` in "Reading Files"](/docs/api/parse-options)
[^3]: See ["SheetJS Data Model"](/docs/csf/) for more details on workbooks, worksheets, and other concepts.
[^4]: See [`sheet_to_json` in "Utilities"](/docs/api/utilities/array#array-output)
[^5]: See [`json_to_sheet` in "Utilities"](/docs/api/utilities/array#array-of-objects-input)
[^6]: See ["Workbook Helpers" in "Utilities"](/docs/api/utilities/wb) for details on `book_new` and `book_append_sheet`.
[^7]: See [`write` in "Writing Files"](/docs/api/write-options)
[^8]: See [`requestFileSystem`](https://developer.mozilla.org/en-US/docs/Web/API/Window/requestFileSystem) in the MDN Web Docs for more details.