Benachrichtigungen erhalten, wenn jQuery-Methoden aufgerufen werden

Problem

Sie wollen eine bestimmte Aktion ausführen, wenn ein DOM-Element mit Hilfe von jQuery verändert wird. Dazu kann das Ändern eines Attributs gehören – wie zum Beispiel eine CSS-Eigenschaft – aber auch das Entfernen aus dem Dokument und so weiter.

Manche Browser bieten für solche Situationen schon Events an (sogenannte Mutation Events [21]), die unsere Anforderungen erfüllen werden, aber Sie können diese nicht Browser-übergreifend nutzen und sie sind auch nicht in jQuery integriert.

Eine andere Situation ist das Verändern der Argumente, die an jQuery-Methoden übergeben werden, bevor sie zur Ausführung gelangen. Nach dem gleichen Prinzip können Sie auch die von einer Methode zurückgegebenen Daten nach dem Ausführen der Funktion beeinflussen.

Lösung

Das hängt mit der aspektorientierten Programmierung zusammen,[22] aber hier werden wir keine Funktionen einbetten, sondern die gewünschte Methode einmal überladen und Events immer dann auslösen, wenn die Methode aufgerufen wird.

Wir brauchen ein Event, das ausgelöst wird, bevor die Funktion abläuft, um die Argumente ändern zu können. Und wir benötigen ein Event, das ausgelöst wird, nachdem die Funktion lief, um die zurückzugebenden Daten auslesen und bei Bedarf auch ändern zu können.

Lassen Sie uns schauen, wie man dies als Plugin programmieren kann. Ich werde Ihnen jeden Schritt zeigen.

Diskussion

Die gewünschte Methode überladen

Zuerst erstellen wir eine Funktion, die die jQuery-Methoden durch unsere eigene Funktion ersetzt. Ich werde sie jQuery.broadcast() nennen, Sie können den Namen natürlich anpassen:

(function($){

    $.broadcast = function(name){
        // ursprüngliche Methode sichern
        var old = $.fn[name];

        $.fn[name] = function(){
            // Broadcast
        };
    };

})(jQuery);

name muss der Methodenname sein, den wir überschreiben wollen, zum Beispiel:

jQuery.broadcast('addClass');

Ein Event vor der Ausführung auslösen

Nachdem wir unsere eigene Funktion als jQuery-Methode etabliert haben, wollen wir uns anschauen, wie wir ein Event auslösen, mit dem wir die übergebenen Argumente ändern können:

// Ein Event-Objekt erstellen
 var e = $.Event('before-'+name);
 // Argumente im Objekt sichern
 e.args = $.makeArray(arguments);
 // Das Event auslösen
 this.trigger(e);

Wenn wir addClass() »verbreiten« wollen, können wir Folgendes tun:

jQuery('body').bind('before-addClass',function(e){
  e.args[0]; // Die CSS-Klasse
});

Die ursprüngliche Methode ausführen

Jetzt wird ein Event ausgelöst, aber wir müssen immer noch die alte addClass() aufrufen. Wir werden auch die zurückgegebenen Daten im Event-Objekt speichern, so dass wir sie später bereitstellen können, wenn wir das andere Event auslösen.

e.ret = old.apply(this, e.args);

Wie Sie sehen können, übergeben wir nicht das ursprüngliche Array arguments, sondern wir nutzen die Version, die wir bereitgestellt haben, falls es schon irgendwie verändert wurde.

Ein Event nach der Ausführung auslösen

Wir haben jetzt die zurückgegebenen Daten in unserem Event-Objekt gespeichert. Nun können wir das abschließende Event auslösen und damit die externe Veränderung der zurückgegebenen Daten ermöglichen.

Wir werden das gleiche Event-Objekt verwenden, aber den Namen des Events ändern.

e.type = 'after-'+name;
this.trigger(e);

Das Ergebnis zurückgeben

Jetzt bleibt uns nur noch, die Ergebnisse zurückzugeben und mit der normalen Ausführung fortzufahren. Wir werden das zurückgeben, was wir in e.ret gesichert haben und was eventuell durch einen Eventhandler verändert wurde:

return e.ret;

Alles zusammen

Dies ist der vollständige Code:

