Hello World
Spiga

让UpdatePanel支持文件上传(3):客户端组件

2007-04-06 10:10 by 老赵, 4906 visits

我们继续编写客户端的部分。

我们的UpdatePanelIFrameExecutor继承了WebRequestExecutor,因此需要实现许多方法和属性。但是我们事实上不用完整地实现所有的成员,因为客户端的异步刷信机制只会访问其中的一部分。以下是异步刷信过程中会使用的成员列表,我们必须正确地实现它们:

  • get_started: 表示一个Executor是否已经开始 了。
  • get_responseAvailable: 表示一个请求是否成功。
  • get_timedOut: 表示一个请求是否超时。
  • get_aborted: 表示一个请求是否被取消了。
  • get_responseData: 获得文本形式的Response Body。 
  • get_statusCode: 获得Response的状态代码
  • executeRequest: 执行一个请求。
  • abort: 停止正在运行的请求。

UploadPanelIFrameExecutor依旧非常简单,只是定义了一些私有变量:

Jeffz.Web.UpdatePanelIFrameExecutor = function(sourceElement)
{
    Jeffz.Web.UpdatePanelIFrameExecutor.initializeBase(this);

    // for properties
    this._started = false;
    this._responseAvailable = false;
    this._timedOut = false;
    this._aborted = false;
    this._responseData = null;
    this._statusCode = null;
    
    // the element initiated the async postback
    this._sourceElement = sourceElement;
    // the form in the page.
    this._form = Sys.WebForms.PageRequestManager.getInstance()._form;
    // the handler to execute when the page in iframe loaded.
    this._iframeLoadCompleteHandler = Function.createDelegate(
        this, this._iframeLoadComplete);
}

当executeRequest方法被调用时,我们会准备一个隐藏的iframe和所有的附加的隐藏输入元素,并将form的target指向iframe。当然,其他一些工作也是必须的,例如准备一个衡量超时的计时器:

executeRequest : function()
{
    // create an hidden iframe
    this._iframe = this._createIFrame();
    
    // all the additional hidden input elements
    this._addAdditionalHiddenElements();
    
    // point the form's target to the iframe
    this._form.target = this._iframe.id;
    this._form.encType = "multipart/form-data";        
    
    // set up the timeout counter.
    var timeout = this._webRequest.get_timeout();
    if (timeout > 0)
    {
        this._timer = window.setTimeout(
            Function.createDelegate(this, this._onTimeout), timeout);
    }
    
    this._started = true;
    
    // restore the status of the element after submitting the form
    setTimeout(Function.createDelegate(this, this._restoreElements), 0);

    // sumbit the form
    this._form.submit();
},

建立一个隐藏得iframe元素很简单,但是我们该创建哪些附加的隐藏输入元素呢?自然我们表示“异步回送”的自定义标记是其中之一,那么剩下的还需要哪些呢?似乎我们只能通过阅读PageRequestManager的代码来找到问题的答案。还好,似乎阅读下面的代码并不困难:

function Sys$WebForms$PageRequestManager$_onFormSubmit(evt)
{
    // ...
    
    // Construct the form body
    var formBody = new Sys.StringBuilder();
    formBody.append(this._scriptManagerID + '=' + this._postBackSettings.panelID + '&');

    var count = form.elements.length;
    for (var i = 0; i < count; i++)
    {
        // ...
        // Traverse the input elements to construct the form body
        // ...
    }

    if (if._additionalInput)
    {
        formBody.append(this._additionalInput);
        this._additionalInput = null;
    }

    var request = new Sys.Net.WebRequest();
    // ...
    // prepare the web request object
    // ...

    var handler = this._get_eventHandlerList().getHandler("initializeRequest");
    if (handler)    {
        var eventArgs = new Sys.WebForms.InitializeRequestEventArgs(
            request, this._postBackSettings.sourceElement);
        handler(this, eventArgs);
        continueSubmit = !eventArgs.get_cancel();
    }

    // ...

    this._request = request;
    request.invoke();

    // ...
}

请注意红色部分的代码。可以发现有两种数据需要被添加为隐藏的输入元素。其一是ScriptManager相关的信息(第一部分的红色代码),其二则是变量“_additionalInput”的内容。我们很容易得到前者的值,但是后者的内容究竟是什么呢?我们继续阅读代码:

