vuetify-admin icon indicating copy to clipboard operation
vuetify-admin copied to clipboard

FetchJSON / FetchHydra providers don't support file uploads

Open nonsintetic opened this issue 3 years ago • 5 comments

When using the FetchJSON or FetchHydra (which mostly inherits from it) providers there is no support for file uploads.

These providers use JSON.stringify() to get form data and send the result to the server using HTTP Fetch. This results in request bodies such as { file: {} } when using a va-file-input field in a create/edit form.

In opposition, the Laravel provider uses FormData which does seem to support file uploads.

nonsintetic avatar Nov 16 '21 12:11 nonsintetic

@nonsintetic Did you figure out this by any chance? Doesn't the vuetify-admin provide objectToFormData utility, which can be utilized for this task?

rokmy avatar Nov 18 '21 10:11 rokmy

@rokmy it does, but you essentially have to write your own data provider for that to work. You could use the Laravel provider and adapt it for json/hydra APIs for example, the principle is the same (using FormData to support file attachments and sending that with Fetch to the server).

If I do get around to writing a better provider I'll be sure to submit a pull request, I seem to be using VA for a lot of things these days.

Due to time crunch and only needing it for a single thing, I just wrote a separate method to upload that particular file at the moment.

nonsintetic avatar Nov 18 '21 12:11 nonsintetic

Would you be able to share the code snipper for the upload? I've had success while hardcoding a listener in one of the CRUD pages (Create.vue) for the form, but it seems like a very messy solution.

The thing I'm working on now, is modifying the hydra provider (hydra.js)

      let data = objectToFormData(params.data);
      const formdata = new FormData();
      formdata.append('file', params.data.file);
      httpClient.post(`${resource}`, formdata)
    },

but the upload payload seems to be empty.

rokmy avatar Nov 18 '21 12:11 rokmy

<template>
  <va-create-layout>
    <v-form ref="form">
      <v-file-input
        v-model="file"
        accept="image/*,.pdf"
        label="Upload file"
      ></v-file-input>
      <v-btn
        :disabled="!file"
        color="success"
        class="mt-4"
        @click="send"
      >
        Upload
      </v-btn>
    </v-form>
  </va-create-layout>
</template>

<script>
export default {
  props: ["id", "item"],
  data() {
    return {
      file: null,
    };
  },
  methods: {
    send() {
      let self = this;
      //assemble headers
      let headers = new Headers({
        Accept: "application/ld+json",
      });
      let token = localStorage.getItem("jwt_token");
      if (token) {
        headers.set("Authorization", `Bearer ${token}`);
      }
      //assemble form data
      var data = new FormData();
      data.append("file", this.file);
     //if you have other fields, here is where you'd add them to the data object

      //send request to api
      //"this.$admin.http.url" - a way to get the URL of your api as set in the admin.js file
      fetch(this.$admin.http.url + "/files", {
        method: "POST",
        headers: headers,
        body: data,
      })
        .then((response) => response.json())
        .then(function (response) {
          if (response["@id"]) {
            //this signals to VA that the item was saved successfully
            self.$emit("saved");
          }
        });
       //TODO: lol error handling
    },
  },
};
</script>

Regarding your example, objectToFormData() already returns a FormData object, so you can just pass it the object that contains the fields and data, then send what it spits out as the body of your request.

nonsintetic avatar Nov 18 '21 12:11 nonsintetic

Thanks so much, I've modified hydra.js file to make it more dynamic, and it seems to be working for the specific use case. I will make some case switch statements later on.

      const formdata = new FormData();
      formdata.append('file', params.data.file);
      console.log(formdata.getAll('file'))

      let headers = new Headers({
        Accept: "application/ld+json",
      });

      fetch('url', {
        method: "POST",
        headers: headers,
        body: formdata
      })
        .then((response) => response.json)
        .then(function (response) {
          console.log(response)
      })

Thanks so much for the help!

rokmy avatar Nov 18 '21 13:11 rokmy