Create Client Controls in ASP.NET 2.0 AJAX 1.0 Extensions
 
Published: 19 Jun 2007
Abstract
In this article Bilal Haidar demonstrates how to create a basic client control Grid with Edit/Delete/Create functionalities with ASP.NET 2.0 AJAX 1.0 Extensions.
by Bilal Haidar
Feedback
Average Rating: This article has not yet been rated.
Views (Total / Last 10 Days): 40159/ 77

Overview

In the last few articles we have been discussing the new features in ASP.NET 2.0 AJAX 1.0. Among the new features is creating client controls that you can instantiate on the client side, add it to the page content, and fire and handle events defined on these client controls.

In this article we will demonstrate the new client controls in AJAX 1.0 by creating a Grid control that allows you to create new records, edit an existing record, and delete an existing record. You might be wondering why we would create such a control in a time we have the GridView that has all this functionality in which we can place it inside an UpdatePanel control and get the Ajaxified GridView! You are right, but with the development of this new AJAX client control, it will show you in details how we can create any control you want, add methods, properties, and events.

Figure 1 below shows the client control Grid we are about to demonstrate in this article.

Figure 1

1.gif

As you can see, when the page loads, a client control is responsible to show a Grid-like table listing the contacts stored in the database. In each row there are two actions, one to edit and another to delete the current row. In addition, there is a button to create a new contact, which when clicked will show another client control that allows the user to add a new contact to the database.

 

The Concept

To demonstrate creating client controls, we will be developing a simple page that will list the contacts you have stored in a database. Each contact contains the first name, last name, and email address. You can edit and delete any contact and add a new one. What we will need is a Grid to display the contacts. In addition, we need a control that is capable of displaying a single record that will be used to edit and add a new contact.

As you can see, we will need to develop two controls: Grid control and Single record control. We will cover all the details of developing these client controls throughout the sections that follow in this article.

