Reputation: 8509
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.
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.
Upvotes: 9
Views: 3363
Reputation: 6123
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();
}
Upvotes: 0
Reputation: 857
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);
}
Upvotes: 0
Reputation: 2734
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);
I have tested this in chrome, firefox, opera nd IE. It works
Upvotes: 1
Reputation: 951
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.
Upvotes: 5
Reputation: 18262
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.
Upvotes: 0