是否有使用JavaScript处理文件选择对话框的现代/新方法?

时间:2018-07-13 16:29:53

标签: javascript html5 html-input

这是我的特定用例:我想单击文件对话框的“取消”按钮。原因是我经常使用Promises,并且我希望在选择文件时能兑现诺言,而在取消对话框时希望兑现诺言。

但是,我在这里和其他地方发现的每个解决此问题的“解决方案”都几乎(几乎不可靠)是一种解决方法,例如捕获body s焦点事件。
我自己的解决方法是将当前的Promises拒绝函数保存在(模块本地)全局变量中,并在第二次调用该函数时调用它,因此,至少在第二个对话框中选择文件时才能实现Promise。

问题是:是否有一种现代,可用的方式来处理这种情况,这不是一种解决方法?如果是这样,那是什么?

这是我当前使用TypeScript和JQuery编写的代码:

export function selectJsonFile() {
    return new Promise<File>((resolve, reject) => {
        let $input = $(`<input type="file" accept=".json">`);
        $input.hide().change(() => {
            let file = (<HTMLInputElement>$input[0]).files[0];
            if (file)
                resolve(file);
            else
                reject();
            $input.remove();
        }).appendTo($("body")).click();
    });
}

这是我刚刚编写的纯JS版本,将其概括了一下:

export function selectJsonFilePureJS() {
    return new Promise((resolve, reject) => {
        let input = document.createElement("input");
        input.setAttribute("type", "file");
        input.setAttribute("accept", ".json");
        input.style.display = "none";
        input.onchange = () => {
            let file = input.files[0];
            if (file)
                resolve(file);
            else
                reject();
            input.remove();
        };
        document.body.appendChild(input);
        input.click();
    });
}

现在,有没有一种实际的好方法来处理此问题?我找不到任何东西。

1 个答案:

答案 0 :(得分:1)

即使在2018年,也无法知道用户是否单击了“取消”,只能跟踪对话框是否处于打开状态时跟踪您是否看到页面上任何地方的用户事件。

在打开文件对话框的同时,页面应完全处于非活动状态,因此请设置一些内容,以指示当用户单击文件输入元素时对话框是打开的,然后监视应避免的一系列详尽的用户输入事件对话框打开时可能出现。如果该监视器看到任何事件,则说明您实际上是在不是在查看文件对话框,因此我们可以推断出他们已取消了该对话框。此时,您可以触发“文件对话框已取消”处理程序函数,并取消绑定事件监视器。

让我们写一个监视器:

class EventMonitor {
  constructor(dialogWasCancelled) {
    this.dialogWasCancelled = dialogWasCancelled;
    // don't use a class function here, since we need to
    // unregister the exact function handle later, and we
    // don't get those if we use e=>this.x() for listening.
    this.x = (function() {
      this.dialogWasCancelled();
      this.unregister();
    }).bind(this);
    this.register();
  }
  register() {
    document.addEventListener("mousemove", this.x);
    document.addEventListener("touchstart", this.x);
    document.addEventListener("keypress", this.x);
    window.addEventListener("scroll", this.x);
  }
  unregister() {
    document.removeEventListener("mousemove", this.x);
    document.removeEventListener("touchstart", this.x);
    document.removeEventListener("keypress", this.x);
    window.removeEventListener("scroll", this.x);
  }
}

尽管正文focus事件不是非常可靠,但mousedowntouchstartscrollkeypress事件却是可靠的。当您看到其中任何一个时,便知道用户正在查看您的页面,而不是应用程序文件对话框。您需要所有这四个,因为您可能有传统的台式机或触摸/移动用户,甚至对于传统的台式机用户,也有很多人出于各种充分的理由而只需要使用键盘。最后,您还需要检查滚动,因为这取决于输入设备,因为这是最常见的用户交互,并且可能根本没有任何鼠标,触摸或按键事件。

这样,我们可以可靠地监视页面,以确定是否选择了文件,或者用户取消了对话框:

let monitor = false;

function handleFiles(evt) {
  // some browsers _will_ kick in a change event if
  // you had a file, then "cancel"ed it on a second
  // select. Chrome, for instance, considers that
  // an action that empties the selection again.
  let files = evt.target.files;
  if (files.length === 0) return dialogCancelled(evt);
  console.log("user selected a file");
  monitor = monitor.unregister();
}

function dialogCancelled(evt) {
  console.log("user did not select a file");
  monitor = monitor.unregister();
}

let fileInput = document.querySelector("input[type=file]")

fileInput.addEventListener("change", e => handleFiles(e));
fileInput.addEventListener("click", e => (monitor = new EventMonitor(dialogCancelled)));

您可以测试代码是否在http://jsbin.com/timafokisu/edit?js,console,output上有效