Hopes

          Start Here..

           

          仿163網盤無刷新多文件上傳系統(tǒng)

          仿163網盤無刷新多文件上傳系統(tǒng)

          這個仿163網盤無刷新多文件上傳系統(tǒng),并沒有用使用.net的控件,完全的手工制作。前臺基本上是靜態(tài)的,跟后臺沒有關系,所以后臺用什么語言做都可以(后面有各個版本的實例下載)。
          本來覺得這個系統(tǒng)會很復雜,但把每個部分都分析清楚后,其實需要的技術并不高。不過當我把各個功能函數(shù)都整理好準備進行封裝時,卻發(fā)現(xiàn)要把程序封裝不是那么容易,因為程序跟html的耦合度太高。然后我逐步把程序中操作html相關的部分分離出來,首先把簡單的分離,接著是文件列表,然后是file控件,最后是一些提示性程序。經過幾次嘗試才把整個結構封裝好,現(xiàn)在程序結構應該算比較清晰,有什么不明白的地方歡迎留言。


          效果預覽

          上傳文件
          添加文件:
          文件路徑
          溫馨提示:最多可同時上傳 個文件,只允許上傳 文件。
               

          這里的預覽只是前臺的效果,要整個系統(tǒng)測試請下載完整實例。

           


          程序說明

          【無刷新上傳

          要實現(xiàn)文件上傳,form必須設置幾個屬性:
          1.action:設為要處理數(shù)據(jù)的頁面地址;
          2.method:設為"post";
          3.enctype/encoding:必須設為"multipart/form-data",這里要注意的是在ie中用js修改form的enctype屬性是沒有效果的,只能修改encoding;

          后面兩個屬性程序初始化時都有設置:

          this.Form.method = "post";
          this.Form.encoding = "multipart/form-data";


          要注意這里的無刷新不是ajax哦,而是利用“古老”的iframe。
          由于ajax提交數(shù)據(jù)必須先獲取數(shù)據(jù),而js(一般情況下)是不能操作客戶端文件,要獲取文件數(shù)據(jù)就更不用說了,所以只能用iframe來做。
          先說說iframe實現(xiàn)無刷新上傳的原理:利用form的target屬性,把數(shù)據(jù)提交到頁面中一個(通常為隱藏的)iframe上直觀點說就是把“刷新”留給iframe。
          其實原理跟一般用iframe實現(xiàn)無刷新提交表單是一樣的,只是這里換成是文件。
          這里關鍵就是把form的target設為iframe的name:

          this.Form.target = this._FrameName;


          【iframe

          如果沒有自定義iframe,程序在初始化時會自動創(chuàng)建無刷新所需的iframe的。
          首先必須選擇一個iframe名,這在無刷新時是必須的,為了每個實例能創(chuàng)建各自的iframe,這里用了一個隨機數(shù):

          this._FrameName = "uploadFrame_" + Math.floor(Math.random() * 1000);


          也可以用一個遞增的計算器來代替隨機數(shù)。

          接著創(chuàng)建iframe,本以為用document.createElement("iframe")創(chuàng)建再設置它的name屬性就行了。
          卻發(fā)現(xiàn)這樣設置的name在ie居然不認(有說name是只讀屬性),還好在網上找到一個方法:“IE 創(chuàng)建元素,還有一個特點,就是可以連同屬性一同創(chuàng)建”。
          例如我想給動態(tài)創(chuàng)建的iframe設置name,可以這樣:

          document.createElement("<iframe name=\"" + this._FrameName + "\">")

          不過這個方式在ff會報錯:
          uncaught exception: String contains an invalid character (NS_ERROR_DOM_INVALID_CHARACTER_ERR)
          估計是用createElement時不能帶name,標準應該也是這樣,所以兼容的方式這樣寫:


          var oFrame = isIE ? document.createElement("<iframe name=\"" + this._FrameName + "\">") : document.createElement("iframe");
          //為ff設置name
          oFrame.name = this._FrameName;
          oFrame.style.display 
          = "none";

          關于這方面更詳細的內容請看這里

          創(chuàng)建完還需要插入到body中,一般的做法是使用document.body.appendChild,但在ie中會有“已終止操作”錯誤,可以用下面這段代碼測試:


          <body>
          <div>
          <script>
          document.body.appendChild(document.createElement(
          "div"));
          </script>
          </div>
          </body>

          網上找到一個解析:“原來FF下的實現(xiàn)機制是當頁面還沒有完全讀取完時body元素就已經存在了,而IE只有頁面完全讀取結束body元素才會存在,所以在頁面中插入上面這條語句在IE下就會出現(xiàn)錯誤”。
          我在web開發(fā)未解之謎中也提到了這個現(xiàn)象,我這里使用了insertBefore代替:

          document.body.insertBefore(oFrame, document.body.childNodes[0]);

          在服務器端文件傳送完(或失敗)之后,怎么通知客戶端呢?
          這里說說我的方法,首先我在客戶端定義一個函數(shù):

          function Finish(msg){ alert(msg); location.href = location.href; }

          很簡單,就是顯示提示并重新加載頁面(如果使用reload會導致ff中iframe重復加載數(shù)據(jù))。
          那服務器端如何通知客戶端的問題,就是iframe如何跟主頁面交互。
          答案是通過window.parent或window.top,在iframe中parent和top屬性“分別返回立即父窗口和最上層的祖先窗口”。
          例如我在服務器端處理完數(shù)據(jù)之后會輸出:

          context.Response.Write("<script>window.parent.Finish('" + _msg + "');</script>");

          就會執(zhí)行主頁面的Finish函數(shù)了。


          【多文件上傳】

          對于多文件上傳,這里的目的是如何做到163網盤那樣,只用一個file控件就實現(xiàn)多文件上傳。
          這里參考了163網盤的思路,下面說說如何實現(xiàn):
          首先必須有一個文件空間(我自己定的名字),例如程序中的"idFile"對象,這個空間不需要內容甚至一個div就可以,主要是用來存放file控件,程序中Folder屬性就是這個文件空間對象。
          ps:這里的要求是把file控件都控制在文件空間里,即使不是單file控件的情況。
          再說說Files屬性,這個屬性放的是file控件集合,方便獲取file控件,在下面“文件列表”就會用到。

          處理這些file控件的程序主要在Ini函數(shù)中:
          首先是處理文件空間中的file控件:


          this.Files = [];
          //整理文件空間,把有值的file放入文件集合
          Each(this.Folder.getElementsByTagName("input"), Bind(thisfunction(o){
              
          if(o.type == "file"){ o.value && this.Files.push(o); this.onIniFile(o); }
          }))

          可以看到這里主要是把file控件放入到Files中,并執(zhí)行附加函數(shù)onIniFile,我是這樣定義這個函數(shù)的:

          onIniFile: function(file){ file.value ? file.style.display = "none" : this.Folder.removeChild(file); }

          這里為了實現(xiàn)單file控件,把原來有值的file都隱藏了,還有那個“單file控件”呢?
          別急,接著就在文件空間插入一個新的file控件:


          var file = document.createElement("input");
          file.name 
          = this.FileName; file.type = "file"; file.onchange = Bind(thisfunction(){ this.Check(file); this.Ini(); });
          this.Folder.appendChild(file);

          可以看到file控件的name是FileName屬性的值,默認是空的,如果服務器端需要這個name的話就可以設置。
          這里可以看到每個file控件都有onchange來執(zhí)行檢測函數(shù)Check,這樣每次選擇文件后都會用Check檢測一次,這里說說這個Check函數(shù):


          //檢測變量
          var bCheck = true;
          //進行空值、文件數(shù)、后綴名、同值檢測
          if(!file.value){
              bCheck 
          = falsethis.onEmpty();
          else if(this.Limit && this.Files.length >= this.Limit){
              bCheck 
          = falsethis.onLimite();
          else if(!!this.ExtIn.length && !RegExp("\.(" + this.ExtIn.join("|"+ ")$""i").test(file.value)){
              
          //檢測是否允許后綴名
              bCheck = falsethis.onNotExtIn();
          else if(!!this.ExtOut.length && RegExp("\.(" + this.ExtOut.join("|"+ ")$""i").test(file.value)) {
              
          //檢測是否禁止后綴名
              bCheck = falsethis.onExtOut();
          else if(!!this.Distinct) {
              Each(
          this.Files, function(o){ if(o.value == file.value){ bCheck = false; } })
              
          if(!bCheck){ this.onSame(); }
          }

          里面有一個檢測變量bCheck,然后進行空值、文件數(shù)限制、后綴名、相同文件的檢測,當其中一個步驟不通過bCheck就會設為false,一個常用的檢測結構。
          這里說說檢測后綴名,由于js不能像后臺那樣獲取文件的文件類型,所以只能根據(jù)后綴名來判斷,例如用正則判斷:

          /\.(jpg|gif)$/i.test(file.value)

          這樣判斷顯然是不夠的,所以如果要做文件類型判斷的話一定要在后臺用ContentType再判斷一次。
          最后如果沒有通過檢測就會執(zhí)行onFail函數(shù):

          !bCheck && this.onFail(file);

          我在onFail函數(shù)中設定了移除沒有通過檢測的file控件:

          onFail: function(file){ this.Folder.removeChild(file); }


          這樣就基本實現(xiàn)(正確的說是模擬)了單file控件上傳多個文件的效果了。


          【文件列表】

          在上面的Ini函數(shù)中,最后執(zhí)行了一個附加函數(shù)onIni,這個函數(shù)是用戶自己定義的,我就在這個函數(shù)中添加文件列表。

          在之前先說說添加文件列表的函數(shù)AddList,這個函數(shù)是用來把file控件的值列在一個table里面。
          函數(shù)的參數(shù)是一個二維數(shù)組,其中第一維是行(tr),第二維是列(td)。
          首先獲取列表對象FileList,再定義一個文檔碎片oFragment來操作dom:

          var FileList = $("idFileList"), oFragment = document.createDocumentFragment();

          然后用兩個Each把二維數(shù)組插入到文檔碎片中:

          Code

          其中用了一個判斷if(typeof o == "string"),如果是文本就直接用innerHTML插入td,如果不是文本(這里不是文本就是一個對象)就用appendChild插入到td。
          當數(shù)據(jù)都插入到文檔碎片,就準備把文檔碎片插入到FileList中,不過還有一個步驟就是清空FileList中原有的數(shù)據(jù)。
          本來把innerHTML設為空來清空FileList會更有效率,但ie的table中只有td支持innerHTML,所以只好用removeChild來清空:

          while(FileList.hasChildNodes()){ FileList.removeChild(FileList.firstChild); }

          之后就可以把文檔碎片插入了:

          FileList.appendChild(oFragment);


          繼續(xù)看onIni函數(shù),現(xiàn)在只需要把要顯示的數(shù)據(jù)組成一個二維數(shù)組,再用AddList就能顯示文件列表了,這時存放file控件集合的Files屬性就大有用處了。
          首先定義一個放顯示數(shù)據(jù)的數(shù)組:

          var arrRows = [];

          然后根據(jù)Files對這個數(shù)組賦值:

          Code

          當Files沒有控件時只是輸出“沒有添加文件”,有控件時就會把每個file控件的要顯示數(shù)據(jù)放到一個數(shù)組中,可以看到這個數(shù)組其實就是td內容的集合,接著把這個數(shù)組加入到arrRows中形成二維數(shù)組,最后把得到的arrRows給AddRow函數(shù)顯示數(shù)據(jù)就行了。
          為了能取消指定的file控件,這里插入了一個a來觸發(fā)刪除函數(shù)Delete,這里也有一個技巧,這里把href設為"javascript:void(0);",并在onclick中返回false,這樣能最大程度的實現(xiàn)僅僅執(zhí)行js而不去跳轉。

          在表單提交時也要重新顯示文件列表,表單提交后就不允許刪除文件了,只顯示文件路徑就行了:

          Code

          說到表單提交要注意一個問題,就是表單是不能嵌套的,最好是把表單放到服務器表單之外,沒有辦法才使用服務器表單作為提交表單(由于程序會修改提交表單的屬性,所以盡量不要這樣使用)。

          這樣文件列表就完成了,有興趣的話也可以自己封裝一下這個功能。


          【file樣式】

          到此,程序的功能都已經實現(xiàn)了,但在163網盤中還有一個特別的地方,就是file控件的樣式。
          如果有用過163網盤上傳文件,就知道那個file控件就像一個按鈕,但功能確實是一個file控件。
          但當自己嘗試修改file控件的樣式時,發(fā)現(xiàn)單單設置file控件的樣式并不能實現(xiàn)想要的效果。
          于是我想了另一個辦法,用一個button來模擬,結果發(fā)現(xiàn)也不行,用js根本操作不了file控件,應該是考慮到安全問題吧。
          最后是參考了163網盤和muxrwc模擬126附件添加的效果,總結了這個方法:
          1.指定用一個容器(例如程序中的idFile)。
          容器最好指定高和寬,并且overflow為hidden,不是塊級元素的最好設display為block(為了高和寬的正確呈現(xiàn));
          2.在容器里放一個file控件,并設置樣式,使能觸發(fā)彈出選擇文件框的部分覆蓋整個容器,并設置成全透明。
          容器指定準確的高和寬就是為了能通過file控件中不多的能設置的樣式來覆蓋整個容器;
          3.現(xiàn)在已經把容器模擬成file控件了,可以直接設置容器的樣式來模擬設置file控件的樣式了。

          在程序中主要用file控件的margin-left和font-size來實現(xiàn)覆蓋整個容器:

          Code

          至于容器,我使用了有偽類hover的a元素(雖然CSS2中hover可以應用于任何對象,但ie6不支持)。
          這里用了一個常用的小技巧,就是用一張圖片作為背景通過在hover時修改background-position來實現(xiàn)兩張圖片的效果:

          Code

          在點擊這個a時后會出現(xiàn)一個虛線框,在這里顯然不太美觀,可以把outline設為none來去掉,可是ie又不支持,在網上找到一個方法ie可以把hideFocus設為true來隱藏聚焦(即不顯示這個虛線框,hideFocus可以在js或html中設置,也可以通過expression放到css中:

          Code


          這樣完全模擬了163網盤的效果了。


          【后臺】

          前臺基本完成了,就到后臺啦。后臺的功能很簡單,就是處理傳遞過來的文件數(shù)據(jù)。
          這里像js + .Net 圖片切割系統(tǒng)那樣使用ashx文件處理IHttpHandler發(fā)送過來的數(shù)據(jù)。
          程序很簡單,就直接貼代碼了:

          Code

          這里只檢測了有無文件和文件數(shù)限制,其他檢測如文件大小等可以自己擴展,應該不難。
          處理完數(shù)據(jù)之后就通知客戶端:

          context.Response.Write("<script>window.parent.Finish('" + _msg + "');</script>");

          這個在上面iframe的內容中已經說明了。


          使用說明

          基本使用很簡單,實例化一個file對象,其中參數(shù)分別是form對象,文件空間對象:

          new FileUpload("uploadForm""idFile")

          這樣就實現(xiàn)了一個簡單的無刷新上傳文件表單。

          還可以使用這幾個屬性:
          Form//表單
          Folder//文件控件存放空間
          Files//文件集合

          更多的功能可以選擇設置這些屬性:
          屬性名:默認值//說明
          FileName:"",//文件上傳控件的name,配合后臺使用
          FrameName:"",//iframe的name,要自定義iframe的話這里設置name
          onIniFile:function(){},//整理文件時執(zhí)行(其中參數(shù)是file對象)
          onEmpty:function(){},//文件空值時執(zhí)行
          Limit:0,//文件數(shù)限制,0為不限制
          onLimite:function(){},//超過文件數(shù)限制時執(zhí)行
          Distinct:true,//是否不允許相同文件
          onSame:function(){},//有相同文件時執(zhí)行
          ExtIn:[],//允許后綴名
          onNotExtIn:function(){},//不是允許后綴名時執(zhí)行
          ExtOut:[],//禁止后綴名,當設置了ExtIn則ExtOut無效
          onExtOut:function(){},//是禁止后綴名時執(zhí)行
          onFail:function(){},//文件不通過檢測時執(zhí)行(其中參數(shù)是file對象)
          onIni:function(){}//重置時執(zhí)行

          使用方法可以參考實例。

          程序中提供了下面幾個方法:
          Ini 整理空間
          Check 檢測file對象
          Delete 刪除指定file
          Clear 刪除全部file


          程序代碼

          Code


          【asp版本補充】

          由于很多人問我asp版本的后臺該如何寫,所以決定寫一個給大家。
          這里我用了化境HTTP上傳程序2.1版(應該是最新版了)的無組件上傳類,但用的時候發(fā)現(xiàn)幾個問題(不知是我不會用還是asp本身的問題):
          1,當file控件的name是空時,后臺會找不到文件;
          2,文件名比較短時(例如我用"f"),后臺也找不到文件;
          3,當有多個file控件,如果使用相同的name,后臺只會保存一個文件;
          4,我在上傳文件后輸出的中文是亂碼(有時又正常)。

          針對前3條,我加了一個RanName屬性,設為true的話會自動生成隨機的file控件名,對于第4條,我發(fā)現(xiàn)如果字是直接寫在文檔上就不會亂碼,所以我這里把輸出的文字都直接寫在文檔上沒有用變量。如果有兄弟知道怎么解決這些問題記得告訴我哦。


          下載完整測試代碼(.net)

          下載完整測試代碼(asp)

          感謝由csdn網友mengshan1986提供的php和jsp版,klniuer的php修正版:
          下載完整測試代碼(php)
          下載完整測試代碼(jsp)

          ps:請注意程序中的文件保存路徑,很多人的錯誤都是沒有設置好文件保存路徑。

          其他上傳系統(tǒng):

          簡便無刷新文件上傳系統(tǒng)

          轉載請注明出處:http://www.cnblogs.com/cloudgamer/

          如有任何建議或疑問,歡迎留言討論。

          如果覺得文章不錯的話,歡迎點一下右下角的推薦。

          程序中包含的js工具庫CJL.0.1.min.js,原文在這里

          分類: AspwebJavascriptAsp.net

          posted on 2012-08-19 20:39 ** 閱讀(220) 評論(0)  編輯  收藏


          只有注冊用戶登錄后才能發(fā)表評論。


          網站導航:
           

          導航

          統(tǒng)計

          公告

          你好!

          常用鏈接

          留言簿(2)

          隨筆檔案

          文章分類

          文章檔案

          新聞檔案

          相冊

          收藏夾

          C#學習

          友情鏈接

          搜索

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 茂名市| 贵港市| 天台县| 郎溪县| 环江| 施秉县| 梧州市| 岳西县| 临颍县| 安化县| 南岸区| 泾川县| 双柏县| 宾阳县| 西昌市| 襄城县| 五原县| 穆棱市| 吉木萨尔县| 汶上县| 柏乡县| 化州市| 大安市| 稻城县| 抚顺市| 清水县| 芦溪县| 云龙县| 稷山县| 沅江市| 清苑县| 嘉禾县| 彝良县| 景谷| 托克托县| 闽清县| 海阳市| 广东省| 资源县| 上饶县| 卢湾区|