Explore Ways to Extend ASP.NET AJAX Client-Side Function
 
Published: 08 Oct 2007
Abstract
With the elegant integration with legacy ASP.NET applications and the introduction of fully object-oriented JavaScript, Microsoft ASP.NET AJAX has been attracting more and more web developers. In this article the author examines the typical ways to extend the ASP.NET AJAX framework from the client side.
by Xianzhong Zhu
Feedback
Average Rating: 
Views (Total / Last 10 Days): 102684/ 111

Introduction

Microsoft AJAX framework provides three means for the developers to extend its client-side functionalitiesbehaviors, visual controls, and non-visual controls.  In cases where we want to extend the behavior of an existing DOM element, we can create a custom Behavior control that inherits from "Sys.UI.Behavior" class. While controls that derive from Sys.UI.Behavior extend the overall behavior of a DOM element, they donot modify the original purpose or intent of the control. When visual controls need to be created that modify the original intent of a DOM element and extend the element’s functionality, the Sys.UI.Control class can be used as the base class for the control. Controls are user interface elements that are associated with DHTML elements and they can manage and control all the rendering for that element. Non-visual controls that do not extend the behavior of a DOM element or extend its overall functionality should derive from the Sys.Component base class. In general, Components are reusable building blocks that can be created declaratively or programmatically. They can easily be wired up to each other through bindings or events and manage their own lifetime.

For the convenience of later discussion, let us first take a look at the relationships between the objects we are interested in.

Figure 1: Hierarchical relationships between Components, Behaviors and Controls

Now, let us look into the above three means one by one.

The Component Way

As mentioned above, behaviors allow you to create specific functionality that can be reused and attached to specific controls and elements on your page.

Now, let us first take a glimpse at the prototype definition of Sys.Component to make us more familiar with the basic functionality of Sys.Component.

Listing 1: Prototype definition for Sys.Component

Sys.Component.prototype = {
      _id: null,
      _idSet: false,
      _initialized: false,
      _updating: false,
      get_events: Sys$Component$get_events,
      get_id: Sys$Component$get_id,
      set_id: Sys$Component$set_id,
      get_isInitialized: Sys$Component$get_isInitialized,
      get_isUpdating: Sys$Component$get_isUpdating,
      add_disposing: Sys$Component$add_disposing,
      remove_disposing: Sys$Component$remove_disposing,
      add_propertyChanged: Sys$Component$add_propertyChanged,
      remove_propertyChanged: Sys$Component$remove_propertyChanged,
      beginUpdate: Sys$Component$beginUpdate,
      dispose: Sys$Component$dispose,
      endUpdate: Sys$Component$endUpdate,
      initialize: Sys$Component$initialize,
      raisePropertyChanged: Sys$Component$raisePropertyChanged,
      updated: Sys$Component$updated
}
Sys.Component.registerClass('Sys.Component', null, Sys.IDisposable, 
Sys.INotifyPropertyChange, Sys.INotifyDisposing);

As is seen above, by deriving from the Component base class, your custom component automatically will inherit many supported features, including the following.

·         A cross-browser model for managing handler bindings to client object events

·         Automatic registration of the component in the client application as a disposable object that implements the Sys.IDisposable interface

·         Raising of notification events when properties are changed

·         Performing batch processing of component property settings, which is more efficient in script size and processing time than handling all logic in individual property get and set accessors

·         An override of the Sys.UI.Control.initialize method to initialize any properties and event listeners

MS AJAX has many built-in components, such as Application, Timer, Validator, etc. which greatly enhanced the client-side function. In the next section, however, we are going to create our own MS AJAX client-side custom components.

Sample 1

Launch Visual Studio 2005 and select template "ASP.NET AJAX-enabled Web Site" to create a web site named AjaxCustCom, as well as select Visual C# as the built-in language. Next, right click the project and select "Add new Item…" to create a Jscript file named AjaxCustCom.js.

For clarity, let us depict a sketch to illustrate the relationship between the two components we are to create in JavaScript.

Figure 2: Relationship between the two components: Class and Student

Programming the Two Components in JavaScript

Open file AjaxCustCom.js and input the following code.

Listing 2: Define the two components in JavaScript

