SamSharma
SamSharma

Reputation: 161

Find if 'cancel' was clicked on file input

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?

http://jsfiddle.net/smV9c/2/

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

Answers (5)

Ulad Kasach
Ulad Kasach

Reputation: 12888

It gets tricky to handle all of the various ways that a user can cancel file input.

  • On most browsers, the file picker immediately opens and takes the user out of the browser. We can use the window.focus event to detect when they come back without selecting anything to detect cancellation
  • On ios browsers, the user first sees an ios modal that lets them pick between camera -vs- gallery. User's can cancel from here by clicking away from the modal. So, we can use the window.touchend to detect this
  • there are likely other browsers and cases that act differently on cancellation, that this hasn't caught yet, too

Implementation 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

ANiket Chavan
ANiket Chavan

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

Simon Sawyer
Simon Sawyer

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

Michael
Michael

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

Abhi Beckert
Abhi Beckert

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

Related Questions