jQuery File Upload是一個非常優秀的上傳組件,主要使用了XHR作爲上傳方式,並且利用了相當多的現代瀏覽器功能,所以可以實現諸如批量上傳、超大文件上傳、圖片預覽、拖拽上傳、上傳進度顯示、跨域上傳等功能。
美中不足的是jQuery File Upload的默認UI比較複雜,集成了全部功能,讓jQuery File Upload的定製變得比較繁瑣。
嘗試用jQuery File Upload製作了一個類似微博圖片上傳的單文件式上傳Demo,將一些要點記錄下來備忘。最終效果如下圖:
jQuery File Upload的最簡模型
jQuery File Upload包含了一堆文件,首先需要弄清楚的是最核心的部分是哪些,根據官方的例子可以知道,一個最簡單的jQuery File Upload上傳組件,必須包括以下文件:
- jQuery核心庫,建議使用jQuery 1.8以上版本
- js/vendor/jquery.ui.widget.js : jQuery UI Widget
- js/jquery.iframe-transport.js : 擴展iframe數據傳輸
- js/jquery.fileupload.js : jQuery File Upload核心類
- js/cors/jquery.xdr-transport.js 在IE下應載入此文件解決跨域問題
此時只需要加載一個上傳按鈕
<input id="fileupload" type="file" name="files[]" data-url="server/php/" multiple>
以及一行代碼
$('#fileupload').fileupload();
就完成了一個最基本的上傳組件。這個最簡單的上傳組件可以將選中的文件以表單形式提交到data-url約定的URL,同時提供了足夠多的設置和基礎事件可供擴展。
jQuery File Upload的簡單擴展
對於最簡模型,稍加擴展就可以實現一些比較常用的功能,比如可以在上傳完畢後可以顯示一個簡單的結果:
$('#fileupload').fileupload({
done: function (e, data) {
$.each(data.result, function (index, file) {
$('<p/>').text(file.name + ' uploaded').appendTo($("body"));
});
}
});
或者顯示上傳進度,配合一些進度條組件就可以構成一個上傳進度條
$('#fileupload').fileupload('option', {
progressall: function (e, data) {
var progress = parseInt(data.loaded / data.total * 100, 10);
console.log(progress + '%');
}
});
等等。只要多閱讀手冊就可以配合項目做更具體的擴展開發。
XHR響應爲Json時IE的下載BUG
這裏需要特別注意的是,由於jQuery File Upload都是採用XHR在傳遞數據,服務器端返回的通常是JSON格式的響應,但是IE會將這些JSON響應誤認爲是文件傳輸,然後直接彈出下載框詢問是否需要下載。
解決這個問題的方法是必須將相應的Http Head從
Content-Type: application/json
更改爲
Content-Type: text/plain
具體的實現根據服務端不同有所區別,比如ZF2中可以在Controller中這樣寫:
$this->getServiceLocator()->get('Application')->getEventManager()->attach(\Zend\Mvc\MvcEvent::EVENT_RENDER, function($event){
$event->getResponse()->getHeaders()->addHeaderLine('Content-Type', 'text/plain');
}, -10000);
這也是我在stackoverflow上的對ZF2更改最終響應類型的一個回答
jQuery File Upload UI的構成與說明
爲了引入更多功能,jQuery File Upload在上面最簡模型的基礎上又實現了一套jQuery File Upload UI,也就是官方給出的最終Demo,這套UI額外提供了以下功能:
- 最大/最小文件限定 Options.maxFileSize / Options.mixFileSize
- 文件類型限定,通過正則表達式檢測文件名實現 Options.acceptFileTypes
- 選擇文件後自動上傳 Options.autoUpload
- 上傳文件數量限制,通過上傳後將選擇文件按鈕置爲Disabled實現 Options.maxNumberOfFiles
- 上傳模板,就是選擇文件後顯示預覽的html代碼 Options.uploadTemplate
- 下載模板,當文件上傳完畢後顯示的html代碼 Options.downloadTemplate
等等,同時還增加了一系列新的接口和事件,具體都可以查閱官方手冊。
具體對應到文件爲:
- JavaScript-Templates : JS模板引擎
- JavaScript-Load-Image : 圖片預覽功能
- js/jquery.fileupload-ui.js & css/jquery.fileupload-ui.css : UI核心類,CSS可以替換舊式的上傳控件爲統一的按鈕
- js/jquery.fileupload-fp.js:進度條擴展功能
也許正是因爲附加功能太多,各功能之間耦合非常重,jQuery File Upload UI顯得不夠友好,主要體現在:
- 上述功能均無法拆分,必須統一全部加載
- 各功能需要界面存在相應元素,如果缺少某些元素,包括JS模板內的元素,整個UI無法正常工作
- JS模板引擎對標籤配對非常嚴格,標籤如果遺漏也有可能引起UI無法正常工作
所以經驗之談是,在定製jQuery File Upload UI時,如果UI無法工作。首先檢查js文件是否全部加載,然後檢查頁面元素是否齊全,再次檢查JS模板標籤是否嚴格配對,最後還可以查看頁面是否有重複調用fileupload()方法。
jQuery File Upload UI構成元素
UI的部件都是硬編碼的HTML class,無法更改。核心的幾個部件爲
全局控制按鈕 (必須)
<div class="fileupload-buttonbar">
<span class="fileinput-button"><input type="file" name="files[]" multiple></span>
<button type="submit" class="start">Start upload</button>
<button type="reset" class="cancel">Cancel upload</button>
<button type="button" class="delete">Delete</button>
<input type="checkbox" class="toggle">
</div>
最外層容器爲.fileupload-buttonbar,內部包含
- 文件選擇按鈕 .fileinput-button (必須),內部必須包裹一個input:file
- 開始上傳按鈕 .start
- 取消上傳按鈕 .cancel
- 刪除按鈕 .delete
- 文件勾選按鈕 .toggle
整體上傳進度 (可選)
<div class="fileupload-progress">
<div class="progress">
<div class="bar" style="width:0%;"></div>
</div>
<div class="progress-extended"></div>
</div>
最外層容器爲.fileupload-progress,內部包含
- 上傳進度條容器.progress
- 上傳進度條 .bar
- 上傳進度文本 .progress-extended
文件顯示容器 (必須)
<div class="files"></div>
.file容器是最重要的UI部件,上傳時的文件預覽模板以及上傳完畢後的文件顯示模板都將顯示在這裏。
文件預覽模板 (必須)
<script id="template-upload" type="text/x-tmpl">
{% for (var i=0, file; file=o.files[i]; i++) { %}
<div class="template-upload">
{% if (file.error) { %}
<div class="error">{%=file.error%}</div>
{% } else { %}
<div class="preview"><span class="fade"></span></div>
<div class="name"><span>{%=file.name%}</span></div>
<div class="size"><span>{%=o.formatFileSize(file.size)%}</span></div>
<div class="progress progress-success progress-striped active" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0" style="height:5px;"><div class="bar" style="width:0%;"></div></div>
<span class="start">
{% if (!o.options.autoUpload) { %}
<button>Start Upload</button>
{% } %}
</span>
{% } %}
<span class="cancel"><button>Cancel</button></span>
</div>
{% } %}
</script>
這部分邏輯不難讀懂,由於文件選擇是多選的,所以被選擇文件一開始以數組方式存放,循環輸出。即使我們加入最大文件只能上傳一個,這裏得到的仍然是數組形式。
當文件有任何錯誤時,如文件類型被禁止,文件大小不符合約定,會得到file.error。文件檢測沒有問題,則可以用以下元素控制當前文件:
- 開始上傳當前文件按鈕.start (必須)
- 取消上傳當前文件按鈕.cancel (可選)
- 當前文件上傳進度.progress (可選)
上傳後文件回調顯示模板 (必須)
<script id="template-download" type="text/x-tmpl">
{% for (var i=0, file; file=o.files[i]; i++) { %}
<div class="template-download">
{% if (file.error) { %}
<div class="error">{%=file.error%}</div>
<span class="cancel"><button class="btn btn-block"><i class="icon-ban-circle"></i>Cancel</span>
{% } else { %}
<div class="preview"><img src="http://blog.163.com/lgh_2002/{%=file.thumbnail_url%}"></div>
<div class="name"><span>{%=file.name%}</span></div>
<div class="size"><span>{%=o.formatFileSize(file.size)%}</span></div>
<div class="delete"><button data-type="{%=file.delete_type%}" data-url="{%=file.delete_url%}">Delete</button>
</div>
{% } %}
</div>
{% } %}
</script>
這一部分的o.files完全來自服務器端的json響應,所以模板內容可以自由發揮。唯一被定製的元素爲刪除按鈕.delete。 點擊這個按鈕會向按鈕中指定的url發送請求,比如
<div class="delete"><button data-type="DELETE" data-url="/file/1">Delete</button></div>
點擊後則會用DELETE方式發送HTTP請求
DELETE /file/1
jQuery File Upload UI工作流程
有了上面羅列的UI元素,就可以拼湊出一個簡單的jQuery File Upload UI工作流程:
- 用戶點擊.fileinput-button選擇要上傳的文件(多個)
- 文件選擇後,文件信息被整理爲數組置入文件預覽模板#template-upload
- 模板引擎循環處理文件信息並生成模板.template-upload
- 每生成一個模板,模板就被插入到文件顯示容器.files的最後。
- 用戶點擊上傳按鈕.start上傳,文件信息被轉換爲XHR請求至服務器端
- UI獲得服務器端生成JSON響應文件
- JSON響應信息也被整理成數組置入回調顯示模板#template-download
- 模板引擎循環處理文件信息並生成模板.template-download
- 每生成一個模板,會將此模板替換對應的.template-upload部分
定製過程
有了上面的基礎,要個性化的定製jQuery File Upload就簡單了很多:
限制文件類型
由於沒有使用Flash空間,上傳的文件選擇框是無法限制文件類型的,所以所謂的限制文件類型,只能讓用戶選擇文件之後,用file.error顯示一個錯誤信息。例如本次需要限定可上傳的文件爲圖片,那麼Options指定:
acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i
即可。
在Google Chrome瀏覽器中,可以用input:file原生支持文件類型限定,可以配合使用:
<input type="file" name="upload[]" accept="image/png, image/gif, image/jpg, image/jpeg">
不過在客戶端做再多的限定也只是提升用戶體驗,不能真正保證安全性,所以不要忘記了在服務器端做同樣的類型檢測。
文件數量限制
只需在Options指定
maxNumberOfFiles : 1
即可。jQuery File Upload UI的處理方式是當用戶上傳一個文件後,文件選擇按鈕被置爲Disabled。
這同樣只是客戶端的小把戲,真正想要嚴格的約束用戶只能上傳一個文件還是需要在服務器端通過Session做更加複雜的控制。
文件大小限制
Options中指定
maxFileSize: 5000000
即只允許單文件最大5MB。
Firefox disable bug
在Firefox環境下測試是,發現如果將文件數量限制爲1,選擇一次文件,刷新頁面之後文件選擇按鈕會莫名其妙的被加上一個Disabled屬性,導致無法點擊。所以最終我們的初始化代碼爲:
var uploader = $("#fileupload");
uploader.fileupload({
dataType: 'json',
autoUpload: false,
acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i,
maxNumberOfFiles : 1,
maxFileSize: 5000000
});
uploader.find("input:file").removeAttr('disabled');