JavaScript, SharePoint, and the getElementById Blues
It doesn’t take long for anyone who does client-side coding in SharePoint to figure out that the trusty ol’ getElementById() method just ain’t all it’s cracked up to be. Sure, it works fine in standard ASP.NET pages but in SharePoint all that pretty client-side code goes right out the window. Just what is going on here?
The simple answer is that SharePoint is coming between you and your control ID’s. Keep in mind that a typical SharePoint page may have dozens of individual server controls, user controls, field elements web parts, and other objects scattered throughout the page hierarchy. In order to maintain viewstate for all these elements, SharePoint must translate simple, easy-to-read ID’s into something that it can be sure is unique, as it has no way to determine if that same control ID might exist somewhere else on the page. So something as easy as ‘txtTextBox1’ becomes ‘ctl00$m$g_1438a021_e6f0_4010_bf4b_33713d2e724a$ctl00
$PersProviderGeneral.ascx$SubNavBase1$General$fvFormView$txtTextBox1′. Ouch.
The real problem here is that SharePoint’s funky ID’s aren’t created until the page is rendered and they may change depending upon what other elements exist on the page. So just grabbing the ID from View Source doesn’t guarantee that your script is going to work. The trick here is to capture the ID before it is rendered to the client and use it in your getElementById() method.
Instead of this:
document.getElementById(‘txtTextBox1’);
You’ll need to do this:
document.getElementById(‘<%=txtTextBox1.ClientID%>’);
But wait, that’s only the beginning. If you, like most other ASP.NET 2.0 programmers, are using any .NET-specific controls, such as GridView, FormView, ItemTemplate, etc. you’ve got another problem. SharePoint builds the unique identifier for individual controls by inspecting the control hierarchy; that is, the long ID string is actually composed of strings representing the control AND it’s parent elements. So the sample ID above actuall represents this structure:
[General Control ID]$[Page GUID]$[Root User Control Name]$[Container Control Name]$[Container Control Name]$[Parent Control]$[Child Control]
This will come back to haunt you if your contol is inside of another control as [Control Name].ClientID will only generate an ID with the topmost parent (the page itself) and the specific control name – it will skip the containing elements in between. To get around this, you frst need to locate the control in the page hierarchy using server-side script and then get the client ID. So, if you are trying to reference a textbox inside of a FormView, the script would look like this:
document.getElementById(‘<%= ((TextBox)this.fvFormView.FindControl("txtTextBox1")).ClientID %>’)
The above code uses the FindControl method to locate a control with the ID of ‘txtTextBox1’ inside of a Form View with an ID of ‘fvFormView’, casts it as a System.Web.UI.WebControls.TextBox object, then renders the Client ID property for the control in its proper context. Now the client-side script will have the proper ID string to located the control on the page after SharePoint has intervened. This same method can be used for just about any control on the page so long as you remember to honor the hierarchy of the control structure (including nested controls).
Most of the controls in sharepoint implement INamingContainer interface which essentially ensures the uniqueness of ID of the control no matter how it is used. This is an Asp.Net concept and is not something that sharepoint is not a sharepoint only thing. Typically if your control needs to do something in script and is rendering the startup script or atleast rendering the call to that script the best way is to pass the ClientID in the call to that script. The value of ClientID can be obtained in the code behind and passed in when rendering the call to your script. The example below explains basically how to do it. The only gotcha is that the clientId value is only available after the control is added to the control collection.
For e.g
//Script
SomeSriptMethod(Id)
{
var ctrl = document.getElementById(Id);
//Do something with the control
}
//Html – Say this script is called by onClick handler on an “a” tag
aaa
Now the next thing is when your control is adding the hyperlink the code should kind of look like this
CreateChildControls(…)
{
HyperLink link = new HyperLink();
//Set properties on the link as needed
this.Controls.Add(link);
//Note the clientId for link wont have the correct value till you add it to the control collection otherwise it will give you what you set in the Id for the link
link.Attribute[“onClick”] = “javascript:SomeScriptMethod(‘ + link.ClientID + “‘);” ;
}
Note: I am not doing any encoding but the control should do appropriate encoding of link.ClientID and this code wont compile and should be treated as pseudo code only.
function getMyElement(sName) {
try {
var aTagTypes = new Array(“DIV”, “INPUT”);
sName = sName.toUpperCase();
for (var i = 0; i < aTagTypes.length; i++) { var sTagType = aTagTypes[i].toString().toUpperCase(); var aEmts = document.getElementsByTagName(sTagType); for (var ii = 0; ii < aEmts.length; ii++) { var oOb = aEmts[ii]; var sID = oOb.getAttribute("id").toString().toUpperCase(); if (sID.indexOf(sName) > -1) {
return oOb;
}
}
}
} catch (e) {
alert(“ERROR: ” + e.Description);
}
return null;
}
function doThis() {
var s = getMyElement(“Text1”).value;
//alert(“The value of this field is: ” + s);
getMyElement(“oDIV_02”).innerHTML = s;
}
function getMyElement(sName) {
try {
var aTagTypes = new Array(“DIV”, “INPUT”);
sName = sName.toUpperCase();
for (var i = 0; i < aTagTypes.length; i++) { var sTagType = aTagTypes[i].toString().toUpperCase(); var aEmts = document.getElementsByTagName(sTagType); for (var ii = 0; ii < aEmts.length; ii++) { var oOb = aEmts[ii]; var sID = oOb.getAttribute("id").toString().toUpperCase(); if (sID.indexOf(sName) > -1) {
return oOb;
}
}
}
} catch (e) {
alert(“ERROR: ” + e.Description);
}
return null;
}
function doThis() {
var s = getMyElement(“Text1”).value;
//alert(“The value of this field is: ” + s);
getMyElement(“oDIV_02”).innerHTML = s;
}
We want to add header breaks between editform input boxes based on strings in the column names. We are very close, but the insertbefore line does not execute.
The full post in under Technet Sharepoint – “Inserting a Table row with Javascript” user JCNET