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 onchange
event.
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.
CodePudding user response:
I've invested countless hours looking for a solution. And now I want to share my solution with you.
I use three event handlers:
onchange
event on the file input: To detect when a file has been selected.onfocus
event onwindow
: To detect when the Select File Dialog has been closed.onmousemove
event ondocument.body
: To detect when the interaction is not blocked anymore. Only when this event is called, you can be sure that theonchange
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', onm ouseMove);
};
const onm ouseMove = () => {
document.body.removeEventListener('mousemove', onm ouseMove);
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', onm ouseMove);
};
const onm ouseMove = () => {
document.body.removeEventListener('mousemove', onm ouseMove);
if (!fileInputElement.files.length) {
document.body.removeChild(fileInputElement);
console.log('No file selected.');
resolve(null);
}
}
window.addEventListener('focus', onFocus);
}, 0);
});
}