Cách đặt hàng các sự kiện bị ràng buộc với jQuery


164

Hãy nói rằng tôi có một ứng dụng web có một trang có thể chứa 4 khối tập lệnh - tập lệnh tôi viết có thể được tìm thấy trong một trong các khối đó, nhưng tôi không biết bộ nào được xử lý bởi bộ điều khiển.

Tôi liên kết một số onclicksự kiện với một nút, nhưng tôi thấy rằng đôi khi chúng thực hiện theo thứ tự mà tôi không mong đợi.

Có cách nào để đảm bảo trật tự, hoặc bạn đã xử lý vấn đề này như thế nào trong quá khứ?


1
Thêm các cuộc gọi lại của bạn vào một đối tượng CallBack và khi bạn muốn kích hoạt chúng, bạn có thể kích hoạt tất cả chúng cùng một lúc và chúng sẽ chạy theo thứ tự được thêm vào. api.jquery.com/jQuery.Callbacks
Tyddlywink

Câu trả lời:


28

Tôi đã cố gắng từ lâu để khái quát loại quy trình này, nhưng trong trường hợp của tôi, tôi chỉ quan tâm đến thứ tự của người nghe sự kiện đầu tiên trong chuỗi.

Nếu nó được sử dụng, đây là plugin jQuery của tôi liên kết một trình lắng nghe sự kiện luôn được kích hoạt trước bất kỳ trình duyệt nào khác:

** CẬP NHẬT nội tuyến với các thay đổi jQuery (cảm ơn 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);

Những điều cần lưu ý:

  • Điều này đã không được thử nghiệm đầy đủ.
  • Nó dựa vào phần bên trong của khung jQuery không thay đổi (chỉ được thử nghiệm với 1.5.2).
  • Nó không nhất thiết phải được kích hoạt trước các trình lắng nghe sự kiện bị ràng buộc theo bất kỳ cách nào khác ngoài thuộc tính của phần tử nguồn hoặc sử dụng jQuery bind () và các hàm liên quan khác.

Điều quan trọng cần lưu ý là điều này cũng chỉ hoạt động cho các sự kiện được thêm thông qua jQuery.
Grinn

1
điều này không còn hoạt động nữa kể từ khi nó sử dụng this.data("events"), xem tại đây stackoverflow.com/questions/12214654/
Khăn

4
Có một chức năng tương tự đã được cập nhật cho jQuery 1.8 tại đây: stackoverflow.com/a/2641047/850782
EpicVoyage 16/07/14

136

Nếu thứ tự là quan trọng, bạn có thể tạo các sự kiện của riêng mình và liên kết các cuộc gọi lại để kích hoạt khi các sự kiện đó được kích hoạt bởi các cuộc gọi lại khác.

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

Ít nhất là như thế.


23
Điều gì xảy ra nếu chúng ta muốn ngừng lan truyền sự kiện nhấp chuột cho các cuộc gọi lại được xác định tại một số điểm trước mã liên kết của chúng tôi.
vinilios

2
Tôi có thể hỏi tại sao điều này không được chấp nhận? Câu trả lời hiện được chấp nhận trích dẫn câu trả lời của tôi là phương pháp tốt nhất, vì vậy tôi hơi bối rối. :-)
dowski

Điều này hoạt động cho trường hợp của mkoryak, nhưng không phải nếu cùng một sự kiện được gọi nhiều lần. Tôi có một vấn đề tương tự, trong đó một tổ hợp phím duy nhất kích hoạt $ (window) .scroll () nhiều lần, theo thứ tự ngược lại .
kxsong

37

Phương pháp của Dowski là tốt nếu tất cả các cuộc gọi lại của bạn luôn luôn có mặt và bạn hài lòng với việc họ phụ thuộc vào nhau.