function Sys$WebForms$PageRequestManager$_onFormElementClick(evt)
{
    var element = evt.target;
    if (element.disabled) {
        return;
    }

    // Check if the element that was clicked on should cause an async postback
    this._postBackSettings = this._getPostBackSettings(element, element.name);

    if (element.name)
    {
        if (element.tagName === 'INPUT')
        {
            var type = element.type;
            if (type === 'submit')
            {
                this._additionalInput = 
                    element.name + '=' + encodeURIComponent(element.value);
            }
            else if (type === 'image')
            {
                var x = evt.offsetX;
                var y = evt.offsetY;
                this._additionalInput = 
                    element.name + '.x=' + x + '&' + element.name + '.y=' + y;
            }
        }
        else if ((element.tagName === 'BUTTON') &&
            (element.name.length !== 0) && (element.type === 'submit'))
        {
            this._additionalInput = element.name + '=' + encodeURIComponent(element.value);
        }
    }
}

_onFormElmentClick方法会在用户点击form中特定元素时执行。方法会提供变量“_additionalInput”的内容,然后紧接着,我们之前分析过的_onFormSubmit方法会被调用。现在我们就能够轻松地为form添加额外的隐藏输入元素了:

_addAdditionalHiddenElements : function()
{
    var prm = Sys.WebForms.PageRequestManager.getInstance();
    
    // clear the array of hidden input elements
    this._hiddens = [];
    
    // custom sign to indicate an async postback
    this._addHiddenElement("__AjaxFileUploading__", "__IsInAjaxFileUploading__");

    // the value related to the ScriptManager
    this._addHiddenElement(prm._scriptManagerID, prm._postBackSettings.panelID);
    
    // find the additional data
    var additionalInput = null;
    var element = this._sourceElement;
    if (element.name)
    {
        var requestBody = this.get_webRequest().get_body();
        
        if (element.tagName === 'INPUT')
        {
            var type = element.type;
            if (type === 'submit')
            {
                var index = requestBody.lastIndexOf("&" + element.name + "=");
                additionalInput = requestBody.substring(index + 1);
            }
            else if (type === 'image')
            {
                var index = requestBody.lastIndexOf("&" + element.name + ".x=");
                additionalInput = requestBody.substring(index + 1);
            }
        }
        else if ((element.tagName === 'BUTTON') &&
             (element.name.length !== 0) && (element.type === 'submit'))
        {
            var index = requestBody.lastIndexOf("&" + element.name + "=");
            additionalInput = requestBody.substring(index + 1);
        }
    }
    
    // parse the additional data
    if (additionalInput)
    {
        var inputArray = additionalInput.split("&");
        for (var i = 0; i < inputArray.length; i++)
        {
            var nameValue = inputArray[i].split("=");
            this._addHiddenElement(nameValue[0], decodeURIComponent(nameValue[1]));
        }
    }
},

_addHiddenElement : function(name, value)
{
    var hidden = document.createElement("input");
    hidden.name = name;
    hidden.value = value;
    hidden.type = "hidden";
    this._form.appendChild(hidden);

    Array.add(this._hiddens, hidden);
},

除去附加的隐藏输入元素非常简单,不值一提。另外iframe在加载结束后的逻辑也很容易理解——不过解析内容的机制就另当别论了:

_iframeLoadComplete : function()
{
    var iframe = this._iframe;
    delete this._iframe;
    
    var responseText = null;
    try
    {    
        // ...
        // retrieve the data we need
        // ...
        
        this._statusCode = 200;
        this._responseAvailable = true;
    }
    catch (e)
    {
        this._statusCode = 500;
        this._responseAvailable = false;
    }
    
    $removeHandler(iframe, "load", this._iframeLoadCompleteHandler);
    iframe.parentNode.removeChild(iframe);
    this._clearTimer();
    this.get_webRequest().completed(Sys.EventArgs.Empty);
},

我们已经快完成我们的项目了。:)

 

点击这里下载整个项目

English Version

Creative Commons License

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言

Add your comment

