J-Query-Probleme mit $.event.trigger(), live() und delegate()

In meinem Artikel „J-Query-Projekte mit Events strukturieren“ habe ich die Möglichkeit vorgestellt, Javascript-Events mittels J-Querys $.event.trigger(eventName) auszulösen. Man sollte sich aber bewusst sein, dass diese Methode nicht mit Elementen zusammenarbeitet, die mittels live() oder delegate() an einen Event gebunden wurden.

Bind(), live() und delegate() – der Test

Ein kleiner Test macht deutlich, dass $.event.trigger(eventName) wenig kooperativ ist:

$(function() {

    $('.class0').bind('myevent', function() {
        $(this).css('backgroundColor', '#f99');
    });

    $('.class1').live('myevent', function() {
        $(this).css('backgroundColor', '#f99');
    });

    $('body').delegate('.class2', 'myevent', function() {
        $(this).css('backgroundColor', '#f99');
    });

    $.each([0,1,2,3,4,5], function(i) {
        $('
') .appendTo('body') .text('class' + i) .addClass('class' + i); }); $('.class3').bind('myevent', function() { $(this).css('backgroundColor', '#f99'); }); $('.class4').live('myevent', function() { $(this).css('backgroundColor', '#f99'); }); $('body').delegate('.class5', 'myevent', function() { $(this).css('backgroundColor', '#f99'); }); $.event.trigger('myevent'); });

Lediglich das Element, welches nach seiner Erzeugung mittels bind() verbunden wurde, reagiert auf den Trigger:

Hintergrund

Dass lediglich Bind funktioniert, hat einen einfachen, aber sehr technischen Hintergrund.

Beim Aufruf von $(selektor).bind() sucht J-Query alle Elemente, auf die der Selektor passt und verknüpft sie mit dem entsprechenden Event-Handler. Zusätzlich wird ein Zeiger in $.event hinterlegt. Dies macht man sich bei $.event.trigger() zunutze.

Bei live() und delegate() wird hingegen gar nicht geprüft, welche Elemente auf den Selektor passen. Stattdessen wird das Event auf der fast höchsten Ebene des DOM-Baums registriert, dem Document. Klickt man nun auf ein Div im Body, wird der erzeugte Click-Event nach oben durchgereicht: Div > Body > HTML > Document. So kann jede Schicht für sich selbst auf das Event reagieren, da ein Klick auf das Div auch ein Klick auf den Body der Seite ist.

Dieses Verhalten bezeichnet man als Event-Propagation und wird von J-Query genutzt: Der Handler $(‚div‘).live(‚click‘, callback) registriert einen Klick-Eventhandler im Document. Kommt nun ein solcher Event im Document an, guckt der Handler nach, ob der Event über ein Element ging, das auf den Selektor (im Beispiel ‚div‘) passt. Falls ja, führt er auf all diesen passenden Elementen die Funktion callback aus.

Die Variante delegate() verhält sich, von ihren Kleinigkeiten abgesehen, genauso.

Diese Funktionsweise erklärt, warum Live und Delegate auch auf Elemente wirken, die zur Zeit der Festlegung noch nicht vorhanden waren: Der Selektor wird nach der Event-Auslösung, nicht zur Event-Registrierung ausgewertet.

Mögliche Lösungen

Eine wirklich gute Lösung konnte ich bisher nicht finden.

Man könnte jedem Event eine Klasse zuweisen, und statt auf $.event auf $(‚.eventklasse‘) triggern. Diese Lösung wird sehr oft vorgeschlagen. Ich hingegen finde sie nicht schön: Klassen gehören zum HTML der Seite. Sie sollten nicht für Zwecke missbraucht werden, die nur Javascript tangieren.

Derzeit versuche ich auf Live und Delegate zu verzichten. In den meisten Fällen ist dies problemlos möglich: Entweder das Element existiert bereits im DOM-Baum (steht also direkt in der HTML-Datei) oder ich registriere den Event-Handler direkt nach der Erzeugung des Elements. Für beides kann man dann einfach Bind nehmen.