2

I have a form with multiple inputs that includes file input too. Now, I want to pass these data on the onSubmit function. but, there is an issue, in the quasar documentation, I didn't see instruction about file upload by Axios in the script part. I read Uploader in the quasar doc and also I read this one from Stackoverlow, But I didn't work for me.

Also, this is my templates code:

<template>
  <div class="q-pa-md q-mt-md">
    <q-card class="my-card">
      <q-form
        @submit="onSubmit"
        class="q-gutter-md"
      >
        <div class="row justify-center">
          <q-uploader
            label="Upload your music"
            color="purple"
            accept=".mp3"
            :max-file-size="20000000"
            square
            flat
            @add="file_selected"
            bordered
          />
        </div>
        <div class="row justify-center">
          <q-btn label="Edit" type="submit" color="primary" v-if="song_id" class="q-ma-md" />
          <q-btn label="Add" type="submit" color="primary" v-else class="q-ma-md" />
          <q-btn label="Cancel" type="reset" color="primary" flat class="q-ml-sm" />
        </div>
      </q-form>
    </q-card>
  </div>
</template>

And the methods part:

file_selected: function (file) {
  console.log(file)
  this.selected_file = file[0]
  this.check_if_document_upload = true
},
onSubmit: function () {
  const url = '/core/v1/api/songs/upload'
  const fileData = new FormData()
  fileData.append('file_data', this.selected_file)
  fileData.append('song_id', this.song_id)
  this.$axios.post(url, fileData, {
    headers: {
      'Content-Type': 'multipart/form-data'
    }
  }).then(function () {
    console.log('SUCCESS!!')
  })
    .catch(function () {
      console.log('FAILURE!!')
    })

And data part:

data: () => ({
    selected_file: '',
    check_if_document_upload: false,
    song_id: '',
    song_data: {
      status: true
    },
    dashData: []
  }),
Ali Hallaji
  • 3,712
  • 2
  • 29
  • 36

3 Answers3

2

If quasar uploads isn't working for you and you are using state management vuex, you could attempt writing custom code to accomplish what you want. Try this for sending the post request using axios

createEvents({ commit }, payload) {
  const stuff = {
    title: payload.title,
    location: payload.location,
    description: payload.description,
    image = payload.image;
  };
  let formData = new FormData();
  bodyFormData.set('title', stuff.title); //done for the text data
  formData.append("imageUrl", stuff.image);  //done for file data
  
  axios
    .post({
      method: 'post',
      url: 'myurl',
      data: formData,
       headers: {'Content-Type': 'multipart/form-data' }
     })
    .then(response => {
      commit("createEvents", response.data);
    })
    .catch(err => err.data);
  }
}

And for the submit function(method), it should look something like this

createEvent(){
  const newEvent = {
    title: '',
    location: '',
    description: '',
    image: this.image,
  };
  this.$store.dispatch("createEvents", newEvent);
};

finally, the form itself in your code. The image should be referenced with a simple <input type='file' ref='image'> and the rest of your form can be normal

<form>
  <input type='text' v-model='text'>
  <-- more of the same -->
  <input type='file' ref='image'>
  // prevent is to keep the page from reloading when the form gets submitted,
  // just a precaution measure
  <button type=submit @click.prevent=createEvent()>submit</button> 
</form>

Hope this helped

Super Kai - Kazuya Ito
  • 22,221
  • 10
  • 124
  • 129
maestro.inc
  • 796
  • 1
  • 6
  • 13
  • Thank you, it didn't work, I put my template code in the question. I need to do this by `q-uploader`. I didn't know how can I pass file data to Axios data. – Ali Hallaji Feb 27 '20 at 16:56
  • I added how to send file data using axios as well in the answer above – maestro.inc Feb 27 '20 at 19:14
1

I found my issue. I should change @add to @added in the template.

