Monday, January 24, 2011

Understanding ClientIDMode="Predictable" in Asp.net 4.0 with Examples


DARE TO SHARE?
Introduction

If you are an Asp.net developer, you may already be well-aware of the fact that, Asp.net 4.0 lets you control how the ID property value of the server controls gets generated in the HTML markup. Before version 4.0, this wasn't possible, and, the ID property value of all server controls used to be generated using the nesting structure of the control hierarchy using some complex syntax which was not so readable and usable.

It was very common to see a server control's ID property value to be generated as follows:

<input name="ctl00$MainContent$GridView1$ctl04$TextBox1" type="text" 
value="Matin" id="ctl00_MainContent_GridView1_ctl04_TextBox1" />

Frankly speaking, we, the developers used to not to bother too much about these generated ID's in the HTML markups. Why? well, we somehow got used to the fact that the generated ID's were not really usable and if we ever needed to access any element on client side, we either used to add some custom properties to the server controls, or, used JQuery with using some searching pattern to find out the element we need and do the rest. Well, it wasn't a "very hard thing to do", but, it was something we had to do each time we need to access elements on client side, and, this used to require some more time to finish our works.

Let us see an example. suppose we are in need of showing some Employee information using a GridView as follows:


Figure : A simple GridView showing some listing data

Lets assume the requirement is to show the user's name (Or, may be to show user profile) when user clicks the "View" button.

Let's look at the XHTML Markup of the GridView:
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" ShowHeader="false">
    <Columns>
        <asp:TemplateField>
            <ItemTemplate>
                <asp:TextBox ID="TextBox1" Text='<%#Eval("Name") %>' runat="server"></asp:TextBox>
                <asp:Button ID="Button1" runat="server" Text="View" />
            </ItemTemplate>
        </asp:TemplateField>
    </Columns>
</asp:GridView>
As you can see, the Name property value of each employee is being shown using a <asp:TextBox> server control, and, an <asp:Button> control is used to display the "View" button on the browser. Both these are inside the <ItemTemplate> element to ensure that, the TextBox and the Button is displayed in each row.

Now, according to the requirement, we need to display the employee profile (Only "Name" in this case) when the "View" button was clicked. For simplicity, let's assume that the "Name" is to be displayed via an "alert" message.

We have a problem

In order to show emplyee's Name using Javascript, we need to access the TextBox of the corresponding row. Question is, when a Button is clicked on a particular row, How do we access the Name of the Employee inside a TextBox on the same row using JavaScript?

We need to know the TextBox client ID, right? Let's see how we manage this.

Here is the HTML that gets rendered in the browser for the GridView:



Figure : Rendered HTML for GridView

As you can see, the ID of the TextBoxes and ID of the Buttons (Marked in red border) are generated by Asp.net, and, these ID's are not really "Predictable" for use while we are writing server side codes. It is obvious that, the generated ID value depends on the hierarchy of the server controls in which the are nested.

Naturally, it is hard and unreliable to write client side scripts (javascripts) to read the value of other element in the same row, in such situations. So, to avoid guessing client IDs of element in the same row, a common strategy was to "feed" the client ID of the elements in the server side code.

Not clear? Well, let's see how we used to manage the overall implementation for this example:

1. Define an "OnRowDataBound" (or similar) event method to the data bound control, which gets fired when each data is bound to the data bound control.

In our case, we used to add the following event to the GridView:

<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" ShowHeader="false"
onrowdatabound="GridView1_RowDataBound">
...
</asp:GridView> 
2. Find target controls inside this event method (Which gets fired for each row) and add an "onclick" javascript method as an attribute of the Button control along with passing the necessary values as method parameter:

protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e)
{
    if (e.Row.RowType == DataControlRowType.DataRow)
    {
        Employee emp = e.Row.DataItem as Employee;
        if (emp != null)
        {
            Button button = e.Row.FindControl("Button1") as Button;
            if (button != null)
            {
                button.Attributes["onclick"] = string.Format("return ViewDetails('{0}')", emp.Name);
            }
        }
    }
}
3. Define a JavaScript method to display the necessary information (Employee name in this case)

function ViewDetails(Name) 
{
    alert(Name);
    return false;
}

Not very elegant, right?

ClientIDMode control property in Asp.net 4.0

In Asp.net 4.0, a new property called "ClientIDMode" has been added the the Control class which lets you control the ID values for the Server controls the way you like. To be very specific, if you set the ClientIDMode="Predictable" (With some other minor settings), you know in advance what the exact client ID of the server controls will be (No matter what nesting structure the control is within, and, no matter which DataBound control uses them). This is really handy if you need to access those element using some client side scripts like JavaScript.

Before seeing a practical example of this property value, let us see the possible options of this property:

ClientIDMode="AutoID"

The classic mode, which is default. ID's are generated by Asp.net run time using the existing logic.

ClientIDMode="Static"

Lets you specify a static ID for the server control, which remains unchanged no matter where the server control is used. Also makes sure that, any nested/child server controls doesn't use this contro's ID as a part of thier ID values.

