在输入类型=“文件”上取消事件

Cancel event on input type="file"

本文关键字:取消 事件 文件 输入 类型      更新时间:2023-09-26

我正在使用用于上传的标准文件输入,并且我正在寻找一种在用户单击/点击"取消"按钮(或从选择文件对话框中转义)时将函数附加到事件的方法。

我找不到任何在所有浏览器和平台上一致工作的事件。

我已经阅读了这个问题的答案:在输入类型=文件上捕获取消事件,但它们不起作用,因为大多数浏览器中的更改事件不会在取消选择文件对话框时触发。

我正在寻找一个纯粹的js解决方案,但也对jquery解决方案开放。

有人成功解决这个问题吗?

一些研究表明,无法检测何时在"文件选择"对话框窗口中选择了"取消"。您可以使用 onchangeonblur 来检查是否已选择文件或是否已将某些内容添加到输入value 中。

这可能看起来像:https://jsfiddle.net/Twisty/j18td9cs/

.HTML

<form>
  Select File:
  <input type="file" name="test1" id="testFile" />
  <button type="reset" id="pseudoCancel">
    Cancel
  </button>
</form>

JavaScript

var inputElement = document.getElementById("testFile");
var cancelButton = document.getElementById("pseudoCancel");
var numFiles = 0;
inputElement.onclick = function(event) {
  var target = event.target || event.srcElement;
  console.log(target, "clicked.");
  console.log(event);
  if (target.value.length == 0) {
    console.log("Suspect Cancel was hit, no files selected.");
    cancelButton.onclick();
  } else {
    console.log("File selected: ", target.value);
    numFiles = target.files.length;
  }
}
inputElement.onchange = function(event) {
  var target = event.target || event.srcElement;
  console.log(target, "changed.");
  console.log(event);
  if (target.value.length == 0) {
    console.log("Suspect Cancel was hit, no files selected.");
    if (numFiles == target.files.length) {
      cancelButton.onclick();
    }
  } else {
    console.log("File selected: ", target.value);
    numFiles = target.files.length;
  }
}
inputElement.onblur = function(event) {
  var target = event.target || event.srcElement;
  console.log(target, "changed.");
  console.log(event);
  if (target.value.length == 0) {
    console.log("Suspect Cancel was hit, no files selected.");
    if (numFiles == target.files.length) {
      cancelButton.onclick();
    }
  } else {
    console.log("File selected: ", target.value);
    numFiles = target.files.length;
  }
}

cancelButton.onclick = function(event) {
  console.log("Pseudo Cancel button clicked.");
}

我建议制作自己的取消或重置按钮,以重置表单或清除输入中的值。

我有一个完美的解决方案。

焦点事件将在更改事件之前执行。

所以我需要使用 setTimeout 使焦点方法比更改方法执行得晚。

const createUpload = () => {
    let lock = false
    return new Promise((resolve, reject) => {
        // create input file
        const el = document.createElement('input')
        el.id = +new Date()
        el.style.display = 'none'
        el.setAttribute('type', 'file')
        document.body.appendChild(el)
        el.addEventListener('change', () => {
            lock = true
            const file = el.files[0]
            
            resolve(file)
            // remove dom
            document.body.removeChild(document.getElementById(el.id))
        }, { once: true })
        // file blur
        window.addEventListener('focus', () => {
            setTimeout(() => {
                if (!lock && document.getElementById(el.id)) {
                    reject(new Error('onblur'))
                    // remove dom
                    document.body.removeChild(document.getElementById(el.id))
                }
            }, 300)
        }, { once: true })
        // open file select box
        el.click()
    })
}

try {
    const file = createUpload()
    console.log(file)
} catch(e) {
    console.log(e.message) // onblur
}

在你的"inputElement.onclick"中,你应该设置一个标志(在我的例子中,我设置了window.inputFileTrueClosed = true),这样你就可以在按下该类型事件的"取消"按钮后检测窗口何时获得焦点。以下检测窗口是否再次获得焦点:这意味着可以按下"取消"按钮:

