Search code examples
filevue.jsdownloadelectronsheetjs

electron sheetjs download generated file from JSON


I have an Electron app with VueJS and I try to download a generated XLS with sheetjs but the provided workaround does not work in my case. Here is what I have been trying:

 exportData(id, type) {
      // eslint-disable-next-line no-console
      console.log(type);

      (async () => {
        const data = await ProjectController.getProject(id);
        const ws = XLSX.utils.json_to_sheet(JSON.parse(data.excel));
        // eslint-disable-next-line no-console

        let wb = XLSX.utils.book_new();
        XLSX.utils.book_append_sheet(wb, ws, "test");
        let o = dialog.showSaveDialog();
        // eslint-disable-next-line no-console
        console.log(o);

        XLSX.writeFile(wb, o);

        // eslint-disable-next-line no-console
        //console.log(file);
      })();
    }

In console I get the following error Uncaught (in promise) TypeError: o.file.lastIndexOf is not a function.

if I use the following code

  (async () => {
        const data = await ProjectController.getProject(id);

        const ws = (XLSX.WorkSheet = XLSX.utils.json_to_sheet(
          JSON.parse(data.excel)
        ));

        let wb = XLSX.utils.book_new();
        XLSX.utils.book_append_sheet(wb, ws, "test");


        const file = XLSX.write(wb, {
          bookType: "xlsx",
          type: "buffer",
          compression: true
        });

        let savePath = dialog.showSaveDialog({});
        XLSX.writeFile(file, savePath);

        fs.writeFileSync("test1.xlsx", file);


      })();

than the file will be downloaded inside of the root of project folder without no popup before. But I would like to be able to download this files in a chosen folder by user.

here is my package.json

{
  "name": "movie-translation-tool",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint",
    "electron:build": "vue-cli-service electron:build",
    "electron:serve": "vue-cli-service electron:serve",
    "i18n:report": "vue-cli-service i18n:report --src './src/**/*.?(js|vue)' --locales './src/locales/**/*.json'",
    "postinstall": "electron-builder install-app-deps",
    "postuninstall": "electron-builder install-app-deps"
  },
  "main": "background.js",
  "dependencies": {
    "awesome-phonenumber": "^2.24.0",
    "core-js": "^3.4.3",
    "dropbox": "^4.0.30",
    "knex": "^0.20.3",
    "mssql": "^6.0.1",
    "objection": "^2.0.3",
    "pdf2json": "^1.2.0",
    "sqlite3": "^4.1.1",
    "vee-validate": "^3.1.3",
    "vue": "^2.6.10",
    "vue-i18n": "^8.0.0",
    "vue-router": "^3.1.3",
    "vue-video-player": "^5.0.2",
    "vuetify": "^2.1.0",
    "vuetify-image-input": "^19.1.0",
    "vuex": "^3.1.2",
    "xlsx": "^0.15.3"
  },
  "devDependencies": {
    "@mdi/font": "^4.6.95",
    "@mdi/js": "^4.6.95",
    "@vue/cli-plugin-babel": "^4.1.0",
    "@vue/cli-plugin-eslint": "^4.1.0",
    "@vue/cli-plugin-router": "^4.1.1",
    "@vue/cli-plugin-vuex": "^4.1.1",
    "@vue/cli-service": "^4.1.0",
    "babel-eslint": "^10.0.3",
    "electron": "^6.0.0",
    "eslint": "^5.16.0",
    "eslint-plugin-vue": "^5.0.0",
    "material-design-icons-iconfont": "^5.0.1",
    "sass": "^1.19.0",
    "sass-loader": "^10.0.0",
    "vue-cli-plugin-electron-builder": "^1.4.3",
    "vue-cli-plugin-i18n": "^0.6.1",
    "vue-cli-plugin-vuetify": "^2.0.2",
    "vue-template-compiler": "^2.6.10",
    "vuetify-loader": "^1.3.0"
  },
  "eslintConfig": {
    "root": true,
    "env": {
      "node": true
    },
    "extends": [
      "plugin:vue/essential",
      "eslint:recommended"
    ],
    "rules": {},
    "parserOptions": {
      "parser": "babel-eslint"
    }
  },
  "browserslist": [
    "> 1%",
    "last 2 versions"
  ]
}

How can I attach the file to the windows properly?


Solution

  • The dialog method dialog.showSaveDialog returns a promise which resolves to an object, it does not return a string:

    dialog.showSaveDialog([browserWindow, ]options)

    Returns Promise<Object> - Resolve with an object containing the following:

    • canceled Boolean - whether or not the dialog was canceled.
    • filePath String (optional) - If the dialog is canceled, this will be undefined.
    • bookmark String (optional) macOS mas - Base64 encoded string which contains the security scoped bookmark data for the saved file. securityScopedBookmarks must be enabled for this to be present. (For return values, see table here.)

    You have to await for dialog.showSaveDialog or use dialog.showSaveDialogSync and also, you can make exportData to be asynchronous rather than creating an inner async function.

    Try to refactor your code like this:

    async exportData(id, type) {
      const data = await ProjectController.getProject(id);
      const ws = XLSX.utils.json_to_sheet(JSON.parse(data.excel));
      const wb = XLSX.utils.book_new();
    
      XLSX.utils.book_append_sheet(wb, ws, "test");
    
      let { filePath, canceled } = await dialog.showSaveDialog();
    
      if (!canceled) {
        XLSX.writeFile(wb, filePath);
      }
    }