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