<template>
  <div class="q-pa-md q-mt-md">
    <q-card class="my-card">
      <q-form
        @submit="onSubmit"
        class="q-gutter-md"
      >
        <div class="row justify-center">
          <q-uploader
            label="Upload your music"
            color="purple"
            accept=".mp3"
            :max-file-size="20000000"
            square
            flat
            @added="file_selected"
            bordered
          />
        </div>
        <div class="row justify-center">
          <q-btn label="Edit" type="submit" color="primary" v-if="song_id" class="q-ma-md" />
          <q-btn label="Add" type="submit" color="primary" v-else class="q-ma-md" />
          <q-btn label="Cancel" type="reset" color="primary" flat class="q-ml-sm" />
        </div>
      </q-form>
    </q-card>
  </div>
</template>
Ali Hallaji
  • 3,712
  • 2
  • 29
  • 36
0

If you want to keep the QUploader functionalities, status changes, upload progress, in my case I made the component extension and it works fine, maybe it is not efficient because I had to add my own methods: upload, __uploadFiles.

Don't add method to override __runFactory, since I omitted batch load option, and I will always use factory as function.

QUploader source:

Part Code from Quasar Components Uploader -> xhr mixin.js

 upload () {
  if (this.canUpload === false) {
    return
  }

  const queue = this.queuedFiles.slice(0)
  this.queuedFiles = []

  if (this.xhrProps.batch(queue)) {
    this.__runFactory(queue)
  }
  else {
    queue.forEach(file => {
      this.__runFactory([ file ])
    })
  }
},

__runFactory (files) {
  this.workingThreads++

  if (typeof this.factory !== 'function') {
    this.__uploadFiles(files, {})
    return
  }

  const res = this.factory(files)

  if (!res) {
    this.$emit(
      'factory-failed',
      new Error('QUploader: factory() does not return properly'),
      files
    )
    this.workingThreads--
  }
  else if (typeof res.catch === 'function' && typeof res.then === 'function') {
    this.promises.push(res)

    const failed = err => {
      if (this._isBeingDestroyed !== true && this._isDestroyed !== true) {
        this.promises = this.promises.filter(p => p !== res)

        if (this.promises.length === 0) {
          this.abortPromises = false
        }

        this.queuedFiles = this.queuedFiles.concat(files)
        files.forEach(f => { this.__updateFile(f, 'failed') })

        this.$emit('factory-failed', err, files)
        this.workingThreads--
      }
    }

    res.then(factory => {
      if (this.abortPromises === true) {
        failed(new Error('Aborted'))
      }
      else if (this._isBeingDestroyed !== true && this._isDestroyed !== true) {
        this.promises = this.promises.filter(p => p !== res)
        this.__uploadFiles(files, factory)
      }
    }).catch(failed)
  }
  else {
    this.__uploadFiles(files, res || {})
  }
},

__uploadFiles (files, factory) {
  const
    form = new FormData(),
    xhr = new XMLHttpRequest()

  const getProp = (name, arg) => {
    return factory[name] !== void 0
      ? getFn(factory[name])(arg)
      : this.xhrProps[name](arg)
  }

  const url = getProp('url', files)

  if (!url) {
    console.error('q-uploader: invalid or no URL specified')
    this.workingThreads--
    return
  }

  const fields = getProp('formFields', files)
  fields !== void 0 && fields.forEach(field => {
    form.append(field.name, field.value)
  })

  let
    uploadIndex = 0,
    uploadIndexSize = 0,
    uploadedSize = 0,
    maxUploadSize = 0,
    aborted

  xhr.upload.addEventListener('progress', e => {
    if (aborted === true) { return }

    const loaded = Math.min(maxUploadSize, e.loaded)

    this.uploadedSize += loaded - uploadedSize
    uploadedSize = loaded

    let size = uploadedSize - uploadIndexSize
    for (let i = uploadIndex; size > 0 && i < files.length; i++) {
      const
        file = files[i],
        uploaded = size > file.size

      if (uploaded) {
        size -= file.size
        uploadIndex++
        uploadIndexSize += file.size
        this.__updateFile(file, 'uploading', file.size)
      }
      else {
        this.__updateFile(file, 'uploading', size)
        return
      }
    }
  }, false)

  xhr.onreadystatechange = () => {
    if (xhr.readyState < 4) {
      return
    }

    if (xhr.status && xhr.status < 400) {
      this.uploadedFiles = this.uploadedFiles.concat(files)
      files.forEach(f => { this.__updateFile(f, 'uploaded') })
      this.$emit('uploaded', { files, xhr })
    }
    else {
      aborted = true
      this.uploadedSize -= uploadedSize
      this.queuedFiles = this.queuedFiles.concat(files)
      files.forEach(f => { this.__updateFile(f, 'failed') })
      this.$emit('failed', { files, xhr })
    }

    this.workingThreads--
    this.xhrs = this.xhrs.filter(x => x !== xhr)
  }

  xhr.open(
    getProp('method', files),
    url
  )

  if (getProp('withCredentials', files) === true) {
    xhr.withCredentials = true
  }

  const headers = getProp('headers', files)
  headers !== void 0 && headers.forEach(head => {
    xhr.setRequestHeader(head.name, head.value)
  })

  const sendRaw = getProp('sendRaw', files)

  files.forEach(file => {
    this.__updateFile(file, 'uploading', 0)
    if (sendRaw !== true) {
      form.append(getProp('fieldName', file), file, file.name)
    }
    file.xhr = xhr
    file.__abort = () => { xhr.abort() }
    maxUploadSize += file.size
  })

  this.$emit('uploading', { files, xhr })
  this.xhrs.push(xhr)

  if (sendRaw === true) {
    xhr.send(new Blob(files))
  }
  else {
    xhr.send(form)
  }
}

