代码改变世界

讲座展示:TechEd Europe DEV344 - ASP.NET AJAX Control Toolkit(下)

2006-11-18 19:18 Jeffrey Zhao 阅读(...) 评论(...) 编辑 收藏

讲座内容

这次我选择的讲座内容是最近在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也会相应的进步。