天天看點

FineUI秘密花園(七) — 上傳控件

FineUI直到V3.0才内置了自己的上傳控件,為什麼唯獨上傳控件姗姗來遲,這其中的緣由是啥?之前又是如何實作上傳功能的呢?下面聽我慢慢道來。

AJAX請求與檔案上傳請求的對比

普通的AJAX請求的請求頭和請求正文:

FineUI秘密花園(七) — 上傳控件
FineUI秘密花園(七) — 上傳控件

檔案上傳請求的請求頭與請求正文:

FineUI秘密花園(七) — 上傳控件
FineUI秘密花園(七) — 上傳控件

可見,普通的AJAX請求其Content-Type為application/x-www-form-urlencoded,其請求正文是鍵值對組成的查詢字元串;而檔案上傳請求其Content-Type為multipart/form-data,其請求正文不僅分段包含正常的表單字段,而且包含要上傳檔案的内容。

也就是說兩種請求方式完全不同,要在AJAX環境中實作檔案上傳,最通用的做法是用一個臨時生成的IFrame來送出檔案。而這個過程将會比較複雜,這也是FineUI一直沒有實作自己的檔案上傳控件的原因(後來發現ExtJS支援這個過程,是以就有了FineUI自己的FileUpload控件)。

之前實作檔案上傳的方式

由于之前FineUI沒有自己的FileUpload控件,隻好求助于ASP.NET的FileUpload控件,并且由于檔案上傳的請求不能是AJAX的請求,是以隻好采用整個頁面回發的方式(這也是很多網友所抱怨的地方),參見這個示例。

1:  <f:PageManager ID="PageManager1" runat="server" EnableAjax="false" />      
2:  <asp:FileUpload ID="FileUpload1" runat="server"></asp:FileUpload>      
3:  <asp:Button ID="btnCloseWindow2" runat="server" Text="上傳檔案" OnClick="btnCloseWindow2_Click"></asp:Button>      
1:  protected void btnCloseWindow2_Click(object sender, EventArgs e)      
2:  {      
3:      if (FileUpload1.HasFile)      
4:      {      
5:          FileUpload1.SaveAs(Server.MapPath("~/upload/" + FileUpload1.FileName));      
6:      }      
7:      Alert.ShowInTop("檔案上傳成功!");      
8:  }      

顯示效果如下圖所示:

FineUI秘密花園(七) — 上傳控件

正如前文所述,這個實作有兩個突出問題:1. 檔案上傳框風格和整個頁面風格不搭配。 2. 上傳時是整個頁面回發,和FineUI預設的AJAX風格也不搭。

現在的檔案上傳方式

現在就簡單多了,并且也漂亮多了,參考這個示例。

FineUI秘密花園(七) — 上傳控件
1:  <ext:SimpleForm ID="SimpleForm1" BodyPadding="5px" runat="server" EnableBackgroundColor="true"      
2:      ShowBorder="True" Title="表單" Width="350px" ShowHeader="True">      
3:      <Items>      
4:          <ext:TextBox runat="server" Label="使用者名" ID="tbxUseraName" Required="true" ShowRedStar="true">      
5:          </ext:TextBox>      
6:          <ext:FileUpload runat="server" ID="filePhoto" EmptyText="請選擇一張照片" Label="個人頭像" Required="true"      
7:              ShowRedStar="true">      
8:          </ext:FileUpload>      
9:          <ext:Button ID="btnSubmit" runat="server" OnClick="btnSubmit_Click" ValidateForms="SimpleForm1"      
10:              Text="送出">      
11:          </ext:Button>      
12:      </Items>      
13:  </ext:SimpleForm>      
1:  protected void btnSubmit_Click(object sender, EventArgs e)      
2:  {      
3:      string fileName = DateTime.Now.Ticks.ToString() + "_" + filePhoto.FileName;      
4:      if (filePhoto.HasFile)      
5:      {      
6:          filePhoto.SaveAs(Server.MapPath("~/upload/" + fileName));      
7:      }      
8:  }      

