Source

src/components/Services/Form.vue

<template>
  <div>
    <div>
      <h2 class="mt-3">{{ service.title }}</h2>
      <p class="text-muted">{{ service.description }}</p>
      <div v-if="!DISABLE_FORM_SAVING">
        <b-button @click="loadForm">Gespeichertes Formular laden</b-button>
        <b-collapse v-model="upload">
          <div style="max-width: 100%; overflow-x: scroll">
            <b-file v-model="uploadFile" accept=".efa" browse-text="Durchsuchen" class="m-2"
                    drop-placeholder="Datei ablegen"
                    placeholder="Keine Datei ausgewählt" style="max-width: 100%"
                    @input="loadForm"></b-file>
          </div>
        </b-collapse>
        <p v-if="wrong" class="text-danger">Diese Datei ist für einen anderen Fragebogen</p>
      </div>
      <hr>
    </div>
    <json-form v-if="form" :json="form" :onSubmit="onSubmit" :ui="service.ui">
      <div style="width: 100%; display: flex; justify-content: center">
        <ActionButtonGroup :doing="doing" :save-button="!DISABLE_FORM_SAVING"
                           :selected="selected" :service="service"
                           @submitModal="submitModal"/>
      </div>
    </json-form>
    <b-modal centered v-model="showPdfViewer" title="PDF Dokument" size="xl" hide-footer>
      <template #modal-header>
        <div>
          <h5 class="mb-0">PDF Dokument</h5>
          <p class="text-muted m-0">Wählen Sie zum Drucken das Drucker-Symbol auf der rechten Seite</p>
        </div>
        <b-button-close @click="showPdfViewer=false"></b-button-close>
      </template>
      <div id="viewerContent" class="m-n3">
        <iframe style="height:75vh; width: 100%; max-width: 100% !important;"
                :src="'/pdf/web/viewer.html?file='+pdfData" allowfullscreen>
          <p>This browser does not support PDF!</p>
        </iframe>

<!--                <iframe style="height:80vh" :src="pdfData" allowfullscreen>-->
<!--                  <p>This browser does not support PDF!</p>-->
<!--                </iframe>-->

        <!--                <object style="height:80vh" :data="pdfData" type="application/pdf" width="100%" height="100%">-->
        <!--                  <p>Ihr Browser unterstützt das Anzeigen von pdfs nicht</p>-->
        <!--                </object>-->
      </div>
    </b-modal>
  </div>
</template>

<script>
import jsonForm from "@educorvi/vue-json-form"
import serviceMixin from "@/components/Services/serviceMixin";
import axios from "axios";
import {fileOptions, normURLS} from "@/utilities/globals.mjs";
import {API_ROOT_URL, DISABLE_FORM_SAVING, INSTANCE_ID} from "../../../config";
import {saveAs} from "file-saver";
import {mapGetters} from "vuex";
import ActionButtonGroup from "@/components/Services/ActionButtonGroup";


/**
 * @module Form
 * @description Renders a form and enables actions for that form
 * @category Components
 * @subcategory Services
 */

