3

I try to do a single post request to upload multiple files. Now I have a functionally method that works for multiple files. But I want a single request.

submitFile(){
    this.contract_file.forEach((file) =>{
        let formData = new FormData();
        formData.append('file', file.file);

        axios.post('contracts/uploadfile/' + this.id_contract,
            formData,
            {
            headers: {
                'Content-Type': 'multipart/form-data',
            }
        }
        ).then(function(){
            //
        })
        .catch(function(){
            //
        });
    })
},

    public function uploadFile(Request $request, Contract $contract)
    {
            $filename = $request->file('file')->getClientOriginalName();

            $path = $request->file('file')->store($contract->id,'uploads');
            $contractFile = new ContractFile();

            $contractFile->fill([
                'contract_id'   => $contract->id,
                'name'          => $filename,
                'path'          => $path,
            ])->save();
    }

Update: This is what I changed,but..

let formData = []
this.contract_file.forEach((file,index) =>{
    formData[index] = new FormData();
    formData[index].append('file', file.file);
})

foreach($request->file('file') as $file){
    //same code but I use  $fille

}

Message:

Missing boundary in multipart/form-data POST data in Unknown

Update2:

    <file-upload
    class="btn btn-primary"
    :multiple="true"
    :drop="true"
    :drop-directory="true"
    v-model="files"
    @input-filter="inputFilter"
    @input-file="inputFile"

    ref="upload">
    <i class="fa fa-plus"></i>
    Select files
    </file-upload>

2 Answers 2

2

My answer is not properly tested since I had to adapt my code. Let me know if it doesn't work or if I'm missing something.

Basically, I built my own FormData to be more flexible and easier to reuse.

Form.vue

<template>
<div>
    <input @change="upload($event)"
        type="file"
        name="picture"
        id="new-file"
        class="custom-file-input"
        aria-label="picture"
        multiple
        >
    <label class="custom-file-label" for="new-file">
        <span>File...</span>
        <span class="btn-primary">Browse</span>
    </label>
    <button @click="submit" type="button" >Submit</button>
</div>
<template>

<script>
    import MyFormData from "./MyFormData";

    export default {
        data() {
           return {
               form: new MyFormData({contract_id: 5, files: []})
           }
        },
        methods: {
            upload(event) {
                for (let file of event.target.files) {
                    try {
                        let reader = new FileReader();
                        reader.readAsDataURL(file); // Not sure if this will work in this context.
                        this.form.files.push(file);
                    } catch {}
                }
            },
            submit(){
                this.form.post('/my-url')
                    .catch(errors => {
                        throw errors;
                    })
                    .then((response) => location = response.data.redirect);
            }
        }
    }
</script>

MyFormData.js

export default class MyFormData {
    constructor(data, headers) {
        // Assign the keys with the current object MyFormData so we can access directly to the data: 
        // (new FormData({myvalue: "hello"})).myvalue; // returns 'hello'
        Object.assign(this, data);

        // Preserve the originalData to know which keys we have and/or reset the form
        this.originalData = JSON.parse(JSON.stringify(data));

        this.form = null;
        this.errors = {};
        this.submitted = false;
        this.headers = headers || {}
    }

    // https://stackoverflow.com/a/42483509/8068675
    // It will build a multi-dimensional Formdata
    buildFormData(data, parentKey) {
        if (data && typeof data === 'object' && !(data instanceof Date) && !(data instanceof File) && !(data instanceof Blob)) {
            Object.keys(data).forEach(key => {
                this.buildFormData(data[key], parentKey ? `${parentKey}[${key}]` : key);
            });
        } else {
            const value = data == null ? '' : data;
            this.form.append(parentKey, value);
        }
    }

    // Returns all the new / modified data from MyFormData
    data() {
        return Object.keys(this.originalData).reduce((data, attribute) => {
            data[attribute] = this[attribute];
            return data;
        }, {});
    }

    post(endpoint) {
        return this.submit(endpoint);
    }

    patch(endpoint) {
        return this.submit(endpoint, 'patch');
    }

    delete(endpoint) {
        return axios.delete(endpoint, {}, this.headers)
            .catch(this.onFail.bind(this))
            .then(this.onSuccess.bind(this));
    }

    submit(endpoint, requestType = 'post') {
        this.form = new FormData();
        this.form.append('_method', requestType);
        this.buildFormData(this.data());

        return axios.post(endpoint, this.form, {
            headers: {
                'Content-Type': `multipart/form-data; boundary=${this.form._boundary}`,
            }
        })
            .catch(this.onFail.bind(this))
            .then(this.onSuccess.bind(this));
    }

    onSuccess(response) {
        this.submitted = true;
        this.errors = {};

        return response;
    }

    onFail(error) {

        console.log(error);

        this.errors = error.response.data.errors;
        this.submitted = false;

        throw error;
    }

    reset() {
        Object.assign(this, this.originalData);
    }
}

Edit Based on your note specifying you're using vue-upload-component

Your submit method should look like this

submitFile(){

    let files = this.contract_file.map((obj) => obj.file));
    let form = new MyFormData({files: files});

    form.post('contracts/uploadfile/' + this.id_contract)
        .then(function(){
            //
        })
        .catch(function(){
            //
        });
},

In your controller

public function uploadFile(Request $request, Contract $contract) {
    if($request->hasFile('files')){
        $files = $request->file('files');
        foreach ($files as $file) {
            $filename = $file->getClientOriginalName();
            $path = $file->store($contract->id,'uploads');

            $contractFile = new ContractFile();
            $contractFile->fill([
                'contract_id'   => $contract->id,
                'name'          => $filename,
                'path'          => $path,
            ])->save();
        }
    }
}
Sign up to request clarification or add additional context in comments.

4 Comments

I created a new FormData, like in your example. But I am not sure how to implement your code in mine. For upload, am am using a library vue-upload-component. I updated the post.
I think you don't need upload method then and only needs this in your submitFile() {let form = new MyFormData({whatever: 'another-optional-data', files: this.contract_file.map((upload) => upload.file}); form.post('url...') }
@Beusebiu I have updated my answer. Let me know if this works for you
Great! Thank you!
1

Adding the boundary to the Content-Type header fixed my problem. You can do it like below. Just change only submitFile() function.

submitFile(){
    this.contract_file.forEach((file) =>{
        let formData = new FormData();
        formData.append('file', file.file);

        axios.post('contracts/uploadfile/' + this.id_contract,
            formData,
            {
            headers: {
                'Content-Type': 'multipart/form-data;boundary=' + Math.random().toString().substr(2),
            }
        }
        ).then(function(){
            //
        })
        .catch(function(){
            //
        });
    })
},

2 Comments

This already works for me, but I do multiple post requests is this case, and I want to do a single request.
Did you add boundary to the Content-Type header?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.