以往對於基於瀏覽器的應用而言,訪問本地文件都是一件頭疼的事情。雖然伴隨着 Web 2.0 應用技術的不斷髮展,JavaScript 正在扮演愈來愈重要的角色,可是出於安全性的考慮,JavaScript 一直是沒法訪問本地文件的。因而,爲了在瀏覽器中可以實現諸如拖拽並上傳本地文件這樣的功能,咱們就不得不求助於特定瀏覽器所提供的各類技術了。好比對於 IE,咱們須要經過 ActiveX 控件來獲取對本地文件的訪問能力,而對於 Firefox,一樣也要藉助插件開發。因爲不一樣瀏覽器的技術實現不盡相同,爲了讓程序可以支持多瀏覽器,咱們的程序就會變得十分複雜而難於維護。不過如今,這一切都由於 File API 的出現而獲得了完全的改變。javascript
File API 是 Mozilla 向 W3C 提交的一個草案,旨在推出一套標準的 JavaScript API,其基本功能是實現用 JavaScript 對本地文件進行操做。出於安全性的考慮,該 API 只對本地文件提供有限的訪問。有了它,咱們就能夠很輕鬆的用純 JavaScript 來實現本地文件的讀取和上傳了。目前,FireFox 3.6 是最早支持這一功能的瀏覽器。除此之外,最新版本的 Google Chrome 瀏覽器和 Safari 瀏覽器也有相應的支持。File API 有望成爲 W3C 目前正在制定的將來 HTML 5 規範當中的一部分。html
本文後續章節將對 File API 作一個基本的介紹。併爲讀者演示:如何利用 File API 實現基於 file input 控件的本地文件讀取與上傳;以及利用拖拽實現從用戶系統批量上傳文件的功能。經過本文,讀者能夠了解最新的 File API 的概況以及使用,從而爲構建具備更好用戶體驗的 Web 2.0 應用提供參考。java
File API 由一組 JavaScript 對象以及事件構成。賦予開發人員操做在 <input type=」file」 … /> 文件選擇控件中選定文件的能力。圖 1 展現了 File API 全部的 JavaScript 的組合關係。程序員
類型 FileList 包含一組 File 對象。一般 FileList 對象能夠從表單中的文件域(<input type=」file」 .../>)中拿取。Blob 對象表明瀏覽器所能讀取的一組原始二進制流。Blob 對象中,屬性 size 表示流的大小。函數 slice() 能夠將一個長的 Blob 對象分割成小塊。File 對象繼承自 Blob 對象,在 Blob 對象基礎上增長了和 File 相關的屬性。其中,屬性 name 表示文件的名字,這個名字去掉了文件的路徑信息,而只保留了文件名。屬性 type 表示文件的 MIME 類型。屬性 urn 則表明這個文件的 URN 信息。爲完成文件讀取的操做,一個 FileReader 對象實例會關聯 File 或 Blob 對象,並提供三種不一樣的文件讀取函數以及 6 種事件。參見表 1 及表 2。web
函數名稱 | 功能 |
---|---|
readAsBinaryString() | 讀取文件內容,讀取結果爲一個 binary string。文件每個 byte 會被表示爲一個 [0..255] 區間內的整數。函數接受一個 File 對象做爲參數。 |
readAsText() | 讀取文件內容,讀取結果爲一串表明文件內容的文本。函數接受一個 File 對象以及文本編碼名稱做爲參數。 |
readAsDataURL | 讀取文件內容,讀取結果爲一個 data: 的 URL。DataURL 由 RFC2397 定義,具體能夠參考 http://www.ietf.org/rfc/rfc2397.txt。 |
事件名稱 | 事件說明 |
---|---|
Onloadstart | 文件讀取開始時觸發。 |
Progress | 當讀取進行中時定時觸發。事件參數中會含有已讀取總數據量。 |
Abort | 當讀取被停止時觸發。 |
Error | 當讀取出錯時觸發。 |
Load | 當讀取成功完成時觸發。 |
Loadend | 當讀取完成時,不管成功或者失敗都會觸發。 |
接下來咱們用一個簡單的例子展現 File API 的基本用法。這個示例包含兩個代碼文件,index.html 包含 Web 端的 HTML 代碼和處理上傳的 JavaScript 代碼;upload.jsp 包含服務器端接收上傳文件的代碼。請參見附件中的 sourcecode.zip。在這個例子中,咱們將顯示一個傳統的帶有 File 選擇域的表單。當用戶選擇文件,點擊提交後,咱們使用 File API 讀取文件內容,並經過 XMLHttpRequest 對象,用 Ajax 的方式將文件上傳到服務器上。圖 2 展現了運行中的演示截圖。數組
咱們逐步展現其中的代碼。清單 1 給出了代碼的 HTML 部分瀏覽器
<body> <h1>File API Demo</h1> <p> <!-- 用於文件上傳的表單元素 --> <form name="demoForm" id="demoForm" method="post" enctype="multipart/form-data" action="javascript: uploadAndSubmit();"> <p>Upload File: <input type="file" name="file" /></p> <p><input type="submit" value="Submit" /></p> </form> <div>Progessing (in Bytes): <span id="bytesRead"> </span> / <span id="bytesTotal"></span> </div> </p> </body>
能夠看到,咱們用普通的 <form> 標籤來包含一個傳統的 <input type=」file」 … /> 元素。在 <form> 中還有一個 submit 元素。在 <form> 以外有一些 <span> 元素用來表示已讀取和總共的數據量。<form> 的 action 屬性指向了一個 JavaScript 函數 uploadAndSubmit()。這個函數完成了讀取文件並上傳的過程。函數代碼見清單 2。安全
function uploadAndSubmit() { var form = document.forms["demoForm"]; if (form["file"].files.length > 0) { // 尋找表單域中的 <input type="file" ... /> 標籤 var file = form["file"].files[0]; // try sending var reader = new FileReader(); reader.onloadstart = function() { // 這個事件在讀取開始時觸發 console.log("onloadstart"); document.getElementById("bytesTotal").textContent = file.size; } reader.onprogress = function(p) { // 這個事件在讀取進行中定時觸發 console.log("onprogress"); document.getElementById("bytesRead").textContent = p.loaded; } reader.onload = function() { // 這個事件在讀取成功結束後觸發 console.log("load complete"); } reader.onloadend = function() { // 這個事件在讀取結束後,不管成功或者失敗都會觸發 if (reader.error) { console.log(reader.error); } else { document.getElementById("bytesRead").textContent = file.size; // 構造 XMLHttpRequest 對象,發送文件 Binary 數據 var xhr = new XMLHttpRequest(); xhr.open(/* method */ "POST", /* target url */ "upload.jsp?fileName=" + file.name /*, async, default to true */); xhr.overrideMimeType("application/octet-stream"); xhr.sendAsBinary(reader.result); xhr.onreadystatechange = function() { if (xhr.readyState == 4) { if (xhr.status == 200) { console.log("upload complete"); console.log("response: " + xhr.responseText); } } } } } reader.readAsBinaryString(file); } else { alert ("Please choose a file."); } }
在這個函數中,首先咱們找到含有 <input type=」file」 … /> 元素的 <form>,並找到含有上傳文件信息的 <input> 元素。如 <input> 元素中不含有文件,說明用戶沒有選擇任何文件,此時將報錯。服務器
var form = document.forms["demoForm"]; if (form["file"].files.length > 0) { var file = form["file"].files[0]; … … } else { alert ("Please choose a file."); }
這裏,從 form[「file」].files 返回的對象類型即爲提到的 FileList。咱們從中拿取第一個元素。以後,咱們構建 FileReader 對象:app
var reader = new FileReader();
在 onloadstart事件觸發時,填充頁面上表示讀取數據總量的 <span> 元素。參見清單 4
reader.onloadstart = function() { console.log("onloadstart"); document.getElementById("bytesTotal").textContent = file.size; }
在 onprogress 事件觸發時,更新頁面上已讀取數據量的 <span> 元素。參見清單 5
reader.onprogress = function(p) { console.log("onloadstart"); document.getElementById("bytesRead").textContent = p.loaded; }
最後的 onloadend 事件中,若是沒有錯誤,咱們將讀取文件內容,並經過 XMLHttpRequest 的方式上傳。
reader.onloadend = function() { if (reader.error) { console.log(reader.error); } else { // 構造 XMLHttpRequest 對象,發送文件 Binary 數據 var xhr = new XMLHttpRequest(); xhr.open(/* method */ "POST", /* target url */ "upload.jsp?fileName=" + file.name /*, async, default to true */); xhr.overrideMimeType("application/octet-stream"); xhr.sendAsBinary(reader.result); … … } }
按照 File API 的規範,咱們也能夠將事件 onloadend 的處理拆分爲事件 error 以及事件 load 的處理。
在這個示例中,咱們後臺使用一個 JSP 來處理上傳。JSP 代碼如清單 7。
<%@ page import="java.io.*" %><% BufferedInputStream fileIn = new BufferedInputStream(request.getInputStream()); String fn = request.getParameter("fileName"); byte[] buf = new byte[1024];
//
接收文件上傳並保存到 d:\
File file = new File("d:/" + fn); BufferedOutputStream fileOut = new BufferedOutputStream(new FileOutputStream(file)); while (true) { // 讀取數據 int bytesIn = fileIn.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 中調試經過。
前面咱們介紹了怎樣經過 HTML5 的 File API 來讀取本地文件內容並上傳到服務器,經過這種方式已經可以知足大部分用戶的需求了。其中一個不足是用戶只能經過點擊「瀏覽」按鈕來逐個添加文件,若是須要批量上傳文件,會致使用戶體驗不是很友好。而在桌面應用中,用戶通常能夠經過鼠標拖拽的方式方便地上傳文件。拖拽一直是 Web 應用的一個軟肋,通常瀏覽器都不提供對拖拽的支持。雖然 Web 程序員能夠經過鼠標的 mouseenter,mouseover 和 mouseout 等事件來實現拖拽效果,可是這種方式也只能使拖拽的範圍侷限在瀏覽器裏面。一個好消息是 HTML5 裏面不只加入了 File API,並且加入了對拖拽的支持,Firefox 3.5 開始已經對 File API 和拖拽提供了支持。下面咱們先簡要介紹一下拖拽的使用,而後用一個例子來講明如何經過拖拽上傳文件。
拖拽通常涉及兩個對象:拖拽源和拖拽目標。
拖拽源:在 HTML5 草案裏若是一個對象能夠做爲源被拖拽,須要設置 draggable 屬性爲 true 來標識該對象可做爲拖拽源。而後偵聽源對象的 dragstart 事件,在事件處理函數裏設置好 DataTransfer。在 DataTransfer 裏能夠設置拖拽數據的類型和值。好比是純文本的值,能夠設置類型爲"text/plain",url 則把類型設置爲"text/uri-list"。 這樣,目標對象就能夠根據指望的類型來選擇數據了。
拖拽目標:一個拖拽目標必須偵聽 3 個事件。
dragenter:目標對象經過響應這個事件來肯定是否接收拖拽。若是接收則須要取消這個事件,中止時間的繼續傳播。
dragover:經過響應這個事件來顯示拖拽的提示效果。
drop:目標對象經過響應這個事件來處理拖拽數據。在下面的例子裏咱們將在 drop 事件的處理函數裏獲取 DataTransfer 對象,取出要上傳的文件。
因爲本文主要介紹 File API,對這部分不做詳細解釋,感興趣的讀者能夠參考 HTML5 草案(見參考資料)。
下面以一個較爲具體的例子說明如何結合拖拽和 File API 來上傳文檔。因爲直接和桌面交互,因此咱們不須要處理拖拽源,直接在目標對象裏從 DataTransfer 對象獲取數據便可。
首先,咱們須要建立一個目標容器用來接收拖拽事件,添加一個 div 元素便可。而後用一個列表來展現上傳文件的縮略圖,進度條及文件名。參見清單 8 的 HTML 代碼和圖 3 的效果圖。詳細代碼請參見附件中的 dnd.html 文件。
<div id="container"> <span>Drag and drop files here to upload.</span> <ul id="fileList"></ul> </div>
拖拽目標建立好以後,咱們須要偵聽其對應的事件 dragenter,dragover 和 drop。在 dragenter 事件處理函數裏,咱們只是簡單地清除文件列表,而後取消 dragenter 事件的傳播,表示咱們接收該事件。更加穩當的做法是判斷 DataTransfer 裏的數據是否爲文件,這裏咱們假設全部拖拽源都是文件。dragover 事件裏咱們取消該事件,使用默認的拖拽顯示效果。在 drop 事件裏咱們註冊了 handleDrop 事件處理函數來獲取文件信息並上傳文件。清單 9 展現了這些事件處理函數。
function addDNDListeners() { var container = document.getElementById("container"); var fileList = document.getElementById("fileList"); // 拖拽進入目標對象時觸發 container.addEventListener("dragenter", function(event) { fileList.innerHTML =''; event.stopPropagation(); event.preventDefault(); }, false); // 拖拽在目標對象上時觸發 container.addEventListener("dragover", function(event) { event.stopPropagation(); event.preventDefault(); }, false); // 拖拽結束時觸發 container.addEventListener("drop", handleDrop, false); } window.addEventListener("load", addDNDListeners, false);
用戶在拖拽結束時鬆開鼠標觸發 drop 事件。在 drop 事件裏,咱們能夠經過 event 參數的 DataTransfer 對象獲取 files 數據,經過遍歷 files 數組能夠獲取每一個文件的信息。而後針對每一個文件,建立 HTML 元素來顯示縮略圖,進度條和文件名稱。File 對象的 getAsDataURL 能夠將文件內容以 URL 的形式返回,對圖片文件來講能夠用來顯示縮略圖。要注意的一點是,在 drop 事件處理函數裏要取消事件的繼續傳播和默認處理函數,結束 drop 事件的處理。清單 10 展現了 drop 事件的處理代碼。
function handleDrop(event) { // 獲取拖拽的文件列表 var files = event.dataTransfer.files; event.stopPropagation(); event.preventDefault(); var fileList = document.getElementById("fileList"); // 展現文件縮略圖,文件名和上傳進度,上傳文件 for (var i = 0; i < files.length; i++) { var file = files[i]; var li = document.createElement('li'); var progressbar = document.createElement('div'); var img = document.createElement('img'); var name = document.createElement('span'); progressbar.className = "progressBar"; img.src = files[i].getAsDataURL(); img.width = 32; img.height = 32; name.innerHTML = file.name; li.appendChild(img); li.appendChild(name); li.appendChild(progressbar); fileList.appendChild(li); uploadFile(file, progressbar) } }
咱們能夠經過 XMLHttpRequest 對象的 sendAsBinary 方法來上傳文件,經過偵聽 upload 的 progress,load 和 error 事件來監測文件上傳的進度,成功完成或是否發生錯誤。在 progress 事件處理函數裏咱們經過計算已經上傳的比例來肯定進度條的位置。參見清單 11。圖 4 展現了上傳文件的效果圖。
function uploadFile(file, progressbar) { var xhr = new XMLHttpRequest(); var upload = xhr.upload; var p = document.createElement('p'); p.textContent = "0%"; progressbar.appendChild(p); upload.progressbar = progressbar; // 設置上傳文件相關的事件處理函數 upload.addEventListener("progress", uploadProgress, false); upload.addEventListener("load", uploadSucceed, false); upload.addEventListener("error", uploadError, false); // 上傳文件 xhr.open("POST", "upload.jsp?fileName="+file.name); xhr.overrideMimeType("application/octet-stream"); xhr.sendAsBinary(file.getAsBinary()); } function uploadProgress(event) { if (event.lengthComputable) { // 將進度換算成百分比 var percentage = Math.round((event.loaded * 100) / event.total); console.log("percentage:" + percentage); if (percentage < 100) { event.target.progressbar.firstChild.style.width = (percentage*2) + "px"; event.target.progressbar.firstChild.textContent = percentage + "%"; } } } function uploadSucceed(event) { event.target.progressbar.firstChild.style.width = "200px"; event.target.progressbar.firstChild.textContent = "100%"; } function uploadError(error) { alert("error: " + error); }
本文經過對 File API 規範的講解,以及兩個展現其使用方法的例子,爲你們提早揭示了做爲將來 HTML5 重要組成部分的 JavaScript File API 的全貌。利用它,結合其餘 HTML5 的新特性,好比 Drag&Drop,咱們能夠利用純 JavaScript 方案,爲用戶提供更好使用體驗的 Web 應用,與此同時,這樣的一致化方案也使咱們避免了以往跨瀏覽器支持所花費的巨大代價。相信 File API 的出現和普遍應用,將會是將來的 Web 2.0 應用的大勢所趨。