export default {
  name: "Form",
  data() {
    return {
      indexOfAction: 0,
      doing: [],
      form: null,
      wrong: false,
      upload: false,
      uploadFile: null,
      filledFormData: null,
      showPdfViewer: false,
      pdfData: null
    }
  },
  mixins: [serviceMixin],
  components: {ActionButtonGroup, jsonForm},
  created() {
    // Set data in load as default in json schema to load files if there is a file loaded
    if (!this.load) {
      this.form = this.service.form;
    } else {
      this.fill(this.load);
    }
  },
  computed: {
    ...mapGetters(["load"]),
    DISABLE_FORM_SAVING() {
      return DISABLE_FORM_SAVING;
    },
  },
  methods: {
    /**
     * Executed when a formaction is pressed
     * @param index The index of the formaction
     */
    selected(index) {
      this.indexOfAction = index;
      if (this.service["formactions"][this.indexOfAction].method === "REDIRECT") {
        this.$router.push("/services/" + this.service["formactions"][this.indexOfAction].name);
      }
    },
    /**
     * Add the loaded data to json schema to load files
     * @param data parsed data
     */
    fill(data) {
      if (data.name !== this.service.name) {
        this.wrong = true;
        return;
      } else {
        this.wrong = false;
      }
      this.form = null;
      this.$nextTick().then(() => {
        let form = JSON.parse(JSON.stringify(this.service.form));
        const d = data.data;
        for (const key of Object.keys(d)) {
          const split = key.split("/");
          const k = split[split.length - 1];

          form.properties[k].default = d[key];
        }

        this.$store.commit("load", null);
        this.form = form;
      });
    },
    /**
     * Loads and parses data from saved file
     * @param file The file to load
     */
    loadForm(file) {
      if (file instanceof File) {
        this.upload = false;
        file.text().then(contents => this.fill(JSON.parse(contents)));
        return;
      }

      async function load() {
        const fileHandle = await window.showOpenFilePicker(fileOptions);
        const file = await fileHandle[0].getFile();
        const contents = await file.text();
        this.fill(JSON.parse(contents));
      }

      if (window.showOpenFilePicker !== undefined) {
        load.call(this);
      } else {
        this.upload = true;
      }

    },

    /**
     * Send data to ellaroot
     * @param data The data
     * @param additional Additional data
     */
    sendData(data, additional) {

      /**
       * convert base64 encoded to data uri
       * @param {string} type mimetype of the data
       * @param {string} base64data the data
       * @returns {string}
       */
      function convertToBase64Link(type, base64data) {
        return `data:${type};base64,${escape(base64data)}`
      }

      /**
       * Make the browser download a file
       * @param contentType mimeType
       * @param base64Data data
       * @param fileName filename
       */
      function downloadBase64File(contentType, base64Data, fileName) {
        const linkSource = convertToBase64Link(contentType, base64Data);
        const downloadLink = document.createElement("a");
        downloadLink.href = linkSource;
        downloadLink.download = fileName;
        downloadLink.click();
      }


      this.$set(this.doing, this.indexOfAction, true)
      //make request to server
      axios({
        method: this.service["formactions"][this.indexOfAction].method.toLowerCase(),
        url: normURLS(API_ROOT_URL) + '/' + INSTANCE_ID + "/" + this.service.name + "/" + this.service["formactions"][this.indexOfAction].name,
        data: {
          form: data,
          additional
        }
      }).then(res => {
        // interpret answer
        switch (res.data.type) {
          case 'email':
            window.location.href = `mailto:?subject=Fragebogen%20teilen&body=${res.data.content}`;
            break;
          case 'link':
            window.location.href = res.data.content;
            break;

          case 'file':
            if (res.data.mimeType === "application/pdf") {

              // create blob from base64
              const b64toBlob = (b64Data, contentType = '', sliceSize = 512) => {
                const byteCharacters = atob(b64Data);
                const byteArrays = [];

                for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
                  const slice = byteCharacters.slice(offset, offset + sliceSize);

                  const byteNumbers = new Array(slice.length);
                  for (let i = 0; i < slice.length; i++) {
                    byteNumbers[i] = slice.charCodeAt(i);
                  }

                  const byteArray = new Uint8Array(byteNumbers);
                  byteArrays.push(byteArray);
                }

                return new Blob(byteArrays, {type: contentType});
              };

              const blob = new Blob([b64toBlob(res.data.content)], {type: 'application/pdf'});

              // load pdf and open modal
              this.pdfData = URL.createObjectURL(blob)
              this.showPdfViewer = true;
            } else {
              downloadBase64File(res.data.mimeType, res.data.content, res.data.fileName);
            }

            break;
        }
      }).catch(err => {
        this.$bvToast.toast("Es gab einen Fehler beim Ausführen dieser Aktion! Bitte versuchen Sie es später erneut.", {
          title: "Fehler",
          variant: "danger"
        });
        console.error(err);
      }).finally(() => this.$set(this.doing, this.indexOfAction, false));
    },
    submitModal(modalData) {
      this.sendData(this.filledFormData, modalData);
    },
    /**
     * on form submit
     * @param data the form data
     */
    onSubmit(data) {
      const toSave = {data, name: this.service.name}
      this.filledFormData = data;

      //Funktion zum Speichern mit Filepicker
      async function save() {
        const handle = await window.showSaveFilePicker(fileOptions);
        const writable = await handle.createWritable();
        // Write the contents of the file to the stream.
        await writable.write(JSON.stringify(toSave));
        // Close the file and write the contents to disk.
        await writable.close();
      }

      // Speichern
      if (this.indexOfAction === 'save') {
        if (window.showSaveFilePicker !== undefined) {
          save();
        } else {
          saveAs(new Blob([JSON.stringify(toSave)], {type: "application/efa"}), this.service.title.replace(/ /g, "_") + ".efa");
        }

      } else {
        //Button action
        if (this.service["formactions"][this.indexOfAction].additional) {
          this.$root.$bvModal.show('modal_' + this.service["formactions"][this.indexOfAction].name);
        } else {
          this.sendData(data);
        }
      }
    }


  }
}
</script>

<style lang="scss">
@import "../../styles";
iframe {
  width: 100%;
  height: 100%;
  border: 0;
  border-bottom-left-radius: $border-radius;
  border-bottom-right-radius: $border-radius;
}

#viewerContent {
}
</style>