theUnforgiven's avatar

Uploading file or any type

Hi all,

I'm looking for a little advice here, I'm trying to get all form inputs as well as any type of file the user selects to upload. I have the following in my Vue component:

<template>
<div>
    <div class="inputArea" v-for="(input, index) in inputs" :key="index">
        <div class="table-responsive">
            <table class="table-sm">
                <tr>
                    <td>Item
                        <input type="text" :id="input.id" v-model="input.item" name="item[]"  class="form-control" value="" autocomplete="off" placeholder="Gas Bill">
                    </td>
                    <td>
                         Amount Outstanding
                        <div class="input-group">
                          <div class="input-group-prepend">
                            <span class="input-group-text">£</span>
                          </div>
                          <input type="number" :id="input.id" v-model="input.amount_os" name="amount_os[]" class="form-control" value="" autocomplete="off" placeholder="22.00">
                        </div>
                    </td>
                    <td>
                        Description
                        <input :id="input.id" v-model="input.description" type="text" class="form-control" name="description[]" value="">
                    </td>
                    <td>
                        Invoice
                        <input type="file" id="file" ref="file" @change="handleFileUpload()">
                    </td>
                    <td>
                        <br />
                        <button v-on:click.prevent="removeInput(input.id)" class="btn btn-sm btn-danger"><i class="fa fa-trash"></i></button>
                        <button @click.prevent="addInput" class="btn btn-sm btn-primary">Add Item</button>
                    </td>
                </tr>
            </table>

            <button class="btn btn-success mt-3" @click.prevent="addBill">Update</button>
        </div>
    </div>
</div>
</template>

<script>

export default {

    data() {
        return {
            counter: 0,
            inputs: [{
                id: '',
                item: '',
                amount_os: '',
                value: '',
                description: '',
                file: ''
            }],
        }
    },

    methods: {
        addInput(e) {
            e.preventDefault();
            this.inputs.push({
                id: `item${++this.counter}`,
                item: '',
                amount_os: '',
                value: '',
                description: '',
                file: ''
            });
        },
        removeInput : function (input) {
            this.inputs.splice(input, 1);
        },
        addBill() {                
            axios.post('/bills', { 
                    headers: {
                        "Content-type": "multipart/form-data; charset=utf-8; boundary=" + Math.random().toString().substr(2)
                    },
                    items: this.inputs, file: this.file     
                }).then(response => {
                        console.log(response);
                });
        },
        handleFileUpload(event){
            this.inputs.file = this.$refs.file.files[0];
          }
    }
}
</script>

Which results in the following response:

array:2 [
  "headers" => array:1 [
    "Content-type" => "multipart/form-data; charset=utf-8; boundary=9473711616144889"
  ]
  "items" => array:1 [
    0 => array:6 [
      "id" => null
      "item" => "qwe"
      "amount_os" => "22"
      "value" => null
      "description" => "qw"
      "file" => null
    ]
  ]
]

But there's no file there, even though I did select a file to upload. Any help & suggestions, greatly appreciated.

0 likes
12 replies
ftiersch's avatar

First thing I'm noticing is:

this.inputs.file = this.$refs.file.files[0];

There is something majorly wrong with that logic.

  1. Since you have a loop you have multiple refs called "file". That won't work. How should Vue know which one you want?
  2. You're calling this.inputs.file . this.inputs is an array of objects so how should JavaScript know which object you want to add the file to?
theUnforgiven's avatar

Ok thanks good spot! How would one change that to do what i need it do?

ftiersch's avatar

I would probably create an extra component for the inputs. Then you have it neatly in it's own scope and can handle everything you need to. And then when it's time to upload you can just loop through your components and ask them for their data, create a structured array from that and send it off to the server.

theUnforgiven's avatar

I don't really want to have it as an extra component, surely it can all be done from the same component?

ftiersch's avatar

It definitely can be but it's probably quite annoying. You could use the index inside your loop to determine which entry of the array you have to change.

Like:

handleFileUpload(event, index) {
    this.inputs[index].file = this.$refs['file' + index].files[0];
}

But then you also need to dynamically name your refs and add the index to the name (you see what I mean with quite annoying? :D)

theUnforgiven's avatar

Yes, I see what you mean. Maybe I can live with that, I'll try it out and see how it feels.

theUnforgiven's avatar

Now, I get Cannot read property 'files' of undefined error based on what you said.

theUnforgiven's avatar

Does anyone else have any suggestions to make this work within one component?

ftiersch's avatar

Have you changed the ref="" part of your

<input type="file">

? Should probably be something like :ref="'file' + index" (if you're using my example)

theUnforgiven's avatar

Seems there's still nothing in the array:

"items" => array:1 [
    0 => array:6 [
      "id" => null
      "item" => "ad"
      "amount_os" => "23"
      "value" => null
      "description" => "23"
      "file" => null
    ]
  ]

Please or to participate in this conversation.