一、簡介
Uploadify是一個基於JQuery的多文件上傳JS組件,高度定制,兩個版本可供選擇。flash版本在最新的Safari等不再支持flash的瀏覽器或者一些手機瀏覽器中就無法正常的加載使用,因此推薦使用html5版本!
最近,想了解JavaScript的編碼規范,所以根據用過的Uploadifive作為參考,看如何組織js代碼以及如果開發jquery的擴展。
二、代碼結構概覽
如果從第一行開始讀代碼,會摸不着頭腦,所以先看一下他的整體布局。
(function($) {
var methods = {
//所有上傳用到的方法和屬性
init:function(options){},
debug : function() {},
clearQueue : function() {}//清理隊列
cancel : fucntion(file, fast){},//取消文件上傳
upload : function(file, keepVars) {},
destroy : function() {},
}
$.fn.uploadifive = function(method) {
if (methods[method]) {
return methods[method].apply(this,Array.prototype.slice.call(arguments, 1));
} else if (typeof method === 'object' || !method) {
return methods.init.apply(this, arguments);
} else {
$.error('The method ' + method + ' does not exist in $.uploadify');
}
}
})(jQuery);
知識點:
1.$.extend
為類級別的擴展,如:
//擴展
$.extend({
req:function(url){return true}
})
//使用
$.req('xxxx')
2.$.fn.extend
為對象級別的擴展,如:
//擴展
$.fn.extend({
validate:function(){return true}
})
//使用
$("#id").validate();
3.call
與apply
通常用於動態改變this,調用其他對象的方法,區別在於call參數逐個傳遞,apply將所有參數放入數組(arguments)行傳遞
三、解析
1.首先擴展中對傳入的method
參數進行判斷,如果存在這個方法就直接調用相應的methods對象中的方法,否則如果是個對象,就調用methods的init
初始化方法。
2.init 方法
var $this = $(this);//當前被綁定的input的context object
$this.data('uploadifive', {
// 這里初始化的是文件上傳隊列、多文件上傳、文件數量等記錄用的
//屬性
});
//暫存uploadifive 並保存在$data中
var $data = $this.data('uploadifive');
//將用戶綁定上傳組件傳入的參數與默認的參數進行合並
var settings = $data.settings = $.extend({},options);
//計算用戶傳入的配置參數fileSizeLimit
if (isNaN(settings.fileSizeLimit)) {
//如果有kb等單位,這里根據用戶輸入的是KB MB GB進行文件大小計算
} else {
//否則以kb為單位
settings.fileSizeLimit = settings.fileSizeLimit * 1024;
}
//創建type為file的input模板保存為$data的一個屬性,這個模板會在后面取代用戶頁面原有的input 作為一個可點擊的透明層存在
$data.inputTemplate = $('<input type="file">')
.css({
'font-size' : settings.height + 'px',
'opacity' : 0,
'position' : 'absolute',
'right' : '-3px',
'top' : '-3px',
'z-index' : 999
});
//接下來初始化一系列方法綁定到$data上
//創建一個新的input 會用到上面的inputTemplate
$data.createInput = function(){...}
//清除一input
$data.destroyInput = function(){...}
//拖動上傳方法
$data.drop = function(e) {...}
//檢查文件在隊列中是否存在方法
$data.fileExistsInQueue = function(file) {...}
//刪除隊列中一個已經存在文件
$data.removeExistingFile = function(file) {...}
//接下來初始化創建itemTemplate模板,這個模板就是開始上傳時,會有一個進度 條以及一個x點擊取消按鈕,保存至$data.queueItem中
if (settings.itemTemplate == false) {
$data.queueItem = $('模板div代碼');
} else {
$data.queueItem = $(settings.itemTemplate);
}
//添加文件到文件隊列中的方法初始化
$data.addQueueItem = function(file) {...}
//上傳隊列中移除其中一個,多文件上傳時候用到
$data.removeQueueItem = function(file, instant, delay) {...}
//計算待上傳文件的數量方法
$data.filesToUpload = function() {...}
//檢查文件是否存在的方法
$data.checkExists = function(file){...}
//真正實現文件流上傳的方法
$data.uploadFile = function(file, uploadAll) {...}
//更新文件上傳進度的方法
$data.progress = function(e, file) {...}
//錯誤信息觸發
$data.error = function(errorType, file, uploadAll) {}
//文件上傳完成后觸發的方法
$data.uploadComplete = function(e, file, uploadAll) {}
//全部文件上傳完成觸發的方法
$data.queueComplete = function() {}
截止到現在,上面的代碼都是方法的定義,模板的定義全部保存在$data中,頁面中還沒有任何的效果,接下來開始執行一些列跟頁面文件上傳展現相關初始化操作。
//判斷是否支持HTML5
if (window.File && window.FileList && window.Blob && (window.FileReader || window.FormData)) {
下面這部分的操作就是創建一個按鈕,設置基本的按鈕的樣式,如果用戶傳入了自定 義的設置參數,則相應的賦值。按鈕其實是在透明的input file上的一個div
settings.id = 'uploadifive-' + $this.attr('id');
// Wrap the file input in a div with overflow set to hidden
$data.button = $('<div id="' + settings.id + '" class="uploadifive-button">' + settings.buttonText + '</div>');
if (settings.buttonClass) $data.button.addClass(settings.buttonClass);
// Style the button wrapper
$data.button.css({
'height' : settings.height,
'line-height' : settings.height + 'px',
'overflow' : 'hidden',
'position' : 'relative',
'text-align' : 'center',
'width' : settings.width
});
// Insert the button above the file input
$this.before($data.button)
// Add the file input to the button
.appendTo($data.button) //將原有的input type=file 插入到div中並display none
// Modify the styles of the file input
.hide();
}
到現在按鈕可以顯示在頁面上了,但是上傳的操作還沒有初始化、綁定。接下來調用上面已經初始化好的createInput方法,創建一個新的input type file以便添加用戶的一些配置信息,如允許的文件類型,是否多文件上傳等。
$data.createInput.call($this);
createInput方法內容:
$data.createInput = function() {
//獲取之前初始化好的保存在$data中的input type file模板
var input = $data.inputTemplate.clone();
//設置input name multip accept等屬性
...
//對創建的input綁定change事件
input.bind('change', function() {
//初始化隊列記錄參數
...
//判斷是否超過了設置的上傳隊列數量限制,超過了則回調報錯,否則進行添加入隊列處理
if(超過了限制){
onError();
}else{
//遍歷files逐個添加到上傳隊列
for (var n = 0; n < limit; n++) {
file = this.files[n];
$data.addQueueItem(file);
}
$data.inputs[inputName] = this;
$data.createInput();//重新創建input並綁定change事件
}
}
//將綁定好事件的input插入到之前創建的button 就是div層里面
$data.button.append(input);
}
然后代碼回到$data.createInput.call($this);
的調用位置接着往下走,可以看到有一個是否設置了queueID的判斷,就是如果設置了queueID就直接創建一個div容器,這里面就是上傳時,上傳狀態進度等信息出現的地方,如果沒有,則默認在上傳按鈕下面創建一個有默認id和class的容器,暫時保存在$data.queueEl
中。
if (!settings.queueID) {
settings.queueID = settings.id + '-queue';
$data.queueEl = $('<div id="' + settings.queueID + '" class="uploadifive-queue" />');
$data.button.after($data.queueEl);
} else {
$data.queueEl = $('#' + settings.queueID);
}
uploadifive還支持拖動上傳,下面的代碼是綁定拖動上傳的監聽事件
if (settings.dnd) {
//dragleave
//dragenter
//dragover
//drop
}
初始化操作已經完成,等待上傳的觸發!
當點擊選擇圖片->選擇一個文件->確定上傳,則觸發了createInput中綁定的change事件,這里是一個過渡,只是做了隊列文件數量的驗證,然后遍歷文件對象調用$data.addQueueItem(file)
.
addQueueItem,添加file對象到隊列中,事實上多文件上傳是html5支持的,所以並不是uploadifive的特性。這個方法完成的事情包括:
- 判斷隊列中是否有重復文件,有則移除重復
- 創建上傳進度信息顯示,並綁定取消上傳的點擊事件
- file.queueItem.data('file', file); 暫存文件流,在后面真正的文件上傳時候要獲取文件流
if(onAddQueueItem方法 沒有被覆蓋){
完成上面所述的1,2,3操作
}
//如果用戶綁定時有onAddQueueItem則觸發回調
...
//文件大小判斷
...
執行完暫存的加入隊列的操作,回到createInput的綁定change事件內繼續執行,如果配置了自動上傳,調用upload方法。
if (settings.auto) {
methods.upload.call($this);
}
下面進入upload方法。upload方法首先拿到所有uploadifive的配置信息,然后判斷是否直接傳入了file文件流,是則調用真正的上傳方法uploadFile
if (file) {
$data.uploadFile.call($this, file);
} else {
if(沒有超過上傳文件文件數量限制){
//循環上傳隊列中沒有錯誤和沒有完成的文件
$('#' + settings.queueID).find('.uploadifive-queue-item').not('.error, .complete').each(function() {
/**
* $(this)代表的是當前的上傳隊列的對象,而在addQueeuItem中 有
* 這樣一句 file.queueItem.data('file', file);就是緩存了數據
* 所以這里可以直接訪問到暫存數據
*/
_file = $(this).data('file');
// Check if the simUpload limit was reached
//是否配置有重復文件檢查腳本鏈接checkScript參數,則check,否則調用真正的uploadFile方法
if (settings.checkScript) {
_file.checking = true;
skipFile = $data.checkExists(_file);
_file.checking = false;
if (!skipFile) {
$data.uploadFile(_file, true);
}
} else {
$data.uploadFile(_file, true);
}
});
if (隊列中文件都上傳完成) {
$data.queueComplete();
}
}
}
uploadFile中使用了兩種上傳方式,如果支持FormData則使用異步的ajax方式上傳,否則使用FileReader以二進制流的方式上傳。這種文件上傳的方式還是值得學習一下的。
//創建XMLHttpRequest對象
xhr = file.xhr = new XMLHttpRequest();
if(支持FormData){
//創建表單
xhr.open(settings.method, settings.uploadScript, true);
// On progress function
xhr.upload.addEventListener('progress', function(e) {
if (e.lengthComputable) {
//調用progress方法,progress方法就是計算上傳完成百分比
$data.progress(e, file);
}
}, false);
}else{
//以二進制流方法上傳
var reader = new FileReader();
...
}
最后上傳完成調用queueComplete
方法,整個流程走完了。
四、總結
簡化版的執行流程是:
綁定input,定制參數->初始化上傳方法、屬性->createInput創建上傳按鈕->綁定change事件->文件上傳觸發change->文件天加到隊列調用addQueueItem->上傳調用upload->upload必要驗證后調用真正的上傳方法uploadFile->上傳過程中調用progress計算進度->上傳完成調用queueComplete方法.
附粗陋流程圖: