2008-11-14 4 views
157

Lets sagen, ich habe eine Web-App, die eine Seite hat, die 4 Skriptblöcke enthalten kann - das Skript, das ich schreibe, kann in einem dieser Blöcke gefunden werden, aber ich weiß nicht welcher wird von der Steuerung behandelt.Wie Ereignisse mit jQuery gebunden werden

Ich binde einige onclick Ereignisse auf eine Schaltfläche, aber ich finde, dass sie manchmal in einer Reihenfolge ausführen, die ich nicht erwartet habe.

Gibt es eine Möglichkeit, die Ordnung zu gewährleisten, oder wie haben Sie dieses Problem in der Vergangenheit gelöst?

Antwort

24

Ich habe seit Ewigkeiten versucht, diese Art von Prozess zu verallgemeinern, aber in meinem Fall ging es mir nur um die Reihenfolge des ersten Ereignishörers in der Kette.

Wenn es irgend, hier ist meine jQuery-Plugin, das ein Ereignis-Listener bindet, die immer vor allen anderen ausgelöst wird:

** AKTUALISIERT inline mit jQuery Änderungen (dank 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); 

Dinge zu beachten:

  • Dies wird vollständig nicht getestet.
  • Es verlässt sich auf die Interna des jQuery-Framework nicht ändern (nur mit 1.5.2 getestet).
  • Es wird nicht unbedingt vor Ereignis-Listenern ausgelöst, die in irgendeiner Weise anders als als ein Attribut des Quellelements gebunden sind oder jQuery bind() und andere zugeordnete Funktionen verwenden.
+0

Es ist wichtig zu beachten, dass dies auch nur für Ereignisse funktioniert, die über jQuery hinzugefügt wurden. – Grinn

+1

dies funktioniert nicht mehr, da es 'this.data (" events ")' verwendet, siehe hier http://stackoverflow.com/questions/12214654/jquery-1-8-find-event-handlers – Toskan

+4

Es gibt ein ähnliche Funktion, die für jQuery 1.8 hier aktualisiert wurde: http: // stackoverflow.com/a/2641047/850782 – EpicVoyage

133

Wenn die Reihenfolge wichtig ist, können Sie eigene Ereignisse erstellen und Callbacks binden, wenn diese Ereignisse durch andere Callbacks ausgelöst werden.

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

So etwas zumindest.

+23

Was ist, wenn wir die Weitergabe des Klickereignisses für Callbacks stoppen möchten, die zu einem bestimmten Zeitpunkt vor unserem Bind-Code definiert wurden. – vinilios

+2

Kann ich fragen, warum das nicht akzeptiert wurde? Die derzeit akzeptierte Antwort zitiert meine Antwort als die beste Methode, also bin ich irgendwie verwirrt. :-) – dowski

+0

Dies funktioniert für mkoryaks Fall, aber nicht, wenn dasselbe Ereignis mehrmals aufgerufen wird. Ich habe ein ähnliches Problem, wo ein einzelner Tastendruck $ (window) .scroll() mehrfach in * umgekehrter * Reihenfolge auslöst. – kxsong

15

Die Reihenfolge, in der die gebundenen Callbacks aufgerufen werden, wird von den Ereignisdaten jedes jQuery-Objekts verwaltet. Es gibt keine Funktionen (die ich kenne), mit denen Sie diese Daten direkt anzeigen und bearbeiten können. Sie können nur bind() und unbind() (oder eine der entsprechenden Hilfsfunktionen) verwenden.

Dowskis Methode ist am besten, Sie sollten die verschiedenen gebundenen Callbacks ändern, um an eine geordnete Folge von benutzerdefinierten Ereignissen zu binden, wobei der "erste" Rückruf an das "echte" Ereignis gebunden ist. Auf diese Weise wird die Sequenz in der richtigen Reihenfolge ausgeführt, egal in welcher Reihenfolge sie gebunden sind. Die einzige Alternative, die ich sehen kann, ist etwas, das Sie wirklich, wirklich nicht betrachten möchten: Wenn Sie wissen, dass die Bindungssyntax der Funktionen vor Ihnen gebunden worden sein könnte, versuchen Sie, alle diese Funktionen zu lösen und dann binden Sie sie in der richtigen Reihenfolge selbst wieder zusammen. Das verlangt nur Ärger, denn jetzt hast du Code dupliziert.

Es wäre cool, wenn Sie mit jQuery einfach die Reihenfolge der gebundenen Ereignisse in den Ereignisdaten eines Objekts ändern könnten, ohne jedoch etwas Code in den jQuery-Kern zu schreiben, der nicht möglich erscheint. Und es gibt wahrscheinlich Implikationen dafür, das zu erlauben, woran ich nicht gedacht habe, vielleicht ist es eine absichtliche Unterlassung.

+12

, um alle von jquery an ein Element gebundenen Ereignisse anzuzeigen var allEvents = $ .data (this, "events"); – redsquare

3

Sie können so etwas wie dies versuchen:

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

Ich denke, "ownerhandlers" sollte nur "Handler" sein – Joril

+1

Dies wird nicht mehr mit der neueren Version von jQuery –

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; 
} 
+0

ich habe es als jQuery Ding umgeschrieben: https://gist.github.com/infostreams/6540654 – Edward

37

Dowski Methode, wenn alle Ihre Rückrufe gut sind immer vorhanden sein werde und Sie sind glücklich, mit ihnen aufeinander angewiesen zu sein.

