ajaxFileUpload 上传后无法再次选择文件,问题到底出在哪?
用 ajaxFileUpload.js 配合 dropify 做文件上传,碰到一个挺隐蔽的坑:第一次选文件、上传,都正常。
可一旦传完,那个上传框就像被冻住了。再点它选新文件,没反应;点删除按钮,事件也不触发。
说白了,上传一次之后,dropify 整个就废了。
先说结论:问题不在 dropify,在 ajaxFileUpload
dropify 是个做上传框样式美化的插件,它会在初始化时给那个 <input type="file"> 绑一堆事件(选择、预览、删除都靠这些事件)。
而 ajaxFileUpload 是老牌的 jQuery 上传方案,原理是偷偷建一个隐藏 iframe + 隐藏表单,把 file input 塞进去提交,模拟出 Ajax 上传的效果。
冲突就发生在这一步。
来看 ajaxFileUpload.js 里 createUploadForm 的原始写法(也就是被注释掉的那几行):
var oldElement = $('#' + fileElementId); // 拿到页面上原始的 input
var newElement = $(oldElement).clone(); // 克隆一个新的
$(oldElement).attr('id', fileId); // 改掉原始 input 的 id
$(oldElement).before(newElement); // 把克隆体插到原位置占位
$(oldElement).appendTo(form); // 把原始 input 搬进隐藏表单
读懂这五行,问题就一目了然了。
它把原始那个被 dropify 绑过事件的 input 整个搬走了,扔进隐藏表单去提交。
然后留在页面上、用户看得见的,是一个全新克隆出来的空壳 input。
jQuery.clone() 默认不复制元素上绑定的事件,dropify 的那些 change、删除监听器,克隆体一个都没继承。
所以你看到的上传框,其实早就不是原来那个了。
它是个长得一样、但没接任何线的假人。再怎么点都没用。
怎么改:让原始 input 原地不动
思路反过来就行。
不要动原始 input,改成把克隆体丢进隐藏表单去顶包:
//20200903 改版:上传成功/失败后重新选文件,上传框不显示新文件、还记着旧文件
//原因:ajaxFileUpload 会把原始上传框搬进隐藏表单提交,留在页面的是克隆空壳,事件全丢了
//解决:改成克隆原始上传框,只把克隆体放进隐藏表单,原始上传框留在原地、事件不动
var oldElement = $('#' + fileElementId).clone(); // 克隆一份
$(oldElement).attr('id', fileId); // 改克隆体的 id,避免重复
$(oldElement).appendTo(form); // 把克隆体放进隐藏表单
关键差别只有一处:原来是 $('#' + fileElementId)(操作原件),改成 $('#' + fileElementId).clone()(操作复制品)。原始 input 连同 dropify 的全部事件,从头到尾都没被碰过,自然就能反复用了。
完整改好的文件放在这里:code/ajaxfileupload.js,可以直接对照替换。
顺手补一个坑:handleError 未定义
翻这份 ajaxFileUpload.js 还会发现,很多老版本里压根没有 handleError 函数。
可代码里上传出错时又会去调 jQuery.handleError(...),结果就是报一个 handleError is not a function,把后续逻辑也带崩。
这份文件开头已经补上了:
handleError: function( s, xhr, status, e ) {
if ( s.error ) {
s.error.call( s.context || s, xhr, status, e );
}
if ( s.global ) {
(s.context ? jQuery(s.context) : jQuery.event).trigger( "ajaxError", [xhr, s, e] );
}
}如果你的版本也缺,一并加上。
一点说明
这套方案是在我自己用到的浏览器环境里验证通过的。需要提醒的是:<input type="file"> 在选完文件后做 clone,能不能带上已选中的文件,各浏览器行为并不完全一致。这份代码是在用户触发上传、提交表单的那一刻才克隆,所以多数情况下没问题;但如果你遇到「克隆体提交后服务端收不到文件」的情况,多半就是卡在这个兼容点上,得单独排查。
另外,ajaxFileUpload 本身已经是很老的方案了,靠隐藏 iframe 撑场面。新项目其实直接用 FormData + fetch/XMLHttpRequest 就能干净地做异步上传,不用受这些克隆 DOM 的折磨。只是老项目动不了大手术,那就按上面这样改一行先把坑填了。
常见问题
为什么上传一次后,dropify 的删除按钮也点不动了?
因为删除按钮的点击事件也是 dropify 初始化时绑在原始 input 相关结构上的。原始 input 被 ajaxFileUpload 搬走、换成空壳后,这些事件随之失效,删除按钮自然就没反应了。修复方式和「无法再次选择文件」是同一个:别搬原件,搬克隆体。
jQuery.clone() 加 true 参数能不能保留事件?
clone(true) 确实能复制元素自身及子元素上用 jQuery 绑定的事件。但它无法恢复 dropify 内部维护的实例状态,效果并不可靠。与其去赌克隆能不能完整复刻,不如直接让原始元素留在原地,这才是稳妥做法。
改完之后服务端收不到上传的文件怎么办?
先确认是不是 file input 克隆后丢了选中文件的浏览器兼容问题。可以在 submit 前打印一下隐藏表单里那个克隆 input 的 files 属性,看是否为空。如果确实为空,说明当前环境不支持克隆 file 值,需要换成不依赖克隆的上传方式(比如直接用 FormData 取原始 input 的文件)。
能不能干脆不用 ajaxFileUpload?
可以,而且更推荐。现代浏览器用 FormData 配合 fetch 或 XMLHttpRequest 就能实现真正的异步文件上传,不需要隐藏 iframe,也不存在搬 DOM、克隆 input 这一摊问题。只有在维护无法重构的老项目时,才需要走这篇文章的修补路线。
如果大家在用 ajaxFileUpload 或 dropify 时还踩到别的坑,欢迎在评论区交流~~~
版权声明
未经授权,禁止转载本文章。
如需转载请保留原文链接并注明出处。即视为默认获得授权。
未保留原文链接未注明出处或删除链接将视为侵权,必追究法律责任!
本文原文链接: https://fiveyoboy.com/articles/ajaxfileupload-cannot-select-file-again/
备用原文链接: https://blog.fiveyoboy.com/articles/ajaxfileupload-cannot-select-file-again/