var isChrome = !!window.chrome;
    window.addEventListener('focus', function (e) {
       
    
    if(window.inputFileTrueClosed != false){
      
        if(isChrome == true){
           setTimeout(
                    function() 
                    {
                         if(window.inputFileTrueClosed != false){
   //if it is Chrome we have to wait because file.change(function(){... comes after "the window gets the focus"
                         window.inputFileTrueClosed = false; 
                         }
                    }, 1000);
}
else
{
        // if it is NOT Chrome (ex.Safari) do something when the "cancel" button is pressed.
        window.inputFileTrueClosed = false;
        
    }
}
})

显然,窗口获得了许多其他事件的焦点,但是使用标志,您可以选择需要检测的事件。

当我们选择文件时,以下事件被称为 -

场景 1:单击选择文件,然后单击取消时

Focus事件值="

Click事件值="

Blur事件值="

Focus事件值="

Blur 事件值="(当用户单击某个位置时)

场景 2 : 选择文件时 -

Focus事件值="

Click事件值="

Blur事件值="

Focus事件值="

Change事件值="文件值"

Blur事件值="文件值"

Focus事件值="文件值"

Blur 事件值="文件值"(当用户单击某个地方时)

我们在这里看到,当在焦点事件之后调用模糊事件(最后一个事件)时,没有文件值意味着单击"取消"按钮。

我的方案是在文件加载时将"提交"按钮文本更改为"请稍候"currentEvent 变量用于保存当前事件值,单击或聚焦如果当前事件 = 焦点和文件值为 null,则表示单击"取消"按钮。

Javascript
var currentEvent = "focus";
function onFileBrowse() {    
    var vtbFile = $('#uploadFile')[0].files[0];
    if (vtbFile != undefined) {
        var extension = vtbFile.name.split('.').pop().toLowerCase();
        var valError = "";
        if (extension === 'xlsx' || extension === 'xlsb' || extension === 'csv') {
            if (vtbFile.size === 0)
                valError = "File '" + vtbFile.name + "' is empty";
        }
        else
            valError = "Extension '" + extension + "' is not supported.";
        if (valError !== "") {            
            alert("There was an issue validating the file. " + valError, 20000);
        }        
    }
    //hide Indicator
    var buttonUpload = document.getElementById('btnUploadTB');
    buttonUpload.innerText = "Submit";
};
function fileClick() {
    //show Indicator
    var buttonUpload = document.getElementById('btnUploadTB');
    buttonUpload.innerText = "Please wait..";
    
    document.getElementById('uploadFile').value = null;   
    currentEvent = "click";
}
function fileFocus() {
    currentEvent = "focus";
}
function fileBlur() {
    
    if (!document.getElementById('uploadFile').value && currentEvent == "focus") {
        console.log('blur' + 'change to submit');
        //hide Indicator
        var buttonUpload = document.getElementById('btnUploadTB');
        buttonUpload.innerText = "Submit";
    }
}
HTML
<input class="k-button k-upload-button" type="file" id="uploadFile" name="uploadFile"
    accept=".csv,.xlsx,.xlsb" onChange='onFileBrowse()' onclick="fileClick()" onfocus="fileFocus()" onblur="fileBlur()" />
<button id="btnUploadTB" type="button" class="btn btn-default" onclick="uploadTBFile()" style="width:28%;margin-left: 3px;">Submit</button>

如果单击取消按钮,此代码不会侦听,但它对我有用。每次输入的值更改时,它都会检查输入是否附加了文件。从那里我几乎可以做我需要做的任何事情。

$("#imageUpload").change(function() {
  if (this.files && this.files[0]) {
    console.log("Has file selected");
  } else {
    console.log("No file selected");
  }
});
<input type="file" id="imageUpload" />
<script src="https://code.jquery.com/jquery-3.5.1.js" integrity="sha256-QWo7LDvxbWT2tbbQ97B53yJnYU3WhH/C8ycbRAkjPDc=" crossorigin="anonymous"></script>

我遇到了一个问题,我单击了输入类型="file"元素上的取消按钮,并希望该函数什么都不做。 如果选择了某些内容并且我单击了打开按钮,那么我希望我的函数执行某些操作。该示例仅显示了该方法,我删除了打开后对它执行的操作。我输入警报只是为了让您看到单击取消时没有从对话框中返回文件名。这是我使用的方法,它很简单,但它有效。

 function openFileOnClick(){
    document.getElementById("fileSelector").value = "";
    document.getElementById("fileSelector").files.length = 0;            
    document.getElementById("fileSelector").click();
    if(document.getElementById("fileSelector").files.length >= 1){
        alert(document.getElementById("fileSelector").value);
        //Do something 
    }
    else{
        alert(document.getElementById("fileSelector").value);
        //Cancel button has been called.
    }
}
<html>
<head>
</head>
<body>
<input type="file" id="fileSelector" name="fileSelector" value="" style="display:none;"  />
<input type="button" value="Open File" name="openFile" onclick="openFileOnClick();" />
</body>
</html>

在使用 jQuery 实现自动提交图像上传表单后,我遇到了类似的问题。如果用户取消了对话,则会发送空白。所需要的只是在同一脚本中检测此空值:

$('#upload-portrait-input').on('change', function(){
   if ( $(this).val() != '' )
    {
    $('#portraitForm').submit();
    }
   else { // do something when the user clicks cancel
    }
 });
无论

用户是否浏览文件资源管理器(活动/非活动),我都需要以不同的方式设置文件上传的样式。仅clickblur事件。只需考虑一下,当用户单击"取消"按钮时,需要在文件上传之外单击(在我的情况下button)。

这是我对 Angular 的解决方案,但我想任何人都可以抓住这个想法并用你最喜欢的框架/库/香草 JS 进行调整。

<!-- HTML - Angular -->
<input hidden type="file" #fileInput (change)="onFileUpload($event.target.files)">
<button (click)="initBrowsingFlags(); fileInput.click()" (blur)="onBlur()" [class.browsing]="browsing">Upload</button>
// Typescript - Angular
/** Whether the user is browsing the file explorer. */
browsing = false;
/** 
 * If a 2nd `blur` event is emitted while {@link browsing} is still true, it means that the user
 * clicked the Cancel button on the file explorer.
 * */
alreadyOneBlurEventWhileBrowsing = false;
onFileUpload(files: FileList) {
  // ...
  this.resetBrowsingFlags();
}
onBlur() {
  if (!this.browsing) return;
  if (this.onCancelClickWhileBrowsing) {
    this.resetBrowsingFlags();
  } else {
    this.onCancelClickWhileBrowsing = true;
  }
}
initBrowsingFlags() {
  this.browsing = true;
  this.alreadyOneBlurEventWhileBrowsing= false;
}
resetBrowsingFlags() {
  this.browsing = false;
  this.alreadyOneBlurEventWhileBrowsing= false;
}

这解决了我的问题,尽管除了谷歌浏览器之外,没有在其他浏览器上进行测试:

$("#fileUpload").on("change", function (event) {
   if (!$(this)[0].files[0]) {
      event.preventDefault();
      return;
   };
   // Else do something here.
});

$(this)[0]是从jQuery对象转换为纯JavaScript对象。单击"取消"按钮时,files的数组长度为零。您可以使用$(this)[0].files.length < 1或检查$(this)[0].files[0]是否undefined

我能够从这个答案中获得灵感,让它在 Chrome 和 Safari 中工作,但它似乎在 Firefox 中的工作方式不同——当对话框关闭时,focus事件从未被触发。看起来 Firefox 利用了 cancel 事件(更多信息在这里)。我在最新版本的Chrome,Safari和Firefox中成功测试了这一点:

function selectFiles(
  options?: {
    /** Allow the selection of multiple files */
    multiple?: boolean,
    /** Restrict the selection to certain types of files (ex: `.txt`) */
    accept?: Array<string>
  }
): Promise<{
  /** The list of selected files (empty if none selected) */
  files: Array<File>,
  /** The event that prompted the dialog to close */
  event: Event
}> {
  return new Promise((resolve) => {
    const fileSelector: HTMLInputElement = document.createElement('input');
    fileSelector.type = 'file';
    fileSelector.style.display = 'none';
    fileSelector.multiple = !!options?.multiple;
    if (Array.isArray(options?.accept)) {
      fileSelector.accept = options.accept.join(',');
    }
    let currTimeout;
    const resolveWithFiles = (event?: Event) => {
      clearTimeout(currTimeout);
      currTimeout = setTimeout(() => {
        // cleanup
        window.removeEventListener('focus', resolveWithFiles);
        fileSelector.remove();
        // resolve with file array and the event associated with
        // what prompted the dialog to close
        resolve({ files: Array.from(fileSelector.files || []), event });
      }, 300);
    };
    // EVENTS
    // "cancel" event in Chrome and Safari
    window.addEventListener('focus', resolveWithFiles);
    // "cancel" event in Firefox
    fileSelector.addEventListener('cancel', resolveWithFiles);
    // files selected
    fileSelector.addEventListener('change', resolveWithFiles);
    // INITIATE
    // open the selection window
    document.body.append(fileSelector);
    fileSelector.click();
  });
}
In react , on Change event of input field , the event has no files on cancel event, we can proceed with this assumption. Cancel event will be captured.
  handleChange = (event) => {
    console.log(event);
    console.log(event.target.files[0]);
    this.setState({
      tableDataResult: false,
    });
    if(event.target.files[0]){
      this.setState({
        csvfile: event.target.files[0],
      });
    }else{
//Cancel event called
      console.log("false");
      this.setState({
        csvfile: oldValue,
      });
    }
  };
<input
                                        style={{
                                          width: "450px",
                                          marginLeft: "15px",
                                          marginTop: "5px",
                                        }}
                                        className="csv-input"
                                        type="file"
                                        ref={(input) => {
                                          this.filesInput = input;
                                        }}
                                        name="file"
                                        placeholder={null}
                                        onChange={this.handleChange}
                                      />

ES2022 方式

创建文件选取器服务

// wait function to delay 
const wait = (ms) => new Promise((res) => setTimeout(res, ms));
class FilePickerServiceK {
    getFileInput() {
        if (this.ref)
            return this.ref;
        const input = document.createElement('input');
        input.type = 'file';
        this.ref = input;
        return this.ref;
    }
    async pick(opt = {}) {
        const input = this.getFileInput();
        input.multiple = opt.multiple;
        const onWindowFocusP = new Promise((res) => window.addEventListener('focus', res, {once: true}));
        input.click();
        await onWindowFocusP;
        await wait(100);
        const files = Array.from(input.files ?? []);
        input.value = '';
        return files;
    }
}
const FilePickerService = new FilePickerServiceK();
// for demo
const button = document.createElement('button');
button.innerHTML = 'Pick File';
document.body.appendChild(button);
const div = document.createElement('div');
document.body.appendChild(div);
const handle = async () => {
    const [file] = await FilePickerService.pick();
    div.innerHTML = file ? file.name : 'cancelled';
};
button.addEventListener('click', handle);

如果您可以使用文件系统访问 API 的 showOpenFilePicker 方法,则还有另一种选择。

在撰写此答案时,API是完全规范的,但只有Chromium浏览器(Chrome和Edge)支持此功能,但希望它很快就会更加可用。

(请注意,下面的代码片段在沙盒或跨源 iframe 中不起作用,因此 StackOverflow、CodePen 和 jsfiddler 无法运行它,您必须有一个 HTTPS 环境来测试它)

document.querySelector("button").addEventListener("click", async () => {
    try {
        const file = await globalThis.showOpenFilePicker({
            types: [
                {
                    description: "Images",
                    accept: {
                        "image/*": [".png", ".gif", ".jpeg", ".jpg"],
                    },
                }
            ]
        });
        console.log("You picked: ", file[0].name);
    } catch (e) {
        if (e.code === e.ABORT_ERR) {
            alert("You cancelled.")
        } else {
            throw e;
        }                
    }
});
<button>Pick a file</button>

在这种情况下,

现在会触发一个cancel事件,大约一年来,所有 3 个主要浏览器引擎都支持该事件(Chrome 和 Firefox 为 2 个)。

document.querySelector("input").addEventListener("cancel", (evt) => {
  console.log("You closed the file picker dialog without selecting a file.");
});
<input type="file">

遗憾的是,我们不能使用 oncancel 全局处理程序来检测此事件,因为这可能指向在<dialog>元素上触发的同音异义词事件,因此是误报。因此,据我所知,没有简单的检测途径。