Wenn Sie möchten, dass die Callbacks voneinander unabhängig sind, könnten Sie die Vorteile des Bubbling nutzen und nachfolgende Ereignisse als Delegaten an die übergeordneten Elemente anhängen. Die Handler auf einem Elternelement werden nach den Handlern auf dem Element ausgelöst und bis zum Dokument fortgesetzt. Das ist ziemlich gut, da Sie event.stopPropagation(), event.preventDefault() usw. verwenden können, um Handler zu überspringen und die Aktion abzubrechen oder die Aktion abzubrechen.

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

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

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

Oder, wenn Sie das nicht wollen, könnten Sie Nick verwenden Leaches bindLast Plugin ein Ereignis zu zwingen zuletzt gebunden zu sein: https://github.com/nickyleach/jQuery.bindLast.

Oder, wenn Sie jQuery 1.5 verwenden, könnten Sie möglicherweise auch etwas schlaues mit dem neuen verzögerten Objekt tun.

+5

'.delegate' wird nicht mehr verwendet und Sie sollten jetzt '.on' verwenden, aber ich weiß nicht, wie Sie das gleiche Verhalten mit' on' erreichen können –

+0

das Plugin muss behoben werden, da es nicht mehr funktioniert siehe hier: http : //stackoverflow.com/questions/12214654/jquery-1-8-find-event-handlers – Toskan

+0

@ eric.itzhak Um .on() sich wie das alte .delegate() zu verhalten, geben Sie es einfach mit einem Selektor an. Details hier http://api.jquery.com/delegate/ –

6

nur binden Handler normalerweise und dann laufen:

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

so zum Beispiel:

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

Dies funktioniert, sofern alle Ereignisse von jQuery hinzugefügt wurden. –

+0

nicht funktioniert und ic keine Möglichkeit, es würde funktionieren –

+1

funktioniert nicht, aber das '' '$ ._ Daten (Element, 'Ereignisse') [Name]. Reverse()' '' funktioniert – Attenzione

8

Bitte beachten Sie, dass in der jQuery Universum dies anders ab Version 1.8 umgesetzt werden muß. Die Release-Information ist von the jQuery blog:

.data („Events“): jQuery speichert seine ereignisbezogene Daten in einem Datenobjekt genannt (warten, bis es) Ereignisse auf jedem Element. Dies ist eine interne Datenstruktur , die in 1.8 aus dem Benutzerdatennamensraum entfernt wird, damit es nicht zu Konflikten mit gleichnamigen Objekten kommt. jQuery-Ereignisdaten noch über jQuery._data zugegriffen werden (Element, „Events“)

Wir haben die vollständige Kontrolle über die Reihenfolge, in der die Handler im jQuery Universum ausgeführt wird. Ricoo weist darauf hin. Sieht nicht so aus, als ob seine Antwort ihm viel Liebe eingebracht hätte, aber diese Technik ist sehr praktisch. Betrachten wir zum Beispiel, Sie jederzeit müssen Ihre eigenen Handler vor einigen Handler in einer Bibliothek Widget auszuführen, oder Sie müssen die Macht haben, den Anruf an das Widget-Handler bedingt aufzuheben:

$("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(); 
}); 
0

Hier ist mein Schuss auf diese, die verschiedene Versionen von 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; 
      } 
     }); 
    } 
}); 
-2

jQuery 1.5 verspricht einführt, und hier ist die einfachste Implementierung ich gesehen habe Reihenfolge der Ausführung zu kontrollieren.Die vollständige Dokumentation bei http://api.jquery.com/jquery.when/

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

In einigen Sonderfällen, wenn Sie nicht ändern können, wie die Click-Ereignisse gebunden sind (Ereignisbindungen sind von anderen Codes gemacht), und Sie können das HTML-Element ändern, ist hier eine mögliche Lösung (Warnung: das ist nicht die empfohlene Methode, Ereignisse zu binden, andere Entwickler können Sie für diesen Mord):

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

Mit dieser Art und Weise zu binden, wird Ihre Veranstaltung hander ersten hinzugefügt werden, so wird es zuerst ausgeführt werden.

+0

Das ist keine Lösung, es ist ein Hinweis bei einer Lösung. Schlägst du vor, dass dieser "onclick" -Handler auf das Element zugreift, das bereits einen (jQuery) -Handler hat, oder ist dein * das * Element mit dem jQuery-Handler umschließt? – Auspex

1

Ich habe dasselbe Problem und dieses Thema gefunden. Die obigen Antworten können dieses Problem lösen, aber ich denke nicht, dass es gute Pläne sind.

lassen Sie uns über die reale Welt nachdenken.

Wenn wir diese Antworten verwenden, müssen wir unseren Code ändern. Sie müssen Ihren Codestil ändern. so etwas wie dieses:

original:

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

Hack:

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

wie die Zeit vergeht, denken Sie an Ihrem Projekt. Der Code ist hässlich und schwer zu lesen! anthoer Grund ist einfach ist immer besser. Wenn Sie 10 bindAtTheStart haben, können keine Fehler auftreten. Wenn Sie 100 bindAtTheStart haben, sind Sie wirklich sicher, dass Sie sie in der richtigen Reihenfolge halten können?

also, wenn Sie gleiche Ereignisse mehrere binden müssen. Ich denke, der beste Weg ist Kontrolle js-Datei oder js-Code laden Reihenfolge. jquery kann Ereignisdaten als Warteschlange behandeln. die Reihenfolge ist first-in-first-out. Sie müssen keinen Code ändern. Ändern Sie einfach die Ladereihenfolge.