Foliotek Development Blog

Extending jQuery to Select ASP Controls

by Brian Grinstead

If you have worked with JavaScript in an ASP.NET Web Forms environment, you almost certainly have been frustrated that this markup:


	<asp:textbox runat="server" id="txtPhoneNumber"></asp:textbox>

renders out as something like:


	<input type="text" id="ctl00_ctl00_ctl00_main_Content_txtPhoneNumber" name="ctl00$ctl00$ctl00$main$Content$txtPhoneNumber">

The fastest and easiest way to get a reference to a DOM element in JavaScript is using the ID attribute and document.getElementById(). Unfortunately, the ID attribute generated by the server is unpredictable and based on the server naming containers. There are a couple ways of that I have previously dealt with that problem, but both have problems.

Old Solutions

  1. Accessing the ClientID of the server control

    If you use inline code inside <% %> tags, you can access the ClientID directly from the .aspx page.

    
    	var goodID = '<% txtPhoneNumber.ClientID %>';  // = 'ctl00_ctl00_ctl00_main_HeaderTabs_txtPhoneNumber'
    	var badID = 'txtPhoneNumber'; // The text box does not have this ID, it will not work
    	var uglyID = 'ctl00_ctl00_ctl00_main_Content_txtPhoneNumber'; // DO NOT hardcode the generated ID into your code!
    
    

    This is not ideal because you cannot reference the ClientID from outside of the page, so you cannot keep your JavaScript in external files.

  2. Setting attributes on control and accessing with jQuery selectors

    jQuery has an excellent selector API that can easily grab an element if you know attributes on it. So, if there were a couple controls defined as:

    
    	<asp:textbox runat="server" id="txtPhoneNumber" cssclass="txtPhoneNumber"></asp:textbox>
    	<asp:textbox runat="server" id="txtAddress" clientid="txtAddress"></asp:textbox>
    
    

    You could access them with jQuery:

    
    	$(".txtPhoneNumber").keyup(...);   // This works
    	$("[ClientID='txtAddress']").keyup(...);   // This works
    
    	$("#txtPhoneNumber").keyup(...);   // This still DOESN'T work
    	$("#txtAddress").keyup(...);   // This still DOESN'T work
    
    

    This is not ideal because it requires adding extra attributes onto any server control that you want to access with JavaScript.

Original jQuery Solution

I first happened upon a solution to the same problem over on John Sheehan’s blog. This looked promising, but did not work the current latest release of jQuery (1.3). Another contributer to this blog, Tim Banks, updated the code in to work with the newer version.

However, I found an error in Internet Explorer and a more reliable way to get the ClientID would be to use the jQuery attribute selector and match based on an “id” attribute that ends with the Server ID. So,I wrote a JavaScript function called $asp that returned a jQuery collection. This function worked well and was implemented in the latest Foliotek release.


	function $asp(serverID) {
		return $("[id$='" + serverID+ "']");
	}

	// Once this function is included,you can call it and get back a jQuery collection
	$asp("txtPhoneNumber").keyup(...);

Updated jQuery and Sizzle Solution

It seemed like it would be better if the solution actually extended the selector engine rather than using a function to select objects. This would fit better into a jQuery development paradigm, allow more complex selectors, and also allow the selector to be used in other library functions, like “filter” or “find”. Also, it would be nice to be able to use the tag name in the selector to give a performance improvement, since getElementsByTagName is a fast operation that will narrow the element collection.

So, I returned to the selector, fixed the IE bug and made sure it worked with the now latest version of jQuery (1.3.2). This short function extends the Sizzle selector engine to return an element that has an ID that ends with the ID that is passed in the parenthesis after the “:asp()” selector.

