[原创]FineUI秘密花园(七) — 上传控件

FineUI直到V3.0才内置了自己的上传控件,为什么唯独上传控件姗姗来迟,这其中的缘由是啥?之前又是如何实现上传功能的呢?下面听我慢慢道来。

 

AJAX请求与文件上传请求的对比

 

普通的AJAX请求的请求头和请求正文:

image

 

image

 

文件上传请求的请求头与请求正文:

image

image

 

可见,普通的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:  }

 

显示效果如下图所示:

image

 

正如前文所述,这个实现有两个突出问题:1. 文件上传框风格和整个页面风格不搭配。 2. 上传时是整个页面回发,和FineUI默认的AJAX风格也不搭。

 

 

现在的文件上传方式

现在就简单多了,并且也漂亮多了,参考这个示例

image

 

   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,用来将上传的文本保存到服务器上。

 

 

太棒了太棒了太棒了太棒了太棒了

观察文件上传的过程

在文章的最开始我们提到,ExtJS是通过临时创建一个IFrame来提交文件请求的,下面就通过一个示例来仔细观察这个过程。

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

打开Firebug,启用脚本调试功能,在右侧搜索框中输入 Ext.extend(Ext.data.Connection ,将定位到Ext.ajax.request函数,所有的AJAX请求都是由这个函数发出了。

image

 

注意往下观察,会看到这样的代码:

   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标签,并追加到页面的底部。此时的页面如下图所示:

image

 

2. 第15到21行非常重要,注意target: id这个设置,其实是将页面中表单的提交目标改变为当前窗体中新创建的iframe,而不是当前窗体了。

 

3. 第23行到37行定义回调函数cb。

 

4. 第38行将回调函数注册为iframe加载完毕时执行的函数。

 

5. 第39行提交表单。注意,此时表单是在新创建的iframe中提交的!

 

6. 再来看看回调函数cb中的处理,首先获取iframe中body的内容并赋值给响应对象的responseText属性,然后触发此次请求的完成事件。此时的页面效果如下图所示:

image

 

7. 至此,完成了文件上传的AJAX效果。

 

 

小结

文件上传控件用起来方便,不过内部实现却不是一帆风顺的,这也是它姗姗来迟的原因之一。不过这也是FineUI控件的目标,内部实现可以非常复杂,但是对开发人员的接口一定要尽量简单。

下一篇文章我们会仔细考察下拉列表在FineUI中的用法。

 

注:《FineUI秘密花园》系列文章由三生石上原创,博客园首发,转载请注明出处。文章目录 官方论坛

posted @ 2012-02-25 21:55  三生石上(FineUI控件)  阅读(14603)  评论(17编辑  收藏  举报