Reputation: 161
I tried using a hack described in various locations which uses:
document.body.onfocus = checkOnCancel();
An example:
var fileSelectEle = document.getElementById('fileinput');
fileSelectEle.onclick = charge;
function charge()
{
document.body.onfocus = checkOnCancel;
}
function checkOnCancel()
{
alert("FileName:" + fileSelectEle.value + "; Length: " + fileSelectEle.value.length);
if(fileSelectEle.value.length == 0) alert('You clicked cancel!')
else alert('You selected a file!');
document.body.onfocus = null;
}
Is there something wrong here? Because fileSelectedEle.value
always returns the previous execution value and NOT the one selected by the user.
Is this the expected behavior of input file? How to resolve this to read the actual file selected?
You can reproduce the error by:
Step 1: SelectFile - some select some file (and notice the output)
Step 2: SelectFile - press cancel (and notice the output)
Upvotes: 16
Views: 26828
Reputation: 12888
It gets tricky to handle all of the various ways that a user can cancel file input.
window.focus
event to detect when they come back without selecting anything to detect cancellationwindow.touchend
to detect thisImplementation wise, you can use addEventListener
to make sure that you dont replace other event listeners that may already be on the window - and to easily clean up the event listener after it fires. For example:
window.addEventListener('focus', () => console.log('no file selected'), { once: true });
Here is an example of how you can use this to get images programatically, handling the considerations listed above (typescript):
/**
* opens the user OS's native file picker, returning the selected images. gracefully handles cancellation
*/
export const getImageFilesFromUser = async ({ multiple = true }: { multiple?: boolean } = {}) =>
new Promise<File[]>((resolve) => {
// define the input element that we'll use to trigger the input ui
const fileInput = document.createElement('input');
fileInput.setAttribute('style', 'visibility: hidden'); // make the input invisible
let inputIsAttached = false;
const addInputToDom = () => {
document.body.appendChild(fileInput); // required for IOS to actually fire the onchange event; https://stackoverflow.com/questions/47664777/javascript-file-input-onchange-not-working-ios-safari-only
inputIsAttached = true;
};
const removeInputFromDom = () => {
if (inputIsAttached) document.body.removeChild(fileInput);
inputIsAttached = false;
};
// define what type of files we want the user to pick
fileInput.type = 'file';
fileInput.multiple = multiple;
fileInput.accept = 'image/*';
// add our event listeners to handle selection and canceling
const onCancelListener = async () => {
await sleep(50); // wait a beat, so that if onchange is firing simultaneously, it takes precedent
resolve([]);
removeInputFromDom();
};
fileInput.onchange = (event: any) => {
window.removeEventListener('focus', onCancelListener); // remove the event listener since we dont need it anymore, to cleanup resources
window.removeEventListener('touchend', onCancelListener); // remove the event listener since we dont need it anymore, to cleanup resources
resolve([...(event.target!.files as FileList)]); // and resolve the files that the user picked
removeInputFromDom();
};
window.addEventListener('focus', onCancelListener, { once: true }); // detect when the window is refocused without file being selected first, which is a sign that user canceled (e.g., user left window into the file system's file picker)
window.addEventListener('touchend', onCancelListener, { once: true }); // detect when the window is touched without a file being selected, which is a sign that user canceled (e.g., user did not leave window - but instead canceled the modal that lets you choose where to get photo from on ios)
// and trigger the file selection ui
addInputToDom();
fileInput.click();
});
Upvotes: 1
Reputation: 95
//This code works in chrome for file selection try it
<--write this line in HTML code-->
<input type='file' id='theFile' onclick="initialize()" />
var theFile = document.getElementById('theFile');
function initialize() {
document.body.onfocus = checkIt;
console.log('initializing');
}
function checkIt() {
setTimeout(function() {
theFile = document.getElementById('theFile');
if (theFile.value.length) {
alert('Files Loaded');
} else {
alert('Cancel clicked');
}
document.body.onfocus = null;
console.log('checked');
}, 500);
}
Upvotes: 1
Reputation: 41
You can hook into the window.focus
event which gets fired when they cancel window's file select box. Then check to see if it actually has a file selected.
Upvotes: 3
Reputation: 1477
One solution is to use the onchange
event of the input
.
var fileSelectEle = document.getElementById('fileinput');
fileSelectEle.onchange = function ()
{
if(fileSelectEle.value.length == 0) {
alert('You clicked cancel - ' + "FileName:" + fileSelectEle.value + "; Length: " + fileSelectEle.value.length);
} else {
alert('You selected a file - ' + "FileName:" + fileSelectEle.value + "; Length: " + fileSelectEle.value.length);
}
}
This responds correctly to changes in the selected filename, as you can test here: http://jsfiddle.net/munderwood/6h2r7/1/
The only potential difference in behaviour from the way you were trying to do it, is that if you cancel right away, or twice in a row, or select the same file twice in a row, then the event won't fire. However, every time the filename actually changes, you'll detect it correctly.
I don't know for sure why your original attempt didn't work, although my best guess is that it's a timing issue with the onfocus
event firing asynchronously, and before the input
control's properties have finished updating.
UPDATE: To determine what the user has selected every time they close the file dialog, even if nothing has changed, the timing issue can be skirted by adding a brief delay between receiving focus again, and checking the value of the file input. Instead of calling checkOnCancel
immediately upon receiving focus, the following version of charge
causes it to be called a tenth of a second later.
function charge() {
document.body.onfocus = function () { setTimeout(checkOnCancel, 100); };
}
Here's a working version: http://jsfiddle.net/munderwood/6h2r7/2/.
Upvotes: 17
Reputation: 33379
Is there something wrong here? Because fileSelectedEle.value always returns the previous execution value and NOT the one selected by the user. Is this the expected behavior of input file? How to resolve this to read the actual file selected?
There's nothing wrong, this is expected behaviour. If the user cancels the file selection process, then it's as if they never started it. So the previous value is left in place.
Upvotes: 0