记住并重新填充文件input

我有一个网站,允许用户多次上传文件进行处理。 目前我有一个单一的文件input,但我想能够记住用户的select,并显示在屏幕上。

我想知道怎么做是在用户select一个文件后,我会记住他们的select,并重新显示文件input与预先select的页面重新加载的文件。 我只需要知道如何记住和重新填充文件input。

我也接受不使用文件input的方法(如果可能的话)。

我正在使用JQuery

好吧,你要“记住和重新填充文件input ”,“记住他们的select,并重新显示文件input页面重新加载预选文件 ”..
在对我以前的回答的评论中,你声明你并不是真正开放的替代品:“对不起,但没有Flash和Applets,只是javscript和/或文件input,可能拖放。

我在浏览(相当一些)重复的问题( 1,2,3等)时注意到,几乎所有其他答案都符合“不,你不能,这将是一个安全问题”,可选地跟随通过概述安全风险的简单概念或代码示例。

然而,顽固的mule子(不一定是某种程度上的坏事)可能会把这些答案看作是:“不,因为我这么说”,这确实是不同的东西:“不,这里是规格说明不允许“。
所以,这是我第三次和最后一次试图回答你的问题(我引导你到了水坑,我带你到河边,现在我把你推到源头,但是我不能让你喝水)。

编辑3:

你想要做的事实际上曾经在RFC1867第3.4节描述/“build议”

VALUE属性可以与<INPUT TYPE=file>标签一起用于默认文件名。 这个用途可能是平台依赖的。 然而,在多个事务的序列中,这可能是有用的,例如避免用户反复提示相同的文件名。

实际上, HTML 4.01规范第17.4.1节规定:

用户代理可以使用value属性的值作为初始文件名。

(“用户代理”是指“浏览器”)。

鉴于javascript既可以修改和提交表单(包括文件input),也可以使用css来隐藏表单/表单元素(如文件input)的事实,单单上述语句就可以静默上传没有他的意图/知识,来自用户计算机的文件。
这显然是非常重要的,这是不可能的,正因如此,(上文)RFC1867在第8节中声明安全性考虑事项

用户代理不发送用户没有明确要求发送的任何文件是非常重要的。 因此,预计HTML解释代理将确认可能使用<INPUT TYPE=file VALUE="yyyy">build议的任何默认文件名。

然而,唯一一个实现了这个function的浏览器是(某些旧版本的) Opera :它接受了一个<input type="file" value="C:\foo\bar.txt>或值由javascript设置( elm_input_file.value='c:\\foo\\bar.txt'; )。
当这个文件框在表单提交时没有改变时,Opera会popup一个安全窗口,通知用户哪个文件将被上传到什么位置(url / webserver)。

现在有人可能会争辩说, 所有其他浏览器都违反了规范,但这是错误的:因为规范声明:“ may ”(它不是说“ must ”)“..使用值属性作为初始文件名” 。
而且,如果浏览器不接受设置文件input值(即,该值只是“只读”),那么浏览器也不需要popup这样的“可怕”和“困难”的安全性(如果用户不理解它(甚至被“限制”,总是点击“确定”),这可能甚至不能达到目的)。

让我们快速转到HTML 5然后..
在这里,所有这些模棱两可的问题都被清除了(但是仍然有些令人费解):

4.10.7.1.18file upload状态下,我们可以阅读簿记细节

  • 值IDL属性在模式文件名中。
  • 元素的值属性必须省略。

因此,必须省略文件input的值属性,但它也以某种“模式”操作,称为“文件名”,详见4.10.7.4公共input元素API

值IDL属性允许脚本处理input元素的值。 该属性是以下模式之一,它定义了它的行为:

跳到这个“ 模式文件名 ”:

在获取时,必须返回string“C:\ fakepath \”,后跟所选文件列表中的第一个文件的文件名(如果有的话),如果列表为空,则返回空string。 在设置时,如果新值是空string,则必须清空所选文件的列表; 否则,它必须抛出一个InvalidStateErrorexception。

让我重复一遍:“如果试图将文件input值设置为非空string,则must抛出InvalidStateErrorexception! (但是可以通过将input字段的值设置为空string来清除input字段。)

因此,在目前和可预见的HTML5未来 (以及除Opera之外的过去)中, 只有用户可以填充文件input (通过浏览器或由OS提供的“文件select器”)。 一个不能(重新)填充文件input到一个文件/目录与JavaScript或通过设置默认值。

