The main purpose of the grid client control is to list the
data we have inside the database. This means we need a property that holds the
data for the grid, mainly a data source property. In addition, there will be
two main events for this simple grid; one is fired when the user clicks on the
edit button and another when the user clicks on the delete button. Moreover, a
render method is needed to render the UI of the grid. We will go through a step
by step process in showing how to develop this grid client control.
To start with, you need to create a new JavaScript file and
register the namespace that will hold the control under development.
Listing 1
Type.registerNamespace("Bhaidar.AJAXControls");
The above statement registers a new namespace called Bhaidar.AJAXControls.
The namespace is used mainly to logically organize the controls and prevent
collisions between the different classes that might for one reason or another
be the same.
Once the namespace is registered, it is time to add the main
constructor of the control. The constructor is simply a function that takes as
input the DOM element on the page that this control we are developing binds to.
In other words, when the current client control renders its content, it will
render them inside the DOM element that this client control is bound to.
Listing 2
Bhaidar.AJAXControls.ContactControl= function(element)
{
Bhaidar.AJAXControls.ContactControl.initializeBase(this, [element]);
this._dataSource= null;
this._deleteDelegate = null;
this._editDelegate = null;
}
The above code shows the constructor for the ContactControl
grid that takes as input a DOM element. The first statement in the constructor
is a call to the constructor of the base class passing in the DOM element. This
is a very important statement that you should always include since it fires the
constructor of the base class, in this case Sys.UI.Control class, and makes all
the properties and functions defined on the base class available in the current
control.
Next, the code defines the data source property that will
hold the data to be rendered inside the grid control. Two delegates are defined,
one for the edit and another for the delete events. As in developing custom
controls in ASP.NET, delegates hold references to event handler methods that
will fire when a certain event is fired. In addition, when creating delegates
in AJAX 1.0 extensions, we make sure that the event handler is fired in the
same context of the class that fired the event handler.
Defining the methods and properties on a control or class
requires using the prototype design pattern. This means adding any property or
method to the prototype of the constructor of the class, meaning these
properties and methods will be defined on the class itself.
Listing 3
initialize: function()
{
Bhaidar.AJAXControls.ContactControl.callBaseMethod(this, 'initialize');
if (this._editDelegate == null)
{
this._editDelegate = Function.createDelegate(this, this._editHandler);
}
if (this._deleteDelegate == null)
{
this._deleteDelegate = Function.createDelegate(this, this._deleteHandler);
}
}
,
The first function to define is the initialize function.
This function is inherited from the Sys.Component class which is the base class
for the Sys.UI.Control class. As a developer you use this function to
initialize any property or event. If it happens you define the initialize
function make sure to call the base initialize method. This way, you make sure
any initializing code in the base class will be done at this moment before
initializing anything in your client control.
Then you define the two event delegates using the Function.createDelegate
function that takes as input the event handler method and the class on which the
event handler method will use a context to fire in.
Another major function to override is the dispose function.
Listing 4
dispose: function()
{
$clearHandlers(this.get_element());
Bhaidar.AJAXControls.ContactControl.callBaseMethod(this, 'dispose');
},
You usually call the dispose function to clear any handlers
that have been added on or inside the DOM element that the control is bound to.
Once you have overridden this function, it is a must to call the base dispose
method that is defined on the Sys.Component class. The base dispose method will
release all resources define on the control and unregisters the control from
the application. You will see how effective this method is when it comes to
closing a displayed control on the page later on in this article.
Listing 5
get_dataSource: function()
{
return this._dataSource;
}
, set_dataSource: function(value)
{
this._dataSource = value;
}
,
The above code defines the data source property. Notice that
in AJAX client side, you define the set and get separately for a property.
There is nothing that provides developers from directly accessing the private
members, however, there is a kind of agreement among developers to access
properties whenever there is a need to change or get the value of a private member.
The get function takes no input and simply returns the value
of the private member _dataSource. The set method takes as input a value that
will be set to the _dataSource members. Later on you will see that we will be
binding a generic collection to this property. This way, we will be able to
access any element inside the data source by using an index representing the
element number in the collection.
As mentioned above, the grid control will expose two events,
the edit and delete events. We will cover one of the events and the other one
will be the same.
Listing 6
add_deleteRecord: function(handler)
{
this.get_events().addHandler('deleteRecord', handler);
}
, remove_deleteRecord: function(handler)
{
this.get_events().removeHandler('deleteRecord', handler);
}
,
The above code shows how to define events on a client
control. You need to define two functions, one prefixed with add and one with remove.
The add_deleteRecord function will be called whenever the $addHandler method is
called or when the event handlers are assigned inside the $create function. We
will see later in this article how the events are assigned in more details. But
the bottom line here is to recognize that the event name is deleteRecord, this
is the event exposed on the control. The add and remove functions will be
called whenever we are assigning or removing an event handler to or from the
control. The get_events() function, which is part of the Sys.Component class,
returns a collection of type EventHandlerList that is a dictionary of client
events defined on the control where the event plays the role of the key and the
event handler is the value.
As you can see in the above code, the addHandler function
simply adds a new mapping or record to the dictionary which binds the
deleteRecord event to the handler parameter, which is nothing but a reference
to a function to execute when the deleteRecord event is fired.
Whenever, the $removeHandler function is called, the
remove_deleteRecord function is called, which removes a record from the dictionary
representing the mapping between the deleteRecord event and the handler
previously assigned to it.
When exposing an event to be utilized by the page
developers, there is a missing function that you need to include, which is the
internal event handler. This function is the one used when creating the
delegate as you have seen in the initialize function above. This means, when
the deleteRecord event is fired, the function handler defined inside the client
control will be first executed. If the event is to be handled internally, i.e.
inside the client control, then the internal function handler will execute and
the event is now considered handled and finished. However, if the event is
exposed and to be handled by the page developer, then this internal function
will simply retrieve the handler mapped to the event from the EventHandlerList
and fires the event handler attached to the event. In this case, we need the
page developer to handler the deleteRecord, so the delete internal event
handler is as follows.
Listing 7
_deleteHandler: function(event, args)
{
var h = this.get_events().getHandler('deleteRecord');
if (h)
h(this, args);
},
The function above uses the getHandler function defined on
the EventHandlerList class to retrieve the event handler function mapped into
the deleteRecord event. Then the code checks, if there is a mapped event
handler then fire it passing as two parameters the sender which is the current
class and empty event args. Later on you will see how to fire an event handler and
pass to it some data in the args parameter.
Binding the grid and rendering the UI requires the presence
of a data bind method that can be called from the host page to render the grid.
Listing 8
dataBind: function()
{
this.renderControl();
},
The above function is called from the host page to render
the grid control. Usually, we will set the value of the data source then call
the data bind function to render the control UI.
The renderControl function will simply create the structure
of the grid control. The code is very simple and we will not go into its
details; you can simply check the code that accompanies this article. However,
we will cover some important code statements inside the renderControl function
that are worth mentioning.
To attach a CSS class to a DOM Element you use the following
statement.
Listing 9
Sys.UI.DomElement.addCssClass(table, 'Professional');
The above code attaches a CSS class called Professional to
the table DOM element using the addCssClass function defines on the Sys.UI.DOMElement
class.
To attach some CSS inside a style element you use the
following line of code.
Listing 10
table.style.cssText = "width:500px;border-collapse:collapse;";
The above code attaches some CSS tags to the style attribute
of the table DOM element.
To attach an event handler to an event on a DOM element you
follow the sample code below.
Listing 11
var img = document.createElement('img');
img.setAttribute("src", "App_Themes/Default/images/edit.gif");
img.setAttribute("border", "0");
img.setAttribute("alt", "Edit Record");
img.style.cssText = "cursor:hand;";
cell.appendChild(img);
$addHandler(img, 'click', Function.createCallback(this._editDelegate, this._dataSource[i]));
First of all, the code creates a new DOM element
representing the img HTML tag then uses the shorthand method $addHandler to
attach an event handler to the click event defined on the img DOM element.
Notice that the code uses
Function.createCallback(delegateName, args). This is the way to fire an event
handler and pass in some data to the args parameter. So now the delegate will
be fired, thus firing the method attached to it which is the internal event
handler and passing to the args parameter whatever data you send in the
createCallback function.
In the code above you can see that we have passed directly
the ith element of the data source as an argument. This is ok; however, we can
also create a custom EventArgs class and pass it in the args parameter.
The custom EventArgs class is very simple.
Listing 12
Type.registerNamespace("Bhaidar.AJAXComponents");
Bhaidar.AJAXComponents.ContactEventArgs = function(data)
{
Bhaidar.AJAXComponents.ContactEventArgs.initializeBase(this);
this._data = data;
}
Bhaidar.AJAXComponents.ContactEventArgs.prototype =
{
get_data: function()
{
return this._data;
}
, set_data: function(value)
{
this._data = value;
}
}
Bhaidar.AJAXComponents.ContactEventArgs.registerClass
('Bhaidar.AJAXComponents.ContactEventArgs', Sys.EventArgs);
Sys.Application.notifyScriptLoaded();
The class inherits from the Sys.EventArgs class and adds a
single property which is data. This class can be used as a generic class to
pass in data to the event handler function. So the data property can contain
any kind of data from string, integer values to object references. Rewriting the
above code looks like listing 13.
Listing 13
var contactEventArgs = new Bhaidar.AJAXComponents.ContactEventArgs(this._dataSource[i]);
$addHandler(img, 'click', Function.createCallback(this._editDelegate, contactEventArgs));
Look how elegant the code is and resembles the code we
usually write when working on the server side.
If on the other hand you simply wanted to fire the event
handler without sending any augments, then you simply use the following line of
code.
Listing 14
$addHandler(img, 'click', this._editDelegate);
Finally, we need to register the control and notify the
ScriptManager that the client control class finished.
Listing 15
Bhaidar.AJAXControls.ContactControl.registerClass
('Bhaidar.AJAXControls.ContactControl', Sys.UI.Control);
Sys.Application.notifyScriptLoaded();