The impact of ClientIDMode=Predictable

ASP.NET 4 introduces a new property on all controls: ClientIDMode. It lets web form developers minimize the size of the id= attribute written into HTML tags. It also helps them dictate the actual form of the ID, avoiding the mangled naming of previous versions when controls are inside of naming containers.

As a commercial web control developer, I’ve studied my products when the user sets ClientIDMode to something other than AutoID (which is that mangled format.) Unfortunately, the Predictable setting can break things.

Predictable is documented as follows:

This algorithm is used for controls that are in data-bound controls. The ClientID value is generated by concatenating the ClientID value of the parent naming container with the ID value of the control. If the control is a data-bound control that generates multiple rows, the value of the data field specified in the ClientIDRowSuffix property is added at the end. For the GridView control, multiple data fields can be specified. If the ClientIDRowSuffix property is blank, a sequential number is added at the end instead of a data field value. Each segment is separated by an underscore character (_).

Since we are talking about the ClientID property, we really are focused on how our client-side javascript code interacts with the HTML generated by the web controls. My controls heavily use javascript, and always grabbing HTML elements by anticipating the id= attribute. For example, suppose you have this TextBox inside of a UserControl whose ID is “UserControl1”.

<asp:TextBox id="TextBox1" runat="server" />

ASP.NET generates this HTML when ClientIDMode=AutoID.

<input type='text' id="UserControl1_TextBox1" />

To get this DHTML element, the Javascript should do this:

var fld = document.getElementById("UserControl1_TextBox1");

This script breaks if the Textbox is inside of a ListView or FormView control when ClientIDMode=Predictable. It’s that ClientIDRowSuffix property, mentioned in the docs above, that is causing the problem. It appends a “_row#” pattern to the overall id= output, like this:

<input type='text' id="UserControl1_TextBox1_0" />


Note: MS has told me that FormView won’t be a problem in the RTM of ASP.NET 4, but for Beta 2, it is.

There is NO problem if you always get the value for the id from the ClientID property itself. For example:

var fld = document.getElementById('<% = FindControl("TextBox1").ClientID %>');

My web controls do something a little different. They have child controls and their IDs have a specific pattern. Take my DateTextBox control. It uses an Image control to toggle a popup calendar.

clip_image002

It is generated this way:

public class DateTextBox : System.Web.UI.WebControls.TextBox
{
protected override void CreateChildControls()
{
System.Web.UI.WebControls.Image image = new System.Web.UI.WebControls.Image();
image.ID = this.ID + "_Img";
this.Controls.Add(image);
}
}

Here’s where it breaks

My javascript attaches some event handlers to the <img> HTML tag generated by the Image control by using the ClientID from the TextBox and appending “_Img” like this:
var img = document.getElementById('<% = FindControl("TextBox1").ClientID %>' + "_Img");

This no longer works when using Predictable mode if the control is inside a ListView or FormView because the image tag has that suffix:

<img id="FormView1_UserControl1_TextBox1_Img_0" />

To solve this, I created the following function that I wanted to share. It handles getting the correct ID, whether or not there is that suffix. It should be used only when a child web control is named in the pattern shown here.

// When joining [ClientID] + "_const", ClientIDMode=Predictable causes problems
// It adds "_#" to the end ([ClientID]_const_0).
// It will move _# from the end of [ClientID] to the end of the combined id.
// This function detects the pattern and copies the "_#" from the ClientId to the end of the overall id
// It returns the new ID.
// pID – The ClientID of the parent control
// pExt – The string appended to the ClientID of the parent, such as “_Img”
// pMode - int.
// 0 = Normal. Move the ClientID extension to the end of the overall Id.
// 1 = use pId + pExt exactly.
// 2 = the HTML tag was hardcoded with an ID (Literal control written out)
// instead of being generated by a webcontrol's ID.
function PrepIdExt(pId, pExt, pMode)
{
if (pMode == 1)
return pId + pExt;
if (!gGBIRE)
gGBIRE = new RegExp("_\\d+$"); // _ followed by 1 or more digits followed by the end of text
var vM = gGBIRE.exec(pId);
if (vM != null) // found it. Revise the ID
{
pId = pId.substr(0, pId.length - vM[0].length) + pExt;
if (!pMode)
pId = pId + vM[0];
}
else
pId = pId + pExt;
return pId;
} // PrepIdExt
var gGBIRE;


Here’s how to use it:

var img = document.getElementById(PrepIdExt('<% = FindControl("TextBox1").ClientID %>', "_Img", 0));

Some concluding thoughts

  • There are other ways to create IDs within the web controls to avoid this. Most common is to implement INamingContainer on your base control and establish child control IDs without adding the parent (image.ID = "_Img" as opposed to image.ID = this.ID + "_Img")
  • If you always use the ClientID property to specify the exact child control, your code will work. I don’t like that because you have to pass every ID you need from the server side, and the ID strings can be lengthy, impacting the page transmission time. I like to pass a single ClientID of the base control and let the javascript know how to work from there.
  • Peter’s soapbox: I do not feel its right for the end-user of my controls to take control over how my child controls are generated. My web control should have complete control over that. Right now, the end-user can set ClientIDMode and impact my scripts. I have no problem with their dictating the value of the ClientID of the main control’s HTML, just the child controls. Microsoft should change the design to allow web controls to ignore ClientIDMode’s impact on their children.
  • If you cannot modify existing code to support ClientIDMode, you can either document to set AutoID on the containing NamingContainer’s ClientIDMode property or make your web control’s code set ClientIDMode=AutoID on every child control.

6 Comments

  • Have they just changed the "_ctl0" to "0" and put it at the end?

    ListView1_UserControl1_TextBox1_Img_0

    instead of

    ListView1_ctl0_UserControl1_TextBox1_Img
    [Peter's comments] Not in this case. The "0" at the end of "_ctl" has a different purpose. You see _ctl# each time a web control is output without anything assigned to its ID property. In that case, to avoid duplicates (when multiple controls have no IDs assigned), it assigns a different number.
    The new digit(s) at the end of the ClientID value (again only when using ClientIDMode=Predictable) are to handle controls inside rows of FormView, GridView, and other databound controls. The new digit is the actual row number, starting with 0 as the first row.


  • Peter,

    In the scenario you described, isn't it possible to force the ClientIDMode of the image control?

    image.ClientIDMode = ClientIDMode.Static;

    Raj
    [Peter's Comments]. Absolutely. My posting is specifically for the case when you have to deal with ClientIDMode=Predictable. As a web control developer, I have to allow the web form developer to assign ClientIDMode to my controls. That impacts the child controls my own web control code generates. This posting is attempting to show web control developers how to program for that problem.


  • its still complete bs that microsoft made it this way. I just cannot see how this came to be. You should of never had to write this code to combat this terrible design.
    [Peter's comments] I hear you. I suspect that they didn't realize they incurred a breaking change because the scenario is subtle. I have contacted the right parties at MS about the problem. Perhaps they'll fix the API prior to RTM?


  • I was thinking of the id you get from an ItemTemplate

    repeater1_ctl100_UserControl1_TextBox1
    repeater1_ctl101_UserControl1_TextBox1

  • Huh? I must be missing the point. It sounds like the old "Doctor, it hurts when I punch myself in the face"......

    If using the "Predictable" option causes you so much grief......Use one of the other options!

  • DaveRuss: For a control developer, which option to use is in someone else's hands

Comments have been disabled for this content.