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 methods—Follow
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.