讲座内容
这次我选择的讲座内容是最近在TechEd 2006 Europe中Shawn Burke的讲座“ASP.NET AJAX Control Toolkit Unleashed: Creating Rich Client-Side Controls and Components”。Shawn Burke是微软.NET Developer Platform总监。这个讲座的PPT和演示代码的下载地址已经由他本人公布在他的Blog上(本地下载)。另外,在MSDN's Showtime上也已经有了这个讲座的完整视频。
此次讲座的内容主要是对于ASP.NET AJAX Control Toolkit进行简单的介绍,展示了Extender控件是如何帮助ASP.NET开发人员简单地将丰富的用户体验集成到他们的Web应用程序中。在这次讲座里将看到应该如何在您的应用中使用ASP.NET AJAX Control Toolkit中的组件,并且了解开发人员是如何方便地开发一个APS.NET AJAX Extender的。
此次讲座分为两部分:“ASP.NET AJAX Control Toolkit介绍和使用”以及“开发一个Extender控件”。本文将对于该讲座的第二部分进行讲述,并且对其第二个演示的剩余部分进行分析。
讲座演示
请注意,在Shawn提供的压缩包内有三个空文件夹:Demo_Base、Demo_Step_2和Demo_Step_3,现在它们在演示时也是有内容的,我已经根据讲座视频设法将它们恢复到了演示时的样子,请点击这里下载。
Step 2:Consuming other behaviors / Handling events / Coding Client-Side Logic
我们可以发现,Extender在客户端生成了代码,能够构造Behavior对象,也正确设置了Behavior的属性。在大多数情况下,下面要做就是可能会使用Toolkit中其他一些组件的功能。这也是我们在早期版本Toolkit中的一个挑战:如何才能使各种组件能够组合使用?结合各种组件的功能能够使UI变得更加有趣,我们也能获得更灵活的控制能力。实现这个功能的一个难点就是,这些组件都是JavaScript。每个组件都有自己的JavaScript文件,他们会被编译在程序集中。在使用Extender时,Tookit会知道应该使用哪些JavaScript文件,并将它们引入到页面中。
那么我们该如何引入别的Component的JavaScript文件呢?如果确定它们的脚本被正确加载了?在服务器端有一个CustomAttribute:RequiredScriptAttribute。它的作用就是告诉服务器:“嘿,你们必须在我加载之前加载到页面中”,这样在客户端就能出现能够正确运行的脚本类库了。在客户端,我们能够使用$create方法创建一个组件。我们一会儿就来看一下应该如何使用。
现在我们会使用另外两个组件,它们原本定义在Atlas中,但是在ASP.NET Beta版本中被移除了。不过它们被转移到了开源的Toolkit中,这样开发人员就能继续使用它们了。他们就是HoverExtender和PopupExtender。我们先在客户端Behavor的构造函数中添加这两个组件和一些其它的变量声明。如下:
FontSize.FontSizeBehavior = function(element) {
...
// the behaviors we create
//
this._popupBehavior = null;
this._hoverBehavior = null;
// Handler delegates
//
this._hoverHandler = null;
this._unhoverHandler = null;
this._incrementHandler = null;
this._decrementHandler = null;
// state bits
//
this._fontSize = 0;
this._popupVisible = false;
this._fontUnit = "px";
}
然后我们需要在Initialize方法中使用这两个Behavior。首先我们使用HoverBehavior,添加如下代码:
// setup the hover behavior
//
if (this._hoverCssClass && this._sizePanelElement) {
this._hoverHandler = Function.createDelegate(this, this._onTargetHover);
this._unhoverHandler = Function.createDelegate(this, this._onTargetUnhover);
this._hoverBehavior = $create(
AjaxControlToolkit.HoverBehavior,
{unhoverDelay:200, hoverElement: this._sizePanelElement},
null, null, this.get_element());
this._hoverBehavior.add_hover(this._hoverHandler);
this._hoverBehavior.add_unhover(this._unhoverHandler);
}
HoverBehavior的hover事件会在鼠标移到某个元素上被触发,我们可以使用它在UI上显示一个良好的鼠标移进/移出效果。首先我们会建立两个用于触发事件的handler:_hoverHandler和_unhoverHandler。然后使用$create方法创建一个HoverBehavior并且与当前Behavior的元素绑定起来。$create方法会使ASP.NET AJAX跟踪这个对象,例如可以在合适的时候销毁它。在$create的第二个参数中会指定一些属性,unhoverDelay表示鼠标移开在多少毫秒后会触发unhover事件,hoverElement指定了另一个对象,以避免在移动到这个元素后unhover事件被触发。最后一个参数就是Behavior需要绑定的元素,通过get_element方法可以获得当前的元素,比如在使用WatermarkExtender时就会得到那个文本框,这里就是我们希望改变字体大小的Panel。最后将handler注册给hover和unhover事件。
接着我们使用PopupBehavior,代码如下:
// setup the popup behavior
//
if (this._sizePanelElement) {
this._popupBehavior = $create(
AjaxControlToolkit.PopupBehavior,
{id: this.get_id() + "_Popup", // make the ID derive from our ID for uniqueness
positioningMode: AjaxControlToolkit.PositioningMode.TopRight,
parentElement: this.get_element()},
null, null, this._sizePanelElement);
}
PopupBehavior的作用是帮助我们显示和隐藏某个UI。这里的代码和上面的比较相似,我们通过当前的Panel的客户端ID来获得PopupBehavior的ID,这样保证了这个ID的唯一性。positioningMode指定了元素在弹出时相对于parentElement的位置。
然后我们增加一些我们需要的Handler。代码如下:
_onTargetHover : function(eventArgs) {
var e = this.get_element();
Sys.UI.DomElement.addCssClass(e, this._hoverCssClass);
if (this._popupBehavior && !this._popupVisible) {
this._popupVisible = true;
// call show to make the the popup visible
//
this._popupBehavior.show();
}
},
_onTargetUnhover : function(eventArgs) {
var e = this.get_element();
Sys.UI.DomElement.removeCssClass(e, this._hoverCssClass);
if (this._popupBehavior && this._popupVisible) {
this._popupVisible = false;
this._popupBehavior.hide();
}
},
在Hover事件被触发时,也就是用户将鼠标移动到了Panel上,我们会调用PopupBehavior的show方法,用以显示它的UI。在Unhover事件触发时,则调用hide方法使UI隐藏。
我们现在编译代码,然后刷新页面。哎,出错了。如图:
这里出错的原因是因为HoverBehavior没有定义。因此我会回到Extender的代码,添加RequiredScriptAttribute:
[Designer(typeof(FontSizeDesigner))]
[ClientScriptResource("FontSize.FontSizeBehavior", "FontSize.FontSizeBehavior.js")]
[TargetControlType(typeof(Control))]
[RequiredScript(typeof(PopupExtender))]
[RequiredScript(typeof(HoverExtender))]
public class FontSizeExtender : ExtenderControlBase
{
...
}
在这里我们传入PopupExtender和HoverExtender的类型,这使Toolkit在使用当前Behavior时保证了PopupBehavior和HoverBehavior的脚本已经被加载了。我们重新编译代码,打开页面。没有发现异常。
当我们将鼠标移动至上方的Panel时,可以发现含有按钮的Panel显示在了右上角。如图:
可以发现,当我们把鼠标移动到右上角的按钮时,它们也不会消失,这个就是HoverBehavior的hoverElement属性的作用。当鼠标移到其它地方时,右上角的按钮就消失了。而PopupBehavior的作用就是显示和隐藏制定的元素。
现在我们需要来处理按钮的功能了。我们回到Behavior的代码,首先在initialize方法内添加一些简单的代码。如下:
initialize : function() {
FontSize.FontSizeBehavior.callBaseMethod(this, 'initialize');
...
// set up our handlers for the +/- behavior
//
if (this._incrementElement) {
this._incrementHandler = Function.createDelegate(this, this._onIncrementFontSize);
$addHandler(this._incrementElement, "click", this._incrementHandler);
}
if (this._decrementElement) {
this._decrementHandler = Function.createDelegate(this, this._onDecrementFontSize);
$addHandler(this._decrementElement, "click", this._decrementHandler);
}
},
就像我们之前所提到的那样,ASP.NET AJAX为了跨浏览器,提供了一些方法让我们进行统一的操作。在这里,我们使用了$addHandler方法,它为任意浏览器中都提供了相同的注册客户端事件功能。比如我们使用了$addHandler方法为增大字体的按钮注册了click事件。如果您写JavaScript代码的话,使用的是“onclick”,请注意这里只是“click”,因为ASP.NET AJAX需要在多个浏览器中工作。$addHandler方法的第一个参数则是需要为它添加事件的HTML元素,在这里就是“增大”按钮。最后一个则是我们需要注册给click事件的方法。
接着我们需要添加一些方法。首先我们在这里要添加的是一个辅助方法_setFontSize,如下:
_setFontSize : function(size) {
// helper function for setting the font size
//
this._fontSize = size;
// append the units.
//
this.get_element().style.fontSize = size + this._fontUnit;
},
由于用户可能以不同的单位来设置字体,例如px或者pt,因此我们提供了_fontUnit。在这里get_element()方法获得了当前Behavior所在的元素,也就是需要改变字体大小的Panel。然后就以最普通不过的方法设置其字体。
然后我们再定义增大和减小字体的方法,如下:
_onDecrementFontSize : function(eventArgs) {
var fontSize = this._getCurrentFontSize();
if (fontSize > this.get_MinFontSize()) {
fontSize -= this._fontSizeIncrement;
}
this._setFontSize(fontSize);
eventArgs.preventDefault();
},
_onIncrementFontSize : function(eventArgs) {
var fontSize = this._getCurrentFontSize();
if (fontSize < this.get_MaxFontSize()) {
fontSize += this._fontSizeIncrement;
}
this._setFontSize(fontSize);
eventArgs.preventDefault();
},
这是两个非常简单的Handler,在点击两个按钮时会调用它们。在这两个方法里,首先使用_getCurrentFontSize方法得到当前的字体大小,然后改变目标元素的字体大小。需要注意的是,我们这里还调用了eventArgs的preventDefault方法。这里又带来了一个跨浏览器的问题。如何在跨浏览器的情况下组织一个事件被继续传播(propagation)?例如我们需要在相应一个Label的click事件后阻止默认的响应方式。在IE您会使用“return false”这样的作法,而在FireFox里您则会使用“e.returnValue = false”,而使用ASP.NET AJAX话您只需要使用同一种方法。
下面则是_getCurrentFontSize方法。这是一个巧妙的方法,可以用来“测量”出某个元素当前字体大小,在这里就是当前Panel的文字大小。如下(代码较长,就不完整贴出了,朋友们可以看一下附件里的代码):
_getCurrentFontSize : function() {
// this function figures out the current font size of the element.
//
...
return this._fontSize;
},
然后就需要为页面上其它的Panel添加相应的UI和Extender了。如下:
<fontSize:FontSizeExtender ID="FontSizeExtender1" runat="server"
SizePanelControlID="extenderUI1" TargetControlID="pnlLeftColumn"
DecreaseSizeControlID="btnDec1" IncreaseSizeControlID="btnInc1"
PanelHoverCssClass="panelHover" />
<asp:Panel ID="extenderUI1" runat="server" CssClass="popup">
<asp:Button ID="btnDec1" runat="server" Text="-" />
<asp:Button ID="btnInc1" runat="server" Text="+" />
</asp:Panel>
<fontSize:FontSizeExtender ID="FontSizeExtender2" runat="server"
SizePanelControlID="extenderUI2" TargetControlID="pnlMiddleColumn"
DecreaseSizeControlID="btnDec2" IncreaseSizeControlID="btnInc2"
PanelHoverCssClass="panelHover" />
<asp:Panel ID="extenderUI2" runat="server" CssClass="popup">
<asp:Button ID="btnDec2" runat="server" Text="-" />
<asp:Button ID="btnInc2" runat="server" Text="+" />
</asp:Panel>
<fontSize:FontSizeExtender ID="FontSizeExtender3" runat="server"
SizePanelControlID="extenderUI3" TargetControlID="pnlRightColumn"
DecreaseSizeControlID="btnDec3" IncreaseSizeControlID="btnInc3"
PanelHoverCssClass="panelHover" />
<asp:Panel ID="extenderUI3" runat="server" CssClass="popup">
<asp:Button ID="btnDec3" runat="server" Text="-" />
<asp:Button ID="btnInc3" runat="server" Text="+" />
</asp:Panel>
编译,重新打开页面。可以发现,现在点击增大/减小按钮字体已经能够变化了(大家可以打开Demo_Step_3的页面察看一下效果)。可能大家会想一个问题,能不能在Panel之间共享一组控制按钮?在这里是不行的,因为一组按钮会被绑定多个Behavior,在click被触发时也会调用多个事件,这样您会发现所有的Panel字体一起变大或者变小了。所以我们在页面中使用了重复的按钮。
Step 3:Saving Client State / Adding Animation Support
可能大家已经发现了,在页面右下方有一个按钮,当我们点击这个按钮时,页面进行了一个PostBack。这里有个问题,页面上字体大小的改变在PostBack之后失效了,我们需要解决这个问题。
ASP.NET AJAX Control Toolkit提供了一系列的高级特性,其中一点就是提供了Client State机制可以在服务器端和客户端保存和传递状态。另外就像我之前提到的,Toolkit中提供了一套非常灵活的动画效果。我们来看一下应该如何使用它们。
如果要使用Client State机制,在服务器端我们要为Extender添加一个构造函数,将EnableClientState属性设为true。如下:
public FontSizeExtender() {
EnableClientState = true;
}
然后需要添加客户端的脚本,您可以直接使用辅助方法,在客户端_setFontSize方法里添加如下代码:
_setFontSize : function(size) {
// helper function for setting the font size
...
// set the clientState
//
this.set_ClientState(size + ":" + this._fontUnit);
},
您可以使用set_ClientState方法设置一个字符串,在页面PostBack之后在服务器端就可以得到这个值。更加重要的是,在PostBack之后,服务器段也会重新设置ClientState的值。在这里我们设定ClientState的值为当前的字体大小+冒号+当前使用的单位。
当然,我们还必须保证在组件被构造时我们能够重新取回这个值。我们在客户端initialize方法内添加如下代码:
initialize : function() {
FontSize.FontSizeBehavior.callBaseMethod(this, 'initialize');
...
// check client state
//
var clientState = this.get_ClientState();
if (clientState) {
var stateItems = clientState.split(":");
if (stateItems.length) {
this._fontUnit = stateItems[1];
this._setFontSize(parseInt(stateItems[0]));
}
}
},
通过另外一个辅助方法get_ClientState我们可以得到ClientState的值。由于initialize方法会在组件被初始化时被调用,因此组件会在一开始重新获得字体大小和单位等状态。这就是我们在PostBack之间保存字体状态的方法。
编译,重新打开页面,点击按钮改变某个Panel的字体大小。再点击下方按钮,可以发现在PostBack之后字体大小被正确恢复了。
最后我们来为UI的显示和隐藏添加动画效果,首先修改_onTargetHover方法。如下:
_onTargetHover : function(eventArgs) {
var e = this.get_element();
Sys.UI.DomElement.addCssClass(e, this._hoverCssClass);
if (this._popupBehavior && !this._popupVisible) {
this._popupVisible = true;
this._stopAnimations();
// call show to make the the popup visible
//
this._popupBehavior.show();
// now animate it's opacity
//
var anim = $create(
AjaxControlToolkit.Animation.FadeInAnimation,
{target: this._sizePanelElement,
duration: .25, minimumOpacity: 0, maximumOpacity:.75});
// we cache the running animation so we can cancel it if we need to hide.
//
this._sizePanelElement._runningAnimation = anim;
var handler = Function.createDelegate(this,
function() {
this._sizePanelElement._runningAnimation = null;
});
anim.add_ended(handler);
anim.play();
}
},
动画效果也是组件,也能够通过$create方法创建。同样我们还需要修改_onTargetUnhover方法,如下:
_onTargetUnhover : function(eventArgs) {
var e = this.get_element();
Sys.UI.DomElement.removeCssClass(e, this._hoverCssClass);
if (this._popupBehavior && this._popupVisible) {
this._popupVisible = false;
// make sure another animation isn't already running.
//
this._stopAnimations();
var anim = $create(
AjaxControlToolkit.Animation.FadeOutAnimation,
{target: this._sizePanelElement, duration: .15,
minimumOpacity: 0, maximumOpacity:.75});
this._sizePanelElement._runningAnimation = anim;
var handler = Function.createDelegate(this,
function() {
// clear out our state.
//
this._sizePanelElement._runningAnimation = null;
this._popupVisible = false;
this._popupBehavior.hide();
});
anim.add_ended(handler);
anim.play();
}
},
我们还需要添加一个_stopAnimations方法。如下:
_stopAnimations : function() {
// stop any running animation
//
if (this._sizePanelElement && this._sizePanelElement._runningAnimation) {
this._sizePanelElement._runningAnimation.stop();
this._sizePanelElement._runningAnimation = null;
}
},
自然也别忘了给服务器端的Extender添加所需的CustomAttribute:
[RequiredScript(typeof(AnimationScripts))]
编译,重新打开页面。现在可以看到,页面上的按钮的显示和隐藏就会产生美妙的淡入淡出效果了(大家可以打开页面观察一下效果)。
我们现在还在招募志愿的开发人员。我们计划最终会在ASP.NET AJAX Control Tookit中有超过50个组件。最终我们会把这个项目完全交付给社区,这是我们的长期计划。而且随着ASP.NET AJAX的改进,Toolkit也会相应的进步。