Event Bubbling: Sending a Message from a User Control to the Page Container

Business Problem

There is a listing of clubs, and the user base wants to submit requests for a new club to be added to the list. In the first view, clubs are listed in a grid view control. The user clicks an add club link to submit a club using the form view control. The view changes to allow a user submission and the list of clubs are hidden from view. The form view control is contained in a generic web user control. When the user selects submit or cancel a message must be sent to the page containing the control. The information continued in the message used to display a message indicating a successful or cancelled submission. When the user has completed or cancelled the submission the view changes back to the listing of the clubs. The appropriate message is displayed and the submission form is hidden from view.

Illustrating the Use Case

This is how the application starts with the grid view populated. The user clicks the add new club link to submit a new club.

1.gif

The user enters the club information and chooses to submit or cancel the entry. The page, which is the container of the web user control, receives a message indicating the user's choice.

2.gif

This demonstrates that the user chose to submit the club and the form view user control sent a message that bubbled up the containing page. The page determines the submission was a success and displays an acknowledgement.

3.gif
 
When the user chooses to cancel the submission, the web user control that contains the form view sends a message to the page containing the control. Here the user chose to cancel the club submission.

4.gif
 
Background

The Challenge and Solution: To implement this business process the developer must leverage event bubbling. Using event bubbling, a message is sent from the web user control back to the club listing page. The information in the message is used to change the state of the page and list an acknowledgement of the submission. Unfortunately, the documentation on the web describing how to implement this process is scarce and sketchy at best. This article illustrates to the developer the details of how to implement this process.

Event Bubbling in a Nut Shell: An event bubble simply contains a message that is sent from one object to another. The message is contained in a class that inherits from EventArgs. EventArgs is a system base class that contains event data. This of course is a simplification of the concept.

To illustrate, the page class contains both the grid view and the custom web user control. The web user control is a container for the form view control. The form view control contains the submit and cancel links.

What we want is to send a message from the submit or cancel links up through the control hierarchy to the Club List page container. The message is said to bubble up to the page container. There an event handler interprets the message and displays the success or cancellation message to the user.
 
5.gif
 
That is about as deep as this article is going to get into the event bubbling theory. On to composing the code.

Key Elements

The UserControlEventArgs class has two parts. The class declaration and the declaration of the event delegate.

UserControlEventArgs Class
Instance Variable(s)
InsertComplete This flag indicates a successful insertion of a new record or a cancellation of the submission.

 
Event Delegate
ClubInsertedEventHandler Accepts two argument. The first is the sender of type object. The second is the UserControlEventArgs used to pass the message in the bubble.
 
The WebUserControlAddUser class requires an instance variable for the event handler.
 
WebUserControlAddUser
Instance Variable
public event ClubInsertedEventHandler ClubInsertComplete; Declare this variable to be an event of type ClubInserted EventHandler. This is used to raise the event that will bubble up to the ClubList page.
Firing the Event
UserControlEventArgs args = new UserControlEventArgs(true); Declare the args variable that will be passed into the event bubble.
ClubInsertComplete(this, args); Raise the event.

Finally, the ClubList page consumes the event.

Club List Page
Click Event (aspx)
OnClubInsertComplete="ClubInsertComplete" The web user control WebUserControlAddUser invodes the methoc ClubInsertComplete.
Handling the Web User Control Event
ClubInsertComplete(object sender, UserControlEventArgs e) "e" exposes the InsertComplete variable that is passed as an argument into the event handler.

Composing the code

Key Concepts: The UserControlEventArgs class is used to pass the message from the web user control to the default web page that contains the user control. A delegate called the ClubInsertedEventHandler is used to declare the "args" delegate type for the event that is raised in the user control. The event is called "ClubInsertComplete."

The delegate event declaration resides in the UserControlEventArgs class. Note that the declaration is external to the class. It is a mistake to include the delegate within the body of the UserControlEventArgs class and will cause the event to be hidden from the default page which is the container for the web user control.

When a record has been inserted into the data table the args value is instantiated and passed into the "ClubInsertComplete" event as an argument. Note that the delegate for the event follows the Microsoft pattern for declaring events.

When the "ClubInsertComplete" event is raised the event bubbles up to the default page that is a container for the "WebUserControlAddUser" and there it is handled. VS2008 intellisence does not show the event in the customary manner. However, the user can go to the GUI portion of the default page where the user control has been added. There VS2008 intellisence will list the "OnClubInsertComplete" event that is set to the "ClubInsertComplete" method in the default code behind.

Steps to Follow:

