jQueryでバインドされたイベントを注文する方法


164

4つのスクリプトブロックを含む可能性のあるページがあるWebアプリがあるとします-私が書くスクリプトはそれらのブロックの1つにあるかもしれませんが、どれがコントローラーによって処理されるのかわかりません。

一部のonclickイベントをボタンにバインドしますが、予期しない順序で実行される場合があります。

注文を確実にする方法はありますか、または過去にこの問題をどのように処理しましたか?


1
コールバックをCallBackオブジェクトに追加します。コールバックを起動する場合は、一度にすべてを起動でき、追加した順序で実行されます。 api.jquery.com/jQuery.Callbacks
Tyddlywink

回答:


28

私は古くからこの種のプロセスを一般化しようと試みていましたが、私の場合はチェーン内の最初のイベントリスナーの順序のみに関係していました。

それがなんらかの用途である場合、他の前に常にトリガーされるイベントリスナーをバインドする私のjQueryプラグインを次に示します。

** jQueryの変更に合わせて更新(thanks Toskan) **

(function($) {
    $.fn.bindFirst = function(/*String*/ eventType, /*[Object])*/ eventData, /*Function*/ handler) {
        var indexOfDot = eventType.indexOf(".");
        var eventNameSpace = indexOfDot > 0 ? eventType.substring(indexOfDot) : "";

        eventType = indexOfDot > 0 ? eventType.substring(0, indexOfDot) : eventType;
        handler = handler == undefined ? eventData : handler;
        eventData = typeof eventData == "function" ? {} : eventData;

        return this.each(function() {
            var $this = $(this);
            var currentAttrListener = this["on" + eventType];

            if (currentAttrListener) {
                $this.bind(eventType, function(e) {
                    return currentAttrListener(e.originalEvent); 
                });

                this["on" + eventType] = null;
            }

            $this.bind(eventType + eventNameSpace, eventData, handler);

            var allEvents = $this.data("events") || $._data($this[0], "events");
            var typeEvents = allEvents[eventType];
            var newEvent = typeEvents.pop();
            typeEvents.unshift(newEvent);
        });
    };
})(jQuery);

注意事項:

  • これは完全にはテストされていません。
  • jQueryフレームワークの内部が変更されないことに依存しています(1.5.2でのみテスト済み)。
  • ソース要素の属性として、またはjQuery bind()およびその他の関連する関数を使用する以外の方法でバインドされたイベントリスナーの前に、必ずしもトリガーされるとは限りません。

これは、jQueryを介して追加されたイベントに対してのみ機能することにも注意してください。
Grinn、2012年

1
これは、それが使用しているので、それ以上は動作しません。this.data("events")ここでは、参照してくださいstackoverflow.com/questions/12214654/...
Toskan

4
ここにjQuery 1.8用に更新された同様の関数があります:stackoverflow.com/a/2641047/850782
EpicVoyage

136

順序が重要な場合は、独自のイベントを作成し、それらのイベントが他のコールバックによってトリガーされたときに発生するコールバックをバインドできます。

$('#mydiv').click(function(e) {
    // maniplate #mydiv ...
    $('#mydiv').trigger('mydiv-manipulated');
});

$('#mydiv').bind('mydiv-manipulated', function(e) {
    // do more stuff now that #mydiv has been manipulated
    return;
});

少なくともそのようなもの。


23
バインドコードの前のある時点で定義されたコールバックのクリックイベントの伝播を停止する場合はどうでしょうか。
vinilios 2011

2
これが受け入れられなかった理由を質問できますか?現在受け入れられている答えは私の答えを最良の方法として引用しているので、ちょっと混乱しています。:-)
dowski

これはmkoryakの場合に機能しますが、同じイベントが複数回呼び出されている場合は機能しません。同様の問題があり、1回のキーストロークで$(window).scroll()が逆の順序で複数回トリガーされます。
kxsong 2014

37

Dowskiのメソッドは、すべてのコールバックが常に存在し、お互いに依存していることに満足している場合に適しています。

ただし、コールバックを互いに独立させたい場合は、バブリングを利用して、後続のイベントをデリゲートとして親要素にアタッチすることができます。親要素のハンドラーは、要素のハンドラーの後にトリガーされ、ドキュメントまで続きます。あなたが使用することができますので、これはかなり良いですevent.stopPropagation()event.preventDefault()ハンドラをスキップして、アクションをキャンセルまたは無キャンセルするなど、。

