Microsoft Ajax 脚本浅析

         最近有时间下载并在本地安装了 AjaxControlToolkit , 在运行里面的示例时,发现所生
成的源文件时发现有几个“特别”的地方。因为本人对Microsoft Ajax未曾做过什么研究,因
此就想看看微软的这个产品中是有什么奥秘。现在就把我所看的源码以及相关的理解记录如下
(本文以SampleWebSite/DragPanel/DragPanel.aspx为例),以便与大家交流,希望大家多提意
见。

    代码段1:
    <script type="text/javascript">
        Sys.WebForms.PageRequestManager._initialize('ctl00$SampleContent$ctl00',
  document.getElementById('aspnetForm'));
        Sys.WebForms.PageRequestManager.getInstance()._updateControls([], [], [], 90);
    </script>

    Sys.WebForms.PageRequestManager._initialize函数可以在这个本文的下载包中的
ScriptResource_ScriptManager.js中找到,它的代码如下:
   Sys.WebForms.PageRequestManager._initialize =
 function Sys$WebForms$PageRequestManager$_initialize(scriptManagerID, formElement)
    {
       if (Sys.WebForms.PageRequestManager.getInstance()) {
          throw Error.invalidOperation(Sys.WebForms.Res.PRM_CannotRegisterTwice);
       }
       Sys.WebForms.PageRequestManager._instance = new Sys.WebForms.PageRequestManager();
       Sys.WebForms.PageRequestManager.getInstance()._initializeInternal(
  scriptManagerID, formElement);
    }

    其中的 if 分支里应该是为了确保当前的 _instance 唯一或为空 。分支结束后则开始运
行初始化并运行实例的_initializeInternal函数这个函数的第二个参数formElement就是页面
表单的ID,如下:
     <form name="aspnetForm" method="post" action="DragPanel.aspx" id="aspnetForm">
    实始化的函数如下(请看注释):
    function Sys$WebForms$PageRequestManager$_initializeInternal(scriptManagerID,
formElement)
    {

        this._scriptManagerID = scriptManagerID; 
        this._form = formElement;
        this._form._initialAction = this._form.action;

        this._onsubmit = this._form.onsubmit;
        this._form.onsubmit = null;

        //下面这一行代码中的_onFormSubmit事件和相关解释参见
 //http://www.cnblogs.com/JeffreyZhao/archive/2007/04/09/Be_careful_with_loading_script_files_after_an_async_postback.html
        this._onFormSubmitHandler = Function.createDelegate(this, this._onFormSubmit);

 //_onFormElementClick应该是对 element.tagName 为INPUT(submit,image)或BUTTON
 //进行元素值的绑定,并对_postBackSettings进行封装 功能:元素绑定(如panel等)和
 //子元素(如果存在)值数据进行封装,请看代码段:_createPostBackSettings
        this._onFormElementClickHandler = Function.createDelegate(this, this._onFormElementClick);

 //运行 this.dispose();
        this._onWindowUnloadHandler = Function.createDelegate(this, this._onWindowUnload);

        //进行相应的事件绑定
        Sys.UI.DomEvent.addHandler(this._form, 'submit', this._onFormSubmitHandler);
        Sys.UI.DomEvent.addHandler(this._form, 'click', this._onFormElementClickHandler);
        Sys.UI.DomEvent.addHandler(window, 'unload', this._onWindowUnloadHandler);

        this._originalDoPostBack = window.__doPostBack;
                if (this._originalDoPostBack) {
            window.__doPostBack = Function.createDelegate(this, this._doPostBack);
        }

 //运行_pageLoaded (true)详见代码段:"_pageLoaded ",如果false
        //则运行Sys.Application.raiseLoad()
        this._pageLoadedHandler = Function.createDelegate(this, this._pageLoadedInitialLoad);
        Sys.UI.DomEvent.addHandler(window, 'load', this._pageLoadedHandler);
    }

    其中_onFormSubmit函数就是当页面发生提交操作时执行的函数,里面有生成formBody和
WebRequest对象的操作,详情不多说了,可参见链接:http://blog.joycode.com/saucer/articles/85745.aspx


   
    接下来看函数_createPostBackSettings代码段:
    function Sys$WebForms$PageRequestManager$_createPostBackSettings(async, panelID, sourceElement)
    {
        return { async:async, panelID:panelID, sourceElement:sourceElement };
    }


    代码段:_pageLoaded
    function Sys$WebForms$PageRequestManager$_pageLoaded(initialLoad) {
        var handler = this._get_eventHandlerList().getHandler("pageLoaded");
        if (handler) {
            handler(this, this._getPageLoadedEventArgs(initialLoad));
        }
        if (!initialLoad) {
            Sys.Application.raiseLoad();
        }
    }


     然后再看一下:Sys.WebForms.PageRequestManager.getInstance()._updateControls 