Step 1) The ClubList class is a helper that simply creates a datatable and populates it with a list of clubs. They will be displayed in the grid view control on the default page.

Static Class ClubList:

Methods   Description
BuildClubList() A data table is populated with a default club list. The club list is later bound to the gridview control.
AddClub() Add a club to the data table.
ClearTable() Specific to the VS2008 debugger. It is necessary to clear out the table when using a static class because the object has not been disposed by the debugger when it is closed between debuggin runs.

  1. using System;  
  2. using System.Data;  
  3. /// <summary>  
  4. /// Jeff Kent - 11/14/2009  
  5. /// Helper that is used to populate the gridview,  
  6. /// add a new club, and expose the data table for binding.  
  7. /// This is intended only for use in demonstrating this application, and  
  8. /// it is not suitable for production use.  
  9. /// </summary>  
  10. public static class ClubList  
  11. {  
  12.   private static DataTable _dt = new DataTable("ClubList");  
  13.   //PROPERTY THAT EXPOSES THE DATA TABLE FOR BINDING.  
  14.   static public DataTable dt  
  15.   {  
  16.     get { return _dt; }  
  17.     set { _dt = value; }  
  18.   }  
  19.   //CLEAR THE TABLE BECAUSE OF A  
  20.   //PECULARITY WITH VS2008 WHEN  
  21.   //RUNNING IN DEBUG MODE.  
  22.   static public void ClearTable()  
  23.   {  
  24.     _dt.Columns.Clear();  
  25.     _dt.Rows.Clear();  
  26.   }  
  27.   // CREATE A CLUB LIST AND RETURN THE DATA TABLE.  
  28.   static public DataTable BuildClubList()  
  29.   {  
  30.     DataColumn col;  
  31.     DataColumn col2;  
  32.     DataColumn col3;  
  33.     DataRow row;  
  34.     // Create new Datacol, set DataType,  
  35.     // colName and add to DataTable.     
  36.     col = new DataColumn();  
  37.     col.DataType = Type.GetType("System.Int32");  
  38.     col.ColumnName = "ClubID";  
  39.     col.ReadOnly = true;  
  40.     col.Unique = false;  
  41.     // Add the col to the DatacolCollection.  
  42.     _dt.Columns.Add(col);  
  43.     col2 = new DataColumn();  
  44.     col2.DataType = Type.GetType("System.String");  
  45.     col2.ColumnName = "ClubName";  
  46.     col2.AutoIncrement = false;  
  47.     col2.Caption = "Club Name";  
  48.     col2.ReadOnly = false;  
  49.     col2.Unique = false;  
  50.     _dt.Columns.Add(col2);  
  51.     col3 = new DataColumn();  
  52.     col3.DataType = Type.GetType("System.String");  
  53.     col3.ColumnName = "URL";  
  54.     col3.AutoIncrement = false;  
  55.     col3.Caption = "URL";  
  56.     col3.ReadOnly = false;  
  57.     col3.Unique = false;  
  58.     _dt.Columns.Add(col3);  
  59.     row = _dt.NewRow();  
  60.     row["ClubID"] = 1000;  
  61.     row["ClubName"] = "Joe's Rocketry";  
  62.     row["URL"] = "www.joes.com";  
  63.     _dt.Rows.Add(row);  
  64.     row = _dt.NewRow();  
  65.     row["ClubID"] = 1001;  
  66.     row["ClubName"] = "American Rocketry";  
  67.     row["URL"] = "www.ar.com";  
  68.     _dt.Rows.Add(row);  
  69.     return _dt;  
  70.   }  
  71.   //ADD A CLUB TO THE DATA TABLE.  
  72.   public static DataTable AddClub(int ClubID, string ClubName, string url)  
  73.   {  
  74.     DataRow row;  
  75.     row = _dt.NewRow();  
  76.     row["ClubID"] = ClubID;  
  77.     row["ClubName"] = ClubName;  
  78.     row["URL"] = url;  
  79.     _dt.Rows.Add(row);  
  80.     row = _dt.NewRow();  
  81.     return _dt;  
  82.   }  

Step 2) Create a class that inherits from EventArgs. This class also has an event delegate declared outside of the body of the class.