Tuy nhiên, nếu bạn muốn các cuộc gọi lại độc lập với nhau, bạn có thể tận dụng lợi thế của bong bóng và đính kèm các sự kiện tiếp theo với tư cách là đại biểu cho các phần tử cha. Các trình xử lý trên một phần tử cha sẽ được kích hoạt sau khi các trình xử lý trên phần tử đó, tiếp tục ngay đến tài liệu. Điều này khá tốt như bạn có thể sử dụng event.stopPropagation(), event.preventDefault(), vv để bỏ qua xử lý và hủy bỏ hoặc bỏ hủy bỏ hành động.

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

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

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

Hoặc, nếu bạn không thích điều này, bạn có thể sử dụng plugin bindLast của Nick Leaches để buộc một sự kiện bị ràng buộc sau cùng: https://github.com/nickyleach/jQuery.bindLast .

Hoặc, nếu bạn đang sử dụng jQuery 1.5, bạn cũng có khả năng làm điều gì đó thông minh với đối tượng Trì hoãn mới.


6
.delegatekhông được sử dụng nữa và bây giờ bạn nên sử dụng .on, nhưng tôi không biết làm thế nào bạn có thể đạt được hành vi tương tự vớion
eric.itzhak

plugin phải được sửa chữa vì nó không hoạt động nữa xem tại đây: stackoverflow.com/questions/12214654/
mẹo

@ eric.itzhak Để làm cho .on () hoạt động giống như .delegate () cũ, chỉ cần cung cấp cho nó một bộ chọn. Chi tiết tại đây api.jquery.com/delegate
Sam Kauffman

Nếu bạn muốn tận dụng bong bóng, tôi nghĩ rằng sự kiện này cần phải trực tiếp chứ không phải ủy thác . api.jquery.com/on/#direct-and-delegated-events
Ryne Everett

15

Thứ tự các cuộc gọi lại bị ràng buộc được gọi trong được quản lý bởi mỗi dữ liệu sự kiện của đối tượng jQuery. Không có bất kỳ chức năng nào (mà tôi biết) cho phép bạn xem và thao tác trực tiếp dữ liệu đó, bạn chỉ có thể sử dụng bind () và unbind () (hoặc bất kỳ chức năng trợ giúp tương đương nào).

Phương pháp của Dowski là tốt nhất, bạn nên sửa đổi các cuộc gọi lại bị ràng buộc khác nhau để liên kết với một chuỗi các sự kiện tùy chỉnh theo thứ tự, với cuộc gọi lại "đầu tiên" bị ràng buộc với sự kiện "thực". Theo cách đó, bất kể chúng bị ràng buộc theo thứ tự nào, chuỗi sẽ thực hiện đúng cách.

Sự thay thế duy nhất tôi có thể thấy là một cái gì đó bạn thực sự, thực sự không muốn suy ngẫm: nếu bạn biết cú pháp ràng buộc của các chức năng có thể đã bị ràng buộc trước bạn, hãy thử hủy liên kết tất cả các chức năng đó và sau đó liên kết lại chúng theo thứ tự thích hợp Đó chỉ là vấn đề rắc rối, vì bây giờ bạn đã sao chép mã.

Sẽ thật tuyệt nếu jQuery cho phép bạn chỉ cần thay đổi thứ tự của các sự kiện bị ràng buộc trong dữ liệu sự kiện của đối tượng, nhưng không viết một số mã để móc vào lõi jQuery mà dường như không thể. Và có lẽ có những hàm ý cho phép điều này mà tôi chưa từng nghĩ đến, nên có lẽ đó là một thiếu sót có chủ ý.


12
để xem tất cả các sự kiện được liên kết với một phần tử bằng jquery, hãy sử dụng var allEvents = $ .data (this, "event");
redsapes

9

Xin lưu ý rằng trong vũ trụ jQuery, điều này phải được thực hiện khác với phiên bản 1.8. Ghi chú phát hành sau là từ blog jQuery:

.data (các sự kiện của Nhật Bản): jQuery lưu trữ dữ liệu liên quan đến sự kiện của nó trong một đối tượng dữ liệu có tên (chờ cho nó) các sự kiện trên mỗi phần tử. Đây là cấu trúc dữ liệu nội bộ, vì vậy, trong 1.8, phần này sẽ bị xóa khỏi không gian tên dữ liệu người dùng để nó không xung đột với các mục cùng tên. Dữ liệu sự kiện của jQuery vẫn có thể được truy cập thông qua jQuery._data (phần tử, "sự kiện")