$( '#mybutton' ).click( function(e) { 
    // Do stuff first
} );

$( '#mybutton' ).click( function(e) { 
    // Do other stuff first
} );

$( document ).delegate( '#mybutton', 'click', function(e) {
    // Do stuff last
} );

または、これが気に入らない場合は、Nick Leaches bindLastプラグインを使用して、イベントが最後にバインドされるように強制できます:https : //github.com/nickyleach/jQuery.bindLast

または、jQuery 1.5を使用している場合は、新しいDeferredオブジェクトを使用して巧妙な処理を行うこともできます。


6
.delegateはもう使用されておらず、使用する必要があります.onが、同じ動作に到達する方法がわかりませんon
eric.itzhak

:これ以上ここを参照してください動作しませんので、このプラグインは、固定された取得する必要がありstackoverflow.com/questions/12214654/...
Toskan

@ eric.itzhak .on()を古い.delegate()のように動作させるには、セレクターを提供するだけです。詳細はこちらapi.jquery.com/delegate
Sam Kauffman

バブリングを利用したい場合は、イベントを委任するのではなく、直接行う必要があると思います。api.jquery.com/on/#direct-and-delegated-events
Ryne Everett

15

バインドされたコールバックが呼び出される順序は、各jQueryオブジェクトのイベントデータによって管理されます。データを直接表示および操作できる関数(私が知っている)はありません。bind()およびunbind()(または同等のヘルパー関数)のみを使用できます。

ダウスキーの方法が最適です。「最初の」コールバックが「実際の」イベントにバインドされた状態で、カスタムイベントの順序付けされたシーケンスにバインドするように、さまざまなバインドされたコールバックを変更する必要があります。そのように、それらがバインドされる順序に関係なく、シーケンスは正しい方法で実行されます。

私が見ることができる唯一の選択肢は、あなたが本当に熟考したくないものです:関数のバインディング構文があなたの前にバインドされている可能性があることがわかっている場合は、それらすべての関数のバインドを解除してから再度バインドしてみてください自分で正しい順序で。これで問題が発生します。コードが重複しているからです。

jQueryがオブジェクトのイベントデータ内のバインドされたイベントの順序を変更することを許可しただけで、jQueryコアにフックするコードを記述せずに可能であると思わない場合は、すばらしいでしょう。そして、おそらくこれを許可することには私が考えていなかった影響があるので、意図的に省略しているのかもしれません。


12
jqueryによって要素にバインドされたすべてのイベントを表示するには、var allEvents = $ .data(this、 "events");を使用します。
redsquare 2008年

9

jQueryユニバースでは、バージョン1.8とは異なる方法で実装する必要があることに注意してください。次のリリースノートはjQueryブログからのものです。

.data(“ events”):jQueryは、そのイベント関連データを、各要素のイベントという名前の(それを待つ)データオブジェクトに格納します。これは内部データ構造であるため、1.8ではユーザーデータ名前空間から削除されるため、同じ名前のアイテムと競合しません。jQueryのイベントデータは、jQuery._data(element、 "events")を介して引き続きアクセスできます。

ハンドラーがjQueryユニバースで実行れる順序を完全に制御できます。Ricooはこれを上で指摘しています。彼の答えが彼に多くの愛情を与えたようには見えませんが、このテクニックは非常に便利です。たとえば、ライブラリウィジェットのハンドラーの前に独自のハンドラーを実行する必要がある場合、またはウィジェットのハンドラーへの呼び出しを条件付きでキャンセルする権限が必要な場合はいつでも検討してください。

$("button").click(function(e){
    if(bSomeConditional)
       e.stopImmediatePropagation();//Don't execute the widget's handler
}).each(function () {
    var aClickListeners = $._data(this, "events").click;
    aClickListeners.reverse();
});

7

ハンドラを通常どおりにバインドして実行します。

element.data('events').action.reverse();

だから例えば:

$('#mydiv').data('events').click.reverse();

2
動作しませんが、これは$._data(element, 'events')[name].reverse()動作します
Attenzione 2015年