Class UserControlEventArgs contains a single property InsertComplete that encapsulates the message being sent from the web user control to the club list page. The EventArgs inheritance is a base class that resides in the system.

  1. /// <summary>  
  2. /// Jeff Kent - 11/14/2009  
  3. /// EVENT CLASS THAT ENCAPSULATES THE EVENT ARGUMENTS.  
  4. /// </summary>  
  5. public class UserControlEventArgs : EventArgs  
  6. {  
  7.   //CONSTRUCTOR USED TO DETERMINE IF A  
  8.   //RECORD INSERTION WAS SUCCESSFUL.  
  9.   public UserControlEventArgs(bool InsertComplete)  
  10.   {  
  11.     if (InsertComplete)  
  12.     {  
  13.       this.InsertComplete = true;  
  14.     }  
  15.     else  
  16.     {  
  17.       this.InsertComplete = false;  
  18.     }  
  19.   }  
  20.   private bool _InsertComplete;  
  21.   public bool InsertComplete{  
  22.     get { return _InsertComplete ; }  
  23.     set { _InsertComplete = value; }  
  24.   }  
  25. }  
  26. //DECLARE THE EVENT DELEGATE FOR THE CONTROL.  
  27. //NOTE HOW THE DELEGATE RESIDES OUTSIDE THE SCOPE OF THE CLASS.  
  28. public delegate void ClubInsertedEventHandler(object sender, UserControlEventArgs e); 
Step 3) Add a web user control to your project. I use the form view control with the insert item template to gather data from the user. Observe how the ItemCommand event is used to manage the state of the form view control. I do that because of peculariaties present in the control.

