[HTML5] HTML5 File API

雖說接觸 HTML5 已經一段時間了,但真正讓我使用到 HTML5 特殊功能的源頭,還是檔案上傳,畢竟檔案上傳這件事每個 Web 應用程式幾乎都有,而且多半是單檔上傳,多檔的話只能用 Flash 或 Silverlight 寫檔案上傳元件配合 JavaScript 來做,但到了 HTML5,Flash 和 Silverlight 大概就只能靠邊站了,因為 HTML5 不愧是號稱可以打趴 Flash 的新技術,幾乎 90% 以上 Flash 可做到的功能,在 HTML5 上都可以做到...

雖說接觸 HTML5 已經一段時間了,但真正讓我使用到 HTML5 特殊功能的源頭,還是檔案上傳,畢竟檔案上傳這件事每個 Web 應用程式幾乎都有,而且多半是單檔上傳,多檔的話只能用 Flash 或 Silverlight 寫檔案上傳元件配合 JavaScript 來做,但到了 HTML5,Flash 和 Silverlight 大概就只能靠邊站了,因為 HTML5 不愧是號稱可以打趴 Flash 的新技術,幾乎 90% 以上 Flash 可做到的功能,在 HTML5 上都可以做到。

File API 在 HTML5 中屬於基礎功能之一,它可以在本地端讀入二進位檔案,並且轉換成 Data URI (文字格式加上 base64 編碼字串的 URL,可參考黑大的這篇文章),只要變成了文字樣式,再配合 DOM 的處理,就能將它塞到 DIV,Hidden Field 或其他的地方 (ex: localstorage),也可以將它指定給 img 來顯示等等,用途很廣。不過目前在 W3C 的規範上,File API 還是屬於草案階段,說是這樣,各家瀏覽器早就先跑了,Chrome, Firefox 以及 IE10 都支援了 File API,所以未來在主流的瀏覽器上,File API 可以獲得各家的支持,能安心的使用它。

在介紹 File API 前,必須要先知道 <input type=”file” /> 有什麼改變,才能和 File API 連接起來,因為 File API 是基於 type=”file” 這個輸入控制項的改變。其實它的改變只有一個,就是多了 multiple 這個屬性,當我們在 HTML 中加入了:

<input type="file" multiple="multiple" id="MyFile" />

表示我們宣告了一個可允許選擇多檔案的功能,使用者可以選擇多個檔案,而讀入的檔案在 DOM 內會變成一個稱為 Blob 的項目,我們可以透過 file 的 DOM 物件來瀏覽檔案的清單,也就是下列程式:


$("input[type='file']").bind("change", function (event) {

    if (event.target.files.length > 0) {

       // get file properties.
    
    }

});

Blob 物件有三個屬性可以存取:

  • name,取得檔案名稱,如果需要做副檔名檢查,可利用它。
  • size,取得檔案大小 (bytes)。
  • type,取得檔案的 MIME 型別 (若無法對應會傳回空白)。

 

依照這樣的說明,我們可以寫出一個讀取使用者選擇的檔案的清單:


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>File API</title>
    <script language="javascript" src="http://code.jquery.com/jquery-1.7.2.min.js"></script>
    <script type="text/javascript">

        $(function () {

            $("input[type='file']").bind("change", function (event) {

                if (event.target.files.length > 0) {

                    for (var i = 0; i < event.target.files.length; i++) {

                        var file = event.target.files[i];

                        $("div[data-id='fileContainer'] > ul").append(
                            $("<li style='padding: 3px' />").html("檔名: " + file.name + "<br /> 大小: " + file.size + "<br /> 類型: " + file.type)
                        );

                    }
                }
                else {
                    $("div[data-id='fileContainer'] > ul").empty();
                }

            });

        });
    
    </script>
</head>
<body style="font-size: 12px;">
    Choose Files:<br />
    <div style="width: 700px; height: 300px; overflow: auto; border: 2px inset #000000;" data-id="fileContainer">
        <ul style="list-style: none; padding: 0px;">
        </ul>
    </div>
    <input type="file" multiple="multiple" data-id="fileUpload" />
    <br />
    <br />
    <input type="submit" value="Upload" />
</body>
</html>

執行結果為:

image

 

這還不夠,如果要上傳的話,我們還需要讀入檔案,這點也是 HTML5 File API 最厲害的地方,它可以將二進位檔案讀入,但基於安全的理由,讀入的資料會被轉成字串 (base64),而不會讓 JavaScript 去操作二進位資料。讀取資料的物件稱為 FileReader,擁有下列方法:

  • abort():取消讀取。
  • readAsArrayBuffer():讀取檔案,並傳回一個 array buffer 物件,這個物件由 JavaScript 支援。
  • readAsBinaryString():讀取檔案,並傳回一個 binary string 字串。
  • readAsDataURL():讀取檔案,並傳回一個 Data URI 格式的字串。
  • readAsText():讀取檔案,並傳回文字內容,可依傳入的編碼做處理。

 

請注意,在 FileReader 裡面,所有的讀取動作都是非同步的,所以在呼叫 read 方法之前,必須要先掛載一個 onload 的事件常式,以擷取讀取的結果,在事件參數中,使用 result 屬性來取得結果,例如:


$("input[type='file']").bind("change", function (event) {

    if (event.target.files.length > 0) {

        for (var i = 0; i < event.target.files.length; i++) {

            var file = event.target.files[i];
            var template = $($("div[data-id='template']").html());
            var reader = new FileReader();

            template.find("div[data-id='name']").html("檔名: " + file.name + "<br /> 大小: " + file.size + "<br /> 類型: " + file.type);
            template.find("div[data-id='status']").html("Reading....");

            reader.onload = (function (element) {

                return function (f) {
                    element.find("div[data-id='data']").text(f.target.result);
                    element.find("div[data-id='status']").text("Done.");
                };

            } (template));

            reader.readAsDataURL(file);

            $("div[data-id='fileContainer'] > ul").append(template);

        }
    }
    else {
        $("div[data-id='fileContainer'] > ul").empty();
    }

});

執行結果如下,由於是非同步的,所以我們可以直接顯示出讀檔的進度:

image

image

 

使用 readAsDataURL() 讀出的檔案內容是這樣:

data:application/vnd.openxmlformats-officedocument.presentationml.presentation
;base64,
UEsDBBQABgAIAAAAIQBa...

伺服端的部份則只要可以解出 base64 的資料就可以了,ASP.NET 可用 Convert.FromBase64String(),將 base64 的部份拆出來後直接轉,但要注意的是 Data URI 會將 “+” 變成空白字元 (傳輸時的相容性),所以在解碼前要先將所有的空白字元轉換成 “+”,否則會解碼失敗。

除了 onload 以外,FileReader 還有下列事件可用:

  • onabort:在呼叫 abort() 時引發。
  • onerror:在讀檔失敗時引發。
  • onloadend:在讀檔工作完成時引發,不論成功或失敗。
  • onloadstart:在讀檔開始時引發。
  • onprogress:在讀檔之間回報進度。

 

另外,FileReader 還有一個孿生物件 FileReaderSync,是同步的讀取器,使用它的話就不需要設定事件即可得到結果,但是缺點就是無法在讀取期間給使用者回應,所以盡可能的還是使用非同步的物件為宜。

 

註:HTML5 由於還不是最終規格,因此相同的物件會有瀏覽器相容性的問題。

 

Reference:

http://www.w3.org/TR/FileAPI/

https://developer.mozilla.org/en/DOM/FileReader