Chúng tôi có toàn quyền kiểm soát thứ tự mà các trình xử lý sẽ thực thi trong vũ trụ jQuery . Ricoo chỉ ra điều này ở trên. Không có vẻ như câu trả lời của anh ấy đã mang lại cho anh ấy rất nhiều tình yêu, nhưng kỹ thuật này rất tiện dụng. Ví dụ, hãy xem xét bất cứ lúc nào bạn cần thực hiện trình xử lý của riêng mình trước một số trình xử lý trong tiện ích thư viện hoặc bạn cần có quyền hủy cuộc gọi đến trình xử lý của tiện ích theo điều kiện:

$("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

chỉ cần ràng buộc xử lý bình thường và sau đó chạy:

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

ví dụ:

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

2
không hoạt động, nhưng điều này $._data(element, 'events')[name].reverse()hoạt động
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;
}

3

Bạn có thể thử một cái gì đó như thế này:

/**
  * 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]);
    }   
}

Tôi nghĩ rằng "chủ sở hữu" nên chỉ là "người xử lý"
Joril

1

Tôi có cùng một vấn đề và tìm thấy chủ đề này. những câu trả lời trên có thể giải quyết những vấn đề đó, nhưng tôi không nghĩ chúng là những kế hoạch tốt.

chúng ta hãy nghĩ về thế giới thực

nếu chúng ta sử dụng những câu trả lời đó, chúng ta phải thay đổi mã của mình. bạn phải thay đổi kiểu mã của bạn đại loại như thế này:

nguyên:

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

gian lận:

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

khi thời gian trôi qua, hãy nghĩ về dự án của bạn. mã này là xấu và khó đọc! Lý do anthoer là đơn giản luôn luôn tốt hơn. nếu bạn có 10 bindAtTheStart, nó có thể không có lỗi. nếu bạn có 100 bindAtTheStart, bạn có thực sự chắc chắn rằng bạn có thể giữ chúng theo đúng thứ tự không?

Vì vậy, nếu bạn phải liên kết nhiều sự kiện giống nhau. Tôi nghĩ cách tốt nhất là kiểm soát thứ tự js-file hoặc js-code. jquery có thể xử lý dữ liệu sự kiện như hàng đợi. thứ tự là nhập trước, xuất trước. bạn không cần thay đổi bất kỳ mã nào. chỉ cần thay đổi thứ tự tải.


0

Đây là ảnh của tôi về điều này, bao gồm các phiên bản khác nhau của 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

Trong một số trường hợp đặc biệt, khi bạn không thể thay đổi cách ràng buộc các sự kiện nhấp chuột (liên kết sự kiện được tạo từ mã của người khác) và bạn có thể thay đổi thành phần HTML, đây là một giải pháp khả thi ( cảnh báo : đây không phải là cách được đề xuất để liên kết sự kiện, nhà phát triển khác có thể giết bạn vì điều này):

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

Với cách ràng buộc này, trình xử lý sự kiện của bạn sẽ được thêm vào trước, vì vậy nó sẽ được thực thi trước.


Đó không phải là một giải pháp, đó là một gợi ý về một giải pháp. Bạn có gợi ý rằng trình xử lý "onclick" này đang sử dụng phần tử đã có trình xử lý (jQuery) hay <span> của bạn bao bọc phần tử bằng trình xử lý jQuery?
Auspex

-2

JQuery 1.5 giới thiệu các lời hứa và đây là cách triển khai đơn giản nhất tôi từng thấy để kiểm soát thứ tự thực hiện. Tài liệu đầy đủ tại http://api.jquery.com/jquery.when/

$.when( $('#myDiv').css('background-color', 'red') )
 .then( alert('hi!') )
 .then( myClickFunction( $('#myID') ) )
 .then( myThingToRunAfterClick() );
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.