ClientIDMode="Predictable"

Lets you control the client ID values the way you like, with some other property settings. More on this later.

ClientIDMode="Inherit"

Specifies that, the control should inherit the ClientIDMode from it's immediate parent server control

The ClientIDMode could be configured in the Application level (in web.config), or, in the Page level, or, in the Control level.

So, let us see how the ClientIDMode property could be used to control the client IDs for the nested server controls which are used inside a DataBound control (In our example, GridView).

The same GridView markup is now as follows:

<asp:GridView ID="GridView1" Visible="false" ClientIDMode="Static" ShowHeader="false" ClientIDRowSuffix="Id" AutoGenerateColumns="False" runat="server">
    <Columns>
        <asp:TemplateField>
            <ItemTemplate>
                <asp:TextBox ID="TextBox1" ClientIDMode="Predictable" Text='<%#Eval("Name") %>' runat="server"></asp:TextBox>
                <asp:Button ID="Button1" runat="server" ClientIDMode="Predictable" OnClientClick='return ViewEmployeDetails(this.id)' Text="View" />
            </ItemTemplate>
        </asp:TemplateField>
    </Columns>
</asp:GridView> 
Using the ClientIDMode property

Lets try to understand the facts with this Markup:

The GridView has ClientIDMode = "Static". This implies that the GridView's ID will be "GridView1" and no child server control's Id will have "GridView1" as part of their ID values.

The TextBox1 has ClientIDMode = "Predictable". This implies that the TextBox's ID will be of TextBox_X format. Here the value of X depends on another property of the DataBoundControl (ClientIDRowSuffix). If ClientIDRowSuffix property is not configured, X is just a sequence of values starting from 0 (TextBox_0, TextBox_1 and TextBox_2 here). On the other hand, if ClientIDRowSuffix property is configured with a property name of the Entity with is being bound (ClientIDRowSuffix="Id" here), X is replaced by the value of the corresponding property (TextBox_ID in this case).

The Button1 also has ClientIDMode = "Predictable". So, the same rule also applies here as above.

So, if we have an Employee class as follows:
public class Employee
{
    public int Id
    {
        get;
        set;
    }
    public string Name
    {
        get;
        set;
    }
} 

And, if we initialize the GridView with some Employees as follows:
protected void Page_Load(object sender, EventArgs e)
{
    var Employees =
    new List<Employee> {
        { new Employee { Id = 10, Name = "Shubho" } },
        { new Employee { Id = 11, Name = "Titu" } },
        { new Employee { Id = 12, Name = "Matin" } }
    };

    GridView1.DataSource = Employees;
    GridView1.DataBind();
}
The HTML that gets generated now is as follows:



Figure : HTML markup that gets generated using ClientIDMode property

Note that, ID of the Text boxes are getting generated in the pattern "TextBox1_X" property (TextBox1_10, TextBox1_11 and TextBox1_12), and, ID of the buttons are getting generated in the same pattern(Button1_10, Button1_11 and Button1_12).

Also note that, because we used ClientIDRowSuffix="Id" for the GridView, the X values are replaced by the ID of the Employees. If we didn't use this property, the value of X would be replaced by a sequenece number starting from 0 and the generated Id's for the text boxes and the buttons would be (TextBox1_0, TextBox1_1 and TextBox1_2) and (Button1_0, Button1_1 and Button1_2) respectively.

So, here is the benefit of having the control ID's having a predictable form:

As each Button has the form Button1_X and each TextBox has the form TextBox1_X, when user clicks a button, we can get the corresponding Employee ID by replacing the "Button1_" with a blank string "". Once we know an Employee's ID, we can construct the ID of each control in the same row which has ClientIDMode="Predictable". That is, we can construct the corresponding TextBox ID as "TextBox_" + ID.

To simplify this further, we can just replace "Button1" with "TextBox1" in the Button's ID to get the corresponding TextBox ID. Once we know the ID of the TextBox, accessing it's value is a matter of child's play.

So, here is the JavaScript method definition that we used as an "OnClientClick" handler for the Buttons:

function ViewEmployeDetails(idOfButton) 
{
    var idOfTextBox = idOfButton.replace("Button1", "TextBox1");
    alert(document.getElementById(idOfTextBox).value);
    return false;
}
As you can see, no need of define any "OnRowDataBound" event method now just to attach JavaScript methods as attributes with necessary parameters, and, setting the ClientIDMode and related property values does the work. This lets us get rid of the extra works that we required to do earlier, adn thus save some of our valuable times and let us feel that we are now able to work smarter then earlier.

Smart work from Asp.net 4.0 team!

4 comments:

Jean said...

Thanks for the article, was very clarifying!

Unknown said...

Thank you shubho....you made it simple to understand...!!

Pooja said...

i really liked the way you explained in a very simple terms.Its really easy to understand. Thanks a ton Shubho!.

Anonymous said...

thanks its a usefull article
careersoft-technology

Post a Comment