ASP.NET AJAX Development Approach Part 4

              Contents

·         1 Introduction

·         2 AJAX Controls

·         3 Control Functionality

·         4 Script Registration

·         5 Thinking Along AJAX Lines

·         6 Consuming the Component

·         7 Conclusion

Introduction

We’ve already discussed many of the new features that ASP.NET AJAX brings to client-side development in parts 1 through 3 of this series. The next subject we’re going to take a look at is developing an example extender using the ASP.NET AJAX approach of developing AJAX components.

I mentioned previously that controls and extenders have both a server and a client API. The server API is responsible for describing the client-side component so both the server and the client side can interoperate (as much as it does). This description process also allows the client-side code to receive values set in the server-side code.

ASP.NET AJAX framework Series

·  Part 1 Overview of the client portion of the ASP.NET AJAX framework.

·  Part 2 Overview of the client portion of the ASP.NET AJAX framework.

·  Part 3 Overview of the client portion of the ASP.NET AJAX framework.

·  Part 4 Overview of the server portion of the ASP.NET AJAX framework.

·  Part 5 Overview of rendering the UI of a control with ASP.NET AJAX.

AJAX Controls

It’s first important to understand a little more about the control process. AJAX controls have at least two code files associated with them: a CS or VB code file that contains the server code, and the .JS file (as an embedded resource) that contains the client code. Both portions are married together to form one entity, the AJAX component. At runtime, the server pushes values down to the client through the description process. The server knows about the client because the code has to identify fully the information about the client component in the script description and reference process.

Control Functionality

Without getting too theoretical about the process, I’d like to go into the sample that is a functional CheckboxList control, called the ClientCheckboxList. This control inherits from CheckBoxList so it doesn’t lose the existing functionality. To take advantage of AJAX functionality, the control implements from IScriptControl. This component will utilize the existing structure and extend it to expose client side properties and events that can be accessed in an ASPX page.

Listing 1: Structure of Server Component

1.public class ClientCheckboxList : CheckBoxList, IScriptControl { }

The component has a few properties inherited from the base ListControl class; one of which is the SelectedIndex property, an important property for this example. Another index property that works similarly is the ActiveIndex, which stores the index of the last item with activity, whether selected or unselected.

AJAX also allows event definition, which is defined using a property. The property read and writes the name of a JavaScript event handler that will receive notification of that event. This works very close to the way .NET registers event handlers. Let’s take a look at the server properties; I’ll touch upon that process later.

Listing 2: ClientCheckboxList server properties

01./// <summary>

02./// Gets the index for the item in the collection, which is active (not necessarily

03./// selected).

04./// </summary>

05.public int ActiveIndex

06.{

07.    get { return (int)(ViewState["ActiveIndex"] ?? -1); }

08.    set { ViewState["ActiveIndex"] = value; }

09.}

10./// <summary>

11./// Gets or sets a client method to receive a call to the all items selected event.

12./// </summary>

13.public string OnClientAllItemsSelected

14.{

15.    get

16.    {

17.        object o = ViewState["OnClientAllItemsSelected"];

18.        return (o == null) ? null : (string)o;

19.    }

20.    set { ViewState["OnClientAllItemsSelected"] = value; }

21.}

22./// <summary>

23./// Gets or sets a client method to receive a call to the item selected event.

24./// </summary>

25.public string OnClientItemSelected

26.{

27.    get { return (string)(ViewState["OnClientItemSelected"] ?? null); }

28.    set { ViewState["OnClientItemSelected"] = value; }

29.}

30./// <summary>

31./// Gets or sets a client method to receive a call to the all item toggled event.

32./// </summary>

33.public string OnClientItemToggled

34.{

35.    get { return (string)(ViewState["OnClientItemToggled"] ?? null); }

36.    set { ViewState["OnClientItemToggled"] = value; }

37.}

38./// <summary>

39./// Gets or sets a client method to receive a call to the no items selected event.

40./// </summary>

41.public string OnClientNoItemsSelected

42.{

43.    get

44.    {

45.        object o = ViewState["OnClientNoItemsSelected"];

46.        return (o == null) ? null : (string)o;

47.    }

48.    set { ViewState["OnClientNoItemsSelected"] = value; }

49.}

These properties are mostly important for the description process, as the values will be pushed down to the client. The description process uses a ScriptControlDescriptor to describe the properties that gets the server component’s values passed down to the client.

Listing 3: Descriping Properties and Events

01.public IEnumerable<ScriptDescriptor> GetScriptDescriptors()

