11 KiB
title | pagination_prev | pagination_next | sidebar_position | sidebar_custom_props | ||
---|---|---|---|---|---|---|
Quasar | demos/static/index | demos/desktop/index | 3 |
|
import current from '/version.js';
The NodeJS Module 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 |
---|---|
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:
cd src-cordova
cordova plugin add cordova-plugin-file
cd ..
Reading data
The q-file
component presents an API reminiscent of File Input elements:
<q-file label="Load File" filled label-color="orange" @input="updateFile"/>
When binding to the input
element, the callback receives an Event
object:
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:
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.
- Ensure all of the dependencies are installed. Install the CLI globally:
npm i -g @quasar/cli cordova
(you may need to run sudo npm i -g
if there are permission issues)
- Create a new app:
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
- Install dependencies:
{`\
cd SheetJSQuasar
npm i
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
- Set up Cordova:
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:
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:
cordova platform rm ios
cordova platform add ios
cordova plugin add cordova-plugin-file
:::
Return to the project directory:
cd ..
- Start the development server:
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.
:::
- Add the Dialog plugin to
quasar.config.js
:
framework: {
config: {},
// ...
// Quasar plugins
// highlight-next-line
plugins: ['Dialog']
},
- In the template section of
src/pages/IndexPage.vue
, replace the example with a Table, Save button and Load file picker component:
<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:
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:
:::caution
If the app is blank or not refreshing, delete the app and close the simulator, then restart the development process.
:::
- Wire up the
updateFile
function:
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.
- 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
:
Once selected, the screen should refresh with new contents.
- Wire up the
saveFile
function:
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:
- Find the file and verify the contents are correct. Run in a new terminal:
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:
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
- Create the Android project:
cd src-cordova
cordova platform add android
cd ..
- Start the simulator:
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 selectpres.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:
adb exec-out run-as org.sheetjs.quasar cat files/files/SheetJSQuasar.xlsx > /tmp/SheetJSQuasar.xlsx
npx xlsx-cli /tmp/SheetJSQuasar.xlsx