---
title: Quasar
pagination_prev: demos/static/index
pagination_next: demos/desktop/index
sidebar_position: 3
sidebar_custom_props:
  summary: VueJS + Web View
---

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:

<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>

### 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
<q-file label="Load File" filled label-color="orange" @input="updateFile"/>
```

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
```

<!-- 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 -->

```bash
cd SheetJSQuasar
npm i
npm i --save https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.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

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"
<template>
  <q-page class="row items-center justify-evenly">
    <!-- highlight-start -->
    <q-table :rows="todos" />
    <q-btn-group>
      <q-file label="Load File" filled label-color="orange" @input="updateFile"/>
      <q-btn label="Save File" @click="saveFile" />
    </q-btn-group>
    <!-- highlight-end -->  </q-page>
</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() {
    }
    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<any>(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 <https://sheetjs.com/pres.numbers>
- 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
```