Quiero evitar envíos múltiples de formularios, pero necesito que el valor del elemento de envío se regrese al servidor (para saber en qué botón hizo clic el usuario).

La mayor parte de la Sabiduría de Internet sobre la supresión de envíos de formularios múltiples parece implicar la desactivación del botón de envío durante el envío del formulario. Esto evita que se haga clic en el botón por segunda vez, pero también evita que se publique su valor.

He encontrado algunos ejemplos de código JS que oculta los botones de envío, lo que permite publicar sus valores. Pero todos esos ejemplos reemplazan el botón (ahora oculto) con algún tipo de mensaje "procesando ...". Realmente quiero una solución que presente al usuario un botón deshabilitado pero que aún publique el valor del botón.

Debo agregar que preferiría una solución que funcione con HTML estándar que se encontraría en la mayoría de los formularios. Sin IFrames mágicos, campos ocultos, nombres de identificación o clase, etc. Quiero una función JS que pueda guardar en una biblioteca y hacer referencia a todos mis formularios existentes para permitir este nuevo comportamiento.

(Tengo una solución, que publicaré como respuesta. Pero tuve que hacer la pregunta para cumplir con el Zen de SO).

7
Bob.at.Indigo.Health 3 sep. 2014 a las 22:26

6 respuestas

La mejor respuesta

Aquí está (otra) respuesta a la pregunta sobre cómo lidiar con la prevención de que el usuario haga clic en el botón de envío del formulario más de una vez. Esta solución hace parecer que el botón ha sido deshabilitado.

Debajo de las cubiertas, crea un botón deshabilitado para mostrar al usuario y oculta el botón real para que se publique su valor. También muevo el botón oculto para que el elemento adicional no estropee los selectores CSS.

También tenga en cuenta la comprobación de campos de formulario no válidos. Si omite esta verificación y la validación del formulario falla, el usuario termina con un formulario que no se publicó (porque la validación del lado del cliente falló) pero los botones están deshabilitados.

// Disables buttons when form is submitted
$('form').submit(function () {
    // Bail out if the form contains validation errors
    if ($.validator && !$(this).valid()) return;

    var form = $(this);
    $(this).find('input[type="submit"], button[type="submit"]').each(function (index) {
        // Create a disabled clone of the submit button
        $(this).clone(false).removeAttr('id').prop('disabled', true).insertBefore($(this));

        // Hide the actual submit button and move it to the beginning of the form
        $(this).hide();
        form.prepend($(this));
    });
});
6
Bob.at.Indigo.Health 3 sep. 2014 a las 18:49

Aquí hay un par de opciones:

1. Puede crear entradas ocultas y cambiar dinámicamente su valor antes de enviar el formulario, ya sea al hacer clic o al presionar el botón mencionado:

2. Puede crear un iframe oculto que sea el objetivo de dicho formulario. Una vez que haga clic en el botón Enviar, puede cancelar el evento de envío, tomar todos los datos y enviarlos programáticamente a través del iframe.

0
Logicgate 3 sep. 2014 a las 18:35

Estaba teniendo el mismo problema que OP, y descubrí que deshabilitar los botones de envío después de un breve tiempo de espera (quizás 0 segundos) a través de setTimeout es el truco. El valor del nombre del botón de envío todavía se publica con el resto de los datos del formulario como se desee, pero el botón se deshabilita (casi) de inmediato, evitando más clics.

El tiempo de espera es un poco feo, pero parece preferible a esquemas de intercambio / cobertura más elaborados.

Esto podría combinarse con también alterar la propiedad onsubmit del formulario por precaución adicional, pero no lo haré en el ejemplo a continuación por razones de claridad. De cualquier manera, me gusta la apariencia / comportamiento de un botón deshabilitado después del primer clic de envío ... la experiencia del usuario me parece mejor ... está más claro lo que está sucediendo.

La etiqueta de inicio de mi elemento de formulario:

<form onsubmit="return formSubmit(this);" method="post" action="">

En mi JavaScript (lo siento, no estoy actualizado con la última tecnología de JS como jQuery, etc., por lo que estoy publicando esto en un código antiguo-compatible-nativo-JavaScript-5-sin-dependencias-compatible ):

