Microsoft AJAX Library定义了一个客户端组件的模型,它的基类是Sys.Component,它实现了三个接口Sys.IDisposable,Sys.INotifyDisposing,Sys.INotifyPropertyChange
可视组件,就是对DOM进行了封装,在Microsoft AJAX Library中可分为两种Sys.UI.Control和Sys.UI.Behavior,不可视组件不继承于Control和Behavior,它是一种辅助对象
这里的声明周期,很像我们的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方法被执行
创建一个名为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
组件处于正在更新的状态称为Update状态,处于更新状态时候组件的数据可能出于不一致的状态,因此,出于更新状态的组件,允许组件处于不一直的状态,但是应该尽量避免与外接的交换,尤其是处于DOM元素有关的交互,有时候,合理的利用Update状态也能够在一定程序上提高性能
windows的DOM事件load被触发
Sys.Application对象的beginCreatComponent方法被调用
SysApplication对象的Init事件被触发
$Creat方法被调用,用于创建对象
组件的beginUpdate方法被调用
部分组件的endUpdate方法被调用
如果他们还没有被初始化,而initialize方法被调用
他们的Updated方法被调用
Sys.Application对象的endCreatComponent方法被调用
剩余组件的endUpdate方法被调用
创建一个名为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>
创建一个名为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模型的使用示例
复合控件主要会涉及到Control模型中的以下两个方法
这两个方法的主要作用是降低父控件和子控件之间的耦合关系,例如子控件不需要知道它的父控件是谁,只需要调用这个方法,把触发的事件向上传递就好啦,至于由谁来接受,这属于另外一个控件的设计啦
创建一个名为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方法的功能和它们的使用方法