1.概述过去,访问本地文件对于基于浏览器的应用程序来说是一件令人头疼的事情。随着Web 2.0应用技术的不断发展,JavaScript发挥着越来越重要的作用,但出于安全考虑,JavaScript一直无法访问本地文件。因此,为了在浏览器中实现本地文件的拖动和上传等功能,我们不得不求助于特定浏览器提供的各种技术。比如对于IE,我们需要ActiveX控件来获取对本地文件的访问,而对于Firefox,我们也需要插件开发。因为不同的浏览器有不同的技术实现,为了支持多种浏览器,我们的程序会变得非常复杂和难以维护。现在,这一切都被File API的出现彻底改变了。
File API是Mozilla向W3C提交的一份草案,旨在推出一套标准的JavaScript API,其基本功能是用JavaScript操作本地文件。出于安全原因,该应用编程接口只提供对本地文件的有限访问。有了它,我们可以用纯JavaScript轻松读取和上传本地文件。目前,FireFox 3.6是第一个支持该功能的浏览器。此外,最新版本的谷歌Chrome浏览器和Safari浏览器也有相应的支持。文件应用编程接口有望成为W3C正在制定的未来HTML 5规范的一部分。
文件应用编程接口概述文件应用编程接口由一组JavaScript对象和事件组成。让开发人员能够操作在输入type=" file "…/文件选择控件中选择的文件。图1展示了文件API中所有JavaScript的组合关系。
文件列表类型包含一组文件对象。通常,文件列表对象可以取自文件字段(输入类型=“文件”./)的形式。Blob对象表示浏览器可以读取的一组原始二进制流。在Blob对象中,属性大小指示流的大小。函数slice()可以将一个长Blob对象分成小块。文件对象继承自斑点对象,与文件相关的属性基于斑点对象添加。其中,属性名表示文件的名称,去掉文件的路径信息,只保留文件名。属性类型指示文件的MIME类型。urn属性表示这个文件的URN信息。为了完成文件读取操作,文件读取器对象实例与文件或Blob对象相关联,并提供三个不同的文件读取函数和六个事件。
文件读取函数的具体内容:readAsBinaryString()读取文件内容,读取结果为二进制字符串。文件中的每个字节都将表示为[0.255].该函数接受文件对象作为参数。ReadAsText()读取文件内容,读取结果是一串代表文件内容的文本。该函数接受文件对象和文本编码名称作为参数。readAsDataURL读取文件的内容,读取结果是一个数据:的URL。RFC2397定义了一个数据URL。文件读取事件具体内容:事件名称事件描述文件读取开始时触发Onloadstart。阅读进行时,进度会定期触发。事件包含读取的数据总量。当读取中止时,中止被触发。读取错误时触发错误。当加载读取成功完成时触发。无论成功还是失败,读取完成时都会触发Loadend。
文件应用编程接口的一个简单例子接下来,我们将通过一个简单的例子展示文件应用编程接口的基本用法。本示例包含两个代码文件。index.html包含网页端的HTML代码和处理上传的JavaScript代码。upload.jsp包含服务器接收和上传文件的代码。请参见附件中的sourcecode.zip。在本例中,我们将显示一个带有文件选择字段的传统表单。用户选择文件并点击提交后,我们使用File API读取文件内容,并通过XMLHttpRequest对象通过Ajax将文件上传到服务器。图2显示了运行中的演示的屏幕截图。
我们一步一步地展示代码。清单1显示了代码的HTML部分。清单示例代码的HTML部分。
body h1File API Demo/h1 p!-文件上载的表单元素-表单名称=' demoform' id=' demoform '方法=' post ' enctype=' multipart/form-data ' action=' JavaScript 3360 uploadandsubmit();'pUpload File:输入类型=' file ' name=' file '/p pinput type=' Submit ' value=' Submit '/p/form div progessing(以字节为单位): Span id=' Bytes read '/Span/Span id=' Bytes total '/Span/div/p/body,我们可以看到我们使用普通的表单标签来包含一个传统的输入类型=" file "…/元素。表单中还有一个submit元素。在表单之外,还有一些span元素用于表示读取的数据量和总量。表单的action属性指向一个JavaScript函数uploadAndSubmit()。这个函数完成了文件的读取和上传过程。函数代码如清单2所示。清单2读取文件并上传文件的JavaScript函数。
函数uploadAndSubmit(){ var form=document . forms[' demo form '];If(形成['文件']。files . length 0){//查找输入类型='file './tag var file=form['file']。表单域中的文件[0];//尝试发送var reader=new file reader();reader . onload start=function(){//此事件在读取开始时触发console . log(' onload start ');document . getelementbyid(' bytes total ')。textContent=file.size}读者。onprogress=function(p){//此事件在读取过程中定期触发console . log(' onprogress ');document . getelementbyid(' bytes read ')。textContent=p.loaded} reader.onload=function() {//此事件在读取成功后触发console.log('加载完成');}读者。onload end=function(){//此事件将触发if (reader。错误){控制台。日志(阅读器。错误)阅读后不管成败;} else { document . getelementbyid(' bytes read ')。textContent=file.size//构造XMLHttpRequest对象并发送文件Binary数据var xhr=new XMLHttpRequest();xhr.open(/* method */'POST ',/*目标url */'upload.jsp?fileName=' file.name /*,async,默认为true */);xhr . overridemimetype(' application/octet-stream ');xhr . sendas BInary(reader . result);xhr . onreadystatechange=function(){ if(xhr . readystate==4){ if(xhr . status==200){ console . log('上传完成');console . log(' response : ' xhr . response text);} } } } } reader.readAsBinaryString(文件);} else { alert('请选择一个文件');}}在这个函数中,我们首先找到带有input type=" file "…/元素的表单,然后找到带有上传文件信息的input元素。如果输入元素中没有文件,则表示用户没有选择任何文件,此时将报告错误。清单3找到输入元素。
var form=document . forms[' DeMoform '];if(form[' file ']. files . length 0){ var file=form[' file ']。文件[0];… } else { alert('请选择一个文件');}这里,对象类型从表单["file"]返回。文件是提到的文件列表。我们从中提取第一个元素。然后,我们构建FileReader对象:var reader=new file reader();当onloadstart事件被触发时,表示页面上读取的数据总量的span元素被填充。参见清单4清单4 onloadstart事件。
读者。onloadstart=function(){ console。日志(' onloadstart ');文件。getelementbyid('字节总数').textContent=file.size}在进度事件触发时,更新页面上已读取数据量的跨度元素。参见清单5清单5关于进度事件
读者。onprogress=function(p){ console。日志(' onloadstart ');文件。getelementbyid('读取字节').textContent=p.loaded}最后的onloadend事件中,如果没有错误,我们将读取文件内容,并通过XMLHttpRequest的方式上传。清单6加载结束事件
读者。onloadend=function(){ if(reader。错误){控制台。日志(阅读器。错误);} else { //构造XMLHttpRequest对象,发送文件二进制的数据var xhr=new XMLHttpRequest();xhr.open(/* method */'POST ',/*目标url */'upload.jsp?fileName=' file.name /*,异步,默认为true */);xhr。overridemimetype('应用程序/八位字节流');xhr。sendas BInary(读取器。结果);… … } }按照文件应用编程接口的规范,我们也可以将事件onloadend的处理拆分为事件错误以及事件负荷的处理。在这个示例中,我们后台使用一个JSP来处理上传JSP。代码如清单7。清单七处理上传的JSP代码
% @ page import=' Java . io . * % % BufferedInputStream file in=new BufferedInputStream(request . getinputstream());string fn=request . GetParameter(' FIlename ');byte[] buf=新字节[1024];//接收并上传文件,保存到d : file file=new file(' d :/' fn);bufferendoutputstream file out=new bufferendoutputstream(new file outputstream(文件));While (true) {//读取数据int bytesin=file in。read (buf,0,1024);system . out . println(Bytesin);if(Bytesin==-1){ break;} else { fileOut.write(buf,0,Bytesin);} } FileOut . flush();fileout . close();out . print(file . getabsolutepath());%在这段JSP代码中,我们接受来自POST请求的文件名和二进制数据。将二进制数据写入服务器的“D:”路径。并返回文件的完整路径。以上代码可以在最新的Firefox 3.6中调试。4.通过拖动上传文件。我们介绍了如何通过HTML5的File API读取本地文件内容并上传到服务器。这样,我们已经可以满足大多数用户的需求了。缺点之一是用户只能通过点击“浏览”按钮逐个添加文件。如果他们需要批量上传文件,用户体验不会很友好。在桌面应用程序中,用户可以通过拖放鼠标方便地上传文件。拖放一直是Web应用程序的一个弱点,大多数浏览器都不支持拖放。虽然Web程序员可以通过mouseenter、mouseover、mouseout等鼠标事件实现拖放效果,但是这种方法只能将拖放的范围限制在浏览器。好消息是HTML5不仅增加了File API,还增加了对拖拽的支持。火狐3.5已经提供了对文件应用编程接口和拖放的支持。先简单介绍一下拖放的用法,然后用一个例子来说明如何通过拖放上传文件。1.《拖拉入门》。拖动一般涉及两个对象:拖动源和拖动目标。拖动源:如果一个对象可以在HTML5草稿中作为源进行拖动,则需要将可拖动属性设置为true,以将该对象标识为拖动源。然后监听源对象的dragstart事件,并在事件处理程序中设置DataTransfer。拖动数据的类型和值可以在数据传输中设置。例如,如果它是纯文本的值,它可以设置为“文本/纯文本”,而url设置为“文本/uri-list”。这样,目标对象可以根据所需的类型选择数据。拖动目标:拖动目标必须听3个事件。Dragenter:目标对象通过响应此事件来确定是否接收拖动。如果收到,则需要取消此事件并停止时间传播。Dragover:响应此事件显示拖动的提示效果。丢弃:目标对象通过响应此事件来处理拖动的数据。在下面的示例中,我们将在drop事件的处理程序中获取DataTransfer对象,并取出要上传的文件。由于本文主要介绍了文件API,但没有详细解释这一部分,感兴趣的读者可以参考HTML5草稿(参见参考资料)。2.拖放上传文件的例子下面是一个具体的例子来说明如何将拖放和文件API相结合来上传文档。由于直接与桌面交互,我们不需要处理拖放源,可以直接从目标对象中的DataTransfer对象获取数据。首先,我们需要创建一个目标容器来接收拖放事件,并添加一个div元素。然后使用列表显示缩略图、进度条和上传文件的文件名。参见清单8中的HTML代码和图3中的渲染。详情请参考随附的dnd.html文件。清单8拖动目标的HTML代码。
div id=' container ' span将文件拖放到此处进行上传。/span ul id=' FileList '/ul/div
创建拖动目标后,我们需要聆听其对应的事件dragenter、dragover和drop。在dragenter事件处理程序中,我们只需清除文件列表,然后取消dragenter事件的传播,表示我们收到了该事件。判断DataTransfer中的数据是否为文件更合适。这里我们假设所有的拖放源都是文件。在dragover事件中,我们取消了该事件,并使用默认的拖放显示效果。在drop事件中,我们注册了handleDrop事件处理程序来获取文件信息并上传文件。清单9显示了这些事件处理程序。清单9设置事件处理程序。
函数adddndlistners(){ var container=document . getelementbyid(' container ');var file list=document . getelementbyid(' file list ');//container . addevent listener(' drag enter '),函数(event) {filelist.innerhtml='在拖动到目标对象中时被触发;event . stopperpagation();event . preventdefault();},false);//在目标对象上拖动时触发container . addeventlistener(' drag over ',function(event){ event . stopperpagation());event . preventdefault();},false);//拖动结束时触发container . addeventlistener(' drop ',handledrop,false);} window.addEventListener('load ',addDNDListeners,false);处理拖放事件的用户在拖动结束时释放鼠标以触发拖放事件。在drop事件中,我们可以通过事件参数的DataTransfer对象获取文件数据,我们可以通过遍历files数组获取每个文件的信息。然后,为每个文件创建HTML元素来显示缩略图、进度条和文件名。File对象的GetAsDataURL可以以URL的形式返回文件内容,可以用来显示图片文件的缩略图。需要注意的是,在drop事件处理程序中,应该取消事件和默认处理程序的继续传播,并结束drop事件的处理。清单10显示了drop事件的处理代码。清单10丢弃事件的处理。
handle drop(event){//获取被拖动文件的列表var file=event . datatransfer . files;event . stopperpagation();event . preventdefault();var file list=document . getelementbyid(' file list ');//显示文件缩略图、文件名和上传进度,上传文件为(var I=0;I .文件.长度;I){ var file=files[I];var Li=document . create element(' Li ');var progress bar=document . create element(' div ');var img=document . create element(' img ');var name=document . create element(' span ');progress bar . class name=' progress bar ';img . src=file[I]。getastaultal();img.width=32img.height=32name . InnerHTML=file . name;Li . appendchild(img);li.appendChild(姓名);Li . appendchild(progress bar);filelist . appendchild(Li);UploadFile(file,progressbar)}}上传文件我们可以通过XMLHttpRequest对象的sendAsBinary方法上传文件,通过监听上传的进度、加载和错误事件,是否成功完成或者是否有错误,来监控文件上传的进度。在进度事件处理程序中,我们计算上传比例来确定进度条的位置。请参见清单11。图4显示了上传文件的呈现。清单11上传文件。
函数uploadFile(文件,进度条){ var xhr=new XMLHttpRequest();定义变量上传=xhr.uploadvar p=document。创建元素(' p ');p . TextContent=' 0% ';进度栏。append child(p);上传。进度条=进度条;//设置上传文件相关的事件处理函数上传。addeventlistener(' progress ',uploadProgress,false);upload.addEventListener('load ',uploadSucceed,false);upload.addEventListener('error ',uploadError,false);//上传文件xhr.open('POST ',' upload.jsp?文件名='文件。姓名);xhr。overridemimetype('应用程序/八位字节流');xhr。sentasbinary(文件。getasbinary());}函数上传进度(事件){ if (event.lengthComputable) { //将进度换算成百分比var百分比=数学。round((事件。loaded * 100)/事件。合计);console.log('percentage: '百分比);如果(百分比100){事件。目标。进度栏。第一个孩子。风格。宽度=(百分比* 2)' px ';事件。目标。进度栏。第一个孩子。文本内容=百分比"%";} } }函数uploadsuccess(事件){ event。目标。进度栏。第一个孩子。风格。宽度=' 200像素';事件。目标。进度栏。第一个孩子。文本内容=' 100% ';}函数上传错误(错误){ alert('error: '错误);}
本文通过对文件应用编程接口规范的讲解,以及两个展示其使用方法的例子,为大家提前揭示了作为未来HTML5重要组成部分的文件应用编程接口的全貌。利用它,结合其他HTML5的新特性,比如DragDrop,我们可以利用纯Java脚本语言方案,为用户提供更好使用体验的网应用,与此同时,这样的一致化方案也使我们避免了以往跨浏览器支持所花费的巨大代价。相信文件应用编程接口的出现和广泛应用,将会是未来的Web 2.0应用的大势所趋。
更多精彩内容请参考专题《ajax上传技术汇总》 , 《javascript文件上传操作汇总》 和《jQuery上传操作汇总》 进行学习。