Microsoft AJAX Library定义了一个客户端组件的模型,它的基类是Sys.Component,它实现了三个接口Sys.IDisposable,Sys.INotifyDisposing,Sys.INotifyPropertyChange
Sys.Component成员
- get_events()
- get_id();
- set_id();
- get_isInitialized();
- initialize();
- dispose();
- raisePropertyChanged();
Sys.IDisposable成员
- dispose();
Sys.INotifyDisposing成员
- add_disposing();
- remove_disposing();
Sys.INotifyPropertyChange成员
- add_propertyChanged();
- remove_propertyChanged();
可视组件和不可视组件
可视组件,就是对DOM进行了封装,在Microsoft AJAX Library中可分为两种Sys.UI.Control和Sys.UI.Behavior,不可视组件不继承于Control和Behavior,它是一种辅助对象
Control和Behavior
- Sys.UI.Control:封装了DOM元素,概念上为一个组合的控件
- Sys.UI.Behavior:扩展了DOM元素,为DOM元素提供了额外的功能
全局容器
- Sys._Application为一个全局的容器类
- 维护着全局的所有Component对象的生命周期
客户端生命周期
这里的声明周期,很像我们的c#语言,实际上,它就是按照这种高级语言的声明周期来开发的,如果我们要创建对象,需要在Sys.Application.init事件中创建,并且调用Component的initialize方法,这样在load事件中,就可以在代码中控制它,这以为着,在Sys.Application的load阶段,所有的组件已经必须准备好
一个客户端与组件生命周期的示例
首先创建一个名为SimpleComponent.js的文件
/// <referenct name="MicrosoftAjax.js"/> Type.registerNamespace("Demo");//注册一个命名空间 Demo.SimpleComponent = function() { Demo.SimpleComponent.initializeBase(this);//调用父类的构造函数 } Demo.SimpleComponent.prototype =//添加成员 { initialize: function() { Demo.SimpleComponent.callBaseMethod(this, "initialize"); //调用父类名为initialize的方法 alert("I've been initialized!"); }, dispose: function() { alert("I'm being disposed!"); Demo.SimpleComponent.callBaseMethod(this, "dispose"); }, sayHi: function() { alert("Hello,i'm you first component!"); } } Demo.SimpleComponent.registerClass("Demo.SimpleComponent", Sys.Component);//注册这个类,继承自Sys.Component
然后创建一个aspx页面
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="SimpleLifeCycle.aspx.cs" Inherits="Demo12_SimpleLifeCycle" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title></title> </head> <body> <form id="form1" runat="server"> <asp:ScriptManager ID="ScriptManager1" runat="server"> <Scripts> <asp:ScriptReference Path="~/Demo12/SimpleComponent.js" /> </Scripts> </asp:ScriptManager> <script language="javascript" type="text/javascript"> function pageInit() { alert("Page is initializaing!"); $create(//Microsoft AJAX Library提供的创建对象的简单方法 Demo.SimpleComponent,//第一个参数,构造组件的类型 { "id": "simpleComponent" },//第二个参数,设置属性,这里只设置一个id { "disposing": onDisposing });//第三个参数,设置事件 } function pageLoad() {//页面加载完成后被调用 var simple = $find("simpleComponent"); //找到simpleComponent这个组件 simple.sayHi();//调用组件的sayHi方法 } function onDisposing() {//Component释放的时候被调用 alert("Component is being disposed"); } Sys.Application.add_init(pageInit); //把pageInit方法添加到Sys.Application的init事件中,这样在Sys.Application的init事件中方法被调用 </script> </form> </body> </html>
我们打开这个页面,一步一步的观察调用步骤
1.Sys.Application.init事件被触发
2.Component的initialize被调用
3.pageLoad被调用Component的sayHi方法被执行
4.离开页面,组件的dispose方法被调用
5.我们已经在创建对象的时候响应了对象的disposing事件,onDisposing方法被执行
开发一个Component
- Sys.Component类(非必须)
- 在构造函数里定义私有变量(将变量设置为默认值)
- 覆盖initialize方法,初始化所有私有变量
- 覆盖dispose方法,释放所有私有变量,避免资源泄漏
- 定义其他成员
一个简单的Component的示例
创建一个名为Timer.js的文件
Type.registerNamespace("Demo"); Demo.Timer = function() { Demo.Timer.initializeBase(this);//调用基类构造函数 this._interval = 1000;//私有变量设置为默认值 this._timer = null; //私有变量设置为默认值 } Demo.Timer.prototype = { get_interval: function() { return this._interval; }, set_interval: function(value) { if (this._interval != value) {//判断现在已有interval的值,是否等于要设置的值 this._interval = value; this.raisePropertyChanged("interval");//告诉外接interval属性被改变 if (this._timer) { this.stop(); this.start(); } } }, add_tick: function(handler) { this.get_events().addHandler("tick", handler);//添加一个事件tick为事件名 }, remove_tick: function(handler) { this.get_events().removeHandler("tick", handler); //去除一个事件tick为事件名 }, _timerCallback: function() { var handler = this.get_events().getHandler("tick");//得到我们设置的事件 if (handler) {//如果这个事件存在 handler(this, Sys.EventArgs.Empty); } }, start: function() { if (this._interval > 0) { this._timer = window.setInterval(//这里使用的是javascript的原生方法 Function.createDelegate(this, this._timerCallback), //使_timerCallback的指针指向组件本身,而不是window this._interval); } }, stop: function() { if (this._timer) {//如果_timer已经被赋值,证明已经开始啦 window.clearInterval(this._timer); this._timer = null; } } } Demo.Timer.registerClass("Demo.Timer", Sys.Component);
创建一个aspx页面
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Timer.aspx.cs" Inherits="Demo12_Timer" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title></title> </head> <body> <form id="form1" runat="server"> <asp:ScriptManager ID="ScriptManager1" runat="server"> <Scripts> <asp:ScriptReference Path="~/Demo12/Timer.js" /> </Scripts> </asp:ScriptManager> Interval: <input type="text" value="1000" id="txtInterval" /> <input type="button" value="Change" onclick="changeInterval()" /><br /> <div id="display"></div> <script language="javascript" type="text/javascript"> Sys.Application.add_init(function() { $create(Demo.Timer,//创建这个Component { "id": "timer" },//定义一个ID { "tick": onTick, "propertyChanged": onPropertyChanged });//创建事件处理程序 }); function onPropertyChanged(sender, args) {//propertyChanged被触发的时候,此方法被调用 var property=args.get_propertyName();//得到改变属性的名称 var value = sender["get_" + property].apply(sender);//apply指定指针指向组件对象 alert(property+" is changed to "+value); } window.counter = 0;//一个计数器 function onTick() {//组件的tick事件被触发时,此方法被调用 $get("display").innerHTML = window.counter++; } function pageLoad() {//页面加载完成,timer开始工作 $find("timer").start(); } function changeInterval() { $find("timer").set_interval( parseInt($get("txtInterval").value, 10)); } </script> </form> </body> </html>
这里就使用到了我们创建的Component,实现一个计数器的效果,类似一个客户端的Timer
Sys.Component成员
- events只读属性//事件集合
- id属性//组件的id
- initialize方法
- isInitialized只读属性//是否在构造中
- raisePropertyChanged方法//告诉外界哪个属性改变
- propertyChanged事件//属性改变后触发
- dispose方法
- disposing事件
- beginUpdate方法//开始Update
- isUpdating只读属性//是否处于Update状态
- endUpdate方法
- updated方法
组件处于正在更新的状态称为Update状态,处于更新状态时候组件的数据可能出于不一致的状态,因此,出于更新状态的组件,允许组件处于不一直的状态,但是应该尽量避免与外接的交换,尤其是处于DOM元素有关的交互,有时候,合理的利用Update状态也能够在一定程序上提高性能
Update状态的使用
- Sys.Component._setProperties方法:批量修改组件的属性(在非Update状态下)(调用beginUpdate方法->设置组件属性->调用endUpdate方法)
Update状态在系统中的使用
windows的DOM事件load被触发
Sys.Application对象的beginCreatComponent方法被调用
SysApplication对象的Init事件被触发
$Creat方法被调用,用于创建对象
组件的beginUpdate方法被调用
部分组件的endUpdate方法被调用
如果他们还没有被初始化,而initialize方法被调用
他们的Updated方法被调用
Sys.Application对象的endCreatComponent方法被调用
剩余组件的endUpdate方法被调用
开发时Update状态的使用方式
- 调用beginUpdate方法
- 修改属性
- 调用endUpdate方法,此外,经常重写Updated方法,提交组件更新信息
一个改进版的Timer示例
创建一个名为BetterTimer.js的文件
/// <reference name="MicrosoftAjax.js"/> Type.registerNamespace("Demo"); Demo.Timer = function() { Demo.Timer.initializeBase(this); this._interval = 1000; this._enabled = false; this._timer = null; } Demo.Timer.prototype = { add_tick: function(handler) { this.get_events().addHandler("tick", handler); }, remove_tick: function(handler) { this.get_events().removeHandler("tick", handler); }, _timerCallback: function() { var handler = this.get_events().getHandler("tick"); if (handler) { handler(this, Sys.EventArgs.Empty); } }, _startTimer: function() { this._timer = window.setInterval( Function.createDelegate(this, this._timerCallback), this._interval); }, _stopTimer: function() { window.clearInterval(this._timer); this._timer = null; }, get_interval: function() { return this._interval; }, set_interval: function(value) { if (this._interval != value) { this._interval = value; this.raisePropertyChanged("interval"); if (!this.get_isUpdating() && this._timer != null) {//timer正在执行,而且组件不出于Update状态 this._stopTimer(); this._startTimer(); } } }, updated: function() {//重写父类的updated方法 Demo.Timer.callBaseMethod(this, "updated");//调用父类的updated方法 this._stopTimer(); if (this._enabled) { this._startTimer(); } }, get_enabled: function() { return this._enabled; }, set_enabled: function(value) { if (value != this._enabled) { this._enabled = value; this.raisePropertyChanged("enabled"); if (!this.get_isUpdating()) {//不出于更新状态 if (value) { this._startTimer(); } else { this._stopTimer(); } } } }, dispose: function() { this.set_enabled(false); this._stopTimer(); Demo.Timer.callBaseMethod(this, "dispose"); } } Demo.Timer.registerClass("Demo.Timer", Sys.Component);
创建一个aspx的页面
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="BetterTimer.aspx.cs" Inherits="Demo12_BetterTimer" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title></title> </head> <body> <form id="form1" runat="server"> <asp:ScriptManager ID="ScriptManager1" runat="server"> <Scripts> <asp:ScriptReference Path="~/Demo12/BetterTimer.js" /> </Scripts> </asp:ScriptManager> Interval:<input type="text" value="1000" id="txtInterval" /><br /> <input type="checkbox" id="chkEnabled" checked="checked" /> <label for="chkEnabled">Enabled</label><br /> <input type="button" value="Change" onclick="changeStatus()" /> <hr /> <div id="display"></div> <script language="javascript" type="text/javascript"> Sys.Application.add_init(function() { $create(Demo.Timer, { "id": "timer", "enabled": true }, { "tick": onTick }); }); window.counter = 0; function onTick() { $get("display").innerHTML = window.counter++; } function changeStatus() { var timer = $find("timer"); timer.beginUpdate(); timer.set_interval(parseInt($get("txtInterval").value, 10)); timer.set_enabled($get("chkEnabled").checked); timer.endUpdate(); } </script> </form> </body> </html>
Control模型(可视化组件模型)
- 封装一个DOM元素
- 提供统一的开发模型
- 可以用于开发复杂组件
- 构造函数接受一个element参数,表示这个组件封装的DOM元素
Sys.UI.Control类成员
- element只读属性//要封装的元素
- visibilityMode属性//枚举:hide(0)相当于style.visibility,collapse(1)相当于style.display
- visable属性//bool,可见性
- addCssClass方法//添加一个class样式
- removeCssClass方法//去除一个class样式
- toggleCssClass方法//如果没有一个class样式,而添加,如果有则去除
- parent属性//
- onBubbleEvent方法
- raiseButtleEvent方法
一个使用Control模型的示例
创建一个名为TextBox.js的文件
/// <reference name="MicrosoftAjax.js"/> Type.registerNamespace("Demo"); Demo.TextBox = function(element) {//注意这里有一个参数,是要封装的DOM元素 Demo.TextBox.initializeBase(this, [element]); this._originalText = null;//保存元素中原先有的值 } Demo.TextBox.prototype = { initialize: function() { Demo.TextBox.callBaseMethod(this, "initialize"); //调用父类方法 //响应文本框的change事件 $addHandler(this.get_element(), "change", Function.createDelegate(this, this._onTextChange)); }, _onTextChange: function(e) { if (this.get_isUpdating()) { return; } var handler = this.get_events().getHandler("textChange"); if (handler) { var args = new Sys.CancelEventArgs(); handler(this, args); if (args.get_cancel()) { this.beginUpdate(); //这样做,是为了设置文本框的text改变的时候,又会触发一个textChange事件 this.set_text(this._originalText); this.endUpdate(); } } this._originalText = this.get_element().value; }, add_textChange: function(handler) { this.get_events().addHandler("textChange", handler); }, remove_textChange: function(handler) { this.get_events().removeHandler("textChange", handler); }, //一个text属性 get_text: function() { return this.get_element().value; //get_element方法,返回封装的DOM元素 }, set_text: function(value) { if (value != null) { this.get_element().value = value; } else { this.get_element().value = ""; } } } Demo.TextBox.registerClass("Demo.TextBox", Sys.UI.Control);
创建一个aspx页面
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="TextBox.aspx.cs" Inherits="Demo12_TextBox" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title></title> </head> <body> <form id="form1" runat="server"> <asp:ScriptManager ID="ScriptManager1" runat="server"> <Scripts> <asp:ScriptReference Path="~/Demo12/TextBox.js" /> </Scripts> </asp:ScriptManager> <input type="text" id="textBox" /> <script language="javascript" type="text/javascript"> Sys.Application.add_init(function() { $create(Demo.TextBox,//创建的组件类型 null,//这里不设置ID { "textChange": onTextChange },//响应事件 null, $get("textBox"));//需要封装的元素 }); function onTextChange(sender, args) { var message = String.format("The text would be changed to '{0}',are you sure?", sender.get_text()); if (!confirm(message)) { args.set_cancel(true);//确定“取消操作” } } </script> </form> </body> </html>
这样,我们实现了文本在改变以后,提示用户是不是确定操作,如果不确定这次操作,则可以撤销这次操作,注意,textChange是在改变文本后,焦点离开文本框以后触发的
我们使用这个组件,对一个普通的textbox进行的封装,这就是一个Control模型的使用示例
$creat方法
- 原型:$creat(type,properties,events,references,element);
- referencts是一个字典,保存对象属性与其他对象的关系,key为属性名,value为其他对象id
- 保证initialize方法调用时,属性已经被设置为所需要的对象,几十调用$creat方法时,其他对象还没有创建
复合控件
复合控件主要会涉及到Control模型中的以下两个方法
- raiseBubbleEvent(source,args);//由子控件调用,将触发的事件向父控件传递
- onBubbleEvent(source,args);//父控件重写该方法,用于接受子控件向上传递过来的事件
这两个方法的主要作用是降低父控件和子控件之间的耦合关系,例如子控件不需要知道它的父控件是谁,只需要调用这个方法,把触发的事件向上传递就好啦,至于由谁来接受,这属于另外一个控件的设计啦
一个复合控件的示例
创建一个名为ButtonList.js的文件
/// <reference name="MicrosoftAjax.js"/> Type.registerNamespace("Demo"); Demo.Button = function(element) { Demo.Button.initializeBase(this, [element]); this._context = null;//上下文对象,用于保存控件的一些信息 this._onClickHandler = null;//click事件的监听器 } Demo.Button.prototype = { initialize: function() { Demo.Button.callBaseMethod(this, "initialize");//调用基类方法 this._onClickHandler = Function.createDelegate(this, this._onClick); $addHandler(this.get_element(), "click", this._onClickHandler); }, //释放资源,防止资源泄漏 dispose: function() { this._onClickHandler = null; $clearHandlers(this.get_element()); Demo.Button.callBaseMethod(this, "dispose"); }, //元素点击后会调用 _onClick: function(e) { var eventArgs = new Demo.ButtonClickEventArgs(this._context); this.raiseClick(eventArgs); }, //context属性 get_context: function() { return this._context; }, set_context: function(value) { this._context = value; }, //把事件向上传递 raiseClick: function(args) { this.raiseBubbleEvent(this, args); } } Demo.Button.registerClass("Demo.Button", Sys.UI.Control);//注意,继承自Sys.UI.Control //自定义的EventArgs Demo.ButtonClickEventArgs = function(context) { Demo.ButtonClickEventArgs.initializeBase(this); this._context = context; } Demo.ButtonClickEventArgs.prototype = { get_context: function() { return this._context; } } Demo.ButtonClickEventArgs.registerClass("Demo.ButtonClickEventArgs", Sys.EventArgs);//继承自Sys.EventArgs Demo.ButtonList = function(element) { Demo.ButtonList.initializeBase(this, [element]); this._itemDataList = null;//存储组件内包含的组件对象 } Demo.ButtonList.prototype = { initialize: function() { Demo.ButtonList.callBaseMethod(this, "initialize"); for (var i = 0; i < this._itemDataList.length; i++) { this._createNewButton(this._itemDataList[i]); } }, _createNewButton: function(data) { var buttonElement = document.createElement("input");//创建一个Input元素 buttonElement.type = "button";//元素的type为button(按钮) buttonElement.value = data.text;//按钮上的文字 this.get_element().appendChild(buttonElement);//按钮添加到这个组件上 //把创建的元素,用上面定义的组件进行封装 $create(Demo.Button, { "context": data.context, "parent": this },//指定父控件为自身(ButtonList) null, null, buttonElement); }, get_itemDataList: function() { return this._itemDataList; }, set_itemDataList: function(value) { this._itemDataList = value; }, //当有事件被传递上来的时候调用 onBubbleEvent: function(source, e) { this.raiseItemClick(e); return true;//表示事件已经被处理掉了,不需要向上传递 }, add_itemClick: function(handler) { this.get_events().addHandler("itemClick", handler); }, remove_itemClick: function(handler) { this.get_events().removeHandler("itemClick", handler); }, raiseItemClick: function(args) { var handler = this.get_events().getHandler("itemClick"); //如果有这个事件 if (handler) { handler(this, args); } } } Demo.ButtonList.registerClass("Demo.ButtonList", Sys.UI.Control);
然后,创建一个aspx页面
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="ButtonList.aspx.cs" Inherits="Demo12_ButtonList" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title></title> </head> <body> <form id="form1" runat="server"> <asp:ScriptManager ID="ScriptManager1" runat="server"> <Scripts> <asp:ScriptReference Path="~/Demo12/ButtonList.js" /> </Scripts> </asp:ScriptManager> <div id="buttonList"></div> <script language="javascript" type="text/javascript"> Sys.Application.add_init( function() { var datalist = [ { text: "Item1", context: "Item 1 has been clicked!" }, { text: "Item2", context: "Item 2 has been clicked!" }, { text: "Item3", context: "Item 3 has been clicked!" } ]; $create(Demo.ButtonList,//组件类型 { "itemDataList": datalist },//指定属性 { "itemClick": onItemClick },//响应事件 null, $get("buttonList"));//要封装的元素 } ); function onItemClick(sender, args) { alert(args.get_context());//得到args的context属性 } </script> </form> </body> </html>
运行页面,我们点击按钮就会看到弹出的结果,注意,这里的click事件虽然是子控件(Button)发起的,但是最后处理它的是我们创建的复合控件(ButtonList),这就是我们的raiseBubbleEvent方法和onBubbleEvent方法的功能和它们的使用方法
Behavior模型
- 另外一种可视化组件模型,继承与Sys.UI.Behavior
- Control包装DOM元素,Behavior为DOM元素提供功能
- 一个DOM元素智能由一个Control来包装,但是可以使用多个Behavior进行装饰
Behavior成员
- 与Component组件相比唯一增加的属性是name
- 由于一个M元素上可以添加多个Behavior,因此如果要通过元素获得Behavior对象就需要通过name属性获得
- $get("elementId")["name_of_behavior"]