Запретить двойную подачу форм в jQuery

170

У меня есть форма, которая требует немного времени для обработки сервера. Я должен убедиться, что пользователь ждет и не пытается повторно отправить форму, нажав кнопку еще раз. Я попытался использовать следующий код JQuery:

<script type="text/javascript">
$(document).ready(function() {
    $("form#my_form").submit(function() {
        $('input').attr('disabled', 'disabled');
        $('a').attr('disabled', 'disabled');
        return true;
    });
});
</script>

Когда я пытаюсь сделать это в Firefox, все отключается, но форма не отправляется ни с какими данными POST, которые она должна включать. Я не могу использовать jQuery для отправки формы, потому что мне нужно, чтобы кнопка была отправлена ​​вместе с формой, поскольку есть несколько кнопок отправки, и я определяю, какое из них использовалось, какое значение включено в POST. Мне нужно отправить форму как обычно, и мне нужно отключить все сразу после того, как это произойдет.

Спасибо!

Адам
источник
Спасибо всем за вашу помощь!
Адам

Ответы:

315

Обновление в 2018 году : я только что получил несколько баллов за этот старый ответ и просто хотел добавить, что лучшим решением было бы сделать операцию идемпотентной, чтобы дублирующие представления были безвредными.

Например, если форма создает заказ, поместите уникальный идентификатор в форму. Когда сервер впервые видит запрос на создание заказа с таким идентификатором, он должен создать его и ответить «успех». Последующие представления также должны отвечать «успех» (в случае, если клиент не получил первый ответ), но не должны ничего менять.

Дубликаты должны быть обнаружены с помощью проверки уникальности в базе данных, чтобы предотвратить состояние гонки.


Я думаю, что ваша проблема заключается в этой строке:

$('input').attr('disabled','disabled');

Вы отключаете ВСЕ входные данные, в том числе, я полагаю, те, чьи данные должна предоставить форма.

Чтобы отключить только кнопку отправки, вы можете сделать это:

$('button[type=submit], input[type=submit]').prop('disabled',true);

Тем не менее, я не думаю, что IE отправит форму, если даже эти кнопки отключены. Я бы предложил другой подход.

Плагин JQuery, чтобы решить это

Мы только что решили эту проблему с помощью следующего кода. Хитрость здесь в том, чтобы с помощью jQuery data()пометить форму как уже отправленную или нет. Таким образом, нам не нужно связываться с кнопками отправки, что выводит IE из себя.

// jQuery plugin to prevent double submission of forms
jQuery.fn.preventDoubleSubmission = function() {
  $(this).on('submit',function(e){
    var $form = $(this);

    if ($form.data('submitted') === true) {
      // Previously submitted - don't submit again
      e.preventDefault();
    } else {
      // Mark it so that the next submit can be ignored
      $form.data('submitted', true);
    }
  });

  // Keep chainability
  return this;
};

Используйте это так:

$('form').preventDoubleSubmission();

Если есть формы AJAX, которые нужно разрешить отправлять несколько раз за загрузку страницы, вы можете дать им класс, указывающий на это, а затем исключить их из вашего селектора следующим образом:

