Donnerstag, 3. Februar 2011

Neue angeheftete Elemente haben keine Funktion (mehr) [Update]

Ohne Frage, jQuery ist eine JS Bibliothek die es in sich hat. Wie erinnere ich mich mit Grauen an den Aufwand in einem select-Element eine Option auszuwählen; dagegen mit jQuerys $select.val(auswahl) ein Kinderspiel.
Aber vor kurzem stolperte ich über ein Problem das mich doch ordentliche Zeit gekostet hat. Mein Ziel war an ein Element (legend) ein span-Element das mit einem click-Eventhandler ausgestattet wurde  anzuheften:
$('legend', fieldset).append(
    $('<span>').….click(tuWas)
);
Brav erschien das span-Element auch im fieldset, nur es rührte sich nicht. Nach langer Fehlersuche fand ich eine Formulierung mit der ich zum Ziel kam:
fieldset.children('legend').append(
    $('<span>').….click(tuWas)
);
Ganz klar, der Unterschied war das Zielelement und nicht das anzuheftende Element, doch wo nur war der Fehler? Nach dem beim konkreten Auftauchen keine Zeit für eine ordentliche Analyse mehr war bin ich heute Abend dem Problem auf den Grund gegangen. Im Prinzip ist auch alles ganz einfach:
  1. Aufgrund komplizierterer Verschachtelungen waren (fehlerhafterweise) bei der Ursprungsversion mehr als ein HTML-Element gespeichert: $('legend', fieldset).length > 1(!)
  2. jQuery kopiert das anzufügende Element, und heftet es an alle Objekte an, nur leider gehen dabei seine Eventhandler verloren, und zwar bei jedem angehefteten Element (und nicht wie naiv erwartet bei allen Kopien, aber nicht dem ersten (original) Objekt).
    Das passiert übrigens unabhängig davon wie ich das Objekt anhefte, also traditionell per Elternteil.append(Kind) oder in der Yoda-Notation mit Kind.appendTo(Elternteil).
  3. Selbst wenn ich das Kind-Element mit der .clone()-Methode klone bevor ich es anhefte, auch das geklonte Element hat keine Eventhandler mehr - unterscheidet sich also deutlich vom Original, so wie Dolly.
  4. Ein neuer Fall von RTFM: Die richtige verwendung der .clone()-Methode hätte geholfen; Eventhandler werden mit .clone(true) mitgeklont.
Es gibt also zwei saubere Lösungen, je nachdem welcher konkrete Fall vorliegt:
  1. Falls es möglich ist sicherzustellen, dass immer nur an ein Element andere Elemente mit Eventhandlern angeheftet werden ist das die einfachere Lösung.
  2. Wenn das nicht möglich ist so kann auf die .each() Notation zurückgegriffen werden:
    ELTERNELEMENT.each(function() {
        $(this).append(
            $(ELEMENT).....EVENTHANDLER(function(){tuWas;})
        )
    });
PS: Ich bin mir nicht sicher ob ich nicht bei jQuery einen Bugreport filen sollte...
PSS: Wen außer mir erinnert die jQuery/JS-im-Allgemeinen Schreibweise an den Kampf der Klammern bei LISP?

Kommentare:

  1. No more my dear:

    http://thenerdary.net/articles/entry/beautiful_element_creation_with_jquery

    AntwortenLöschen
  2. Hallo Tim,

    danke für deinen Hinweis. Tatsächlich lag mein (eigentlicher) Fehler jedoch nicht in der Erstellung des Elements. Es ist nämlich unerheblich ob ich $('<…>').attr({…}).$event(function(){…}) oder $('<…>…') oder $('<…>', {…}) verwende.

    Wichtig ist die Art und Weise des Klonens. Und da sollte dann wohle eher .clone(TRUE) der Standard sein (warum sollte ich ein Element klonen aber andere Eventhandler nutzen?)

    AntwortenLöschen