Wednesday, April 7, 2010

Handling ClientID issues in SharePoint Webparts & jQuery

In ASP.NET/SharePoint a Control has a fully qualified ID that can be deduced from the control hierarchy and can be accessed with the properties ClientID or UniqueID. It then becomes the unique id or name of rendered html controls. It makes sense that these properties should be used right after the control hierarchy is completely defined, that means before the rendering, therefore in the PreRender.

What is not so well known is that accessing those two properties sets the _cachedUniqueID member, which sets irrevocably the ID to a control. That's why using these properties in ItemCreated events, for example, makes the html id of controls to remain the default defined one.

Example: In a DataList, you have an item template that contains a control, let's call it Control1. The rendered id in html will look like this: ctl00_m_g_aaf13d41_fc78_40be_81d5_2f40e534844f_txtName, but if you use ClientID inside the DataList_ItemCreated event, the rendered html id will be just Control1, thus making any javascript manipulation futile.

Of course, one could create a method to return the UniqueID without setting the cached value, since there are moments when the partial hierarchy is enough to define a proper id. Unfortunately, for controls without a specific and declared id, ASP.NET creates and automatic ID like ctl[number] or _ctl[number] and, of course, those methods and fields are all private or internal. One could use Reflection to get to them, but what would be the point?

UniqueID and ClientID are overridable, though, so one can change their behaviour in user defined controls.

Solution for handling in jQuery, Javascript & SharePoint Webparts (C#).

jQuery is fantastic! It makes client-side development faster and countless plug-ins are available for just about every need. Using jQuery with Asp.NET Web-Forms gets aggravating when dealing with nested server controls. ClientID’s get appended when using ASP.NET Master Pages. Objects in JavaScript tend to look like this:
ctl00_m_g_aaf13d41_fc78_40be_81d5_2f40e534844f_txtName
The difficulty of the issue above is that, in order to get the element txtName, It’s necessary to know the full “path”. It’s quite aggravating to refer to get the object using the method below:
document.getElementByID('ctl00_m_g_aaf13d41_fc78_40be_81d5_2f40e534844f_txtName');
This becomes a big problem when developing server controls or web parts that may be used in a typical ASP.NET application or SharePoint. You cannot hard-code the path above if you don’t know the full path of the control.

Fortunately, there are a few ways to get around this. There are three, in particular, I will mention. The first is the jQuery equivalent to the standard JavaScript method:
document.getElementById("<%=txtName.ClinetID%>");");
This can be done in jQuery by using:
$("#'<%=txtName.ClinetID%>");");
The second jQuery method does not require server tags. This method searches through all tags and looks for an element ending with the specified text. The jQuery code for this method is shown below:
$("[id$='_txtName']");
There are, of course, drawbacks to both methods above. The first is fast, but requires server tags. It’s fast, but it just looks messy. Also, it will not work with external script files. The second alternative is clean, but it can be slow. As I said earlier, there are several other alternatives, but these two are the ones I find myself using the most.

The third registering Javascript in C# code behind.
Page.ClientScript.RegisterStartupScript(GetType(), "saveScript",

String.Format("function EnableSave( isDisabled )"+

"{{ var saveButton = document.getElementById(\"{0}\");"+

"saveButton.disabled=isDisabled;}}", btnSave.ClientID), true);

Do not forget to call this script after controls have been loaded, I mean after Controls.Add(); in CreateChildControls method while developing webparts.

No comments:

Post a Comment