(function($){

    $.broadcast = function(name){
        var old = $.fn[name];

        $.fn[name] = function(){
            var e = $.Event('before-'+name);

            e.args = $.makeArray(arguments);
            this.trigger(e);

            e.ret = old.apply(this, e.args);

            e.type = 'after-'+name;
            this.trigger(e);

            return e.ret;
        };
    };

})(jQuery);

Was kann man noch machen?

Ich habe versucht, das Beispiel kurz zu halten, um die Idee deutlich zu machen. Es gibt ein paar Dinge, die noch verbessert werden können – hier ein paar Ideen:

  • Nutzen Sie triggerHandler() anstatt trigger() : Müssen die Events nicht aufsteigen, dann können Sie einfach triggerHandler() verwenden. Dadurch läuft der gesamte Prozess schneller ab. Beachten Sie allerdings, dass triggerHandler() nur das Event für das erste Element der Collection auslöst.
  • Lassen Sie den Prozess für jedes Element getrennt ablaufen. Im vorigen Beispiel wird trigger() für die gesamte Collection auf einmal aufgerufen. Das ist in den meisten Fällen in Ordnung, kann aber zu unerwarteten Ergebnissen führen, wenn man dies für Collections mit mehreren Elementen nutzt.
    Sie können das, was wir in die Funktion gesteckt haben, mit einem Aufruf von map() einpacken. Damit sollte der Code einmal pro Element ausgeführt werden.
    Der Nachteil ist der, dass die Performance etwas niedriger ist und zudem durch den Aufruf von map() ein (unerwarteter) Eintrag auf dem Stack entsteht (pushStack()).
  • Erlauben Sie es externem Code, die normale Ausführung zu verhindern. Nutzen Sie jQuery 1.3 oder höher, können Sie auf die Methoden für jQuery.Event zurückgreifen.
    Sie können das Event-Objekt »fragen«, ob jemand seine Methode preventDefault() aufgerufen hat. Dazu müssen Sie nur e.isDefaultPrevented() abfragen.
    Gibt diese Methode true zurück, dann rufen Sie die ursprüngliche Funktion einfach nicht auf.
  • Vermeiden Sie mehrfaches Überladen der gleichen jQuery-Methode. Dies lässt sich leicht kontrollieren – erzeugen Sie einfach ein internes Objekt, in dem Sie dokumentieren, welche Methoden überladen wurden. Dann ignorieren Sie wiederholte Aufrufe.
  • Nutzen Sie jQuery.event.special : Dadurch müssen Sie nicht für jede Methode, die Sie überladen wollen, jQuery.broadcast() aufrufen.
    Stattdessen fügen Sie für jede Methode einen Eintrag in jQuery.event.special hinzu und rufen intern jQuery.broadcast() auf, wenn jemand ein Event bindet. Dies sollte mit der Prüfung auf doppelte Aufrufe verbunden werden.
JQuery Kochbuch
titlepage.xhtml
part0000.html
part0001.html
part0002_split_000.html
part0002_split_001.html
part0002_split_002.html
part0003_split_000.html
part0003_split_001.html
part0003_split_002.html
part0003_split_003.html
part0003_split_004.html
part0003_split_005.html
part0003_split_006.html
part0003_split_007.html
part0004_split_000.html
part0004_split_001.html
part0004_split_002.html
part0004_split_003.html
part0004_split_004.html
part0004_split_005.html
part0004_split_006.html
part0004_split_007.html
part0004_split_008.html
part0004_split_009.html
part0004_split_010.html
part0004_split_011.html
part0004_split_012.html
part0004_split_013.html
part0004_split_014.html
part0004_split_015.html
part0004_split_016.html
part0004_split_017.html
part0004_split_018.html
part0005_split_000.html
part0005_split_001.html
part0005_split_002.html
part0005_split_003.html
part0005_split_004.html
part0005_split_005.html
part0005_split_006.html
part0005_split_007.html
part0005_split_008.html
part0005_split_009.html
part0005_split_010.html
part0005_split_011.html
part0005_split_012.html
part0005_split_013.html
part0006_split_000.html
part0006_split_001.html
part0006_split_002.html
part0006_split_003.html
part0006_split_004.html
part0006_split_005.html
part0006_split_006.html
part0006_split_007.html
part0006_split_008.html
part0006_split_009.html
part0006_split_010.html
part0007_split_000.html
part0007_split_001.html
part0007_split_002.html
part0007_split_003.html
part0007_split_004.html
part0007_split_005.html
part0007_split_006.html
part0007_split_007.html
part0007_split_008.html
part0007_split_009.html
part0007_split_010.html
part0007_split_011.html
part0008_split_000.html
part0008_split_001.html
part0008_split_002.html
part0008_split_003.html
part0008_split_004.html
part0008_split_005.html
part0008_split_006.html
part0008_split_007.html
part0008_split_008.html
part0008_split_009.html
part0008_split_010.html
part0008_split_011.html
part0008_split_012.html
part0008_split_013.html
part0008_split_014.html
part0008_split_015.html
part0008_split_016.html
part0008_split_017.html
part0008_split_018.html
part0008_split_019.html
part0008_split_020.html
part0008_split_021.html
part0008_split_022.html
part0009_split_000.html
part0009_split_001.html
part0009_split_002.html
part0009_split_003.html
part0009_split_004.html
part0009_split_005.html
part0009_split_006.html
part0009_split_007.html
part0009_split_008.html
part0009_split_009.html
part0009_split_010.html
part0010_split_000.html
part0010_split_001.html
part0010_split_002.html
part0010_split_003.html
part0010_split_004.html
part0010_split_005.html
part0010_split_006.html
part0010_split_007.html
part0010_split_008.html
part0010_split_009.html
part0010_split_010.html
part0010_split_011.html
part0011_split_000.html
part0011_split_001.html
part0011_split_002.html
part0011_split_003.html
part0011_split_004.html
part0011_split_005.html
part0011_split_006.html
part0011_split_007.html
part0011_split_008.html
part0011_split_009.html
part0011_split_010.html
part0011_split_011.html
part0012_split_000.html
part0012_split_001.html
part0012_split_002.html
part0012_split_003.html
part0012_split_004.html
part0012_split_005.html
part0012_split_006.html
part0012_split_007.html
part0012_split_008.html
part0013_split_000.html
part0013_split_001.html
part0013_split_002.html
part0013_split_003.html
part0013_split_004.html
part0013_split_005.html
part0013_split_006.html
part0013_split_007.html
part0013_split_008.html
part0013_split_009.html
part0013_split_010.html
part0013_split_011.html
part0013_split_012.html
part0014_split_000.html
part0014_split_001.html
part0014_split_002.html
part0014_split_003.html
part0014_split_004.html
part0014_split_005.html
part0014_split_006.html
part0014_split_007.html
part0014_split_008.html
part0014_split_009.html
part0014_split_010.html
part0014_split_011.html
part0015_split_000.html
part0015_split_001.html
part0015_split_002.html
part0015_split_003.html
part0015_split_004.html
part0015_split_005.html
part0015_split_006.html
part0015_split_007.html
part0015_split_008.html
part0015_split_009.html
part0015_split_010.html
part0016_split_000.html
part0016_split_001.html
part0016_split_002.html
part0016_split_003.html
part0016_split_004.html
part0016_split_005.html
part0016_split_006.html
part0016_split_007.html
part0016_split_008.html
part0016_split_009.html
part0017_split_000.html
part0017_split_001.html
part0017_split_002.html
part0017_split_003.html
part0017_split_004.html
part0017_split_005.html
part0017_split_006.html
part0017_split_007.html
part0017_split_008.html
part0017_split_009.html
part0017_split_010.html
part0017_split_011.html
part0018_split_000.html
part0018_split_001.html
part0018_split_002.html
part0018_split_003.html
part0018_split_004.html
part0018_split_005.html
part0018_split_006.html
part0019_split_000.html
part0019_split_001.html
part0019_split_002.html
part0019_split_003.html
part0019_split_004.html
part0019_split_005.html
part0019_split_006.html
part0019_split_007.html
part0019_split_008.html
part0019_split_009.html
part0019_split_010.html
part0020_split_000.html
part0020_split_001.html
part0020_split_002.html
part0020_split_003.html
part0020_split_004.html
part0020_split_005.html
part0020_split_006.html
part0020_split_007.html
part0020_split_008.html
part0021_split_000.html
part0021_split_001.html
part0021_split_002.html
part0021_split_003.html
part0021_split_004.html
part0021_split_005.html
part0021_split_006.html
part0021_split_007.html
part0021_split_008.html
part0021_split_009.html
part0022.html
part0023.html
part0024.html
part0025.html