Explore Ways to Extend ASP.NET AJAX Client-Side Function
page 7 of 9
by Xianzhong Zhu
Feedback
Average Rating: 
Views (Total / Last 10 Days): 38836/ 71

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.


View Entire Article

User Comments

Title: ZK@Web Marketing Blog   
Name: zk5182@yahoo.com
Date: 2009-06-03 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-2024 ASPAlliance.com  |  Page Processed at 2024-04-26 9:01:42 AM  AspAlliance Recent Articles RSS Feed
About ASPAlliance | Newsgroups | Advertise | Authors | Email Lists | Feedback | Link To Us | Privacy | Search