Raising and handling events with Atlas
In this article, we'll see how we can raise and handle events using the Atlas framework.When we talk about events and Atlas, we have to distinguish between
- Events raised by DOM elements. For example, a button element raises an onclick event when we click it.
- Events raised by Atlas components. The framework provides some patterns to deal with events; once we have defined an event, we can raise it and have multiple subscriptions to that event by using delegates. For example, the Atlas Button control intercepts the onclick event raised by the button element that it wraps (we can consider it an "internal" click event) and in turn it raises an "Atlas" click event (an "external" event) that can be handled by objects in our application.
To better illustrate events in the Atlas framework, we'll code a simple KeyPressBehavior that handles the onkeypress event raised by a textbox element, and give a chance to objects in our application to handle it by raising an external keypress event.
How to handle events raised by DOM elements
The recommended approach to handle an event raised by a DOM element is to first define the event handler through the creation of a delegate. The Atlas framework allows to do this by using the createDelegate function:
var _keypressHandler = Function.createDelegate(this, keypressHandler);
In the above code, we made the _keypressHandler variable to reference a keypressHandler function that will act as the delegate for the onkeypress event.After having defined the internal delegate that will handle the event, we have to subscribe to the desired DOM event, calling the attachEvent function from the element:
this.control.element.attachEvent('onkeypress', keypressHandler);
The above statement means that whenever the onkeypress event is raised by the DOM textbox, the function keypressHandler will be invoked to handle the event:
function keypressHandler() {
// Handle the onkeypress event.
}
While the keypressHandler function is declared inside the KeyPressBehavior class, both the attachEvent and createDelegate methods are usually called in the overridden initialize function, while in the dispose function we usually unsubscribe from the event using the detachEvent function and remove the reference to the handler:
this.control.element.detachEvent('onkeypress', _keypressHandler);
_keypressHandler = null;
Please check this post for more details about the lifecycle of an Atlas control.
How to create and handle events raised by Atlas controls
Creating an "Atlas event" is simple. Since we want our behavior to raise a keypressed event in response to the internal onkeypress event, we'll first create an event with the statement:
this.keypress = this.createEvent();
the createEvent method is called to setup the infrastructure needed to raise the event and subscribe to it.When we want to raise the event, we'll have to call the invoke function from our event:
this.keypress.invoke(this, Web.EventArgs.Empty);
The arguments passed to the invoke method are a reference to the current instance and a "state object" in which we can store references that might be useful to the object that handles the event. In this case we are adding an instance of Web.EventArgs.Empty, that is an empty state object.Let's see how an external object can subscribe to the keypress event raised by our behavior. This can be done by writing a statement like
ourInstance.keypress.add(myKeypressHandler);
where ourInstance is an instance of the KeyPressBehavior class and myKeypressHandler is a reference to the function that is supposed to handle the keypress event. The declaration of this function will be something like:
function myKeypressHandler(sender, e) {
// Handle the imageLoaded event here.
}
As you can see, the handler receives two parameters as arguments: sender is a reference to the object that raised the event, while e is a reference to the state object we talked before. This is very similar to the event model used by the .NET framework.Moreover, we can handle our event in a declarative way; to do this, we have to add a line in the getDescriptor function:
td.addEvent('keypress', true);
For example, if we upgrade a textbox element to an Atlas TextBox, we can write the following declaration:
<textBox id="txtMy">
<behaviors>
<keyPressBehavior keypress="txtMy_keypress"
ignoreEnterDefault="true" />
</behaviors>
</textBox>
In the above code, the function txtMy_keypress is called whenever the keypress event is raised. Notice also the ignoreEnterDefault attribute, that is used to prevent the default browser's behavior (tipically a postback) when hitting enter inside a textbox.
The propertyChanged event
Finally, let's spend some words about the propertyChanged event. This is the name of an event that can be raised by any Atlas component (an Atlas component is a class that inheritsfrom Sys.UI.Component).This event can be raised using the function raisePropertyChanged('propertyName') and is used to inform that a value that a property exposes has changed. This allows to support the binding mechanism and reflect a change in a bound property as soon as it happens.If you subscribe to the propertyChange event using the mechanism described above, every time a property of a particular component changes you'll get a Sys.PropertyChangedEventArgs instance that contains the name of that property.
KeyPressBehavior.js
Type.registerNamespace('AtlasExamples');
AtlasExamples.KeyPressBehavior = function() {
AtlasExamples.KeyPressBehavior.initializeBase(this);
// Private members.
var _keypressHandler;
var _ignoreEnterDefault = false;
// Properties.
this.get_ignoreEnterDefault = function() {
return _ignoreEnterDefault;
}
this.set_ignoreEnterDefault = function(value) {
if(value != _ignoreEnterDefault) {
_ignoreEnterDefault = value;
// Raise the propertyChanged event.
this.raisePropertyChanged('ignoreEnter');
}
}
// Create the keypress event.
this.keypress = this.createEvent();
// Initialize / Dispose.
this.initialize = function() {
AtlasExamples.KeyPressBehavior.callBaseMethod(this, 'initialize');
// Create the delegate to handle the onkeypress event.
_keypressHandler = Function.createDelegate(this, keypressHandler);
// Subscribe to the onkeypress event.
this.control.element.attachEvent('onkeypress', _keypressHandler);
}
this.dispose = function() {
// Unsubscribe from the onkeypress event and cleanup.
this.control.element.detachEvent('onkeypress', _keypressHandler);
_keypressHandler = null;
AtlasExamples.KeyPressBehavior.callBaseMethod(this, 'dispose');
}
// Descriptor.
this.getDescriptor = function() {
var td = AtlasExamples.KeyPressBehavior.callBaseMethod(this, 'getDescriptor');
td.addEvent('keypress', true);
td.addProperty('ignoreEnterDefault', Boolean);
return td;
}
// Event Handler for the keypress event.
function keypressHandler() {
if((event.keyCode == 13) && _ignoreEnterDefault) {
window.event.returnValue = false;
}
// Raise the keypress event.
this.keypress.invoke(this, Sys.EventArgs.Empty);
}
}
AtlasExamples.KeyPressBehavior.registerClass('AtlasExamples.KeyPressBehavior', Sys.UI.Behavior);
Sys.TypeDescriptor.addType('script', 'keyPressBehavior', AtlasExamples.KeyPressBehavior);
KeyPressBehavior.aspx
<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Untitled Page</title>
</head>
<body>
<form id="form1" runat="server">
<atlas:ScriptManager ID="sm" runat="server">
<Scripts>
<atlas:ScriptReference Path="KeyPressBehavior.js" />
</Scripts>
</atlas:ScriptManager>
<div>
<asp:TextBox ID="txtMy" runat="server"></asp:TextBox>
</div>
</form>
<script type="text/javascript">
<!--
function txtMy_keypress(sender, e) {
alert('key pressed!');
}
//-->
</script>
<script type="text/xml-script">
<page>
<components>
<textBox id="txtMy">
<behaviors>
<keyPressBehavior keypress="txtMy_keypress"
ignoreEnterDefault="true" />
</behaviors>
</textBox>
</components>
</page>
</script>
</body>
</html>