下面來看看FileUpload的屬性:

  1. ButtonText:按鈕文本。
  2. ButtonOnly:是否隻顯示按鈕,不顯示隻讀輸入框。
  3. ButtonIcon:按鈕圖示。
  4. ButtonIconUrl:按鈕圖示位址。
  5. PostedFile:上傳的檔案。
  6. HasFile:是否包含檔案。
  7. FileName:上傳檔案名。

還有一個重要的方法 SaveAs,用來将上傳的文本儲存到伺服器上。

FineUI秘密花園(七) — 上傳控件
FineUI秘密花園(七) — 上傳控件
FineUI秘密花園(七) — 上傳控件
FineUI秘密花園(七) — 上傳控件
FineUI秘密花園(七) — 上傳控件

觀察檔案上傳的過程

在文章的最開始我們提到,ExtJS是通過臨時建立一個IFrame來送出檔案請求的,下面就通過一個示例來仔細觀察這個過程。

首先在Firefox中打開示例頁面: http://extasp.net/form/fileupload.aspx

打開Firebug,啟用腳本調試功能,在右側搜尋框中輸入 Ext.extend(Ext.data.Connection ,将定位到Ext.ajax.request函數,所有的AJAX請求都是由這個函數發出了。

FineUI秘密花園(七) — 上傳控件

注意往下觀察,會看到這樣的代碼:

1:  url = url || form.action;       
2:  if(o.isUpload || (/multipart\/form-data/i.test(form.getAttribute("enctype")))) {      
3:          return me.doFormUpload.call(me, o, p, url);      
4:  }      

很明顯,檔案上傳的代碼都是在doFormUpload函數内完成的,下面是此函數的簡化版本(已經删除了很多不影響了解的代碼):

1:  doFormUpload: function (o, ps, url) {      
2:      var id = Ext.id(),      
3:          doc = document,      
4:          frame = doc.createElement('iframe'),      
5:          form = Ext.getDom(o.form),      
6:          encoding = 'multipart/form-data';      
7:         
8:      Ext.fly(frame).set({      
9:          id: id,      
10:          name: id,      
11:          cls: 'x-hidden'      
12:      });      
13:      doc.body.appendChild(frame);      
14:         
15:      Ext.fly(form).set({      
16:          target: id,      
17:          method: POST,      
18:          enctype: encoding,      
19:          encoding: encoding,      
20:          action: url || buf.action      
21:      });      
22:         
23:      function cb() {      
24:          var me = this,      
25:              r = {      
26:                  responseText: '',      
27:                  responseXML: null,      
28:                  argument: o.argument      
29:              },      
30:              doc, firstChild;      
31:          doc = frame.contentWindow.document || frame.contentDocument || WINDOW.frames[id].document;      
32:          if (doc) {      
33:              r.responseText = doc.body.innerHTML;      
34:              r.responseXML = doc.XMLDocument || doc;      
35:          }      
36:          me.fireEvent(REQUESTCOMPLETE, me, r, o);      
37:      }      
38:      Ext.EventManager.on(frame, LOAD, cb, this);      
39:      form.submit();      
40:  }      

下面我們來分析一下這個函數表達了哪些思想:

1. 首先第8行到13行是建立一個空的iframe标簽,并追加到頁面的底部。此時的頁面如下圖所示:

FineUI秘密花園(七) — 上傳控件

2. 第15到21行非常重要,注意target: id這個設定,其實是将頁面中表單的送出目标改變為目前窗體中新建立的iframe,而不是目前窗體了。

3. 第23行到37行定義回調函數cb。

4. 第38行将回調函數注冊為iframe加載完畢時執行的函數。

5. 第39行送出表單。注意,此時表單是在新建立的iframe中送出的!

6. 再來看看回調函數cb中的處理,首先擷取iframe中body的内容并指派給響應對象的responseText屬性,然後觸發此次請求的完成事件。此時的頁面效果如下圖所示:

FineUI秘密花園(七) — 上傳控件

7. 至此,完成了檔案上傳的AJAX效果。

小結

檔案上傳控件用起來友善,不過内部實作卻不是一帆風順的,這也是它姗姗來遲的原因之一。不過這也是FineUI控件的目标,内部實作可以非常複雜,但是對開發人員的接口一定要盡量簡單。

下一篇文章我們會仔細考察下拉清單在FineUI中的用法。