Botón que sólo se puede clicar una vez

Una funcionalidad que he necesitado siempre mucho y que por un motivo u otro nunca había implementado, es la de hacer que cuando un usuario clique sobre un botón, éste se deshabilite para que no se pueda volver a hacer clic en él.

La gota que colmó el vaso ocurrió cuando comprobé que en Todoexpertos.com había una gran cantidad de usuarios que hacían clic sobre el botón de "enviar pregunta" o "enviar respuesta", hasta dos y tres veces. Naturalmente, ya habíamos implementado una funcionalidad que evitaba que un mismo item se insertara varias veces. Esta funcionalidad creaba un log informando algo parecido a "Intento de inserción de una pregunta ya creada".

Pero... ¿pudiendo evitar que ocurriera en cliente, por qué hacerlo en servidor?

Pues eso, vale ya de tanto rollo y vayamos al tema. Hacer que un botón se deshabilite en el momento se hace un clic, no es tan trivial como puede parecer. Básicamente hay que tener dos cosas en cuenta:
  • No hay que deshabilitar el botón si estamos haciendo una validación y esa validación no produce resultado positivo.
  • El botón NO puede ser un input-type-submit. Debe ser un input-type-button. Es curioso: si es submit, sí que funciona todo bien en Firefox, pero no funciona en Internet Explorer... y por primera vez en mucho tiempo, estoy de acuerdo con IE... pero eso ya lo explicaré en otro momento.
Teniendo ambas cosas en cuenta, pongámonos en esta situación:

.aspx
        <asp:TextBox ID="TextBox1" runat="server" ValidationGroup="SubgurimTest"></asp:TextBox>
       
        <asp:RequiredFieldValidator ID="RequiredFieldValidator1" runat="server"
            ErrorMessage="*" ControlToValidate="TextBox1" ValidationGroup="SubgurimTest">
        </asp:RequiredFieldValidator>
       
        <asp:Button ID="Button1" runat="server" OnClick="Button1_Click" Text="OnceClickMe" ValidationGroup="SubgurimTest"
            UseSubmitBehavior="false" OnClientClick="clickOnce(this, 'Cargando...')" />
           
        <br />
       
        <asp:Label ID="Label1" runat="server" Text=""></asp:Label>



.aspx.cs
    protected void Button1_Click(object sender, EventArgs e)
    {
        System.Threading.Thread.Sleep(1000);
        Label1.Text = DateTime.Now.ToString();
    }


Como vemos, no se trata más que de un Textbox, un Button y un Validator. Al presionar sobre el Button, siempre y cuando se supere la validación, se ejecutará el método "Button1_Click" que esperará un segundo y asignará un valor a una Label.

Cabe destacar que el botón debe tener el atributo UseSubmitBehavior a false (esto hará que el Button se convierta en el input-type-submit que queremos), así como el OnClientClick enlazado a la función javascript que será la que haga la magia.

Y la función javascript es la siguiente:

        function clickOnce(btn, msg)
        {
            // Comprobamos si se está haciendo una validación
            if (typeof(Page_ClientValidate) == 'function')
            {
                // Si se está haciendo una validación, volver si ésta da resultado false
                if (Page_ClientValidate() == false) { return false; }
            }
           
            // Asegurarse de que el botón sea del tipo button, nunca del tipo submit
            if (btn.getAttribute('type') == 'button')
            {
                // El atributo msg es totalmente opcional.
                // Será el texto que muestre el botón mientras esté deshabilitado
                if (!msg || (msg='undefined')) { msg = 'Loading...'; }
               
                btn.value = msg;

                // La magia verdadera :D
                btn.disabled = true;
            }
           
            return true;
        }