Creating the Client Grid Control

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(thisthis._editHandler);
  }
 
  if (this._deleteDelegate == null)
  {
    this._deleteDelegate = Function.createDelegate(thisthis._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();
Creating Contact Record Control

This control is used to either add a new contact to the database or edit an existing record. Figure 2 shows the control when a new record is to be added to the database.

Figure 2: Contact Record Control when adding a new record
2.gif

As you can see, the control displays a very simply UI that contains 3 input fields to enter the details of a new contact. Once the user enters the data, he or she can submit the record by pressing on the submit button. If the user by mistake clicked on the New Contact, he or she can close the control by clicking on the close button on the top right corner.

You can conclude that we should define two events, one to handle the submit button and one to handle the close button.

In addition, a data source property is required. Why? We will be using the same control to add a new record and to edit an existing record. So, in the case of adding a new record, the control will be displayed without any data bindings. In the case of editing an existing record, we will first bind the data into the control using its data source property. Then when rendering the control, the code will check whether there is data in the data source; if there is an, the input textboxes will be bound to data. This algorithm is shown below.

Listing 16

if (this._dataSource) 
{
   txtBox.setAttribute("value", this._dataSource.FirstName);
}

There is no need to re-explain the details of creating this control since we followed the same procedure as we did in creating the grid client control.

Server side AJAX service

Displaying, adding, deleting, and editing contact records require an AJAX service to be able to interact with the database. The database is very simple and contains a single table named Contacts.

The contacts table contains, ContactID, FirstName, LastName, and Email columns.

The AJAX service is defined as follows:

Listing 17

namespace Bhaidar.Services
{
  [ScriptService]
  public class ContactsService : System.Web.Services.WebService
  {

The major thing to notice is the addition of the attribute ScriptService. This attribute is the one that allows the server to be a script-callable service.

The Web service contains the following web methods.

Listing 18

public System.Collections.Generic.List<Contact> GetContactList
(int startIndex, int maximumRows)
{
  return ContactsManager.Instance.GetContactList(startIndex, maximumRows);
}

The above web method simply returns a generic list of contacts from the database. It has a start index and maximum rows input parameters that allow you, if you want, to implement paging inside the grid client control.

The web method calls the ContactsManager class that contains all the details of connecting to the database. The code is very simple, you can follow it by downloading the accompanying code of this article.

Listing 19

[WebMethod]
public int AddNewContact(string firstName, string lastName, string email)
{
  return ContactsManager.Instance.AddNewContact(firstName, lastName, email);
}

The above Web method is used to add a new contact to the database. It takes as input the first name, last name and email input parameters and internally calls the ContactsManager’s AddNewContact method to add the new contact to the database.

Listing 20

[WebMethod]
public bool UpdateContact(int contactID, string email)
{
  return ContactsManager.Instance.UpdateContact(contactID, email);
}

The above Web method is used to update an existing record in the database. The method takes as input the contact ID and the email address, which means, only the email address is allowed to be updated. Internally, the method calls the ContactsManager’s UpdateContact method to do the actual update on the database.

The last Web method is the DeleteContact.

Listing 21

[WebMethod]
public bool DeleteContact(int contactID)
{
  return ContactsManager.Instance.DeleteContact(contactID);
}

It takes as input the contact ID to be deleted and calls internally the ContactsManager’s DeleteContact method.

Running the Controls all together

The first thing to do in order to show the grid on the page is create the grid client control and bind data to it as shown in the code below.

Listing 22

var app = Sys.Application;
app.add_load(applicationLoadHandler);
 
function applicationLoadHandler(sender, args)
{
  var Contacts = $create(Bhaidar.AJAXControls.ContactControl,  // Type
  null,  // Properties
  {
    editRecord: EditRecord, deleteRecord: DeleteRecord
  }
  , // Events
  null// References
  $get("ContactControl")); // Element
 
  // Wire up the add new context button click event handler
  $addHandler($get('btnNewContact'), 'click', Function.createDelegate(this,
    AddNewContact));
 
  // Bind the Contact Control data
  GetContactList();
}

The code handles the pageLoad function that is part of the Application object and creates the ContactControl by using the $create method. Using the $create method we can pass in the Type of the control we are creating in this case Bhaidar.AJAXControls.ContactControl, define properties in this case no properties are defined, attach event handler to events, in this case the code attaches two events editRecord and DeleteRecord to two event handlers respectively, and finally set the DOM element that this control is bound to. In this case the ContactControl is bound to a div element on the page.

Listing 23

<div id="ContactControl" style="width:100%"></div>

Next, a handler is added to the button that creates a new contact record. The event handler is included which is AddNewContact function.

Finally, a call to the GetContactList function is done. This function is responsible of retrieving data from the AJAX service and binding it to the grid client controls.

Listing 24

function GetContactList()
{    
  var startIndex = currentPage;
  var endIndex = pageSize   
  Bhaidar.Services.ContactsService.GetContactList
   (startIndex, endIndex, OnDataReady);
}

The success callback function that will be executed when the response is back from the server is as follows:

Listing 25

function OnDataReady(result, userContext, methodName)
{
  var comp = $find("ContactControl");
  comp.set_dataSource(result);
  comp.dataBind();
}

The function gets a reference to the grid client control already created on the page using the $find function, then sets the data source property to the data returned from the AJAX service, in this case a List<Contact>. Finally, a call to the dataBind() function is issued to render the grid client control and display all the records returned from the server.

If we go back to the renderControl function inside the ContactControl, you will notice something.

Listing 26

this._dataSource[i].FirstName

After setting the data source property to the data returned from the server, the data source now contains all the data on the client side. To access a contact record, simply use an indexer. Every item returned by an indexer represents an object of type Contact. That is why you see in the above code how easy it is to access any property inside the Contact class.

Once the New Contact button is clicked, the AddNewContact function will be fired according to the binding shown above.

Listing 27

function AddNewContact() {
    var newContact= $create(
        Bhaidar.AJAXControls.ContactRecordControl,  // Type
        null,                                       // Properties
        {click: HandleGridUpdate},                  // Events
        null,                                       // References
        $get("ContactRecordControl"));              // Element
    
    return false;
}

What the method does is simply create a new ContactRecordControl and add it to the page. There is nothing new in defining this control; the code simply attaches the HandleGridUpdate function to the click event defined on the control.

The HandleGridUpdate is defined as follows:

Listing 28

function HandleGridUpdate(sender, args)
{
  // Get the fields from the page
  var errorMsg = '';
 
  var txtFirstName = $get("txtFirstName");
  if ((txtFirstName.value == null) || (txtFirstName.value == ''))
    errorMsg += "First Name is required.\r\n";
 
  var txtLastName = $get("txtLastName");
  if ((txtLastName.value == null) || (txtLastName.value == ''))
    errorMsg += "Last Name is required.\r\n";
 
  var txtEmail = $get("txtEmail");
  if ((txtEmail.value == null) || (txtEmail.value == ''))
    errorMsg += "Email is required.\r\n";
 
  if (errorMsg != '')
    alert(errorMsg);
  else
  {
    ShowUpdateProgress();
 
    if (null == args.get_data())
    {
     // Add new record
      Bhaidar.Services.ContactsService.AddNewContact(txtFirstName.value,
        txtLastName.value, txtEmail.value,  // Parameters
      OnAddNewContactCompleted); // Success Callback
    }
    else
    {
      // Update old record
      Bhaidar.Services.ContactsService.UpdateContact(parseInt(args.get_data()
        .ContactID), txtEmail.value,  // Parameters
      OnUpdateContactCompleted); // Success Callback
    }
  }
}

The function starts by doing some validation on the fields. It then decides whether we are adding a new record or editing an existing record. The args parameter is null in the case of adding a new row and in case of editing a row, it contains the row that is being edited.

The EditRecord function is fired when the edit button is clicked on the grid client control.

Listing 29

function EditRecord(sender, args)
{
  if (args)
  {
    var newContact = $create(Bhaidar.AJAXControls.ContactRecordControl,  // Type
    {
      dataSource: args.get_data()
    }
    ,  // Properties
    {
      click: HandleGridUpdate
    }
    ,  // Events
    null,  // References
    $get("ContactRecordControl")); // Element
  }
}

The function simply creates a new instance of the ContactRecordControl passing in the value of the dataSource property to be args.get_data(). In this case the dataSource is bound to the contact record representing the row to be edited on the grid control. The function to handle the click event is the HandleGridUpdate, which is defined and explained above. That method handles both edit and new actions. Figure 3 shows the page when a record is in the edit mode.

Figure 3

3.gif

The DeleteRecord event handler handles the delete event on the grid client control.

Listing 30

function DeleteRecord(sender, args)
{
  Bhaidar.Services.ContactsService.DeleteContact(args.get_data(),  // Parameters
  OnDeleteContactCompleted);
  // Success Callback
}
}

It simply calls the AJAX service to delete a record passing in the contact ID, which is stored inside the args parameter get_data() property.

There are still other methods that represent the success callbacks of the AJAX service method calls that you can simply read and understand from the downloadable code of this article.

Downloads

References

You can refer to the official documentation available at http://ajax.asp.net/docs and check the Client Side Reference classes, which includes a detailed explanation of all the objects present and that you can use to develop new controls. In addition, you can refer to an MSDN web cast delivered by Rob Bagby on developing Client Controls in AJAX 1.0 Extensions.

Conclusion

In this article we have discussed creating client controls in ASP.NET 2.0 AJAX 1.0 Extensions. The article discussed creating a grid client control to show data from a database and provide functionalities to edit and delete a record. In addition, a control was discussed that is used to either edit an existing record or create a new record. The article is a step-by-step process in explaining all what is needed to define a new control, add events and event handlers, and render the UI element of a control.

Happy Ajaxified Dot Netting!!



User Comments

Title: gsdfg   
Name: nikesh
Date: 2009-12-24 6:21:47 AM
Comment:
not bad
Title: Mr   
Name: Malcolm Swaine
Date: 2008-07-09 6:24:45 PM
Comment:
"A control is already associated with the element." - try removing the Ajax Functionality and this goes away ... quick workaround
Title: edit data error   
Name: jfliuyun
Date: 2007-06-21 4:26:44 PM
Comment:
when first click editimage everything is ok,but before you submit the data ,you click editimage throw error "A control is already associated with the element." can you tell me what happened,and how to result it,thanks
Title: Yes it does!   
Name: Muhammad Mosa
Date: 2007-06-21 8:10:15 AM
Comment:
Yes it does! at least you closed the door. I was thinking of another way and want to make sure there is no other way
Title: Re: Muhammad   
Name: Bilal Haidar [MVP]
Date: 2007-06-20 6:54:11 AM
Comment:
In AJAX 1.0 you only have two ways to interact with methods on the server:
1- WS methods
2- Page static methods

In the second option, you can place a static method on the page, inside it you can directly access your API the way you want. Does this help you?

Regards
Title: Bind The Control with a data layer   
Name: Muhammad Mosa
Date: 2007-06-20 3:56:54 AM
Comment:
No, this is not my target. sorry for this. what I mean I have Business Logic Rule Library. I want to use this library's methods to bind data to the AJAX control. Just as in your sample, but instead of using WS I want to use normal library
Title: Re: Bind The Control with a data layer   
Name: Bilal Haidar [MVP]
Date: 2007-06-19 3:47:13 PM
Comment:
I guess I didn't get your question right!

Do you mean you want to use WS not developed in .NET? Please correct me if I am wrong!

Thanks
Title: Bind The control with a data layer   
Name: Muhammad Mosa
Date: 2007-06-19 1:26:28 PM
Comment:
Well, we are on the same boat then. Ok in this sample, do you have any idea how can I bind the control to a data layer method that is not a WS method?!
Keep the good work brother
Title: Re: Carlito   
Name: Bilal Haidar [MVP]
Date: 2007-06-19 1:09:59 PM
Comment:
Carlito,
I will be checking on that and get back to you here on this article!
Regards
Title: Re: Muhammad Mosa   
Name: Bilal Haidar [MVP]
Date: 2007-06-19 1:07:59 PM
Comment:
Hello Muhammad,
We never use WS as a data layer. I can speak on myself, it is only for demonstration purpose that I embed the nasty DB work inside my WS. Just to save some time!
But in real projects, WS should only be an interface for your application and all your logic (BL and DAL) should be in a seperate API!!

Hope this answers your question!

Regards
Title: Mozilla compatibility   
Name: Carlito
Date: 2007-06-19 12:30:41 PM
Comment:
Hi, I was just trying your sample code which seems to work well with IE but not so well with Firefox.
It seems whenever I click on 'New Contact', the new contact sections shows for a second then disappears and the page reloads the contact list again...
Title: Nice By why always Web Services?!   
Name: Muhammad Mosa
Date: 2007-06-19 9:41:23 AM
Comment:
Wonderful as usual Bilal. I have one comment but it is not on the article it is general. Every time I read something about ASP.NET AJAX -well not everything- they just present Web Services as the data service to retrive data! While in most cases we don't use web services unless it is required. I mean I'm not going to use Web Service just to act as a data layer, I already have my data layer library!
In this I'm not going to use Web Service! you may suggest to use static web methods on the page itself, well is there are any other alternatives?!






Community Advice: ASP | SQL | XML | Regular Expressions | Windows


©Copyright 1998-2024 ASPAlliance.com  |  Page Processed at 2024-04-25 7:16:12 PM  AspAlliance Recent Articles RSS Feed
About ASPAlliance | Newsgroups | Advertise | Authors | Email Lists | Feedback | Link To Us | Privacy | Search