Cara memesan acara yang terikat dengan jQuery

164

Katakanlah saya memiliki aplikasi web yang memiliki halaman yang mungkin berisi 4 blok skrip - skrip yang saya tulis dapat ditemukan di salah satu blok itu, tetapi saya tidak tahu yang mana, yang ditangani oleh pengontrol.

Saya mengikat beberapa onclickacara ke tombol, tetapi saya menemukan bahwa mereka kadang-kadang mengeksekusi dalam urutan yang tidak saya harapkan.

Apakah ada cara untuk memastikan pesanan, atau bagaimana Anda menangani masalah ini di masa lalu?

mkoryak
sumber
1
Tambahkan panggilan balik Anda ke objek CallBack dan ketika Anda ingin memecat mereka, Anda bisa memecat mereka sekaligus dan mereka akan berjalan dalam urutan yang ditambahkan. api.jquery.com/jQuery.Callbacks
Tyddlywink

Jawaban:

28

Saya telah berusaha selama bertahun-tahun untuk menggeneralisasi proses semacam ini, tetapi dalam kasus saya, saya hanya peduli dengan urutan pendengar acara pertama dalam rantai.

Jika ada gunanya, berikut ini adalah plugin jQuery saya yang mengikat pendengar acara yang selalu dipicu sebelum yang lain:

** DIPERBARUI sejalan dengan perubahan jQuery (terima kasih 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);

Hal yang perlu diperhatikan:

  • Ini belum sepenuhnya diuji.
  • Itu bergantung pada internal kerangka kerja jQuery tidak berubah (hanya diuji dengan 1.5.2).
  • Itu tidak perlu dipicu sebelum pendengar acara yang terikat dengan cara apa pun selain sebagai atribut elemen sumber atau menggunakan jQuery bind () dan fungsi terkait lainnya.
Ed.
sumber
Penting untuk dicatat bahwa ini juga hanya berfungsi untuk acara yang ditambahkan melalui jQuery.
Grinn
1
ini tidak berfungsi lagi sejak digunakan this.data("events"), lihat di sini stackoverflow.com/questions/12214654/…
Toskan
4
Ada fungsi serupa yang telah diperbarui untuk jQuery 1.8 di sini: stackoverflow.com/a/2641047/850782
EpicVoyage
136

Jika pesanan penting, Anda dapat membuat acara Anda sendiri dan mengikat panggilan balik ke api ketika peristiwa itu dipicu oleh panggilan balik lainnya.

$('#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;
});

Setidaknya sesuatu seperti itu.

dowski
sumber
23
bagaimana jika kita ingin menghentikan penyebaran acara klik untuk panggilan balik yang ditentukan di beberapa titik sebelum kode ikat kita.
vinilios
2
Bisakah saya bertanya mengapa ini tidak dapat diterima? Jawaban yang diterima saat ini mengutip jawaban saya sebagai metode terbaik, jadi saya agak bingung. :-)
dowski
Ini berfungsi untuk kasus mkoryak, tetapi tidak jika acara yang sama dipanggil beberapa kali. Saya memiliki masalah serupa, di mana satu penekanan tombol memicu $ (window) .scroll () beberapa kali, dalam urutan terbalik .
kxsong
37

Metode Dowski bagus jika semua panggilan balik Anda akan selalu ada dan Anda senang mereka saling bergantung.

Namun, jika Anda ingin panggilan balik itu tidak tergantung satu sama lain, Anda dapat memanfaatkan penggelembungan dan melampirkan peristiwa selanjutnya sebagai delegasi ke elemen induk. Penangan pada elemen induk akan dipicu setelah penangan pada elemen, terus sampai ke dokumen. Ini cukup baik karena Anda dapat menggunakan event.stopPropagation(), event.preventDefault(), dll untuk melewatkan penangan dan membatalkan atau un-membatalkan tindakan.

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

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

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

Atau, jika Anda tidak suka ini, Anda bisa menggunakan plugin Nick Leaches bindLast untuk memaksa suatu acara terikat terakhir: https://github.com/nickyleach/jQuery.bindLast .

Atau, jika Anda menggunakan jQuery 1.5, Anda juga bisa berpotensi melakukan sesuatu yang pintar dengan objek Ditangguhkan baru.

Daniel Lewis
sumber
6
.delegatetidak digunakan lagi dan sekarang Anda harus menggunakan .on, namun saya tidak tahu bagaimana Anda dapat mencapai perilaku yang sama denganon
eric.itzhak
plugin harus diperbaiki karena tidak berfungsi lagi lihat di sini: stackoverflow.com/questions/12214654/…
Toskan
@ eric.itzhak Untuk membuat .on () berperilaku seperti .delegate () lama, cukup berikan dengan pemilih. Detail di sini api.jquery.com/delegate
Sam Kauffman
Jika Anda ingin mengambil keuntungan dari menggelegak saya pikir acara perlu langsung daripada didelegasikan . api.jquery.com/on/#direct-and-delegated-events
Ryne Everett
15

Urutan panggilan balik terikat ini dikelola oleh data acara masing-masing objek jQuery. Tidak ada fungsi apa pun (yang saya ketahui) yang memungkinkan Anda untuk melihat dan memanipulasi data secara langsung, Anda hanya dapat menggunakan bind () dan unbind () (atau fungsi pembantu yang setara).