The key here is the ClubInsertedEventHandler, which is a an event delegate that declares the variable ClubInsertComplete. When the form view ItemCommand event fires the switch structure fall through to CommitInsert or CancelInsert. There the args variable is instantiated using the constructor. The value of true or false flags whether the use chose to submit a club or cancel the submission. ClubInsertComplete initiates the event bubbling process. A message is sent back to the Club List page where it is handled.
  1. /// <summary>  
  2. /// Jeff Kent - 11/14/2009  
  3. /// SAMPLE WEB USER CONTROL WITH EVENT BUBBLING SUPPORT.  
  4. /// THE FORM VIEW CONTROL IS USED TO COLLECT AND PROCESS CLUB INFORMATION.  
  5. /// ONLY THE INSERT ITEM TEMPLATE IS USED IN THIS EXAMPLE.  
  6. /// </summary>  
  7. public partial class WebUserControlAddUser : System.Web.UI.UserControl  
  8. {  
  9.   //DECLARE THE DELEGATE VARIABLE.  
  10.   public event ClubInsertedEventHandler ClubInsertComplete;  
  11.   //PUT THE FORM VIEW INTO THE INSERT MODE  
  12.   //EVERY TIME THE PAGE LOADS THE CONTROL.  
  13.   protected void FormView1_Load(object sender, EventArgs e)  
  14.   {  
  15.     FormView1.ChangeMode(FormViewMode.Insert);  
  16.   }  
  17.   // THE FORM VIEW CONTROL CAN BE TRICKY.  
  18.   // THE WORK AROUND THE QUIRKS IS TO INTERCEPT THE ITEMCOMMAND EVENT  
  19.   // THEN USE THE SWITCH STRUCTURE TO CONTROL THE STATE OF THE CONTROL.  
  20.   protected void FormView1_ItemCommand(object sender, FormViewCommandEventArgs e)  
  21.   {  
  22.       UserControlEventArgs args;  
  23.       switch (e.CommandName)  
  24.       {  
  25.         case "StartInsert":  
  26.           //NOT USED IN THIS IMPLEMENTATION.  
  27.           break;  
  28.         case "CommitInsert":  
  29.           InsertNewClub();  
  30.           FormView1.ChangeMode(FormViewMode.ReadOnly);  
  31.           //FIRE THE EVENT TO SEND NOTICE TO  
  32.           //THAT THE CLUB SUBBMISSION WAS SUCCESSFUL.  
  33.           args = new UserControlEventArgs(true);  
  34.           ClubInsertComplete(this, args);  
  35.           break;  
  36.         case "CancelInsert":  
  37.           FormView1.ChangeMode(FormViewMode.ReadOnly);  
  38.           FormView1.DataBind();  
  39.           //FIRE THE EVENT TO SEND NOTICE TO  
  40.           //THAT THE CLUB SUBBMISSION WAS CANCELLED.  
  41.           args = new UserControlEventArgs(false);  
  42.           ClubInsertComplete(this, args);  
  43.           break;  
  44.         default:  
  45.           break;  
  46.     }  
  47.   }  
  48.   //OBTAIN THE DATA FROM THE FORM  
  49.   //AND INSERT IT INTO THE DATA TABLE.  
  50.   protected void InsertNewClub()  
  51.   {  
  52.     TextBox tb;  
  53.     tb = (TextBox)FormView1.FindControl("ClubID");  
  54.     int ClubID = Convert.ToInt32(tb.Text);  
  55.     tb = (TextBox)FormView1.FindControl("ClubName");  
  56.     string ClubName = tb.Text;  
  57.     tb = (TextBox)FormView1.FindControl("URL");  
  58.     string url = tb.Text;  
  59.     ClubList.AddClub(ClubID, ClubName, url);  
  60.   }  

Step 4) Create the user control and use the form view control to collect the data. Note that only the insert item template is used in this example.
  1. <%@ Control Language="C#" AutoEventWireup="true"  
  2. CodeFile="WebUserControlAddUser.ascx.cs"  
  3.   Inherits="WebUserControlAddUser" %>  
  4. <asp:FormView ID="FormView1" runat="server" Width="300px"  
  5. EmptyDataText="Empty rocketry data"  
  6.   OnItemCommand="FormView1_ItemCommand" OnLoad="FormView1_Load">  
  7.   <InsertItemTemplate>  
  8.     <table cellpadding="2" cellspacing="0"  
  9.     style="vertical-align: top; border-color: Black;  
  10.       border-style: outset; border-width: 3px; width: 400px">  
  11.       <tr>  
  12.         <td style="text-align: left; vertical-align: top; width: 33%">  
  13.           Club ID (xxx - int):  
  14.         </td>  
  15.         <td style="text-align: left; vertical-align: top">  
  16.           <asp:TextBox ID="ClubID" runat="server" Columns="30"  
  17.           Style="text-align: left" Text='<%# Bind("ClubID") %>'  
  18.             TabIndex="10"> </asp:TextBox>  
  19.           <asp:RequiredFieldValidator ID="RequiredFieldValidator2"  
  20.           runat="server" Text="*" ErrorMessage="Enter an integer"  
  21.           ControlToValidate="ClubID"></asp:RequiredFieldValidator>  
  22.         </td>  
  23.       </tr>  
  24.       <tr>  
  25.         <td style="text-align: left; vertical-align: top">  
  26.           Club Name:  
  27.         </td>  
  28.         <td style="text-align: left; vertical-align: top">  
  29.           <asp:TextBox ID="ClubName" runat="server" Columns="30"  
  30.           Style="text-align: left" Text='<%# Bind("ClubName") %>'  
  31.             TabIndex="10"> </asp:TextBox>  
  32.           <asp:RequiredFieldValidator ID="RequiredFieldValidator1"  
  33.            runat="server" ControlToValidate="ClubName"  
  34.             Text="*"  
  35.             ErrorMessage="The club name is required."></asp:RequiredFieldValidator>  
  36.           <br />  
  37.         </td>  
  38.       </tr>  
  39.       <tr>  
  40.         <td style="text-align: left; vertical-align: top">  
  41.           URL:  
  42.         </td>  
  43.         <td style="text-align: left; vertical-align: top">  
  44.           <asp:TextBox ID="url" runat="server" Style="text-align: left"  
  45.           Text='<%# Bind("url") %>'  
  46.             TabIndex="20"></asp:TextBox>  
  47.           <br />  
  48.           <br />  
  49.         </td>  
  50.       </tr>  
  51.       <tr>  
  52.         <td colspan="2">  
  53.           <asp:LinkButton ID="LinkButton4" runat="server"  
  54.           CommandName="CommitInsert" CausesValidation="true"  
  55.             TabIndex="100">Submit</asp:LinkButton>  
  56.           <asp:LinkButton ID="LinkButton3" runat="server"  
  57.           CommandName="CancelInsert" CausesValidation="false"  
  58.             TabIndex="200">Cancel</asp:LinkButton>  
  59.         </td>  
  60.       </tr>  
  61.       <tr>  
  62.         <td colspan="2">  
  63.           <asp:ValidationSummary ID="ValidationSummary1" runat="server" />  
  64.         </td>  
  65.       </tr>  
  66.     </table>  
  67.   </InsertItemTemplate>  
  68.   <EditRowStyle />  
  69. </asp:FormView> 

Step 5) Finally, create a default page with a multiview control and drop the user control into part one of the view. Put a data grid in the second view.
 