获取文件名/文件path

现在, 假设使用默认值(重新)填充文件input并不是不可能的,那么显然你需要完整path:目录+文件名(+扩展名)。

在过去,一些浏览器(最值得注意的)IE6(直到IE8) 的确显示了完整的path+文件名作为值:只是一个简单的alert( elm_input_file.value ); 等等在JavaScript和浏览器也发送这个完整的path+文件名(+扩展名)的表单提交接收服务器。
注:一些浏览器也有一个'文件或文件名'属性(通常发送到服务器),但显然这不包括path..

这是一个现实的安全/隐私风险:恶意网站(所有者/利用者)可以获得用户主目录(个人资料,帐户,cookie,用户registry,历史logging,collections夹,桌面等)的path位于已知的常量位置),当典型的非技术Windows用户将从C:\Documents and Settings\[UserName]\My Documents\My Pictures\kinky_stuff\image.ext上传他的文件。
我甚至没有谈到传输数据时的风险(甚至是通过https进行“encryption”)或“安全”存储这些数据!

因此,越来越多的替代浏览器开始遵循最古老的安全措施之一:在需要知道的基础上共享信息。
而绝大多数的网站不需要知道文件path,所以他们只显示文件名(+扩展名)。

在IE8发布的时候,MS决定跟随竞争,并添加了一个URLAction选项,名为“上传文件时包含本地目录path”,它被设置为“禁用”的一般Internet区域(和“启用”在信任区域)。

这种改变造成了一个小小的破坏(主要是针对IE环境进行了优化),其中各种自定义代码和专有“控件”都无法获取上传文件的文件名:它们被硬编码以期望包含string一个完整的path,并提取最后一个反斜杠后的部分(或者如果你是幸运的话正斜杠…)。 1,2

随着HTML5,
正如你在上面所看到的,'模式文件名'指定:

获取时,必须返回string“C:\ fakepath \”,后跟所选文件列表中的第一个文件的文件名(如果有的话),如果列表为空,则返回空string。

他们注意到这一点

这个“假道”的要求是一个悲伤的历史事件

