Подтвердить что ты не робот

Как я могу правильно захватить и перезапустить событие отправки формы?

Это, вероятно, не ваш обычный "Как мне отображать события отправки формы?" вопрос.

Я пытаюсь понять, как обрабатываются события формы, передаваемые jQuery, ванильным Javascript и браузером (IE/FF/Chrome/Safari/Opera), а также отношения между ними. (См. мой другой вопрос.) После нескольких часов работы в Google и экспериментов я все еще не могу прийти к выводу, из-за раздора или неопределенности.

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

В идеале:

  • Пользователь заполняет форму
  • Форма отправляется - но обработчики обработанных ранее привязанных вызовов не вызываются, кроме my
  • Мой обработчик делает запрос API (асинхронно, конечно)
  • Пользователь подтверждает результаты проверки ответа API
  • Подача формы продолжается нормально, автоматически, вызывая другие обработчики, которые до этого были подавлены.

Мое настоящее понимание заключается в следующем: (это может быть неправильно, пожалуйста, исправьте меня, если да)

  • jQuery привязывает форму отправки обработчиков событий к кнопке отправки click событий
  • Обработчики событий непосредственно в элементе отправки элемента click (будь то в разметке типа onclick="" или связаны с использованием jQuery) выполняются сначала
  • Обработчики событий в событиях формы submit (будь то в разметке типа onsubmit="" или связаны с использованием jQuery) выполняются следующим образом
  • Вызов $('[type=submit]').click() не вызывает событие формы submit, поэтому его обработчики не получают вызов
  • Вызов $('form').submit() не вызывает событие отправки click, поэтому его обработчики не получают вызов
  • Тем не менее, пользователь, который нажимает кнопку отправки, завершает вызов обработчиков, привязанных к событию формы submit... (но, как упоминалось выше, вызов на кнопку отправки не делает то же самое)
  • Фактически, любым способом пользователь отправляет форму (через кнопку отправки или нажав Enter), обработчики, связанные с jQuery в форме submit, вызываются...

Сейчас я:

  • Отвязывание обработчиков, связанных с jQuery, с кнопкой отправки click при сохранении ссылки на них
  • Привязка моего обработчика к событию отправки click, поэтому он выполняет первый
  • Принимая любые обработчики, связанные в разметке, используя onclick="" и onsubmit="" (по их соответствующим элементам) и повторно привязывая их с помощью jQuery (чтобы они выполнялись после моего), а затем устанавливая атрибуты null
  • Повторное связывание своих обработчиков (с шага 1), чтобы они выполняли последние

На самом деле, это было замечательно эффективно в моем собственном тестировании, так что мой обработчик событий запускается первым (существенным).

Проблема и мои вопросы:

Мой обработчик запускается первым, как и ожидалось (до сих пор). Проблема в том, что мой обработчик является асинхронным, поэтому я должен подавить (preventDefault/stopPropagation/etc) форму submit или отправить кнопку click событие, которое вызвало его... до тех пор, пока запрос API не будет выполнен. Затем, когда возвращается запрос API, и все это A-OK, мне нужно повторно вызвать форму submit автоматически. Но из-за моих замечаний выше, как я могу убедиться, что все обработчики событий запущены, как если бы это была естественная форма submit?

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

А какая разница, если таковая имеется, между $('form').submit() и $('form')[0].submit()? (И то же самое для $('[type=submit]').click() и $('[type=submit]')[0].click())

tl; dr. Какова каноническая, ясная документация об использовании Javascript/jQuery/браузера form-submit-event-handling? (Я не ищу рекомендации для книги.)


Некоторое объяснение: я пытаюсь компенсировать большое количество Javascript на страницах проверки корзины покупок, где иногда форма отправляется только тогда, когда пользователь нажимает кнопку BUTTON (не кнопку отправки) в нижней части страницы, или есть другие сложные сценарии. До сих пор он был довольно успешным, он просто повторно ссылался на submit, что действительно проблема.

4b9b3361

Ответ 1

