[原创]FineUI秘密花园(七) — 上传控件
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: <ext: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中的用法。