Using JQuery to Make Asp.Net Play Nice with Asp.Net

I have found that developing an Asp.Net application that makes heavy use of javascript is very difficult. One of the major pain points is dealing with the horrendously long ids that Asp.Net server controls generates. For example, the following Asp.Net markup:

<asp:TextBox ID="myTextBox" runat="server" />

can cause the TextBox to be rendered as:

<input name="ctl00$ContentPlaceHolder1$myTextBox" 
    type="text" id="ctl00_ContentPlaceHolder1_myTextBox" />

if the page uses a MasterPage or any moderately complex control hierarchy. Asp.Net does this to minimize naming conflicts. This is the whole concept of a Naming Container. Asp.Net uses the interface INamingContainer to mark controls that can be rendered inside of a naming container. The end result is a really long id that can change based on the items place in the control hierarchy. This is a problem if you want to use the elements rendered by server controls in your client side javascript code.

First, the Hack

One technique for getting around this I have seen, has a definite bad code smell to it. You basically have Asp.Net render the itemsID inside your javascript code using the <% %> syntax like so:

<script type="text/javascript">
    var myValue = document.getElementById('<%= myTextBox.ClientID %>');
</script>

this will render as

<script type="text/javascript">
    var myValue = document.getElementById('ctl00_ContentPlaceHolder1_myTextBox');
</script>

This technique has a couple of huge short commings:

  • It is really hard to maintain
  • It doesn’t support having your javascript in a library or a separte .js file

JQuery to the Rescue…sort of

There are many javascript client libraries out there that help ease the headache of writing javascript code that works across browsers. I am currently working on a project where we have decided to use JQuery which I am very impressed with.

One of the main features of using the JQuery library, is its use of selectors to find elements on the page. I will not get that deeply into this rich DOM querying language here, but you can read all about it from the JQuery Documentation.

JQuery selectors allow me to find items in the page many different ways. For example, I can find the textbox in my example above using the following code:

var myTextBox = $("input:text");

This will actually find every textbox on the page but since we have only on textbox on our page, it works. Alternatively, you can use something like the Css class of the item. Suppose we gave the textbox a css class of ‘textInput’:

<asp:TextBox ID="myTextBox" runat="server" CssClass="textInput" />

Now, the control renders as

<input name="ctl00$ContentPlaceHolder1$myTextBox" 
    type="text" id="ctl00_ContentPlaceHolder1_myTextBox" 
    class="textInput" />

so we can use JQuery to select just this item using the following javascript

var myTextBox = $("input:text[@class=textInput]");

or

var myTextBox = $("input.textInput");

or even simpler

var myTextBox = $(".textInput");

There are many ways to select the same item, but you get the idea.

So does this solve our problem with long IDs? No, not really, we would still have to use the following javascript to select this item by id:

var myTextBox = $("#ctl00_ContentPlaceHolder1_myTextBox");

The Solution

As I was working through this problem in my head, I remembered somethign about javascript: it is more than just the attributes that already exist in DOM elements. You are not restricted to just using ID or Class. So why not just come up with your own attribute to use just for the purpose of selecting DOM elements? So here is a pretty workable solution I came up with:

<asp:TextBox ID="myTextBox" runat="server" ClientSelector="myTextBox" />

