In this article I will discuss how you can manipulate the ClientId of each control being rendered in the browser. The article also demonstrates how you can use Predictable ClientId for data bound controls.
Introduction
ClientID of a control is one of the feature in ASP.NET which always hunts me in the past. It is very hard to predict what client id the page is going to render when it is put in the client side. ASP.NET has an unique naming system, which generates the client ids and sends to the browser based on certain logic. With the introduction of ASP.NET 4.0, the work become very easier. Handling with the client ids are now very easy.
In this article I am going to discuss how you can take benefit of the feature to manipulate the client id generated in the client side.
The Problem
Before I start discussing about the Solution, let us discuss what exactly the problem is when writing an ASP.NET application.
There is a wide difference between the id of the html and the id on a server control. ASP.NET page renders itself to produce HTML output. Every server control has an ID which uniquely identifies the control in the class. Now this control can render quite different html ids based on certain scenarios.
Lets I have a simple page :
<asp:Label Id="lblName" runat="server" Text="Select Name : " />
<asp:TextBox ID="txtName" runat="server"></asp:TextBox><br />
<asp:Button runat="server" OnClick="Submit_Click" OnClientClick="javascript:return validateName();" Text="PostPage" />
Here I have a
TextBox control which is set
runat="server" that means the control is not an html control and the server will render it. While it renders, it creates a
<input type="text" id="txtName" />
I have defined one javascript which looks like :
function validateName() {
var element = document.getElementById("txtName");
if (element.value.length > 0)
return true;
return false;
}
This will work Fine. But say after a certain while I add one MasterPage for this Html. Here comes the problem.
If you place the any control inside the contentplaceholder (with name MainContent) of a masterpage, its id will be generated as ctl00$MainContent$txtName.
The id can be divided into 3 sections which are separated using $ signs.
1. ctl00 : This is a random id generated for every masterpage you nest. Its ctl00, ctl01 ...
2. MainContent : It represents the id of the MasterPage. Here I have specified the id of the MasterPage as MainContent.
3. txtName : This is the actual Id of the control in the serverside.
Thus you can see, depending on the MasterPage id, the id of all the control will change. So after placing it inside the masterpage, my javascript will not work and will always return false, as element will be null.
In case of this problem, either I can write
var element = document.getElementById("ctl00$MainContent$txtName");
Or more precisely I might go for :
var element = document.getElementById("<%=this.txtName.ClientID %>");
Both the scenario will work, but both of them has certain limitations.
Limitation of 1st Approach
- Laborious job to modify all the clientid reference if I have to change the name of the MasterPage and /or Nest the masterpage inside another masterpage to change layout.
- Need to change the javascript files that are referenced from the page, and hence the javascript will be totally dependent on the current page.
Limitation of 2nd Approach
- In spite of limitations of the first approach, it is also not full proof to use the second approach, even though it is better to use the second.
- Javascript should be placed with the page. It cant replace the server tags to the external js file.
- Page size will increase, as each page will have lots of javascript blocks which just interface between client side scripts.
- Mix of Server side and client side blocks which should always be avoided in ASP.NET (unlike Classic ASP)
New Approach to Solve the Issue
ASP.NET 4.0 comes with a new feature that allows you to specify the mode of the client id for each control. So you will get full control of what id to be rendered in the client side and hence it becomes easier to handle the problems that I have discussed previously.
Every Control of ASP.NET 4.0 comes with a new property called
ClientIDMode. It determines how the ClientID will be rendered. Let us discuss about the different modes of ClientID rendering and see how you can use it :
ClientIDMode.Static
In this mode, the control will render the ID of the client side as it is defined in the server side. It comes handy when you dont want to change the id of the client side html controls. In this case, as ASP.NET doesn't takes care of the uniqueness of the control id rendered in the client side, it will be the responsibility of the programmer to uniquely identify each control it defines.
In the above example, I can use
<asp:Label Id="lblName" runat="server" Text="Select Name : " />
<asp:TextBox ID="txtName" runat="server" ClientIDMode="Static"></asp:TextBox><br />
<asp:Button runat="server" OnClick="Submit_Click" OnClientClick="javascript:return validateName();" Text="PostPage" />
In this case the client html will be as :
<input name="ctl00$MainContent$txtName" type="text" id="txtName" /><br />
<input type="submit" name="ctl00$MainContent$ctl00" value="PostPage" onclick="javascript:return validateName();" />
So I can now easily refer to the control in javascript only using
txtName even from external javascript files. Ahh... The problem solved.
ClientIDMode.AutoId
AutoId is the default ClientIDMode when it is not defined anywhere in the page. It comes handy when you have configured all the ControlIDMode of the page or of the whole application as Static or any other value, and you want only a specific control to have Inherit value.
If I place the
ClientIDMode="AutoId" in the above code, the html will look like
<input name="ctl00$MainContent$txtName" type="text" id="ctl00_MainContent_txtName" /><br />
<input type="submit" name="ctl00$MainContent$ctl00" value="PostPage" onclick="javascript:return validateName();" />
You can see the ID of the control is modified to refer the
ContentPlaceHolder name with an AutoId just before the Id.
AutoId mode is best when you don't want to handle the uniqueness of the control id yourself and want to rely totally on ASP.NET.
ClientIDMode.Inherit
Inherit is slightly different than the AutoId. AutoId places one Unique Sequence page id of the controls using ctl00, ctl01 .. In case of Inherit mode, it only places the inherited control id for each controls.
As I am within the masterpage, it will give you
"MainContent_txtName". So the only difference between inherit mode and AutoId is that AutoId places the unique PageId while Inherits doesnt.
ClientIDMode.Predictable
The modes that I have discussed earlier works when we are going to use static controls on the page. Predictable mode comes handy when we are going to use DataBound controls which renders more than one control with same id. So using Predictable we can predict the id that the control will generate when it rendered in the client side.
Let us take the example of a
ListView to demonstrate how you can easily use this. For simplicity I am using
XmlDataSource to define the Data.
<asp:XmlDataSource ID="xdsStudent" runat="server">
<Data>
<Students>
<Student id="1" name="Abhijit" />
<Student id="2" name="Sheo" />
<Student id="3" name="Puneet" />
<Student id="4" name="Kunal" />
</Students>
</Data>
</asp:XmlDataSource>
Say this is my
DataSource and I define a
ListView using this DataSource.
<asp:ListView runat="server" ID="lvShowNames" DataSourceID="xdsStudent" ClientIDMode="Predictable">
<ItemTemplate>
<asp:Label runat="server" Text='<%# Eval("name") %>' ID="lblStudentName"/> </br>
</ItemTemplate>
</asp:ListView>
Now as I defined the
ClientIDMode as predictable, the span which is rendered in the client side will look like :
<span id="MainContent_lvShowNames_lblStudentName_0">Abhijit</span>
<span id="MainContent_lvShowNames_lblStudentName_1">Sheo</span>
<span id="MainContent_lvShowNames_lblStudentName_2">Puneet</span>
<span id="MainContent_lvShowNames_lblStudentName_3">Kunal</span>
Therefore you can see the ids will be generated. It has 4 parts(each separated using underscores). The first part represents ContentPage Name, Next shows ListViewId, and then Control Id and then a unique number for each record.
Now say I don't want the uniqueness to be implied automatically by ASP.NET. I would like to render the suffix with the same id that is present for each student. There is another property for each databound controls that I can make use of which is called
ClientIDRowSuffix which enables you to use a proper suffix for each control.
<asp:ListView runat="server" ID="lvShowNames" DataSourceID="xdsStudent" ClientIDRowSuffix="id">
<ItemTemplate>
<asp:Label runat="server" Text='<%# Eval("name") %>' ID="lblStudentName"/> </br>
</ItemTemplate>
</asp:ListView>
Thus the html will seem like :
<span id="MainContent_lvShowNames_lblStudentName_1">Abhijit</span>
<span id="MainContent_lvShowNames_lblStudentName_2">Sheo</span>
<span id="MainContent_lvShowNames_lblStudentName_3">Puneet</span>
<span id="MainContent_lvShowNames_lblStudentName_4">Kunal</span>
So the suffix will be the same as the record id of each student.
Similarly, you can use as many suffix as you want. Each of them should be separated using comma. So if I write ClientIDRowSuffix="id,name" it will be like :
<span id="MainContent_lvShowNames_lblStudentName_1_Abhijit">Abhijit</span> </br>
<span id="MainContent_lvShowNames_lblStudentName_2_Sheo">Sheo</span> </br>
<span id="MainContent_lvShowNames_lblStudentName_3_Puneet">Puneet</span> </br>
<span id="MainContent_lvShowNames_lblStudentName_4_Kunal">Kunal</span> </br>
Thus you can see, now the id which is rendered in the client will possess the name of each students.
Note
You can also configure the ClientIDMode to a page. This will modify the default ClientIDMode of the page in which it is configured. Similarly you can also set the ClientIDMode in Web.config to apply this to every control in the current web site.
Conclusion
It is a common problem for every ASP.NET developer to handle with ClientIds. With the introduction of ASP.NET 4.0, the problem is cured, and you can get a better approach to handle the client end control ids easily.
I hope this would help you.