Як замовити події, пов'язані з jQuery


164

Скажімо, у мене є веб-додаток, на якому розміщена сторінка, яка може містити 4 блоки сценаріїв - сценарій, який я пишу, може бути знайдений в одному з цих блоків, але я не знаю, який з них обробляється контролером.

Я пов'язую деякі onclickподії до кнопки, але виявляю, що вони іноді виконуються в порядку, якого я не очікував.

Чи є спосіб забезпечити порядок, або як ви вирішили цю проблему в минулому?


1
Додайте свої зворотні дзвінки до об'єкта CallBack, і коли ви хочете звільнити їх, ви можете звільнити їх одразу, і вони працюватимуть у порядку, що додається. api.jquery.com/jQuery.Callbacks
Tyddlywink

Відповіді:


28

Я впродовж віків намагався узагальнити подібний процес, але в моєму випадку я стосувався лише порядку першого слухача подій у ланцюжку.

Якщо вам це корисно, ось мій плагін jQuery, який пов'язує слухача подій, який завжди спрацьовує перед будь-якими іншими:

** ОНОВЛЕНО в inline зі змінами jQuery (спасибі Тоскан) **

(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.
Грінн

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

2
Чи можу я запитати, чому це було неприйнято? В даний час прийнята відповідь називає мою відповідь найкращим методом, тож я трохи розгублений. :-)
dowski

Це працює для справи mkoryak, але не, якщо одна і та ж подія викликається кілька разів. У мене є аналогічна проблема, коли один натискання клавіші запускає $ (window) .scroll () кілька разів у зворотному порядку.
kxsong

37

Метод Довського хороший, якщо всі ваші зворотні дзвінки завжди будуть присутніми, і ви щасливі, коли вони залежать один від одного.

Якщо ви хочете, щоб зворотні виклики не залежали один від одного, ви можете скористатись бульбашкою та приєднати наступні події як делегати батьківських елементів. Обробники батьківських елементів будуть ініційовані після обробників елемента, продовжуючи праворуч до документа. Це дуже добре , як ви можете використовувати 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, ви також можете потенційно зробити щось розумне з новим відкладеним об'єктом.


6
.delegateбільше не використовується, і тепер ви повинні використовувати .on, але я не знаю, як ви можете досягти такої ж поведінки зon
eric.itzhak

плагін повинен виправляються , так як він більше не працює побачити тут: stackoverflow.com/questions/12214654 / ...
Toskan

@ eric.itzhak Щоб змусити .on () вести себе як старий .delegate (), просто надайте йому селектор. Деталі тут api.jquery.com/delegate
Сем Кауффман

Якщо ви хочете скористатися барбусом, я думаю, що подія має бути прямою, а не делегованою . api.jquery.com/on/#direct-and-delegated-events
Everett

15

Порядок виклику пов'язаних зворотних викликів управляється даними про події кожного об'єкта jQuery. Немає жодних функцій (про які я знаю), які дозволяють вам переглядати та керувати цими даними безпосередньо, ви можете використовувати лише bind () та unbind () (або будь-яку з еквівалентних допоміжних функцій).

Найкращим є метод Довського, ви повинні модифікувати різні зв'язані зворотні виклики, щоб прив'язати до упорядкованої послідовності користувацьких подій, при цьому "перший" зворотний виклик прив'язаний до "реальної" події. Таким чином, незалежно від того, в якому порядку вони зв'язані, послідовність буде виконана правильно.

Єдиною альтернативою, яку я бачу, є те, про що ви насправді не хочете роздумувати: якщо ви знаєте, що синтаксис обов'язкових функцій, можливо, був пов’язаний перед вами, спробуйте відв'язати всі ці функції, а потім повторно пов'язати їх. в належному порядку самі. Це просто прохання про неприємності, тому що тепер у вас дублюється код.

Було б здорово, якби jQuery дозволив вам просто змінити порядок пов'язаних подій у даних подій об'єкта, але без написання якогось коду підключитися до ядра jQuery, що здається неможливим. І, мабуть, є наслідки того, щоб дозволити це, про що я не думав, тому, можливо, це навмисне упущення.


12
щоб побачити всі події, пов'язані з елементом jquery, використовуйте var allEvents = $ .data (це, "події");
переосмислити

9

Зверніть увагу, що у Всесвіті jQuery це має бути реалізовано інакше, ніж у версії 1.8. Наступна примітка до випуску - із блогу jQuery:

.data ("події"): jQuery зберігає свої пов'язані з подіями дані в об'єкті даних, названому (чекайте цього) події на кожному елементі. Це внутрішня структура даних, тому в 1.8 це буде видалено з простору імен користувача, щоб не суперечити предметам з тим самим іменем. До даних подій jQuery все ще можна отримати доступ через jQuery._data (елемент, "події")

У нас є повний контроль над порядком, в якому обробники будуть виконуватись у Всесвіті 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();
});

7

просто зв’яжіть обробник нормально і потім запустіть:

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

так наприклад:

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

2
не працює, але це $._data(element, 'events')[name].reverse()працює
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

Ви можете спробувати щось подібне:

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

Я думаю, що "власники" повинні бути просто "обробниками"
Йоріл,

1

У мене те саме питання і я знайшов цю тему. вищевказані відповіді можуть вирішити цю проблему, але я не думаю, що це гарні плани.

давайте подумаємо про реальний світ.

якщо ми використовуємо ці відповіді, нам доведеться змінити наш код. ви повинні змінити стиль свого коду. щось на зразок цього:

оригінал:

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

рубати:

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

з плином часу подумайте над своїм проектом. код некрасивий і важко читати! Анторова причина проста, завжди краще. якщо у вас є 10 bindAtTheStart, вона може не мати помилок. якщо у вас є 100 bindAtTheStart, чи справді ви впевнені, що можете підтримувати їх у правильному порядку?

тому, якщо вам доводиться прив'язувати одні й ті ж події кілька разів. Я думаю, що найкращим способом є контроль порядку завантаження js-файлу або js-коду. 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

-2

JQuery 1.5 вводить обіцянки, і ось найпростіша реалізація, яку я бачив, щоб контролювати порядок виконання. Повна документація на http://api.jquery.com/jquery.when/

$.when( $('#myDiv').css('background-color', 'red') )
 .then( alert('hi!') )
 .then( myClickFunction( $('#myID') ) )
 .then( myThingToRunAfterClick() );
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.