Vue JS multiple file upload with custom file name

Viewed 12

In my Vue app I have a product edit page where I can upload multiple attachments. For each attachment, the user must be able to enter a custom file name.

Therefore I have an array containing all the attachment items, where ach attachment item is rendered by a component called 'AttachmentsItem'.

The user can add extra attachments, or even remove them again.

The issue that I have is when removing items, for example:

Let's say I added 4 attachments in total. When I remove attachment number 3, the item is being removed correctly from the array. However, the 'file input html element' still seems to hold the file from attachment number 3 (which just has been removed).

What is the cause of this and how can I fix it?

The code of my parent component:

<template>
    <div class="products-edit">

        <!-- Spinner -->
        <spinner v-show="isLoading"></spinner>

        <!-- Form nl -->
        <section v-if="!isLoading" class="form">

            <form @submit.prevent="updateProduct">

                <div class="form-group-wrapper">

                    <!-- Featured image -->
                    <div class="form-group">

                        <h2>Product afbeelding</h2>

                        <div class="img-placeholder">
                            <img v-if="!product_details" src="/assets/images/placeholder.png" alt="Featured image">
                            <img v-else-if="product_details.featured_image"
                                :src="'/images/' + product_details.featured_image" alt="Featured image">
                            <img v-else src="/assets/images/placeholder.png" alt="Featured image">
                        </div>

                        <input type="file" ref="featured_image" id="featured_image" name="featured_image"
                            v-on:change="handleFileUpload()">

                        <span v-if="errors && errors.featured_image" class="error-message">{{ errors.featured_image[0]
                            }}</span>

                    </div>

                    <!-- Attachments -->
                    <div class="form-group">
                        <h2>Bijlagen</h2>

                        <AttachmentsItem @removeAttachment="removeAttachment" @addAttachment="addAttachment" v-for="(attachment,index) in attachments" :key="index" :attachment="attachment" :index="index"></AttachmentsItem>
                    </div>

                </div>

                <!-- Submit button -->
                <div class="form-group-wrapper">

                    <div class="form-group">

                        <button class="btn btn-primary btn-submit">
                            Opslaan

                            <spinner v-show="isPending"></spinner>
                        </button>

                        <p v-show="success" class="successMessage">
                            Wijzigigen opgeslagen
                            <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24"
                                stroke="currentColor" stroke-width="2">
                                <path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" />
                            </svg>
                        </p>

                    </div>

                </div>

            </form>

        </section>

    </div>
</template>

<script>
import { SET_PAGE_TITLE, SET_PAGE_CTA, IS_LOADING } from '../../constants';
import spinner from '../../components/spinner.vue';
import AttachmentsItemVue from '../../components/AttachmentsItem.vue';
import AttachmentsItem from '../../components/AttachmentsItem.vue';

export default {
    name: 'ProductsEdit',
    components: {
    spinner,
    AttachmentsItemVue,
    AttachmentsItem
},
    mounted() {
        this.setPageTitle;
        this.loadData();
    },
    data() {
        return {
            product: null,
            product_details: null,

            featured_image: '',
            attachments: [
                {
                    file: '',
                    name: '',
                }
            ],

            isLoading: true,
            isPending: false,
            errors: null,
            success: false,
        }
    },
    methods: {
        removeAttachment(index) {
            if (this.attachments.length > 1) {
                this.attachments.splice(index, 1);
            } else {
                this.attachments = [{}];
            }
        },
        addAttachment() {
            this.attachments.push({});
        },
        handleFileUpload() {
            this.featured_image = this.$refs.featured_image.files[0];
        },
        loadData() {
            axios.get(`/items/${this.$route.params.productId}/edit`)
                .then((res) => {
                    this.product = res.data.product;
                    this.product_details = res.data.product_details;

                    this.isLoading = false;
                })
                .catch((err) => {
                    console.log(err);

                    this.isLoading = false;
                });
        },
        updateProduct() {
            this.isPending = true;

            let f = new FormData();

            f.append('featured_image', this.featured_image);
            f.append('attachments', this.attachments);

            axios.post(`/items/${this.$route.params.productId}/update`, f)
                .then((res) => {
                    this.isPending = false;
                    this.success = true;

                    this.product_details = res.data.product_details;

                    setTimeout(() => {
                        this.success = false;
                    }, 3000);
                })
                .catch((err) => {
                    this.errors = err.response.data.errors;

                    this.isPending = false;
                });
        },
    },
    computed: {
        setPageTitle() {
            this.$store.dispatch('appStore/' + SET_PAGE_TITLE, 'Product aanpassen');
            this.$store.dispatch('appStore/' + SET_PAGE_CTA, { 'title': 'Terug', 'url': '/products' });
        },
        currentLang() {
            return this.$store.state.appStore.currentLanguage;
        },
    },
}
</script>

The code of my child component:

<template>
    <div class="attachments-item">
        <label :for="'attachment-' + index">Bijlage {{index + 1}}</label>
        
        <div class="input-group">
            <input type="file" :id="'attachment-' + index" ref="attachmentFile" v-on:change="handleFileUpload()" accept=".pdf,.docx">

            <input type="text" placeholder="Bijlage naam" v-model="attachment.name">

            <div class="action-buttons">
                <svg @click="addAttachment" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
                    stroke="currentColor" class="w-6 h-6">
                    <path stroke-linecap="round" stroke-linejoin="round" d="M12 9v6m3-3H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z" />
                </svg>

                <svg @click="removeAttachment" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
                    class="w-6 h-6">
                    <path stroke-linecap="round" stroke-linejoin="round" d="M15 12H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z" />
                </svg>
            </div>
        </div>

    </div>
</template>

<script>
export default {
    name: 'AttachmentsItem',
    props: {
        attachment: {
            type: Object,
            required: true,
        },
        index: {
            type: Number,
            required: true,
        },
    },
    methods: {
        addAttachment() {
            this.$emit('addAttachment');
        },
        removeAttachment() {
            this.$emit('removeAttachment', this.index);
        },
        handleFileUpload() {
            this.attachment.file = this.$refs.attachmentFile.files[0];
        },
    },
}
</script>
0 Answers
Related