4つのスクリプトブロックを含む可能性のあるページを持つWebアプリがあるとしましょう-私が書いたスクリプトはそれらのブロックの1つで見つかるかもしれませんが、コントローラーによって処理されるものはわかりません。
いくつかのonclick
イベントをボタンにバインドしますが、予期しない順序で実行されることがあります。
順序を保証する方法はありますか、または過去にこの問題をどのように処理しましたか?
私は長年にわたってこの種のプロセスを一般化しようとしていましたが、私の場合は、チェーン内の最初のイベントリスナーの順序にのみ関心がありました。
何らかの用途がある場合、常に他のイベントの前にトリガーされるイベントリスナーをバインドするjQueryプラグインを次に示します。
**jQueryの変更に合わせて更新(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);
注意事項:
順序が重要な場合、独自のイベントを作成し、それらのイベントが他のコールバックによってトリガーされたときに起動するコールバックをバインドできます。
$('#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;
});
少なくともそのようなもの。
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オブジェクトを使用して巧妙な操作を行うこともできます。
バインドされたコールバックが呼び出される順序は、各jQueryオブジェクトのイベントデータによって管理されます。そのデータを直接表示および操作できる関数(私が知っている)はなく、bind()およびunbind()(または同等のヘルパー関数)のみを使用できます。
Dowskiの方法が最適です。さまざまなバインドされたコールバックを変更して、カスタムイベントの順序付けられたシーケンスにバインドし、「最初の」コールバックを「実際の」イベントにバインドする必要があります。そうすれば、どの順序でバインドされていても、シーケンスは正しい方法で実行されます。
私が見ることができる唯一の選択肢は、あなたが本当に、本当に考えたくないものです:関数のバインディング構文があなたの前にバインドされているかもしれないことを知っているなら、それらのすべての関数をアンバインドしてから再バインドしてみてください自分で正しい順序で。コードを複製しているので、それは単に問題を求めているだけです。
JQueryを使用して、オブジェクトのイベントデータ内のバインドされたイベントの順序を単純に変更できるが、不可能と思われるjQueryコアにフックするためのコードを記述することなく、クールになります。そして、おそらく私が考えていないことをこれを許可する意味があるので、多分それは意図的な省略かもしれません。
JQueryユニバースでは、バージョン1.8以降、これを異なる方法で実装する必要があることに注意してください。次のリリースノートは jQueryブログ: からのものです。
.data(“ events”):jQueryは、イベント関連データを各要素のイベントという名前のデータオブジェクト(待機)に保存します。これは内部データ構造であるため、1.8ではこれはユーザーデータの名前空間から削除されるため、同じ名前のアイテムと競合しません。 jQueryのイベントデータには引き続きjQuery._data(element、 "events")からアクセスできます
JQueryユニバースでハンドラーが実行される順序を完全に制御できます。リコーはこれを上で指摘しています。彼の答えが彼に多くの愛を獲得したようには見えませんが、このテクニックは非常に便利です。たとえば、ライブラリウィジェットのハンドラーの前に独自のハンドラーを実行する必要がある場合や、ウィジェットのハンドラーの呼び出しを条件付きでキャンセルする権限が必要な場合を考えてみてください。
$("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();
});
ハンドラを通常どおりバインドしてから実行します。
element.data('events').action.reverse();
例えば:
$('#mydiv').data('events').click.reverse();
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;
}
次のようなものを試すことができます:
/**
* 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]);
}
}
同じ問題があり、このトピックを見つけました。上記の答えはこれらの問題を解決できますが、私はそれらが良い計画だとは思いません。
現実の世界について考えてみましょう。
これらの回答を使用する場合は、コードを変更する必要があります。コードスタイルを変更する必要があります。このようなもの:
元の:
$('form').submit(handle);
ハック:
bindAtTheStart($('form'),'submit',handle);
時間が経つにつれて、あなたのプロジェクトについて考えてください。コードはくて読みにくいです! anthoer reason is simple is always better。 bindAtTheStartが10個ある場合、バグはありません。 100個のbindAtTheStartがある場合、それらを正しい順序に保つことができると本当に確信していますか?
同じイベントを複数バインドする必要がある場合は、jsファイルまたはjsコードの読み込み順序を制御するのが最善の方法だと思います。 jqueryはイベントデータをキューとして処理できます。順序は先入れ先出しです。コードを変更する必要はありません。ロード順を変更するだけです。
ここに私のショットがあり、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;
}
});
}
});
いくつかの特殊なケースでは、クリックイベントのバインド方法を変更できない場合(イベントバインドは他のコードから作成されます)、HTML要素を変更できる場合、考えられる解決策があります(warning:this notはイベントをバインドするための推奨方法です。他の開発者はこのためにあなたを殺すかもしれません):
<span onclick="yourEventHandler(event)">Button</span>
このバインド方法では、イベントハンドラーが最初に追加されるため、最初に実行されます。