Result: With AXIOS - Component Vue that extend from QUploader

    <script lang="ts">
import { QUploader } from 'quasar';

export default class AxiosUploader extends QUploader {
  constructor(props) {
    super(props);
  }


  AxiosUpload() {
    if (this.canUpload === false) {
      return;
    }

    const queue = this.queuedFiles.slice(0);
    this.queuedFiles = [];

    const factory = this.factory(queue);


    queue.forEach(file => {
      this.workingThreads++;
      this.uploadFiles([file], factory);
    });
  }

  uploadFiles(files, factory) {
    const form = new FormData(),
      headers = {};

    factory.headers.forEach(head => {
      headers[head.name] = head.value;
    });


    factory.formFields.forEach(field => {
      form.append(field.name, field.value);
    });

    form.append(factory.fieldName, files[0], files[0].name);

    let uploadIndex = 0,
      uploadIndexSize = 0,
      uploadedSize = 0,
      maxUploadSize = 0,
      aborted;


    const xhr = this.$axios.post(factory.url, form, {
      headers,
      onUploadProgress: (e: ProgressEvent) => {
        if (aborted === true) {
          return;
        }
        const loaded = Math.min(maxUploadSize, e.loaded);

        this.uploadedSize += loaded - uploadedSize;
        uploadedSize = loaded;

        let size = uploadedSize - uploadIndexSize;
        for (let i = uploadIndex; size > 0 && i < files.length; i++) {
          const file = files[i],
            uploaded = size > file.size;

          if (uploaded) {
            size -= file.size;
            uploadIndex++;
            uploadIndexSize += file.size;
            this.__updateFile(file, 'uploading', file.size);
          } else {
            this.__updateFile(file, 'uploading', size);
            return;
          }
        }
      }
    });

    this.xhrs.push(xhr);
    this.$emit('uploading', { files, xhr });

    xhr
      .then(res => {
        this.uploadedFiles = this.uploadedFiles.concat(files);
        files.forEach(f => {
          this.__updateFile(f, 'uploaded');
        });
        this.$emit('uploaded', { files, xhr });
      })
      .catch(err => {
        aborted = true;
        this.uploadedSize -= uploadedSize;
        this.queuedFiles = this.queuedFiles.concat(files);
        files.forEach(f => {
          this.__updateFile(f, 'failed');
        });
        this.$emit('failed', { files, xhr });
      })
      .finally(() => {
        this.workingThreads--;
        this.xhrs = this.xhrs.filter(x => x !== xhr);
      });

    files.forEach(file => {
      this.__updateFile(file, 'uploading', 0);
      file.xhr = xhr;
      file.__abort = () => {
        xhr.abort();
      };
      maxUploadSize += file.size;
    });

    this.$emit('uploading', { files, xhr });
    this.xhrs.push(xhr);

  }
}
</script>

The component to use is AxiosUploader instead of q-uploader, and instead of calling the upload () method, I call the AxiosUpload method. You could adapt to your needs