20

これはおそらく、通常の「フォーム送信イベントをキャプチャするにはどうすればよいですか?」という質問ではないでしょう。質問。

フォーム送信イベントが jQuery、バニラ Javascript、およびブラウザー (IE/FF/Chrome/Safari/Opera) によってどのように処理されるか、およびそれらの間の関係を正確に理解しようとしています。(私の他の質問を参照してください。 ) 何時間にもわたるグーグル検索と実験の後、不一致または曖昧さのために、私はまだ結論に達することができません。

AJAX 要求が戻ってくるまでフォームを送信できないように、Web サイトのフォームと統合するスクリプトを完成させています。

理想的には:

  1. ユーザーがフォームに記入する
  2. フォームが送信されます -- ただし、以前にバインドされたイベント ハンドラーは呼び出されません。
  3. ハンドラーが API リクエストを作成します (もちろん非同期で)
  4. ユーザーは API 応答から検証結果を確認します
  5. フォームの送信は通常どおり自動的に続行され、以前は抑制されていた他のハンドラーが呼び出されます

私の現在の理解は次のとおりです:(これらは間違っている可能性があります。間違っている場合は修正してください)

  • jQuery は、フォーム送信イベント ハンドラーを送信ボタンclickイベントにバインドします。
  • clicksubmit 要素イベント (マークアップのようなものでonclick=""あれ、jQuery を使用してバインドされているものであれ)で直接イベント ハンドラーが最初に実行されます。
  • フォーム イベントのイベント ハンドラーsubmit(マークアップのようなものでonsubmit=""あれ、jQuery を使用してバインドされているものであれ) が次に実行されます。
  • 呼び出し$('[type=submit]').click()はフォームのsubmitイベントを呼び出さないため、そのハンドラーは呼び出されません
  • 呼び出し$('form').submit()は送信ボタンのclickイベントを呼び出さないため、そのハンドラーは呼び出されません
  • それでも、どういうわけか、送信ボタンをクリックするユーザーは、フォームのイベントにバインドされたハンドラーを呼び出すことになりますsubmit... (ただし、上記のように、送信ボタンをクリックしても同じことは行われません)
  • 実際、ユーザーがフォームを送信する方法(送信ボタンまたはEnterキーを押す)では、jQueryでフォームイベントにバインドされたハンドラーsubmit呼び出されます...

現在、私は:

  1. clickそれらへの参照を保持しながら、送信ボタンのイベントにjQueryでバインドされたハンドラーのバインドを解除します
  2. 独自のハンドラーを送信ボタンのclickイベントにバインドして、最初に実行する
  3. onclick=""andを使用してマークアップにバインドされたハンドラーをonsubmit=""(それぞれの要素で) 取得し、jQuery を使用してそれらを再バインドし (私の後に実行されるように)、属性を に設定します。null
  4. ハンドラーを (ステップ 1 から) 再バインドして、最後に実行するようにする

実際、これは私自身のテストで非常に効果的であり、イベント ハンドラーが最初に起動します (必須)。

問題と私の質問:

予想どおり (これまでのところ)、私のハンドラーが最初に起動します。問題は、ハンドラーが非同期であるため、API 要求が完了するまで、それを呼び出したフォームsubmitまたは送信ボタンイベントを抑制 (preventDefault/stopPropagation/etc) する必要があることです。clickその後、API リクエストが戻ってきて、すべて問題がなければ、フォーム送信を自動的に再度呼び出す必要があります。しかし、上記の観察から、すべてのイベント ハンドラーが自然なフォーム送信であるかのように起動されるようにするにはどうすればよいでしょうか?

すべてのイベント ハンドラーを取得し、最初に自分のイベント ハンドラーを配置してから、フォーム送信を再度呼び出して、すべてが適切な順序で呼び出されるようにする最もクリーンな方法は何ですか?

$('form').submit()と の間に違いがあるとすれば、それは何$('form')[0].submit()ですか? ( と も$('[type=submit]').click()同様$('[type=submit]')[0].click())

tl;dr、Javascript/jQuery/browser form-submit-event-handling に関する標準的で明確なフリーサイズのドキュメントは何ですか? (本のおすすめを探しているわけではありません。)


いくつかの説明: ショッピング カートのチェックアウト ページで多くの 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

これら 2 つの関数は、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
    }
});
于 2012-10-19T19:26:10.507 に答える
2

これに対処するには多くの方法があるに違いありません。

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

ノート:

  • これらのハンドラーで deferred を解決して、残りのキューを処理せずにフォームをすぐに送信することができます。ただし、一般に、遅延は、キューがトリガーされる前にキューに動的に追加された「.last」ハンドラーによって解決されます (ハンドラー A に戻ります)。
  • デモでは、すべてのハンドラーがパターン 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 に答える