FineUI直到V3.0才内置了自己的上傳控件,為什麼唯獨上傳控件姗姗來遲,這其中的緣由是啥?之前又是如何實作上傳功能的呢?下面聽我慢慢道來。
AJAX請求與檔案上傳請求的對比
普通的AJAX請求的請求頭和請求正文:

檔案上傳請求的請求頭與請求正文:
可見,普通的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: }
顯示效果如下圖所示:
正如前文所述,這個實作有兩個突出問題:1. 檔案上傳框風格和整個頁面風格不搭配。 2. 上傳時是整個頁面回發,和FineUI預設的AJAX風格也不搭。
現在的檔案上傳方式
現在就簡單多了,并且也漂亮多了,參考這個示例。
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的屬性:
- ButtonText:按鈕文本。
- ButtonOnly:是否隻顯示按鈕,不顯示隻讀輸入框。
- ButtonIcon:按鈕圖示。
- ButtonIconUrl:按鈕圖示位址。
- PostedFile:上傳的檔案。
- HasFile:是否包含檔案。
- FileName:上傳檔案名。
還有一個重要的方法 SaveAs,用來将上傳的文本儲存到伺服器上。
觀察檔案上傳的過程
在文章的最開始我們提到,ExtJS是通過臨時建立一個IFrame來送出檔案請求的,下面就通過一個示例來仔細觀察這個過程。
首先在Firefox中打開示例頁面: http://extasp.net/form/fileupload.aspx
打開Firebug,啟用腳本調試功能,在右側搜尋框中輸入 Ext.extend(Ext.data.Connection ,将定位到Ext.ajax.request函數,所有的AJAX請求都是由這個函數發出了。
注意往下觀察,會看到這樣的代碼:
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标簽,并追加到頁面的底部。此時的頁面如下圖所示:
2. 第15到21行非常重要,注意target: id這個設定,其實是将頁面中表單的送出目标改變為目前窗體中新建立的iframe,而不是目前窗體了。
3. 第23行到37行定義回調函數cb。
4. 第38行将回調函數注冊為iframe加載完畢時執行的函數。
5. 第39行送出表單。注意,此時表單是在新建立的iframe中送出的!
6. 再來看看回調函數cb中的處理,首先擷取iframe中body的内容并指派給響應對象的responseText屬性,然後觸發此次請求的完成事件。此時的頁面效果如下圖所示:
7. 至此,完成了檔案上傳的AJAX效果。
小結
檔案上傳控件用起來友善,不過内部實作卻不是一帆風順的,這也是它姗姗來遲的原因之一。不過這也是FineUI控件的目标,内部實作可以非常複雜,但是對開發人員的接口一定要盡量簡單。
下一篇文章我們會仔細考察下拉清單在FineUI中的用法。