7
function bindFirst(owner, event, handler) {
    owner.unbind(event, handler);
    owner.bind(event, handler);

    var events = owner.data('events')[event];
    events.unshift(events.pop());

    owner.data('events')[event] = events;
}

3

あなたはこのようなことを試すことができます:

/**
  * Guarantee that a event handler allways be the last to execute
  * @param owner The jquery object with any others events handlers $(selector)
  * @param event The event descriptor like 'click'
  * @param handler The event handler to be executed allways at the end.
**/
function bindAtTheEnd(owner,event,handler){
    var aux=function(){owner.unbind(event,handler);owner.bind(event,handler);};
    bindAtTheStart(owner,event,aux,true);

}
/**
  * Bind a event handler at the start of all others events handlers.
  * @param owner Jquery object with any others events handlers $(selector);
  * @param event The event descriptor for example 'click';
  * @param handler The event handler to bind at the start.
  * @param one If the function only be executed once.
**/
function bindAtTheStart(owner,event,handler,one){
    var eventos,index;
    var handlers=new Array();
    owner.unbind(event,handler);
    eventos=owner.data("events")[event];
    for(index=0;index<eventos.length;index+=1){
        handlers[index]=eventos[index];
    }
    owner.unbind(event);
    if(one){
        owner.one(event,handler);
    }
    else{
        owner.bind(event,handler);
    }
    for(index=0;index<handlers.length;index+=1){
        owner.bind(event,ownerhandlers[index]);
    }   
}

「ownerhandlers」は単なる「handlers」である必要があると思います
Joril

1

同じ問題があり、このトピックが見つかりました。上記の答えはそれらの問題を解決することができますが、私はそれらが良い計画だとは思いません。

現実の世界について考えてみましょう。

これらの回答を使用する場合は、コードを変更する必要があります。コードスタイルを変更する必要があります。このようなもの:

元の:

$('form').submit(handle);

ハック:

bindAtTheStart($('form'),'submit',handle);

時間の経過とともに、プロジェクトについて考えます。コードは醜くて読みにくいです!anthoerの理由は単純ですが常に優れています。bindAtTheStartが10個ある場合は、バグがない可能性があります。bindAtTheStartが100ある場合、それらを正しい順序で保持できると本当に確信していますか?

同じイベントを複数バインドする必要がある場合は、js-fileまたはjs-codeの読み込み順序を制御するのが最善の方法だと思います。jqueryはイベントデータをキューとして処理できます。順序は先入れ先出しです。コードを変更する必要はありません。ロード順序を変更するだけです。


0

これが私のショットです。jQueryのさまざまなバージョンをカバーしています。

// Binds a jQuery event to elements at the start of the event chain for that type.
jQuery.extend({
    _bindEventHandlerAtStart: function ($elements, eventType, handler) {
        var _data;

        $elements.bind(eventType, handler);
        // This bound the event, naturally, at the end of the event chain. We
        // need it at the start.

        if (typeof jQuery._data === 'function') {
            // Since jQuery 1.8.1, it seems, that the events object isn't
            // available through the public API `.data` method.
            // Using `$._data, where it exists, seems to work.
            _data = true;
        }

        $elements.each(function (index, element) {
            var events;

            if (_data) {
                events = jQuery._data(element, 'events')[eventType];
            } else {
                events = jQuery(element).data('events')[eventType];
            }

            events.unshift(events.pop());

            if (_data) {
                jQuery._data(element, 'events')[eventType] = events;
            } else {
                jQuery(element).data('events')[eventType] = events;
            }
        });
    }
});

0

いくつかの特殊なケースでは、クリックイベントのバインド方法を変更できず(イベントバインディングは他のコードから作成されます)、HTML要素を変更できる場合、考えられる解決策を次に示します(警告:これは推奨されるバインド方法ではありません)イベント、他の開発者はこれであなたを殺害するかもしれません):

<span onclick="yourEventHandler(event)">Button</span>

このバインド方法では、イベントハンドラーが最初に追加されるため、最初に実行されます。


これは解決策ではなく、解決策のヒントです。この "onclick"ハンドラーは、(jQuery)ハンドラーが既にある要素で実行されているのでしょうか、それとも<span> が要素をjQueryハンドラーでラップしているのですか?
Auspex 2017

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.