views:

1250

answers:

7

I have about 600+ references of code similar to the following...

$("#ctl00_ContentMainPane_eliteUser").html

And changed the master template, only to find all the code had broken, due to the control hierarchy changing.

All javascript is included in separate files, so I cannot use code such as this...

$("#<%= eliteUser.clientID%>").html

I want to have a good solution to fixing this issue so that after fixing it once, I don't have to modify all the code if the template changes in the future.

+1  A: 

As far as I know, asp.net doesn't mess with class names. Add a class name to your control and use that name as a reference in your code.

Scharrels
+2  A: 

You can do something like this :

$("input[id*=eliteUser]").html()

But if you have multiple controls ending their IDs in eliteUser - it won't work.

sirrocco
+3  A: 

One way is to use a js variables object that gets written out at the top of each page. Your external js can then reference the setting object with the desired property. This way you can change the id in your aspx pages without hardcoding anything in the external js files.

You can then expose a materpage or base page method that allows each page to add the control id items to a list. The base method then has the responsibility to write out the pageVars script at the top of each page.

Rick Strahl talks about this here

I prefer this method as you still get the benefits of fast selectors.

E.g

If you get the aspx to write out the following script.

var pageVars = {
    idList: {
        eliteUser : 'ctl00_ContentMainPane_eliteUser',
        normalUser : 'ctl00_ContentMainPane_normalUser', 
        powerUser : 'ctl00_ContentMainPane_powerUser', 
        .....
    }
};

Then in your external js

$('#' + pageVars.idList.eliteUser)
$('#' + pageVars.idList.powerUser)....etc
redsquare
+5  A: 

How about defining a JavaScript function in your master page such as:

function getContainerId() {
    return "<%=ContentMainPane.ClientID %>";
}

Then in your included JavaScript files do something like this:

$("#" + getContainerId() + "_eliteUser");
d4nt
A: 

This is an issue which I understand to be being addressed in the next version of Visual Studio, however I realise this won't help you.

The way we addressed it on our project was something line this:

$.extend({
    clientID: function(id) {
        return $("[id$='_" + id + "']");
    }
});

And the code that used it:

statusBar = $.clientID("MSS_MESS_LINE_Static")
James Wiseman
But it ends up with dog slow selectors as it will zoom around EVERY element on the page with an id and then filter.
redsquare
Do you have a link to how "visual studio" will solve this? or do you mean the .NET framework?
digiguru
halfway down this article http://www.mostlylucid.net/archive/2008/11/03/way-too-much-information-on-control-ids-and-asp.net-4.0.aspx
redsquare
Agree with the comments. I did mean that ASP.NET was due to resolve the long naming issue, as the article suggested.The selector would indeed be slow, however i feel the template for the solution (i.e. using the $.extend() is a good way forward)
James Wiseman
@digiguru - http://weblogs.asp.net/asptest/archive/2009/01/06/asp-net-4-0-clientid-overview.aspx
Russ Cam
+2  A: 

I have been using a class that extends WebControl to essentially do the same as RedSquare's suggestion. I read the suggestion on this blog

/// <summary>
/// Custom Web control that renders two JavaScript functions out into the page 
/// so that defined control IDs can be used to get a reference to HTML DOM elements
/// </summary>
[DefaultProperty("Text")]
[ToolboxData("<{0}:ClientIdHandler runat=\"server\"></{0}:ClientIdHandler>")]
public class ClientIdHandler : WebControl
{
    private StringBuilder _builder = new StringBuilder();

public ClientIdHandler(){}


/// <summary>
/// Renders the ClientID of the control in JavaScript function "clientId" to the specified writer.
/// </summary>
/// <param name="output">A <see cref="Control"/> whose ClientID will be included 
/// in the rendered HTML output.</param>
public void AddControl(Control c)
{
    _builder.AppendFormat("    case '{0}': return '{1}'; break;" + Environment.NewLine, c.ID, c.ClientID);
}

/// <summary>
/// Overrides the Render method to prevent a Start and End <see cref="HtmlTextWriterTag"/>
/// being rendered in the HTML for the control.
/// </summary>
/// <param name="output">A <see cref="HtmlTextWriter"/> that represents 
/// the output stream to render HTML content on the client.</param>
protected override void Render(HtmlTextWriter writer)
{       
    RenderContents(writer);
}


/// <summary>
/// Renders the contents of the control to the specified writer.
/// </summary>
/// <param name="output">A <see cref="HtmlTextWriter"/> that represents 
/// the output stream to render HTML content on the client.</param>
protected override void RenderContents(HtmlTextWriter output)
{
    output.Write("<script type=\"text/javascript\"> " + Environment.NewLine);
    output.Write("function clientId(id) {" + Environment.NewLine);
    output.Write("switch (id) {" + Environment.NewLine);
    output.Write(_builder.ToString());
    output.Write("     }" + Environment.NewLine);
    output.Write("}" + Environment.NewLine);
    output.Write("function getElementByClientId(id) {" + Environment.NewLine);
    output.Write("    return document.getElementById(clientId(id));" + Environment.NewLine);
    output.Write("}" + Environment.NewLine);
    output.Write("</script>" + Environment.NewLine);
}
}

Then you can use in aspx page or code-behind

 protected override void OnInit(EventArgs e)
    {
        ClientIdHandler clientIds = new ClientIdHandler();
        clientIds.AddControl(myControl);

        this.Controls.Add(clientIds);

        base.OnInit(e);
    }

Which will render out the following in the page

<script type="text/javascript"> 
function clientId(id) {
switch (id) {
    case 'myControl': return 'ctl00_Main_myControl'; break;
     }
}
function getElementByClientId(id) {
    return document.getElementById(clientId(id));
}
</script>

So in your external JavaScript file, you can use the following

// for jQuery selectors
var myControl = $('#' + clientId('myControl'));

// for vanilla JavaScript
 var myControl = getElementByClientId('myControl'));
Russ Cam
+1  A: 

I'm using ScriptManager.RegisterStartupScript to get all the ClientIDs I need.

  1. Define javascript variables in 'head' of the page to store IDs.
  2. Use RegisterStartupScript in Page_Load to assign values to the variables as follows:

string myScript = String.Format("window.onload = function() { id1 = {0}, id2={1}, id3={2}}", Control1.ClientID, Control2.ClientID, Control3.ClientID);

ScriptManager.RegisterStartupScript(Page, Page.GetType(), "SomeKey", myScript, true);

Artem K.