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

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

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

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

理想的情况是:

  1. 用户填写表格
  2. 表单已提交 – 但除了我的之外,没有调用以前绑定的事件处理程序
  3. 我的处理程序发出API请求(当然是异步的)
  4. 用户确认API响应的validation结果
  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请求返回时,一切都是A-OK,我需要自动重新调用表单提交。 但是由于我上面的观察,我如何确保所有事件处理程序都被触发,就像它是一个自然forms提交一样?

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

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

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


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

使用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的提交处理程序。

这两个函数可以帮助您绑定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 } }); 

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

它通过在自定义事件队列中仅将A放置在标准“提交”队列和B,C,D等中,使您的ajax函数(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'); }); }); }); 

演示 (带模拟的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中)。
  • 在Demo中,所有处理程序都是模式1。

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

这是针对SmartyStreets LiveAddress API jQuery插件 ,它在用户离开页面之前validation地址。

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

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