Search code examples
javascripthtmltypescriptfileupload

How can I detect that the Cancel Button has been clicked on a <input type="file" /> Select File Dialog?


To handle file uploads on a website we have to use a hidden <input type="file" /> element.

To find out what file has been selected in the Select File Dialog, we can use the onchangeevent.

But how can we detect if the user has hit the cancel button? Unfortunately, there is no oncancel event.

There exist a lot of workarounds to detect if the user has hit the cancel button, but due to the problem I described here, none of them worked for me reliably.


Solution

  • I've invested countless hours looking for a solution. And now I want to share my solution with you.

    I use three event handlers:

    1. onchange event on the file input: To detect when a file has been selected.

    2. onfocus event on window: To detect when the Select File Dialog has been closed.

    3. onmousemove event on document.body: To detect when the interaction is not blocked anymore. Only when this event is called, you can be sure that the onchange event of the input element has been called.

    The first two points are obvious and you find them in most proposed solutions. But the crucial point is number 3. In other solutions I sometimes faced the problem that I selected a file, but this selected file has not been propagated to the onchange event handler before window's got focus.

    To make a long story short, here's my implementation:

    TypeScript solution:

    public static selectFile(accept: string = null): Promise<File> {
        return new Promise<File>(async resolve => {
            const fileInputElement = document.createElement('input') as HTMLInputElement;
            fileInputElement.type = 'file';
            fileInputElement.style.opacity = '0';
            if (accept) fileInputElement.accept = accept;
            fileInputElement.addEventListener('change', () => {
                const file = fileInputElement.files[0];
                console.log('File "' + file.name + '" selected.');
                document.body.removeChild(fileInputElement);
                resolve(file);
            });
            document.body.appendChild(fileInputElement);
            setTimeout(_ => {
                fileInputElement.click();
                const onFocus = () => {
                    window.removeEventListener('focus', onFocus);
                    document.body.addEventListener('mousemove', onMouseMove);
                };
                const onMouseMove = () => {
                    document.body.removeEventListener('mousemove', onMouseMove);
                    if (!fileInputElement.files.length) {
                        document.body.removeChild(fileInputElement);
                        console.log('No file selected.');
                        resolve(null);
                    }
                }
                window.addEventListener('focus', onFocus);
            }, 0);
        });
    }
    

    JavaScript solution:

    function selectFile(accept = null) {
        return new Promise(async resolve => {
            const fileInputElement = document.createElement('input');
            fileInputElement.type = 'file';
            fileInputElement.style.opacity = '0';
            if (accept) fileInputElement.accept = accept;
            fileInputElement.addEventListener('change', () => {
                const file = fileInputElement.files[0];
                console.log('File "' + file.name + '" selected.');
                document.body.removeChild(fileInputElement);
                resolve(file);
            });
            document.body.appendChild(fileInputElement);
            setTimeout(_ => {
                fileInputElement.click();
                const onFocus = () => {
                    window.removeEventListener('focus', onFocus);
                    document.body.addEventListener('mousemove', onMouseMove);
                };
                const onMouseMove = () => {
                    document.body.removeEventListener('mousemove', onMouseMove);
                    if (!fileInputElement.files.length) {
                        document.body.removeChild(fileInputElement);
                        console.log('No file selected.');
                        resolve(null);
                    }
                }
                window.addEventListener('focus', onFocus);
            }, 0);
        });
    }