Last week I came to a problem that I need a dynamically created user control and more importantly I need to attach different scripts for different cases at runtime. Here are things that I am going to cover :
- Creating Dynamic User Control
- Finding the Control that caused the PostBack
- Attaching javascript to the dynamically created user control at runtime from code behind with ScriptManager class
- using ScriptManager’s RegisterClientScriptBlock and RegisterClientScriptInclude methods.
1) Creating a Simple User Control
In order to start creating a dynamic user control let’s implement a simple user control that consisted from a button and a label element.
Here is the .ascx code for the user control.
<%@ Control Language="C#" AutoEventWireup="true" CodeFile="SimpleControl.ascx.cs"
Inherits="SimpleControl" %>
<table style="background-color: ActiveCaption; border: solid thin black">
<tr>
<td>
<asp:Label ID="_label1" runat="server" Text="Simple Control" BackColor="ActiveCaption"></asp:Label>
</td>
<td>
<asp:Button ID="_button1" runat="server" Text="Click Me" />
</td>
</tr>
</table>
and here is the design view :
In the code-behind file of the simple user control we only expose the ID of the “Click Me” button with a property. We will use the ID of the “Click Me” in order to find the control that the simple script will be attached to.
Now we will design the start page for the sample. We will use an updatepanel containing an asp.net placeholder control which will serve as a container for our dynamically created user control. The placeholder control does not produce any visible output, just simply serves as a container for the dynamically created controls. We also have a button to create dynamic control. We won't handle the click event of the button in the code behind instead the update panel will listen the "click" event.
Here is the default page for our sample solution :
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<%@ Register Src="SimpleControl.ascx" TagName="SimpleControl" TagPrefix="uc1" %>
<!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>Dynamic User Control and Attaching Script Runtime Demo</title>
</head>
<body>
<form id="form1" runat="server">
<asp:ScriptManager ID="ScriptManager1" runat="server">
</asp:ScriptManager>
<table>
<tr>
<td colspan="2" valign="middle">
<asp:Label ID="Label2" runat="server" BackColor="Red" Text="CREATING DYNAMIC USER CONTROL AND ATTACHING JAVASCRIPT AT RUNTIME"></asp:Label>
</td>
</tr>
<tr>
<td>
<asp:Button ID="_button1" runat="server" Text="Click Here to Create Dynamic User Control"
OnClick="_button1_Click" />
</td>
<td>
<asp:Menu ID="Menu1" runat="server" BackColor="#CCCCFF" OnMenuItemClick="Menu1_MenuItemClick">
<Items>
<asp:MenuItem Text="Insert Javascript From Code" Value="1"></asp:MenuItem>
<asp:MenuItem Text="Insert Javascript From External File" Value="2"></asp:MenuItem>
</Items>
</asp:Menu>
</td>
</tr>
<tr>
<td colspan="2">
<asp:UpdatePanel ID="UpdatePanel1" runat="server">
<ContentTemplate>
<asp:Label ID="Label1" runat="server" BackColor="#CCCCFF" Text=""></asp:Label>
<br />
<asp:PlaceHolder ID="_placeHolder1" runat="server"></asp:PlaceHolder>
</ContentTemplate>
<Triggers>
<asp:AsyncPostBackTrigger ControlID="_button1" EventName="Click" />
<asp:AsyncPostBackTrigger ControlID="Menu1" EventName="MenuItemClick" />
</Triggers>
</asp:UpdatePanel>
</td>
</tr>
</table>
<br />
</form>
</body>
</html>
and design view of the default page :
The next thing we should determine is the page event where we will create our dynamic control. When we take a look to the asp.net page life cycle the OnPreInit or Page_PreInit method is the place that we should create dynamic controls.
2) Finding the Control that Caused the Postback
Because we will create the dynamic user control in the OnPreInit method we should determine the control that caused the postback. If you look to the source view of an asp.net page you will see that there is a hidden field named __EVENTTARGET which records the name of the control that caused the postback with the __doPostBack javascript function.
Source view of an aspx page
The __doPostBack function sets the name of the control to the __EVENTTARGET hidden field and postbacks the page. This is true for all the asp.net server controls except the button controls. The Button controls doesn't call the __doPostBack function, instead they simply submit the page. So, for the ID of the button controls are not written to the __EVENTTARGET field in the postback process instead they are written to the Page.Request.Form collection. Note that if the page contains more than one button only the one that caused the postback will be written to the Form collection.
(The name of the button control is written to the Form collection)
Here is the method to determine the control that caused the postback:
protected static Control GetPostBackControl(Page page)
{
Control control = null;
string ctrlName = page.Request.Params.Get("__EVENTTARGET");
if (ctrlName != null && ctrlName != String.Empty)
{
control = page.FindControl(ctrlName);
}
else
{
for (int i = 0, len = page.Request.Form.Count; i < len; i++)
{
control = page.FindControl(page.Request.Form.AllKeys[i])
as System.Web.UI.WebControls.Button;
if (control != null) break;
}
}
return control;
}
First we check the __EVENTTARGET field and if we can not find a name value than iterate over the Form collection to see if the postback is caused by a button click. The GetPostBackControl return a reference to the control that caused the postback else it returns null.
3) Overriding OnPreInit Page Method
Although we use the ajax technique to create dynamic pages without page refreshing, the full asp.net page life cycle still occurs in every asynchronous call. In every postback the page is reconstructed from the declarative controls in the aspx page and the properties of the controls is set with the values that are saved in the Viewstate. So we should create our dynamic user control in every call to the server. To achieve this, in the OnPreInit method first we check if the postback is caused by the “Create Dynamic User Control” button and if this is true we recreate the user control and attach it to the placeholder’s control collection.
Here is the code :
protected override void OnPreInit(EventArgs e)
{
base.OnPreInit(e);
Control control = GetPostBackControl(this.Page);
// Check if the postback is caused by the button
// Titled "Click to Create a Dynamic Control"
// OR
// createAgain field is true
// which means a call is made to the server while the
// user control is active
if ((control != null && control.ClientID == _button1.ClientID) || createAgain)
{
//should be set before the CreateUserControl method
createAgain = true;
CreateUserControl(controlID);
}
}
because the dynamically created user controls need to be created in every page life cycle we use a flag if the user control is on the page before the postback and if so we recreate it in the next page life cycle.
protected void CreateUserControl(string controlID)
{
// createAgain field is set to true in the OnPreInit method
// when the 'Create User Control' button is clicked
// the createAgain field is used to check if the
// user control is on the page before the call
// if so create the control again and add it to the
// Control Hierarchy again
if (createAgain)
{
SimpleControl userControl = LoadControl("SimpleControl.ascx") as SimpleControl;
if (userControl != null)
{
userControl.ID = controlID;
PlaceHolder1.Controls.Add(userControl);
}
}
}
4) Attaching Javascript events to the Dynamically Created User Control
When the “Insert Javascript from Code” or “Insert Javascript from External file” item is clicked first we get a reference to the dynamic user control and then get a reference to the button inside the dynamic user control. Then we add the “onclick” event to the button control and register the choosen script with the ScriptManager.RegisterClientScriptInclude or with the ScriptManager.RegisterClientScriptBlock methods.
protected void Menu1_MenuItemClick(object sender, MenuEventArgs e)
{
// Load the user control
SimpleControl ctl = PlaceHolder1.FindControl(controlID) as SimpleControl;
if (ctl != null)
{
// Get a reference to the button control
// that is inside the simple user control
// and attach the script to the button
Button btn = ctl.FindControl(ctl.ButtonId) as Button;
if (btn != null)
{
if (Menu1.SelectedItem.Text == "Insert Javascript From Code")
{
AttachScript(ref btn, (int)KScriptType.EScriptFromCode);
Label1.Text = "Script is attached to the user control from Code";
}
else
{
AttachScript(ref btn, (int)KScriptType.EScriptFromFile);
Label1.Text = "Script is attached to the user control from external Javascript File";
}
}
}
}
and the AttachScript method :
protected void AttachScript(ref Button btn, int ScriptType)
{
// attach the selected script to the button control
string scriptKey = "SimpleScript";
if (ScriptType == (int)KScriptType.EScriptFromCode)
{
string script = " function HelloWorld(){alert('HelloWorld from code :)');}";
ScriptManager.RegisterClientScriptBlock(btn, btn.GetType(), scriptKey, script, true);
btn.Attributes.Add("onclick", "HelloWorld();");
// set the AttachedJavascript property
// to load the script again during a postback
// when the simple user control was on the page
// before the postback
AttachedJavascript = (int)KScriptType.EScriptFromCode;
}
else if (ScriptType == (int)KScriptType.EScriptFromFile)
{
// load the script from external javascript file
// (SampleScript.js) and add it to the Button' click event
ScriptManager.RegisterClientScriptInclude(btn, btn.GetType(), scriptKey, ResolveClientUrl("SampleScript.js"));
btn.Attributes.Add("onclick", "HelloWorldFromFile();");
AttachedJavascript = (int)KScriptType.EScriptFromFile;
}
}
and the content of the external javascript file called SampleScript.js
function HelloWorldFromFile()
{
alert("Hello From External File :) !");
}
if(typeof(Sys) !== "undefined")
Sys.Application.notifyScriptLoaded();
and finally we save the selected script type in the Viewstate to re-register it in the case of a postback :
public int AttachedJavascript
{
get { return (int)(ViewState["ScriptType"] ?? -1); }
set { ViewState["ScriptType"] = value; }
}
Hope it helps.
(The sample demo is attached –written with VS 2008 and Asp.Net 3.5).