ClubInsertCompleteIntercepts the message that bubbled up from the web user control. The message is passed to ClubInsertComplete and is exposed by the value e, which is of the type UserControlEventArgs. It is the UserControlEventArgs object that encapsulates the message that was sent from the web user control back to the Club List page. It is here that the submit successful or cancellation label is set to be made visible. Also, the view is set as necessary.

  1. public partial class _Default : System.Web.UI.Page  
  2. {  
  3.     protected void Page_Load(object sender, EventArgs e)  
  4.     {  
  5.       if (!Page.IsPostBack)  
  6.       {  
  7.         // SET THE INITIAL VIEW TO THE GRID WITH  
  8.         // A SAMPLE LISTING OF CLUBS.  
  9.         MultiView1.SetActiveView(view2);  
  10.         ClubList.ClearTable();  
  11.         gvClubList.DataSource = ClubList.BuildClubList();  
  12.         gvClubList.DataBind();  
  13.         SubmissionSuccessful.Visible = false;  
  14.         SubmissionCancelled.Visible = false;  
  15.       }  
  16.     }  
  17.     //CHANGE THE VIEW SO THAT THE FORM FOR A CLUB ENTRY IS EXPOSED.  
  18.     protected void LinkButton1_Click(object sender, EventArgs e)  
  19.     {  
  20.       MultiView1.SetActiveView(view1);  
  21.     }  
  22.     //CUSTOM EVENT THAT BUBBLES FROM ROCKETRY CLUB CONTROL.  
  23.     protected void ClubInsertComplete(object sender, UserControlEventArgs e)  
  24.     {  
  25.       //NOTIFY THE USER WHEN THE SUBMISSION IS SUCCESSFUL.  
  26.       if (e.InsertComplete)  
  27.       {  
  28.         SubmissionSuccessful.Visible = true;  
  29.         SubmissionCancelled.Visible = false;  
  30.       }  
  31.       else  
  32.       {  
  33.         SubmissionSuccessful.Visible = false;  
  34.         SubmissionCancelled.Visible = true;  
  35.       }  
  36.       MultiView1.SetActiveView(view2);  
  37.       gvClubList.DataSource = ClubList.dt;  
  38.       gvClubList.DataBind();  
  39.     }  

Of special note is that VS2008 intellisense does not expose the events for the web user control in the customary manner. However, in the aspx page the OnClubInsertComplete event appears in intellisense. Set that to ClubInsertComplete to handle the event that is bubbled up to the page from the web user control.

  1. <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>  
  2. <%@ Register Src="WebUserControlAddUser.ascx" TagName="WebUserControlAddUser" TagPrefix="uc1" %>  
  3. <!DOCTYPE html  
  4. PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"  
  5. "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">  
  6. <html xmlns="http://www.w3.org/1999/xhtml">  
  7. <head runat="server">  
  8.   <title>User Control Event Bubbling</title>  
  9.   <link rel="Stylesheet" type="text/css" href="App_Themes/Default/StyleSheet.css" />  
  10. </head>  
  11. <body>  
  12.   <form id="form1" runat="server">  
  13.   <div>  
  14.     <asp:MultiView ID="MultiView1" runat="server">  
  15.       <asp:View ID="view1" runat="server">  
  16.         <!-- THE "OnClubInsertComplete" EVENT IS EXPOSED TO  
  17.         INTELLISENSE FOR THE USER CONTROL. -->  
  18.         <uc1:WebUserControlAddUser ID="AddClubUser" runat="server"  
  19.         OnClubInsertComplete="ClubInsertComplete" />  
  20.       </asp:View>  
  21.       <asp:View ID="view2" runat="server">  
  22.         <asp:Label ID="SubmissionSuccessful" runat="server"  
  23.         Text="Your club has been submitted." style="color:Green"></asp:Label><br />  
  24.         <asp:Label ID="SubmissionCancelled" runat="server"  
  25.         Text="Submission cancelled." style="color:red"></asp:Label>  
  26.         <br />  
  27.         <asp:LinkButton ID="LinkButton1" runat="server"  
  28.         Text="Add New Club" OnClick="LinkButton1_Click">  
  29.         Add New Club</asp:LinkButton><br />  
  30.         <asp:GridView ID="gvClubList" runat="server">  
  31.         </asp:GridView>  
  32.       </asp:View>  
  33.     </asp:MultiView>  
  34.   </div>  
  35.   </form>  
  36. </body>  
  37. </html> 

Conclusion: It was challenging to find all of the materials necessary for developing the actual solution that has been implemented on a live production site. The development process involved putting together diverse concepts, and there was a clear lack of references on the web. At best, the materials that I found were dated. It is my hope that developers will find the example useful in understanding how event bubbling as it relates to web user controls can be utilized on production web sites.