从代码生成的 INPUT 元素中选择文件后,不会触发事件 onChange



我正在玩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事件时,文件被选中(或更多的文件时,使用INPUTmultiple属性)。

我知道onChange不会触发当你重新选择相同的文件,但显然这不是这里的情况。它不会在我第一次使用这个函数时触发事件,有时只是。如果从对话框中选择了某些内容,则每次单击下一步通常会触发onChange

已经尝试在这里和周围搜索这个问题,但似乎所有onChange问题和解决方案都与再次重新选择相同文件的著名问题有关。

我发现这种情况发生在最新的Opera和Firefox上,从未在其他浏览器上测试过。也。我试图等待整个页面加载,但结果仍然是相同的-有时它不会触发onChange在第一次调用。

谁能给我解释一下为什么会这样?我已经有了变通代码,这不是问题,只是需要解释为什么当INPUT被创建并以这种方式调用时会发生这种情况。

更新:

级联延迟

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();
}

相关内容

  • 没有找到相关文章

最新更新