I’ve been using a small hack to deal with the ASP.NET naming container morass that is so prevalent in client side development with ASP.NET. Particularly in Master Pages all server control IDs are basically munged because the master page place holders are naming containers.
To recap – the problem is that if you are dealing with something as simple as this:
<div class="contentcontainer" runat="server" id="PageContent">
<h2>Stock Quote Lookup</h2>
<div class="labelheader" style="margin-top: 20px;">Enter a stock symbol:</div>
<asp:TextBox runat="server" ID="txtSymbol" />
<input type="button" id="btnGetQuote" value="Go" />
<div id="divStockWrapper">
<div id="divStockDetail">
</div>
<img id="imgStockChart" />
</div>
</div>
inside of master page or even a container or user control you end up generating HTML output that looks something like this:
<div class="contentcontainer" id="ctl00_MainContent_PageContent">
<h2>Stock Quote Lookup</h2>
<div style="margin-top: 20px;" class="labelheader">Enter a stock symbol:</div>
<input type="text" id="ctl00_MainContent_txtSymbol"
name="ctl00$MainContent$txtSymbol" />
<input type="button" value="Go" id="btnGetQuote"/>
<div id="divStockWrapper">
<div id="divStockDetail">
</div>
<img id="imgStockChart"/>
</div>
</div>
Notice the ctl00_MainContent prefixes that are added to the server controls that point to the master page and content container respectively. You get those nice and un-friendly ASP.NET naming container generated ids that are a bitch to use in client side code. You don’t want to use these generated names directly because they can easily change if you rename any of the IDs down the chain – if you change the content placeholder’s id so will the id and your script code might break because of it. Not a good idea to use those ids directly.
Now if you want to access this client code you have to jump through a few hoops. A few common ways to access these values with jQuery look like this:
$("#<%= txtSymbol.ClientID %>")
or slightly better more generic approach that stores all client IDs in an object globally:
var ids = {
txtSymbol: "<%= txtSymbol.ClientID %>",
PageContent: "<%= PageContainer.ClientID %>"
}
// to use it any where in code:
$("#" + ids.txtSymbol)
The latter also partially gets around the main problem with ClientIDs in that they only work in ASPX pages not external .js files. The latter approach lets you declare only the id definitions in the main page, but the IDs can be globally accessed even in a .js file.
I also posted about a ScriptVariables component that can automate this object creation on the server as part of a component (ScriptVariables component and updated code) so you don’t have to manually map all of the client ids – the component can do it automatically (as well as a few other things like passing any values into JavaScript explicitly with proper formatting ). With this component you have a couple of lines on the server (anywhere in a Page):
ScriptVariables scriptVars = new ScriptVariables(Page, "serverVars");
scriptVars.AddClientIds(Form, true);
which generates a serverVars variable into the client. You can then use the ids like so:
$("#" + serverVars.txtSymboldId)
All of these workarounds are pretty ugly though.
A better Solution
A solution I’ve been using a lot lately is by taking advantage of jQuery itself. We can search for Ids directly from JavaScript since there’s a pretty simple pattern to clientID – at least when dealing with non-repeating values.
Remember a typical client ID that isn’t in a list control of some sort looks something like this:
ctl00_MainContent_txtSymbol
which is actually fairly easy to find with jQuery:
alert( $("[id$=_txtSymbol]").attr("id"));
You can use that directly in your JS code from anywhere that works for most situations, but while it’s not exactly complicated you still have to remember what the format of these controls looks like and how to do an attribute search. It also doesn’t deal with the case where a control might just exist on a page without a master/naming container. How many times have you created a client code page that started out as standalone page and then you later added a Master Page only to find out most of your script code is broken due to the invalid IDs? I know I have.
Knowing the above though it’s easy enough to create a small routine that returns us a jQuery object based on only the id:
function $$(id, context) {
var el = $("#" + id, context);
if (el.length < 1)
el = $("[id$=_" + id + "]", context);
return el;
}
This code uses jQuery to first do a plain ID search and the behavior of the function is the same as the native jQuery constructor except for the specific selector implementation that only finds IDs. Just as with jQuery you can pass in a relative selector context ( a jQuery object or DOM Element) that limits the search to children of that elements’ level in the DOM hierarchy.
If the function finds a direct match on the ID that’s returned. If not it checks to see if a context was passed. A context is the container context and if passed the search here is limited to that specific container and its immediate children. This is meant to make it easier to isolate naming containers in case there is a possibility of overlapping names so you can drill into a specific user control for example.
So in simple usage to select my ctl00_MainContent_txtSymbol value both of the following work:
alert( $$("txtSymbol").attr("id") );
Or if I want to be specific about the container:
alert( $$("txtSymbol",$("#wrapper")).attr("id") );
This is a low impact approach to dealing with client ids that is easy to type and understand. Obviously you can name this function anything you chose – the $$ just seemed like a good choice to me that doesn’t conflict with anything that jQuery isn’t already conflicting with :-}. I suppose it would also be possible to create a special selector operator in jQuery for this, but I think I actually like the single function implementation better even though it lives outside of jQuery.
Please realize that this won’t address all scenarios where naming container variables might be involved. Specifically this function only deals with the simple ASP.NET variable case – it doesn’t deal with list controls that output sequentially numbered ids, but then that’s not really been an issue for me. I don’t think one often searches for list items by ID directly – they are usually accessed by event behavior or iteration – rarely directly by ID.
Single controls however - I constantly access to get or set values on and manipulate items. While I get more and more away from using server controls at all there are still plenty of times when naming container naming ends up in markup and this makes using these controls a no-brainer.
Performance
Also realize that doing attribute selector searches are fairly slow (but then ‘slow’ is relative). If you have very large documents with lots of elements and you’re doing many lookups using this function you may run into slow performance issues. If that’s the case using the original concept of a global variable that holds all the ids – either manually created or via the ScriptVariables component from the West Wind Web Toolkit – to get better performance. Because that approach uses direct element ids it’s very efficient as ID lookups in a document are among the fastest lookups the DOM can perform.
So as usual there are trade-offs – you’re trading convenience over performance with the $$() function I showed here.
I hope some of you find this simple solution useful.
Other Posts you might also like