9

I'm playing with JavaScript and wrote simple function that creates INPUT element (type="file") and simulates click.

var createAndCallFileSelect = function () {
    var input = document.createElement ("input");
    input.setAttribute ("type", "file");
    input.addEventListener ("change", function () {
        console.log (this.files);
    }, false);
    input.click();
}

It works great most of time but sometimes it doesn't fire onChange event when file is selected (or more files when used with multiple attribute on INPUT).

I know that onChange won't fire when you re-select same file but clearly this is not the case here. It doesn't fire an event only first time I use this function, and sometimes only. Every next click normally fires onChange if something is selected from dialog.

Already tried searching for this problem here and around but seems that all onChange problems and solutions are related to famous problem with re-selecting same file again.

I found this happens on latest Opera and Firefox, never tested with other browsers. Also. I tried to wait entire page to be loaded but result is still the same - sometimes it doesn't trigger onChange on first call.

Can anyone explain to me why this happens? I already have workaround code, that's not the question, just need explanation of why this happens when INPUT is created and called this way.

Update:

Cascade delay

var function createAndCallFileSelect = function () {
    var input = document.createElement ("input");
    setTimeout (function () { // set type with 1s delay
        input.setAttribute ("type", "file");
        setTimeout (function () {  // attach event with 1s delay
            input.addEventListener ("change", function () {
                console.log (this.files);
            }, false);
            setTimeout (function () { // simulate click with 1s delay
                input.click();
            }, 1000);
        }, 1000);
    }, 1000);
}

This also doesn't work. I tried to delay execution of each line to be sure that everything is executed in right order. 3 seconds after call it opens file-select dialog but again, sometimes it doesn't fire onChange event after file is selected.