Type.registerNamespace("Samples");
Samples.Student = function(number,gender,name){
      Samples.Student.initializeBase(this);
      this.Number = number;
      this.Gender = gender;
      this.Name = name;
}
Samples.Student.prototype = {
      initialize: function() {
            Samples.Student.callBaseMethod(this,"initialize");
      },
      get_Number: function() {
            /// <value type="Number"></value>
            if (arguments.length !== 0) throw Error.parameterCount();
            return this.Number;
      },
 
      set_Number: function(value) {
            var e = Function._validateParams(arguments,
                [{name: "value", type: Number}]);
            if (e) throw e;
            this.Number = value;
      },
      get_Gender: function() {
            /// <value type="String"></value>
            if (arguments.length !== 0) throw Error.parameterCount();
            return this.Gender;
      },
      set_Gender: function(value) {
            var e = Function._validateParams(arguments, 
                [{name: "value", type: String}]);
            if (e) throw e;
            this.Gender = value;
      },
      get_Name: function() {
            /// <value type="String"></value>
            if (arguments.length !== 0) throw Error.parameterCount();
            return this.Name;
      },
      set_Name: function(value) {
            var e = Function._validateParams(arguments, 
                [{name: "value", type: String}]);
            if (e) throw e;
            this.Name = value;
      },
      dispose: function() {
            this.Number = null;
            this.Gender = null;
            this.Name = null;
            Samples.Student.callBaseMethod(this, "dispose");
      }
}
Samples.Student.registerClass("Samples.Student", Sys.Component, Sys.IDisposable);
 
Samples.Class = function()
{
      Samples.Class.initializeBase(this);
      this.Number;
      this.Address;
      this.Students = [];
}  
Samples.Class.prototype = {
      initialize: function() {
            Samples.Class.callBaseMethod(this,"initialize");
      },
      get_Number: function() {
            return this.Number;
      },
      set_Number: function(value) {
            this.Number = value;
      },
      get_Address: function() {
            return this.Address;
      },
      set_Address: function(value) {
            this.Address = value;
      },
      
      addStudent: function(student){
      /// <value type="Samples.Student"></value>
            if (Object.getTypeName(student) != "Samples.Student") 
            {
                  var e = Error.argumentType("student", Object.getType(student),
                  Samples.Student,"Samples.Student required!");
                  e.popStackFrame();
                  throw e;
            }
            Array.add(this.Students,student);
      },
      removeStudent: function(student)
      {
            Array.remove(this.Students,student);
      },
      
      get_Students: function()
      {
            return this.Students;
      },
      
      dispose: function() {
            this.Number = -1;
            this.Students = null;
            this.Address = null;
            Samples.Class.callBaseMethod(this, "dispose");
      }
}
Samples.Class.registerClass("Samples.Class", Sys.Component, Sys.IDisposable);
if (typeof(Sys) !== 'undefined') Sys.Application.notifyScriptLoaded();

Here, we first declared a namespace Samples, and then we started to define two components, Student and Class, in the namespace. Typically, you should define properties within the constructor, and define the get and set accessors of the properties, methods, and event handlers inside the prototype block. Note, inside all of these you are suggested to validate the necessary parameters first with effective means, such as arguments, Function._validateParams, etc., before putting them into use. Here we have followed the general routines. Since the later two samples will further discuss them, we are not to detail them any more.

On the whole we have defined two simple components, with the second component Class as the container of the first component Student.

Create the Components on the Client Side

Now, let us see the client-side code.

Listing 3: Client-side JavaScript code to create the two components

…………………………
<script type="text/javascript">
var class01,class02,classes=[];
function pageLoad(sender, args){
      class01 = new Samples.Class();
      class01.set_Num(1);
      class01.set_Address("Address for Class 01");
      class01.addStudent(new Samples.Student("1001","male","Mike"));
      class01.addStudent(new Samples.Student("1002","male","John"));
      class02 = new Samples.Class();
      class02.set_Num(2);
      class02.set_Address("Address for Class 02");
      class02.addStudent(new Samples.Student("2001","male","Jack"));
      class02.addStudent(new Samples.Student("2002","male","Sam"));
      class02.addStudent(new Samples.Student("2003","female","Jenny"));
      Array.add(classes,class01);
      Array.add(classes,class02);
}
function btnTest_onclick() {
      Sys.Debug.trace('Start the test!');
      Sys.Debug.traceDump(class01,'Details about class01:');
}
</script>
</head>
<body>
<form id="form1" runat="server">
<asp:ScriptManager ID="ScriptManager1" runat="server">
<Scripts>
      <asp:ScriptReference Path="AjaxCustCom.js" />