Metode Dowski adalah yang terbaik, Anda harus memodifikasi berbagai panggilan balik terikat untuk mengikat urutan acara kustom, dengan panggilan balik "pertama" terikat ke acara "nyata". Dengan begitu, tidak peduli dalam urutan apa mereka terikat, urutannya akan dijalankan dengan cara yang benar.

Satu-satunya alternatif yang bisa saya lihat adalah sesuatu yang Anda benar-benar tidak ingin renungkan: jika Anda tahu sintaks mengikat dari fungsi-fungsi tersebut mungkin telah terikat di depan Anda, cobalah untuk melepaskan ikatan semua fungsi-fungsi itu dan kemudian mengikatnya kembali dalam urutan yang tepat sendiri. Itu hanya meminta masalah, karena sekarang Anda memiliki kode duplikasi

Akan keren jika jQuery memungkinkan Anda untuk hanya mengubah urutan peristiwa terikat dalam data acara objek, tetapi tanpa menulis beberapa kode untuk menghubungkan ke inti jQuery yang sepertinya tidak mungkin. Dan mungkin ada implikasi dari membiarkan ini yang belum saya pikirkan, jadi mungkin itu adalah kelalaian yang disengaja.

Adam Bellaire
sumber
12
untuk melihat semua acara terikat ke suatu elemen dengan jquery gunakan var allEvents = $ .data (ini, "events");
redsquare
9

Harap dicatat bahwa di jQuery universe ini harus diimplementasikan secara berbeda pada versi 1.8. Catatan rilis berikut ini dari blog jQuery:

.data ("events"): jQuery menyimpan data terkait acara dalam objek data yang bernama (tunggu) peristiwa pada setiap elemen. Ini adalah struktur data internal sehingga pada 1.8 ini akan dihapus dari ruang nama data pengguna sehingga tidak akan bertentangan dengan item dengan nama yang sama. Data acara jQuery masih dapat diakses melalui jQuery._data (elemen, "events")

Kami memiliki kendali penuh atas urutan yang akan dieksekusi oleh penangan di jQuery universe . Ricoo menunjukkan ini di atas. Sepertinya jawabannya tidak membuatnya mendapatkan banyak cinta, tetapi teknik ini sangat berguna. Pertimbangkan, misalnya, kapan saja Anda perlu menjalankan handler Anda sendiri sebelum melakukan handler di widget library, atau Anda harus memiliki kekuatan untuk membatalkan panggilan ke handler widget secara kondisional:

$("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();
});
rattler kandang
sumber
7

hanya mengikat handler secara normal dan kemudian jalankan:

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

jadi misalnya:

$('#mydiv').data('events').click.reverse();
tomasbedrich
sumber
2
tidak bekerja, tetapi ini $._data(element, 'events')[name].reverse()berhasil
Attenzione
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;
}
robin
sumber
3

Anda dapat mencoba sesuatu seperti ini:

/**
  * 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]);
    }   
}
wildblue
sumber
Saya pikir "pemilik barang bawaan" seharusnya hanya "penangan"
Joril
1

Saya memiliki masalah yang sama dan menemukan topik ini. jawaban di atas dapat menyelesaikan masalah tersebut, tetapi saya rasa itu bukan rencana yang bagus.

mari kita berpikir tentang dunia nyata.

jika kita menggunakan jawaban itu, kita harus mengubah kode kita. Anda harus mengubah gaya kode Anda. sesuatu seperti ini:

asli:

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

retas:

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

seiring berjalannya waktu, pikirkan proyek Anda. kodenya jelek dan sulit dibaca! alasan anthoer sederhana selalu lebih baik. jika Anda memiliki 10 bindAtTheStart, mungkin tidak ada bug. jika Anda memiliki 100 bindAtTheStart, apakah Anda yakin dapat menyimpannya dengan benar?

jadi jika Anda harus mengikat beberapa acara yang sama. Saya pikir cara terbaik adalah mengontrol js-file atau js-code load order. jquery dapat menangani data acara sebagai antrian. urutannya adalah masuk pertama, keluar pertama. Anda tidak perlu mengubah kode apa pun. cukup ubah urutan muat.

9nix00
sumber
0

Inilah kesempatan saya untuk ini, mencakup berbagai versi 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;
            }
        });
    }
});
perkasa
sumber
0

Dalam beberapa kasus khusus, ketika Anda tidak dapat mengubah bagaimana acara klik diikat (binding acara dibuat dari kode orang lain), dan Anda dapat mengubah elemen HTML, berikut adalah solusi yang mungkin ( peringatan : ini bukan cara yang disarankan untuk mengikat peristiwa, pengembang lain dapat membunuh Anda untuk ini):

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

Dengan cara pengikatan ini, event hander Anda akan ditambahkan terlebih dahulu, sehingga akan dieksekusi terlebih dahulu.

Dam Linh
sumber
Itu bukan solusi, itu adalah petunjuk untuk solusi. Apakah Anda menyarankan bahwa penangan "onclick" ini berjalan pada elemen yang sudah memiliki penangan (jQuery), atau apakah <span> Anda membungkus elemen dengan penangan jQuery?
Auspex
-2

JQuery 1.5 memperkenalkan janji, dan inilah implementasi paling sederhana yang saya lihat untuk mengontrol urutan eksekusi. Dokumentasi lengkap di http://api.jquery.com/jquery.when/

$.when( $('#myDiv').css('background-color', 'red') )
 .then( alert('hi!') )
 .then( myClickFunction( $('#myID') ) )
 .then( myThingToRunAfterClick() );
Tanya Sweeney
sumber