我正在玩JavaScript,并编写了一个简单的函数,创建INPUT
元素(type="file"
)并模拟点击。
var createAndCallFileSelect = function () {
var input = document.createElement ("input");
input.setAttribute ("type", "file");
input.addEventListener ("change", function () {
console.log (this.files);
}, false);
input.click();
}
它工作得很好,但有时它不触发onChange
事件时,文件被选中(或更多的文件时,使用INPUT
的multiple
属性)。
我知道onChange
不会触发当你重新选择相同的文件,但显然这不是这里的情况。它不会在我第一次使用这个函数时触发事件,有时只是。如果从对话框中选择了某些内容,则每次单击下一步通常会触发onChange
。
已经尝试在这里和周围搜索这个问题,但似乎所有onChange问题和解决方案都与再次重新选择相同文件的著名问题有关。
我发现这种情况发生在最新的Opera和Firefox上,从未在其他浏览器上测试过。也。我试图等待整个页面加载,但结果仍然是相同的-有时它不会触发onChange
在第一次调用。
更新:
级联延迟
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);
}
这也不起作用。我试图延迟每一行的执行,以确保每一行都按正确的顺序执行。调用3秒后打开文件-选择对话框,但再次,有时它不触发onChange
事件后文件被选中
这是一个竞争条件。这取决于堆栈中的内容,以及在调用同步文件浏览器阻止堆栈的其余部分完成之前,某些内容可能需要多长时间。使用addeventlistener,它正在排队等待回调以供以后使用,当堆栈清除时,事件循环将拾取回调。如果堆栈没有及时清除,它就不会被及时调用。不能保证什么时候开什么课。如果您像Pawel建议的那样使用setTimeout(fn, 0),您将在事件侦听器放置后将click()函数排队调用。
这里有一个很棒的视频,可以将我所说的一切可视化:https://www.youtube.com/watch?v=8aGhZQkoFbQ
更新:我已经注意到一些非常有趣的chrome后,进一步研究这个…它只允许一次最多创建5个这样的元素。我这样做了:
for(var i = 0; i < 20; i += 1) {
createAndCallFileSelect()
}
里面有几个不同的数字…每次,任何大于5的数字只产生5个输入元素和5个回调,而小于等于5的则产生正确的数量。
我也尝试了这个递归而不是使用for循环…同样的结果。
同样,我选择的文件越大,花费的时间就越长,但最终它会在处理完文件后调用回调。到目前为止,这些测试都是在chrome中进行的。
你可以这样做来触发点击改变动态创建的文件输入
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小提琴
我已经在chrome, firefox, opera和IE中测试过了。它是
事件监听器input.addEventListener ("change" ...
没有立即注册。这就像在setTimeout(fn, 0)
中包装代码,使其添加到执行队列的末尾。
但input.click ();打开一个文件选择弹出窗口,立即暂停JavaScript(所以事件不会注册,直到弹出窗口关闭)。如果你包装输入。点击setTimeout(function() { input.click(); }, 0)
,那么它肯定会在事件注册后执行,这个理论可能是正确的。
我无法重现你的问题,所以这只是纯理论。
发生这种情况是因为当您关闭"打开文件"对话框窗口时,您的input
元素不再存在,因此没有可以引发onchange
事件的目标。可能是因为JavaScript的垃圾收集器已经收集了这个元素,或者是其他原因。
要解决这个问题,只需保存您的input
元素在DOM的某个地方:
input.style.visibility='hidden';
document.body.appendChild(input);
也不要忘记保存这个元素的链接,并在文件上传完成时将其从DOM中删除(我在这里使用Ext.js中的this.set()
和this.get()
函数):
// 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);
}
我知道这个问题不是关于一个变通办法,但我来这里寻找一个解决方案。这对我在Chrome上工作。我只是将let input
移到了函数之外。这是因为(正如side所建议的)它可以防止input
被垃圾收集。我希望每次只打开一个打开文件对话框,所以这里的单例模式是可以的。
let input;
var createAndCallFileSelect = function () {
input = document.createElement ("input");
input.setAttribute ("type", "file");
input.addEventListener ("change", function () {
console.log (this.files);
}, false);
input.click();
}