16

I have a button on my web application, which has the following code in the click event handler:

const fileInputEl = document.createElement('input');
fileInputEl.type = 'file';
fileInputEl.accept = 'image/*';

fileInputEl.addEventListener('input', (e) => {
  if (!e.target.files.length) {
    return;
  }

  // Handle files here...
});  

fileInputEl.dispatchEvent(new MouseEvent('click'));

Sometimes (about 1 out of 8), after selecting the file, the input event doesn't fire after choosing a file. I'm guessing this is a browser bug around the lifecycle of the element.

Any way around this short of appending the element to the page and removing it later? What's the proper way to handle this in modern browsers these days?

I'm testing with Google Chrome on Windows.

JSFiddle: http://jsfiddle.net/pja1d5om/2/

Bharata
  • 13,509
  • 6
  • 36
  • 50
Brad
  • 159,648
  • 54
  • 349
  • 530
  • Why use the `input` event for a file input? Wouldn't `change` be more appropriate? – Barmar Aug 30 '18 at 19:04
  • @Barmar It makes no difference either way for a file input element. This problem also occurs with `change`. I switched to `input`, experimenting. – Brad Aug 30 '18 at 19:05
  • if you select the same file, it will not fire a change event.... – epascarello Sep 18 '18 at 16:45
  • @epascarello That's not the issue here. In fact, since I'm creating the element dynamically, it isn't even possible for me to select the same file as the element is new and didn't have anything in it previously. – Brad Sep 18 '18 at 16:59
  • Your code does not show you using it once so one can assume it was being reused. Maybe show more code with it be removed and inserted would be better. – epascarello Sep 18 '18 at 17:04
  • @epascarello The first sentence says, "I have a button on my web application, which has the following code in the click event handler". I added a JSFiddle link for clarity. – Brad Sep 18 '18 at 17:06
  • 1
    seems to be working fine on firefox – tosi Sep 18 '18 at 18:50
  • 1
    Google chrome 69.0.3497.92 on Windows 10, the event fires every time I select a file. – Munim Munna Sep 18 '18 at 19:36
  • 1
    @MunimMunna Weird... thanks for testing it! I've had this problem with Chrome on Android as well, come to think of it. I wonder if this was a bug that was recently fixed. I've had this issue for at least 3 or 4 years now, but I cannot reproduce it after rebooting this afternoon and getting a Chrome update. (Whatever version changed the UI of the tabs. I'm on v69.0.3497.100 now.) It would be weird coincidence if the day I add a bounty to this, the problem is resolved. Perhaps there's a side effect of visiting the page multiple times, or with JSFiddle. – Brad Sep 18 '18 at 20:20
  • @Brad, I think you have to try it longer than 8 or even 25 times. It happens about 1 out of 25. And do not forget to reload the page from time to time. – Bharata Sep 18 '18 at 21:04
  • 1 cent plug for using `change` instead of `input` event in your workaround if you need cross-browser support is that Edge seems to be having trouble with the `input` event for file inputs (using `change` seems to resolve it). [See recent SO question on the Edge issue here](https://stackoverflow.com/questions/52404212/input-event-not-recognised-on-input-type-file-in-edge). Unfortunately, I haven't been able to reproduce the bug you are seeing in Chrome (may be just a lack of patience on my part). – benvc Sep 21 '18 at 18:03
  • 1
    Interesting, seems OK on chrome for mac, just selected a file 18 times in a row without seeing a bug. – Rose Robertson Sep 22 '18 at 22:17
  • Hm I wonder if this is actually the same as https://stackoverflow.com/questions/39399947/event-onchange-wont-trigger-after-files-are-selected-from-code-generated-input – Rose Robertson Sep 22 '18 at 22:18
  • Different event but same principle and the accepted answer calling it a race condition sounds like it could be what you are encountering too. – Rose Robertson Sep 22 '18 at 22:20
  • @Bharata I just awarded a 250-point bounty on your answer, why did you delete it? That's ridiculous. – Brad Sep 25 '18 at 19:42

2 Answers2

4

Citate from your question: Sometimes (about 1 out of 8), after selecting the file, the input event doesn't fire after choosing a file.

I can confirm this behavior with input and with change events using Opera (ver. 55.0.2994.61, newest version at this time) which uses Google Chrome browser engine "Blink". It happens about 1 out of 25.

Solution

This happens because sometimes your input element object was deleted after file dialog closing because it is not in using anymore. And when it happens you have not the target which could receive input or change event.

To solve this just add your input element somewhere to the DOM after creating as hidden object like follows:

fileInputEl.style.display = 'none';
document.body.appendChild(fileInputEl);

And then when the event was fired you can delete it like follows:

document.body.removeChild(fileInputEl);

Full example

function selectFile()
{
    var fileInputEl = document.createElement('input');
    fileInputEl.type = 'file';
    fileInputEl.accept = 'image/*';
    //on this way you can see how many files you select (is for test only):
    fileInputEl.multiple = 'multiple';

    fileInputEl.style.display = 'none';
    document.body.appendChild(fileInputEl);

    fileInputEl.addEventListener('input', function(e)
    {
        // Handle files here...
        console.log('You have selected ' + fileInputEl.files.length + ' file(s).');
        document.body.removeChild(fileInputEl);
    });  

    try
    {
        fileInputEl.dispatchEvent(new MouseEvent('click'));
    }
    catch(e)
    {
        console.log('Mouse Event error:\n' + e.message);
        // TODO:
        //Creating and firing synthetic events in IE/MS Edge:
        //https://learn.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/compatibility/dn905219(v=vs.85)
    }
}
<input type="button" onclick="selectFile()" value="Select file">

Citate from your bounty description: Bounty will be awarded to someone who ... show an appropriate workaround.

My old suggested workaround (now irrelevant)

We can use setInterval function to check if input value was changed. We save intervalID in our new fileInputEl as property. Because we always create a new file input element then its value is always empty on start (on each button click). And if this value was changed we can detect it when we compare it with empty string. And when it happens then we pass our fileInputEl to fileInputChanged() function and clear/stop our interval function.

function selectFile()
{
    var fileInputEl = document.createElement('input');
    fileInputEl.type = 'file';
    fileInputEl.accept = 'image/*';
    //on this way you can see how many files you select (is for test only):
    fileInputEl.multiple = 'multiple';

    fileInputEl.intervalID = setInterval(function()
    {
        // because we always create a new file input element then
        // its value is always empty, but if not then it was changed:
        if(fileInputEl.value != '')
            fileInputChanged(fileInputEl);
    }, 100);

    try
    {
        fileInputEl.dispatchEvent(new MouseEvent('click'));
    }
    catch(e)
    {
        console.log('Mouse Event error:\n' + e.message);
        // TODO:
        //Creating and firing synthetic events in IE/MS Edge:
        //https://learn.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/compatibility/dn905219(v=vs.85)
    }
}

function fileInputChanged(obj)
{
    // Handle files here...
    console.log('You have selected ' + obj.files.length + ' file(s).');
    clearInterval(obj.intervalID);
}
<input type="button" onclick="selectFile()" value="Select file">
Bharata
  • 13,509
  • 6
  • 36
  • 50
  • 1
    I'm happy to hear you were able to reproduce the problem! However, I suspect the setInterval solution is not the most optimal. If that's working, then we have a couple possible explanations for the behavior. One of which is that the reference to the input element is not collected (as it's referenced in an ongoing timer), which allows events to be fired on it later. (My suspicion is that the input element is being collected early, despite having event handlers attached.) Another possible explanation is that the event listener isn't binding until later, which seems like a bug to me. – Brad Sep 18 '18 at 21:12
  • 1
    (continued) I'm hoping that someone familiar with the browser internals could explain which of those is correct, or if there's some other explanation. – Brad Sep 18 '18 at 21:12
  • I didn't... I just awarded the bounty? – Brad Sep 25 '18 at 19:18
  • @Brad, I have deleted it to check who has deleted his upvoting and then has downvoted my answer. I have recognized it now from his reputation after deleting from my answer his reputation has got +1. I know now who was it, and when he will remove his downvoting and will upvote my answer again, then I will forgive him. I do not understand why somebody on SO downvotes the good answers. I have never downvoted even one the most worst answer on SO – only bad questions. I wrote my answer very good (solution + workaround) and this is not fair to downvote it. – Bharata Sep 25 '18 at 20:11
  • @Brad, I am still waiting for your answer. The recognizing of the mistakes is the strength and not the weakness. After your correcting of your mistakes we can forget it. ;-) – Bharata Sep 26 '18 at 09:47
1

It seems this is a browser bug/fluke and likely has something to do with garbage collection. I can get around it by adding the file input to the document:

fileInputEl.style.display = 'none';
document.querySelector('body').appendChild(fileInputEl);

When done, it can be cleaned up with:

fileInputEl.remove();
Brad
  • 159,648
  • 54
  • 349
  • 530