question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

File input with "multiple" attribute doesn't upload newly added files after server returns error response

See original GitHub issue

Describe the bug Using a File Input with “multiple” attribute and custom uploader, as shown in the documentation.

If the server responds with an error when you select a file, then when you try to upload another file, it appears in the list, but the actual upload is not performed.

To Reproduce Steps to reproduce the behavior:

  1. Go to reproduction page
  2. Click on the file input and select one file. The “server” rejects the file and an error is shown as expected.
  3. Click on the + Add File button and select another file.
  4. The file appears in the list, but the actual upload is not performed.
  5. [Optional] Repeat step 3 and see the result as in the step 4.

Reproduction https://codepen.io/apotheosis91/pen/xxgGQKb

Expected behavior Newly added files after the server responds with an error should be uploaded.

Issue Analytics

  • State:open
  • Created 2 years ago
  • Reactions:1
  • Comments:6

github_iconTop GitHub Comments

2reactions
stefanazcommented, May 1, 2021

@vicenterusso thanks! That is very nice one.

2reactions
apotheosis91commented, May 1, 2021

@vicenterusso, Sure, here’s what I did:

  1. Create FormulateDropzone.vue component with the following content: (I also changed the default dropzone styles and markup, but I omit that part to make the code clearer)
<template>
    <div ref="dz" :id="context.attributes.id" class="dropzone">
        <div class="dz-message">
            <div v-if="!hasFiles">
                {{ dropzoneLabel }}
            </div>
        </div>
    </div>
</template>

<script>
import Dropzone from 'dropzone';
import 'dropzone/dist/min/dropzone.min.css';

Dropzone.autoDiscover = false;

export default {
    name: "FormulateDropzone",
    props: {
        context: {
            type: Object,
            required: true
        },
        initialFiles: {
            type: Array,
            default: () => []
        },
        maxFiles: {
            type: Number,
            default: null
        },
        maxFilesize: {
            type: Number,
            default: null
        },
        acceptedFiles: {
            type: String,
            default: null
        },
        dropzoneOptions: {
            type: Object,
            default: () => ({})
        },
        dropzoneLabel: {
            type: String,
            default: 'Drop files here or click to upload'
        }
    },
    mounted() {
        this.context.model = this.context.model || [];

        this.dropzone = new Dropzone(this.$refs.dz, this.options);

        this.dropzone
            .on('addedfile', this.onFileAdded)
            .on('removedfile', this.onFileRemoved)
            .on('success', this.onFileUploaded);

        // Display initial files
        // @see https://github.com/dropzone/dropzone/wiki/faq#how-to-show-files-already-stored-on-server
        this.initialFiles.forEach((item) => {
            const mockFile = {
                name: item.filename,
                size: item.size,
                mediaId: item.id,
                isMock: true
            };
            this.dropzone.displayExistingFile(mockFile, item.url, null, null, false);
        });

        // Dropzone's maxFiles option doesn't take into account initial mock files, so we need to reduce it (dropzone.js hack)
        if (this.options.maxFiles !== null) {
            this.dropzone.options.maxFiles -= this.initialFiles.length;
        }

    },
    beforeDestroy() {
        if (this.dropzone) {
            this.dropzone.destroy();
        }
    },
    computed: {
        options() {
            const defaults = {
                url: '/api/admin/uploads',
                method: 'post',
                paramName: 'file',
                headers: {
                    // 'X-XSRF-TOKEN': Cookies.get('XSRF-TOKEN')
                },
                addRemoveLinks: true,
                maxFiles: this.maxFiles,
                maxFilesize: this.maxFilesize,     // in MB
                acceptedFiles: this.acceptedFiles, // e.g. 'image/jpeg,image/png'
                error: this.onError,               // replace error handler here, to customize server error response handling
                dictMaxFilesExceeded: `Maximum allowed number of files: ${this.maxFiles}` // for displaying correct maxFiles count (dropzone.js hack)
            };

            return {...defaults, ...this.dropzoneOptions};
        },
        hasFiles() {
            return this.filesCount > 0;
        }
    },
    data() {
        return {
            filesCount: 0
        }
    },
    methods: {
        onFileAdded(file) {
            this.filesCount++;
        },
        onFileUploaded(file, response) {
            // Handle the case when the file is successfully uploaded to the server here.

            // In my case, the server returns id of the upload in the database.
            // I store it in the mediaId property and then write the required data to the VueFormulate context.model
            file.mediaId = response.id;

            this.context.model.push({id: response.id, action: 'add'});
        },
        onFileRemoved(file) {
            // Handle the case when the file is removed from the dropzone here.

            // In my case, I write the required data to the VueFormulate context.model
            // and then handle deletions on server side, after submitting the form with dropzone.
            if (file.mediaId) {
                this.context.model.push({id: file.mediaId, action: 'delete'});
            }

            this.filesCount--;

            // When removing initial file, we need to correct maxFiles option (dropzone.js hack)
            if (file.isMock && this.dropzone.options.maxFiles !== null) {
                this.dropzone.options.maxFiles++;
            }
        },
        onError(file, message, xhr) {
            // Handle Errors here.

            // I've modified the standard dropzone's error handler:
            // https://github.com/dropzone/dropzone/blob/9dfbd74fd245736dc4051f34a43e9ac7126f764e/src/options.js#L677
            // And when an error occurs, I remove the file preview from the dropzone
            // and show error notifications using euvl/vue-notification
            if (file.previewElement) {
                file.previewElement.classList.add("dz-error");

                // If the error was due to the XMLHttpRequest the xhr parameter will be present.
                if (typeof message === 'string') {
                    message = [message];
                } else if (xhr) {
                    message = this.parseUploadErrors(xhr);
                } else {
                    message = ['Error :('];
                }

                for (let node of file.previewElement.querySelectorAll("[data-dz-errormessage]")) {
                    node.textContent = message.join('; ');
                }

                if (this.$notify && typeof this.$notify === 'function') {
                    this.dropzone.removeFile(file);

                    message.forEach((value) => {
                        this.$notify({
                            group: 'app',
                            type: 'error',
                            title: 'Error',
                            text: value
                        });
                    });
                }
            }
        },
        parseUploadErrors(error) {
            // Parse server error response here and return array with error messages

            return ['Error. Something wrong happened.'];
        }
    }
}
</script>
  1. Register component in VueFormulate. In the file where you instantiate Vue add the following:
import FormulateDropzone from "./components/FormulateDropzone";

Vue.component('FormulateDropzone', FormulateDropzone);

Vue.use(VueFormulate, {
    library: {
        dropzone: {
            classification: 'custom',
            component: 'FormulateDropzone',
            slotProps: {
                component: ['initialFiles', 'maxFiles', 'maxFilesize', 'acceptedFiles', 'dropzoneOptions', 'dropzoneLabel']
            }
        }
    },
});
  1. Use it like any other FormulateInput components:
<formulate-form
    v-model="formValues"
    @submit="submitHandler"
>
    <formulate-input
        type="dropzone"
        name="images"
        label="Images"
        help="Add some images"
        dropzone-label="Click to upload files or drop them here"
        :max-files="5"
        :max-filesize="10"
        accepted-files="image/jpeg,image/png"
        :initial-files="[{id: 1, filename: 'file.jpg', size: '12345', url: 'path/to/file.jpg'}]"
    ></formulate-input>

    <!--  Other inputs ... -->

</formulate-form>
Read more comments on GitHub >

github_iconTop Results From Across the Web

multiple file input html not working - Stack Overflow
If the user operates the same file input upload process a second time anything selected prior is discarded and only the most recent...
Read more >
<input type="file"> - HTML: HyperText Markup Language | MDN
Once chosen, the files can be uploaded to a server using form ... When the multiple Boolean attribute is specified, the file input...
Read more >
Fix "Unexpected field" Error From Multer - Maxim Orlov
The foremost cause for this error is when the name attribute of the file input doesn't match the argument passed to one of...
Read more >
A strategy for handling multiple file uploads using JavaScript
The [mutiple] attribute. The first thing we need to make sure to do is set the mutiple attribute to the file input. ·...
Read more >
ASP.NET Core Blazor file uploads - Microsoft Learn
File selection isn't cumulative when using an InputFile component or its underlying HTML <input type="file"> , so you can't add files to an ......
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found