❑ System.ComponentModel.EventHandlerList : This class is a linked list, where each list entry contains the event handlers for an event type with a specified key.. This class exposes the
Trang 1Event Programming
Extensions One of the great advantages of the NET Framework is its event-programming facilities The ASP.NET AJAX client-side framework provides you with similar facilities to make client-side JavaScript event programming more like server-side NET event programming as much as possible
This chapter provides you with in-depth coverage of the ASP.NET AJAX event-programming extensions and examples that use these extensions
Event Programming
The NET Framework provides you with the following three classes to facilitate event programming
in the NET Framework:
❑ System.EventArgs : This is the base class from which all event data classes derive, directly
or indirectly This class exposes a single read-only property of type EventArgs named
Empty , which simply instantiates and returns an instance of the class
❑ System.ComponentModel.CancelEventArgs : This is the base class from which all event data classes associated with cancelable events derive, directly or indirectly This class exposes
a single read/write Boolean property named Cancel
❑ System.ComponentModel.EventHandlerList : This class is a linked list, where each list entry contains the event handlers for an event type with a specified key This class exposes the following three important methods:
❑ AddHandler : This method adds a specified event handler to the list entry associated with an event type with a specified key
❑ RemoveHandler : This method removes a specified event handler from the list entry associated with an event type with a specified key
❑ AddHandlers : This method adds the content of a specified EventHandlerList — that
is, a link list of list entries — to the EventHandlerList on which the method is called
Trang 2The ASP.NET AJAX client-side framework comes with three classes named Sys.EventArgs ,
Sys CancelEventArgs , and Sys.EventHandlerList that respectively emulate the NET
.EventHandlerList classes as discussed in the following sections
Before diving into the implementation of these classes, here’s a basic description of what an event data
class is and what role it plays in server-side NET or client-side ASP.NET AJAX event programming An
instance of a class raises an event to inform interested clients that something of interest to the clients has
occurred The clients of certain types of events may need more information to process the event This
information is known as event data The event data class is a class whose instances contain the event
data associated with a particular type of event An event data class normally exposes properties that
contain the event data As you’ll see later, it is the responsibility of the instance that raises the event to
instantiate an instance of the appropriate event data class, to initialize the properties of this event data
class instance with the appropriate event data, and to pass this event data class instance into the event
handlers registered for the specified event when it invokes these event handlers
Sys.EventArgs
The ASP.NET AJAX client-side framework contains a base event data class that emulates the NET
System.EventArgs base event data class, as shown in Listing 5-1
Listing 5-1: The Sys.EventArgs Base Event Data Class
Sys.EventArgs = function Sys$EventArgs() { }
Sys.EventArgs.registerClass(‘Sys.EventArgs’);
The Sys.EventArgs base event data class of the ASP.NET AJAX client-side framework, just like the
System.EventArgs base event data class of the NET Framework, features a static property named
Empty Here’s how it works:
Sys.EventArgs.Empty = new Sys.EventArgs();
Sys.CancelEventArgs
The ASP.NET AJAX client-side framework also includes an event data class named Sys.CancelEventArgs
that emulates the NET System.ComponentModel.CancelEventArgs event data class, as defined
in Listing 5-2 The Sys.CancelEventArgs class inherits from the Sys.EventArgs base class and
extends its functionality to add support for a new read/write Boolean property named cancel The
Sys.CancelEventArgs class, just like its NET counterpart, is the base class for the event data classes
of all cancelable events in the ASP.NET AJAX client-side framework
Listing 5-2: The Sys.CancelEventArgs Event Data Class
Sys.CancelEventArgs = function Sys$CancelEventArgs() {
Sys.CancelEventArgs.initializeBase(this);
this._cancel = false;
}
(continued)
Trang 3function Sys$CancelEventArgs$get_cancel() { return this._cancel;
}function Sys$CancelEventArgs$set_cancel(value) { this._cancel = value;
}Sys.CancelEventArgs.prototype = { get_cancel: Sys$CancelEventArgs$get_cancel, set_cancel: Sys$CancelEventArgs$set_cancel}
Sys.CancelEventArgs.registerClass(‘Sys.CancelEventArgs’, Sys.EventArgs);
EventHandlerList
Listing 5-3 presents the definition of the Sys.EventHandlerList class
Listing 5-3: The Sys.EventHandlerList Class
Sys.EventHandlerList = function Sys$EventHandlerList() { this._list = {};
}Sys.EventHandlerList.prototype = { addHandler: Sys$EventHandlerList$addHandler, removeHandler: Sys$EventHandlerList$removeHandler, getHandler: Sys$EventHandlerList$getHandler, _getEvent: Sys$EventHandlerList$_getEvent}
Sys.EventHandlerList.registerClass(‘Sys.EventHandlerList’);
As you can see, the constructor of this class simply instantiates an internal object named _list :
this._list = {};
Also note that this class features four methods: addHandler , removeHandler , getHandler , and
_getEvent The definitions of these methods are presented in the following sections
_ get Event
The Sys.EventHandlerList class contains an internal method named _getEvent as defined in Listing 5-4 As mentioned, this method is used internally by other methods of the class, which means that you should not directly use this method in your JavaScript code Instead, you should use the other methods of the class However, understanding the internal implementation of this method helps you get
a better understanding of the other methods of the class
Trang 4Listing 5-4: The _ get Event Method
function Sys$EventHandlerList$_getEvent(id, create) {
As you can see, the _getEvent method takes two arguments The first argument is used as an index into
the _list The second argument is a Boolean value that specifies whether the method should instantiate
a subarray associated with the specified index if the _list does not already contain the subarray In
summary, the _getEvent method uses its first argument as an index into the _list to return the
subarray associated with the index
add Handler
This method adds a specified event handler to the subarray of the _list with the specified index This
sub-array contains the event handlers for the event type associated with the specified index As such, this method
takes two arguments The first argument is used as an index into the _list to access the associated subarray
The second argument references the event handler being added As Listing 5-5 shows, addHandler first calls
the _getEvent method to return the subarray associated with the specified index, and then calls the add
method on the Array class to add the specified event handler to this subarray
Listing 5-5: The add Handler Method
function Sys$EventHandlerList$addHandler(id, handler) {
Array.add(this._getEvent(id, true), handler);
}
remove Handler
This method removes a specified event handler from the subarray of the _list with the specified
index This subarray contains the event handlers for the event type associated with the specified index
As such, this method takes two arguments, as shown in Listing 5-6 The first argument is used as an
index into the _list to access the associated subarray The second argument references the event
handler being removed
Listing 5-6: The remove Handler Method
function Sys$EventHandlerList$removeHandler(id, handler) {
var evt = this._getEvent(id);
if (!evt)
return;
Array.remove(evt, handler);
}
Trang 5As you can see, removeHandler first calls the _getEvent method to access the subarray associated with the specified index and then calls the remove method on the Array class to remove the specified event handler from the subarray
get Handler
This method returns a reference to a JavaScript function whose invocation automatically invokes all the event handlers for an event type with a specified index See Listing 5-7 for the implementation of this method
Listing 5-7: The get Handler Class
function Sys$EventHandlerList$getHandler(id) { var evt = this._getEvent(id);
if (!evt || (evt.length === 0)) return null;
evt = Array.clone(evt);
if (!evt._handler) { evt._handler = function(source, args) { for (var i = 0, l = evt.length; i < l; i++) { evt[i](source, args);
} };
} return evt._handler;
}
As you can see, getHandler first calls the _getEvent method to access the subarray of the _list with the specified index:
var evt = this._getEvent(id);
Then it defines a function that iterates through the event handlers in this subarray and invokes each enumerated event handler:
evt._handler = function(source, args) { for (var i = 0, l = evt.length; i < l; i++) { evt[i](source, args);
}};
One of the great features of the ASP.NET Framework is its convenient event programming pattern for implementing a new event That is, adding a new event to a class involves the following steps:
1 Add a property of type EventHandlerList to your class if your class does not already contain this property
2 Choose an appropriate name for your event
Trang 63 Choose an appropriate key for your event The key is normally an instance of the
System.Object class
4 Determine whether your class must pass data to the event subscribers when it raises the event
If so, proceed to step 5 If not, use the EventArgs and EventHandler base classes as your event
data class and event delegate, and proceed to step 9 (skipping steps 5 through 8)
5 Determine whether the NET Framework or your own custom library already comes with an
event data class and event delegate that you can use directly If so, skip steps 6, 7, and 8 and go
directly to step 9 Otherwise, proceed to the next step
6 Determine which event data class of the NET Framework or your own custom library is the
most appropriate base class
7 Implement an event data class that derives from the base class chosen in step 6
8 Define an event delegate that takes two arguments where the first argument is of type
System.Object and the second argument is of the same type as your event data class
9 Declare an event with the same type as your event delegate as the member of your class The
add and remove event accessors must add and remove the specified event handler for the event
type with the specified key to the EventHandlerList property of your class
10 Add a method to your class that raises the event This method must first access the list entry in
the EventHandlerList link list that contains the event handlers for the event type with the
specified key This list entry exposes a delegate property whose invocation automatically
invokes the event handlers that the list entry contains in the order in which they were added to
the list entry
Following the ASP.NET Framework, the ASP.NET AJAX client-side framework offers this similar event
programming pattern:
1 Add a method named get_events to your class if your class does not already contain this
method The method must return an instance of the EventHandlerList type This instance is
where your class must store all the event handlers registered for its events A typical
implemen-tation of this method is as follows:
2 Choose an appropriate name for your event
3 Determine whether your class must pass data to the event subscribers when it raises the event
If so, proceed to step 4 If not, use the EventArgs base class as your event data class, skip steps
4 through 6, and go directly to Step 7
4 Determine whether the ASP.NET AJAX client-side framework or your own custom library
already comes with an event data class that you can directly use If so, skip steps 5 and 6 and go
directly to step 7 Otherwise, proceed to step 5
5 Determine which event data class of the ASP.NET AJAX client-side framework or your own
Trang 7cus-6 Implement an event data class that derives from the base class chosen in step 5
7 Implement a method named add_EventName where the EventName is the placeholder for the name of the event The clients of your class will use this method to register event handlers for the event with the specified name A typical implementation of this method is as follows:
function add_EventName(handler){
var eventHandlerList = this.get_events();
8 Implement a method named remove_EventName where the EventName is the placeholder for the name of the event The clients of your class will use this method to remove event handlers from the list of event handlers registered for the event with the specified name A typical imple-mentation of this method is as follows:
function remove_EventName(handler){
var eventHandlerList = this.get_events();
9 Implement a method named onEventName where the EventName is the placeholder for the name of the event Your class must use this method to raise the event and consequently to invoke the event handlers registered for the event with the specified name A typical implemen-tation of this method is as follows:
function onEventName(e){
var eventHandlerList = this.get_events();
var handler = eventHandlerList.getHandler(“EventName”);
if (handler) handler(this, e);
}
Trang 8This method must take a single argument that references the event data class instance that
contains the event data and perform the following tasks:
1 It must invoke the get_events method to return a reference to the EventHandlerList
object where the class stores all the event handlers registered for its events
2 It must invoke the getHandler method on this EventHandlerList object, passing in
the name of the event This method returns a reference to a JavaScript function This function automatically invokes all the event handlers registered for the event with the specified name
10 Implement a method that includes the logic that instantiates the event data class instance,
initial-izes the properties of this instance with the event data, and invokes the onEventName method,
passing in the event data class instance You’ll see an example of this later in the chapter
Using Event Programming
This section shows you how to use the previously mentioned event programming pattern to add new
events to your client-side classes The example used in this section is a shopping cart application First,
the basic classes of the application are presented, and then the application is enhanced with events
Base Classes
Listing 5-8 presents the content of a JavaScript file named ShoppingCart.js that contains the
implementation of the base classes As you can see, the example shopping cart application consists of
two base classes:
❑ ShoppingCartItem : As the name suggests, the instances of this class represent the shopping
cart items that the end user adds to the shopping cart
❑ ShoppingCart : As the name implies, the instances of this class represent the user’s shopping carts
Listing 5-8: The Content of the ShoppingCart.js JavaScript File
Trang 9function Shopping$ShoppingCartItem$get_price(){
return this.price;
}Shopping.ShoppingCartItem.prototype = {
get_id : Shopping$ShoppingCartItem$get_id, get_name : Shopping$ShoppingCartItem$get_name, get_price : Shopping$ShoppingCartItem$get_price};
Shopping.ShoppingCartItem.registerClass(“Shopping.ShoppingCartItem”);
Shopping.ShoppingCart = function() { }
function Shopping$ShoppingCart$initialize(){
this.shoppingCartItems = {};
}function Shopping$ShoppingCart$get_shoppingCartItems(){
return this.shoppingCartItems;
}function Shopping$ShoppingCart$addShoppingCartItem(shoppingCartItem){
var cartItems = this.get_shoppingCartItems();
var cartItemId = shoppingCartItem.get_id();
if (cartItems[cartItemId]) {
var exception = Error.duplicateItem(“Duplicate Shopping Cart Item!”, {name: shoppingCartItem.get_name()});
throw exception;
} else this.shoppingCartItems[cartItemId] = shoppingCartItem;
}Shopping.ShoppingCart.prototype = { addShoppingCartItem : Shopping$ShoppingCart$addShoppingCartItem, initialize : Shopping$ShoppingCart$initialize,
get_shoppingCartItems : Shopping$ShoppingCart$get_shoppingCartItems};
Shopping.ShoppingCart.registerClass(“Shopping.ShoppingCart”);
if(typeof(Sys)!==’undefined’) Sys.Application.notifyScriptLoaded();
Trang 10Listing 5-9 presents an ASP.NET page that uses these base classes, which are discussed in more
var shoppingCartItems = shoppingCart.get_shoppingCartItems();
for (var id in shoppingCartItems)
<form id=”form1” runat=”server”>
<asp:ScriptManager runat=”server” ID=”ScripManager1”>
As you can see from Listing 5-9 , the pageLoad method first instantiates a ShoppingCart object to
represent the current user’s shopping cart:
var shoppingCart = new Shopping.ShoppingCart();
Next, it calls the initialize method (discussed in more detail later) on the newly instantiated
ShoppingCart object to initialize the object:
shoppingCart.initialize();
Trang 11Then, it instantiates a ShoppingCartItem object to represent the item that the current user wants to add
to her shopping cart:
var shoppingCartItem = new Shopping.ShoppingCartItem(1, “item1”, 23);
To keep this discussion focused, I’ve skipped the user interface that presents the current user with the list
of available items to choose from and hard-coded the item being added
Next, the pageLoad method adds the newly instantiated ShoppingCartItem object to the current user’s shopping cart:
Listing 5-10: The ShoppingCartItem Class
Shopping.ShoppingCartItem = function Shopping$ShoppingCartItem(id, name, price) {
this.id = id;
this.name = name;
this.price = price;
} Shopping.ShoppingCartItem.prototype = {
get_id : Shopping$ShoppingCartItem$get_id, get_name : Shopping$ShoppingCartItem$get_name, get_price : Shopping$ShoppingCartItem$get_price };
Shopping.ShoppingCartItem.registerClass(“Shopping.ShoppingCartItem”);
As you can see, the ShoppingCartItem class exposes three properties named id , name , and price The id property of a ShoppingCartItem object uniquely identifies the object among other
Trang 12
ShoppingCartItem objects Notice that Listing 5-10 assigns the following object to the prototype
property of the ShoppingCartItem class:
get_id : Shopping$ShoppingCartItem$get_id,
get_name : Shopping$ShoppingCartItem$get_name,
get_price : Shopping$ShoppingCartItem$get_price
};
The object shown in this code fragment exposes three methods named get_id , get_name , and get_price ,
which respectively reference three JavaScript functions named Shopping$ShoppingCartItem$get_id ,
This ensures that all instances of the ShoppingCartItem class share the same copy of the get_id ,
get_name , and get_price methods If you were to directly define these three methods inside the
con-structor of the ShoppingCartItem class, each instance of the class would have its own copy of these
methods This would waste a lot of resources
As Listing 5-11 shows, the Shopping$ShoppingCartItem$get_id , Shopping$ShoppingCartItem$get
_name , and Shopping$ShoppingCartItem$get_price methods respectively return the id, name, and
price of the associated ShoppingCartItem object
Listing 5-11: The Referenced JavaScript Functions
Listing 5-12 shows the implementation of the ShoppingCart class
Listing 5-12: The ShoppingCart Class
Trang 13initialize : Shopping$ShoppingCart$initialize, get_shoppingCartItems : Shopping$ShoppingCart$get_shoppingCartItems };
Shopping.ShoppingCart.registerClass(“Shopping.ShoppingCart”);
In this listing, the following object is added to the prototype property of the ShoppingCart class:
{ addShoppingCartItem : Shopping$ShoppingCart$addShoppingCartItem, initialize : Shopping$ShoppingCart$initialize,
get_shoppingCartItems : Shopping$ShoppingCart$get_shoppingCartItems };
This object features three methods named addShoppingCartItem , initialize , and CartItems , which respectively reference the Shopping$ShoppingCart$addShoppingCartItem ,
JavaScript functions, as discussed in the following sections
initialize
As you can see in Listing 5-13 , the initialize JavaScript function instantiates an internal object named shoppingCartItems that will contain the ShoppingCartItems added to the current user’s shopping cart
Listing 5-13: The initialize JavaScript Function
function Shopping$ShoppingCart$initialize(){
this.shoppingCartItems = {};
}
get _ shopping CartItems
As Listing 5-14 shows, this JavaScript function returns a reference to the shoppingCartItems internal array that contains the ShoppingCartItem objects added to the current user’s shopping cart
Listing 5-14: The get _ shopping CartItems JavaScript Function
function Shopping$ShoppingCart$get_shoppingCartItems(){
shoppingCartItems collection
Trang 14Listing 5-15: The Shopping$ShoppingCart$addShoppingCartItem JavaScript Functions
function Shopping$ShoppingCart$addShoppingCartItem(shoppingCartItem)
{
var cartItems = this.get_shoppingCartItems();
var cartItemId = shoppingCartItem.get_id();
In this section, the functionality of the ShoppingCart class developed in the previous section is
extended to add support for events You may be wondering why you need to enhance a class with
events When you’re implementing a class, you do your best to ensure that your class provides its clients
with the necessary functionality However, you cannot add application-specific functionality to your
class if you want different applications to use your class This means that your class will not meet the
application-specific requirements of its clients
Let’s take a look at some of the application-specific requirements that the version of the ShoppingCart
class discussed in the previous section does not meet
In Listing 5-13 , the initialize method of the ShoppingCart class performed a single task — that is, it
instantiated the shoppingCartItems collection that will contain the ShoppingCartItem objects added
to the current user’s shopping cart There are several application-specific requirements that the current
implementation of the initialize method does not meet, such as the following:
❑ As part of the initialization process, a typical shopping cart application also needs to populate
the shoppingCartItems collection with the items that the current user selected in the previous
session To do so, the application needs to run some application-specific code to retrieve the
pre-vious session’s items from the underlying data store
❑ As part of the initialization process, a shopping cart application may also need to run some
application-specific code to perform certain filtering on the items that the current user selected
in the previous session
As you’ll see later in this section, the ShoppingCart class can be enhanced with an event named
ShoppingCartInitialized , which the initialize method can raise to allow the clients of the class
to execute application-specific initialization code
In Listing 5-15 , the addShoppingCartItem method of the ShoppingCart class added the specified
ShoppingCartItem object to the shoppingCartItems collection Before adding the object to the collection,
Trang 15the shopping cart application may need to run some code that contains some application- specific logic to determine whether the addition of the specified object would violate some application-specific rules
As you’ll see later in this section, the ShoppingCart class can be enhanced with a cancelable event named ShoppingCartItemAdding , which the addShoppingCartItem method can raise to allow the clients of the class to cancel the add operation if it violates application-specific rules
In Listing 5-15 , the addShoppingCartItem method raised a DuplicateItemException exception if the
shoppingCartItems already contains a ShoppingCartItem object with the same id value as the one being added Many applications prefer to use application-specific exception-handling mechanisms to handle exceptions
As you’ll see later, the ShoppingCart class can be enhanced with an event named ItemAdded , which the addShoppingCartItem method can raise to allow the clients of the class to use application-specific exception-handling logic to handle the exception
This event is useful even when no exception is raised because it allows the application to run application-specific code after an item is added For example, the application may want to display information about a special promotion for the newly added item
As you can see, enhancing your classes with events enables the clients of your classes to extend the functionality of your classes to incorporate application-specific logic
Listing 5-16 presents the new version of the ShoppingCart.js JavaScript file that contains the mentation of all the classes of the shopping cart application These classes are discussed in detail later in this chapter
Listing 5-16: The New Version of the ShoppingCart.js File
return this.id;
}function Shopping$ShoppingCartItem$get_name(){
return this.name;
}
(continued)