Вот как я это сделал, и он был очень успешным до сих пор в многочисленных тестах. Я узнал очень много о событиях, особенно о форме отправки событий, в отношении jQuery. У меня нет времени опубликовать полную энциклопедию всей собранной мной информации, но этого пока достаточно:

Это было для SmartyStreets LiveAddress API jQuery Plugin, который проверяет адреса перед тем, как пользователь покидает страницу.

Самый успешный метод - захват кнопки отправки click. Ниже приведен фрагмент в файле jquery.liveaddress.js. Он получает ссылки на как можно больше обработчиков событий (jQuery, onclick --- onclick one fire first), выкручивает их, ломает свой собственный (submitHandler) и выравнивает остальные поверх него. Он успешно работал на таких сайтах, как TortugaRumCakes.com(checkout) и MedicalCareAlert.com(домашняя страница и выписка), а также многие другие.

Полный код находится на GitHub. Этот сегмент относится к "click" в кнопке отправки, но аналогичный код используется также для обработки формы submit. Функция jQuery submit() кажется довольно запатентованной... но эта обработка гарантирует, что она будет вызвана даже тогда, когда .submit() называется программным способом для объекта jQuery.

var oldHandlers, eventsRef = $._data(this, 'events');

// If there are previously-bound-event-handlers (from jQuery), get those.
if (eventsRef && eventsRef.click && eventsRef.click.length > 0)
{
    // Get a reference to the old handlers previously bound by jQuery
    oldHandlers = $.extend(true, [], eventsRef.click);
}

// Unbind them...
$(this).unbind('click');

// ... then bind ours first ...
$(this).click({ form: f, invoke: this }, submitHandler);

// ... then bind theirs last:
// First bind their onclick="..." handles...
if (typeof this.onclick === 'function')
{
    var temp = this.onclick;
    this.onclick = null;
    $(this).click(temp);
}

// ... then finish up with their old jQuery handles.
if (oldHandlers)
    for (var j = 0; j < oldHandlers.length; j++)
        $(this).click(oldHandlers[j].data, oldHandlers[j].handler);

Ответ 2

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

$("#formid").submit(function(e){
    // prevent submit
    e.preventDefault();

    // validate and do whatever else


    // ...


    // Now when you want to submit the form and bypass the jQuery-bound event, use 
    $("#formid")[0].submit();
    // or this.submit(); if `this` is the form node.

});

Вызывая метод submit формы node, браузер выполняет отправку формы без запуска jQuery отправителя.

Ответ 3

Эти две функции могут помочь вам связать обработчики событий в передней части очереди jquery. Вам все равно придется разделить встроенные обработчики событий (onclick, onsubmit) и повторно связать их с помощью jQuery.

// prepends an event handler to the callback queue
$.fn.bindBefore = function(type, fn) {

    type = type.split(/\s+/);

    this.each(function() {
        var len = type.length;
        while( len-- ) {
            $(this).bind(type[len], fn);

            var evt = $.data(this, 'events')[type[len]];
            evt.splice(0, 0, evt.pop());
        }
    });
};

// prepends an event handler to the callback queue
// self-destructs after it called the first time (see jQuery .one())
$.fn.oneBefore = function(type, fn) {

    type = type.split(/\s+/);

    this.each(function() {
        var len = type.length;
        while( len-- ) {
            $(this).one(type[len], fn);

            var evt = $.data(this, 'events')[type[len]];
            evt.splice(0, 0, evt.pop());
        }
    });
};

Привяжите обработчик отправки, который выполняет вызов ajax:

$form.bindBefore('submit', function(event) {
    if (!$form.hasClass('allow-submit')) {
        event.preventDefault();
        event.stopPropagation();
        event.stopImmediatePropagation();

        // perform your ajax call to validate/whatever
        var deferred = $.ajax(...);
        deferred.done(function() {
            $form.addClass('allow-submit');
        });

        return false;
    } else {
        // the submit event will proceed normally
    }
});

Привязать отдельный обработчик для блокировки событий click на [type="submit"], пока вы не будете готовы:

$form.find('[type="submit"]').bindBefore('click', function(event) {
    if (!$form.hasClass('allow-submit')) {
        // block all handlers in this queue
        event.preventDefault();
        event.stopPropagation();
        event.stopImmediatePropagation();
        return false;
    } else {
        // the click event will proceed normally
    }
});

Ответ 4

Должно быть много способов решить эту проблему - здесь одна.

Он сохраняет вашу функцию ajax (A) отдельно от всех остальных (B, C, D и т.д.), помещая только A в стандартную очередь "отправить" и B, C, D и т.д. в пользовательскую очередь событий, Это позволяет избежать сложных махинаций, которые в противном случае необходимы для того, чтобы сделать B, C, D и т.д. Зависимыми от асинхронного ответа.

$(function(){
    var formSubmitQueue = 'formSubmitQueue';

    //Here a worker function that performs the ajax.
    //It coded like this to reduce bulk in the main supervisor Handler A.
    //Make sure to return the jqXHR object that returned by $.ajax().
    function myAjaxHandler() {
        return $.ajax({
            //various ajax options here
            success: function(data, textStatus, jqXHR) {
                //do whatever is necessary with the response here
            },
            error: function(jqXHR, textStatus, errorThrown) {
                //do whatever is necessary on ajax error here
            }
        });
    }

    //Now build a queue of other functions to be executed on ajax success.
    //These are just dummy functions involving a confirm(), which allows us to reject the master deferred passed into these handlers as a formal variable.
    $("#myForm").on(formSubmitQueue, function(e, def) {
        if(def.state() !== 'rejected') {
            if (!confirm('Handler B')) {
                def.reject();
            }
        }
    }).on(formSubmitQueue, function(e, def) {
        if(def.state() !== 'rejected') {
            if (!confirm('Handler C')) {
                def.reject();
            }
        }
    }).on(formSubmitQueue, function(e, def) {
        if(def.state() !== 'rejected') {
            if (!confirm('Handler D')) {
                def.reject();
            }
        }
    });

    $("#myForm").on('submit', function(e) {
        var $form = $(this);
        e.preventDefault();
        alert('Handler A');
        myAjaxHandler().done(function() {
            //alert('ajax success');
            var def = $.Deferred().done(function() {
                $form.get(0).submit();
            }).fail(function() {
                alert('A handler in the custom queue suppressed form submission');
            });
            //add extra custom handler to resolve the Deferred.
            $form.off(formSubmitQueue+'.last').on(formSubmitQueue+'.last', function(e, def) {
                def.resolve();
            });
            $form.trigger(formSubmitQueue, def);
        }).fail(function() {
            //alert('ajax failed');
        });
    });
});

DEMO (с имитированным ajax)

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

Образец 1:

Выполняет свои действия только в том случае, если все предыдущие обработчики не отклонили def. и может подавлять все следующие обработчики шаблона 1 и шаблона 2.

$("#myForm").on(formSubmitQueue, function(e, def) {
    if(def.state() !== 'rejected') {
        //actions as required here
        if (expression) {
            def.reject();
        }
    }
});

Образец 2:

Выполняет свои действия только в том случае, если все предыдущие обработчики не отклонили def. но не подавляет следующие обработчики.

$("#myForm").on(formSubmitQueue, function(e, def) {
    if(def.state() !== 'rejected') {
        //actions as required here
    }
});

Рисунок 3:

Выполняет свои действия безоговорочно, но все равно может подавлять все следующие обработчики шаблона 1 и шаблона 2.

$("#myForm").on(formSubmitQueue, function(e, def) {
    //actions as required here
    if (expression) {
        def.reject();
    }
});

Рисунок 4:

Выполняет свои действия безоговорочно и не подавляет следующие обработчики.

$("#myForm").on(formSubmitQueue, function(e, def) {
    //actions as required here
});

Примечания:

  • Отложенные могут быть разрешены в этих обработчиках, чтобы немедленно отправить форму без обработки оставшейся части очереди. Но в общем случае отложенное будет разрешено обработчиком ".last", добавленным в очередь динамически до запуска очереди (обратно в обработчик A).
  • В демонстрации все обработчики имеют шаблон 1.