20 条回复

  1. kingcat[未注册用户]
    *.*.*.*
    链接

    kingcat[未注册用户] 2007-04-06 10:32:00

    太多了,能有个图看下思路就清晰多了

  2. Huacn Lee
    *.*.*.*
    链接

    Huacn Lee 2007-04-06 10:40:00

    哦,对了,昨晚你跟我说哪个调用js的方法好像不行,看这里:
    http://www.cnblogs.com/huacn/archive/2007/04/05/701478.html

  3. Huacn Lee
    *.*.*.*
    链接

    Huacn Lee 2007-04-06 10:40:00

    先下载来看看效说再看代码,呵呵

  4. 老赵
    admin
    链接

    老赵 2007-04-06 10:48:00

    您是不是没有给他加载时间?Script加载是异步的,您必须响应Script元素的load事件或readystatechange事件,等到脚本文件下载下来以后才能继续工作。

  5. 老赵
    admin
    链接

    老赵 2007-04-06 10:48:00

    @kingcat
    什么样的图?

  6. Clark Zheng
    *.*.*.*
    链接

    Clark Zheng 2007-04-06 10:59:00

    写得太快,学的跟不上教的,发愁呀。。。继续收藏先!

  7. liuningjian[未注册用户]
    *.*.*.*
    链接

    liuningjian[未注册用户] 2007-04-06 18:22:00

    我使用这个控件出现了问题:Error parsing near 'Action||MyInfo.aspx|*/}</script>'. 错误出现在点击MasterPage中Login控件的LinkButton(比如注销按钮),另:Login控件是在MasterPage的一个UpdatePanel中的。请问是什么原因。

  8. llinzzi[未注册用户]
    *.*.*.*
    链接

    llinzzi[未注册用户] 2007-04-06 18:52:00

    终于出来了,呵呵
    稍稍有一些不足,如果能实现就更好了
    1,在用TabContainer的时候,在某个TabPanel里有文件上传,而别的只是普通的数据,因为.net每次点击都会post所有的数据,而实际应用的应该都是在只post button所在TabPanel内的数据就够了,因为post的所有的了,所以在无文件上传的TabPanel中,也会利用iframe上传,到无大碍,只是状态栏会有一个加载提示。
    2,似乎不支持UpdateProgress?

  9. llinzzi[未注册用户]
    *.*.*.*
    链接

    llinzzi[未注册用户] 2007-04-06 19:28:00

    sorry
    用老赵的代码测试,是支持UpdateProgress,是我自己的问题,找找。

  10. 老赵
    admin
    链接

    老赵 2007-04-06 19:39:00

    @liuningjian
    谢谢您的反馈,我会看一下的。:)

  11. 老赵
    admin
    链接

    老赵 2007-04-06 19:40:00

    @llinzzi
    这是UpdatePanel的一个特点,需要PostBack所有的内容。至于什么时候用XHR,什么时候用Iframe,我是根据页面上是不是有<input type="file" />来确定的。
    至于UpdateProgress是不会有问题的,因为我使用了“标准”的扩展方式,呵呵。

  12. llinzzi[未注册用户]
    *.*.*.*
    链接

    llinzzi[未注册用户] 2007-04-06 22:11:00

    嗯呢
    再次谢谢老赵带来这么好的东西。

  13. 数据绑定者
    *.*.*.*
    链接

    数据绑定者 2007-04-09 09:25:00

     
    老赵,我的出现这个错误,未知错误,晕

  14. TT[未注册用户]
    *.*.*.*
    链接

    TT[未注册用户] 2007-04-17 14:30:00

    同上
    也发现500

  15. 老赵
    admin
    链接

    老赵 2007-04-17 15:13:00

    @数据绑定者
    @TT
    能不能把出错的示例发给我看一下呢?

  16. 叶子绿了
    *.*.*.*
    链接

    叶子绿了 2007-09-12 12:01:00

    我也出现上面的错误.

  17. 叶子绿了
    *.*.*.*
    链接

    叶子绿了 2007-09-12 14:18:00

    发了一个示例给赵老师了,老师有空帮我看一下

  18. 需要帮助[未注册用户]
    *.*.*.*
    链接

    需要帮助[未注册用户] 2007-12-22 16:57:00

    也发现500 的解决方法

  19. 需要帮助[未注册用户]
    *.*.*.*
    链接

    需要帮助[未注册用户] 2007-12-22 16:58:00

    强烈需要500错误的解决方法

  20. 江大鱼
    *.*.*.*
    链接

    江大鱼 2007-12-27 18:39:00

    传大一点的文件就会出现问题,不知老赵有无解决方法。

发表回复

登录 / 登录并记住我 ,登陆后便可删除或修改已发表的评论 (请注意保留评论内容)

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

评论内容(大于5个字符):

  1. Your Name yyyy-MM-dd HH:mm:ss

使用Live Messenger联系我