</Scripts>
</asp:ScriptManager>
<div style="width: 549px; height: 1px">
<h1 style="color:Coral">
<span style="color: navy">Client Side Custom Component Test</span></h1>
</div>
<hr style="width: 507px" align="left" />
<input id="btnTest" type="button" value="Start to Test" language="javascript" 
onclick="return btnTest_onclick()" />
<br />
<textarea id='TraceConsole' cols="60" style="height: 277px"></textarea>

Seen above, the three global variables that were first declared used to hold related class instances later. Then inside MS AJAX typical startup functionpageLoad we created two instances for class Class.  We then called their property-related set accessors to set the property values. Next, we called method addStudent to add new sub objectinstances of class Student to the parent variables. Last, we added the two Class instances to an MS AJAX special Array object.  That is it!

In the last part of the above code, we defined a special HTML <textarea> element named "TraceConsole" which was a good means to debug MS AJAX based applications. Here, when we click "Start to Test," the related click handler btnTest_onclick will be invoked, and the <textarea> "TraceConsole" will be stuffed with the interesting debugging information, as shown in Figure 3.

Figure 3: Run-time snapshot for Sample 1

The Behavior Way

Beyond simple data access and updates, presentation layers generally present some more sophisticated user interfaces, intended to ease a user’s workflow. One of the technologies to this end offered by MS AJAX is behaviors. In fact, these behaviors are similar to DHTML behaviors, but they are greatly improved because they are not limited to Internet Explorer. Built using standard JavaScript, they work on all mainstream browsers. An MS AJAX behavior attaches itself to DHTML elements using controls and dynamically changes the behavior of the control based on this attachment.

For example, if you want to add drag-and-drop functionality to a control, you can do this by attaching the appropriate behavior. Other typical GUI enhancements such as tooltips, floating windows, and some animations and visual effects can also be implemented as MS AJAX behaviors.

In the next section we will show you how to create a custom behavior named MyHoverBehavior.

Sample 2

In this sample we will guide you step-by-step through the creation of a client behavior, MyHoverBehavior, which is able to programmatically change the CSS class associated with a DOM element in response to its events. Figure 4 and 5 respectively correspond to the two run-time snapshots of Sample 2.

Figure 4: Snapshot 1 for Sample 2

When you move the mouse over the <DIV> element in yellow background in Figure 4, you will get the snapshot of Figure 5.

Figure 5: Snapshot 2 for Sample 2

Based on what you have learned in the previous sections, our task is to encapsulate the client logic into a behavior. Now let us dissect the logics in programming a behavior piece by piece.

Author's Note:  Here we try to simulate the style (in a separate location) of MS AJAX script library to define the functionality for a property or method rather than in the commonly-used inline style.

Prototype Definition

First, as usual, after registering the namespace and defining constructor, we declare private properties, and specifying all the property related get and set accessors, methods initialize and dispose, and all the related event handlers in the following prototype block.

Listing 4: Prototype block

ZXZSamples.MyHoverBehavior.prototype = {
      _hoverElement : null,
      _unhoverDelay : 0,
      _hoverCount : 0,
      _hoverHandler : null,
      _unHoverHandler : null,
      get_hoverElement: ZXZSamples$MyHoverBehavior$get_hoverElement,
      set_hoverElement: ZXZSamples$MyHoverBehavior$set_hoverElement,
      get_unhoverDelay: ZXZSamples$MyHoverBehavior$get_unhoverDelay,
      set_unhoverDelay: ZXZSamples$MyHoverBehavior$set_unhoverDelay,
      initialize: ZXZSamples$MyHoverBehavior$initialize,
      dispose: ZXZSamples$MyHoverBehavior$dispose,
      add_hover: ZXZSamples$MyHoverBehavior$add_hover,
      remove_hover: ZXZSamples$MyHoverBehavior$remove_hover,
      _onhover: ZXZSamples$MyHoverBehavior$_onhover,
      add_unhover: ZXZSamples$MyHoverBehavior$add_unhover,
      remove_unhover: ZXZSamples$MyHoverBehavior$remove_unhover,
      _onunhover: ZXZSamples$MyHoverBehavior$_onunhover,
      _delayedUnhoverHandler:ZXZSamples$MyHoverBehavior$_delayedUnhoverHandler
}