$('form:not(.js-allow-double-submission)').preventDoubleSubmission();
Натан Лонг
источник
2
Просто указатель - не лучше ли будет кэшировать в $(this)a var that = $(this)?
Stuart.Sklinar
5
@ Stuart.Sklinar - хорошая идея. $formХотя я использовал - «форма», потому что он более описательный, и ведущий, $потому что по моему соглашению это означает, что это объект jQuery.
Натан Лонг
21
При использовании jquery validate ненавязчивая валидация лучше проверить, верна ли форма. if ($ (this) .valid)) {$ form.data ('submit', true);}
cck
1
@cck Отлично, я использовал это. У меня было дополнительное закрывающее имя, которое я удалил: if ($ (this) .valid) {$ form.data ('submit', true);
Брайан Маккей,
1
@BrianMacKay, если форма недействительна и нажата кнопка отправки, этот плагин jquery помечает отправленный атрибут как true, но ненавязчивая проверка предотвращает отправку из-за ошибок проверки. После исправления ошибок нельзя отправить форму, так как отправленный атрибут имеет значение true!
cck
44

Сроки подхода неверны - как узнать, сколько времени займет действие в браузере клиента?

Как это сделать

$('form').submit(function(){
  $(this).find(':submit').attr('disabled','disabled');
});

Когда форма отправлена, она отключит все кнопки отправки внутри.

Помните, в Firefox, когда вы отключаете кнопку, это состояние будет запомнено, когда вы вернетесь в историю. Чтобы предотвратить это, вы должны включить кнопки при загрузке страницы, например.

Ctrl-C,
источник
2
+1 из-за подсказки firefox, но есть ли другая опция, кроме включения кнопок при загрузке страницы?
Рафаэль Исидро
1
Это самый чистый путь. Я пытался отключить кнопку отправки с помощью обработчика события щелчка, но мой подход вызывал проблемы с Chrome. Это решение работает прекрасно.
Энтони До
1
Будьте очень осторожны с этим подходом: если у вас есть значение на кнопке отправки и оно вам нужно, форма не будет отправлять его.
Фабьен Менагер
Согласно последней документации jQuery вы должны использовать .prop вместо .attr. Ссылка: api.jquery.com/prop/#entry-longdesc-1 «Метод .prop () следует использовать для установки отключенного и проверенного вместо метода .attr ().»
J Plana
19

Я думаю, что ответ Натана Лонга - это путь. Для меня я использую проверку на стороне клиента, поэтому я просто добавил условие, чтобы форма была действительной.

РЕДАКТИРОВАТЬ : Если это не добавлено, пользователь никогда не сможет отправить форму, если проверка на стороне клиента обнаружит ошибку.

        // jQuery plugin to prevent double submission of forms
        jQuery.fn.preventDoubleSubmission = function () {
            $(this).on('submit', function (e) {
                var $form = $(this);

                if ($form.data('submitted') === true) {
                    // Previously submitted - don't submit again
                    alert('Form already submitted. Please wait.');
                    e.preventDefault();
                } else {
                    // Mark it so that the next submit can be ignored
                    // ADDED requirement that form be valid
                    if($form.valid()) {
                        $form.data('submitted', true);
                    }
                }
            });

            // Keep chainability
            return this;
        };
ПТК
источник
9

event.timeStampне работает в Firefox. Возвращение false не является стандартным, вам следует позвонить event.preventDefault(). И пока мы это делаем, всегда используйте фигурные скобки с управляющей конструкцией .

Подводя итог всем предыдущим ответам, вот плагин, который делает работу и работает кросс-браузер.

jQuery.fn.preventDoubleSubmission = function() {

    var last_clicked, time_since_clicked;

    jQuery(this).bind('submit', function(event) {

        if(last_clicked) {
            time_since_clicked = jQuery.now() - last_clicked;
        }

        last_clicked = jQuery.now();

        if(time_since_clicked < 2000) {
            // Blocking form submit because it was too soon after the last submit.
            event.preventDefault();
        }

        return true;
    });
};

Чтобы обратиться к Kern3l, метод синхронизации работает для меня просто потому, что мы пытаемся остановить двойной щелчок по кнопке отправки. Если у вас очень большое время отклика на отправку, я рекомендую заменить кнопку «Отправить» на бланк.

Полное блокирование последующих представлений формы, как это делается в большинстве приведенных выше примеров, имеет один плохой побочный эффект: если произошел сбой в сети, и они захотят повторить попытку, они не смогут это сделать и потеряют изменения, которые они сделали. Это определенно сделает злой пользователя.

Робин Догерти
источник
2
Ты прав. Вы можете пойти другим путем - немедленно отключить, а затем, после долгого перерыва, включить снова. Предотвращает двойной щелчок, разрешает повторную отправку, но все же разрешает недопустимую повторную отправку для отстающих пользователей.
Ctrl-C
8

Пожалуйста, проверьте плагин jquery-safeform .

Пример использования:

$('.safeform').safeform({
    timeout: 5000,  // disable next submission for 5 sec
    submit: function() {
        // You can put validation and ajax stuff here...

        // When done no need to wait for timeout, re-enable the form ASAP
        $(this).safeform('complete');
        return false;
    }
});
Макс Каменков
источник
4

... но форма не отправлена ​​с данными POST, которые она должна включать.

Верный. Имена / значения отключенных элементов формы не будут отправлены на сервер. Вы должны установить их как элементы только для чтения .

Кроме того, якоря не могут быть отключены таким образом. Вам нужно будет либо удалить их HREF (не рекомендуется), либо предотвратить их поведение по умолчанию (лучший способ), например:

<script type="text/javascript">
$(document).ready(function(){
    $("form#my_form").submit(function(){
      $('input').attr('readonly', true);
      $('input[type=submit]').attr("disabled", "disabled");
      $('a').unbind("click").click(function(e) {
          e.preventDefault();
          // or return false;
      });
    });
</script>
karim79
источник
@ Адам, да, все прикрепленные обработчики кликов сработают. Я думал, ты просто хотел помешать им измениться?
karim79
@ karim79 Мне все равно, будут ли запущены обработчики кликов, я просто хочу убедиться, что форма не будет повторно отправлена ​​пользователем, нажимающим другую кнопку отправки.
Адам
@ Адам - ​​нет, этого не произойдет. Я добавил свой пример, $('input[type=submit]').attr("disabled", "disabled");но мой любимый способ - добавить его onclick="this.disabled = 'disabled'"в клик.
karim79
@ karim79 Я пытался это сделать, но кнопка «Отправить», которую я нажимаю, не включена в сообщение POST. Мне нужно, чтобы он был включен в POST, чтобы определить, на какую кнопку нажимали
Адам
Я знаю, что вы только что распустили событие click для «а», но почему бы просто не использовать $ ('a'). Click (function (e) {e.preventDefault (); // или return false;}); ?
Фабио
4

Есть возможность улучшить подход Натана Лонга . Вы можете заменить логику обнаружения уже отправленной формы этой:

var lastTime = $(this).data("lastSubmitTime");
if (lastTime && typeof lastTime === "object") {
    var now = new Date();
    if ((now - lastTime) > 2000) // 2000ms
        return true;
    else
        return false;
}
$(this).data("lastSubmitTime", new Date());
return true; // or do an ajax call or smth else
Михаил Федозенко
источник
1
С какой стати вы используете таймер для этого подхода?
Боборт,
4

Код Натана, но для плагина jQuery Validate

Если вы используете плагин jQuery Validate, в них уже реализован обработчик отправки, и в этом случае нет причин для реализации более одного. Код:

jQuery.validator.setDefaults({
  submitHandler: function(form){
    // Prevent double submit
    if($(form).data('submitted')===true){
      // Previously submitted - don't submit again
      return false;
    } else {
      // Mark form as 'submitted' so that the next submit can be ignored
      $(form).data('submitted', true);
      return true;
    }
  }
});

Вы можете легко развернуть его в } else {-блоке, чтобы отключить ввод и / или кнопку отправки.

ура

Alph.Dev
источник
2

В итоге я использовал идеи из этого поста, чтобы найти решение, очень похожее на версию AtZako.

 jQuery.fn.preventDoubleSubmission = function() {

    var last_clicked, time_since_clicked;

    $(this).bind('submit', function(event){

    if(last_clicked) 
      time_since_clicked = event.timeStamp - last_clicked;

    last_clicked = event.timeStamp;

    if(time_since_clicked < 2000)
      return false;

    return true;
  });   
};

Используя как это:

$('#my-form').preventDoubleSubmission();

Я обнаружил, что решения, которые не включают в себя какое-то время ожидания, а просто отключают отправку или отключают элементы формы, вызывают проблемы, потому что после срабатывания блокировки вы не сможете отправить снова, пока не обновите страницу. Это создает некоторые проблемы для меня при работе с AJAX.

Это, вероятно, можно немного приукрашивать, так как это не так уж и красиво.

Jacklin
источник
2

Если для отправки формы используется AJAX , set async: falseдолжен предотвращать дополнительные отправки до очистки формы:

$("#form").submit(function(){
    var one = $("#one").val();
    var two = $("#two").val();
    $.ajax({
      type: "POST",
      async: false,  // <------ Will complete submit before allowing further action
      url: "process.php",
      data: "one="+one+"&two="+two+"&add=true",
      success: function(result){
        console.log(result);
        // do something with result
      },
      error: function(){alert('Error!')}
    });
    return false;
   }
});
Аль Кари
источник
3
Установка <code> asnyc: true </ code> - плохой подход, потому что он останавливает браузер до тех пор, пока запрос не будет выполнен, что превосходит суть AJAX
Эдвин М
2

Немного изменил решение Натана для Bootstrap 3. Это установит загрузочный текст для кнопки отправки. Кроме того, он будет отключен через 30 секунд и позволит повторно отправить форму.

jQuery.fn.preventDoubleSubmission = function() {
  $('input[type="submit"]').data('loading-text', 'Loading...');

  $(this).on('submit',function(e){
    var $form = $(this);

    $('input[type="submit"]', $form).button('loading');

    if ($form.data('submitted') === true) {
      // Previously submitted - don't submit again
      e.preventDefault();
    } else {
      // Mark it so that the next submit can be ignored
      $form.data('submitted', true);
      $form.setFormTimeout();
    }
  });

  // Keep chainability
  return this;
};

jQuery.fn.setFormTimeout = function() {
  var $form = $(this);
  setTimeout(function() {
    $('input[type="submit"]', $form).button('reset');
    alert('Form failed to submit within 30 seconds');
  }, 30000);
};
AlexZ
источник
2

Используйте две кнопки отправки.

<input id="sub" name="sub" type="submit" value="OK, Save">
<input id="sub2" name="sub2" type="submit" value="Hidden Submit" style="display:none">

И JQuery:

$("#sub").click(function(){
  $(this).val("Please wait..");
  $(this).attr("disabled","disabled");
  $("#sub2").click();
});
izmir_LEE
источник
2

Используйте простой счетчик при отправке.

    var submitCounter = 0;
    function monitor() {
        submitCounter++;
        if (submitCounter < 2) {
            console.log('Submitted. Attempt: ' + submitCounter);
            return true;
        }
        console.log('Not Submitted. Attempt: ' + submitCounter);
        return false;
    }

И вызовите monitor()функцию по отправке формы.

    <form action="/someAction.go" onsubmit="return monitor();" method="POST">
        ....
        <input type="submit" value="Save Data">
    </form>
Мистер Мак
источник
1

У меня были подобные проблемы, и мои решения следующие.

Если у вас нет проверки на стороне клиента, вы можете просто использовать метод jquery one (), как описано здесь.

http://api.jquery.com/one/

Это отключает обработчик после его вызова.

$("#mysavebuttonid").on("click", function () {
  $('form').submit();
});

Если вы делаете проверку на стороне клиента, как я делал, то это немного сложнее. Приведенный выше пример не позволит вам отправить снова после неудачной проверки. Попробуйте этот подход вместо

$("#mysavebuttonid").on("click", function (event) {
  $('form').submit();
  if (boolFormPassedClientSideValidation) {
        //form has passed client side validation and is going to be saved
        //now disable this button from future presses
        $(this).off(event);
   }
});
mattbloke
источник
1

Вы можете остановить вторую отправку этим

$("form").submit(function() {
        // submit more than once return false
        $(this).submit(function() {
            return false;
        });
        // submit once return true
        return true; // or do what you want to do
    });
});
Мохаммед Заян
источник
0

Мое решение:

// jQuery plugin to prevent double submission of forms
$.fn.preventDoubleSubmission = function () {
    var $form = $(this);

    $form.find('[type="submit"]').click(function () {
        $(this).prop('disabled', true);
        $form.submit();
    });

    // Keep chainability
    return this;
};

источник
0

В моем случае на отправке формы был некоторый проверочный код, поэтому я увеличиваю ответ Натана Лонга, включая контрольную точку на отправке.

$.fn.preventDoubleSubmission = function() {
      $(this).on('submit',function(e){
        var $form = $(this);
        //if the form has something in onsubmit
        var submitCode = $form.attr('onsubmit');
        if(submitCode != undefined && submitCode != ''){
            var submitFunction = new Function (submitCode);
            if(!submitFunction()){
                event.preventDefault();
                return false;
            }                   
        }

        if ($form.data('submitted') === true) {
            /*Previously submitted - don't submit again */
            e.preventDefault();
        } else {
          /*Mark it so that the next submit can be ignored*/
          $form.data('submitted', true);
        }
      });

      /*Keep chainability*/
      return this;
    };
bocapio
источник
0

Изменить кнопку отправки:

<input id="submitButtonId" type="submit" value="Delete" />

С нормальной кнопкой:

<input id="submitButtonId" type="button" value="Delete" />

Затем используйте функцию щелчка:

$("#submitButtonId").click(function () {
        $('#submitButtonId').prop('disabled', true);
        $('#myForm').submit();
    });

И помните кнопку повторного включения, когда это необходимо:

$('#submitButtonId').prop('disabled', false);
Дани
источник
0

Я не могу поверить старому доброму уловке css событий указателя: ни один еще не был упомянут. У меня была та же проблема, добавив отключенный атрибут, но это не отправляет назад. Попробуйте следующее и замените #SubmitButton на идентификатор вашей кнопки отправки.

$(document).on('click', '#SubmitButton', function () {
    $(this).css('pointer-events', 'none');
})
Том Макдоно
источник
Это не останавливает отправку формы, когда пользователь Enterнажимает клавишу.
реформирован
0

Почему бы не просто - это отправит форму, но также отключит кнопку отправки,

   $('#myForm').on('submit', function(e) {
       var clickedSubmit = $(this).find('input[type=submit]:focus');
       $(clickedSubmit).prop('disabled', true);
   });

Кроме того, если вы используете jQuery Validate, вы можете поместить эти две строки в if ($('#myForm').valid()).

ген б.
источник
0

этот код будет отображать загрузку на ярлыке кнопки и установить кнопку в

отключить состояние, затем после обработки снова включить и вернуть исходный текст кнопки **

$(function () {

    $(".btn-Loading").each(function (idx, elm) {
        $(elm).click(function () {
            //do processing
            if ($(".input-validation-error").length > 0)
                return;
            $(this).attr("label", $(this).text()).text("loading ....");
            $(this).delay(1000).animate({ disabled: true }, 1000, function () {
                //original event call
                $.when($(elm).delay(1000).one("click")).done(function () {
                    $(this).animate({ disabled: false }, 1000, function () {
                        $(this).text($(this).attr("label"));
                    })
                });
                //processing finalized
            });
        });
    });
    // and fire it after definition
});
Mohamed.Abdo
источник
-1

Я решил очень похожую проблему, используя:

$("#my_form").submit(function(){
    $('input[type=submit]').click(function(event){
        event.preventDefault();
    });
});
Нолан
источник