MS AJAX: A helper for creating component events
Introduction
Microsoft Ajax defines a component as a class that inherits from the base Sys.Component class. Components are able to raise events thanks to a pattern similar to the one in .NET framework. Let's see how we can expose an event with a single line of JavaScript.
Component events and DOM events
We must not confuse events raised by DOM elements with events raised by MS AJAX components. To attach an event handler to a DOM element, we can use the $addHandler and $removeHandler shortcuts, that provide a cross-browser interface to deal with events raised by DOM elements.
The new beta2 release adds two nice helpers for rapidly attaching and detaching handlers to/from a DOM element. The first one, $addHandlers(), allows to add handlers to multiple events with a single statement, and takes care of creating the corresponding delegates. The second one, $clearHandlers(), detaches all the handlers attached with $addHandlers and disposes the corresponding delegates. Please check this blog entry by Bertrand LeRoy for the details.
Component events
If we want to expose an event in a component, we have to:
- Declare a method to add an event handler: add_someEvent() where someEvent is the name of the event that we want to create.
- Declare a method to remove an event handler: remove_someEvent().
- Declare a method that raises the event: raiseSomeEvent().
The above steps must be followed for each event we are going to create. Actually the third step could be simplified by declaring a method that raises a generic event, given the event name and the event arguments:
| myComponent.prototype._raiseEvent = function(eventName, eventArgs) |
| { |
| var handler = this.get_events().getHandler(eventName); |
| |
| if (handler) { |
| if (!eventArgs) { |
| eventArgs = Sys.EventArgs.Empty; |
| } |
| handler(this, eventArgs); |
| } |
| } |
At this point, we have to declare only the methods for adding and removing an handler, and then call _raiseEvent with the name of the event. The following is the code needed to expose a dummyEvent:
| MyComponent.prototype.add_dummyEvent = function(handler) |
| { |
| this.get_events().addHandler('dummyEvent', handler); |
| } |
|
| MyComponent.prototype.remove_dummyEvent : function(handler) |
| { |
| this.get_events().removeHandler('dummyEvent', handler); |
| } |
Even with the help of _raiseEvent(), exposing a component event is a bit verbose in terms of code written.
The createEvent helper
The following code defines a createEvent helper that:
- Adds the corresponding add_ and remove_ methods for adding and removing an event handler, given the name of the event.
- The first time an instance of a particular class is created, it adds to the class a Sys.EventHandlerList collection used to hold event handlers, the get_events() method to retrieve the collection and the _raiseEvent method to raise an event.
- Allows to expose events from every class and not only from components.
Here is the code:
| Type.prototype.createEvent = function(eventName) |
| { |
| var addHandler = function(handler) { |
| this.get_events().addHandler(eventName, handler); |
| } |
| |
| var removeHandler = function(handler) { |
| this.get_events().removeHandler(eventName, handler); |
| } |
| |
| this.prototype['add_' + eventName] = addHandler; |
| this.prototype['remove_' + eventName] = removeHandler; |
| |
| addHandler = removeHandler = null; |
| |
| if(!this.__regEvents) { |
| if(!this.inheritsFrom(Sys.Component)) { |
| this.prototype.get_events = function() { |
| if (!this._events) { |
| this._events = new Sys.EventHandlerList(); |
| } |
| |
| return this._events; |
| } |
| } |
| |
| this.prototype._raiseEvent = function(eventName, eventArgs) { |
| var handler = this.get_events().getHandler(eventName); |
| |
| if (handler) { |
| if (!eventArgs) { |
| eventArgs = Sys.EventArgs.Empty; |
| } |
| |
| handler(this, eventArgs); |
| } |
| } |
| |
| this.__regEvents = true; |
| } |
| } |
As you can see, we are extending Type (that is an alias for Function in the MS AJAX core library) with the createEvent method. The method is added to the prototype object; therefore, we must call the method from inside a class' constructor.
What the method does is creating the two functions for adding and removing an handler, and then adding them to the prototype object of the class. Notice how we are able to use the eventName parameter even inside the two child functions. This is possible thanks to the concept of closure. A closure is a child function that is able to access the context of the parent function. Note also that we are disposing the two closures after having used them.
Example
With the createEvent helper, this is the code necessary to expose a dummyEvent:
| Type.registerNamespace('Samples'); |
|
| Samples.MyClass = function() { |
| Samples.MyClass.createEvent('dummyEvent'); |
| } |
| Samples.MyClass.registerClass('Samples.MyClass'); |
To raise the event, we just have to call _raiseEvent:
| this._raiseEvent('dummyEvent'); |
Conclusion
The createEvent helper allows to expose a MS AJAX event in every class with a single JavaScript statement. A little overhead added by this approach is that the first instance of a class is responsible for adding the get_events() and _raiseEvent() methods to the prototype object of the class.