I just use a made up attribute called ‘ClientSelector’ (you can use whatever you fancy and it renders like this:

<input name="ctl00$ContentPlaceHolder1$myTextBox" type="text" 
    id="ctl00_ContentPlaceHolder1_myTextBox" 
    ClientSelector="myTextBox" />

so now we can use JQuery to select the item with this statement

var myTextBox = $("input[@ClientSelector=myTextBox]");

So now we can use the same selector for just this one textbox no matter if it is in a MasterPage or not. We don’t have to care how the id renders anymore. I have found this technique very useful. What do you think? Is this also too much of a hack?

Advertisement

36 Responses to Using JQuery to Make Asp.Net Play Nice with Asp.Net

  1. Chad says:

    jQuery, yes!!! I love jQuery… anyway, yeah – that’s a great idea! I’ve definitely run into the same problem, but have usually just used the css class solution you detailed above to get around it. However, I like your solution as well, and will probably steal your idea.

    Something that may not be clear from your article, is that if your query matches more than one object, jQuery will execute the provided code for *all* objects found by the query. So, if you wanted to clear the values of all textboxes, you could do this:

    $(“input:text”).val(“”);

    There’s also this cool trick that I found recently that basically removes the need to reference the textbox by an ID or class or a custom attribute. It involves using something called “predicates”: (:first, :last, :eq)

    Examples:
    $(“input:text:first”); //grabs only the first textbox on the page

    $(“input:text:last”); //grabs the last textbox on the page

    $(“input:text:eq(2)”); //grabs the third textbox on the page

    $(“input:text:lt(3)”); //grabs all textboxes at positions less than 3

    Anyway, hope that helps.

    Good post.

    This of course only works to retrieve the *first* textbox on the page – but I’ve found it very useful

  2. Luke Foust says:

    Chad – that is a great point. It isn’t immediatly obvious that JQuery selectors return an array of items found (even if only one item is found).

    To get to the actual DOM element in the example above you would have to do something like:

    var myTextBox = $(“input[@ClientSelector=myTextBox]”)[0];

    I haven’t found a use for predicates yet but I do love the idea behind them. If I really do need to select a list of items, I try to select them by something like CssClass.

  3. Chad says:

    Yeah, I haven’t made too much use of predicates yet, but one statement that I’ve been using a lot is this:

    $(“input:text:first”).focus();

    Gives focus to the first textbox on the page… pretty helpful and it’ll work regardless of the ID or CSS class you use.

  4. Yex says:

    That’s a great solution to a problem that I’ve been wrestling with for a while. Thanks for sharing the idea.

  5. […] Using JQuery to Make Asp.Net Play Nice with Asp.Net « Spontaneous Publicity var myTextBox = $(“input[@ClientSelector=myTextBox]”); […]

  6. Dan says:

    Nice idea indeed

  7. Matt Casto says:

    The ClientID property on System.Web.UI.Control will give you a control’s ID as it will be in the rendered web page. You can use this to easily get a control’s ID. For instance, put the following code in your Page_Load…

    string script = “”
    + “var btnInsert = document.getElementById(‘{0}’);”
    + “”;
    ClientScript.RegisterClientScriptBlock(this.GetType(),
    “btnInsert_GetID”, string.Format(script, btnInsert.ClientID));

    Have your page dynamically generate javascript is kind of smelly too, though.

  8. Matt Casto says:

    Part of my code I just posted was mangled. The first and third line in the string declaration was adding the javascript begin and end tag.

  9. Luke Foust says:

    Matt – Thanks for the comments. That technique is similiar to my example above where I did:

    var myValue = document.getElementById(‘’);

    while that may work for some situations, it is not very scalable and you can’t use external .js files.

  10. That is a cleaver solution, no doubt and it really showcases JQuery’s power… but I also smell something a big fugly. 🙂

    The problem is, by adding attributes that don’t exist in the XHTML specification you’re going to produce markup invalid markup. This might not be a huge deal to some (or even most people) but it’s something I try to avoid if at all possible.

  11. Luke Foust says:

    Steven – I had thought of the compliance issue but fortunately I don’t often work on sites that require strict standards compliance.

  12. Steven,

    The XHTML requirement seems to me the big code smell. There is almost zero real world value in maintaining absolute XHTML compliance.

    Not saying we shouldn’t try to conform as much as possible;only that if the solution (such as this one) creates a clean maintainable solution, but violates XHTML, I think we’d all agree the solution should be used regardless.

    Then again, most of my work in for internal consumption these days, not external.

  13. Rory Fitzpatrick says:

    I’m guilty of many counts of the first example, at the time you just think what the heck!? I agree that standards compliance isn’t the be all and end all when an outcome like this one reaps many benefits. My only concern would possibly be the speed of the selector – I’m sure its negligible if you’re doing one or two, but a lot could cause problems. Have you tried timing it? The jQuery test suite may have a similar example that would confirm or deny this concern, I don’t have the time to look right now….

  14. Although I prefer to use classes for this kind of thing (you do know you can have multiple classes on the same element, separated by spaces, don’t you?), it is possible to make these custom attributes validate in XHTML:

    http://www.alistapart.com/articles/customdtd/

    @Lucas: I think it’s going a bit far to describe XHTML as a bad smell. It hasn’t made the progress it might have thanks to a certain market-dominating corporation, but I find it worthwhile to have all my content available as valid XML – you never know what further processing might one day be required.

  15. es says:

    I don’t see how using ClientID is messy if you maintain the javascript code in that page/control. It will always return the correct ID so no maintainance is needed.

    If you use external .js files, then yes, I agree its a pain but ClientID is not nearly as messy and smelly as you are making it out to be.

  16. SeanG says:

    This is a great article! It has the right attitude! I think I’ll just use custom attributes. That’s what I’ve been doing in other apps I’ve been working on.

    @Chrie Ovenden: The article regarding custom DTDs is extremely interesting, and makes me feel all warm and fuzzy inside. I forgot that you can declare your own DTDs!

  17. SeanG says:

    To be honest, though, now that I think about it, it may be best to just use the Css class to find the given element.

    Microsoft has clearly chosen to favor the Css class of elements for this purpose, because your stylesheets will break if you refer to elements by Id — just like your Javascript!

    So, really, all you have to let go of is the fact that more than one element can have the same class name. And really, if robust and complex web development is made easier by munging Id values, perhaps it’s truly appropriate to start using Css class names in this way.

    And you’ll never screw yourself by using the same class twice.

    And like @Chrie Ovenden points out, you can always use multiple classes to specify a generic class as well.

  18. […] Foust, in his article Using JQuery to Make Asp.Net Play Nice with Asp.Net, explains a couple of client-side methods to let your javascripts select the right element in this […]

  19. Rick Strahl says:

    I’ve been back and forth on this as well. I agree with the commenter though that says that embedding the ClientID is not as bad as a solution as you’re making it out to be. In fact, it’s the most efficient solution. Adding Attributes and having jQuery find it only adds additional code you have to maintain in two places now.

    What I’ve been doing in my apps is create a startup script block in markup with global vars that load all the controls in one place. That way at least it’s all localized in one place.

    I was thinking it might be cool to create a control that automatically generates client controls for each server control either automatically or via some trigger you add in code. It just goes through the collection pulls each one out and writes out the appropriate control code on the client into the startup code. Well, for another day…

  20. Jim says:

    I love the idea of the custom attribute. I have used them in the past for client side validation routines. Since I am using customized versions of most of the base asp.net controls, it makes this idea even easier to incorporate.

  21. Col says:

    personally I’m amazed that M$ haven’t included a function to deal with this problem in their JS framework. Anyway, i’ve quickly knocked up a JS function you can use to get around the NamingContainer problem. You pass it the ID and the tagName of the control you’re after; it first checks if it can simply find the element using getElementById; if not it finds all elements with the tagName you supplied, loops through them and checks if the ID you’ve supplied is contained within the ID of each element. If it finds one it returns it.

    function findElement( id, tagName ) {
    // see if we can get it simply
    var elem = getElementById(id);
    if ( elem != null ) { alert(“quick”); return elem; }

    // find it if it’s in a naming container
    var elems = document.getElementsByTagName(tagName);
    var elemId;
    for (var i = 0; i <= elems.length; i++) {
    elem = elems[i];
    if ( (elem.attributes) && (elem.attributes[“id”])) {
    elemId = elem.attributes[“id”].value;
    if (elemId.match(id) != null) { return elem; }
    }
    }
    }

    Feel free to use this, modify it, comment on it or ignore it!

  22. Col says:

    just realised I left an alert(“quick”); in there from debugging…

  23. Sam says:

    Remember that the fastest way to find a DOM element is by its ID, because the browser maintains a hashtable of ID -> element values internally.

    Searching by class or expando has the potential to be a lot slower and at the end of the day we want speed from JavaScript.

    For this reason I render ClientID references into a JavaScript object in the page, which is then passed as an initialization parameter to an external script module. You can get the benefit of external scripts without having to hardcode their client ID references.

  24. Ron says:

    I agree with Sam. One of the problems I had with using anything but an ID to find an element is performance. If you have a very large page and use a class name to find a single object, it’s noticeably slow because jquery has to go through all the objects on the page. Doing the input[@selector=name] trick is a little faster because it immediately restricts the search to input elements, but it’s still slower than using an ID.

    I think the absolute best answer would be for Microsoft to add an option to ASP.NET that reduce it’s need to rename client ids. Master pages is the biggest cause of this and I for one never use the same ID for objects in my master and content pages, so there’d be no conflict. It gets a lot tricker though if you are embedding arrays of user controls or repeaters or things that will have repeat ids…

    Sam’s suggestion actually sounds the best so far. It shouldn’t be hard (if he hasn’t done it already) to have some code that dynamically returns a javascript include file that has a cross reference you can use to more easily get your elements.

  25. Steve says:

    MS’s mungling of the ID’s is a bad smell. If they are concerned with duplicate ID’s, then throw an exception.

    “Duplicate ID ‘label1”

    Developer goes to the source, makes sure it’s a unique ID and continues on.

  26. Douglas Greenshields says:

    I don’t understand why you feel the need to invent an XHTML attribute that will invalidate your document (quite apart from the fact it uses caps) when it would be much easier to just declare other classes, and make the jQuery much more succinct.

  27. I agree that creating a custom attribute is a bad thing, at least in the world of XHTML. Unique class selectors are easy enough to work with, at least when it comes to finding the element(s) with jQuery (imho).

    I hate the fact that I cannot use the same Id attribute on the client side as on the server side – it makes writing client/server code so cumbersome. I do not want to be forced into using MS’s JS library just so I can “easily” write ajax pages. I prefer jQuery, thanks.

    Sam’s method sounds really good to me – I wonder if he would post some sample code?

  28. While it makes sense to use classes I wouldn’t use them simply because it is semantically incorrect and would restrict my flexibility in styling common elements the same way.

    Has anyone tried overriding the INamingContainer class(?) to make it always print out the element ID (without all the crap before it)?

  29. Mike J says:

    I strongly disagree with the idea that “putting the client id in the javascript” is not a Bad Idea(tm).

    If you are making any efforts to be an actual developer and are looking for good, maintainable ways to accomplish tasks ( like was the original intent of this article ) then you should see the issue with this. If you couple your javascript ( front-end behavior ) with your back-end generation ( server-side controller essentially ) then how can you ever do standalone tests to make sure your HTML renders correctly on it’s own? How can you ever test to know that the javascript code you wrote works correctly? Simple answer : You *can’t*.

    You should always, always be able to separate the different components of your apps to be able to test them. Then if a bug crops up, you know if you are fighting an issue with the base code, or the two playing together well – as happens frequently with .net coupled with other tools.

    If you ask me, the Bad Smell comes from .Net operating under the assumption that nobody who uses it to do web development could ever possibly know ANYTHING about writing valid html / css / javascript code and therefore is obligated to assist you because it will always know better than the developer using it.

  30. Rob says:

    Why not just use JQuery selectors:
    $(“input[id$=myTextBox]”)

    Looks for any input element with an ID that ends in “myTextBox”. Or to ensure no conflicts (though I didn’t test this one):
    $(“input[name$=$myTextBox]”)

  31. zeb says:

    for all of my tests, the jquery cannot get the serverside clientid since the client is called before the server side.

    even simple examples i copy/paste never work,case in point is the attached code sample. In both IE and FF the it steps right over the ready function:

    $(document).ready(function() {
    $(“#”).hide(‘slow’);
    });

  32. geedubb says:

    Of course if you are using ASP.NET 4, you can use the ClientIDMode property in the page directive to control how the IDs of the server side controls are named.

    This helps a lot in getting the client side JS to reference by IDs.

  33. leighshenn says:

    Code below worked like a charm for me using the CssClass technique:

    …..
    $(document).ready(function () {
    $(“input.dueDate”).datepicker();
    });

    …..

    <asp:TextBox ID="txtDateClosed" CssClass="dueDate"
    runat="server" Text='’/>

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: