如何才能正确捕获并重新启动表单提交事件?

时间:2012-10-19 16:32:50

标签: javascript jquery forms event-handling

这可能不是您通常的“如何捕获表单提交事件?”问题

我正在尝试理解正是如何通过jQuery,vanilla Javascript和浏览器(IE / FF / Chrome / Safari / Opera)处理表单提交事件 - 以及它们之间的关系。 (See my other question.)经过数小时的谷歌搜索和实验,我仍然无法得出结论,无论是因为不和谐还是含糊不清。

我正在完成一个与网站表单集成的脚本,以便在AJAX请求返回之前无法提交表单。

理想情况下:

  1. 用户填写表格
  2. 表单已提交 - 但除了我的
  3. 之外,没有调用以前绑定的事件处理程序
  4. 我的处理程序发出API请求(当然是异步的)
  5. 用户确认API响应的验证结果
  6. 表单提交继续正常,自动,调用之前被压缩的其他处理程序
  7. 我目前的理解是:(这些可能有误,如果有,请纠正我)

    • jQuery binds form submit event handlers to the submit button click events
    • 直接在提交元素click事件上的事件处理程序(无论是在标记中如onclick=""还是使用jQuery绑定)都会先执行
    • 下一步执行submit个事件上的事件处理程序(无论是在onsubmit=""标记中还是使用jQuery绑定)
    • 调用$('[type=submit]').click()不会调用表单的submit事件,因此不会调用其处理程序
    • 调用$('form').submit()不会调用提交按钮的click事件,因此不会调用其处理程序
    • 然而,不知何故,单击提交按钮的用户最终会调用绑定到表单submit事件的处理程序...(但如上所述,调用单击提交按钮不会执行相同操作)< / LI>
    • 实际上,用户提交表单的任何方式(通过提交按钮或按Enter键),jQuery绑定到submit形式事件的处理程序被调用...

    现在,我是:

    1. 将jQuery绑定的处理程序解除绑定到提交按钮的click事件,同时保留对它们的引用
    2. 将我自己的处理程序绑定到提交按钮的click事件,因此它首先执行
    3. 使用onclick=""onsubmit=""(在各自的元素上)绑定标记中的任何处理程序并使用jQuery重新绑定它们(因此它们在我的后面执行),然后将属性设置为{{ 1}}
    4. 重新绑定其处理程序(从步骤1开始),以便它们执行最后一次
    5. 实际上,这在我自己的测试中非常有效,所以我的事件处理程序首先触发(必要)。

      问题和我的问题:

      我的处理程序首先触发,就像预期的那样(到目前为止)。问题是我的处理程序是异步的,所以我必须禁止(preventDefault / stopPropagation / etc)表单null或提交调用它的按钮submit ...直到API请求完成。然后,当API请求返回时,一切都是A-OK,我需要自动重新调用表单提交。但是由于我上面的观察,我如何确保所有事件处理程序被触发,就好像它是一个自然的表单提交一样?

      获取所有事件处理程序的最简洁方法是什么,先放入我的,然后重新调用表单提交,以便以正确的顺序调用所有内容?

      click$('form').submit()之间有什么区别? ($('form')[0].submit()$('[type=submit]').click()

      也是如此

      tl; dr ,关于Javascript / jQuery / browser form-submit-event-handling的规范,清晰,一刀切的文档是什么? (我不是在寻找书籍推荐。)


      一些解释:我正在尝试补偿购物车结帐页面中的大量Javascript,有时只有当用户点击页面底部的按钮(不是提交按钮)时才会提交表单,或者还有其他棘手的情况。到目前为止,它已经相当成功,它只是重新调用提交,这确实是问题。

4 个答案:

答案 0 :(得分:6)

使用jQuery绑定到表单的提交处理程序并阻止默认操作,然后,当您要提交表单时,直接在表单节点上触发它。

$("#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方法,浏览器执行表单提交而不触发jQuery的提交处理程序。

答案 1 :(得分:4)

这两个函数可以帮助您绑定jquery队列前面的事件处理程序。您仍然需要删除内联事件处理程序(onclickonsubmit)并使用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's called the first time (see jQuery's .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
    }
});

绑定一个单独的处理程序来阻止[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
    }
});

答案 2 :(得分:2)

必须有很多方法来解决这个问题 - 这是一个。

它将ajax函数(A)与所有其他函数(B,C,D等)分开,方法是将A放在标准的“submit”队列中,将B,C,D等放在自定义事件队列中。这避免了制作B,C,D等所需的棘手的诡计,这取决于A的异步响应。

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

    //Here's a worker function that performs the ajax.
    //It's coded like this to reduce bulk in the main supervisor Handler A.
    //Make sure to return the jqXHR object that's 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'处理程序来解决(回到Handler A中)。
  • 在演示中,所有处理程序都是模式1。

答案 3 :(得分:1)

以下是我最终如何做到这一点,到目前为止,它在很多测试案例中都非常成功。我在jQuery方面学到了很多关于事件的知识,特别是表单提交事件。我没有时间发布我收集的所有信息的综合百科全书,但现在就足够了:

这是SmartyStreets LiveAddress API jQuery Plugin,用于在用户离开页面之前验证地址。

最成功的方法是抓住提交按钮的click事件。下面的代码段位于jquery.liveaddress.js文件中。它引用了尽可能多的事件处理程序(jQuery,onclick ---首先触发onclick),将它们连根拔起,自己(submitHandler),并将其他事件分层。它在TortugaRumCakes.com(结账)和MedicalCareAlert.com(主页和结账)以及许多其他网站上成功运作。

完整代码位于GitHub。此特定段用于提交按钮上的“单击”,但类似的代码也用于处理表单提交。 jQuery的submit()函数似乎是相当专有的......但这种处理都确保即使在jQuery对象上以编程方式调用.submit()时也会调用它。

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);