由于历史原因,值IDL属性为文件名前加string“C:\ fakepath \”。 一些传统用户代理实际上包含完整path(这是一个安全漏洞)。 由此,以向后兼容的方式从值IDL属性获得文件名是非平凡的。 以下函数以适当兼容的方式提取文件名:

 function extractFilename(path) { if (path.substr(0, 12) == "C:\\fakepath\\") return path.substr(12); // modern browser var x; x = path.lastIndexOf('/'); if (x >= 0) // Unix-based path return path.substr(x+1); x = path.lastIndexOf('\\'); if (x >= 0) // Windows-based path return path.substr(x+1); return path; // just the filename } 

注意:我认为这个函数是愚蠢的:整个问题总是有一个虚假的windowspath来parsing..所以第一个“if”不仅没有用处,而且还会引发一个bug:假设用户使用旧版浏览器上传一个文件来自: c:\fakepath\Some folder\file.ext (因为它会返回: Some folder\file.ext )…
我会简单地使用:

 function extractFilename(s){ // returns string containing everything from the end of the string // that is not a back/forward slash or an empty string on error // so one can check if return_value==='' return (typeof s==='string' && (s=s.match(/[^\\\/]+$/)) && s[0]) || ''; } 

(正如HTML5规范清楚地表明的那样)。

让我们回顾一下(获取path/文件名):

  • 旧的浏览器(和更新的浏览器,其中一个可以启用这个选项,如IE> = 8)将显示一个完整的Windows / Unixpath
  • 较旧的浏览器不会显示任何path,只是一个文件名(+扩展名)
  • 当前/未来/符合HTML5的浏览器在获取文件input的时,将始终将string: c:\fakepath\预先c:\fakepath\到文件名
    最重要的是,如果文件input接受多个文件并且用户select了多个文件,它们将只返回第一个文件名(来自“选定文件列表”)。

因此,在最近的过去,现在和可预见的HTML5将来通常只会得到文件名。

这使我们需要检查最后一件事情:这个“所选文件列表”/多个文件,这导致我们到了难题的第三部分:

(HTML5)文件API

首先:“文件API”不应与“ 文件系统API ”混淆,下面是文件系统API的摘要:

本规范定义了一个用于导航文件系统层次结构的API,并定义了一种用户代理可以将用户本地文件系统的沙盒部分公开给Web应用程序的方法。 它build立在[FILE-WRITER-ED]上,而FILE-WRITER-ED又build立在[FILE-API-ED]上,每一个都增加了不同的function。

用户本地文件系统的“沙箱部分”已经清楚地表明,用户不能使用它来获取沙盒外的用户文件(因此与问题无关,尽pipe可以将用户select的文件复制到持久的本地存储,并使用AJAX重新上传该副本等。有用作失败的上传“重试”但它不会是一个指针,原来的文件可能已经改变了平均时间)。
更重要的是,只有webkit(认为旧版本的chrome)实现了这个function,并且这个规范很可能无法继续存在,因为它is no more actively maintained, the specification is abandonned for the moment as it didn't get any significant traction

让我们继续“ 文件API ”,
这是抽象的告诉我们:

本规范提供了一个用于表示Web应用程序中的文件对象的API,以及以编程方式select它们并访问其数据的API。 这包括:

  • FileList接口,表示从底层系统中单独select的文件的数组。 用户select的用户界面可以通过<input type="file">来调用,即当input元素处于file upload状态[HTML]时。
  • Blob接口,表示不可变的原始二进制数据,并允许将Blob对象内的字节范围作为单独的Blob访问。
  • 一个文件接口,其中包括有关文件的只读信息属性,例如文件的名称和上次修改(在磁盘上)的date。
  • FileReader接口提供读取文件或Blob的方法,以及获取这些读取结果的事件模型。
  • 用于二进制数据(如文件)的URLscheme,以便在Web应用程序中引用它们。

因此, FileList可以通过文件模式下的input字段填充: <input type="file">
这意味着所有关于价值属性的上述内容仍然适用!

当input字段处于文件模式时,它将获得一个只读属性files ,这是一个类似数组的FileList object ,它引用了input元素的用户select的文件,并且可以通过FileList interface访问。
我有没有提到typesFileList只读的 (File API section 5.2) ? :

HTMLInputElement接口[HTML]具有types为FileList的只读属性…

那么, 拖放呢?

从mdn文档 – 使用拖放select文件

真正的魔法发生在drop()函数中:

 function drop(e) { e.stopPropagation(); e.preventDefault(); var dt = e.dataTransfer; var files = dt.files; handleFiles(files); } 

在这里,我们从事件中检索dataTransfer字段,然后从中取出文件列表,并将其传递给handleFiles()。 从这一点来看,无论用户使用input元素还是拖放,处理文件都是一样的。

所以,(就像input字段types=“文件”,)事件的dataTransfer属性有一个类似数组的属性files ,这是一个类似于数组的FileList object ,我们刚才已经了解到FileList是只读的 ..

FileList包含对用户select(或放置在放置目标上)和某些属性的文件的引用。 从File API Section 7.2文件属性中,我们可以读到:

名称

文件的名称; 在获取时,这必须以stringforms返回文件的名称。 在不同的系统上有很多文件名称的变化; 这只是文件的名称,没有path信息。 获取时,如果用户代理不能使这个信息可用,他们必须返回空string。

lastModifiedDate

文件的最后修改date。 获取时,如果用户代理可以使这些信息可用,则必须返回一个新的Date [HTML]对象,该对象被初始化为文件的最后修改date。 如果上次修改date和时间未知,则属性必须以Date对象的forms返回当前date和时间。

并有一个size属性:

F.size与fileBits Blob参数的大小相同,该参数必须是F的不可变原始数据。

再次,没有path,只是只读文件名。

从而:

  • (elm_input||event.dataTransfer).files给出FileList对象。
  • (elm_input||event.dataTransfer).files.length给出了文件的数量。
  • (elm_input||event.dataTransfer).files[0]是第一个选中的文件。
  • (elm_input||event.dataTransfer).files[0].name是所选第一个文件的文件名
    (这是从inputtypes=“文件”返回的value )。

那么这个“与二进制数据一起使用的URLscheme,如文件,以便它们可以在Web应用程序中引用”,当然可以容纳一个用户select的文件的私人引用?

从File API – 一个Blob和File引用的URL我们可以得知:

本规范定义了一个具有以下sorting的URL的scheme:
BLOB:550e8400-e29b-41d4-a716-446655440000#aboutABBA。

这些都存储在一个URL store (浏览器甚至应该有自己的迷你HTTP服务器上,所以可以使用这些URL在CSS,IMG SRC,甚至XMLHttpRequest。

人们可以创build这些Blob URL

  • var myBlobURL=window.URL.createFor(object); 返回首次使用后自动撤消的Blob URL
  • var myBlobURL=window.URL.createObjectURL(object, flag_oneTimeOnly); 返回一个可重用的Blob URL (除非flag_oneTImeOnly的计算结果为true),并且可以通过window.URL.revokeObjectURL(myBlobURL)来撤销。

宾果你可能会认为…但是…只有在会话中维护URL Store (所以它将在页面刷新后仍然存在,因为它仍然是相同的会话),并在文档被卸载时丢失。

从MDN – 使用对象URL :

对象URL是标识File对象的string。 每次调用window.URL.createObjectURL()时,都会创build一个唯一的对象URL,即使您已经为该文件创build了一个对象URL。 每一个都必须被释放。 当文档被卸载时它们被自动释放,如果你的页面dynamic地使用它们,你应该通过调用window.URL.revokeObjectURL()

这意味着,即使将Blob URL string存储在cookie或持久本地存储中,该string在新会话中也将毫无用处!

这应该带给我们一个完整的圆圈和最后的结论:
无法(重新)填充input字段或用户select的文件 (不在沙盒浏览器的“本地存储”区域中)。
(除非强制用户使用过时的Opera版本,或强制用户使用IE和一些activeX编码/模块(实现自定义文件select器)等)

进一步阅读:
http://www.cs.tut.fi/~jkorpela/forms/file.html
https://developer.mozilla.org/en-US/docs/Using_files_from_web_applications
http://www.html5rocks.com/en/tutorials/file/filesystem/
http://www.html5rocks.com/en/tutorials/file/dndfiles/
http://caniuse.com/filereader
JavaScript:权威指南 – David Flanagan,第22章:文件系统api
如何保存window.URL.createObjectURL()结果以供将来使用?
Blob持续多久?
如何解决C:\ fakepath?

在表单上创build一个input字段。 当用户select一个文件时,将结果复制到这个字段,如下所示:

 jQuery('#inFile').change( function(){ jQuery('#inCopy').val( jQuery('#inFile').val() ); } ); 

实际上,结果并不是完全复制,而是复制“C:/ fakepath / SELECTED_FILE_NAME”。 虽然不允许设置文件input的值,但可以在服务器准备好表单时设置文本input字段的值,而不使用“C:/ fakepath /”。

现在,当服务器获取表单时,请检查文本input字段。 如果它以“C:/ fakepath /”开头,那么用户必须select一个新文件,因此上传他们的新select。 如果没有,那么用户select了先前的select,这应该不是问题,因为根据原始问题,之前的select已经被上载,并且应该(至less在适当的编程的情况下,它可能)仍然是在服务器上。

有可能踩上GitaarLAB提供的巨大信息,我可能会build议DaveWalley非常接近,为这个问题提供了一个实际的解决scheme。 你们两位都非常感谢我。

我的信息是,

  1. 文件input提供了一个单行道。 它坐在那里等待处理你的下一个上传。 这就是所有这一切,非常。 它收到
  2. 在您的文件input标签附近的div或只读文本表单input可以做服务 。 即它可以用来告诉用户,“这里是上传的内容:” – 要显示的文件名将需要从你的服务器端逻辑填充。

所以一个基本的用例是:

  • 用户通过网页上的表单input上传文件
  • 服务器端逻辑存储文件
  • 在Ajax循环或网页中重新加载服务器端代码,标识要在div中写入的fileNamestring,“这里是当前上传的内容”
  • 文件input也可以重新呈现,以便用户也可以上传另一个文件。

您可能需要做更多的工作来处理“* required”validation,但这是另一回事。

如果您真正想要的只是对文件数据的临时持久性,比如页面出现错误或想要确认页面,则在将数据提交到数据库之前,通常需要执行下列操作临时将文件保存在隐藏字段中。

这不会重新填充文件input字段。 但是你也可以把input文件的名字放在文件input框旁边。

像这样:

 <input type=hidden name="filename" value="<?php echo $filename; ?>" /> <input type="file" name="uploadfile" size="50" /> <?php if (!empty($filename)) echo $filename; ?>