代码改变世界

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

2007-04-06 10:10  Jeffrey Zhao  阅读(5447)  评论(20编辑  收藏  举报

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

我们的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