JASoft.org

El blog de José Manuel Alarcón Aguín. Programación web y mucho más...

MENÚ - JASoft: JM Alarcón

Bloquear los botones mientras se envía un formulario

Un efecto bastante indeseable en una página web es que los usuarios puedan enviar al servidor dos o más veces una misma información.

Esto suele ocurrir porque existe una latencia entre el cliente y el servidor que hace que, si el servidor es lento o si hay mucha información que enviar o recibir en el proceso, la página tarde varios segundos en desaparecer del navegador.  Por lo tanto un usuario impaciente puede pulsar varias veces el botón de envío, recibiéndose varias veces la información en el servidor.

Esto, según sea el sistema que hemos diseñado, puede tener resultados desastrosos para nuestra aplicación.

Evitarlo par aun botón concreto es muy sencillo. Basta con deshabilitarlo usando un código como este:

   1: <input type="submit" value="Enviar!" onclick="this.disabled=true;">

De esta forma cuando se pulse el botón, aparte de efectuar su función habitual de enviar el formulario (es un botón de tipo “submit”) conseguiremos que éste se deshabilite y por lo tanto que el usuario no pueda pulsarlo más que la primera vez.

El aspecto del botón deshabilitado es como este:

Submit_Disabled

Fíjate en que el botón aparece plano y desdibujado, y no es posible pulsarlo ni interactuar con él.

Hacerlo automáticamente en cualquier página y cualquier botón

Lo anterior es muy sencillo y rápido de conseguir, pero ¿no sería estupendo poder conseguir lo mismo de manera automática en todos los formularios que haya en una página y sin necesidad de ir botón por botón?

Además existen otros problemas asociados con lo anterior. Por ejemplo, si queremos hacer más cosas antes de enviar el formulario (por ejemplo validarlo) y no siempre se va a enviar al servidor por el mero hecho de pulsar el botón, entonces tendremos que complicar el código un poco más y no sería tan directo. Por otro lado los formularios se pueden enviar también solo con pulsar la tecla ENTER al estar dentro de cualquier campo de texto, en cuyo caso nuestro botón no se enteraría y podría ser pulsado también.

He escrito un pequeño código contenido en un archivo .js que usa otra técnica para interceptar el envío del los formularios y en caso de enviar desactiva el botón submit correspondiente. Esto lo hace de manera automática con tan solo incluirlo en cualquier página. Su contenido es el siguiente:

   1: var tmrReady = setInterval(isPageFullyLoaded, 100);
   2:  
   3: function isPageFullyLoaded() {
   4:     if (document.readyState == "loaded" || document.readyState == "complete") {
   5:         subclassForms();
   6:         clearInterval(tmrReady);
   7:     }
   8: }
   9:  
  10: function submitDisabled(_form, currSubmit) {
  11:     return function () {
  12:         var mustSubmit = true;
  13:         if (currSubmit != null)
  14:             mustSubmit = currSubmit();
  15:  
  16:         var els = _form.elements;
  17:         for (var i = 0; i < els.length; i++) {
  18:             if (els[i].type == "submit")
  19:                 if (mustSubmit)
  20:                     els[i].disabled = true;
  21:         }
  22:         return mustSubmit;
  23:     }
  24: }
  25:  
  26: function subclassForms() {
  27:     for (var f = 0; f < document.forms.length; f++) {
  28:         var frm = document.forms[f];
  29:         frm.onsubmit = submitDisabled(frm, frm.onsubmit);
  30:     }
  31: }

La primera parte del código (hasta la línea 8) se encarga de llamar al método subclassForms una vez que la página esté lista para ser manipulada desde JavaScript. Puedes ver cómo funciona esta técnica en mi anterior post.

Lo interesante aquí es que sub-clasificamos el evento onsubmit de los formularios de nuestra página de modo que le añadimos código propio. La sub-clasificación de un método o un evento consiste en sustituir el código original (de haberlo) por uno propio pero al mismo tiempo conservando toda la funcionalidad inicial.

Lo que hace el método subclassForms es recorrer todos los formularios que haya en la página e interceptar su evento onsubmit para que cuando se envíe el formulario al servidor se ejecute nuestro código. Para ello definimos una función submitDiabled que es una clausura o closure. Éstas nos permiten crear funciones genéricas pero que conservan parámetros internos para una ejecución posterior (ver mi post al respecto).

Como parámetros para la función le pasamos el formulario actual y el código del evento onsubmit de éste (que puede que tenga o no). El objeto de este segundo parámetro es conservar toda la funcionalidad del método onsubmit existente aunque nosotros vayamos a hacer más cosas. Es la sub-clasificación de la que hablaba antes.

Si vemos el código de la clausura, nuestra función lo que hace es, antes de nada suponer que el formulario se va a enviar (que es la acción por defecto), por eso se define una variable mustSubmit que por defecto vale true.

Si originalmente existía un código específico para ejecutar en el evento de envío del formulario (línea 13) entonces lo ejecutamos llamándolo con los paréntesis, y guardamos el resultado en la variable mustSubmit anterior. Como nota de cultura general diré que si el resultado de un evento onsubmit es true es que debe enviarse el formulario. Si por el contrario devuelve un false entonces el formulario no se envía.

Bien. Ahora recorremos todos los elementos del formulario en cuestión y verificamos si hay alguno que sea del tipo “submit”, o sea, un botón de envío de formulario. En caso de encontrarlo, si realmente se va a enviar el formulario (es decir, si mustSubmit es true, línea 19) entonces deshabilitamos el botón para evitar que pueda pulsarse más de una vez.

Finalmente (línea 22) se devuelve el resultado apropiado para enviar o no el formulario.

Con este código conseguimos el desactivado de los botones evitando los problemas que mencionábamos antes y además de forma completamente transparente ya que no se ve afectada la funcionalidad del evento onsubmit.

Podríamos incluso ir un paso más allá y hacerlo más seguro detectando si se ha enviado el formulario ya o no, evitando así un doble envío directamente en el código de nuestro evento onsubmit sub-clasificado (devolviendo siempre false si el evento se llama más de una vez). Pero con esto debería ser suficiente para la mayor parte de los casos.

El archivo con este código lo puedes descargar desde aquí: disableSubmits.zip (ZIP, 514 bytes). Para usar la funcionalidad lo único que tienes que hacer es copiar el .js a la misma carpeta que tu página e incluir una línea como esta en la cabecera de la misma:

   1: <script language="javascript" type="text/javascript" src="disableSubmits.js"></script>

¡Espero que te resulte útil!

Para saber más: JavaScript profesional para desarrolladores y diseñadores web

José Manuel Alarcón José Manuel Alarcón
Fundador de campusMVP.es, el proyecto de referencia en formación on-line para programadores en lengua española. Autor de varios libros y cientos de artículos. Galardonado como MVP de Microsoft desde 2004. Gallego de Vigo, amante de la ciencia y la tecnología, la música y la lectura. Ayudando a la gente en Internet desde 1996.
Descarga GRATIS mi último libro (no técnico): "Tres Monos, Diez Minutos".
Banner

Comentarios (13) -

La forma más elegante de bloquear los controles mientras se espera la respuesta del servidor es utilizar un píxel transparente, extenderlo al 100% del ancho y el alto de la página y modificar el "z-index" para que esté por encima de todos los controles.

Responder

Spain José Manuel Alarcón

Eso no está mal, aunque no se usa tan a menudo como lo de deshabilitar los botones. Además tiene la pega de que seguirçia funcionando el darle a ENTER para enviar el formulario.

La técnica de este post intercepta el envío del formulario, y en paralelo deshabilita los botones, pero en mi opinión lo más interesante no es esto último, sino la subclasificación del método "submit" que de hecho es una técnica que se puede utilizar para muchas otras cosas.

Saludos!

Responder

Hola José, me gustaría saber si puedo hacer esto con un evento de otro elemento html o control asp.net, es decir, si tengo un gridview, debajo un dropdownlist y debajo un botón, si selecciono una opción en el dropdownlist se actualizan los datos en el gridview, pero durante ese proceso de actualización el usuario puede dar click en el botón, en
IE9 no hay problema dado que el navegador espera a actualizar el gridview y luego ejecuta lo que se encuentre dentro del evento onclick del botón, pero firefox no se comporta igual dado que actualiza la información del gridview pero no ejecuta el evento onclick del botón, por tanto es necesario nuevamente presionar el botón.

La pregunta concreta es si con el javascript que facilitas sería posible cuando cambie el indice del dropdownlist deshabilitar el botón hasta que el gridview haya sido actualizado.  Espero me haya hecho entender.  Gracias y felicitaciones
por tu blog, estoy próximo a comprar un libro de krasiss press, aún no decido cual, :).

