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()
anstatttrigger()
: Müssen die Events nicht aufsteigen, dann können Sie einfachtriggerHandler()
verwenden. Dadurch läuft der gesamte Prozess schneller ab. Beachten Sie allerdings, dasstriggerHandler()
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 vonmap()
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 vonmap()
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 MethodepreventDefault()
aufgerufen hat. Dazu müssen Sie nure.isDefaultPrevented()
abfragen.
Gibt diese Methodetrue
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 injQuery.event.special
hinzu und rufen internjQuery.broadcast()
auf, wenn jemand ein Event bindet. Dies sollte mit der Prüfung auf doppelte Aufrufe verbunden werden.