02.{

03.    ScriptBehaviorDescriptor descriptor = new

04.         ScriptBehaviorDescriptor("Nucleo.Web.ListControls.ClientCheckboxList",

05.         this.ClientID);

06.    descriptor.AddProperty("activeIndex", this.ActiveIndex);

07.    descriptor.AddProperty("selectedIndex", this.SelectedIndex);

08.    descriptor.AddElementProperty("newItemsClientState", this.NewItemClientStateID);

09.    descriptor.AddElementProperty("removedItemsClientState",

10.         this.RemovedItemClientStateID);

11.    descriptor.AddEvent("allItemsSelected", this.OnClientAllItemsSelected);

12.    descriptor.AddEvent("noItemsSelected", this.OnClientNoItemsSelected);

13.    descriptor.AddEvent("itemSelected", this.OnClientItemSelected);

14.    descriptor.AddEvent("itemToggled", this.OnClientItemToggled);

15.    return new ScriptDescriptor[] { descriptor };

16.}

Notice all of the described items are properties at the server level (some of which aren’t fully functional yet, as I haven’t completely implemented them). Also notice that the client names of the properties (in AddProperty and AddElementProperty) are lower-camel case and defined on the client this way. This differs from the server, of course, as the server uses upper-camel case. Events are passed using the AddEvent method. If the method name is actually null (which is a good thing if there isn’t an event handler), the event declaration is ignored, so no handler is added.

Two methods are very helpful with the description process, one of which is shown. The AddElementProperty method, on the server at runtime when the description process happens, actually passes down a live reference to that HTML element, which is very useful. The AddComponentProperty method would pass down a reference to a client AJAX component. Elements can be referenced through the $get helper method and components through $find, but this is a shortcut to get access to the object.

On the client, these values are passed down to the following objects:

Listing 4: Client Object’s Properties/Events

001.Nucleo.Web.ListControls.ClientCheckboxList.prototype = {

002.    //******************************************************

003.    // Properties

004.    //******************************************************

005.    get_activeIndex : function()

006.    {

007.        return this._activeIndex;

008.    },

009.    set_activeIndex : function(value)

010.    {

011.        if (this._activeIndex != value)

012.        {

013.            this._activeIndex = value;

014.            this.raisePropertyChanged("activeIndex");

015.        }

016.    },

017.    get_checkboxes : function()

018.    {

019.        if (this._checkboxes == null)

020.            this._processCheckboxes();

021.        return this._checkboxes;

022.    },

023.    get_newItemsClientState : function()

024.    {

025.        return this._newItemsClientState;

026.    },

027.    set_newItemsClientState : function(value)

028.    {

029.        if (this._newItemsClientState != value)

030.        {

031.            this._newItemsClientState = value;

032.            this.raisePropertyChanged("newItemsClientState");

033.        }

034.    },

035.    get_removedItemsClientState : function()

036.    {

037.        return this._removedItemsClientState;

038.    },

039.    set_removedItemsClientState : function(value)

040.    {

041.        if (this._removedItemsClientState != value)

042.        {

043.            this._removedItemsClientState = value;

044.            this.raisePropertyChanged("removedItemsClientState");

045.        }

046.    },

047.    get_selectedIndex : function()

048.    {

049.        return this._selectedIndex;

050.    },

051.    set_selectedIndex : function(value)

052.    {

053.        if (this._selectedIndex != value)

054.        {

055.            this._selectedIndex = value;

056.            this.raisePropertyChanged("selectedIndex");

057.        }

058.    },

059.    //******************************************************

060.    // Event Methods

061.    //******************************************************

062.    add_allItemsSelected : function(handler)

063.    {

064.        this.get_events().addHandler("allItemsSelected", handler);

065.    },

066.    remove_allItemsSelected : function(handler)

067.    {

068.        this.get_events().removeHandler("allItemsSelected", handler);

069.    },

070.    _onAllItemsSelected : function()

071.    {

072.        var handler = this.get_events().getHandler("allItemsSelected");

073.        if (handler != null)

074.            handler(this, Sys.EventArgs.Empty);

075.    },

076.    add_noItemsSelected : function(handler)

077.    {

078.        this.get_events().addHandler("noItemsSelected", handler);

079.    },

080.    remove_noItemsSelected : function(handler)

081.    {

082.        this.get_events().removeHandler("noItemsSelected", handler);

083.    },

084.    _onNoItemsSelected : function()

085.    {

086.        var handler = this.get_events().getHandler("noItemsSelected");

087.        if (handler != null)

088.            handler(this, Sys.EventArgs.Empty);

089.    },

090.    add_itemSelected : function(handler)

091.    {

092.        this.get_events().addHandler("itemSelected", handler);

093.    },

094.    remove_itemSelected : function(handler)

095.    {

096.        this.get_events().removeHandler("itemSelected", handler);

097.    },

098.    _onitemSelected : function()

099.    {

100.        var handler = this.get_events().getHandler("itemSelected");

101.        if (handler != null)

102.            handler(this, Sys.EventArgs.Empty);

103.    },

104.    add_itemToggled : function(handler)

105.    {

106.        this.get_events().addHandler("itemToggled", handler);

107.    },

108.    remove_itemToggled : function(handler)

109.    {

110.        this.get_events().removeHandler("itemToggled", handler);

111.    },

112.    _onitemToggled : function()

113.    {

114.        var handler = this.get_events().getHandler("itemToggled");

115.        if (handler != null)

116.            handler(this, Sys.EventArgs.Empty);

117.    }

118.}

The description process passes the values to the setters (prefixed with the set_) and the values can be retrieved using the get_ prefix in client code. Notice that what this is doing is not simply processing data on the client-side; it’s creating a complete object model that can be taken advantage of. On the client, these properties can be get and set, which makes it a powerful combination. Notice that these properties and events are defined in the prototype; I’ve explained the purpose of properties and events, as well as the prototype in my previous AJAX articles.

Moving along, AJAX components have two lifecycle methods (outside of being able to tap into the init, load, and dispose events) that they can take advantage of, shown in Figure 5 below.

Listing 5: Initialize and Dispose methods

01.initialize : function()

02.{

03.    Nucleo.Web.ListControls.ClientCheckboxList.callBaseMethod(this, "initialize");

04.    this._processCheckboxes();

05.},

06.dispose : function()

07.{

08.    for (var i = 0; i <  this.get_checkboxes().length; i++)

09.        $removeHandler(this.get_checkboxes()[i], "click",

10.                this._checkboxClickHandler);

11.    this._checkboxClickHandler = null;

12.    Nucleo.Web.ListControls.ClientCheckboxList.callBaseMethod(this, "dispose");

13.},

14._processCheckboxes : function()

15.{

16.    this._checkboxClickHandler = Function.createDelegate(this,

17.         this.checkboxClickCallback);   

18.    this._checkboxes = this.get_element().getElementsByTagName("input");

19.    for (var i = 0; i < this.get_checkboxes().length; i++)

20.        $addHandler(this.get_checkboxes()[i], "click",

21.         this._checkboxClickHandler);

22.},

Initialize processes the checkbox controls by looping through each check and attaching to it an event handler. This method is called in the processCheckboxes private method. Private methods are noted by the underscore, and although they can still be executed, they should be ignored if prefixed with an underscore. Dispose is responsible for removing the handlers for each checkbox, and to remove the instance of the handler.

So what happens when a checkbox is clicked? This is the most important routine of the entire component. This is important because all of the events fire and the properties get set accordingly.

Listing 6: Handling the check clicks

01.checkboxClickCallback : function(domEvent)

02.{

03.    var checkbox = domEvent.target;

04.    if (checkbox == null) return;

05.    if (checkbox.checked)

06.        this.raise_itemSelected();

07.    var total = this.get_checkboxes().length;

08.    var count = 0;

09.    var hasSelected = false;

10.    for (var i = 0; i < this.get_checkboxes().length; i++)

11.    {

12.        var checkboxToCompare = this.get_checkboxes()[i];

13.        if (checkbox == checkboxToCompare)

14.            this.set_activeIndex(i);

15.        if (checkboxToCompare.checked)

16.        {

17.            if (!hasSelected)

18.            {

19.                this.set_selectedIndex(i);

20.                hasSelected = true;

21.            }

22.            count++;

23.        }

24.    }

25.    if (hasSelected == false)

26.        this.set_selectedIndex(-1);

27.    if (count == 0)

28.        this.raise_noItemsSelected();

29.    else if (count == total)

30.        this.raise_allItemsSelected();

31.    this.raise_itemToggled();

32.}

So what happens? The first part of the process is to get a reference of the checkbox. If the checkbox is checked, fire the itemSelected client event. Next, looping through the checkboxes occur to count the number of checked checkboxes. While this is occurring, the selected index is set to the lowest index of the checkbox list; otherwise, its defaulted to -1. The active index is also set to the currently active item, whether selected or deselected. At the end of the script, if the total number of items counted is zero, the noItemsSelected client event fires. If the count of items matches the total, the allItemsSelected event fires. Otherwise, the itemToggled event fires. While it may seem complicated, it isn’t overly complicated, but AJAX scripting features need properly planned.

Script Registration

The final process that has to occur is to register the script. There are two steps that have to occur in OnPreRender and Render. They are shown below.

Listing 7: Script Registration

01.protected override void OnPreRender(EventArgs e)

02.{

03.    ScriptManager manager = ScriptManager.GetCurrent(this.Page);

04.    manager.RegisterScriptControl<ClientCheckboxList>(this);

05.    base.OnPreRender(e);

06.    base.EnsureID();

07.    ScriptManager.RegisterHiddenField(this, this.NewItemClientStateID,

08.         string.Empty);

09.    ScriptManager.RegisterHiddenField(this, this.RemovedItemClientStateID,

10.         string.Empty);

11.}

12.protected override void Render(HtmlTextWriter writer)

13.{

14.    if (!base.DesignMode)

15.    {

16.        ScriptManager manager = ScriptManager.GetCurrent(this.Page);

17.        manager.RegisterScriptDescriptors(this);

18.    }

19.    base.Render(writer);

20.}

The two process that must occur are:

·         Call RegisterScriptControl or RegisterExtenderControl to register the control with the ScriptManager component on PreRender

·         Call the RegisterScriptDescriptors method to register the descriptors with the ScriptManager component on Render

These two methods are key methods to getting the component to work. They are already defined in the ScriptControl base class, so if you implement from ScriptControl or any derivative thereof, you don’t have to worry about this process. Another situation where you will have to worry about it is if you override the Render method, and do not call base.Render(). In this case, script descriptor registration doesn’t occur and you have to add the code manually to your component.

Thinking Along AJAX Lines

In thinking AJAX, one of the features I was adding was the ability to add and remove items dynamically on the client to the list through a method. This method would then write the values to a hidden field, and I could parse the hidden field on the server to add or remove those items from the list. Turns out there was some trouble rendering the interface correctly, which I’m rethinking that approach, and so the component wasn’t completely finished.

But these kinds of features can be added into components, and as you design AJAX components, think about these kinds of features into your controls and extenders. That’s thinking the AJAX way.

Consuming the Component

As a test, I created the following user control:

Listing 8: Consuming the Control

01.<%@ Control Language="C#" AutoEventWireup="true"

02.    CodeBehind="ClientCheckboxListTest.ascx.cs"

03.    Inherits="Nucleo.Web.ListControls.ClientCheckboxListTest" %>

04.<script language="javascript" type="text/javascript"> 

05.    function ClientCheckboxListTest_AllItemsSelected(e)

06.    {

07.        alert('all items');

08.    }

09.    function ClientCheckboxListTest_ItemSelected(e)

10.    {

11.        alert('item selected');

12.    }

13.    function ClientCheckboxListTest_ItemToggled(e)

14.    {

15.        alert('item toggled');

16.    }

17.    function ClientCheckboxListTest_NoItemsSelected(e)

18.    {

19.        alert('no items');

20.    }

21.    function clearAll()

22.    {

23.        var checkbox = $find("<%= extItems.ClientID %>");

24.        checkbox.clearAll();

25.    }

26.    function selectAll()

27.    {

28.        var checkbox = $find("<%= extItems.ClientID %>");

29.        checkbox.selectAll();

30.    }

31.</script>

32.<n:ClientCheckboxList ID="extItems" runat="server" RepeatDirection="Vertical"

33.    RepeatLayout="Flow"

34.    OnClientAllItemsSelected="ClientCheckboxListTest_AllItemsSelected"

35.    OnClientNoItemsSelected="ClientCheckboxListTest_NoItemsSelected"

36.    OnClientItemSelected="ClientCheckboxListTest_ItemSelected"

37.    OnClientItemToggled="ClientCheckboxListTest_ItemToggled">

38.    <asp:ListItem>1</asp:ListItem>

39.    <asp:ListItem>2</asp:ListItem>

40.    <asp:ListItem>3</asp:ListItem>

41.    <asp:ListItem>4</asp:ListItem>

42.    <asp:ListItem>5</asp:ListItem>

43.</n:ClientCheckboxList>

44.<br />

45.<input type="button" value="Select All" οnclick="selectAll()" />

46.    

47.<input type="button" value="Clear All" οnclick="clearAll()" />

48.<br /><br />

49.Text: <asp:TextBox ID="txtNewItemText" runat="server" /><br />

50.Value: <asp:TextBox ID="txtNewItemValue" runat="server" /><br />

51.Selected: <asp:CheckBox ID="chkNewItemSelected" runat="server" /><br />

52.Enabled: <asp:CheckBox ID="chkNewItemEnabled" runat="server" /><br />

53.<br />

54.<input type="button" value="Add New Item" οnclick="addItem()" />

55.<br /><br />

56.<asp:Button ID="btnPostback" runat="server" Text="Postback" />

As each item is checked, the correct events fire an alert to the screen. Also, two buttons allow for selecting all of, and clearing of, the checkbox values through two helpful methods in the component (omitted, but in the code sample).

Notice the use of $find. This method finds the client component by the control’s ID value. As I said before, an AJAX control has a server and client piece, and $find returns a reference to the underlying control.

Conclusion

Hopefully you’ve learned more about the control development process, how it gets described, where the values go, what can be exposed, and how it can be used.

 

转载于:https://www.cnblogs.com/likedotnet/archive/2010/12/01/1893408.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值