在异步ajax完成时触发input=file的点击事件

34

我有一个带有一些数据和上传功能的表单。只有在成功接收和处理数据后,才能启动上传。为此,我进行了ajax调用,其中我:

  1. 发送数据,
  2. 检查其结果,
  3. 触发click()方法以打开文件对话框。

使用click()方法似乎无法打开上传窗口,因为异步调用会阻止它。只有当我设置async: false时,它才可以工作。

我在文档和这个网站上找不到任何信息,想知道问题出在哪里,以及如何使其异步调用仍然起作用?

示例:

$.ajax({
    type: "POST",
    url: "/Save",
    data: jsonText,
    dataType: "json",
    //async: false            [1]
}).done(function (msg) {    
    $("#upload").click();   
});

//$("#upload").click();       [2]

演示:http://jsfiddle.net/c2v00uxn/

注意:

  • 如果取消注释[1]或[2],它会起作用(文件对话框会按预期出现)。
  • 用trigger('click')替换click()不起作用。
  • 用live()/on()替换click()也没有帮助。
  • 文件上传控件与示例一样可见(所以不是因为控件被隐藏了)
  • ajax的超时设置也无济于事。

更新

问题不在于如何进行"click"操作,而是关于如何在异步ajax调用之后进行点击操作(目前只能在非异步调用中工作)。


1
@Tushar 这不是问题!请不要粘贴链接。 - Royi Namir
5
是的,这是浏览器的一项安全功能。文件输入类型的点击只能从另一个点击中触发,并且它需要在同一作用域/同时触发原始的点击。我最近也遇到了这个确切的问题,找不到解决方法。 - powerbuoy
1
同时,https://dev59.com/kXI-5IYBdhLWcg3wfoW1 - Bryan Downing
2
补充一下@powerbuoy所说的,它能够同步工作的原因是因为对$("#upload").click();的调用仍然在由用户生成事件(触发ajax的按钮点击)创建的调用堆栈中。Ajax实际上与您的问题无关——完成处理程序已成功触发。这本质上就像在控制台中运行$("#upload").click(); - Bryan Downing
显示剩余8条评论
3个回答

35

由于W3C推荐的浏览器安全特性,目前不可能从异步ajax回调中打开文件弹出窗口。

在文件输入元素的激活行为中,首先检查算法是否允许显示弹出窗口,如果不允许,则在不执行任何其他操作的情况下中止下一步。来自w3c.org