Responder

Spain José Manuel Alarcón

Hola Henry,

Por supuesto: todo lo que sea controlar el lado de cliente puedes hacerlo. Si identificas tu botón (o el elemento HTML que desees) en el lado cliente a través de su ID. Lo localizas y puedes subclasificar su evento onclick y con la técnica explicada hacer lo que desees en tu código pero al mismo tiempo conservar la acción que tuviera previamente.

Saludos,

Responder

Buenas.

La idea de "subclasificar" el submit es muy buena. Muchas gracias!

Sólo un par de cosas a decir:
1) Creo que el código tiene un error: hay que cambiar la línea 20 [i].disabled por els[i].disabled

2) Al deshabilitar el botón luego no es capaz llamar al manejador adecuado en el servidor (Probado con el ejemplo básico Button1_Click).Si estableces la propiedad UseSubmitBehavior de los botones en cuestión a false no se pierde el evento del botón en el servidor pero habría que cambiar el código para deshabilitar también los elementos de tipo botón (ahora sólo lo hace con los submit).

3) No estaría mal añadir una clase css a los botones que nos sirva para discriminar en el código  aquellos botones que no queramos deshabilitar en el envío. Esto sería útil para casos como los siguientes:
    a) Botones que provocan la descarga o generación de un archivo y que siempre deben estar disponibles porque no se refresca la página
    b) Botones que provocan el refresco de un UpdatePanel y no están dentro del mismo.

Responder

Spain José Manuel Alarcón

Hola,

Gracias por las puntualizaciones.
En cualquier caso mi idea con este post no era hacer algo 100% válido para todo, sino ilustrar un concepto (en este caso la subclasificación de eventos). Lo he hecho solamente para botones submit de HTML normal y no atado específicamente a ASP.NET u otra tecnología de servidor, pero entiendo que sería fácil ampliarlo para cualquier otro tipo de botón o circunstancia como comentas, y en ese caso además sería necesario hacer lo que dices.

Un saludo!

Responder

Spain William Chiquito

Hola José Manuel,

Gracias por el artículo.

Si comprendo bien el código, ¿cambiar el condicional de la línea 19 a la línea 15 (como bloque) no evita el ciclo cuando la variable mustSubmit es false?

¿En la línea 20 falta el els en [i].disabled?

Un saludo.

Responder

Spain José Manuel Alarcón

Hola William,

Ya he corregido lo de la línea 20, gracias. Se ve que se me traspapeló al copiarlo porque en el código de ejemplo de la descarga estaba bien.

En cuanto a lo de mover el condicional, tienes razón: se evitaría el bucle que recorre los controles si no es necesario hacer el submit. Gracias por la optimización.

De todos modos insisto: no trataba de hacer un código perfecto y super-optimizado, sino de ilustrar un cocepto interesante de javaScript que es la subclasificación.

Saludos!

Responder

Spain William Chiquito

Hola José Manuel,

Entiendo perfectamente, la idea es transmitir el concepto (que es realmente lo importante) más que el código, fue solo un aporte.

Gracias y un saludo.

Responder

Hola buenas, se puede realizar también cuando hay un UPDATEPANEL?

Ya que tengo un radiobuttonlist que al activar rblGrupo_SelectedIndexChanged este cambia la información, pero si el usuario cambia de radiobutton y pulsa el botón rápido, la información que obtiene es la del radiobutton anterior.

¿Esto se podría controlar de la misma manera, o existen otros métodos para el tema de updatepanels?

Un saludo, eres un crack!

Responder

Spain José Manuel Alarcón

Hola,

Sí claro: basta con adaptar un poco el código para pasarle cualquier botón. Un botón que está dentro de un UpdatePanel es un botón normal y corriente de lado cliente. Lo único que hay que hacer es localizarlo. Mi código de ejemplo está hecho para buscar dentro de formularios, pero basta con adaptarlo mínimamente para que busque cualquier elemento. Si lo combinas con jQuery es realmente fácil.

Saludos,

Responder

Peru Clauss Mike

No se que estare haciendo mal, pero el código no me funciona, el <input type="submit"> nunca de bloquea, o se debe identificar con algún "id" o "name".

Responder

Ronal Gutierrez

Muchas gracias, excelente aporte

Responder

Agregar comentario