Javascript vs. controles ASP.NET

Imaginaos que queréis que al presionar sobre un Button aparezca un alert por pantalla con el contenido de un TextBox... con controles HTML típicos es muy fácil, pero con los controles de ASP.NET hay que darse cuenta de un par de cosillas para consegir que funcione.

Por cierto, os recomiendo que si lo consideráis oportuno le echéis un vistazo a un artículo previo de título Insertar javascript dinámicamente.

Pero vamos a lo que interesa, lo que quisiéramos es que esto funcionara

estonofunciona.aspx
<script language="javascript">
function mostrarTexto()
{
   alert(document.getElementById('Text1').value);
}
</script>

<asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
<input id="Button1" type="button" onclick="mostrarTexto()" value="button" runat="server" />


En teoría debería funcionar pero no es así... y si no sabes el motivo, puede darte más de un dolor de cabeza!!
El motivo es que ASP.NET trata al TextBox de modo que acaba presentando al usuario lo siguiente:

    <input name="ctl00$ContentPlaceHolder1$Text1" type="text" id="ctl00_ContentPlaceHolder1_Text1" />

por lo que en teoría la solución será cambiar nuestro código javascript a

    alert(document.getElementById('ctl00$ContentPlaceHolder1$Text1').value);

Lo cual es una chapuza como un piano. Pero vayamos por partes... ¿Por qué ASP.NET cambia el name y el id del código HTML a "ctl00$ContentPlaceHolder1$Text1" si nosotros le hemos explicitado que sea "TextBox1"?

El motivo es muy sencillo, y es que la razón de ser de ASP.NET es que toda la página es un formulario, y cada vez que se va y vuelve al servidor ASP.NET debe poder distinguir entre un TextBox y otro (y por extensión al resto de controles).

De modo que cuando se usa un control de servidor (el TextBox, el DropDownList, etc.) o un control HTML típico con el atributo runat="server" (que es como decirle a ASP.NET que ese atributo debe poder ser tratado en código), ASP.NET les cambia el id y el name de modo que sean únicos en toda la página. Tiene su lógica, ¿no?

Pero bueno, al grano... entonces, ¿cómo lo podemos solucionar?

Bien, pues personalmente uso dos soluciones diferentes, la primera usando el RegisterStartupScript o el RegisterClientScriptBlock explicados ambos en el artículo Insertar javascript dinámicamente y la segunda variando dinámicamente  el atributo onclick del, en este caso, Button1.

En ambos casos, el quid de la cuestión se trata de utilizar la propiedad ClientID común a todos los controles que trata ASP.NET, propiedad que nos devuelve el  id final  e único de el control en cuestión, que ASP.NET presentará al usuario.

A continuación ejemplos de ambas soluciones:

sifunciona1.aspx.cs
    protected void Page_Load(object sender, EventArgs e)
    {
        Type cstype = this.GetType();
        string nombreScript = "alertar";
        ClientScriptManager cs = Page.ClientScript;
        StringBuilder sb = new StringBuilder();
        sb.AppendLine("function mostrarTexto()");
        sb.AppendLine("{");
        sb.AppendLine("alert(document.getElementById('" + Text1.ClientID + "').value);");
        sb.AppendLine("}");

        /*if (!cs.IsClientScriptBlockRegistered(nombreScript))
        {
           cs.RegisterClientScriptBlock(cstype, nombreScript, sb.ToString(), true);
        }*/

        if (!cs.IsStartupScriptRegistered(nombreScript))
        {
            cs.RegisterStartupScript(cstype, nombreScript, sb.ToString(), true);
        }
    }


Lo dicho, este primer ejemplo es clavado al del artículo triplemente mencionado Insertar javascript dinámicamente, por lo que el código no hace falta explicarlo.

En cuanto a la otra opción, que en mi opinión es menos "elegante", cambiamos un poco el código del .aspx y del .aspx.cs:

sifunciona2.aspx
function mostrarTexto(idTexto)
{
   alert(document.getElementById(idTexto).value);
}
</script>
<asp:TextBox ID="Text1" runat="server"></asp:TextBox>
<input id="Button3" type="button" value="button" runat="Server" />


sifunciona2.aspx.cs
Button3.Attributes.Add("onclick", "mostrarTexto('" + Text1.ClientID + "')");

Como veis, no hemos más que añadido una variable de entrada a la función javascript (que NO creamos dinámicamente) y quitar el atributo del Button3 en el .aspx. Mientras tanto en el .aspx.cs no hacemos más que añadir el atributo onclick dinámicamente, de modo que será desde aquí desde donde se pasará el ClientID del Text1... más corto pero más chapucero, ¿no?

Como anexo, os recomiendo esta función para conseguir que el getElemetById de javascript funcione en todos los navegadores:

if(!document.getElementById){
  if(document.all)
  document.getElementById=function(){
    if(typeof document.all[arguments[0]]!="undefined")
    return document.all[arguments[0]]
    else
    return null
  }
  else if(document.layers)
  document.getElementById=function(){
    if(typeof document[arguments[0]]!="undefined")
    return document[arguments[0]]
    else
    return null
  }
}