Here we defined field _hoverElement to specify the DOM element the behavior would be bound to, field _unhoverDelay to specify the maximum time limit the mouse hover over the target element (i.e. after the _unhoverDelay second the target's style will change acccordingly), and field _hoverCount only to remember the count the mouse hover over the target element.  The two handler fields, _hoverHandler and _unHoverHandler, are to represent delegates used by the behavior that route the mouseover/focus and mouseout/blur events data to two corresponding event handlers, _onhover and _onunhover.

Initialization and Handling Events

Now, let us look into the method initialize.

Listing 5: initialize() method

function ZXZSamples$MyHoverBehavior$initialize() {
      ZXZSamples.MyHoverBehavior.callBaseMethod(this, 'initialize');
      this._hoverHandler = Function.createDelegate(this, this._onhover);
      this._unHoverHandler = Function.createDelegate(this, this._unhoverDelay?
      this._delayedUnhoverHandler :this._onunhover);
      $addHandler(this.get_element(), "mouseover", this._hoverHandler);
      $addHandler(this.get_element(), "focus", this._hoverHandler);
      $addHandler(this.get_element(), "mouseout", this._unHoverHandler);
      $addHandler(this.get_element(), "blur", this._unHoverHandler);
 
      if (this._hoverElement){
            $addHandler(this._hoverElement, "mouseover", this._hoverHandler);
            $addHandler(this._hoverElement, "focus", this._hoverHandler);
            $addHandler(this._hoverElement, "mouseout", this._unHoverHandler);
            $addHandler(this._hoverElement, "blur", this._unHoverHandler);
      }
}

Here, we first called method initialize of base class. Next, we invoked the built-in global method Function.createDelegate to bind two delegates, _hoverHandler and _unHoverHandler, to the event handlers _onhover and _onunhover, respectively.  Note if the user specifies the property _unhoverDelay then we call handler _delayedUnhoverHandler which will further wrap handler _onunhover and better deal with the mouseover/focus event delay problem via window.setTimeout method. Once the delegates are created, they can be used to hook the related events directly to the event handlers using MS AJAX $addHandler method. For details about method $addHandler, you can refer to the official website of ASP.NET AJAX.

Next, let us discuss the two event handlers, whose codes are listed as below.

Listing 6:  _onhover handler

function ZXZSamples$MyHoverBehavior$_onhover(){
      this._hoverCount++;
      var handler = this.get_events().getHandler("hover");
      if (handler) {
      handler(this, Sys.EventArgs.Empty);
      }
}
function ZXZSamples$MyHoverBehavior$_onunhover(){
      this._hoverCount--;
      if (this._hoverCount ==0){
            var handler = this.get_events().getHandler("unhover");
            if (handler) {
                  handler(this, Sys.EventArgs.Empty);
            }
      }
}

In the first handler function, we added 1 to the hover time counter _hoverCount. Then we got the special handler associated with event "unhover" from the Sys.EventHandlerList instance that was maintained by parent Component. Finally, we fired the special event. (Note, stored here in the handler variable is a function that, when called, executes all the related handlers for the specific event "unhover.")

In the second handler function, however, things were nearly the opposite with those in the first.  Here, only when the time counter equals to 0 can we fire the special event "unhover."

The Descriptor Block

Listing 7: Descriptor block definition

ZXZSamples.MyHoverBehavior.descriptor  ={
      properties: [ 
            { name: 'hoverElement', type: Object,isDomElement:true,readOnly:true},
            { name: 'unhoverDelay', type: Number}
      ],
      events: [ 
            {name: 'hover'} ,
            {name: 'unhover'} 
      ]
}

Note that the MS AJAX XML-script engine needs a type descriptor to parse the markup code into an instance of a client component, therefore only those client components that provide a type descriptor can be used in XML-script code. Here, in behavior MyHoverBehavior, we exposed two properties, hoverElement and unhoverDelay, and two events, hover and unhover.

Using Behavior MyHoverBehavior

Note, in order to use the custom behavior, we would better create the sample website (named MyHoverBehavior) using the "ASP.NET CTP-enabled Website" template.  For brevity, we only listed the xml-script block.

Listing 8: Descriptor block definition

<script type="text/xml-script">
<span lang=FR><page xmlns:script="http://schemas.microsoft.com/xml-script/2005"</span>
    <span lang=FRxmlns:cc="javascript:ZXZSamples"></span>
      <components>
            <control id="panel1">
                  <behaviors>
                        <cc:MyHoverBehavior unhoverDelay="300"  >
                        <hover>
                              <setPropertyAction target="panel1" 
                                  property="element" 
                                  propertyKey="className" value="hover"/>
                        </hover>
                        <unhover>
                              <setPropertyAction target="panel1" 
                                  property="element" 
                                  propertyKey="className" value="start"/>
                        </unhover>
                        </cc:MyHoverBehavior>
                  </behaviors>
            </control>
            <application>
                  <load>
                        <setPropertyAction target="panel1" property="element" 
                                  propertyKey="className" value="start"/>
                  </load>
            </application>
      </components>
 </page>
</script>

Here, we first declared a custom XML namespace named ZXZSamples. Next, we attached our custom behavior MyHoverBehavior to an HTML DIV element (with ID being 'panel1"). When the object Application was loaded, we initialized the CSS style of the DIV element "panel1."  Next, we defined two events, hover and unhover, of the custom behavior. With each of the two events fired, the CSS style of the DIV element "panel1" would change accordingly. 

The Control Way

We have learned fthat an MS AJAX client control represents a DOM element as a client object and extends a markup representation or provides additional functionality for the element. In a word, a client control encapsulates JavaScript code that is intended to be reusable across applications. In fact, the MS AJAX client side framework has already supplied a good many built-in client controls, which includes not only the basic controls, such as Label, Button, InputControl, TextBox, Image, HyperLink, CheckBox, and Select, but also the advanced controls, ListView, ItemView, and XSLTView.

On the other hand, by deriving from the Control base class, we can create our own custom controls which automatically inherit many built-in cross-browser features from the parent. In the following section we are to build up a menu control that will be rendered using dynamic contents.

Sample 3

In this sample we will construct a menu control based on dynamic contents.  That is to say, every time the user clicks the menu node a remote Web Service will be invoked asynchronously to transfer the server-side data (typically from various kinds of databases) in real time, all of which are encapsulated inside a MS AJAX client-side custom menu control. Note here, for simplicity, we will use a string array to persist the data on the server side. The following Figure shows the related rough flowchart in handling the menu control.

Figure 6: Flowchart in creating and using an MS AJAX client side custom menu control

As seen from Figure 6, we will not only achieve the goal of the common client-side routines but also invoke the server side Web Service to fetch the menu related data.

Next, let us first take a look at the run-time snapshot of the dynamic menu in this sample, as illustrated in Figure 7.

Figure 7: Run-time snapshot of the dynamic menu in Sample 3

Note the bottom rectangle in Figure 7 is a HTML <textarea> element (with id being "TraceConsole") suggested by MS AJAX to debug the client-side JavaScript programming.

Next, let us concentrate on how to build the MS AJAX client-side menu control step-by-step.

Generally, to create a MS AJAX client-side control, the following prescribed set of steps can be followed.

1. Register a namespace Register the namespace for the control using the Type class’s registerNamespace() method.

2. Create a constructor Create the control’s constructor and pass in the DOM element that represents the container for the control as a parameter. Initialize the base class by calling the initializeBase() method and define any fields that will be used by the control to store data.

3. Prototype properties and methodsFollow the prototype design pattern to define properties and methods exposed by the control. Properties and methods are defined using JSON notation.

4. Override method initialize()Within the prototype section of the control, initialize the control by overriding the initialize() method. Within this method, you can dynamically assign values to fields defined in the constructor, create delegates, handle events, and more.

5. Override method dispose()Within the prototype section of the control, override the dispose() method to allow objects such as delegates used by the control to be cleaned up properly.

6. Register the control and inherit from a base class Register the control with the ASP.NET AJAX client-side object model by using the Type class’s registerClass() method.  Pass the appropriate base class to derive from as a parameter to registerClass().

Registering a Control Namespace

This only needs one line of coding.

Listing 9

Type.registerNamespace("Samples");

Here, we named our custom namespace "Samples."

Creating the Control Constructor

This is done by prefixing the control’s name with the namespace that it should be placed in and defining an anonymous JavaScript function as shown in Listing 10.

Listing 10

Samples.AjaxMenuCtrl = function(associatedElement)
{
      /// <param name="associatedElement" domElement="true"></param>
      var e = Function._validateParams(arguments, [
            {name: "associatedElement", domElement: true}]);
      if (e) throw e;
      Samples.AjaxMenuCtrl.initializeBase(this,[associatedElement]);
      this._menutopDiv=null;
      this._menulistDiv=null;
      this._MenuTitle = null;
      this._MenuTopCssClass = null;
      this._MenuListCssClass = null;
      this._ServicePath = null;
      this._ServiceMethod = null;
      this._clickMenuHandler = null;
      this._propertyChanged = null;
}

The AjaxMenuCtrl constructor, shown above, accepts a DOM element object representing the parent container that the control will use.  In other words, the AjaxMenuCtrl control uses a div DOM element as its parent container.  Note, using the global method Function._validateParams to validate the passed parameters is a good practice.

Next, in the constructor we defines several fields, such as this._menutopDiv to contain the top menu bar, this._menulistDiv to hold all the sub menu items, this._MenuTitle to specify the top menu caption, this._MenuTopCssClass to store the CSS class applied to this._menutopDiv and, this._MenuListCssClass to store the CSS class applied to this._menulistDiv.  Other fields such as this._ServicePath and this._ServiceMethod are used to store information about the web service that provides the server-side data to the control.  The handler fields shown (this._clickMenuHandler and this._propertyChanged) represent delegates used by the control that route data from events to event handlers.

Using the Prototype Design Pattern with JSON

JavaScript has long supported the ability to extend objects by using the prototype property. By using it, JavaScript objects can inherit from base types and add additional properties and methods. The ASP.NET AJAX Library uses the prototype property and a pattern referred to as the "prototype design pattern" to extend existing objects and create new ones.

Listing 11

get_MenuTitle: function() {
      return this._MenuTitle;
},
set_MenuTitle: function(value) {
      this._MenuTitle = value;
},
get_MenuTopCssClass: function() {
      return this._MenuTopCssClass;
},
 
set_MenuTopCssClass: function(value) {
      this._MenuTopCssClass = value;
},
get_MenuListCssClass: function() {
      return this._MenuListCssClass;
},
set_MenuListCssClass: function(value) {
      this._MenuListCssClass = value;
},
get_ServicePath : function() {
      return this._ServicePath;
},
set_ServicePath : function(value) {
      if (this._ServicePath != value) {
            this._ServicePath = value;
            this.raisePropertyChanged('ServicePath');
      }
},
get_ServiceMethod : function() {
      return this._ServiceMethod;
},
set_ServiceMethod : function(value) {
      if (this._ServiceMethod != value) {
            this._ServiceMethod = value;
            this.raisePropertyChanged('ServiceMethod');
      }
},

Here, the AjaxMenuCtrl control defines several different properties used to control CSS styles, specifying the sub elements within the control, and web service calls. Although these properties are really JavaScript functions prefixed by get_ and set_ keywords, their only purpose is to get and set data. On the other hand, the class consumer needs to use a function syntax when calling the getter or setter, as there is no explicit property syntax support in JavaScript.

There is also one point to be noticed, it is that the setter for the two properties, ServicePath and ServiceMethod, adds a call to a method named raisePropertyChanged(). Calling this method is useful in situations where a control needs to be notified when a property changes so that it can act upon the change. Understandably, the AjaxMenuCtrl control requires notification if the ServicePath or ServiceMethod properties are changed by the consumer of the control so that it can retrieve fresh server-side data.

Initializing the AjaxMenuCtrl Control and Handling Events

As with the general rules, in this AjaxMenuCtrl control, we override the initialize() method and use it to initialize the base class, initialize properties values, define event delegates, and hook events to event handlers. The complete source code for initialize() method is shown in Listing 12.

Listing 12

initialize : function() {
      var e = this.get_element();
      this._menutopDiv=document.createElement("DIV");
      this._menutopDiv.className = this._MenuTopCssClass;
      this._menutopDiv.innerHTML=this.get_MenuTitle();
      this._clickMenuHandler = Function.createDelegate(this, this._onClick);
      $addHandler(this._menutopDiv,"click",this._clickMenuHandler);
      this._menulistDiv=document.createElement("DIV");
      this._menulistDiv.className = this._MenuListCssClass;
      this._propertyChanged = Function.createDelegate(this,this._onPropertyChanged);
      this.add_propertyChanged(this._propertyChanged);
      e.appendChild(this._menutopDiv);
      e.appendChild(this._menulistDiv);
      Samples.AjaxMenuCtrl.callBaseMethod(this, 'initialize');
},

As is seen from above, we first define a variable e pointed to the parent container of the AjaxMenuCtrl control. Then we create two <DIV> elements specifying their respective CSS styles and finally embed them as the sub element inside the parent container of the AjaxMenuCtrl control. Also notice that for simplicity, we have only bond the click event handler to the first <DIV> element.  You can, of course, add the click event handler to the second <DIV> element to handler each sub menu item clicking.

Calling Web Services within the AjaxMenuCtrl Control

In early times, calling a web service could be a tricky process, as different browsers had different XmlHttp objects be taken into account. Joyfully, MS AJAX script library has now provided cross-browser functionality making it pretty easy to make web service calls and retrieve data without worrying about the differences between the mainstream browsers.

The _invokeWebService() method defined in the AjaxMenuCtrl Control is shown in Listing 13.

Listing 13

_invokeWebService : function() {
Sys.Net.WebServiceProxy.invoke(this._ServicePath, this._ServiceMethod,
  false, { prefixText:searchText, count:10 },
  Function.createDelegate(this, this._onMethodComplete),
  Function.createDelegate(this, this._onMethodError));

We have called the script library’s Sys.Net.WebServiceProxy.invoke method, which is made asynchronously, to provide a nice visual effect for the end users so that they are aware that the request is being processed.

Once the web service is called and a response is returned, _onMethodComplete() is used to handle the response data. This method accepts the string array returned from the web service, any context data associated with the call, and the name of the Web Method that was called.

Listing 14

_onMethodComplete : function(result, userContext, methodName) {
      // Bind returned data
      this.set_data(result);
}
set_data: function(result) {
      var index=0;
      var tempHTML="";
      while(typeof(result[index++])!='undefined')
            tempHTML+=result[index-1]+"<br/>";
      this._menulistDiv.innerHTML=tempHTML;
},

The data returned from the web service is bound to the AjaxMenuCtrl control by calling the set_data() method which is responsible for abstracting data from the string array, recomposing an HTML block and finally assigning it to the innerHTML property of this._menulistDiv.

Web service calls can fail when the service is unavailable or when invalid data is passed to or from the service.  Fortunately, the Sys.Net.WebServiceProxy.invoke() method used to call the service allows a callback method to be specified that handles errors. The _onMethodError() method handles any errors returned by the web service or errors generated if an invalid service path or Web Method name was used.

Listing 15

_onMethodError : function(webServiceError, userContext, methodName) {
      // Call failed
      if (webServiceError.get_timedOut()) {
            alert("Web Service call timed out.");
      } else {
            alert("Error calling Web Service: " + 
            webServiceError.get_statusCode() + " " +  
            webServiceError.get_message());
      }
}

If an error occurs and the callback method is invoked, a Sys.Net.WebServiceError object is passed as the first parameter, along with call context data and the name of the Web Method that was initially called.

Disposing of Control Resources

Listing 16

dispose: function() {
      var e = this.get_element();
      this.remove_propertyChanged(this._propertyChanged);
      for (var i=0;i<e.childNodes.length;i++) {
            var subElem = e.childNodes[i];
            $clearHandlers(subElem);
      }
      this._clickMenuHandler = null;
      this._propertyChanged = null;
      Samples.AjaxMenuCtrl.callBaseMethod(this, "dispose");
}

In this dispose function, our main task is to remove all the predefined event handlers. Finally, we call the dispose function of base class.

Registering the AjaxMenuCtrl Control Class

Listing 17

Samples.AjaxMenuCtrl.registerClass("Samples.AjaxMenuCtrl", Sys.UI.Control);
if (typeof(Sys) !== 'undefined') Sys.Application.notifyScriptLoaded();

As with any client-side script used by the ASP.NET AJAX framework, the AjaxMenuCtrl control adds the last line of code at the bottom of the script file to notify the framework when the entire script has been loaded and is ready to be used.

Creating an Instance of the Client-Side Menu Control

Now let us test the above menu control. Launch Visual Studio 2005 and then select menu item "File | New Website…" to create a new website using the template named "ASP.NET AJAX-Enabled Web Site." Name the project AjaxMenu (select Visual C# as the built-in language). After that, the system should automatically have added references to the necessary assemblies, System.Web.Extensions.dll.  Also, you can see a ScriptManager server control (the brains of an MS Ajax-enabled page) automatically added to the page.

Here, we are only interested in the JavaScript code and the related HTML code snippet.

Listing 18

…… (Omitted)
<link href="Style/StyleSheet.css" rel="stylesheet" type="text/css" />
<script type="text/javascript">
      var omenu = null;
      function pageLoad(){
            Sys.Debug.trace("starting from inside pageLoad"); 
            omenu = $create(Samples.AjaxMenuCtrl,
                  {MenuTitle: "Topics",
                  MenuTopCssClass: "menutop",MenuListCssClass: "menulist",
            ServicePath: "MenuService.asmx",ServiceMethod: "GetMenuData"}, 
            null,null,$get("divMenuContainer"));
      }
</script>
</head>
<body>
<form id="form1" runat="server">
      <asp:ScriptManager id="smgr" runat="Server">
            <Scripts>
                  <asp:ScriptReference Path="~/Scripts/AjaxMenuCtrl.js" />
            </Scripts>
      </asp:ScriptManager>
      <h2> <u>ASP.NET AJAX Client-Cental Based Menu Control Test</u></h2>
      <div id="divMenuContainer" style="width:200px;">
      </div>
      <hr style="width: 611px; " align="left" />
      <textarea id="TraceConsole" style="width: 611px; height: 134px"></textarea>
</form>
</body>
</html>

First, in the HTML part we define an <div> element with id being "divMenuContainer," which is to be used as the container of the dynamic AjaxMenuCtrl control. And as mentioned above, the bottom <textarea> element with id being "TraceConsole" is just for debugging convenience.

Next, in function pageLoad (which will be invoked when object Application is loaded), we, with the help of the global method "$create," create an instance of the AjaxMenuCtrl control. Seen from above, within the complicated method "$create," the control to create, the required properties and the parent HTML container element are all specified, all in one method.

For now, everything gets ready and when you start up the sample web page "Default.aspx," you will see the snapshot as given in Figure 7.

Author's Note: In fact, I have just scratched the surface of constructing a MS AJAX client-side dynamic menu control. Thus, you can further modify and improve it to a really useful and fully functional one.

Downloads

Conclusion

In this article we have examined the three ways (Components, Behaviors, and Controls) to extend MS AJAX client-side functions by respective examples. In a real web farm scenario, we have to select the appropriate means to construct custom MS AJAX component according to different circumstances, prerequisites, and requirements. If possible, in later articles I will explore the ways to construct ASP.NET AJAX controls from the standpoint of the server side.



User Comments

Title: ZK@Web Marketing Blog   
Name: zk5182@yahoo.com
Date: 6/3/2009 1:35:51 AM
Comment:
I downloaded the ASP.NET 2.0 PayPal Commerce Starter Kit (CSK) today for kicks and ended up creating a new provider, called InventoryProvider, that provides the basis for a more functional and feature-rich inventory management system. I say "provides the basis," because my immediate intentions were to do the minimal amount of work possible to have CSK use the provider and then add new features in increments later.






Community Advice: ASP | SQL | XML | Regular Expressions | Windows


©Copyright 1998-2014 ASPAlliance.com  |  Page Processed at 4/18/2014 9:47:50 PM  AspAlliance Recent Articles RSS Feed
About ASPAlliance | Newsgroups | Advertise | Authors | Email Lists | Feedback | Link To Us | Privacy | Search