As I mentioned before, classes go through a registration
process that we will see later on. Registration of a class allows for
inheritance. In this example, the client base class that is used with
ExtenderControlBase objects is the AjaxControlToolkit.BehaviorBase class. This
class inherits from Sys.UI.Behavior defined in the ASP.NET AJAX library, which
itself inherits from Sys.Component.
The Sys.Component base class defines some properties that
can be used in our client development, most notably:
·
id - Gets the ID of the component; although the ID could be
something other than the server control's client ID, the two are usually the
same value. This is accessible through get_id().
·
events - Gets the events of the components; events are not
defined as you see in the .NET framework, rather the JavaScript library uses a
dictionary-based approach to define events. The object representing events is
an object of type EventHandlerList, and we will see how to use it later.
·
raisePropertyChanged - This method, used in conjunction with the
INotifyPropertyChanged interface, fires an event, notifying the consumers of
the class that the property value changed.
The AJAX Control Toolkit's BehaviorBase class provides additional
properties. The clientState property, accessible through get_clientState and
set_clientState, represents the value stored in a hidden field, which
represents the state of the control (similar to a control's viewstate). Because
AJAX components do not have ViewState on the client side (because ViewState
is strictly a server mechanism), a hidden field is used as a ViewState
mechanism on the client.
Let us get into the code. The shell of the component looks
like the following.
Listing 4
Type.registerNamespace("Nucleo.Web.ButtonControls");
//Constructor
Nucleo.Web.ButtonControls.ButtonEnabledExtender = function(associatedElement)
{
Nucleo.Web.ButtonControls.ButtonEnabledExtender.initializeBase(this,
[associatedElement]);
this._isEnabledInitially = null;
this._receiverControlID = null;
this._clickHandler = null;
}
//Class definition
Nucleo.Web.ButtonControls.ButtonEnabledExtender.prototype =
{
…
}
//Descriptor
Nucleo.Web.ButtonControls.ButtonEnabledExtender.descriptor =
{
properties: [..], events: [..]
}
//Class registration, registerClass's second parameter is the base class
Nucleo.Web.ButtonControls.ButtonEnabledExtender.registerClass
('Nucleo.Web.ButtonControls.ButtonEnabledExtender',
AjaxControlToolkit.BehaviorBase);
if (typeof(Sys) != = 'undefined')
Sys.Application.notifyScriptLoaded();
In the example above, every script registers the namespace
it belongs to. Do not worry if you define this multiple times amongst your
various scripts; the AJAX framework does not have issues with this. The next
definition is the constructor that defines a single parameter: the underlying
HTML element being extended. This element is passed to the base class and will
be accessible through get_element(). All variables used in the script are also
defined in the constructor.
At the end of the script, always register your scripts using
the fully quantified name of the class, along with the registerClass method.
The registerClass method registers with the system that the
ButtonEnabledExtender type is a class that inherits from
AjaxControlToolkit.BehaviorBase.
The next section, the prototype, defines the definition of
the class. This is where the properties, events and handlers, methods, and lifecycle
methods are defined. Remember the properties and events exposed in the server
component? Those property values are mapped to the following properties/events
below.
Listing 5
get_isEnabledInitially : function()
{
return this._isEnabledInitially;
},
set_isEnabledInitially : function(value)
{
if (this._isEnabledInitially != value)
{
this._isEnabledInitially = value;
this.raisePropertyChanged("isEnabledInitially");
}
},
get_receiverControlID : function()
{
return this._receiverControlID;
},
set_receiverControlID : function(value)
{
if (this._receiverControlID != value)
{
this._receiverControlID = value;
this.raisePropertyChanged("receiverControlID");
}
},
add_enabledStatusChanged : function(handler)
{
this.get_events().addHandler('enabledStatusChanged', handler);
},
remove_enabledStatusChanged : function(handler)
{
this.get_events().removeHandler('enabledStatusChanged', handler);
},
raise_enabledStatusChanged : function()
{
var eventHandler = this.get_events().getHandler('enabledStatusChanged');
if (eventHandler != null)
eventHandler(this, Sys.EventArgs.Empty);
},
When the application runs, the IsEnabledInitially and the
ReceiverControlID properties on the server side are passed to the set_isEnabledInitially
and set_receiverControlID operations on the client-side (because the server
component needs to set the values). This happens through a $create static method,
but $create is out of the scope of this article.
I mentioned before that there are two lifecycle methods. The
initialize method is a key method in this script. Because it is pretty long, I
am going to break it up into sections. First, the method calls the initialize
method on the base class, as you would using MyBase.New() in VB.NET.
Listing 6
initialize : function()
{
Nucleo.Web.ButtonControls.ButtonEnabledExtender.callBaseMethod(this, "initialize");
.
.
}
Remember that previously I said the extender works in two
modes. If the ReceiverControlID property is not null, then the extender toggles
the receiving control's disabled status. The first statement of the initialize
script processes toggling requests. Normally, the button control contains a
script to post back to the server. This needs prevented below; for the time
being, I am returning false and clearing the onclick code.
Listing 7
if (this.get_receiverControlID() != null)
{
if (this.get_element().onclick != null)
this.get_element().onclick = "return false";
}
The button instance has a click handler, which we want to
map to. The click action requires the use of a delegate, which is the purpose
of Function.createDelegate below. The delegate is a pointer that maps the
JavaScript method clickCallback to the click event of the button. I will
discuss clickCallback in a moment; for now, it simply handles the click event.
Listing 8
this._clickHandler = Function.createDelegate(this, this.clickCallback);
$addHandler(this.get_element(), 'click', this._clickHandler);
Think of a delegate as an intermediary; it simply points the
HTML button click event to the clickCallback method. The last step of the initialize
method is to retrieve and compare the value stored in client state. If the
extender is protecting double-clicking, no processing of client state is
needed. Otherwise, client state is used to store the current disabled status of
the button.
If client state is present, the current value is passed to
the changeStatus method; otherwise, the current status used is pulled from the
isEnabledInitially property. Passing in a boolean value signifies that the
button should be setting a direct value, and not toggle the value.
Listing 9
if (this.get_receiverControlID() != null)
{
var clientState =
Nucleo.Web.ButtonControls.ButtonEnabledExtender.callBaseMethod(this,
"get_ClientState");
if (clientState != null)
this.changeStatus(clientState);
else
this.changeStatus(this.get_isEnabledInitially());
}
Another method also uses the changeStatus method:
clickCallback. This event fires when the button is clicked, passing in a null
value to changeStatus. Null signifies that the button should be toggled.
Listing 10
clickCallback : function(domEvent)
{
this.changeStatus(null);
},
The changeStatus method is where the core of the work is
performed in this script. Depending on the value passed into the method
determines the action to take. If null is passed in, then the enabled status is
toggled. If a value is passed in, that value is assigned to the control.
Listing 11
changeStatus: function(isDisabled)
{
//If no receiver is defined, disable the button after changing the status
if (this.get_receiverControlID() == null)
{
this.get_element().disabled = false;
return ;
}
var receiverElement = $get(this.get_receiverControlID());
if (receiverElement.disabled == = "undefined")
throw new Error.invalidOperation(
"The receiver control doesn't support disabling");
//If null, toggle the current disabled state
if (isDisabled == null)
{
receiverElement.disabled = !receiverElement.disabled;
this.raise_enabledStatusChanged();
}
else
receiverElement.disabled = isDisabled;
Nucleo.Web.ButtonControls.ButtonEnabledExtender.callBaseMethod(this,
'set_ClientState', [receiverElement.disabled]);
}
,
Initially, if the receiver control is null (which means the
extender is preventing double clicks), the button is disabled and processing
stops. Otherwise, the receiver control is retrieved using the $get helper
method (simply a shortcut to document.getElementById). Not every element has a
disabled property necessarily, so an error is thrown if it does not.
Notice that the enabledStatusChanged event is fired only
when the toggle happens; this occurs when the parameter passed in is null.
Otherwise, a direct assignment occurs. The value assigned to the disabled field
is stored in client state by using the callBaseMethod method to set the client
state.