AJAX-Enabled Web-Parts: solving the problem of changing Client IDs
One of the most challenging aspects of building SharePoint-enabled web parts is using JavaScript to interact with specific controls. As a consequence of how SharePoint renders controls with id’s in Sharepoint WebParts, it’s near impossible to write clean JavaScript.
From a maintenance perspective I HATE using server-side code to render JavaScript. It’s inelegant and reeks of a lack of forethought. Yuck yuck yuck. It’s like sweeping dust under the rug.
Wherever possible, webpart-specific javascript should be held within a single .js file (stored in the 12Hive). The problem is that Sharepoint alters a control’s ID during the page rendering process.
So, a label with the ID “label1” might become “ctl00_label1”. The naming process is entirely unpredictable.
This makes interacting with a specific DIV, INPUT or any other html element very difficult because you can’t predict the rendered IDs during development. This is especially problematic if you’re looking to host several instances of a webpart on a single page.
One really terrible way of dealing with this is to render the html yourself by adding literal controls ala
LiteralControl l = new LiteralControl();
l.text = “
blah
”;
this.Controls.Add(l);
This is a terrible approach which is severely limiting and unnecessary.
To tackle the problem I’ve devised the following solution which revolves around creating a Javascript class to accompany the web part:
- create properties to store the dynamically generated IDs of every html component to wish to interact with
- create pointers to every html component you wish to interact with
- create an init function. As input you pass the clientID of the webpart AND the id’s of every component you wish to interact with.
- this function will store the id’s using the properties
- this function will also attach the pointers to their respective objects
- this function should also attach any eventhandlers
- create any supporting functions (i.e. webservice calls, webservice receivers)
This approach embraces the absence of reliable identification info. It is also sympathetic to having multiple copies of a webpart on the same page.
Here’s what the javascript might look like
function UpdatingLabelWebPart()
{
var me = this; //required to reference the object at runtime
this.clientID = null; //ids
this.labelID = null;
this.buttonID = null;
this.label = null; //controls
this.button = null;
this.register = function (clientID, labelID, buttonID)
{
me.clientID = clientID; //init the id
me.labelID = labelID;
me.buttonID = buttonID;
me.attachToButton();
}
this.attachToButton = function()
{
//if the controls haven't loaded yet
if (document.getElementById(me.buttonID) == null || document.getElementById(me.labelID) == null)
{
setTimeout(me.attachToButton, 50);
}
else
{
//register the controls
me.label = document.getElementById(me.labelID);
me.button = document.getElementById(me.buttonID);
//attach any events
button.AttachEvent("click", me.buttonClicked);
}
}
this.buttonClicked = function(sender, args)
{
me.label.innerHTML = "Hello World";
}
}
Then in your webpart you write a teeny tiny bit of javascript which you register as a clientscriptblock.
String myScriptBlock = String.Format(@"
var tmp = new UpdatingLabelWebPart();
tmp.register('{0}','{1}','{2}');
", this.clientID
, this.label1.clientID
, this.button1.clientID);
Page.ClientScript.RegisterClientScriptBlock(typeof(Page), "UpdatingLabelWebPartJS",myScriptBlock, true);
Page.ClientScript.RegisterClientScriptInclude(GetType(), GetType().ToString(), "_layouts/1033/myJS/UpdatingLabelWebPart.js");
This approach lets you have multiple instances of the same AJAX-enabled webpart and saves you the hassle of having to write annoying javascript (having to write me.label is hardly a chore).