如果以下条件之一成立,则算法可以显示弹出窗口:

  1. 正在处理算法的任务当前正在处理其单击事件受信任的激活行为。(受信任事件:由用户代理生成的事件,无论是作为用户交互的结果,还是直接由DOM更改的结果,都得到了用户代理的信任,这些事件具有脚本通过DocumentEvent.createEvent("Event")方法生成的事件所没有的特权,使用Event.initEvent()方法进行修改或通过EventTarget.dispatchEvent()方法分派。受信任事件的isTrusted属性具有true值,而不受信任事件的isTrusted属性值为false。否则。 http://www.w3.org/TR/2012/WD-DOM-Level-3-Events-20120614/#trusted-events。)
  2. 正在处理算法的任务当前正在运行以下列表中类型为trusted event的事件侦听器:

    • change
    • click
    • dblclick
    • mouseup
    • reset
    • submit
  3. 正在处理算法的任务是由允许显示弹出窗口的算法排队,而且这样的算法链条在用户代理定义的时间范围内启动。

w3c.org

您的代码中,点击事件不是由用户触发的,而是由ajax完成回调函数触发的。在这里,浏览器声明了不能信任该事件来打开弹窗。在某些浏览器中,如果事件被声明为可信,则可以看到isTrusted属性设置为true。https://developer.mozilla.org/en-US/docs/Web/API/Event/isTrusted

注意

不同的浏览器使用不同的方法区分脚本激活的单击和真实用户。

在这种情况下,您可以禁用文件输入按钮(或整个表单),并在ajax完成后启用。这样,直到完成ajax请求之前,用户将无法单击上传按钮。目前没有其他方法可以在一个单击中同时完成这两个操作,因为打开弹窗也有时间限制。当我在Chrome中检查时,时间限制为1000毫秒。即用户动作后1000毫秒,窗口将不会打开。


回答是否意图表明或通过参考文档提供证明,说明要求“关于如何在异步Ajax调用之后进行单击”是不可能的? - guest271314
1
@guest271314 这很有帮助,因为由于上述所有条件,您无法在异步ajax的回调中打开文件选择弹出窗口。这是浏览器的行为。浏览器不希望我们从不受信任的事件中打开文件弹出窗口。所以我的答案是放弃找到实现用户想要做的事情的方法。即使您找到了某些东西,也不能依赖它,因为那可能是由于安全漏洞而起作用。 - tkay
1
好的。那么,在表单输入的相应更改事件/在照片按钮上方的表单输入更改事件上验证表单输入,而不是在单击照片按钮事件上进行验证怎么样?这样,您可以确保在用户单击照片按钮之前,用户是否允许附加图像。您可以在请求完成之前禁用照片按钮。 - tkay
1
这是一个票务应用程序,用户需要手动输入票号(例如“1001”)。该号码必须在系统内唯一。只有当用户单击保存或上传时,才会保存票据。它也可以被取消(退出而不保存)。我可以在onchange()上检查唯一号码,但另一个用户可能会在此之后输入相同的号码,因为在单击保存或上传之前,我不会将#保存在数据库中。因此,您的想法只有在我暂时“阻止”#并且不让其他用户输入它时才能为我工作。 - user2316116
1
@smirnov 感谢您清楚地解释了这个难点。我认为目前没有办法在单击事件中完成此操作。从文档中可以清楚地看出,在这种特定情况下需要至少两次用户点击。 - tkay
显示剩余7条评论

0

我有一个现在可用的解决方案。这是一种hack方法,所以它可能在不久的将来无法使用,因为它绕过了Tkay上面提到的安全功能。

我引入了一个超时等待ajax请求返回我想要检查的数据,然后再启动文件浏览器对话框。

customFileUploadButton.addEventListener('click', function(e) {

    var returnValueToCheck; //Value we want to check against

    //reference to vanilla JS ajax function that takes callback
    ajax(function(ajaxData) { 
        returnValueToCheck = ajaxData;
    });

    setTimeout(function() {
        if (returnValueToCheck !== undefined) { //dummy check for example
            file.click();
        } else {
           console.log("Criteria not fulfilled");
        }
    }, 1000);//timer should be larger than AJAX timeout
});

说实话,我有点不确定这个工作的原因,除了显然通过浏览器特定测试通常禁止这种行为。因此,我认为这是一种hack。
我的例子是vanilla JS,但应该很容易创建一个jQuery版本。请参见JSFiddle以获取完整示例。
(这是我第一次尝试贡献,请在潜在错误和疏漏方面发表评论)

0

JQuery本身就说了,触发器不适用于文件上传和锚点等元素。(来源 - https://learn.jquery.com/events/triggering-event-handlers/)

.trigger()函数不能用于模拟浏览器原生事件,例如单击文件输入框或锚标签。这是因为,没有使用jQuery的事件系统附加的事件处理程序与这些事件相对应。

因此,在这种情况下,您可能需要使用以下JavaScript函数手动创建事件。

  • document.createEvent
  • event.initMouseEvent
  • element.dispatchEvent
可以在 Mozilla 的开发者网站上找到上述函数的示例代码和定义。

https://developer.mozilla.org/samples/domref/dispatchEvent.html

也许这会对你有所帮助。


请注意,createEvent() / initMouseEvent() 函数现已弃用。 - Alexis Wilke

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接