function formSubmit(form) {
    // MUST DELAY so as not to break input/button[type=submit] name submission
    setTimeout(function () {
        var els = form.elements;
        for (var i = 0; i < els.length; i++) {
            var el = els[i];
            if (el.getAttribute('type') == 'submit') {
                el.setAttribute('disabled', 'disabled');
            }
        }
    }, 0);
    return true;
}
0
Todd Ditchendorf 25 jun. 2019 a las 19:50

Quería evitar que el usuario causara múltiples envíos de formularios haciendo doble clic en el botón Enviar o presionando la tecla Intro dos veces. Me gusta esta solución, porque no requiere un campo de formulario oculto u ocultar el botón de enviar.

Los dos puntos clave son:

  1. Devuelva verdadero / falso en lugar de usar e.preventDefault () y form.submit (), porque form.submit () no sabe en qué botón se hizo clic y, por lo tanto, no puede pasar el nombre / valor del botón.

  2. Deshabilite el botón con pointer-events: none; en lugar de disabled="disabled", porque el atributo deshabilitado no enviará el nombre / valor del botón. Creo que pointer-events: none; no es compatible con Internet Explorer 10 o inferior.

Código javascript / jquery:

var form_selector = 'form',
    button_selector = 'button, input[type=submit], input[type=button], input[type=reset]',
    deactivated_classname = 'state-submitting',
    deactivated_class = '.'+'state-submitting';

// Capture the submit event so it will handle both the 
// enter key and clicking the submit button.
$(document).on('submit', form_selector, function(e) {
  var form = e.target,
      buttons = $( form ).find( button_selector );

  // Returns, because the form is already being submitted by a previous attempt.
  if( $( form ).find( deactivated_class ).length > 0  ) return false;

  disableButtons( buttons );

  // Safari (version 11) bugfix: Safari needs a timeout or it won't 
  // show the deactivated styles.
  setTimeout(function() {
    // Must use return true, because using form.submit(), won't pass the button value.
    return true;
  }, 50 );
});

function disableButtons( buttons ) {
  // Disables all buttons in the form.
  $( buttons ).each(function( index, elem ) {
    $( elem ).addClass( deactivated_classname );
  });
}

Para los formularios AJAX, querrá volver a habilitar los botones después de que se devuelva la respuesta.

$( document ).on( 'ajax:complete', form_selector, function(e) {
  var form = e.target,
      buttons = $( form ).find( button_selector );

  enableButtons( buttons );
});

function enableButtons( buttons ) {
  $( buttons ).each(function( index, elem ) {
    $( elem ).removeClass( deactivated_classname );
  });
}

Css:

// The button is disabled while it is submitting.
.state-submitting {   
  // Turns off hover and click events. Not supported in IE 10 and below.
  pointer-events: none;
  opacity: 0.5;
}
0
rhutchens 25 ene. 2018 a las 12:28

Puede simular el comportamiento de aspecto deshabilitado. P.ej. si tienes un botón como este:

<input id="btn" type="button" onclick="disableMe(this)" value="Submit" />

Puedes definir CSS como este

.disabled {
    backround-color:grey;
    color:darkgrey;
}

Y a JS le gusta esto

function disableMe(btn) {
    btn.className = "disabled";
    btn.onclick = function(){return false}
}

Qué sucederá: el botón del primer clic se volverá gris (a través de CSS aplicado) y el evento onclick cambiará a "devolver falso" para todas las llamadas consecutivas evitando futuras acciones de clic. El botón aparecerá y actuará como deshabilitado, pero no lo estará, por lo que no impedirá el envío del botón.

1
Yuriy Galanter 3 sep. 2014 a las 19:50

Debido a que puede enviar un formulario de otras maneras que simplemente haciendo clic en el botón de envío, es mejor agregar un oyente al evento de envío del formulario en lugar del evento de clic en el botón de envío. Este detector de eventos jQuery debería funcionar en cualquier forma y evitar que se envíe más de una vez.

$('form').on('submit', function(e) {
    if (!$(this).data('submitted')) {
        $(this).data('submitted', true);
    }
    else {
        e.preventDefault();
    }
});

Para hacer que el formulario se vea deshabilitado, puede agregar algunos css que hagan que el formulario se vea deshabilitado y luego agregar el nombre de clase en el envío del formulario.

$('form').on('submit', function(e) {
    if (!$(this).data('submitted')) {
        $(this).data('submitted', true).addClass('disabled');
    }
    else {
        e.preventDefault();
    }
});
2
chriserwin 3 sep. 2014 a las 19:32