View this code on github: https://gist.github.com/4148366


	// Include this function before you use any selectors that rely on it
	if ($.expr.createPseudo) {
	  jQuery.expr[':'].asp = $.expr.createPseudo(function( id ) {
		  return function(elem) {
			  return elem.id && elem.id.match(id + "$")
		  };
	  });
	}
	else {
		jQuery.expr[':'].asp = function(elem, i, match) {
			return !!(elem.id && elem.id.match(match[3] + "$"));
		};
	}

	// Now all of these are valid selectors
	// They show why this method has more functionality than the previous $asp() function.
	$(":asp(txtPhoneNumber)")
	$("input:asp(txtPhoneNumber):visible")
	$(":asp(txtPhoneNumber), :asp(txtAddress)")
	$("ul:asp(listTodos) li")
	$("#content").find("ul:asp(listTodos)")

This function allows access to server controls without adding additional markup and without the JavaScript existing on the .aspx page.


Note, there is a potential limitation if you had one control with the ID=”txtPhoneNumber” and another with ID=”mytxtPhoneNumber”. Both elements end with “txtPhoneNumber” and the selector would not necessarily return the correct value. This solution is not perfect in that sense, but the benefits it provides over other methods (cleaner markup and ability to use external JavaScript files) make it a good alternative.

Tags: , , , ,

« »

10 Responses to “Extending jQuery to Select ASP Controls”

  1. paulshaver Says:

    I like your solution here. In order to get around the txtPhoneNumber/mytxtPhoneNumber dilemma, you could assume that it actually ends with a “_txtPhoneNumber”.

    Just prepend that “_” as you find your match. I”m fairly certain that the underscore is always used.

    Anxiously awaiting asp.net 4.0 to make these problems go away. :)

  2. Saas Says:

    NICE!! Great help.. thanks

  3. Amit Says:

    I want to apply autocomplete in gridview (in asp.net3.5 with C#) using jquery and this

    gridview is within the ajax update panel
    .

    For this i am using method within the

    $(document).ready(function(){
    $(”[id$=txtItemCode]”).autocomplete(“SearchItem.aspx”).result(function (event, data,

    formatted) {
    if (data) {
    alert(”Value: ”+ data[1]);

    }
    else {
    $(”[id$=txtItemID]”).val(”-1”);
    }
    });
    });

    since in gridview the textbox id is but

    on browser such as
    and then for the next row same textbox id is now

    i tried
    1. put this code within the
    2.
    3. $(“*[@id$=theGridId] input[@id$=txtItemCode]“)

    but autocomplete does not work in all the cases

    plz help me.

  4. JK Says:

    Thank you – this has been driving me nuts! I also like the CSS selector alternative, clever. Finally I might be able to finish this project …

  5. Foliotek Development Blog » Blog Archive » Datagrid Checkbox Column Says:

    [...] our javascript code that controls the select/deselect all functionality. See this blog post in reference to the :asp() selector, and this post for more information on the select all [...]

  6. Keith Says:

    Is there a reason you can”t just use the jQuery native attribute-ends-with selector?

    $(”[id$=_textBox]”)

  7. Brian Grinstead Says:

    Keith,
    You can use that just as well. I”m just not crazy about having that [id$=] in the selector since the :asp() reads a little more clearly to me. Also, it makes it easier if this technique needed to be modified (for example, if you changed the ClientIDMode and wanted to get the performance benefits of a straight document.getElementByID it could be hidden away in the function rather than having to change a bunch of code).

    Either way works, though! We have been using MVC on our newer projects, so I haven”t been needing to use this technique much lately.

  8. ptcg Says:

    I can”t seem to get this working. It keeps giving me the error “Uncaught TypeError: Cannot read property ”3” of undefined”. Does this not work in later versions of jQuery? I”m currently using v1.8.0.

  9. Brian Grinstead Says:

    ptcg,
    I do not think it is compatible with jQuery 1.8. I will check and see the new way to do it.

  10. Brian Grinstead Says:

    ptcg, I have updated the post with the new code to make it work in 1.8. It is in the post, and also linked to here: https://gist.github.com/4148366

Leave a Reply