Events – Sebastians Blog https://sgaul.de Neues aus den Softwareminen Thu, 13 Mar 2014 20:34:54 +0000 de-DE hourly 1 https://wordpress.org/?v=6.1.7 https://sgaul.de/wp-content/uploads/2019/02/cropped-sgaul-2-1-32x32.jpg Events – Sebastians Blog https://sgaul.de 32 32 J-Query-Probleme mit $.event.trigger(), live() und delegate() https://sgaul.de/2011/09/19/event-trigger-live-delegate-problem/ Mon, 19 Sep 2011 17:18:13 +0000 https://sgaul.de/?p=529 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.

]]>
J-Query-Projekte mit Events strukturieren https://sgaul.de/2011/09/04/j-query-projekte-mit-events-strukturieren/ Sun, 04 Sep 2011 09:23:13 +0000 https://sgaul.de/?p=511 Sobald es sich um mehr als ein kleines Aufklappmenü handelt, können J-Query-Projekte schnell groß und somit unübersichtlich werden. Schon bevor es soweit ist, sollte man sich eine klare Struktur überlegen. Es gibt unzählige Projekte im Web und somit auch viele verschiedene Vorlagen. Viele nutzen einfache Funktionsstrukturen, einzelne oder auch mehrere Klassen. Was mir bisher kaum über den Weg lief: Mit eigenen Events bietet J-Query eine Möglichkeit, sauber und vor allem unabhängig zu modularisieren. Ganz im Stile des Observer-Patterns muss eine Datenquelle so nicht mehr wissen, wer genau an ihr interessiert ist.

Verschiedene Funktionen auf verschiedene Listener aufteilen

Als Grundlage benötigt man zunächst HTML. Dieses dient als Template, auf welches man dann mittels Javascript zugreifen kann. Hierbei sollte man beachten, überlegt mit IDs und Klassen zu arbeiten. Ist ein Event-Listener wirklich nur für ein Element notwendig, ist die ID das beste Zugriffsmittel. Klassen sind aber wichtiger: Oft ist nicht klar, wie viele Elemente eine spezielle Funktionalität haben müssen, daher sollte man für jede Funktion eine Klasse vergeben:

$('.send-value').click(function() {
    // Button-Value mittels Ajax senden
});

$('.exclusive').click(function() {
    // Andere Buttons mit Klasse exclusive deaktivieren
});

Man erlangt so einen recht gut strukturierten Code, den man einfach wiederverwenden kann. Zudem vermeidet man riesige Funktionskörper in einem einzelnen Listener, in dem man später kaum etwas wiederfinden kann.

Code nach dem Laden der Seite ausführen

Viele J-Query-Plugins bieten bereits eine recht breite Palette an Events, mit denen man arbeiten kann. Will man eigene Event-Ketten aufbauen, muss man in aller Regel nach dem Laden der Seite beginnen.

$(function() {
    // Dieser Code wird nach dem Laden der Seite ausgeführt
});

J-Query-Mobile hat hierfür noch einige andere Events, die man stattdessen nehmen sollte:

$('#page').bind('pageshow', function() {
    // Dieser Code wird nach dem Laden der Seite ausgeführt
});

Eigene Events auslösen: $.event.trigger

Oft kommt man mit den vorgegebenen Events nicht aus. Vor allem wenn man Code wiederverwenden will, muss man diesen oft in verschiedenen Situationen triggern. Ein Beispiel: Ein Listenupdate wird einmal nach dem Laden der Seite und dann immer wieder periodisch ausgeführt. Daher ist es schlecht, die Update-Funktion in die Document-Ready-Event zu packen:

$(function() {
    $.event.trigger('updatelist');
    window.setInterval(
        function() {
            $.event.trigger('updatelist');
        }, 10000);
});

$('#list').bind('updatelist', function() {
    // Das Listenupdate...
});

Die Funktion $.event.trigger ist hierbei enorm wichtig, in der J-Query-Dokumentation aber kaum zu finden. Diese spricht immer nur von $(selector).trigger(event), welche den Event auf einer konkrete Liste von gematchten Elementen auslöst. Dies ist in aller Regel aber eine denkbar schlechte Lösung, da wir eben bei dem Triggern des Events nicht wissen wollen (und sollten), wer an diesem Event interessiert ist. $.event.trigger hingegen triggert den Event auf allen Elementen, die sich dafür registriert haben.

Auf verschiedene Dateien verteilen

Da die Verteilung auf viele kleine Events zwar übersichtlicher, aber keineswegs kürzer ist, wird die entsprechende Javascript-Datei schnell sehr lang. Hier ist es daher empfehlenswert, die Funktionen irgendwie zu gruppieren und auf verschiedene Dateien aufzuteilen. Bei J-Query-Mobile hat man oft mehrere Einzel-Pages in einer Seite. Hier kann man für jede Einzelseite eine eigene Javascript-Datei erzeugen.

Anschließend kann man den JS-Code dann serverseitig wieder zusammenfassen und komprimieren, um die Netzwerklast zu minimieren.

]]>