Wh1T3h4Ck5
  • 8,399
  • 9
  • 59
  • 79
  • This is absolutely not working for me. Always! Debian 8, Mate, FF ESR 45.2.0. But if presume... http://stackoverflow.com/questions/793014/jquery-trigger-file-input – Deep Sep 12 '16 at 04:00
  • Can't replicate at all. What do you mean with sometimes, can you be more specific about the frequency? What are your system specs? – cviejo Sep 12 '16 at 11:10
  • @cviejo it mostly works well. Sometimes I have no problems for hours and thousands of reloads, sometimes it doesn't work 7 out of 10 reloads (this is rare but happens). There's no pattern. System specs are irrelevant. – Wh1T3h4Ck5 Sep 12 '16 at 19:04
  • @Deep That doesn't provide detailed explanation for my problem. Read question please, I didn't ask for solution or suggestion + I never mentioned jQuery (my code is in pure JavaScript). – Wh1T3h4Ck5 Sep 12 '16 at 19:10
  • 1
    I was able to reproduce the issue and it is definitely *sometimes*. There's no clear pattern. – Fabio Poloni Sep 14 '16 at 15:55
  • @Wh1T3h4Ck5 Where is element appended to `document` at `javascript` at Question? – guest271314 Sep 18 '16 at 15:15
  • @guest271314 It's not appended to document at all. – Wh1T3h4Ck5 Sep 18 '16 at 16:21
  • @Wh1T3h4Ck5 First approach at Question appears to return expected result at firefox. Though not at chrome, chromium. Are you trying to achieve same result at chrome, chromium? – guest271314 Sep 18 '16 at 16:25
  • @guest271314 no, i use opera and firefox only – Wh1T3h4Ck5 Sep 18 '16 at 16:27
  • @Wh1T3h4Ck5 Which version of firefox have you tried `javascript` at Question? Tried at version 47, where `files` object was logged at `console` at each call to `createAndCallFileSelect` – guest271314 Sep 18 '16 at 16:28
  • FF 48.0... same here, in console everything looks fine. – Wh1T3h4Ck5 Sep 18 '16 at 16:30
  • @Wh1T3h4Ck5 _"FF 48.0... same here, in console everything looks fine."_ What is issue? – guest271314 Sep 18 '16 at 16:31
  • It still doesn't trigger onChange sometimes, but when I write input variable to console log it shows up there normally. In other words, it looks fine in console only. Tried also with Opera 36.0 and using Windows XP and Windows 10 but (same browser versions) but I still have same problems. – Wh1T3h4Ck5 Sep 18 '16 at 16:33
  • @Wh1T3h4Ck5 The function is firing `change` event at each call here. _"it looks fine in console only"_ Are there other portions of `javascript` not included at Question? _"It still doesn't trigger onChange sometimes"_ Can you create or fork plnkr http://plnkr.co/edit/GcKUHwgiUNyj9ASjZD7w?p=preview to demonstrate? – guest271314 Sep 18 '16 at 16:37
  • @guest271314 Those 8 lines of code (first function in the question) is actually copy-paste of same function I use for testing. No other lines or modifications. – Wh1T3h4Ck5 Sep 18 '16 at 16:46
  • @Wh1T3h4Ck5 If there are not other portions of `javascript`, cannot reproduce issue described of `change` event not firing when `createAndCallFileSelect` is called at firefox 47. – guest271314 Sep 18 '16 at 16:48
  • @Wh1T3h4Ck5 Concerning second approach at Question, see [Trigger click on input=file on asynchronous ajax done()](http://stackoverflow.com/questions/29728705/trigger-click-on-input-file-on-asynchronous-ajax-done/29873845#29873845) – guest271314 Sep 18 '16 at 16:55

5 Answers5

5

It's a race condition. It's dependant on what is in the stack and how long certain things might take before the synchronous file browser is called to block the rest of the stack from finishing. With addeventlistener, it's queuing a callback for later use which will be picked up by the event loop when the stack clears. If the stack isn't cleared in time, it won't be called in time. There's no guarantee what will be run when. If you use setTimeout(fn, 0) as Pawel suggested, you'll queue the click() function to be called after the event listener has been placed.

Here's a great video that visualizes everything I'm talking about: https://www.youtube.com/watch?v=8aGhZQkoFbQ

Update: I've noticed something very interesting with chrome after looking into this a bit further...it only allows up to 5 of those elements to be created at once. I did this:

for(var i = 0; i < 20; i += 1) {
    createAndCallFileSelect()
}

with several different numbers in there...and each time, any number greater than 5 only produced 5 input elements with 5 callbacks, while 5 and under produced the correct amount.

I also tried this recursively instead of using the for loop...same results.

Also, the larger the file I select, the longer it takes, but eventually it'll call the callback after it processes the file. This testing so far has all been in chrome.

Mike S.
  • 969
  • 5
  • 13
  • Sounds logical and it could be an answer but there's a thing. Delaying `click()` with `setTimeout` doesn't help. In fact, every time `onChange` trigger fails to fire it was previously attached to the element. I also tried to make cascade execution and call each line with 1 second delay but still doesn't help. When I append element to document and delay `click` same happens again. Guess you're partially right, there's something with execution stack but it's still confusing. To me it really looks like a bug, but what's a chance that same bug affects more browsers!? – Wh1T3h4Ck5 Sep 17 '16 at 20:30
  • Hmm, your update is surely interesting. I actually don't use Chrome at all but I'll try that with other browsers. At least, it may contain some hidden clue to solve my mystery. – Wh1T3h4Ck5 Sep 18 '16 at 16:26
1

You can do something like this to trigger click on change of dynamically created file input

var input = document.createElement ("input");
input.setAttribute ("type", "file");

input.addEventListener('change', function(){
    input.addEventListener('click', function(){
      alert("Clicked");
      input.removeEventListener("click", function(){})
    }, false);
    input.click();
}, false); 

JS fiddle

I have tested this in chrome, firefox, opera nd IE. It works

Siddhartha Chowdhury
  • 2,724
  • 1
  • 28
  • 46
  • If you read the question, you'll see: _Can anyone explain to me why this happens? I already have workaround code, that's not the question, just need explanation of why this happens when INPUT is created and called this way._ – Wh1T3h4Ck5 Sep 19 '16 at 06:20
  • Hey sorry I didnt understand your question before, your question is quite interesting, I don't have a "for sure" answer to it at this moment but I am trying to find out myself. Clearly its not a race condition, there is no race-condition in JS , JS does atomic operations and is single threaded. The only hint i got is that its a *context-stack* execution issue. Once i have a complete answer to your question i will modify my answer. Thanks for the question. – Siddhartha Chowdhury Sep 19 '16 at 07:04
0

Event listener input.addEventListener ("change" ...

is not getting registered immediately. It's like wrapping code in setTimeout(fn, 0) which makes it added to the end of the execution queue.

But input.click(); opens a file selection popup instantly which pauses JavaScript(so the event won't register until the popup is closed). If you wrap input.click in setTimeout(function() { input.click(); }, 0) then it will definitely be executed after the event is registered and this theory may be correct.

I can't reproduce your problem so this is just pure theory.

Pawel
  • 16,093
  • 5
  • 70
  • 73
  • 1
    `Event listener is not getting registered immediately.` **Incorrect!** It certainly is getting registering immediately most of time (excepting sometimes and on first call only). Input element is re-created in local scope on every function call, so why it fails on first call only and sometimes only? That's the answer I'm looking for. – Wh1T3h4Ck5 Sep 15 '16 at 18:40
0

This happens because your input element no longer exist when you close "Open file" dialog window, so there is no target on whom to raise the onchange event. Maybe because JavaScript's garbage collector already collected this element, or by some other reason.

To fix this just save your input element somewhere in the DOM:

input.style.visibility='hidden';
document.body.appendChild(input);

Also don't forget to save link to this element and delete it from DOM when file upload completes (I use here this.set() and this.get() functions from Ext.js):

// after element initialization:
this.set("inputFileElement", input);
...
// in the "OnFileComplete" event handler or in some similar place:
var inputFileElement = this.get("input");
if(inputFileElement !== null && inputFileElement !== undefined)
{
    inputFileElement.parentNode.removeChild(inputFileElement);
}
Andrii Viazovskyi
  • 777
  • 2
  • 9
  • 15
0

I know this question was not about a workaround, but I came here looking for a solution. This worked for me on Chrome. I have simply moved let input outside the function. This works because (as suggested by bside) it prevents input from being garbage collected. I only expect to have one open-file dialog open at a time so the singleton pattern is OK here.

let input;
var createAndCallFileSelect = function () {
    input = document.createElement ("input");
    input.setAttribute ("type", "file");
    input.addEventListener ("change", function () {
        console.log (this.files);
    }, false);
    input.click();
}
James
  • 5,635
  • 2
  • 33
  • 44