它的作用应该是将位于当前panel下的所有数据元素绑定到PageRequestManager的相关属性上,
这样就可以做局部页面数据的提交和相关操作(如刷新等)。
    可能是因为初始化页面,所以这个函数里的前三个参数将为“[]” ,只有第四个参数是90
也就是AsyncPostBackTimeOut的属性值(单位:秒)。相关的函数如下:  
    function Sys$WebForms$PageRequestManager$_updateControls(updatePanelIDs,
asyncPostBackControlIDs, postBackControlIDs, asyncPostBackTimeout)
    {
        if (updatePanelIDs) {
            this._updatePanelIDs = new Array(updatePanelIDs.length);
            this._updatePanelClientIDs = new Array(updatePanelIDs.length);
            this._updatePanelHasChildrenAsTriggers = new Array(updatePanelIDs.length);
            for (var i = 0; i < updatePanelIDs.length; i++) {
                var realPanelID = updatePanelIDs[i].substr(1);
                var childrenAsTriggers = (updatePanelIDs[i].charAt(0) === 't');

                this._updatePanelHasChildrenAsTriggers[i] = childrenAsTriggers;
                this._updatePanelIDs[i] = realPanelID;
                this._updatePanelClientIDs[i] = this._uniqueIDToClientID(realPanelID);
            }
            // 设置异步回传过期时间
            this._asyncPostBackTimeout = asyncPostBackTimeout * 1000;
        }
        else {
            this._updatePanelIDs = [];
            this._updatePanelClientIDs = [];
            this._updatePanelHasChildrenAsTriggers = [];
            this._asyncPostBackTimeout = 0;
        }
 ......
    }
   

  
    然后就是下面的代码段了:
    Sys.Application.initialize();
    Sys.Application.add_init(function() {
       $create(AjaxControlToolkit.FloatingBehavior, {"handle":
 $get("ctl00_SampleContent_Panel7"),"id":"ctl00_SampleContent_DragPanelExtender1"},
 null, null, $get("ctl00_SampleContent_Panel6"));
    });

    函数Sys.Application.initialize用于初始化并进行创建组件的处理句柄绑定,代码如下:
    function Sys$_Application$initialize() {
        if(!this._initialized && !this._initializing) {
            this._initializing = true;
            window.setTimeout(Function.createDelegate(this, this._doInitialize), 0);
        }
    }
    其中_doInitialize函数如下:
    function Sys$_Application$_doInitialize() {
        Sys._Application.callBaseMethod(this, 'initialize');

        //getHandler("init")返回值是Sys.Application.add_init函数中传入的处理句柄
        var handler = this.get_events().getHandler("init");
        if (handler) {
            this.beginCreateComponents();
            handler(this, Sys.EventArgs.Empty);
            this.endCreateComponents();
        }
        //创建组件 
        this.raiseLoad();
        this._initializing = false;
    }

    其中raiseLoad 函数如下:
    function Sys$_Application$raiseLoad() {
        var h = this.get_events().getHandler("load");
        var args = new Sys.ApplicationLoadEventArgs(Array.clone(this._createdComponents),
   !this._initializing);
        if (h) {
            h(this, args);
        }

        if (window.pageLoad) {
            window.pageLoad(this, args);
        .....
    }

   
    最后就是Sys.Application.add_init将创建句柄转入了,它里面的函数$create 用于创建相应的
客户端组件,ASP.NET AJAX 客户端组件对象类型:Sys.UI.Behavior和Sys.UI.Control,它们扩充了
基本组件的功能,行为类组件继承于Sys.UI.Behavior,控件继承于Sys.UI.Control。
    $create 代码段如下(下载包中的ScriptResource_EventHandlerList文件):

    function Sys$Component$create(type, properties, events, references, element)
    {
        .......

 //FloatingBehavior是否是组件
        if (!type.inheritsFrom(Sys.Component)) {
            throw Error.argument('type', String.format(Sys.Res.createNotComponent,
  type.getName()));
     }

 //FloatingBehavior是否是行为类组件或控件
        if (type.inheritsFrom(Sys.UI.Behavior) || type.inheritsFrom(Sys.UI.Control)) {
        if (!element) throw Error.argument('element', Sys.Res.createNoDom);
        }
        else if (element) throw Error.argument('element', Sys.Res.createComponentOnDom);

        //按指定类型创建相关的组件
        var component = (element ? new type(element): new type());
        var app = Sys.Application;
  
 ......

        //为组类实例绑定相应的属性
        if (properties)
        {
           Sys$Component$_setProperties(component, properties);
        }

        //为组件添加相应的事件名称
        if (events)
        {
          for (var name in events) {
            if (!(component["add_" + name] instanceof Function))
   throw new Error.invalidOperation(String.format(Sys.Res.undefinedEvent, name));

            if (!(events[name] instanceof Function))
  throw new Error.invalidOperation(Sys.Res.eventHandlerNotFunction);

     //注意此处加入了"add_"前缀 ,在FloatingBehavior类中将会有一个move事件,请看代码段
     //FloatingBehavior
            component["add_" + name](events[name]); 
          }
        }

        .....

    return component;
}
   
     看了创建组件的函数后再看一下要创建的组件类:

     代码段:FloatingBehavior
     AjaxControlToolkit.FloatingBehavior = function(element)
     {
        ...... 
 
        this.add_move = function(handler) {
           this.get_events().addHandler('move', handler);
        }

        this.remove_move = function(handler) {
           this.get_events().removeHandler('move', handler);
        }

        this.get_handle = function() {
           return _handle;
        }

        this.set_handle = function(value) {
           if (_handle != null) {
            $removeHandler(_handle, "mousedown", _mouseDownHandler);           
        }
   
        _handle = value;
        $addHandler(_handle, "mousedown", _mouseDownHandler);       

        ......

        function mouseDownHandler(ev) {
           window._event = ev;
           var el = this.get_element();
       
           if (this.checkCanDrag(ev.target)) {
            _dragStartLocation = CommonToolkitScripts.getLocation(el);
           
            ev.preventDefault();
           
            this.startDragDrop(el);
        }

        ......

        this.initialize = function() {
           AjaxControlToolkit.FloatingBehavior.callBaseMethod(this, 'initialize');
           AjaxControlToolkit.DragDropManager.registerDropTarget(this);

        ......
    }
    这里面mouseDownHandler应该就是处理拖动操作的程序代码了。而在这个类的initialize中有
DragDropManager.registerDropTarget(this)这一行,它的作用是调用 _wireDropTargetEvents函
数(下载包中ScriptResource_Drag文件,加入相应的事件处理器如:_dragEnterHandler,
_dragLeaveHandler等),值得一提的是在DragDropManager(下载包中ScriptResource_DragDropManage
文件)的_getInstance()函数中出现了如下代码,大家看到了吧,为了实现在不同浏览器下正常工作,
开发人员在这里做了分别处理:
 _getInstance: function()
        {
          if (!this._instance) {
            if (Sys.Browser.agent === Sys.Browser.InternetExplorer) {
                this._instance = new AjaxControlToolkit.IEDragDropManager();
            }
            else {
                this._instance = new AjaxControlToolkit.GenericDragDropManager();
            }

     相关的设置大家可以看一下IEDragDropManager和GenericDragDropManager这两个类,我就不
在这里多聊了。

     最后是:
     AjaxControlToolkit.FloatingBehavior.registerClass('AjaxControlToolkit.FloatingBehavior',
  AjaxControlToolkit.BehaviorBase,
  AjaxControlToolkit.IDragSource,
  AjaxControlToolkit.IDropTarget,
  Sys.IDisposable);

     从字面上看,可以知道FloatingBehavior 的基类就是AjaxControlToolkit.BehaviorBase,因为
registerClass函数声明如下[下载包中ScriptResource_Application文件]:

    //interfaceTypes接口数组
    function Type$registerClass(typeName, baseType, interfaceTypes)
    {
         ......
        //检查类型名称是否有效
        if (!Type.__fullyQualifiedIdentifierRegExp.test(typeName))
    throw Error.argument('typeName', Sys.Res.notATypeName);
     
        ......
        if (baseType && !baseType.__class)
    throw Error.argument('baseType', Sys.Res.baseNotAClass);

        this.prototype.constructor = this;
        this.__typeName = typeName;
        this.__class = true;

        if (baseType)
        {
        this.__baseType = baseType;
        this.__basePrototypePending = true;
        }
        ......

    分析至此告一段落。因为Microsoft Ajax js 文件如此复杂(值得学习借签的地方确实不少),
只能看到哪里学到哪里了。因为是初次接触,因此文章中有不当之处希望大家多提宝贵意见,共同
进步:)

下载包:
     https://files.cnblogs.com/daizhj/package.rar

     注:下载包中的文件已被重命名,只为分析时方便。详情参见AjaxControlToolkit和相关文档。

     参考文章:
     http://www.cnblogs.com/JeffreyZhao/archive/2007/04/09/Be_careful_with_loading_script_files_after_an_async_postback.html
     http://www.cnblogs.com/fanrong/archive/2007/04/27/729875.html
     http://www.cnblogs.com/hblynn/archive/2007/01/31/635507.html

     http://blog.csdn.net/TheMoment_Rain/archive/2006/12/14/1443128.aspx
     http://blog.joycode.com/saucer/articles/85745.aspx
   
   

posted @ 2007-05-28 12:09  代震军  阅读(5797)  评论(16编辑  收藏  举报