20

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

我试图准确理解jQuery、vanilla Javascript 和浏览器(IE/FF/Chrome/Safari/Opera)如何处理表单提交事件——以及它们之间的关系。(请参阅我的另一个问题。)经过数小时的谷歌搜索和实验,我仍然无法得出结论,无论是因为不和谐还是含糊不清。

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

理想情况下:

  1. 用户填写表格
  2. 表单已提交——但没有调用以前绑定的事件处理程序,除了我的
  3. 我的处理程序发出 API 请求(当然是异步的)
  4. 用户确认 API 响应的验证结果
  5. 表单提交正常自动继续,调用之前被抑制的其他处理程序

我目前的理解是:(这些可能是错误的,如果是,请纠正我)

  • jQuery 将表单提交事件处理程序绑定到提交按钮click事件
  • 直接在提交元素click事件上的事件处理程序(无论是在标记中onclick=""还是使用 jQuery 绑定)首先执行
  • 接下来执行表单submit事件上的事件处理程序(无论是在标记中onsubmit=""还是使用 jQuery 绑定)
  • 调用$('[type=submit]').click()不会调用表单的submit事件,因此不会调用其处理程序
  • 调用$('form').submit()不会调用提交按钮的click事件,因此不会调用其处理程序
  • 然而不知何故,单击提交按钮的用户最终会调用绑定到表单submit事件的处理程序......(但如上所述,调用提交按钮上的单击不会做同样的事情)
  • 事实上,用户提交表单的任何方式(通过提交按钮或按 Enter),使用 jQuery 绑定到表单submit事件的处理程序都会被调用......

现在,我是:

  1. 将使用 jQuery 绑定的处理程序解除绑定到提交按钮的click事件,同时保留对它们的引用
  2. 将我自己的处理程序绑定到提交按钮的click事件,因此它首先执行
  3. onclick=""使用和(在它们各自的元素上)获取标记中绑定的任何处理程序onsubmit=""并使用 jQuery 重新绑定它们(因此它们在我的之后执行),然后将属性设置为null
  4. 重新绑定它们的处理程序(从步骤 1 开始),以便它们最后执行

实际上,这在我自己的测试中非常有效,因此我的事件处理程序首先触发(必不可少)。

问题和我的问题:

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

获取所有事件处理程序的最干净的方法是什么,把我的放在第一位,然后重新调用表单提交,以便以正确的顺序调用所有内容?

$('form').submit()和之间有什么区别(如果有的话)$('form')[0].submit()?(对于$('[type=submit]').click()and也是一样的$('[type=submit]')[0].click()

tl;博士,关于 Javascript/jQuery/浏览器表单提交事件处理的规范、清晰、一刀切的文档是什么?(我不是在寻找书籍推荐。)


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

4

4 回答 4

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 的提交处理程序的情况下进行表单提交。

于 2012-10-19T18:52:57.817 回答
4

这两个函数可能会帮助您在 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'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
    }
});
于 2012-10-19T19:26:10.507 回答
2

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

它使您的 ajax 函数 (A) 与所有其他函数(B、C、D 等)分开,仅将 A 放在标准“提交”队列中,将 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)

作为额外的好处,可以使自定义队列中的任何处理程序抑制任何/所有后续处理程序,和/或抑制表单提交。只需根据需要选择适当的模式:

模式一:

仅当所有前面的处理程序都没有拒绝 def 时才执行其操作。并且可以抑制模式 1 和模式 2 的所有后续处理程序。

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

模式二:

仅当所有前面的处理程序都没有拒绝 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)。
  • 在 Demo 中,所有的处理程序都是模式 1。
于 2012-10-19T23:22:14.283 回答
1

这就是我最终这样做的方式,到目前为止,它在许多测试用例中都非常成功。我学到了很多关于 jQuery 的事件,尤其是表单提交事件。我没有时间发布我收集的所有信息的综合百科全书,但现在就足够了:

这适用于SmartyStreets LiveAddress API jQuery 插件,它在用户离开页面之前验证地址。

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

完整代码在GitHub 上。这个特定的部分用于提交按钮上的“点击”,但类似的代码也用于处理表单提交。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);
于 2012-11-09T17:13:13.477 回答